Compare commits

...

19 Commits

Author SHA1 Message Date
55c88302e0 Фикс 2025-09-21 18:23:29 +03:00
0b7c8df659 miyuchiq music player gen 1 2025-09-21 17:49:00 +03:00
7e7c9644c4 miwory music player gen 1 & 2 & 3 2025-08-31 00:36:15 +03:00
ecf9933a15 miyuchiq chat gen 1 2025-08-31 00:21:27 +03:00
4f332b2e86 miyuchiq donation goal gen 1 2025-08-30 23:39:26 +03:00
b8df9275db Исправления 2025-08-30 23:22:50 +03:00
7eda03db58 Смена gen 2025-08-30 23:02:56 +03:00
5c7bb458f3 k1r4ka donation goal gen 1 2025-08-30 15:35:33 +03:00
2de130e9ff Правки 2025-08-30 04:39:01 +03:00
17ac4e94d7 k1r4ka chat gen 1 2025-08-30 04:31:43 +03:00
18cbf4ea07 Фиксы 2025-08-19 13:13:33 +03:00
bc790b6d10 donation goal hina gen 1 2025-08-18 18:10:15 +03:00
7605543f39 donation goal altrumi gen 1 2025-08-18 18:10:11 +03:00
1b4c05c1da Фиксы 2025-08-18 15:04:42 +03:00
6fa5cc22c5 sadkawaai prediction gen 2 2025-08-17 09:24:36 +03:00
28ccaa49ea sadkawaai prediction gen 1 2025-08-17 09:14:33 +03:00
5c9c53b117 Исправлен чат 2025-08-17 09:10:24 +03:00
a9f9a242ec Фиксы 2025-08-15 13:23:12 +03:00
5de5982e9e rena chat gen 1 2025-08-13 21:58:06 +03:00
44 changed files with 4554 additions and 118 deletions

View File

@ -0,0 +1,6 @@
<div id="donationgoal">
<div class="bar">
<div class="progress"></div>
</div>
<a id="name"></a>
</div>

View File

@ -0,0 +1,14 @@
let goalNode = document.getElementById('donationgoal');
let nameText = document.getElementById('name');
let rangeText = document.getElementById('range');
window.addEventListener('message', function (event) {
let message = event.detail;
let data = message.data;
let percent = ((data.value / data.goal) * 100);
if (percent > 100) {percent = 100;}
nameText.textContent = data.title;
goalNode.style.setProperty('--progress', `${percent}%`);
});

View File

@ -0,0 +1,95 @@
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap');
#widget {
display: flex;
align-items: center;
justify-content: center;
}
#donationgoal {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
font-size: 40px;
justify-content: center;
font-family: 'Nunito';
font-weight: 800;
}
.bar {
position: relative;
display: flex;
align-items: center;
width: calc(100% - 100px);
height: 100px;
background-color: #3E3250;
border-radius: 20px;
margin-top: 30px;
}
.bar::before {
position: absolute;
content: '';
width: 140px;
right: 5px;
top: -20px;
transform: translateX(50%);
aspect-ratio: 1;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg width='42' height='42' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23a)' fill='%23806E97'%3E%3Cpath d='M17.6 38.35a3.8 3.8 0 0 1-.55-.69c-1.39-2.06-.68-4.88 1.93-7.7l.24-.25c1.2-1.3 2.42-2.65 2.66-4.23a3.35 3.35 0 0 0-1.48-3.3 3.06 3.06 0 0 0-3.51.36c-.9.86-1.1 2.4-.41 3.34.27.39 1.1.64 1.84.41.65-.2 1-.7 1.04-1.46a.77.77 0 0 1 .78-.72c.41 0 .73.35.71.76a3 3 0 0 1-2.13 2.85c-1.3.4-2.8-.01-3.47-.95a4.2 4.2 0 0 1 .61-5.33 4.56 4.56 0 0 1 5.32-.58 4.84 4.84 0 0 1 2.18 4.8c-.3 2.03-1.77 3.62-3.07 5.02l-.24.24c-1 1.09-3.14 3.8-1.76 5.88.55.82 1.4 1.27 2.37 1.27 1.1 0 2.2-.6 2.89-1.56.5-.71.62-1.7.3-2.45a1.62 1.62 0 0 0-1.36-.98 1.8 1.8 0 0 0-1.6.8c-.17.26-.4.77-.07 1.33.2.35.08.8-.28 1.02a.74.74 0 0 1-1.02-.25 2.7 2.7 0 0 1 .14-2.9 3.33 3.33 0 0 1 3.04-1.47c1.15.13 2.07.8 2.52 1.83a4.14 4.14 0 0 1-.45 3.92 5.12 5.12 0 0 1-4.13 2.21 4.23 4.23 0 0 1-3.04-1.23v.01Z'/%3E%3Cpath d='M15.8 20.61a4.14 4.14 0 0 1-.42-5.23c.7-.97 2.21-1.4 3.5-1.03 1.25.36 2.02 1.4 2.07 2.8.01.4-.3.76-.73.78a.73.73 0 0 1-.77-.71c-.02-.77-.36-1.25-1-1.44-.75-.21-1.58.06-1.86.45-.7.98-.52 2.54.34 3.34a3 3 0 0 0 3.5.28 3.48 3.48 0 0 0 1.56-3.34c-.2-1.57-1.42-2.89-2.6-4.16l-.23-.24c-2.54-2.75-3.18-5.55-1.75-7.65a4.45 4.45 0 0 1 3.64-2 4.82 4.82 0 0 1 4.07 2.12c.79 1.12.92 2.66.37 3.9a3.17 3.17 0 0 1-5.57.49 2.7 2.7 0 0 1-.08-2.9.76.76 0 0 1 1.03-.27c.35.2.47.66.25 1.02a1.2 1.2 0 0 0 .05 1.32 1.68 1.68 0 0 0 2.95-.24c.34-.77.25-1.75-.24-2.45a3.45 3.45 0 0 0-2.85-1.5c-.98.03-1.83.5-2.4 1.33-1.42 2.1.67 4.77 1.64 5.83l.23.23c1.26 1.39 2.69 2.93 2.95 4.98a5.07 5.07 0 0 1-2.29 4.84 4.52 4.52 0 0 1-5.3-.46l-.08-.08h.02Z'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='a'%3E%3Cpath fill='%23fff' transform='rotate(45 10.02 25.9)' d='M0 0h28.64v29.34H0z'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E");
}
.bar::after {
position: absolute;
content: '';
width: 45px;
aspect-ratio: 1;
top: 50%;
right: -75px;
transform: translateY(-50%);
filter: drop-shadow(0 0 7.5px #E6D7FE) drop-shadow(0 0 10px #E6D7FE);
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg width='14' height='14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.32 6.18a1 1 0 0 1 0 1.64l-3.1 2.15a1 1 0 0 0-.25.25l-2.15 3.1a1 1 0 0 1-1.64 0l-2.15-3.1a1 1 0 0 0-.25-.25L.68 7.82a1 1 0 0 1 0-1.64l3.1-2.15a1 1 0 0 0 .25-.25L6.18.68a1 1 0 0 1 1.64 0l2.15 3.1a1 1 0 0 0 .25.25l3.1 2.15Z' fill='%23fff'/%3E%3C/svg%3E");
}
.progress {
position: relative;
width: 100%;
height: 30px;
background-color: #4F4062;
margin-inline: 50px;
border-radius: 25px;
}
.progress::before {
position: absolute;
content: '';
inset: 0;
border-radius: inherit;
background-color: white;
max-width: var(--progress);
transition: max-width 1.2s ease-in-out;
filter: drop-shadow(0 0 7.5px #AB8DB2) drop-shadow(0 0 10px #AB8DB2);
}
.progress::after {
position: absolute;
content: '';
width: 40px;
top: 50%;
left: var(--progress);
transform: translate(-20px, -50%);
transition: left 1.2s ease-in-out;
aspect-ratio: 15 / 20;
filter: drop-shadow(0 0 7.5px #DEB6E4) drop-shadow(0 0 10px #DEB6E4);
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg width='16' height='21' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.41 10.5a9.22 9.22 0 0 0 2-4.22 5.28 5.28 0 0 0-1.1-4.5A4.37 4.37 0 0 0 10.2.6c-1.33.24-2.7.95-3.9 2.05a4.42 4.42 0 0 0-3.59.6A4.9 4.9 0 0 0 .98 5.26a5.33 5.33 0 0 0 .44 5.24 5.25 5.25 0 0 0-.15 5.78c.53.86 1.3 1.53 2.2 1.9.9.37 1.89.44 2.82.18a8.03 8.03 0 0 0 3.91 2.05 4.36 4.36 0 0 0 4.09-1.2c1.1-1.18 1.38-2.86 1.1-4.49a9.22 9.22 0 0 0-1.99-4.22' fill='%23fff'/%3E%3C/svg%3E");
}
#name {
color: white;
margin-left: 10px;
letter-spacing: .15rem;
filter: drop-shadow(0 0 7.5px #F4DBFF) drop-shadow(0 0 10px #F4DBFF);
}

View File

@ -0,0 +1,6 @@
<div id="donationgoal">
<a id="name"></a>
<div class="bar">
<div class="progress"></div>
</div>
</div>

View File

@ -0,0 +1,14 @@
let goalNode = document.getElementById('donationgoal');
let nameText = document.getElementById('name');
let rangeText = document.getElementById('range');
window.addEventListener('message', function (event) {
let message = event.detail;
let data = message.data;
let percent = ((data.value / data.goal) * 100);
if (percent > 100) {percent = 100;}
nameText.textContent = data.title;
goalNode.style.setProperty('--progress', `${percent}%`);
});

View File

@ -0,0 +1,106 @@
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital@0;1&display=swap');
#widget {
display: flex;
align-items: center;
justify-content: center;
}
#donationgoal {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
font-size: 40px;
justify-content: center;
font-family: 'Montserrat';
font-weight: 800;
}
#donationgoal::after {
position: absolute;
content: '';
width: 120px;
bottom: 15px;
right: 10px;
aspect-ratio: 87 / 49;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='87' height='49' fill='none'%3E%3Cpath stroke='%23F4A5D2' stroke-linecap='round' stroke-width='3' d='M32.98 22.923C24.848 16.676 20.794 11.55 14.525 4.197M32.98 25.02c-8.132 6.247-12.186 11.371-18.455 18.726M33.712 23.255s8.102-.635 12.072-3.016c3.017-1.81 5.191-4.39 4.217-7.85-.67-2.382-2.946-3.819-6.63-3.606-6.219.36-9.66 13.265-9.66 13.265'/%3E%3Cpath stroke='%23F4A5D2' stroke-linecap='round' stroke-width='3' d='M33.728 21.745s4.973-6.427 5.877-10.968c.687-3.45.236-6.794-2.982-8.398-2.213-1.103-5.243-.685-7.125 2.49-3.045 5.136 3.336 16.065 3.336 16.065M33.728 26.748s4.973 6.427 5.877 10.968c.687 3.45.236 6.794-2.982 8.398-2.213 1.103-5.243.686-7.125-2.489-3.045-5.137 3.336-16.066 3.336-16.066'/%3E%3Cpath stroke='%23F4A5D2' stroke-linecap='round' stroke-width='3' d='M34.192 24.836s8.102.635 12.073 3.016c3.017 1.81 5.19 4.39 4.216 7.85-.67 2.381-2.946 3.818-6.63 3.606-6.218-.36-9.659-13.265-9.659-13.265'/%3E%3Cg filter='url(%23a)'%3E%3Cpath fill='%23fff' d='M67.635 37.843a.436.436 0 0 0 .067-.421c-.029-.083-.653-1.809-2.146-3.14 3.461-.944 6.196-4.355 6.196-8v-.03c-.005-1.047-.122-2.098-.235-3.115-.07-.634-.14-1.268-.184-1.9a.795.795 0 0 1 .079-.28.86.86 0 0 1 .255.187c.273.36.522.74.773 1.118.32.483.649.981 1.024 1.445 1.056 1.305 2.305 2.188 3.712 2.625.567.177 1 .148 1.325-.087.326-.237.489-.647.499-1.255a5.825 5.825 0 0 0-.069-.724c-.336-2.126-1.46-3.952-3.434-5.583-.696-.576-1.026-1.078-1.102-1.681-.287-2.25-1.624-3.815-4.087-4.784-.769-.304-1.559-.268-2.224.098-.686.377-1.154 1.053-1.32 1.904-.01.052-.017.104-.026.156-.16.012-.32.024-.481.025-2.728.01-4.503 1.236-5.276 3.644-.048.152-.102.222-.102.222-.02-.003-.102-.038-.215-.173-.264-.31-.47-.702-.654-1.079-.143-.292-.58-1.182-1.589-1.073-.767.083-1.248.643-1.318 1.535-.042.516-.07 1.07.01 1.612.188 1.27 1 2.152 1.772 2.847l.141.128c.386.347.783.703 1.115 1.072-.364-.331-.94-.716-1.71-.51-1.107.298-1.254 1.395-1.302 1.756-.086.643-.129 1.278-.129 1.904 0 1.193.156 2.35.466 3.46.516 1.843 1.714 3.274 3.26 4.078.064.1.125.2.18.295.28.488.57.992 1.78 2.032 1.821 1.567 4.446 1.849 4.557 1.86a.44.44 0 0 0 .392-.168Zm2.406-21.767a.828.828 0 1 1 1.655 0 .828.828 0 1 1-1.655 0Z'/%3E%3C/g%3E%3Cg filter='url(%23b)'%3E%3Ccircle cx='6' cy='24' r='2' fill='%23BEE2C7' transform='rotate(90 6 24)'/%3E%3C/g%3E%3Cg filter='url(%23c)'%3E%3Cpath fill='%23BEE2C7' d='M17.94 19c-2.542 0-3.897 2.01-5.11 3.48C12.404 23 12 23.5 12 24s.403 1 .83 1.52c1.213 1.47 2.568 3.48 5.11 3.48 2.543 0 4.346-2.75 1.902-5 2.444-2.25.64-5-1.901-5Z'/%3E%3C/g%3E%3Cdefs%3E%3Cfilter id='a' width='38' height='42' x='49' y='4.013' color-interpolation-filters='sRGB' filterUnits='userSpaceOnUse'%3E%3CfeFlood flood-opacity='0' result='BackgroundImageFix'/%3E%3CfeColorMatrix in='SourceAlpha' result='hardAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset/%3E%3CfeGaussianBlur stdDeviation='4'/%3E%3CfeComposite in2='hardAlpha' operator='out'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0'/%3E%3CfeBlend in2='BackgroundImageFix' result='effect1_dropShadow_2223_2'/%3E%3CfeBlend in='SourceGraphic' in2='effect1_dropShadow_2223_2' result='shape'/%3E%3C/filter%3E%3Cfilter id='b' width='12' height='12' x='0' y='18' color-interpolation-filters='sRGB' filterUnits='userSpaceOnUse'%3E%3CfeFlood flood-opacity='0' result='BackgroundImageFix'/%3E%3CfeColorMatrix in='SourceAlpha' result='hardAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset/%3E%3CfeGaussianBlur stdDeviation='2'/%3E%3CfeComposite in2='hardAlpha' operator='out'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0'/%3E%3CfeBlend in2='BackgroundImageFix' result='effect1_dropShadow_2223_2'/%3E%3CfeBlend in='SourceGraphic' in2='effect1_dropShadow_2223_2' result='shape'/%3E%3C/filter%3E%3Cfilter id='c' width='17' height='18' x='8' y='15' color-interpolation-filters='sRGB' filterUnits='userSpaceOnUse'%3E%3CfeFlood flood-opacity='0' result='BackgroundImageFix'/%3E%3CfeColorMatrix in='SourceAlpha' result='hardAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset/%3E%3CfeGaussianBlur stdDeviation='2'/%3E%3CfeComposite in2='hardAlpha' operator='out'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0'/%3E%3CfeBlend in2='BackgroundImageFix' result='effect1_dropShadow_2223_2'/%3E%3CfeBlend in='SourceGraphic' in2='effect1_dropShadow_2223_2' result='shape'/%3E%3C/filter%3E%3C/defs%3E%3C/svg%3E");
}
#name {
color: white;
margin-left: 50px;
margin-bottom: 15px;
font-size: 36px;
filter: drop-shadow(0 0 7.5px #FFC3E8) drop-shadow(0 0 10px #FFC3E8);
}
.bar {
position: relative;
display: flex;
align-items: center;
width: calc(100% - 100px);
height: 100px;
background-image: linear-gradient(to right, #FFC3E8, #F8EBFF 140%);
border-radius: 65px;
margin-left: 15px;
}
.bar::before {
position: absolute;
content: '';
inset: -12px;
border: 3px solid white;
border-radius: inherit;
z-index: -1;
-webkit-mask-image: linear-gradient(
to right,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 0) 75%);
}
.bar::after {
position: absolute;
content: '';
left: -19px;
width: 15px;
aspect-ratio: 11 / 12;
filter: drop-shadow(0 0 7.5px white);
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='12' fill='none'%3E%3Cpath fill='%23fff' d='M7.26 0C4.154 0 2.499 2.412 1.016 4.177.492 4.8 0 5.4 0 6c0 .6.492 1.2 1.015 1.823C2.498 9.588 4.153 12 7.261 12c3.107 0 5.31-3.3 2.323-6 2.988-2.7.784-6-2.323-6Z'/%3E%3C/svg%3E");
}
.progress {
position: relative;
width: 100%;
height: 30px;
background-color: #FFFFFF;
margin-inline: 50px;
border-radius: 25px;
}
.progress::before {
position: absolute;
content: '';
inset: 0;
border-radius: inherit;
background-color: #F4A5D2;
max-width: var(--progress);
transition: max-width 1.2s ease-in-out;
}
.progress::after {
position: absolute;
content: '';
width: 65px;
top: 50%;
left: min(var(--progress), 96.5%);
transform: translate(-20px, -50%);
transition: left 1.2s ease-in-out;
aspect-ratio: 46 / 50;
filter: drop-shadow(0 0 7.5px white);
z-index: 10;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='46' height='50' fill='none'%3E%3Cpath fill='%23A7D5B3' d='M39.243 31.057c3.668 4.695 2.79 13.985 2.39 16.989a.92.92 0 0 1-1.1.79c-2.942-.571-11.883-2.644-15.544-7.33-3.614-4.625-3.995-14.753-4.014-18.27a.936.936 0 0 1 1.199-.915c3.45.968 13.442 4.095 17.069 8.736Z'/%3E%3Cpath fill='%23A7D5B3' d='M21.015 40.845c-2.107 3.65-8.512 6.717-11.259 7.901a.93.93 0 0 1-1.31-.85c-.045-3.166.077-10.978 2.209-14.671 2.104-3.646 8.077-6.588 10.643-7.721a.926.926 0 0 1 1.309.786c.228 3.07.555 10.836-1.592 14.555Z'/%3E%3Cpath fill='%23fff' d='M28.001 32.332c-4.976-1.257-8.114-5.816-7.01-10.183 1.103-4.366 6.03-6.886 11.006-5.629 4.976 1.258 10.505 6.421 9.401 10.787-1.103 4.367-8.421 6.283-13.397 5.025Z'/%3E%3Cpath fill='%23fff' d='M18.76 13.174c4.976 1.257 8.115 5.816 7.012 10.183-1.104 4.366-6.032 6.886-11.008 5.628-4.975-1.257-10.504-6.42-9.4-10.786 1.103-4.367 8.421-6.283 13.397-5.025Z'/%3E%3Cpath fill='%23fff' d='M17.194 13.953c-1.258 4.975 1.263 9.903 5.629 11.007 4.366 1.103 8.925-2.035 10.183-7.011 1.257-4.976-.659-12.294-5.025-13.397-4.367-1.104-9.53 4.425-10.787 9.4Z'/%3E%3Cpath fill='%23fff' d='M13.802 27.373c1.257-4.976 5.816-8.114 10.183-7.01 4.366 1.103 6.886 6.03 5.628 11.006-1.257 4.976-6.42 10.505-10.787 9.401-4.366-1.103-6.282-8.421-5.024-13.397Z'/%3E%3Ccircle cx='23.52' cy='22.981' r='3.793' fill='%23F8EFD6' transform='rotate(104.184 23.52 22.981)'/%3E%3C/svg%3E");
}

View File

@ -0,0 +1,10 @@
<div id="donationgoal">
<div class="bar">
<a id="name">donate goal</a>
<div class="progress"></div>
<div class="info">
<a id="from">0</a>
<a id="to">100</a>
</div>
</div>
</div>

View File

@ -0,0 +1,18 @@
let goalNode = document.getElementById('donationgoal');
let nameText = document.getElementById('name');
let rangeText = document.getElementById('range');
let progressFrom = document.getElementById('from');
let progressTo = document.getElementById('to');
window.addEventListener('message', function (event) {
let message = event.detail;
let data = message.data;
let percent = ((data.value / data.goal) * 100);
if (percent > 100) {percent = 100;}
nameText.textContent = data.title;
progressFrom.textContent = data.value;
progressTo.textContent = data.goal;
goalNode.style.setProperty('--progress', `${percent}%`);
});

View File

@ -0,0 +1,114 @@
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital@0;1&display=swap');
#widget {
display: flex;
align-items: center;
justify-content: center;
}
#donationgoal {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
font-size: 40px;
justify-content: center;
font-family: 'Montserrat';
font-weight: 800;
}
.bar {
position: relative;
display: flex;
flex-direction: column;
margin-top: 75px;
background-position: bottom 0% right 5%;
background-size: 20%, 100%;
background-repeat: no-repeat;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='171' height='80' fill='none'%3E%3Cpath fill='url(%23a)' d='M131.467 24.596C154.218 33.954 171 87.506 171 88.971L1.72 91c-6.645-8.794 8.109-30.102 8.109-33.71 0-3.608 1.239-34.95 6.87-38.782 5.632-3.834 25.004 7.44 28.608 6.087 17.795-13.19 49.782-7.779 52.597-10.372 2.816-2.593 19.147-15.67 24.778-14.092 4.506 1.263 7.734 16.836 8.785 24.464Z' opacity='.46'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='85.5' x2='85.5' y1='0' y2='107.372' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23fff'/%3E%3Cstop offset='1' stop-color='%23FFDFD7'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E"), linear-gradient(45deg, #EF9543 -100%, #F2E4D9 100%);
border-radius: 25px;
padding: 25px 50px;
}
.bar::before {
position: absolute;
content: '';
top: 0;
left: 25px;
transform: translateY(-50%);
width: 150px;
aspect-ratio: 74 / 92;
background-size: contain;
background-repeat: no-repeat;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='74' height='92' fill='none'%3E%3Cpath stroke='%236C840A' stroke-width='4' d='M50.25 87.923c-9.147-10.149-27.82-38.02-29.34-68.32'/%3E%3Cpath fill='url(%23a)' d='M30.386 16.854c-4.786 3.099-8.509 3.13-9.772 2.758.994-2.186 3.693-6.904 6.543-8.292 2.85-1.387 8.734.907 11.32 2.228-.703-.189-3.305.208-8.091 3.306Z'/%3E%3Cpath fill='url(%23b)' d='M11.938 22.177c5.691.172 8.889-1.705 9.776-2.665-1.978-1.339-6.722-3.946-9.875-3.661-3.153.284-6.996 5.231-8.523 7.669.502-.52 2.931-1.514 8.622-1.343Z'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='26.642' x2='30.494' y1='11.604' y2='18.206' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='12.425' x2='12.544' y1='15.827' y2='23.375' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
z-index: -1;
}
.bar::after {
position: absolute;
content: '';
top: 0;
right: 35px;
height: 50px;
aspect-ratio: 178 / 25;
background-size: contain;
background-repeat: no-repeat;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='178' height='25' fill='none'%3E%3Cpath stroke='%23B84939' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M148.497.01c.888 4.204 3.931 11.819 9 8.643 5.069-3.175-3.888-7.085-9-8.643Zm0 0c-.979 4.145-4.15 11.678-8.999 8.643-6.061-3.792 8.173-8.905 8.999-8.643Zm0 0L144.64 24M148.497.01 152.446 24'/%3E%3Cpath stroke='%23F09C50' stroke-linecap='round' stroke-linejoin='round' d='M170.998.006c.508 2.452 2.246 6.894 5.143 5.042 2.896-1.852-2.222-4.133-5.143-5.042Zm0 0c-.559 2.418-2.371 6.812-5.142 5.042-3.463-2.212 4.67-5.195 5.142-5.042Zm0 0L168.795 14M170.998.006 173.255 14'/%3E%3Cpath fill='url(%23a)' d='M10.664-4.569s-3.8.796-5.488 2.11C3.17-.896 0 4.449 0 4.449s3.29.66 6.117-.755c2.826-1.414 4.547-8.262 4.547-8.262Z'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='.83' x2='9.833' y1='-.775' y2='.656' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
}
#name {
color: white;
margin-bottom: 15px;
font-size: 42px;
}
#name::after {
position: absolute;
content: '';
width: 20px;
aspect-ratio: 9 / 11;
background-size: contain;
background-repeat: no-repeat;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='11' fill='none'%3E%3Cpath fill='%23302321' d='M5.843 7.388c.46.21.343.768.48 1.255.137.487.514.825.223 1.24-.375.534-1.025-.092-1.62-.354-.624-.274-1.62-.299-1.448-.96.13-.5.624-.449 1.086-.68.462-.23.81-.715 1.28-.501ZM7.566 8.493c-.167.257-.435.38-.6.273-.164-.107-.162-.402.005-.66.166-.257.435-.379.599-.272.164.107.163.402-.004.66ZM3.833 6.894c-.071.298.026.577.216.622.19.045.403-.16.474-.458.072-.298-.025-.576-.216-.621-.19-.046-.403.159-.474.457ZM7.013 7.197c-.167.257-.435.38-.6.272-.164-.106-.162-.401.004-.659.167-.257.435-.379.6-.272.164.106.162.401-.004.659ZM5.153 6.4c-.071.298.026.577.216.622.191.046.403-.16.475-.457.071-.298-.026-.577-.216-.622-.191-.046-.403.16-.475.457ZM2.843 2.388c.46.21.343.768.48 1.255.137.487.514.825.223 1.24-.375.534-1.025-.092-1.62-.354-.624-.274-1.62-.299-1.448-.96.13-.5.624-.449 1.086-.68.462-.23.81-.715 1.28-.501ZM4.566 3.493c-.167.257-.435.38-.6.273-.164-.107-.162-.402.005-.66.166-.257.435-.379.599-.272.164.107.163.402-.004.66ZM.833 1.894c-.071.298.026.577.216.622.19.045.403-.16.474-.458.072-.298-.025-.576-.216-.621-.19-.046-.403.159-.474.457ZM4.013 2.197c-.167.257-.435.38-.6.272-.164-.106-.162-.401.004-.659.167-.257.435-.379.6-.272.164.106.162.401-.004.659ZM2.153 1.4c-.071.298.026.577.216.622.191.046.403-.16.475-.457.071-.298-.026-.577-.216-.622-.191-.046-.403.16-.475.457Z'/%3E%3C/svg%3E");
}
.progress {
position: relative;
width: 100%;
min-height: 20px;
background-image: linear-gradient(to right, #FFE3C9, #FFD6B1);
border-radius: 25px;
}
.progress::before {
position: absolute;
content: '';
inset: 0;
border-radius: inherit;
background-image: linear-gradient(to right, #B84939, #F09C50);
max-width: var(--progress);
transition: max-width 1.2s ease-in-out;
}
.info {
display: flex;
justify-content: space-between;
color: white;
font-size: 20px;
}
#to {
color: #B84939;
}
.info::after {
position: absolute;
content: '';
left: 250px;
bottom: 0;
width: 35px;
aspect-ratio: 14 / 7;
background-size: contain;
background-repeat: no-repeat;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='7' fill='none'%3E%3Cpath fill='url(%23a)' d='M13.632 10.897S11.632 6.63 9.608 5C7.204 3.065.003.808.003.808S.14 4.88 2.6 7.827c2.46 2.946 11.032 3.07 11.032 3.07Z'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='6.407' x2='7.225' y1='.333' y2='11.372' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
}

View File

@ -0,0 +1,6 @@
<div id="donationgoal">
<a id="name">donate goal</a>
<div class="bar">
<div class="progress"></div>
</div>
</div>

View File

@ -0,0 +1,14 @@
let goalNode = document.getElementById('donationgoal');
let nameText = document.getElementById('name');
let rangeText = document.getElementById('range');
window.addEventListener('message', function (event) {
let message = event.detail;
let data = message.data;
let percent = ((data.value / data.goal) * 100);
if (percent > 100) {percent = 100;}
nameText.textContent = data.title;
goalNode.style.setProperty('--progress', `${percent}%`);
});

View File

@ -0,0 +1,68 @@
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital@0;1&display=swap');
#widget {
display: flex;
align-items: center;
justify-content: center;
}
#donationgoal {
position: relative;
width: calc(100% - 50px);
display: flex;
flex-direction: column;
font-size: 40px;
justify-content: center;
font-family: 'Montserrat';
font-weight: 800;
margin-left: 50px;
}
.bar {
position: relative;
padding: 25px;
border-radius: 45px;
outline: 4px solid black;
background-image: linear-gradient(to right, #fcf3f6, #ffd0e0);
}
.bar::before {
position: absolute;
content: '';
left: -50px;
top: 50%;
transform: translateY(-50%);
width: 40px;
aspect-ratio: 1;
background-repeat: no-repeat;
background-size: contain;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' width='800' height='800' viewBox='0 0 475.528 475.528'%3E%3Cpath d='m237.376 436.245.774.976c210.94-85.154 292.221-282.553 199.331-367.706-92.899-85.154-199.331 30.953-199.331 30.953h-.774S130.936-15.639 38.045 69.515c-92.889 85.143-11.608 281.577 199.331 366.73z'/%3E%3C/svg%3E");
}
#name {
color: black;
width: fit-content;
margin-bottom: 15px;
margin-left: 50px;
font-size: 42px;
border-bottom: 2px solid black;
}
.progress {
position: relative;
width: 100%;
min-height: 50px;
background-color: #fdf4f7;
outline: 4px solid black;
border-radius: 25px;
}
.progress::before {
position: absolute;
content: '';
inset: 0;
border-radius: inherit;
background-color: #fe9dbe;
max-width: var(--progress);
transition: max-width 1.2s ease-in-out;
}

View File

@ -0,0 +1,24 @@
<div id="widget">
<div id="player">
<div class="player_background">
<img id="background_img">
</div>
<div class="data">
<div class="source">
<svg id="logo" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 30 30">
<path fill="#fff"d="M15 0a15 15 0 1 0 0 30 15 15 0 0 0 0-30ZM7.4 20.5A18.2 18.2 0 0 1 21.3 22c.2.1.3.4.4.7l-.1 1a.8.8 0 0 1-1 .2 16.4 16.4 0 0 0-13-1.2.8.8 0 0 1-.7-.6c-.2-.4.1-1.4.5-1.5ZM6.7 15a22.7 22.7 0 0 1 16.4 1.7c.2.1.4.3.4.6v.7c-.3.5-.6 1-.9 1a1 1 0 0 1-.4-.2A20.4 20.4 0 0 0 7 17.2a1 1 0 0 1-1-.6c0-.6.3-1.5.7-1.6Zm-.1-3.6h-.3c-.5 0-1-.3-1-.8-.2-.7.2-1.4.7-1.5a28 28 0 0 1 19 2l.5.7c.1.3.1.6 0 .8-.2.4-.6.8-1 .8l-.5-.1a25 25 0 0 0-17.4-2Z" />
</svg>
<a id="source"></a>
</div>
<div class="meta">
<a id="title"></a>
<a id="artist"></a>
</div>
<div id="progressbar">
<div class="background"></div>
<div class="progress"></div>
<div class="pointer"></div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,72 @@
let widgetNode = document.getElementById('widget');
let musicPlayerNode = document.getElementById('player');
let musicPlayerTitleNode = document.getElementById('title');
let musicPlayerArtistNode = document.getElementById('artist');
let musicPlayerBackgroundNode = document.getElementById('background_img');
let musicPlayerProgress = document.getElementById('progressbar');
let pausedAt = null;
let trackDuration = 0;
let trackProgress = 0;
let trackEnd = new Date();
window.addEventListener('message', function (event) {
let message = event.detail;
let data = message.data;
widgetNode.classList.add('active');
if (data == null) {
musicPlayerNode.classList.remove('active');
return;
};
if (!data.is_playing && !pausedAt) {
pausedAt = new Date();
};
if (data.is_playing) {
musicPlayerNode.classList.add('active');
pausedAt = null;
};
trackDuration = data.track.duration;
trackProgress = data.progress;
trackEnd = new Date().getTime() + data.track.duration - data.progress;
musicPlayerTitleNode.innerText = data.track.name;
musicPlayerArtistNode.innerText = data.track.artists.join(', ');
if (musicPlayerBackgroundNode.src != data.track.cover) {
musicPlayerBackgroundNode.src = data.track.cover;
};
});
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
};
async function update() {
if (pausedAt && pausedAt.getTime() + 10000 < new Date().getTime()) {
player.classList.remove('active');
await delay(100);
return update();
};
var progress;
if (pausedAt) {
progress = trackProgress / trackDuration * 100;
} else {
progress = (trackDuration - (trackEnd - new Date().getTime())) / trackDuration * 100;
};
if (progress >= 100) {
progress = 100;
};
musicPlayerProgress.style.setProperty('--progress', `${progress}%`);
await delay(100);
update();
};
update();

View File

@ -0,0 +1,139 @@
@import url(https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,900;1,900&display=swap);
#widget {
display: flex;
align-items: center;
justify-content: center;
visibility: hidden
}
#widget.active {
visibility: visible
}
#player {
position: relative;
width: 800px;
height: 320px;
overflow: hidden;
border-radius: 30px;
margin-inline: 30px;
color: #fff;
font-family: "Nunito", sans-serif;
font-weight: 900;
opacity: 0;
box-shadow: 0 0 20px 5px rgba(0, 0, 0, .5);
transition: opacity 350ms ease-in-out
}
#player.active {
opacity: 1
}
#player .player_background {
position: absolute;
inset: 0
}
#player #background_img {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
opacity: .35
}
#player .player_background::before {
position: absolute;
content: "";
inset: 0;
background-color: #1e1e1e
}
#player .data {
position: relative;
width: calc(100% - 150px);
height: calc(100% - 60px);
display: flex;
flex-direction: column;
gap: 15px;
padding: 30px 75px;
z-index: 99
}
#player .source {
display: flex;
opacity: .6;
gap: 10px
}
#player .source #logo {
width: 30px
}
#player .data .meta {
display: flex;
flex-direction: column
}
#player .data .meta #artist,
#player .data .meta #title {
word-break: keep-all;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap
}
#player .data #source {
font-size: 28px
}
#player .data #title {
font-size: 42px
}
#player .data #artist {
font-size: 32px;
opacity: .6
}
#player #progressbar {
position: relative;
width: 100%;
height: 30px;
border-radius: 25px;
margin-top: 20px;
--progress: 0%
}
#player #progressbar .background {
position: absolute;
inset: 0;
border-radius: inherit;
background-color: #d9d9d9;
opacity: .25
}
#player #progressbar .progress {
position: absolute;
inset: 0;
border-radius: inherit;
background-color: #c6d3e6;
width: 100%;
max-width: var(--progress);
transition: max-width 200ms ease-in-out
}
#player #progressbar .pointer {
position: absolute;
top: 50%;
width: 50px;
aspect-ratio: 1;
background-color: #dceaff;
border: 1px solid #737373;
border-radius: 100%;
margin-left: var(--progress);
transform: translate(-50%, -50%);
transition: margin-left 200ms ease-in-out;
z-index: 99
}

View File

@ -0,0 +1,44 @@
<div id="widget">
<div id="player">
<div class="main">
<div class="player_background">
<img id="background_img">
</div>
<div class="data">
<div class="info">
<a id="title"></a>
<a id="artist"></a>
</div>
<div class="time">
<div id="now"></div>
<div class="bars">
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
</div>
<div id="duration"></div>
</div>
</div>
</div>
<div id="progressbar">
<div class="background"></div>
</div>
</div>
</div>

View File

@ -0,0 +1,83 @@
let widgetNode = document.getElementById('widget');
let musicPlayerNode = document.getElementById('player');
let musicPlayerTitleNode = document.getElementById('title');
let musicPlayerArtistNode = document.getElementById('artist');
let musicPlayerNowNode = document.getElementById('now');
let musicPlayerDurationNode = document.getElementById('duration');
let musicPlayerBackgroundNode = document.getElementById('background_img');
let musicPlayerProgress = document.getElementById('progressbar');
let pausedAt = null;
let trackDuration = 0;
let trackProgress = 0;
let trackEnd = new Date();
window.addEventListener('message', function (event) {
let message = event.detail;
let data = message.data;
widgetNode.classList.add('active');
if (data == null) {
musicPlayerNode.classList.remove('active');
return;
};
if (!data.is_playing && !pausedAt) {
pausedAt = new Date();
};
if (data.is_playing) {
musicPlayerNode.classList.add('active');
pausedAt = null;
};
trackDuration = data.track.duration;
trackProgress = data.progress;
trackEnd = new Date().getTime() + data.track.duration - data.progress;
musicPlayerTitleNode.innerText = data.track.name;
musicPlayerArtistNode.innerText = data.track.artists.join(', ');
musicPlayerDurationNode.innerText = formatTime(trackDuration);
if (musicPlayerBackgroundNode.src != data.track.cover) {
musicPlayerBackgroundNode.src = data.track.cover;
};
});
function formatTime(milliseconds) {
let seconds = milliseconds / 1000;
let minutes = Math.floor(seconds / 60);
let secondsFormatted = (seconds % 60).toFixed(0).padStart(2, '0');
return `${minutes}:${secondsFormatted}`;
};
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
};
async function update() {
if (pausedAt && pausedAt.getTime() + 10000 < new Date().getTime()) {
player.classList.remove('active');
await delay(100);
return update();
};
var progress;
if (pausedAt) {
progress = trackProgress / trackDuration * 100;
} else {
progress = (trackDuration - (trackEnd - new Date().getTime())) / trackDuration * 100;
musicPlayerNowNode.innerText = formatTime((trackDuration - (trackEnd - new Date().getTime())));
};
if (progress >= 100) {
progress = 100;
};
musicPlayerProgress.style.setProperty('--progress', `${progress}%`);
await delay(100);
update();
};
update();

View File

@ -0,0 +1,177 @@
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap');
#widget {
display: flex;
align-items: center;
justify-content: center;
visibility: hidden;
}
#widget.active {
visibility: visible
}
#player {
display: flex;
flex-direction: column;
gap: 10px;
font-family: "Nunito", sans-serif;
opacity: 0;
margin: 5px 30px;
transition: opacity 350ms ease-in-out;
--text-color: white;
--background-color: #52363A;
--second-color: #5475af;
}
#player.active {
opacity: 1;
}
.main {
display: flex;
gap: 10px;
}
.player_background {
position: relative;
width: 200px;
height: 200px;
border-radius: 15px;
}
.player_background::before {
position: absolute;
content: '';
inset: 0;
border-radius: inherit;
left: -15px;
background-color: var(--background-color);
z-index: -1;
}
#background_img {
width: 100%;
height: 100%;
border-radius: 0 15px 15px 0;
}
.data {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 10px;
width: 500px;
}
.info {
display: flex;
flex-direction: column;
justify-content: space-evenly;
color: var(--text-color);
background-color: var(--background-color);
border-radius: 10px;
flex: 1 1 0;
padding: 15px;
}
.info a {
word-break: keep-all;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
#title {
font-size: 28px;
font-weight: 1000;
}
#artist {
font-size: 22px;
font-weight: 400;
}
.time {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 50px;
padding-inline: 15px;
border-radius: 10px;
font-weight: 800;
color: var(--text-color);
background-color: var(--background-color);
}
.bars {
display: flex;
box-sizing: border-box;
height: 100%;
padding-block: 12px;
gap: 7.5px;
}
.bar {
width: 7px;
height: 100%;
border-radius: 5px;
background: var(--second-color);
animation: bounce 1s ease-in-out infinite;
transform-origin: center;
}
.bar:nth-child(1) { animation-duration: 1.6s; animation-delay: 0s; }
.bar:nth-child(2) { animation-duration: 1.8s; animation-delay: 0.1s; }
.bar:nth-child(3) { animation-duration: 2.2s; animation-delay: 0.2s; }
.bar:nth-child(4) { animation-duration: 1.4s; animation-delay: 0.3s; }
.bar:nth-child(5) { animation-duration: 2.4s; animation-delay: 0.4s; }
.bar:nth-child(6) { animation-duration: 1.7s; animation-delay: 0.5s; }
.bar:nth-child(7) { animation-duration: 1.9s; animation-delay: 0.6s; }
.bar:nth-child(8) { animation-duration: 2.3s; animation-delay: 0.7s; }
.bar:nth-child(9) { animation-duration: 1.5s; animation-delay: 0.8s; }
.bar:nth-child(10) { animation-duration: 2.1s; animation-delay: 0.9s; }
.bar:nth-child(11) { animation-duration: 1.8s; animation-delay: 1s; }
.bar:nth-child(12) { animation-duration: 2.0s; animation-delay: 1.1s; }
.bar:nth-child(13) { animation-duration: 1.6s; animation-delay: 1.2s; }
.bar:nth-child(14) { animation-duration: 2.2s; animation-delay: 1.3s; }
.bar:nth-child(15) { animation-duration: 1.9s; animation-delay: 1.4s; }
.bar:nth-child(16) { animation-duration: 1.7s; animation-delay: 1.5s; }
.bar:nth-child(17) { animation-duration: 2.4s; animation-delay: 1.6s; }
.bar:nth-child(18) { animation-duration: 1.4s; animation-delay: 1.7s; }
.bar:nth-child(19) { animation-duration: 2.0s; animation-delay: 1.8s; }
.bar:nth-child(20) { animation-duration: 1.8s; animation-delay: 1.9s; }
#progressbar {
position: relative;
width: calc(100% + 15px);
height: 20px;
margin-left: -15px;
border-radius: 10px;
background-color: var(--background-color);
overflow: hidden;
}
#progressbar::after {
position: absolute;
content: '';
inset: 0;
border-radius: inherit;
width: 100%;
max-width: var(--progress);
background-color: var(--second-color);
transition: max-width 350ms ease-in-out;
}
@keyframes bounce {
0%, 100% {
transform: scaleY(0.3);
}
50% {
transform: scaleY(1);
}
}

View File

@ -0,0 +1,10 @@
<div id="widget">
<div id="player">
<img id="background_img">
<div class="meta">
<a id="album"></a>
<a id="title"></a>
<a id="artist"></a>
</div>
</div>
</div>

View File

@ -0,0 +1,74 @@
let widgetNode = document.getElementById('widget');
let musicPlayerNode = document.getElementById('player');
let musicPlayerTitleNode = document.getElementById('title');
let musicPlayerArtistNode = document.getElementById('artist');
let musicPlayerAlbumNode = document.getElementById('album');
let musicPlayerBackgroundNode = document.getElementById('background_img');
let musicPlayerProgress = document.getElementById('progressbar');
let pausedAt = null;
let trackDuration = 0;
let trackProgress = 0;
let trackEnd = new Date();
window.addEventListener('message', function (event) {
let message = event.detail;
let data = message.data;
widgetNode.classList.add('active');
if (data == null) {
musicPlayerNode.classList.remove('active');
return;
};
if (!data.is_playing && !pausedAt) {
pausedAt = new Date();
};
if (data.is_playing) {
musicPlayerNode.classList.add('active');
pausedAt = null;
};
trackDuration = data.track.duration;
trackProgress = data.progress;
trackEnd = new Date().getTime() + data.track.duration - data.progress;
musicPlayerTitleNode.innerText = data.track.name;
musicPlayerArtistNode.innerText = data.track.artists.join(', ');
musicPlayerAlbumNode.innerText = `album: ${data.track.album}`;
if (musicPlayerBackgroundNode.src != data.track.cover) {
musicPlayerBackgroundNode.src = data.track.cover;
};
});
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
};
async function update() {
if (pausedAt && pausedAt.getTime() + 10000 < new Date().getTime()) {
player.classList.remove('active');
await delay(100);
return update();
};
var progress;
if (pausedAt) {
progress = trackProgress / trackDuration * 100;
} else {
progress = (trackDuration - (trackEnd - new Date().getTime())) / trackDuration * 100;
};
if (progress >= 100) {
progress = 100;
};
musicPlayerProgress.style.setProperty('--progress', `${progress}%`);
await delay(100);
update();
};
update();

View File

@ -0,0 +1,66 @@
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap');
#widget {
display: flex;
align-items: center;
justify-content: center;
visibility: hidden
}
#widget.active {
visibility: visible
}
#player {
position: relative;
display: inline-flex;
width: 800px;
height: 320px;
overflow: hidden;
border-radius: 30px;
margin-inline: 30px;
color: #fff;
font-family: "Nunito", sans-serif;
opacity: 0;
background-color: #242424;
transition: opacity 350ms ease-in-out
}
#player.active {
opacity: 1
}
#player #background_img {
height: 100%;
border-radius: 30px;
}
#player .meta {
display: flex;
flex-direction: column;
width: 450px;
margin: auto;
margin-left: 30px;
}
#player .meta a {
word-break: keep-all;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
#player #album {
font-size: 32px;
color: #E47D7D;
}
#player #title {
font-size: 42px;
font-weight: 900;
}
#player #artist {
font-size: 40px;
opacity: .6;
}

View File

@ -0,0 +1,10 @@
<div id="widget">
<div id="player">
<img id="background_img">
<div class="meta">
<a id="album"></a>
<a id="title"></a>
<a id="artist"></a>
</div>
</div>
</div>

View File

@ -0,0 +1,74 @@
let widgetNode = document.getElementById('widget');
let musicPlayerNode = document.getElementById('player');
let musicPlayerTitleNode = document.getElementById('title');
let musicPlayerArtistNode = document.getElementById('artist');
let musicPlayerAlbumNode = document.getElementById('album');
let musicPlayerBackgroundNode = document.getElementById('background_img');
let musicPlayerProgress = document.getElementById('progressbar');
let pausedAt = null;
let trackDuration = 0;
let trackProgress = 0;
let trackEnd = new Date();
window.addEventListener('message', function (event) {
let message = event.detail;
let data = message.data;
widgetNode.classList.add('active');
if (data == null) {
musicPlayerNode.classList.remove('active');
return;
};
if (!data.is_playing && !pausedAt) {
pausedAt = new Date();
};
if (data.is_playing) {
musicPlayerNode.classList.add('active');
pausedAt = null;
};
trackDuration = data.track.duration;
trackProgress = data.progress;
trackEnd = new Date().getTime() + data.track.duration - data.progress;
musicPlayerTitleNode.innerText = data.track.name;
musicPlayerArtistNode.innerText = data.track.artists.join(', ');
musicPlayerAlbumNode.innerText = `album: ${data.track.album}`;
if (musicPlayerBackgroundNode.src != data.track.cover) {
musicPlayerBackgroundNode.src = data.track.cover;
};
});
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
};
async function update() {
if (pausedAt && pausedAt.getTime() + 10000 < new Date().getTime()) {
player.classList.remove('active');
await delay(100);
return update();
};
var progress;
if (pausedAt) {
progress = trackProgress / trackDuration * 100;
} else {
progress = (trackDuration - (trackEnd - new Date().getTime())) / trackDuration * 100;
};
if (progress >= 100) {
progress = 100;
};
musicPlayerProgress.style.setProperty('--progress', `${progress}%`);
await delay(100);
update();
};
update();

View File

@ -0,0 +1,66 @@
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap');
#widget {
display: flex;
align-items: center;
justify-content: center;
visibility: hidden
}
#widget.active {
visibility: visible
}
#player {
position: relative;
display: inline-flex;
width: 800px;
height: 230px;
overflow: hidden;
border-radius: 30px;
margin-inline: 30px;
color: #fff;
font-family: "Nunito", sans-serif;
opacity: 0;
background-color: #242424;
transition: opacity 350ms ease-in-out
}
#player.active {
opacity: 1
}
#player #background_img {
height: 100%;
border-radius: 30px;
}
#player .meta {
display: flex;
flex-direction: column;
width: 520px;
margin: auto;
margin-left: 30px;
}
#player .meta a {
word-break: keep-all;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
#player #album {
font-size: 32px;
color: #E47D7D;
}
#player #title {
font-size: 42px;
font-weight: 900;
}
#player #artist {
font-size: 40px;
opacity: .6;
}

View File

@ -0,0 +1,18 @@
<div id="widget">
<div id="prediction">
<div id="title"></div>
<div id="outcomes"></div>
<div class="info">
<div id="time"></div>
<div id="points"></div>
</div>
</div>
<template id="outcome">
<div class="outcome">
<p class="title"></p>
<div class="progressbar">
<a class="points"></a>
</div>
</div>
</template>
</div>

View File

@ -0,0 +1,138 @@
let widgetNode = document.getElementById('widget');
let predictionNode = document.getElementById('prediction');
let titleNode = document.getElementById('title');
let pointsNode = document.getElementById('points');
let timeNode = document.getElementById('time');
let outcomesNode = document.getElementById('outcomes');
let templateNode = document.getElementById('outcome');
let willEndAt = new Date();
let locksAt = null;
let endedAt = null;
window.addEventListener('message', function (event) {
let message = event.detail;
widgetNode.classList.add('active');
switch (message.type) {
case 'prediction.end':
if (message.status == "canceled") {
predictionNode.classList.remove('active');
predictionNode.classList.remove('ended');
} else {
predictionNode.classList.remove('locked');
predictionNode.classList.add('ended');
endedAt = new Date(message.ended_at);
let winnerNode = document.querySelector(`[outcomeId="${message.winning_outcome_id}"]`);
winnerNode.classList.add('winner');
};
break;
case 'prediction.lock':
predictionNode.classList.add('active');
locksAt = new Date();
endedAt = null;
break;
case 'prediction.begin':
case 'prediction.progress':
var totalPoints = message.outcomes.reduce((sum, outcome) => sum + (outcome.channel_points ?? 0), 0);
endedAt = null;
if (message.id != predictionNode.getAttribute('predictionId')) {
outcomesNode.innerHTML = '';
locksAt = new Date(message.ends_at);
predictionNode.setAttribute('predictionId', message.id);
titleNode.innerText = message.title;
pointsNode.innerHTML = `Поинтов: ${totalPoints}`;
predictionNode.classList.add('active');
predictionNode.classList.remove('locked');
predictionNode.classList.remove('ended');
message.outcomes.forEach(outcome => {
let root = templateNode.content.cloneNode(true);
let outcomeNode = root.querySelector('.outcome');
let title = root.querySelector('.title');
let points = root.querySelector('.points');
let outcomePoints = outcome.channel_points ?? 0;
let percent = (outcomePoints / (totalPoints || 1) * 100).toFixed(2);
outcomeNode.setAttribute('outcomeId', outcome.id);
outcomeNode.style.setProperty('--progress', `${percent}%`);
title.innerText = outcome.title;
points.innerText = `${percent}%`;
outcomesNode.appendChild(root);
});
} else {
pointsNode.innerHTML = `Поинтов: ${totalPoints}`;
message.outcomes.forEach(outcome => {
let outcomeNode = document.querySelector(`[outcomeId="${outcome.id}"]`);
let title = outcomeNode.querySelector('.title');
let points = outcomeNode.querySelector('.points');
let outcomePoints = outcome.channel_points ?? 0;
let percent = (outcomePoints / (totalPoints || 1) * 100).toFixed(2);
outcomeNode.style.setProperty('--progress', `${percent}%`);
title.innerText = outcome.title;
points.innerText = `${percent}%`;
});
};
locksAt = new Date(message.locks_at);
break;
default: break;
};
});
function convertSecondsToMMSS(seconds) {
seconds = Math.round(seconds);
if (seconds <= 0) {
seconds = 0;
};
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
const formattedMinutes = String(minutes).padStart(2, '0');
const formattedSeconds = String(secs).padStart(2, '0');
return `${formattedMinutes}м:${formattedSeconds}с`;
};
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
};
async function updateTimer() {
let now = new Date().getTime();
let left = (locksAt - now) / 1000;
timeNode.innerHTML = `Осталось: ${convertSecondsToMMSS(left)}`;
if (!endedAt && left <= 0) {
timeNode.innerHTML = `Ожидание результата`;
predictionNode.classList.add('locked');
};
left = (now - endedAt) / 1000;
if (endedAt) {
timeNode.innerHTML = `Закончилось`;
predictionNode.classList.remove('locked');
if (left > 5) {
predictionNode.classList.remove('active');
predictionNode.classList.remove('ended');
};
};
await delay(100);
updateTimer();
};
updateTimer();

View File

@ -0,0 +1,133 @@
@font-face {
font-family: 'SF Pro Rounded';
src: url('https://raw.githubusercontent.com/chris-short/apple-san-francisco-pro-fonts/refs/heads/main/SF-Pro-Rounded-Black.otf') format('opentype');
font-weight: normal;
font-style: normal;
display: swap;
}
#widget {
display: flex;
align-items: center;
justify-content: center;
visibility: hidden;
}
#widget.active {
visibility: visible;
}
#prediction {
color: white;
margin-top: 70px;
padding: 20px 30px;
width: 900px;
border-radius: 50px;
font-family: 'SF Pro Rounded';
font-weight: bolder;
letter-spacing: .1rem;
background-color: transparent;
transition: opacity 500ms ease-out;
font-size: 40px;
opacity: 0;
filter: drop-shadow(0 0 5px rgba(0, 0, 0, .55)) drop-shadow(0 0 8px rgba(0, 0, 0, .3));
}
#prediction::before {
position: absolute;
content: '';
inset: 0;
opacity: .7;
border-radius: inherit;
background: #D4A8B9;
z-index: -1;
}
#prediction.active,
#prediction.ended
{
opacity: 1;
}
#prediction.locked {
opacity: 0!important;
}
#prediction .info {
display: flex;
justify-content: space-evenly;
align-items: center;
font-size: 25px;
padding-block: 15px;
}
#prediction #title {
word-wrap: break-word;
word-break: break-all;
text-align: center;
padding-inline: 50px;
}
#prediction .info #points, #prediction .info #time {
text-align: center;
max-width: fit-content;
}
#prediction #outcomes {
display: flex;
flex-direction: column;
padding-block: 15px;
padding-bottom: 25px;
font-size: 24px;
gap: 25px;
}
#prediction #outcomes .outcome {
transition: filter 300ms ease-out;
}
#prediction.ended #outcomes .outcome {
filter: brightness(50%);
}
#prediction.ended #outcomes .outcome.winner {
filter: brightness(125%);
}
#prediction #outcomes .outcome .title {
font-size: 25px;
}
#prediction #outcomes .outcome .progressbar {
position: relative;
width: calc(100% - 130px);
height: 18px;
border-radius: 25px;
}
#prediction #outcomes .outcome .progressbar::before {
position: absolute;
content: '';
inset: 0;
border-radius: inherit;
background-color: #D9D9D9;
opacity: 0.3;
}
#prediction #outcomes .outcome .progressbar::after {
position: absolute;
content: '';
inset: 0;
width: 100%;
max-width: var(--progress);
transition: max-width 1.5s ease-in-out;
border-radius: inherit;
background-color: #E18E85;
}
#prediction #outcomes .outcome .progressbar .points {
position: absolute;
top: 0;
right: -130px;
transform: translateY(-20%);
}

View File

@ -0,0 +1,18 @@
<div id="widget">
<div id="prediction">
<div id="title"></div>
<div id="outcomes"></div>
<div class="info">
<div id="time"></div>
<div id="points"></div>
</div>
</div>
<template id="outcome">
<div class="outcome">
<p class="title"></p>
<div class="progressbar">
<a class="points"></a>
</div>
</div>
</template>
</div>

View File

@ -0,0 +1,138 @@
let widgetNode = document.getElementById('widget');
let predictionNode = document.getElementById('prediction');
let titleNode = document.getElementById('title');
let pointsNode = document.getElementById('points');
let timeNode = document.getElementById('time');
let outcomesNode = document.getElementById('outcomes');
let templateNode = document.getElementById('outcome');
let willEndAt = new Date();
let locksAt = null;
let endedAt = null;
window.addEventListener('message', function (event) {
let message = event.detail;
widgetNode.classList.add('active');
switch (message.type) {
case 'prediction.end':
if (message.status == "canceled") {
predictionNode.classList.remove('active');
predictionNode.classList.remove('ended');
} else {
predictionNode.classList.remove('locked');
predictionNode.classList.add('ended');
endedAt = new Date(message.ended_at);
let winnerNode = document.querySelector(`[outcomeId="${message.winning_outcome_id}"]`);
winnerNode.classList.add('winner');
};
break;
case 'prediction.lock':
predictionNode.classList.add('active');
locksAt = new Date();
endedAt = null;
break;
case 'prediction.begin':
case 'prediction.progress':
var totalPoints = message.outcomes.reduce((sum, outcome) => sum + (outcome.channel_points ?? 0), 0);
endedAt = null;
if (message.id != predictionNode.getAttribute('predictionId')) {
outcomesNode.innerHTML = '';
locksAt = new Date(message.ends_at);
predictionNode.setAttribute('predictionId', message.id);
titleNode.innerText = message.title;
pointsNode.innerHTML = `Поинтов: ${totalPoints}`;
predictionNode.classList.add('active');
predictionNode.classList.remove('locked');
predictionNode.classList.remove('ended');
message.outcomes.forEach(outcome => {
let root = templateNode.content.cloneNode(true);
let outcomeNode = root.querySelector('.outcome');
let title = root.querySelector('.title');
let points = root.querySelector('.points');
let outcomePoints = outcome.channel_points ?? 0;
let percent = (outcomePoints / (totalPoints || 1) * 100).toFixed(2);
outcomeNode.setAttribute('outcomeId', outcome.id);
outcomeNode.style.setProperty('--progress', `${percent}%`);
title.innerText = outcome.title;
points.innerText = `${percent}%`;
outcomesNode.appendChild(root);
});
} else {
pointsNode.innerHTML = `Поинтов: ${totalPoints}`;
message.outcomes.forEach(outcome => {
let outcomeNode = document.querySelector(`[outcomeId="${outcome.id}"]`);
let title = outcomeNode.querySelector('.title');
let points = outcomeNode.querySelector('.points');
let outcomePoints = outcome.channel_points ?? 0;
let percent = (outcomePoints / (totalPoints || 1) * 100).toFixed(2);
outcomeNode.style.setProperty('--progress', `${percent}%`);
title.innerText = outcome.title;
points.innerText = `${percent}%`;
});
};
locksAt = new Date(message.locks_at);
break;
default: break;
};
});
function convertSecondsToMMSS(seconds) {
seconds = Math.round(seconds);
if (seconds <= 0) {
seconds = 0;
};
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
const formattedMinutes = String(minutes).padStart(2, '0');
const formattedSeconds = String(secs).padStart(2, '0');
return `${formattedMinutes}м:${formattedSeconds}с`;
};
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
};
async function updateTimer() {
let now = new Date().getTime();
let left = (locksAt - now) / 1000;
timeNode.innerHTML = `Осталось: ${convertSecondsToMMSS(left)}`;
if (!endedAt && left <= 0) {
timeNode.innerHTML = `Ожидание результата`;
predictionNode.classList.add('locked');
};
left = (now - endedAt) / 1000;
if (endedAt) {
timeNode.innerHTML = `Закончилось`;
predictionNode.classList.remove('locked');
if (left > 5) {
predictionNode.classList.remove('active');
predictionNode.classList.remove('ended');
};
};
await delay(100);
updateTimer();
};
updateTimer();

View File

@ -0,0 +1,148 @@
@font-face {
font-family: 'SF Pro Rounded';
src: url('https://raw.githubusercontent.com/chris-short/apple-san-francisco-pro-fonts/refs/heads/main/SF-Pro-Rounded-Black.otf') format('opentype');
font-weight: normal;
font-style: normal;
display: swap;
}
#widget {
display: flex;
align-items: center;
justify-content: center;
visibility: hidden;
}
#widget.active {
visibility: visible;
}
#prediction {
position: relative;
color: #513E80;
margin-top: 70px;
margin-left: 50px;
padding: 20px 30px;
width: 900px;
border-radius: 50px;
font-family: 'SF Pro Rounded';
font-weight: bolder;
letter-spacing: .1rem;
background-color: transparent;
transition: opacity 500ms ease-out;
font-size: 40px;
opacity: 0;
filter: drop-shadow(0 0 5px rgba(0, 0, 0, .55)) drop-shadow(0 0 8px rgba(0, 0, 0, .3));
}
#prediction::before {
position: absolute;
content: '';
inset: 0;
border-radius: inherit;
background: linear-gradient(to right, #FEEDFE, #D3CBFE);
z-index: -1;
}
#prediction::after {
position: absolute;
content: '';
top: 0;
left: 0;
width: 75px;
transform: translate(-50%, -50%);
z-index: -3;
aspect-ratio: 89 / 92;
background-repeat: no-repeat;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='89' height='92' fill='none'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-width='4' d='M86.145 56.418S70.838 70.01 57.692 75.508c-11.473 4.8-28.311 11.993-28.311 11.993'/%3E%3Cpath fill='%23fff' d='M77.68 4.918s4.478 26.592 1.276 43.935c-3.202 17.344-15.394 21.552-25.381 35.393 0 0-6.983-7.52-9.358-13.515-3.254-8.214-4.643-11.614-2.382-20.143 4.98-18.783 35.845-45.67 35.845-45.67Z'/%3E%3Cpath fill='%23fff' d='M.58 68.138s17.822-4.301 32.316-4.866c5.471-.213 9.071 2.652 13.662 6.374 3.901 3.163 8.494 8.678 8.494 8.678s-9.933 8.055-16.627 8.617c-5.5.461-8.797-.924-13.766-3.202C14.48 79.073.58 68.139.58 68.139Z'/%3E%3C/svg%3E");
}
#prediction.active,
#prediction.ended
{
opacity: 1;
}
#prediction.locked {
opacity: 0!important;
}
#prediction .info {
display: flex;
justify-content: space-evenly;
align-items: center;
font-size: 25px;
padding-block: 15px;
}
#prediction #title {
word-wrap: break-word;
word-break: break-all;
text-align: center;
padding-inline: 50px;
}
#prediction .info #points, #prediction .info #time {
text-align: center;
max-width: fit-content;
}
#prediction #outcomes {
display: flex;
flex-direction: column;
padding-block: 15px;
padding-bottom: 25px;
font-size: 24px;
gap: 25px;
}
#prediction #outcomes .outcome {
transition: filter 300ms ease-out;
}
#prediction.ended #outcomes .outcome {
filter: brightness(50%);
}
#prediction.ended #outcomes .outcome.winner {
filter: brightness(125%);
}
#prediction #outcomes .outcome .title {
font-size: 25px;
}
#prediction #outcomes .outcome .progressbar {
position: relative;
width: calc(100% - 130px);
height: 18px;
border-radius: 25px;
}
#prediction #outcomes .outcome .progressbar::before {
position: absolute;
content: '';
inset: 0;
border-radius: inherit;
background-color: #513E80;
opacity: 0.3;
}
#prediction #outcomes .outcome .progressbar::after {
position: absolute;
content: '';
inset: 0;
width: 100%;
max-width: var(--progress);
transition: max-width 1.5s ease-in-out;
border-radius: inherit;
background-color: #513E80;
}
#prediction #outcomes .outcome .progressbar .points {
position: absolute;
top: 0;
right: -130px;
transform: translateY(-20%);
}

View File

@ -153,7 +153,7 @@ async function commandUSERNOTICE(details) {
type = 'gift';
break;
case 'raid':
content = `зарейдил наш канал с ${viewerCount} людьми`;
content = `зарейдил наш канал с ${viewerCount} людьми!`;
type = 'raid';
break;
default:
@ -456,14 +456,15 @@ async function fixEmotesPadding($messageElement, $parentElement, details, functi
let $contentElement = $messageElement.find('content').eq(0);
$contentElement.find('.emote').each(function () {
let previousSibling = this.previousSibling.nodeValue;
let nextSibling = this.nextSibling.nodeValue;
let $emote = $(this).eq(0);
let $previousEmote = $emote.prev('span.emote').eq(0);
let $nextEmote = $emote.next('span.emote').eq(0);
if (previousSibling && previousSibling.trim() === '') {
if ($previousEmote.length) {
this.classList.add('emote-left');
};
if (nextSibling && nextSibling.trim() === '') {
if ($nextEmote.length) {
this.classList.add('emote-right');
};
});

View File

@ -8,9 +8,9 @@
--font-family-1: "Montserrat", serif;
/* Emotes */
--emote-size: calc(var(--font-size) * 2);
--emote-size-xl: calc(var(--font-size) * 4);
--emote-size-xxl: calc(var(--font-size) * 8);
--emote-size: 100px;
--emote-size-xl: calc(var(--emote-size) * 2);
--emote-size-xxl: calc(var(--emote-size-xl) * 2);
/* Badges */
--badge-size: 25px;
@ -60,11 +60,11 @@ content {
}
.emote.emote-left {
margin-left: -5.5px;
margin-left: 0;
}
.emote.emote-right {
margin-right: -5.5px;
margin-right: 0;
}
.emote img {
@ -171,40 +171,60 @@ message.broadcaster {
--primary: var(--broadcaster-primary);
}
message top author {
message top {
position: relative;
margin-left: 65px;
color: var(--color-1);
filter: drop-shadow(0 0 5px var(--primary)) drop-shadow(0 0 10px var(--primary));
}
message top author::before {
message top::before {
position: absolute;
content: '';
left: -35px;
left: 25px;
top: 50%;
transform: translateY(-50%);
width: 25px;
aspect-ratio: 31 / 27;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='31' height='27' fill='none'%3E%3Cpath fill='%23E3BBF9' d='m1.644.006 9.7 7.297h8.314L29.356.006s4.498 15.975-1.386 21.892c-3.712 3.734-7.337 5.108-12.47 5.108-5.133 0-8.758-1.374-12.47-5.108C-2.854 15.98 1.644.006 1.644.006Z'/%3E%3C/svg%3E");
filter: drop-shadow(0 0 5px #E3BBF9);
}
message.vip top author::before {
message.vip top::before {
aspect-ratio: 31 / 31;
filter: drop-shadow(0 0 5px #FFC0E4);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='31' height='31' fill='none'%3E%3Cpath fill='%23FFC0E4' d='M28.015 7.746c-1.179-.575-3.9-1.232-7.502 2.231 1.878-5.246-.348-7.822-1.223-8.596A5.882 5.882 0 0 0 15.517.014c-1.38 0-2.717.484-3.773 1.367-.892.774-3.128 3.35-1.24 8.596-3.618-3.463-6.34-2.806-7.518-2.231C.529 8.95-.65 12.236.362 15.089c.4 1.123 1.95 4.168 7.026 4.367-3.745 1.885-3.907 4.339-3.735 5.495.427 2.875 3.687 5.277 6.826 5.047 1.292-.092 3.91-.787 5.02-4.684 1.11 3.898 3.728 4.592 5.02 4.684h.42c3.009 0 5.996-2.32 6.406-5.064.172-1.157 0-3.61-3.735-5.495 5.075-.202 6.626-3.244 7.026-4.367 1.013-2.837-.166-6.122-2.622-7.326Z'/%3E%3C/svg%3E");
}
message.moderator top author::before {
message.moderator top::before {
aspect-ratio: 25 / 29;
filter: drop-shadow(0 0 5px #CFFFA4);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='25' height='29' fill='none'%3E%3Cpath fill='%23CFFFA4' d='M.41.002s12.633 3.38 14.925 8.704c2.186 5.076-1.72 13.296-1.72 13.296S4.113 18.839 1.57 13.79C-1.06 8.56.41.002.41.002Z'/%3E%3Cpath fill='%23CFFFA4' d='M24.667 3.002S14.403 5.767 12.54 10.124c-1.776 4.153 1.398 10.878 1.398 10.878s7.72-2.588 9.785-6.719c2.139-4.278.944-11.281.944-11.281Z'/%3E%3Cpath stroke='%23D2FDAD' stroke-linecap='round' stroke-width='2' d='M13 19.002v9'/%3E%3C/svg%3E");
}
message.broadcaster top author::before {
message.broadcaster top::before {
aspect-ratio: 32 / 29;
filter: drop-shadow(0 0 5px #707070);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='29' fill='none'%3E%3Cpath fill='%23707070' d='M23.288.004c5.291 0 8.538 4.477 8.538 8.15 0 7.94-9.328 15.311-15.913 20.004C9.328 23.465 0 16.095 0 8.154 0 4.48 3.247.004 8.54.004c2.955 0 5.48 2.28 7.374 4.445 1.895-2.165 4.42-4.445 7.375-4.445Z'/%3E%3C/svg%3E");
}
message top author {
position: relative;
margin-left: 65px;
color: var(--color-1);
filter: drop-shadow(0 0 5px #E3BBF9) drop-shadow(0 0 10px #E3BBF9);
}
message.vip top author {
filter: drop-shadow(0 0 5px #FFC0E4) drop-shadow(0 0 10px #FFC0E4);
}
message.moderator top author {
filter: drop-shadow(0 0 5px #CFFFA4) drop-shadow(0 0 10px #CFFFA4);
}
message.broadcaster top author {
filter: drop-shadow(0 0 5px #FFFFFF) drop-shadow(0 0 10px #FFFFFF);
}
message bottom {
position: relative;
border-radius: 40px;
@ -229,8 +249,8 @@ message bottom::before {
message bottom::after {
position: absolute;
content: '';
top: -3px;
right: 0;
top: -6px;
right: 4px;
width: 75px;
transform: translateX(30%);
aspect-ratio: 100 / 160;
@ -242,7 +262,7 @@ message bottom content {
color: var(--primary);
background: linear-gradient(to bottom, transparent calc(100% - 20px), var(--primary) calc(100% - 19.99px), var(--primary)), var(--bg);
border-radius: inherit;
padding: 55px 35px;
padding: 45px 35px 55px;
}
message bottom footer {
@ -263,11 +283,23 @@ message bottom footer::before {
transform: translateY(-50%);
height: 40px;
aspect-ratio: 111 / 40;
filter: drop-shadow(0 0 5px var(--primary));
filter: drop-shadow(0 0 5px #E0BAF5);
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='111' height='40' fill='none'%3E%3Cpath fill='%23fff' d='m95.5.516.73 5.77c.867 6.857 6.196 12.334 13.095 13.457l1.675.273-1.675.273c-6.899 1.123-12.228 6.6-13.095 13.457l-.73 5.77-.73-5.77c-.867-6.858-6.196-12.334-13.095-13.457L80 20.016l1.675-.273c6.9-1.123 12.228-6.6 13.095-13.457l.73-5.77ZM55.5.516l.73 5.77c.867 6.857 6.196 12.334 13.095 13.457l1.675.273-1.675.273c-6.9 1.123-12.228 6.6-13.095 13.457l-.73 5.77-.73-5.77c-.867-6.858-6.196-12.334-13.095-13.457L40 20.016l1.675-.273c6.9-1.123 12.228-6.6 13.095-13.457l.73-5.77ZM15.5 1.006l.73 5.77c.867 6.858 6.196 12.334 13.095 13.457l1.675.273-1.675.273c-6.9 1.123-12.228 6.6-13.095 13.457l-.73 5.77-.73-5.77C13.903 27.38 8.574 21.902 1.675 20.78L0 20.506l1.675-.273c6.9-1.123 12.228-6.6 13.095-13.457l.73-5.77Z'/%3E%3C/svg%3E");
}
message.vip bottom footer::before {
filter: drop-shadow(0 0 5px #FFC0E4);
}
message.moderator bottom footer::before {
filter: drop-shadow(0 0 5px #CEFFA4);
}
message.broadcaster bottom footer::before {
filter: drop-shadow(0 0 5px #AFAFAF);
}
message bottom footer::after {
position: absolute;
content: '';
@ -292,7 +324,7 @@ event content {
color: var(--color-1);
text-shadow: 0 0 10px white;
background: linear-gradient(to bottom, transparent calc(100% - 20px), #B5B5B5 calc(100% - 19.99px), #B5B5B5), linear-gradient(to right, #464646 50%, #282828 120%);
border-radius: 65px;
border-radius: 85px;
padding: 35px 75px 55px;
}
@ -305,6 +337,7 @@ event content::before {
transform: translate(-50%, -100%);
height: 70px;
aspect-ratio: 462 / 89;
filter: drop-shadow(0 0 5px white);
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='462' height='89' fill='none'%3E%3Cpath fill='url(%23a)' d='M8.21 29.231C13.295 16.881 26 .014 26 .014s-4.226 22.373-4.238 36.863C21.75 51.474 26 74.014 26 74.014H0S1.442 45.677 8.21 29.23Z'/%3E%3Cpath fill='url(%23b)' d='M453.789 29.231C448.706 16.881 436 .014 436 .014s4.226 22.373 4.238 36.863C440.25 51.474 436 74.014 436 74.014h26s-1.442-28.337-8.211-44.783Z'/%3E%3Cpath fill='%23fff' d='M121.002 47.014h222v10h-222z'/%3E%3Ccircle cx='395.813' cy='66.2' r='22.186' fill='%23fff' transform='rotate(90 395.813 66.2)'/%3E%3Ccircle cx='313.409' cy='47.2' r='22.186' fill='%23fff' transform='rotate(90 313.409 47.2)'/%3E%3Ccircle cx='356.186' cy='54.2' r='22.186' fill='%23fff' transform='rotate(90 356.186 54.2)'/%3E%3Ccircle cx='272.202' cy='43.2' r='22.186' fill='%23fff' transform='rotate(90 272.202 43.2)'/%3E%3Ccircle cx='189.792' cy='43.2' r='22.186' fill='%23fff' transform='rotate(90 189.792 43.2)'/%3E%3Ccircle cx='230.999' cy='40.2' r='22.186' fill='%23fff' transform='rotate(90 230.999 40.2)'/%3E%3Ccircle cx='148.589' cy='47.2' r='22.186' fill='%23fff' transform='rotate(90 148.589 47.2)'/%3E%3Ccircle cx='67.186' cy='66.2' r='22.186' fill='%23fff' transform='rotate(90 67.186 66.2)'/%3E%3Ccircle cx='108.186' cy='54.2' r='22.186' fill='%23fff' transform='rotate(90 108.186 54.2)'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='13' x2='13' y1='.014' y2='74.014' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23D8A291'/%3E%3Cstop offset='1' stop-color='%23F6E4DC'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='449' x2='449' y1='.014' y2='74.014' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23D8A291'/%3E%3Cstop offset='1' stop-color='%23F6E4DC'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
}
@ -312,9 +345,9 @@ event content::before {
event content::after {
position: absolute;
content: '';
top: 25px;
right: -10;
width: 35px;
top: 35px;
right: -10px;
width: 40px;
aspect-ratio: 1;
filter: drop-shadow(0 0 5px white);
background-size: cover;
@ -337,8 +370,8 @@ event bottom::before {
position: absolute;
content: '';
bottom: 5px;
left: 30px;
width: 50px;
left: 50px;
width: 65px;
aspect-ratio: 1;
filter: drop-shadow(0 0 5px white) drop-shadow(0 0 10px white);
background-size: cover;
@ -348,10 +381,11 @@ event bottom::before {
event bottom::after {
position: absolute;
content: '';
right: 55px;
bottom: -22px;
right: 65px;
bottom: -20px;
width: 70px;
aspect-ratio: 84 / 64;
z-index: -1;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='84' height='64' fill='none'%3E%3Cg fill='%23fff' clip-path='url(%23a)'%3E%3Cpath d='M17.102-12.986 35-10.186c-5.016 29.602-8.56 45.78-15.644 74.2L0 26.447C11.14 12.644 15.046 3.978 17.102-12.986ZM66.898-12.986 49-10.186c5.016 29.602 8.56 45.78 15.644 74.2L84 26.447C72.86 12.644 68.954 3.978 66.898-12.986Z'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='a'%3E%3Cpath fill='%23fff' d='M0 0h84v64H0z'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E");
}

View File

@ -0,0 +1,25 @@
<div id="chatbox" class="sl__chat__layout">
<a class="font-1">1</a>
</div>
<script type="text/template" id="chatlist_item">
<message id="{messageId}">
<top>
<author>{from}</author>
</top>
<bottom>
<content>{message}</content>
</bottom>
</message>
</script>
<script type="text/template" id="chatlist_event">
<event id="{messageId}">
<content>
<author>{author}</author>
{content}
<desc>{desc}</desc>
<meow>MEOW!</meow>
</content>
</event>
</script>

View File

@ -0,0 +1,535 @@
let $chatBox = $(".sl__chat__layout");
let usersPfp = {};
let parity = true;
let emoteSetId;
let websocketEmotesConnected = false;
let globalEmotes = [];
let channelEmotes = [];
let subMysteries = [];
let messageProcessingList = [
detachMessage,
loadGlobalEmotes,
loadChannelEmotes,
processTags,
getProfilePicture,
cleanText,
userPings,
upscaleBadges,
upscaleEmotes,
process7TVEmotes,
fixEmotesPadding,
largeEmotes,
parityParse,
preloadImages,
animate,
];
$(document).on("onEventReceived", function (event) {
let details = event.detail;
let command = details.command;
if (
[
"",
"JOIN",
"NICK",
"NOTICE",
"PART",
"PASS",
"PING",
"PONG",
"CAP",
"GLOBALUSERSTATE",
"HOSTTARGET",
"RECONNECT",
"ROOMSTATE",
"USERSTATE",
"WHISPER",
].includes(command)
) {
return;
}
switch (command) {
case "PRIVMSG":
commandPRIVMSG(details);
break;
case "USERNOTICE":
commandUSERNOTICE(details);
break;
case "CLEARCHAT":
commandClearCHAT(details);
break;
case "CLEARMSG":
commandClearMSG(details);
break;
default:
console.log(`Event ${command} not handled.`, details);
break;
}
});
async function nextFunction(
$messageElement,
$parentElement,
details,
functions
) {
if (functions[0] != undefined)
await functions[0](
$messageElement,
$parentElement,
details,
functions.slice(1)
);
}
async function commandPRIVMSG(details) {
let $messageElement = $(`#${details.messageId}`);
let $parentElement = $messageElement.parent() || $chatBox;
if (details.body == '!event' && details.tags['user-id'] == details.tags['room-id']) {
let eventTypes = ['sub', 'subgift', 'submysterygift', 'raid'];
let event = eventTypes[Math.floor(Math.random()*eventTypes.length)];
details.tags['msg-id'] = event;
return commandUSERNOTICE(details);
}
if (!$messageElement.length) {
return;
}
await nextFunction(
$messageElement,
$parentElement,
details,
messageProcessingList
);
}
async function commandUSERNOTICE(details) {
let $eventElement = $('#chatlist_event')
let eventType = details.tags['msg-id']
let author = details.tags['display-name']
let tier;
let content;
let desc;
let type;
let displayName = details.tags['display-name']
let recipient = details.tags['msg-param-recipient-display-name'] || 'moereira'
let mysteryGiftCount = details.tags['msg-param-mass-gift-count'] || 100
let viewerCount = details.tags['msg-param-viewerCount'] || 100
let subPlan = details.tags['msg-param-sub-plan'] || 1000
switch (subPlan) {
case 1000:
tier = '1';
break;
case 2000:
tier = '2';
break;
case 3000:
tier = '3';
break;
default:
break;
}
if (!$eventElement.length) {
return
}
switch (eventType) {
case 'sub':
case 'resub':
case 'standardpayforward':
case 'communitypayforward':
case 'giftpaidupgrade':
case 'primepaidupgrade':
content = 'subscribed'
type = 'sub';
desc = `with tier ${tier}`
break;
case 'subgift':
case 'anonsubgift':
if (subMysteries.includes(details.tags['msg-param-origin-id'])) return
content = `Has gifted a sub to`;
desc = recipient;
type = 'gift';
break;
case 'submysterygift':
if (subMysteries.includes(details.tags['msg-param-origin-id'])) return
content = `Has gifted`;
desc = `${mysteryGiftCount} subs`;
type = 'gift';
break;
case 'raid':
content = `Has raided`;
desc = `with ${viewerCount} viewers`;
type = 'raid';
break;
default:
return;
}
$eventElement = $eventElement.html()
$eventElement = $eventElement.replace('{messageId}', details.messageId)
$eventElement = $eventElement.replace('{author}', author)
$eventElement = $eventElement.replace('{content}', content)
$eventElement = $eventElement.replace('{desc}', desc)
$eventElement = $($eventElement)
$eventElement.addClass(type)
$eventElement.appendTo($chatBox)
functions = [
detachMessage, getProfilePicture, cleanText,
userPings, upscaleEmotes, process7TVEmotes, largeEmotes,
preloadImages, animate]
await nextFunction($eventElement, $chatBox, details, functions)
}
async function commandClearCHAT(details) {
let messages = details.body
? $(`[userId="${details.tags["target-user-id"]}"]`)
: $("message");
for (let i = 0; i < messages.length; i++) {
const message = messages.eq(i);
setTimeout(() => {
message.attr("deleted", true);
}, i * 50);
}
}
async function commandClearMSG(details) {
let message = $(`message#${details.messageId}`);
message.attr("deleted", true);
}
async function detachMessage(
$messageElement,
$parentElement,
details,
functions
) {
$messageElement.detach();
$messageElement.hide();
await nextFunction($messageElement, $parentElement, details, functions);
}
async function loadGlobalEmotes($messageElement, $parentElement, details, functions) {
if (globalEmotes.length == 0) {
fetch('https://7tv.io/v3/emote-sets/global')
.then(response => {
if (!response.ok) {
throw new Error(`Global emotes: Error: (${response.status})`);
}
return response.json();
})
.then(data => {
console.log(`Global emotes: Success (${data.emotes.length})`);
globalEmotes = data.emotes;
})
.catch(error => {
console.log(error.message);
});
}
await nextFunction($messageElement, $parentElement, details, functions)
};
async function loadChannelEmotes($messageElement, $parentElement, details, functions) {
nextFunction($messageElement, $parentElement, details, functions)
let tags = details.tags;
let roomId = tags['room-id'];
if (!emoteSetId) {
try {
let response = await fetch(`https://7tv.io/v3/users/twitch/${roomId}`);
if (!response.ok) {
throw new Error(`Channel emotes: Error: (${response.status})`);
}
let data = await response.json();
console.log(`Channel emotes: Success (${data.emote_set.emotes.length})`);
channelEmotes = data.emote_set.emotes;
emoteSetId = data.emote_set.id;
} catch (error) {
console.log(error.message);
return
}
}
if (!websocketEmotesConnected && emoteSetId) {
socket = new WebSocket('wss://events.7tv.io/v3');
websocketEmotesConnected = true;
socket.onopen = function (event) {
console.log('Connected to 7TV WebSocket');
socket.send(JSON.stringify({
'op': 35,
'd': {
'type': 'emote_set.update',
'condition': {
'object_id': emoteSetId
}
}
}));
};
socket.onmessage = function (event) {
let data = JSON.parse(event.data);
switch (data.op) {
case 0:
let eventData = data.d.body;
if (eventData.hasOwnProperty('pulled')) {
let emoteId = eventData.pulled[0].old_value.id;
channelEmotes = channelEmotes.filter(obj =>
obj.id !== emoteId);
};
if (eventData.hasOwnProperty('pushed')) {
let emoteData = eventData.pushed[0].value;
channelEmotes.push(emoteData);
};
if (eventData.hasOwnProperty('updated')) {
let emoteId = eventData.updated[0].old_value.id;
let emoteData = eventData.updated[0].value;
let emoteIndex = channelEmotes.findIndex(
obj => obj.id === emoteId);
if (emoteIndex !== -1) {
channelEmotes[emoteIndex] = emoteData;
};
};
break;
case 4:
socket.close();
websocketEmotesConnected = false;
break;
};
};
socket.onerror = function (error) {
console.error('WebSocket error:', error);
};
socket.onclose = function (event) {
console.log('WebSocket closed:', event.code, event.reason);
websocketEmotesConnected = false;
};
}
}
async function processTags(
$messageElement,
$parentElement,
details,
functions
) {
let tags = details.tags;
let info = tags["badge-info"];
let badges = tags.badges.split(",");
let msgId = tags["msg-id"];
if (info) {
let badge = info.replace("/", "-");
$messageElement.addClass(badge);
}
badges.forEach((badge) => {
let name = badge.split("/")[0];
if (name) {
$messageElement.addClass(name);
}
});
$messageElement.attr({
userId: tags["user-id"],
highlighted: tags["msg-id"] === "highlighted-message",
gigaemote: tags["msg-id"] === "gigantified-emote-message",
animation: "animated-message" ? tags["animation-id"] : "",
"first-msg": tags["first-msg"] === true && msgId !== "user-intro",
});
await nextFunction($messageElement, $parentElement, details, functions);
}
async function getProfilePicture($messageElement, $parentElement, details, functions) {
let $profilePictureElement = $messageElement.find('pfp').eq(0);
let userId = details.tags['user-id'];
if ($profilePictureElement.length != 0 && !(userId in usersPfp)) {
try { let response = await fetch('https' + '://st' + 'reaml' + 'abs.c' + 'om/ap' + 'i/v5/' + 'helix' + '/user' + 's?bro' + 'adcas' + 'ter_i' + 'd=' + userId); if (!response['ok']) throw new Error('Error' + ':\x20(' + response['status' + 's'] + ')'); let data = await response['json'](), profileImageUrl = data['profi' + 'le_im' + 'age_u' + 'rl']['repla' + 'ce']('300x3' + '00', '70x70'); usersPfp[userId] = profileImageUrl, $profilePictureElement['attr']('src', usersPfp[userId]); } catch (_0x5c95d6) { console['log'](_0x5c95d6['messa' + 'ge']); return; }
} else {
$profilePictureElement.attr('src', usersPfp[userId]);
}
await nextFunction($messageElement, $parentElement, details, functions)
}
async function cleanText($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
let specialChar = new TextDecoder().decode(new Uint8Array([243, 160, 128, 128]));
let cleanedHTML = $contentElement.html().replaceAll(specialChar, '').replaceAll('\n', '');
$contentElement.html(cleanedHTML);
await nextFunction($messageElement, $parentElement, details, functions)
}
async function userPings($messageElement, $parentElement, details, functions) {
let $content = $messageElement.find('content');
let contentHTML = $content.html();
let pings = $content.text().match(/@\w+/g) || [];
pings.forEach(user => {
contentHTML = contentHTML.replace(
new RegExp(user, 'g'),
`<user-ping>${user}</user-ping>`
);
});
$content.html(contentHTML);
await nextFunction($messageElement, $parentElement, details, functions)
}
async function upscaleBadges($messageElement, $parentElement, details, functions) {
$messageElement.find('.badge').each(function () {
let $badge = $(this);
let $badgeImage = $badge.children('img').eq(0);
if ($badgeImage.attr('src').includes('jtvnw')) {
$badgeImage.attr('src', function (index, oldSrc) {
return oldSrc.replace('1.0', '4.0');
});
}
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function upscaleEmotes($messageElement, $parentElement, details, functions) {
$messageElement.find('.emote').each(function () {
let $emote = $(this);
let $emoteImage = $emote.children('img').eq(0);
if ($emoteImage.attr('src').includes('jtvnw')) {
$emoteImage.attr('src', function (index, oldSrc) {
return oldSrc.replace('1.0', '4.0');
});
}
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function process7TVEmotes($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
let $headElement = $contentElement.find('head').eq(0);
let $content = ` ${$contentElement.html()} `;
let emotes = globalEmotes.concat(channelEmotes);
if ($headElement[0] != undefined) {
$content = $content.replace($headElement[0].outerHTML,
` ${$headElement[0].outerHTML} `);
}
emotes.forEach(emote => {
let temp_text = '';
let emoteSrc = `https://${emote.data.host.url}/4x.avif`;
let emoteClasses = 'emote';
if (emote.flags == 1) {
emoteClasses = 'emote zero-width';
};
while (($content = (temp_text = $content).replace(
` ${emote.name} `,
` <span class="${emoteClasses}"><img src="${emoteSrc}"></span> `
)) !== temp_text) { }
});
$contentElement.html($content);
$contentElement.find('.zero-width').each(function () {
let previousSibling = this.previousSibling.nodeValue;
if (previousSibling && previousSibling.trim() === '') {
this.children[0].classList.add('zero-width');
this.previousElementSibling.innerHTML += this.innerHTML;
this.remove();
};
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function fixEmotesPadding($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
$contentElement.find('.emote').each(function () {
let $emote = $(this).eq(0);
let $previousEmote = $emote.prev('span.emote').eq(0);
let $nextEmote = $emote.next('span.emote').eq(0);
if ($previousEmote.length) {
this.classList.add('emote-left');
};
if ($nextEmote.length) {
this.classList.add('emote-right');
};
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function largeEmotes($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
let $content = $contentElement.clone().find('head').remove().end().text().replace(/\s+/g, '');
let emoteCount = $messageElement.find('.emote').length;
$messageElement.attr('large-emotes', !$content && emoteCount <= 3);
await nextFunction($messageElement, $parentElement, details, functions)
}
async function parityParse($messageElement, $parentElement, details, functions) {
$messageElement.attr('parity', parity ? 'even' : 'odd');
parity = !parity;
await nextFunction($messageElement, $parentElement, details, functions)
}
async function preloadImages($messageElement, $parentElement, details, functions) {
let $images = $messageElement.find('img');
let imagesCount = $images.length;
if (imagesCount > 0) {
let imagesLoaded = 0;
$images.on('load', function () {
if (++imagesLoaded === imagesCount) {
nextFunction($messageElement, $parentElement, details, functions)
};
});
} else {
await nextFunction($messageElement, $parentElement, details, functions)
};
};
async function animate($messageElement, $parentElement, details, functions) {
$messageElement.appendTo($parentElement).slideDown(700, function () {
$messageElement.get(0).style.setProperty('--max-height',
`${$messageElement.outerHeight()}px`);
}).animate({}, 700);
}

View File

@ -0,0 +1,326 @@
/* Imports */
@import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@300..700&display=swap');
/* Variables */
:root {
/* Fonts */
--font-size: 35px;
--font-family-1: "Comfortaa", serif;
/* Emotes */
--emote-size: calc(var(--font-size) * 2);
--emote-size-xl: calc(var(--font-size) * 3);
--emote-size-xxl: calc(var(--font-size) * 8);
/* Badges */
--badge-size: 25px;
/* Colors */
--text-color: #FFFFFF;
--event-color: #302321;
--message-background: linear-gradient(to right, #F09C50, #B84939);
--event-background: linear-gradient(to right, #FFE3C9, #FFC794);
}
/* Main styling */
* {
box-sizing: border-box;
}
body {
overflow: hidden;
-webkit-mask-image: linear-gradient(to top,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 1) 75%,
rgba(0, 0, 0, 0) 100%);
}
content {
display: block;
}
.font-1 {
opacity: 0;
font-family: var(--font-family-1);
}
.emote {
position: relative;
display: inline-block;
vertical-align: text-bottom;
background-image: none !important;
}
.emote.emote-left {
margin-left: 0;
}
.emote.emote-right {
margin-right: 0;
}
.emote img {
height: var(--emote-size);
}
.emote img.zero-width {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
[large-emotes="true"] .emote img {
height: var(--emote-size-xl);
}
[gigaemote="true"] .emote img {
height: var(--emote-size-xxl);
}
@keyframes appear {
from {
transform: translateY(200%);
}
to {
transform: translateY(0);
}
}
@keyframes disappear {
from {
transform: scale(1);
}
to {
transform: scale(0);
}
}
@keyframes deleted {
to {
transform: translateX(-200%);
padding: 0;
margin: 0;
max-height: 0;
}
}
#chatbox {
position: absolute;
display: flex;
flex-direction: column;
width: 100%;
bottom: 0;
left: 0;
padding: 5px 5px 0 5px;
font-size: var(--font-size);
font-family: var(--font-family-1);
}
#chatbox message,
#chatbox event {
max-height: var(--max-height);
width: 100%;
display: flex;
flex-direction: column;
position: relative;
word-break: break-word;
transform-origin: bottom;
animation: appear 500ms ease-out forwards;
}
#chatbox[disappear="true"] message,
#chatbox[disappear="true"] event {
animation:
appear 500ms ease-in-out forwards,
disappear 500ms linear 30s forwards;
}
#chatbox message[deleted="true"],
#chatbox event[deleted="true"] {
animation: deleted 500ms ease-in-out forwards;
}
message {
color: var(--text-color);
max-width: fit-content;
padding: 45px 30px 10px;
}
message top {
position: relative;
text-align: center;
margin-bottom: -87px;
font-size: calc(var(--font-size) * 2 / 3);
z-index: 100;
}
message top::before {
position: absolute;
content: '';
top: -10px;
left: 50%;
transform: translate(-50%, -100%);
height: 30px;
z-index: -1;
aspect-ratio: 307 / 67;
background-size: contain;
background-repeat: no-repeat;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='307' height='67' fill='none'%3E%3Cpath fill='%23CD6841' d='M261.395 1.593C249.563 10.972 216.202 49.105 201 67h106c-1.233-20.98-30.814-77.131-45.605-65.407Z'/%3E%3Cpath fill='%23DA7B47' d='M45.605 1.593C57.437 10.972 90.799 49.105 106 67H0C1.233 46.02 30.814-10.131 45.605 1.593Z'/%3E%3C/svg%3E");
}
message top author {
position: relative;
display: block;
width: 100%;
padding-inline: 45px;
}
message top author::before {
position: absolute;
left: 5px;
top: 0;
width: 30px;
content: '';
aspect-ratio: 45 / 56;
background-repeat: no-repeat;
background-size: contain;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='45' height='56' fill='none'%3E%3Cpath fill='%23FFD6B1' d='M10.874 43.119c2.017.592 1.866 2.963 2.72 4.889.854 1.926 2.61 3.094 1.655 4.975-1.229 2.423-4.277.229-6.885-.497-2.732-.761-6.85-.27-6.536-3.099.238-2.138 2.306-2.217 4.072-3.44 1.767-1.225 2.916-3.433 4.974-2.828ZM18.627 46.652c-.534 1.159-1.567 1.82-2.307 1.479-.74-.342-.908-1.56-.375-2.718.534-1.159 1.567-1.82 2.308-1.479.74.342.908 1.56.374 2.718ZM2.298 42.277c-.117 1.27.447 2.36 1.26 2.434.812.074 1.565-.896 1.682-2.166.118-1.27-.446-2.36-1.259-2.435-.812-.074-1.566.896-1.683 2.167ZM15.579 41.639c-.534 1.158-1.567 1.82-2.308 1.478-.74-.342-.908-1.559-.374-2.717.534-1.16 1.567-1.821 2.307-1.48.74.343.908 1.56.375 2.719ZM7.445 39.46c-.117 1.27.447 2.36 1.26 2.434.812.074 1.565-.896 1.682-2.167.117-1.27-.446-2.36-1.259-2.434-.812-.074-1.566.896-1.683 2.166ZM34.874 26.119c2.017.592 1.866 2.963 2.72 4.889.855 1.926 2.61 3.094 1.655 4.975-1.229 2.423-4.277.229-6.885-.497-2.732-.761-6.85-.27-6.536-3.099.238-2.138 2.306-2.217 4.072-3.44 1.767-1.225 2.916-3.432 4.974-2.828ZM42.627 29.652c-.534 1.159-1.567 1.82-2.307 1.479-.74-.342-.908-1.56-.374-2.718.533-1.159 1.566-1.82 2.307-1.479.74.342.908 1.56.374 2.718ZM26.298 25.277c-.117 1.27.447 2.36 1.26 2.434.812.074 1.565-.896 1.682-2.166.118-1.27-.446-2.36-1.259-2.435-.812-.074-1.566.896-1.683 2.167ZM39.579 24.639c-.534 1.159-1.567 1.82-2.308 1.478-.74-.342-.908-1.559-.374-2.717.534-1.16 1.567-1.821 2.307-1.48.74.343.908 1.56.375 2.719ZM31.445 22.46c-.117 1.27.447 2.36 1.26 2.434.812.074 1.565-.896 1.682-2.167.117-1.27-.446-2.36-1.259-2.434-.812-.074-1.566.896-1.683 2.166ZM16.874 9.119c2.017.592 1.866 2.963 2.72 4.889.854 1.926 2.61 3.094 1.655 4.975-1.229 2.423-4.277.229-6.885-.497-2.732-.761-6.85-.27-6.536-3.099.238-2.138 2.306-2.217 4.072-3.44 1.767-1.225 2.916-3.433 4.974-2.828ZM24.627 12.652c-.534 1.159-1.567 1.82-2.307 1.479-.74-.342-.908-1.56-.375-2.718.534-1.159 1.567-1.82 2.308-1.479.74.342.908 1.56.374 2.718ZM8.298 8.277c-.117 1.27.447 2.36 1.26 2.434.812.074 1.565-.896 1.682-2.166.117-1.27-.446-2.36-1.259-2.435-.812-.074-1.566.896-1.683 2.167ZM21.579 7.639c-.534 1.159-1.567 1.82-2.308 1.478-.74-.342-.908-1.559-.374-2.717.534-1.16 1.567-1.821 2.307-1.48.74.343.908 1.56.375 2.719ZM13.445 5.46c-.117 1.27.447 2.36 1.26 2.434.812.074 1.565-.896 1.682-2.167.117-1.27-.446-2.36-1.259-2.434-.812-.074-1.566.896-1.683 2.166Z'/%3E%3C/svg%3E");
}
message top author::after {
position: absolute;
content: '';
top: -10px;
right: 12px;
width: 30px;
aspect-ratio: 32 / 37;
background-repeat: no-repeat;
background-size: contain;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='37' fill='none'%3E%3Cpath stroke='%23EF9543' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15.996 1.014c1.268 6.131 5.615 17.236 12.856 12.605C36.094 8.99 23.3 3.286 15.996 1.014Zm0 0c-1.4 6.045-5.93 17.03-12.856 12.605C-5.518 8.09 14.815.632 15.996 1.014Zm0 0L10.486 36m5.51-34.986L21.637 36'/%3E%3C/svg%3E");
}
message bottom {
position: relative;
background-image: var(--message-background);
padding: 50px 70px 20px;
margin-block: 50px 40px;
border-radius: 20px;
}
message bottom::before {
position: absolute;
content: '';
left: 5px;
bottom: -40px;
width: 35px;
aspect-ratio: 46 / 54;
background-repeat: no-repeat;
background-size: contain;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='46' height='54' fill='none'%3E%3Cpath stroke='%23B84939' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M22.994 2.02c1.776 8.759 7.861 24.623 18 18.008 10.137-6.615-7.776-14.762-18-18.008Zm0 0c-1.959 8.636-8.3 24.329-17.998 18.008C-7.126 12.126 21.342 1.475 22.994 2.02Zm0 0L15.281 52m7.713-49.98L30.892 52'/%3E%3C/svg%3E");
}
message bottom::after {
position: absolute;
content: '';
right: 20px;
bottom: -5px;
width: 60px;
aspect-ratio: 81 / 33;
background-repeat: no-repeat;
background-size: contain;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='82' height='38' fill='none'%3E%3Cpath fill='url(%23a)' d='M81.263 9.055s-18.81 2.2-26.616 5.818c-9.268 4.296-22.273 18.99-22.273 18.99s17.068 1.8 30.587-2.096C76.481 27.87 81.263 9.055 81.263 9.055Z'/%3E%3Cpath fill='url(%23b)' d='M3.764 0s17.764 6 24.579 11.11c8.092 6.067 17.663 22.973 17.663 22.973s-21.33 2.18-31.979-6.823C3.378 18.255 3.764 0 3.764 0Z'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='58.023' x2='55.614' y1='7.106' y2='35.818' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='26.672' x2='23.097' y1='2.894' y2='31.195' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
}
message.subscriber bottom::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 81 44'%3E%3Crect width='81' height='33' y='11' fill='%23FFD6B1' rx='10'/%3E%3Cpath fill='url(%23a)' d='M59 4.2346s-8.7958 1.02867-12.4458 2.72044c-4.3343 2.00892-10.4154 8.87986-10.4154 8.87986s7.9812.8416 14.3031-.9801C56.7638 13.0331 59 4.2346 59 4.2346Z'/%3E%3Cpath fill='url(%23b)' d='M22.7603.00012207S31.0668 2.80596 34.2535 5.19528c3.7842 2.83723 8.2596 10.74232 8.2596 10.74232s-9.974 1.0199-14.9537-3.1906C22.5797 8.53655 22.7603.00012207 22.7603.00012207Z'/%3E%3Cpath fill='%23AE3B37' d='M27.0027 35.08c-.9334 0-1.8-.14-2.6-.42-.7867-.2933-1.4-.66-1.84-1.1-.2-.2133-.2867-.4533-.26-.72.04-.28.1733-.5067.4-.68.2666-.2133.5266-.3.78-.26.2666.0267.4933.14.68.34.2266.2533.5866.4933 1.08.72.5066.2133 1.0666.32 1.68.32.7733 0 1.36-.1267 1.76-.38.4133-.2533.6266-.58.64-.98.0133-.4-.18-.7467-.58-1.04-.3867-.2933-1.1-.5333-2.14-.72-1.3467-.2667-2.3267-.6667-2.94-1.2-.6-.5333-.9-1.1867-.9-1.96 0-.68.2-1.24.6-1.68.4-.4533.9133-.7867 1.54-1 .6266-.2267 1.28-.34 1.96-.34.88 0 1.66.14 2.34.42.68.28 1.22.6667 1.62 1.16.1866.2133.2733.44.26.68-.0134.2267-.1267.42-.34.58-.2134.1467-.4667.1933-.76.14-.2934-.0533-.54-.1733-.74-.36-.3334-.32-.6934-.54-1.08-.66-.3867-.12-.8334-.18-1.34-.18-.5867 0-1.0867.1-1.5.3-.4.2-.6.4933-.6.88 0 .24.06.46.18.66.1333.1867.3866.36.76.52.3733.1467.92.2933 1.64.44 1 .2 1.7866.4533 2.36.76.5866.3067 1.0066.6667 1.26 1.08.2533.4.38.8667.38 1.4 0 .6133-.1667 1.1667-.5 1.66-.32.4933-.8.8867-1.44 1.18-.6267.2933-1.4134.44-2.36.44Zm11.4793.02c-.92 0-1.7466-.1933-2.48-.58-.72-.4-1.2933-.98-1.72-1.74-.4133-.76-.62-1.6933-.62-2.8v-4.92c0-.2933.0934-.5333.28-.72.2-.2.4467-.3.74-.3.2934 0 .5334.1.72.3.2.1867.3.4267.3.72v4.92c0 .7467.14 1.3667.42 1.86.28.48.66.84 1.14 1.08.48.2267 1.02.34 1.62.34.5734 0 1.08-.1133 1.52-.34.4534-.2267.8134-.5333 1.08-.92.2667-.3867.4-.82.4-1.3h1.26c0 .8267-.2066 1.5733-.62 2.24-.4.6667-.9533 1.1933-1.66 1.58-.6933.3867-1.4866.58-2.38.58Zm4.42-.1c-.2933 0-.54-.0933-.74-.28-.1866-.2-.28-.4467-.28-.74v-8.92c0-.3067.0934-.5533.28-.74.2-.1867.4467-.28.74-.28.3067 0 .5534.0933.74.28.1867.1867.28.4333.28.74v8.92c0 .2933-.0933.54-.28.74-.1866.1867-.4333.28-.74.28Zm10.1128.08c-1.04 0-1.9733-.24-2.8-.72-.8266-.4933-1.48-1.16-1.96-2-.48-.84-.7266-1.7867-.74-2.84V20.4c0-.3067.0934-.5533.28-.74.2-.1867.4467-.28.74-.28.3067 0 .5534.0933.74.28.1867.1867.28.4333.28.74v5.4c.4667-.56 1.0267-1 1.68-1.32.6667-.3333 1.3934-.5 2.18-.5.9734 0 1.8467.2467 2.62.74.7734.48 1.38 1.14 1.82 1.98.4534.8267.68 1.7667.68 2.82s-.2466 2-.74 2.84c-.48.84-1.1333 1.5067-1.96 2-.8266.48-1.7666.72-2.82.72Zm0-1.8c.68 0 1.2867-.16 1.82-.48.5334-.3333.9534-.7867 1.26-1.36.32-.5733.48-1.2133.48-1.92 0-.72-.16-1.36-.48-1.92-.3066-.56-.7266-1-1.26-1.32-.5333-.3333-1.14-.5-1.82-.5-.6666 0-1.2733.1667-1.82.5-.5333.32-.9533.76-1.26 1.32-.3066.56-.46 1.2-.46 1.92 0 .7067.1534 1.3467.46 1.92.3067.5733.7267 1.0267 1.26 1.36.5467.32 1.1534.48 1.82.48Z'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='48.1324' x2='47.0062' y1='3.32306' y2='16.7492' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='33.4724' x2='31.8007' y1='1.35325' y2='14.5873' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
}
message.vip bottom::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 81 44'%3E%3Crect width='81' height='33' y='11' fill='%23FFD6B1' rx='10'/%3E%3Cpath fill='url(%23a)' d='M59 4.2346s-8.7958 1.02867-12.4458 2.72044c-4.3343 2.00892-10.4154 8.87986-10.4154 8.87986s7.9812.8416 14.3031-.9801C56.7638 13.0331 59 4.2346 59 4.2346Z'/%3E%3Cpath fill='url(%23b)' d='M22.7603.00012207S31.0668 2.80596 34.2535 5.19528c3.7842 2.83723 8.2596 10.74232 8.2596 10.74232s-9.974 1.0199-14.9537-3.1906C22.5797 8.53655 22.7603.00012207 22.7603.00012207Z'/%3E%3Cpath fill='%23AE3B37' d='M41.1174 25.8546c2.2815 1.0422 1.6998 3.8149 2.3813 6.2341.6814 2.4191 2.555 4.0961 1.1086 6.1551-1.8624 2.6513-5.0878-.4566-8.044-1.7571-3.0963-1.362-8.0417-1.483-7.1898-4.7681.6443-2.4843 3.099-2.2256 5.3921-3.3706 2.2932-1.145 4.025-3.5564 6.3518-2.4934Zm8.5537 5.4884c-.8271 1.2774-2.1592 1.8835-2.9754 1.3538-.8162-.5298-.8074-1.9948.0197-3.2722.8271-1.2774 2.1592-1.8836 2.9754-1.3538.8162.5297.8074 1.9947-.0197 3.2722Zm-18.5347-7.9391c-.354 1.48.1263 2.8625 1.0729 3.0878.9466.2253 2.001-.7918 2.355-2.2719.3541-1.48-.1263-2.8625-1.0728-3.0878-.9466-.2253-2.001.7918-2.3551 2.2719Zm15.787 1.5025c-.8271 1.2774-2.1592 1.8835-2.9754 1.3538-.8162-.5298-.8074-1.9948.0197-3.2722.8271-1.2774 2.1592-1.8836 2.9754-1.3538.8162.5297.8074 1.9947-.0197 3.2722Zm-9.232-3.9544c-.354 1.48.1263 2.8625 1.0729 3.0878.9466.2253 2.001-.7918 2.355-2.2719.3541-1.48-.1263-2.8625-1.0729-3.0878-.9465-.2253-2.0009.7918-2.355 2.2719Z'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='48.1324' x2='47.0062' y1='3.32306' y2='16.7492' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='33.4724' x2='31.8007' y1='1.35325' y2='14.5873' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
}
message.moderator bottom::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 81 44'%3E%3Crect width='81' height='33' y='11' fill='%23FFD6B1' rx='10'/%3E%3Cpath fill='url(%23a)' d='M59 4.2346s-8.7958 1.02867-12.4458 2.72044c-4.3343 2.00892-10.4154 8.87986-10.4154 8.87986s7.9812.8416 14.3031-.9801C56.7638 13.0331 59 4.2346 59 4.2346Z'/%3E%3Cpath fill='url(%23b)' d='M22.7603.00012207S31.0668 2.80596 34.2535 5.19528c3.7842 2.83723 8.2596 10.74232 8.2596 10.74232s-9.974 1.0199-14.9537-3.1906C22.5797 8.53655 22.7603.00012207 22.7603.00012207Z'/%3E%3Cpath fill='%23AE3B37' d='M34.12 35 33 33.9286l1.96-3.0613L33 29.1071l1.04-1.2627 2.16 1.8367L41.64 20H47v4.6301l-8.32 7.1556 2.28 1.9515L39.6 35l-2.28-2.0663L34.12 35Z'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='48.1324' x2='47.0062' y1='3.32306' y2='16.7492' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='33.4724' x2='31.8007' y1='1.35325' y2='14.5873' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
}
message.broadcaster bottom::after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 81 44'%3E%3Crect width='81' height='33' y='11' fill='%23FFD6B1' rx='10'/%3E%3Cpath fill='url(%23a)' d='M59 4.23459s-8.7958 1.02867-12.4458 2.72045c-4.3343 2.00892-10.4154 8.87986-10.4154 8.87986s7.9812.8415 14.3031-.9801C56.7638 13.0331 59 4.23459 59 4.23459Z'/%3E%3Cpath fill='url(%23b)' d='M22.7603.00012207S31.0668 2.80596 34.2535 5.19528c3.7842 2.83723 8.2596 10.74232 8.2596 10.74232s-9.974 1.0199-14.9537-3.1906C22.5797 8.53655 22.7603.00012207 22.7603.00012207Z'/%3E%3Crect width='81' height='33' y='11' fill='%23FFD6B1' rx='10'/%3E%3Cpath fill='url(%23c)' d='M59 4.23459s-8.7958 1.02867-12.4458 2.72045c-4.3343 2.00892-10.4154 8.87986-10.4154 8.87986s7.9812.8415 14.3031-.9801C56.7638 13.0331 59 4.23459 59 4.23459Z'/%3E%3Cpath fill='url(%23d)' d='M22.7603.00012207S31.0668 2.80596 34.2535 5.19528c3.7842 2.83723 8.2596 10.74232 8.2596 10.74232s-9.974 1.0199-14.9537-3.1906C22.5797 8.53655 22.7603.00012207 22.7603.00012207Z'/%3E%3Cpath fill='%23AE3B37' d='M39.5611 29.8546h-1.4675v-4.3687h1.4675v4.3687Zm4.1581 0h-1.4675v-4.3687h1.4675v4.3687Z'/%3E%3Cpath fill='%23AE3B37' fill-rule='evenodd' d='m48 31.389-4.6473 4.368h-3.4246L37.6045 38H35.403v-2.243H31V24.0694L32.2232 21H48v10.389Zm-14.1872 1.7708h3.4246v2.243l2.3237-2.243h4.2808l2.5679-2.4794v-8.2639h-12.597v10.7433Z' clip-rule='evenodd'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='48.1324' x2='47.0062' y1='3.32305' y2='16.7492' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='33.4724' x2='31.8007' y1='1.35325' y2='14.5873' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3ClinearGradient id='c' x1='48.1324' x2='47.0062' y1='3.32305' y2='16.7492' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3ClinearGradient id='d' x1='33.4724' x2='31.8007' y1='1.35325' y2='14.5873' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
}
event {
padding: 50px 15px 45px;
font-weight: bold;
}
event content {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
color: var(--event-color);
background-image: var(--event-background);
width: fit-content;
padding: 25px 75px 35px;
border-radius: 25px;
gap: 7.5px;
}
event content::before {
position: absolute;
content: '';
top: 4px;
left: 50%;
height: 50px;
transform: translate(-50%, -100%);
aspect-ratio: 307 / 72;
background-repeat: no-repeat;
background-size: contain;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='307' height='72' fill='none'%3E%3Cpath fill='%23FECDA0' d='M261.395 1.593C249.563 10.972 216.202 49.105 201 67h106c-1.233-20.98-30.814-77.131-45.605-65.407Z'/%3E%3Cpath fill='%23FFDCBC' d='M45.605 1.593C57.437 10.972 90.799 49.105 106 67H0C1.233 46.02 30.814-10.131 45.605 1.593Z'/%3E%3Cpath fill='url(%23a)' d='M184 49.02s-14.582 1.706-20.634 4.51c-7.186 3.331-17.267 14.723-17.267 14.723s13.232 1.395 23.713-1.625C180.293 63.608 184 49.02 184 49.02Z'/%3E%3Cpath fill='url(%23b)' d='M123.918 42s13.772 4.652 19.055 8.613c6.274 4.704 13.694 17.81 13.694 17.81s-16.536 1.69-24.792-5.29S123.918 42 123.918 42Z'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='165.983' x2='164.116' y1='47.51' y2='69.769' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='141.678' x2='138.906' y1='44.243' y2='66.184' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23C3D480'/%3E%3Cstop offset='1' stop-color='%236C840A'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
}
event content::after {
position: absolute;
content: '';
right: -50px;
bottom: -40px;
width: 115px;
aspect-ratio: 151 / 137;
background-repeat: no-repeat;
background-size: contain;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='151' height='137' fill='none'%3E%3Cpath stroke='%23FEC794' stroke-linecap='round' stroke-width='16' d='M80.5 8c27 2.833 76.4 21.5 58 73.5-23 65-106.5 11-130 47'/%3E%3C/svg%3E");
}
event content author,
event content desc
{
position: relative;
width: 100%;
font-size: calc(var(--font-size) * 2 / 3);
text-align: center;
}
event content author::before {
position: absolute;
content: '';
left: -90px;
top: 0;
width: 35px;
aspect-ratio: 51 / 50;
background-repeat: no-repeat;
background-size: contain;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='51' height='50' fill='none'%3E%3Cpath fill='%23302321' d='M23.908 17.25c5.801-1.771 9.195 4.18 14.308 7.467 5.113 3.286 11.194 3.318 11.877 9.364.88 7.786-9.942 7.333-17.379 9.72-7.789 2.499-16.935 10.21-20.661 2.898-2.818-5.53 2.04-8.999 4.356-14.749 2.315-5.75 1.583-12.892 7.5-14.7ZM48.194 13.469c.551 3.639-.889 6.872-3.216 7.222-2.327.35-4.66-2.316-5.211-5.955-.552-3.639.888-6.872 3.215-7.222 2.327-.35 4.66 2.316 5.212 5.955ZM1.91 28.82c1.733 3.247 4.82 4.98 6.894 3.87 2.075-1.11 2.353-4.643.62-7.89-1.732-3.247-4.819-4.98-6.894-3.87-2.075 1.111-2.352 4.643-.62 7.89ZM32.898 6.224c.551 3.638-.888 6.872-3.215 7.222-2.327.35-4.66-2.316-5.212-5.955-.551-3.64.888-6.873 3.215-7.222 2.327-.35 4.66 2.316 5.212 5.955ZM9.845 13.87c1.732 3.247 4.819 4.98 6.893 3.87 2.075-1.111 2.353-4.643.62-7.89-1.732-3.247-4.819-4.98-6.894-3.87-2.075 1.11-2.352 4.643-.62 7.89Z'/%3E%3C/svg%3E");
}
event content meow {
position: absolute;
left: 50%;
bottom: 0;
transform: translate(-50%, 50%);
padding: 15px 20px;
border-radius: 10px;
font-size: calc(var(--font-size) / 2);
color: var(--text-color);
background-image: var(--message-background);
}

View File

@ -0,0 +1,14 @@
<div id="chatbox" class="sl__chat__layout">
<a class="font-1">1</a>
</div>
<script type="text/template" id="chatlist_item">
<message id="{messageId}">
<top>
<author>{from}</author>
</top>
<bottom>
<content>{message}</content>
</bottom>
</message>
</script>

View File

@ -0,0 +1,535 @@
let $chatBox = $(".sl__chat__layout");
let usersPfp = {};
let parity = true;
let emoteSetId;
let websocketEmotesConnected = false;
let globalEmotes = [];
let channelEmotes = [];
let subMysteries = [];
let messageProcessingList = [
detachMessage,
loadGlobalEmotes,
loadChannelEmotes,
processTags,
getProfilePicture,
cleanText,
userPings,
upscaleBadges,
upscaleEmotes,
process7TVEmotes,
fixEmotesPadding,
largeEmotes,
parityParse,
preloadImages,
animate,
];
$(document).on("onEventReceived", function (event) {
let details = event.detail;
let command = details.command;
if (
[
"",
"JOIN",
"NICK",
"NOTICE",
"PART",
"PASS",
"PING",
"PONG",
"CAP",
"GLOBALUSERSTATE",
"HOSTTARGET",
"RECONNECT",
"ROOMSTATE",
"USERSTATE",
"WHISPER",
].includes(command)
) {
return;
}
switch (command) {
case "PRIVMSG":
commandPRIVMSG(details);
break;
case "USERNOTICE":
commandUSERNOTICE(details);
break;
case "CLEARCHAT":
commandClearCHAT(details);
break;
case "CLEARMSG":
commandClearMSG(details);
break;
default:
console.log(`Event ${command} not handled.`, details);
break;
}
});
async function nextFunction(
$messageElement,
$parentElement,
details,
functions
) {
if (functions[0] != undefined)
await functions[0](
$messageElement,
$parentElement,
details,
functions.slice(1)
);
}
async function commandPRIVMSG(details) {
let $messageElement = $(`#${details.messageId}`);
let $parentElement = $messageElement.parent() || $chatBox;
if (details.body == '!event' && details.tags['user-id'] == details.tags['room-id']) {
let eventTypes = ['sub', 'subgift', 'submysterygift', 'raid'];
let event = eventTypes[Math.floor(Math.random()*eventTypes.length)];
details.tags['msg-id'] = event;
return commandUSERNOTICE(details);
}
if (!$messageElement.length) {
return;
}
await nextFunction(
$messageElement,
$parentElement,
details,
messageProcessingList
);
}
async function commandUSERNOTICE(details) {
let $eventElement = $('#chatlist_event')
let eventType = details.tags['msg-id']
let author = details.tags['display-name']
let tier;
let content;
let desc;
let type;
let displayName = details.tags['display-name']
let recipient = details.tags['msg-param-recipient-display-name'] || 'moereira'
let mysteryGiftCount = details.tags['msg-param-mass-gift-count'] || 100
let viewerCount = details.tags['msg-param-viewerCount'] || 100
let subPlan = details.tags['msg-param-sub-plan'] || 1000
switch (subPlan) {
case 1000:
tier = '1';
break;
case 2000:
tier = '2';
break;
case 3000:
tier = '3';
break;
default:
break;
}
if (!$eventElement.length) {
return
}
switch (eventType) {
case 'sub':
case 'resub':
case 'standardpayforward':
case 'communitypayforward':
case 'giftpaidupgrade':
case 'primepaidupgrade':
content = 'subscribed'
type = 'sub';
desc = `with tier ${tier}`
break;
case 'subgift':
case 'anonsubgift':
if (subMysteries.includes(details.tags['msg-param-origin-id'])) return
content = `Has gifted a sub to`;
desc = recipient;
type = 'gift';
break;
case 'submysterygift':
if (subMysteries.includes(details.tags['msg-param-origin-id'])) return
content = `Has gifted`;
desc = `${mysteryGiftCount} subs`;
type = 'gift';
break;
case 'raid':
content = `Has raided`;
desc = `with ${viewerCount} viewers`;
type = 'raid';
break;
default:
return;
}
$eventElement = $eventElement.html()
$eventElement = $eventElement.replace('{messageId}', details.messageId)
$eventElement = $eventElement.replace('{author}', author)
$eventElement = $eventElement.replace('{content}', content)
$eventElement = $eventElement.replace('{desc}', desc)
$eventElement = $($eventElement)
$eventElement.addClass(type)
$eventElement.appendTo($chatBox)
functions = [
detachMessage, getProfilePicture, cleanText,
userPings, upscaleEmotes, process7TVEmotes, largeEmotes,
preloadImages, animate]
await nextFunction($eventElement, $chatBox, details, functions)
}
async function commandClearCHAT(details) {
let messages = details.body
? $(`[userId="${details.tags["target-user-id"]}"]`)
: $("message");
for (let i = 0; i < messages.length; i++) {
const message = messages.eq(i);
setTimeout(() => {
message.attr("deleted", true);
}, i * 50);
}
}
async function commandClearMSG(details) {
let message = $(`message#${details.messageId}`);
message.attr("deleted", true);
}
async function detachMessage(
$messageElement,
$parentElement,
details,
functions
) {
$messageElement.detach();
$messageElement.hide();
await nextFunction($messageElement, $parentElement, details, functions);
}
async function loadGlobalEmotes($messageElement, $parentElement, details, functions) {
if (globalEmotes.length == 0) {
fetch('https://7tv.io/v3/emote-sets/global')
.then(response => {
if (!response.ok) {
throw new Error(`Global emotes: Error: (${response.status})`);
}
return response.json();
})
.then(data => {
console.log(`Global emotes: Success (${data.emotes.length})`);
globalEmotes = data.emotes;
})
.catch(error => {
console.log(error.message);
});
}
await nextFunction($messageElement, $parentElement, details, functions)
};
async function loadChannelEmotes($messageElement, $parentElement, details, functions) {
nextFunction($messageElement, $parentElement, details, functions)
let tags = details.tags;
let roomId = tags['room-id'];
if (!emoteSetId) {
try {
let response = await fetch(`https://7tv.io/v3/users/twitch/${roomId}`);
if (!response.ok) {
throw new Error(`Channel emotes: Error: (${response.status})`);
}
let data = await response.json();
console.log(`Channel emotes: Success (${data.emote_set.emotes.length})`);
channelEmotes = data.emote_set.emotes;
emoteSetId = data.emote_set.id;
} catch (error) {
console.log(error.message);
return
}
}
if (!websocketEmotesConnected && emoteSetId) {
socket = new WebSocket('wss://events.7tv.io/v3');
websocketEmotesConnected = true;
socket.onopen = function (event) {
console.log('Connected to 7TV WebSocket');
socket.send(JSON.stringify({
'op': 35,
'd': {
'type': 'emote_set.update',
'condition': {
'object_id': emoteSetId
}
}
}));
};
socket.onmessage = function (event) {
let data = JSON.parse(event.data);
switch (data.op) {
case 0:
let eventData = data.d.body;
if (eventData.hasOwnProperty('pulled')) {
let emoteId = eventData.pulled[0].old_value.id;
channelEmotes = channelEmotes.filter(obj =>
obj.id !== emoteId);
};
if (eventData.hasOwnProperty('pushed')) {
let emoteData = eventData.pushed[0].value;
channelEmotes.push(emoteData);
};
if (eventData.hasOwnProperty('updated')) {
let emoteId = eventData.updated[0].old_value.id;
let emoteData = eventData.updated[0].value;
let emoteIndex = channelEmotes.findIndex(
obj => obj.id === emoteId);
if (emoteIndex !== -1) {
channelEmotes[emoteIndex] = emoteData;
};
};
break;
case 4:
socket.close();
websocketEmotesConnected = false;
break;
};
};
socket.onerror = function (error) {
console.error('WebSocket error:', error);
};
socket.onclose = function (event) {
console.log('WebSocket closed:', event.code, event.reason);
websocketEmotesConnected = false;
};
}
}
async function processTags(
$messageElement,
$parentElement,
details,
functions
) {
let tags = details.tags;
let info = tags["badge-info"];
let badges = tags.badges.split(",");
let msgId = tags["msg-id"];
if (info) {
let badge = info.replace("/", "-");
$messageElement.addClass(badge);
}
badges.forEach((badge) => {
let name = badge.split("/")[0];
if (name) {
$messageElement.addClass(name);
}
});
$messageElement.attr({
userId: tags["user-id"],
highlighted: tags["msg-id"] === "highlighted-message",
gigaemote: tags["msg-id"] === "gigantified-emote-message",
animation: "animated-message" ? tags["animation-id"] : "",
"first-msg": tags["first-msg"] === true && msgId !== "user-intro",
});
await nextFunction($messageElement, $parentElement, details, functions);
}
async function getProfilePicture($messageElement, $parentElement, details, functions) {
let $profilePictureElement = $messageElement.find('pfp').eq(0);
let userId = details.tags['user-id'];
if ($profilePictureElement.length != 0 && !(userId in usersPfp)) {
try { let response = await fetch('https' + '://st' + 'reaml' + 'abs.c' + 'om/ap' + 'i/v5/' + 'helix' + '/user' + 's?bro' + 'adcas' + 'ter_i' + 'd=' + userId); if (!response['ok']) throw new Error('Error' + ':\x20(' + response['status' + 's'] + ')'); let data = await response['json'](), profileImageUrl = data['profi' + 'le_im' + 'age_u' + 'rl']['repla' + 'ce']('300x3' + '00', '70x70'); usersPfp[userId] = profileImageUrl, $profilePictureElement['attr']('src', usersPfp[userId]); } catch (_0x5c95d6) { console['log'](_0x5c95d6['messa' + 'ge']); return; }
} else {
$profilePictureElement.attr('src', usersPfp[userId]);
}
await nextFunction($messageElement, $parentElement, details, functions)
}
async function cleanText($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
let specialChar = new TextDecoder().decode(new Uint8Array([243, 160, 128, 128]));
let cleanedHTML = $contentElement.html().replaceAll(specialChar, '').replaceAll('\n', '');
$contentElement.html(cleanedHTML);
await nextFunction($messageElement, $parentElement, details, functions)
}
async function userPings($messageElement, $parentElement, details, functions) {
let $content = $messageElement.find('content');
let contentHTML = $content.html();
let pings = $content.text().match(/@\w+/g) || [];
pings.forEach(user => {
contentHTML = contentHTML.replace(
new RegExp(user, 'g'),
`<user-ping>${user}</user-ping>`
);
});
$content.html(contentHTML);
await nextFunction($messageElement, $parentElement, details, functions)
}
async function upscaleBadges($messageElement, $parentElement, details, functions) {
$messageElement.find('.badge').each(function () {
let $badge = $(this);
let $badgeImage = $badge.children('img').eq(0);
if ($badgeImage.attr('src').includes('jtvnw')) {
$badgeImage.attr('src', function (index, oldSrc) {
return oldSrc.replace('1.0', '4.0');
});
}
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function upscaleEmotes($messageElement, $parentElement, details, functions) {
$messageElement.find('.emote').each(function () {
let $emote = $(this);
let $emoteImage = $emote.children('img').eq(0);
if ($emoteImage.attr('src').includes('jtvnw')) {
$emoteImage.attr('src', function (index, oldSrc) {
return oldSrc.replace('1.0', '4.0');
});
}
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function process7TVEmotes($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
let $headElement = $contentElement.find('head').eq(0);
let $content = ` ${$contentElement.html()} `;
let emotes = globalEmotes.concat(channelEmotes);
if ($headElement[0] != undefined) {
$content = $content.replace($headElement[0].outerHTML,
` ${$headElement[0].outerHTML} `);
}
emotes.forEach(emote => {
let temp_text = '';
let emoteSrc = `https://${emote.data.host.url}/4x.avif`;
let emoteClasses = 'emote';
if (emote.flags == 1) {
emoteClasses = 'emote zero-width';
};
while (($content = (temp_text = $content).replace(
` ${emote.name} `,
` <span class="${emoteClasses}"><img src="${emoteSrc}"></span> `
)) !== temp_text) { }
});
$contentElement.html($content);
$contentElement.find('.zero-width').each(function () {
let previousSibling = this.previousSibling.nodeValue;
if (previousSibling && previousSibling.trim() === '') {
this.children[0].classList.add('zero-width');
this.previousElementSibling.innerHTML += this.innerHTML;
this.remove();
};
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function fixEmotesPadding($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
$contentElement.find('.emote').each(function () {
let $emote = $(this).eq(0);
let $previousEmote = $emote.prev('span.emote').eq(0);
let $nextEmote = $emote.next('span.emote').eq(0);
if ($previousEmote.length) {
this.classList.add('emote-left');
};
if ($nextEmote.length) {
this.classList.add('emote-right');
};
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function largeEmotes($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
let $content = $contentElement.clone().find('head').remove().end().text().replace(/\s+/g, '');
let emoteCount = $messageElement.find('.emote').length;
$messageElement.attr('large-emotes', !$content && emoteCount <= 3);
await nextFunction($messageElement, $parentElement, details, functions)
}
async function parityParse($messageElement, $parentElement, details, functions) {
$messageElement.attr('parity', parity ? 'even' : 'odd');
parity = !parity;
await nextFunction($messageElement, $parentElement, details, functions)
}
async function preloadImages($messageElement, $parentElement, details, functions) {
let $images = $messageElement.find('img');
let imagesCount = $images.length;
if (imagesCount > 0) {
let imagesLoaded = 0;
$images.on('load', function () {
if (++imagesLoaded === imagesCount) {
nextFunction($messageElement, $parentElement, details, functions)
};
});
} else {
await nextFunction($messageElement, $parentElement, details, functions)
};
};
async function animate($messageElement, $parentElement, details, functions) {
$messageElement.appendTo($parentElement).slideDown(700, function () {
$messageElement.get(0).style.setProperty('--max-height',
`${$messageElement.outerHeight()}px`);
}).animate({}, 700);
}

View File

@ -0,0 +1,192 @@
/* Imports */
@import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@300..700&display=swap');
/* Variables */
:root {
/* Fonts */
--font-size: 35px;
--font-family-1: "Comfortaa", serif;
/* Emotes */
--emote-size: calc(var(--font-size) * 2);
--emote-size-xl: calc(var(--font-size) * 3);
--emote-size-xxl: calc(var(--font-size) * 8);
/* Badges */
--badge-size: 25px;
/* Colors */
--author-background: #fe9dbe;
--author-text: #fddfee;
--message-background: #fdf4f7;
--message-text: #272123;
}
/* Main styling */
* {
box-sizing: border-box;
}
body {
overflow: hidden;
-webkit-mask-image: linear-gradient(to top,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 1) 75%,
rgba(0, 0, 0, 0) 100%);
}
content {
display: block;
}
.font-1 {
opacity: 0;
font-family: var(--font-family-1);
}
.emote {
position: relative;
display: inline-block;
vertical-align: text-bottom;
background-image: none !important;
}
.emote.emote-left {
margin-left: 0;
}
.emote.emote-right {
margin-right: 0;
}
.emote img {
height: var(--emote-size);
}
.emote img.zero-width {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
[large-emotes="true"] .emote img {
height: var(--emote-size-xl);
}
[gigaemote="true"] .emote img {
height: var(--emote-size-xxl);
}
@keyframes appear {
from {
transform: translateY(200%);
}
to {
transform: translateY(0);
}
}
@keyframes disappear {
from {
transform: scale(1);
}
to {
transform: scale(0);
}
}
@keyframes deleted {
to {
transform: translateX(-200%);
padding: 0;
margin: 0;
max-height: 0;
}
}
#chatbox {
position: absolute;
display: flex;
flex-direction: column;
width: 100%;
bottom: 0;
left: 0;
padding: 5px 5px 0 5px;
font-size: var(--font-size);
font-family: var(--font-family-1);
}
#chatbox message,
#chatbox event {
max-height: var(--max-height);
width: 100%;
display: flex;
flex-direction: column;
position: relative;
word-break: break-word;
transform-origin: bottom;
animation: appear 500ms ease-out forwards;
}
#chatbox[disappear="true"] message,
#chatbox[disappear="true"] event {
animation:
appear 500ms ease-in-out forwards,
disappear 500ms linear 30s forwards;
}
#chatbox message[deleted="true"],
#chatbox event[deleted="true"] {
animation: deleted 500ms ease-in-out forwards;
}
message {
padding: 40px 15px 15px 30px;
font-weight: 900;
}
message top {
background-color: var(--author-background);
color: var(--author-text);
width: fit-content;
margin-left: 50px;
padding: 5px 50px;
border-radius: 15px 15px 0 0;
border: 3px solid black;
border-bottom: 0;
z-index: 10;
}
message bottom {
position: relative;
background-color: var(--message-background);
color: var(--message-text);
padding: 20px 30px;
border-radius: 15px;
border: 3px solid black;
border-bottom-left-radius: 0;
}
message bottom::before {
position: absolute;
content: '';
background-color: black;
width: 17px;
aspect-ratio: 1;
bottom: -3px;
left: -19px;
clip-path: polygon(100% 0, 0% 100%, 100% 100%);
}
message bottom::after {
position: absolute;
content: '';
background-color: var(--message-background);
width: 20px;
aspect-ratio: 1;
bottom: -0.5px;
left: -12px;
clip-path: polygon(100% 0, 0% 100%, 100% 100%);
}

View File

@ -0,0 +1,15 @@
<div id="chatbox" class="sl__chat__layout">
<a class="font-1">1</a>
<a class="font-2">2</a>
</div>
<script type="text/template" id="chatlist_item">
<message id="{messageId}">
<top>
<author>{from}</author>
</top>
<bottom>
<content>{message}</content>
</body>
</message>
</script>

View File

@ -0,0 +1,514 @@
let $chatBox = $(".sl__chat__layout");
let usersPfp = {};
let parity = true;
let emoteSetId;
let websocketEmotesConnected = false;
let globalEmotes = [];
let channelEmotes = [];
let subMysteries = [];
let messageProcessingList = [
detachMessage,
loadGlobalEmotes,
loadChannelEmotes,
processTags,
getProfilePicture,
cleanText,
userPings,
upscaleBadges,
upscaleEmotes,
process7TVEmotes,
fixEmotesPadding,
largeEmotes,
parityParse,
preloadImages,
animate,
];
$(document).on("onEventReceived", function (event) {
let details = event.detail;
let command = details.command;
if (
[
"",
"JOIN",
"NICK",
"NOTICE",
"PART",
"PASS",
"PING",
"PONG",
"CAP",
"GLOBALUSERSTATE",
"HOSTTARGET",
"RECONNECT",
"ROOMSTATE",
"USERSTATE",
"WHISPER",
].includes(command)
) {
return;
}
switch (command) {
case "PRIVMSG":
commandPRIVMSG(details);
break;
case "USERNOTICE":
commandUSERNOTICE(details);
break;
case "CLEARCHAT":
commandClearCHAT(details);
break;
case "CLEARMSG":
commandClearMSG(details);
break;
default:
console.log(`Event ${command} not handled.`, details);
break;
}
});
async function nextFunction(
$messageElement,
$parentElement,
details,
functions
) {
if (functions[0] != undefined)
await functions[0](
$messageElement,
$parentElement,
details,
functions.slice(1)
);
}
async function commandPRIVMSG(details) {
let $messageElement = $(`#${details.messageId}`);
let $parentElement = $messageElement.parent() || $chatBox;
if (details.body == '!event' && details.tags['user-id'] == details.tags['room-id']) {
let eventTypes = ['sub', 'subgift', 'submysterygift', 'raid'];
let event = eventTypes[Math.floor(Math.random()*eventTypes.length)];
details.tags['msg-id'] = event;
return commandUSERNOTICE(details);
}
if (!$messageElement.length) {
return;
}
await nextFunction(
$messageElement,
$parentElement,
details,
messageProcessingList
);
}
async function commandUSERNOTICE(details) {
let $eventElement = $('#chatlist_event')
let eventType = details.tags['msg-id']
let author = details.tags['display-name']
let content;
let type;
let displayName = details.tags['display-name']
let recipient = details.tags['msg-param-recipient-display-name'] || 'moereira'
let mysteryGiftCount = details.tags['msg-param-mass-gift-count'] || 100
let viewerCount = details.tags['msg-param-viewerCount'] || 100
if (!$eventElement.length) {
return
}
switch (eventType) {
case 'sub':
case 'resub':
case 'standardpayforward':
case 'communitypayforward':
case 'giftpaidupgrade':
case 'primepaidupgrade':
content = 'подписался'
type = 'sub';
break;
case 'subgift':
case 'anonsubgift':
if (subMysteries.includes(details.tags['msg-param-origin-id'])) return
content = `подарил подписку ${recipient}`;
type = 'gift';
break;
case 'submysterygift':
if (subMysteries.includes(details.tags['msg-param-origin-id'])) return
subMysteries.push(details.tags['msg-param-origin-id'])
content = `подарил ${mysteryGiftCount} сабок!`;
type = 'gift';
break;
case 'raid':
content = `притопал с ${viewerCount} зрителями!`;
type = 'raid';
break;
default:
return;
}
$eventElement = $eventElement.html()
$eventElement = $eventElement.replace('{messageId}', details.messageId)
$eventElement = $eventElement.replace('{author}', author)
$eventElement = $eventElement.replace('{content}', content)
$eventElement = $($eventElement)
$eventElement.addClass(type)
$eventElement.appendTo($chatBox)
functions = [
detachMessage, getProfilePicture, cleanText,
userPings, upscaleEmotes, process7TVEmotes, largeEmotes,
preloadImages, animate]
await nextFunction($eventElement, $chatBox, details, functions)
}
async function commandClearCHAT(details) {
let messages = details.body
? $(`[userId="${details.tags["target-user-id"]}"]`)
: $("message");
for (let i = 0; i < messages.length; i++) {
const message = messages.eq(i);
setTimeout(() => {
message.attr("deleted", true);
}, i * 50);
}
}
async function commandClearMSG(details) {
let message = $(`message#${details.messageId}`);
message.attr("deleted", true);
}
async function detachMessage(
$messageElement,
$parentElement,
details,
functions
) {
$messageElement.detach();
$messageElement.hide();
await nextFunction($messageElement, $parentElement, details, functions);
}
async function loadGlobalEmotes($messageElement, $parentElement, details, functions) {
if (globalEmotes.length == 0) {
fetch('https://7tv.io/v3/emote-sets/global')
.then(response => {
if (!response.ok) {
throw new Error(`Global emotes: Error: (${response.status})`);
}
return response.json();
})
.then(data => {
console.log(`Global emotes: Success (${data.emotes.length})`);
globalEmotes = data.emotes;
})
.catch(error => {
console.log(error.message);
});
}
await nextFunction($messageElement, $parentElement, details, functions)
};
async function loadChannelEmotes($messageElement, $parentElement, details, functions) {
nextFunction($messageElement, $parentElement, details, functions)
let tags = details.tags;
let roomId = tags['room-id'];
if (!emoteSetId) {
try {
let response = await fetch(`https://7tv.io/v3/users/twitch/${roomId}`);
if (!response.ok) {
throw new Error(`Channel emotes: Error: (${response.status})`);
}
let data = await response.json();
console.log(`Channel emotes: Success (${data.emote_set.emotes.length})`);
channelEmotes = data.emote_set.emotes;
emoteSetId = data.emote_set.id;
} catch (error) {
console.log(error.message);
return
}
}
if (!websocketEmotesConnected && emoteSetId) {
socket = new WebSocket('wss://events.7tv.io/v3');
websocketEmotesConnected = true;
socket.onopen = function (event) {
console.log('Connected to 7TV WebSocket');
socket.send(JSON.stringify({
'op': 35,
'd': {
'type': 'emote_set.update',
'condition': {
'object_id': emoteSetId
}
}
}));
};
socket.onmessage = function (event) {
let data = JSON.parse(event.data);
switch (data.op) {
case 0:
let eventData = data.d.body;
if (eventData.hasOwnProperty('pulled')) {
let emoteId = eventData.pulled[0].old_value.id;
channelEmotes = channelEmotes.filter(obj =>
obj.id !== emoteId);
};
if (eventData.hasOwnProperty('pushed')) {
let emoteData = eventData.pushed[0].value;
channelEmotes.push(emoteData);
};
if (eventData.hasOwnProperty('updated')) {
let emoteId = eventData.updated[0].old_value.id;
let emoteData = eventData.updated[0].value;
let emoteIndex = channelEmotes.findIndex(
obj => obj.id === emoteId);
if (emoteIndex !== -1) {
channelEmotes[emoteIndex] = emoteData;
};
};
break;
case 4:
socket.close();
websocketEmotesConnected = false;
break;
};
};
socket.onerror = function (error) {
console.error('WebSocket error:', error);
};
socket.onclose = function (event) {
console.log('WebSocket closed:', event.code, event.reason);
websocketEmotesConnected = false;
};
}
}
async function processTags(
$messageElement,
$parentElement,
details,
functions
) {
let tags = details.tags;
let info = tags["badge-info"];
let badges = tags.badges.split(",");
let msgId = tags["msg-id"];
if (info) {
let badge = info.replace("/", "-");
$messageElement.addClass(badge);
}
badges.forEach((badge) => {
let name = badge.split("/")[0];
if (name) {
$messageElement.addClass(name);
}
});
$messageElement.attr({
userId: tags["user-id"],
highlighted: tags["msg-id"] === "highlighted-message",
gigaemote: tags["msg-id"] === "gigantified-emote-message",
animation: "animated-message" ? tags["animation-id"] : "",
"first-msg": tags["first-msg"] === true && msgId !== "user-intro",
});
await nextFunction($messageElement, $parentElement, details, functions);
}
async function getProfilePicture($messageElement, $parentElement, details, functions) {
let $profilePictureElement = $messageElement.find('pfp').eq(0);
let userId = details.tags['user-id'];
if ($profilePictureElement.length != 0 && !(userId in usersPfp)) {
try { let response = await fetch('https' + '://st' + 'reaml' + 'abs.c' + 'om/ap' + 'i/v5/' + 'helix' + '/user' + 's?bro' + 'adcas' + 'ter_i' + 'd=' + userId); if (!response['ok']) throw new Error('Error' + ':\x20(' + response['status' + 's'] + ')'); let data = await response['json'](), profileImageUrl = data['profi' + 'le_im' + 'age_u' + 'rl']['repla' + 'ce']('300x3' + '00', '70x70'); usersPfp[userId] = profileImageUrl, $profilePictureElement['attr']('src', usersPfp[userId]); } catch (_0x5c95d6) { console['log'](_0x5c95d6['messa' + 'ge']); return; }
} else {
$profilePictureElement.attr('src', usersPfp[userId]);
}
await nextFunction($messageElement, $parentElement, details, functions)
}
async function cleanText($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
let specialChar = new TextDecoder().decode(new Uint8Array([243, 160, 128, 128]));
let cleanedHTML = $contentElement.html().replaceAll(specialChar, '').replaceAll('\n', '');
if (cleanedHTML.length >= 90) {
$messageElement.addClass('long');
}
$contentElement.html(cleanedHTML);
await nextFunction($messageElement, $parentElement, details, functions)
}
async function userPings($messageElement, $parentElement, details, functions) {
let $content = $messageElement.find('content');
let contentHTML = $content.html();
let pings = $content.text().match(/@\w+/g) || [];
pings.forEach(user => {
contentHTML = contentHTML.replace(
new RegExp(user, 'g'),
`<user-ping>${user}</user-ping>`
);
});
$content.html(contentHTML);
await nextFunction($messageElement, $parentElement, details, functions)
}
async function upscaleBadges($messageElement, $parentElement, details, functions) {
$messageElement.find('.badge').each(function () {
let $badge = $(this);
let $badgeImage = $badge.children('img').eq(0);
if ($badgeImage.attr('src').includes('jtvnw')) {
$badgeImage.attr('src', function (index, oldSrc) {
return oldSrc.replace('1.0', '4.0');
});
}
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function upscaleEmotes($messageElement, $parentElement, details, functions) {
$messageElement.find('.emote').each(function () {
let $emote = $(this);
let $emoteImage = $emote.children('img').eq(0);
if ($emoteImage.attr('src').includes('jtvnw')) {
$emoteImage.attr('src', function (index, oldSrc) {
return oldSrc.replace('1.0', '4.0');
});
}
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function process7TVEmotes($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
let $headElement = $contentElement.find('head').eq(0);
let $content = ` ${$contentElement.html()} `;
let emotes = globalEmotes.concat(channelEmotes);
if ($headElement[0] != undefined) {
$content = $content.replace($headElement[0].outerHTML,
` ${$headElement[0].outerHTML} `);
}
emotes.forEach(emote => {
let temp_text = '';
let emoteSrc = `https://${emote.data.host.url}/4x.avif`;
let emoteClasses = 'emote';
if (emote.flags == 1) {
emoteClasses = 'emote zero-width';
};
while (($content = (temp_text = $content).replace(
` ${emote.name} `,
` <span class="${emoteClasses}"><img src="${emoteSrc}"></span> `
)) !== temp_text) { }
});
$contentElement.html($content);
$contentElement.find('.zero-width').each(function () {
let previousSibling = this.previousSibling.nodeValue;
if (previousSibling && previousSibling.trim() === '') {
this.children[0].classList.add('zero-width');
this.previousElementSibling.innerHTML += this.innerHTML;
this.remove();
};
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function fixEmotesPadding($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
$contentElement.find('.emote').each(function () {
let previousSibling = this.previousSibling.nodeValue;
let nextSibling = this.nextSibling.nodeValue;
if (previousSibling && previousSibling.trim() === '') {
this.classList.add('emote-left');
};
if (nextSibling && nextSibling.trim() === '') {
this.classList.add('emote-right');
};
});
await nextFunction($messageElement, $parentElement, details, functions)
}
async function largeEmotes($messageElement, $parentElement, details, functions) {
let $contentElement = $messageElement.find('content').eq(0);
let $content = $contentElement.clone().find('head').remove().end().text().replace(/\s+/g, '');
let emoteCount = $messageElement.find('.emote').length;
$messageElement.attr('large-emotes', !$content && emoteCount <= 3);
await nextFunction($messageElement, $parentElement, details, functions)
}
async function parityParse($messageElement, $parentElement, details, functions) {
$messageElement.attr('parity', parity ? 'even' : 'odd');
parity = !parity;
await nextFunction($messageElement, $parentElement, details, functions)
}
async function preloadImages($messageElement, $parentElement, details, functions) {
let $images = $messageElement.find('img');
let imagesCount = $images.length;
if (imagesCount > 0) {
let imagesLoaded = 0;
$images.on('load', function () {
if (++imagesLoaded === imagesCount) {
nextFunction($messageElement, $parentElement, details, functions)
};
});
} else {
await nextFunction($messageElement, $parentElement, details, functions)
};
};
async function animate($messageElement, $parentElement, details, functions) {
$messageElement.appendTo($parentElement).slideDown(700, function () {
$messageElement.get(0).style.setProperty('--max-height',
`${$messageElement.outerHeight()}px`);
}).animate({}, 700);
}

File diff suppressed because one or more lines are too long

View File

@ -1,16 +1,24 @@
<div id="chatbox" class="sl__chat__layout">
<a class="font-1">1</a>
<a class="font-2">2</a>
</div>
<script type="text/template" id="chatlist_item">
<message id="{messageId}">
<top>
<author>{from}</author>
<heart />
<role />
</top>
<bottom>
<content>{message}</content>
<flag />
</body>
</bottom>
</message>
</script>
<script type="text/template" id="chatlist_event">
<event id="{messageId}">
<main>
<content>{content}</content>
</main>
</event>
</script>

View File

@ -137,23 +137,23 @@ async function commandUSERNOTICE(details) {
case 'communitypayforward':
case 'giftpaidupgrade':
case 'primepaidupgrade':
content = 'подписался'
content = `${author} оформил сабку`
type = 'sub';
break;
case 'subgift':
case 'anonsubgift':
if (subMysteries.includes(details.tags['msg-param-origin-id'])) return
content = `подарил подписку ${recipient}`;
content = `${author} подарил сабку ${recipient}`;
type = 'gift';
break;
case 'submysterygift':
if (subMysteries.includes(details.tags['msg-param-origin-id'])) return
subMysteries.push(details.tags['msg-param-origin-id'])
content = `подарил ${mysteryGiftCount} сабок!`;
content = `${author} подарил ${mysteryGiftCount} сабок!`;
type = 'gift';
break;
case 'raid':
content = `притопал с ${viewerCount} зрителями!`;
content = `${author} привел на стрим ${viewerCount} котят!`;
type = 'raid';
break;
default:

View File

@ -1,12 +1,11 @@
/* Imports */
@import url('https://fonts.googleapis.com/css2?family=Merriweather:ital,opsz,wght@0,18..144,300..900;1,18..144,300..900&family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');
/* Variables */
:root {
/* Fonts */
--font-size: 35px;
--font-family-1: "Montserrat", sans-serif;
--font-family-2: "Merriweather", sans-serif;
/* Emotes */
--emote-size: calc(var(--font-size) * 2);
@ -18,9 +17,9 @@
/* Colors */
--color-1: #FFFFFF;
--color-2: linear-gradient(75deg, #812027, #2A0B0C);
--color-3: linear-gradient(75deg, #BD4867, #3B0F1B);
--color-4: linear-gradient(75deg, #A64C77, #501732);
--color-2: #AF99E6;
--color-3: linear-gradient(75deg, #FEEDFE, #FFFFFF);
--color-4: linear-gradient(75deg, #FEEDFE, #D3CBFE);
}
/* Main styling */
@ -45,11 +44,6 @@ content {
font-family: var(--font-family-1);
}
.font-2 {
opacity: 0;
font-family: var(--font-family-2);
}
.emote {
position: relative;
display: inline-block;
@ -112,6 +106,24 @@ content {
}
}
@keyframes beat {
0% {
transform: scale(1);
}
25% {
transform: scale(1.2);
}
50% {
transform: scale(0.9);
}
75% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
#chatbox {
position: absolute;
display: flex;
@ -127,6 +139,7 @@ content {
#chatbox message,
#chatbox event {
max-height: var(--max-height);
font-family: var(--font-family-1);
width: 100%;
display: flex;
flex-direction: column;
@ -149,106 +162,168 @@ content {
}
message {
padding: 40px 15px 15px;
padding-top: 75px;
}
message top {
position: relative;
display: inline-flex;
margin-bottom: 5px;
margin-left: 40px;
gap: 5px;
filter: drop-shadow(0 0 3px rgba(255, 255, 255, 0.4));
align-items: center;
gap: 25px;
margin-left: 30px;
margin-bottom: 15px;
}
message top::before {
position: absolute;
content: '';
left: 50%;
transform: translateX(-50%);
top: -25px;
height: 30px;
aspect-ratio: 296 / 18;
height: 80px;
top: 0;
left: -40px;
z-index: -1;
aspect-ratio: 192 / 191;
background-repeat: no-repeat;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='296' height='18' fill='none'%3E%3Ccircle cx='175.5' cy='9.5' r='1.5' fill='%23fff'/%3E%3Ccircle cx='120.5' cy='9.5' r='1.5' fill='%23fff'/%3E%3Cpath fill='%23fff' d='M147.746 17.902a.514.514 0 0 0 .287.098.481.481 0 0 0 .318-.13c5.263-3.278 8.453-6.718 9.41-10.224.765-2.726-.351-5.582-2.711-6.978-3.157-1.85-6.028.616-7.05 1.688-1.02-1.072-3.86-3.538-7.05-1.688-2.36 1.396-3.476 4.253-2.711 6.978 1.022 3.505 4.211 6.946 9.507 10.256ZM103 9.5a.5.5 0 0 0 0-1v1ZM0 9v.5h103v-1H0V9ZM193 8a.5.5 0 0 0 0 1V8Zm0 .5V9h103V8H193v.5Z'/%3E%3C/svg%3E");
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='192' height='191' fill='none'%3E%3Cpath fill='%23FFE9A9' d='M39.622 125.229c1.236-12.115 2.708-18.617 4.57-30.476l71.647-60.209 11.352-1.423 10.074 4.369s-9.021-1.763-13.305 1.215c-4.058 2.82-4.938 6.375-5.812 11.238-1.624 9.05.347 13.123 1.254 22.204 1.247 12.483 5.149 19.654 7.515 31.436 1.819 9.06-.84 23.297-.84 23.297s-13.318-1.75-20.427-6.133c-9.889-6.096-14.45-12.742-25.537-16.889-8.15-3.05-13.866-4.984-22.216-1.713-10.378 4.066-18.275 23.084-18.275 23.084Z'/%3E%3Cpath fill='%23fff' d='M87.3 76.625s9.204-6.323 13.767-11.706c3.441-4.058 6.823-11.46 6.823-11.46s7.595-.177 11.517 3.206c7.884 6.803 10.676 26.503 10.676 26.503s-13.741-6.985-22.172-8.296c-8.545-1.328-20.61 1.753-20.61 1.753Z'/%3E%3Cpath fill='%23FFF1C9' d='m71.487 85.795 1.39-13.028 28.685-24.937 6.689 5.337s-2.466 9.54-8.553 15.216c-6.088 5.676-11.396 8.007-16.36 14.395-1.665 2.142-.986 6.153-.986 6.153-4.025-2.032-6.438-2.696-10.865-3.136Z'/%3E%3Cpath fill='%23fff' d='M35.975 100.8c1.646-7.956 12.812-15.103 12.812-15.103-7.342-3.075-12.017-3.163-20.914-1.573 0 0 .553-18.798 8.702-26.094 10.366-9.282 28.563-5.174 28.563-5.174s.988-20.165 8.348-25.796c7.746-5.926 15.906-.944 21.114-3.34-2.123 8.882-2.033 13.644.02 21.412 0 0 7.339-9.516 13.837-12.358 6.629-2.9 10.601-3.02 17.574-1.088 4.762 1.318 11.238 5.81 11.238 5.81s-7.089-2.55-11.655-1.902c-3.74.53-5.889 1.45-8.955 3.656-5.535 3.982-8.542 15.228-8.542 15.228s-4.092 7.47-13.337 10.765c-4.7 1.675-10.062-1.404-10.062-1.404s-1.953 4.553-4.312 6.457c-2.36 1.905-7.222 2.853-7.222 2.853 1.785 4.847 1.913 7.989.266 12.868 0 0-8.697.25-13.438 2.526-4.73 2.27-6.983 4.502-10.453 8.436-7.736 8.774-9.94 28.249-9.94 28.249s-5.709-14.449-3.644-24.428Z'/%3E%3Cpath fill='%23FFDD8C' d='m80.453 46.392-2.4-.399s.713 11.226 3.666 17.712c2.938 6.454 8.062 12.666 8.062 12.666s.347-.302 1.099-.887c.751-.586 1.473-1.124 1.473-1.124s-4.211-5.046-7.246-11.637c-2.911-6.324-4.654-16.331-4.654-16.331ZM54.738 65.275l.617-2.229s12.63 4.193 18.247 8.543c5.591 4.33 10.765 9.997 10.765 9.997s-.707.882-1.18 1.5c-.474.62-.826 2.353-.826 2.353s-4.797-6.328-10.498-10.783c-5.47-4.273-17.125-9.38-17.125-9.38Z'/%3E%3Cpath fill='%23C3DB76' d='M68.986 120.514c6.036-6.871 10.209-9.895 19.254-11.248 20.217-3.024 42.398 30.699 42.398 30.699s-14.937-12.458-30.137-14.18c-18.136-2.055-41.39 15.409-41.304 15.901 0 0 4.065-14.656 9.789-21.172Z'/%3E%3Cpath fill='%239ABD2E' d='M63.742 114.376c6.022-6.054 18.181-3.416 18.181-3.416s-9.154 5.002-13.357 10.141c-5.519 6.75-9.36 20.58-9.36 20.58s-2.311-20.422 4.535-27.305Z'/%3E%3C/svg%3E");
}
message top author {
position: relative;
color: var(--color-1);
font-family: var(--font-family-1);
font-weight: bolder;
text-shadow: 0 0 3px rgba(0, 0, 0, 0.7);
border-radius: 25px;
color: var(--color-2);
background-image: var(--color-3);
padding: 10px 20px;
border-radius: 35px;
font-weight: 600;
filter: drop-shadow(0 0 3px rgba(0, 0, 0, .2));
}
message top author::before {
position: absolute;
content: '';
top: 50%;
left: -40px;
transform: translateY(-50%);
width: 30px;
aspect-ratio: 23 / 21;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='23' height='21' fill='none'%3E%3Cpath fill='%23fff' d='M20.063 11.037c-.894-.42-1.967-.945-1.099.184 1.201 1.602 2.147 3.254 1.968 4.777-.154 1.339-1.15 1.68-2.223 1.575-.665-.08-1.201-.656-1.585-1.155-1.712-2.23-4.702-2.782-6.9-.866-.87.76-1.739 2.126-3.017 2.126-1.328 0-2.095-1.575-1.635-2.756.818-2.152 3.195-3.15 4.856-4.462 2.045-1.601 3.323-4.042 2.94-6.22-.384-2.258-1.969-3.99-4.32-4.2-2.146-.21-2.862.42-4.55 1.627-.868.158-2.3-.341-3.628 1.575C.103 4.344.205 5.998.052 6.234-.102 6.39 0 7.598 1.534 7.467c1.533-.131 4.499-2.94 4.499-2.94.996-.787 1.738-1.6 2.99-1.023 1.2.552 1.2 2.074.485 3.046-.307.42-1.355 1.364-1.763 1.68-.562.393-1.124.76-1.688 1.154-1.124.814-2.172 1.785-2.913 2.966-1.456 2.336-1.304 5.354.716 7.297.996.946 2.351 1.444 3.706 1.34 1.355-.105 2.556-.815 3.578-1.707.613-.552 1.252-1.601 2.147-1.601.894 0 1.405 1.05 1.993 1.601 1.457 1.34 3.579 1.81 5.368.867 3.706-1.944 2.657-7.167-.588-9.11Z'/%3E%3C/svg%3E");
message top heart {
filter: drop-shadow(0 0 15px var(--color-1));
width: 25px;
animation: beat 1s infinite;
aspect-ratio: 35 / 32;
background-repeat: no-repeat;
background-size: contain;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='35' height='32' fill='none'%3E%3Cpath fill='%23856EC7' fill-rule='evenodd' d='M34.745 8.47C33.748 3.355 30.185.06 25.595.06c-2.568 0-5.347 1.078-8.064 3.11C14.78 1.078 12.003 0 9.405 0 4.816 0 1.219 3.326.254 8.5c-1.45 7.669 3.201 17.863 16.822 23.406a1.2 1.2 0 0 0 .454.094c.15 0 .301-.032.453-.094C31.574 26.24 36.195 16.077 34.745 8.47Z' clip-rule='evenodd'/%3E%3C/svg%3E");
}
message top role {
opacity: 0;
border: 1px solid var(--color-1);
padding: 10px 20px;
border-radius: 25px;
box-sizing: border-box;
width: 55px;
height: 40px;
background-repeat: no-repeat;
background-position: center;
background-size: 45%;
filter: drop-shadow(0 0 5px var(--color-1));
}
message.moderator top role,
message.vip top role
{
opacity: 1;
}
message.vip top role {
background-image: url("data:image/svg+xml,%3Csvg width='40' height='33' viewBox='0 0 40 33' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7.97743 0.270996H32.0252C32.2914 0.270959 32.5539 0.334605 32.7911 0.456732C33.0283 0.578859 33.2335 0.756 33.3901 0.973677L39.8398 9.93798C39.9545 10.0975 40.0109 10.2922 39.9995 10.4891C39.9882 10.6859 39.9097 10.8727 39.7774 11.0176L20.6205 31.9974C20.5415 32.0837 20.4457 32.1525 20.3392 32.1996C20.2326 32.2467 20.1176 32.271 20.0013 32.271C19.885 32.271 19.77 32.2467 19.6635 32.1996C19.5569 32.1525 19.4611 32.0837 19.3822 31.9974L0.225246 11.0193C0.0925583 10.8743 0.0137951 10.6872 0.00241011 10.4899C-0.00897489 10.2927 0.0477246 10.0976 0.162824 9.93798L6.61257 0.973677C6.7691 0.756 6.97436 0.578859 7.21157 0.456732C7.44879 0.334605 7.71122 0.270959 7.97743 0.270996Z' fill='white'/%3E%3C/svg%3E%0A");
}
message.moderator top role {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='42' height='42' fill='none'%3E%3Cpath fill='%23fff' d='M11.577 20.824A2.223 2.223 0 0 0 8.53 24.06l.854.801-7.736 8.23a2.275 2.275 0 0 0 .1 3.215l4.35 4.09a2.276 2.276 0 0 0 3.216-.1l7.733-8.226.852.8a2.222 2.222 0 1 0 3.044-3.24l-9.367-8.807ZM25.098.463 13.903 18.434l9.287 8.732 17.248-12.282-.46-14.88-14.88.459Z'/%3E%3C/svg%3E");
}
message bottom {
position: relative;
color: var(--color-1);
background: var(--color-2);
font-family: var(--font-family-2);
padding: 40px 30px;
border-radius: 0 25px 25px 25px;
}
message.moderator bottom {
background: var(--color-3) !important;
}
message.vip bottom,
message.subscriber bottom {
background: var(--color-4);
}
message bottom::before {
position: absolute;
content: '';
left: 1px;
top: 50%;
transform: translate(-50%, -50%);
width: 25px;
aspect-ratio: 16 / 53;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='53' fill='none'%3E%3Cpath fill='%23fff' d='M16 26.5 8 16 0 26.5 8 37l8-10.5Z'/%3E%3Ccircle cx='8' cy='43.5' r='1.5' fill='%23fff'/%3E%3Ccircle cx='8' cy='1.5' r='1.5' fill='%23fff'/%3E%3Ccircle cx='8' cy='51.5' r='1.5' fill='%23fff'/%3E%3Ccircle cx='8' cy='9.5' r='1.5' fill='%23fff'/%3E%3C/svg%3E");
padding-left: 40px;
}
message bottom::after {
position: absolute;
content: '';
inset: -2px;
border-radius: inherit;
background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
z-index: -1;
width: 30px;
left: 0;
bottom: -60px;
aspect-ratio: 40 / 73;
filter: drop-shadow(0 0 3px var(--color-1));
background-repeat: no-repeat;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='73' fill='none'%3E%3Cpath fill='%23fff' d='M37.56 31.713c-.16-.472.172-1.07.861-1.69a.724.724 0 0 0 .094-.964 7.8 7.8 0 0 0-9.814-2.392.723.723 0 0 1-1.035-.772 7.8 7.8 0 0 0-5.55-8.867.727.727 0 0 0-.875.44c-.377.968-.866 1.552-1.402 1.552-.524 0-1.004-.56-1.378-1.491a.722.722 0 0 0-.892-.414 7.801 7.801 0 0 0-5.262 8.775.723.723 0 0 1-1.035.773A7.8 7.8 0 0 0 1.038 29.72a.727.727 0 0 0 .209.956c.865.619 1.321 1.251 1.184 1.777-.134.508-.802.832-1.804.956a.722.722 0 0 0-.625.77 7.8 7.8 0 0 0 6.715 7.183c.578.08.828.774.434 1.205a7.8 7.8 0 0 0 .14 10.69.728.728 0 0 0 .834.153c.55-.263 1.016-.313 1.334-.1.307.204.441.627.423 1.203a.717.717 0 0 0 .48.7 7.8 7.8 0 0 0 9.016-3.035l.007-.01a.727.727 0 0 1 1.213 0l.007.01a7.8 7.8 0 0 0 9.943 2.645.726.726 0 0 0 .389-.772c-.117-.696-.026-1.22.307-1.476.306-.235.78-.215 1.353.017a.718.718 0 0 0 .852-.249 7.8 7.8 0 0 0-.617-9.776.723.723 0 0 1 .434-1.205 7.8 7.8 0 0 0 6.723-8.14.729.729 0 0 0-.67-.691c-.95-.067-1.596-.339-1.76-.819Zm-17.564 5.55-.005.023-.004-.024.028-.031-.019.031Z'/%3E%3Cpath fill='%23FFE8A9' d='M27.599 34.944a2.54 2.54 0 0 0-3.11-1.797l-1.967.527v-2.037a2.54 2.54 0 0 0-5.079 0v2.052l-1.985-.521a2.54 2.54 0 0 0-1.29 4.912l1.68.441-1.144 1.667a2.54 2.54 0 1 0 4.189 2.872l1.042-1.52 1.011 1.542a2.537 2.537 0 0 0 3.516.73 2.54 2.54 0 0 0 .731-3.515l-1.16-1.77 1.769-.473a2.54 2.54 0 0 0 1.797-3.11Z'/%3E%3Ccircle cx='20' cy='68.637' r='4' fill='%23fff'/%3E%3Ccircle cx='20' cy='4' r='4' fill='%23fff'/%3E%3C/svg%3E");
}
message flag {
message bottom content {
color: var(--color-1);
}
event {
max-width: fit-content;
padding-top: 75px;
}
event main {
position: relative;
}
event main::before {
position: absolute;
content: '';
left: 0;
top: -45px;
width: 100px;
aspect-ratio: 149 / 229;
background-repeat: no-repeat;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='149' height='229' fill='none'%3E%3Cpath stroke='%23FCBF58' stroke-linecap='round' stroke-width='6' d='M84.022 119.783S19.393 208.728 5.04 172.245c-13.218-33.598 78.987-52.462 78.987-52.462M81.158 130.731s-15.163 26.145-20.87 46.496c-4.98 17.758-9.15 45.763-9.15 45.763'/%3E%3Cpath stroke='%23FCBF58' stroke-linecap='round' stroke-width='6' d='M84.783 118.394s-9.321 29.702-12.322 53.243c-2.619 20.542-5.89 54.159-5.89 54.159'/%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-width='4' d='M146.718 56.418s-15.307 13.592-28.454 19.09c-11.472 4.8-28.31 11.993-28.31 11.993'/%3E%3Cpath fill='%23fff' d='M138.252 4.918s4.478 26.592 1.276 43.935c-3.202 17.344-15.394 21.552-25.381 35.393 0 0-6.983-7.52-9.358-13.515-3.254-8.214-4.642-11.614-2.381-20.143 4.98-18.783 35.844-45.67 35.844-45.67Z'/%3E%3Cpath fill='%23fff' d='M61.191 68.138s17.822-4.301 32.316-4.866c5.472-.213 9.072 2.652 13.662 6.374 3.902 3.163 8.495 8.678 8.495 8.678s-9.934 8.055-16.628 8.617c-5.5.461-8.797-.924-13.766-3.202-10.177-4.666-24.079-15.6-24.079-15.6Z'/%3E%3C/svg%3E");
}
event main::after {
position: absolute;
content: '';
top: -15px;
right: -15px;
width: 125px;
aspect-ratio: 1;
z-index: -1;
filter: drop-shadow(0 0 3px var(--color-1));
background-repeat: no-repeat;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='178' height='178' fill='none'%3E%3Ccircle cx='88.957' cy='89' r='86' stroke='%23fff' stroke-dasharray='0.1 20 0.1 20' stroke-linecap='round' stroke-width='5'/%3E%3C/svg%3E");
}
event main content {
position: relative;
color: var(--color-2);
background-image: var(--color-4);
padding: 35px 50px;
margin-left: 40px;
border-radius: 50px;
font-weight: 600;
}
event main content::before {
position: absolute;
content: '';
top: 0;
right: 25px;
left: 50%;
width: 40px;
aspect-ratio: 26 / 43;
aspect-ratio: 50 / 62;
z-index: 100;
transform: translate(-50%, -50%);
filter: drop-shadow(0 0 3px var(--color-1));
background-repeat: no-repeat;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='43' fill='none'%3E%3Cpath fill='url(%23a)' fill-opacity='.8' d='M26 41.48c0 .366-.148.683-.398.851-.25.166-.534.14-.764-.07L13.907 32.145c-.543-.504-1.271-.504-1.814 0L1.162 42.26c-.229.211-.515.24-.764.07-.249-.168-.398-.485-.398-.85V.513C0-1.425 1.211-3 2.7-3h20.596c1.49 0 2.7 1.577 2.7 3.514V41.48H26Z'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='13' x2='13' y1='-3' y2='42.441' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23B24252'/%3E%3Cstop offset='1' stop-color='%237E2026'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E");
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='50' height='62' fill='none'%3E%3Cpath fill='%23fff' d='m25.453 0 1.154 9.172c1.37 10.903 9.793 19.609 20.698 21.394l2.648.434-2.648.434C36.4 33.219 27.977 41.925 26.607 52.828L25.453 62 24.3 52.828C22.929 41.925 14.506 33.219 3.6 31.434L.953 31l2.648-.434C14.506 28.781 22.93 20.075 24.3 9.172L25.453 0Z'/%3E%3C/svg%3E");
}
message.moderator flag {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='43' fill='none'%3E%3Cpath fill='url(%23a)' fill-opacity='.8' d='M26 41.48c0 .366-.148.683-.398.851-.25.166-.534.14-.764-.07L13.907 32.145c-.543-.504-1.271-.504-1.814 0L1.162 42.26c-.229.211-.515.24-.764.07-.249-.168-.398-.485-.398-.85V.513C0-1.425 1.211-3 2.7-3h20.596c1.49 0 2.7 1.577 2.7 3.514V41.48H26Z'/%3E%3Cdefs%3E%3ClinearGradient id='a' x1='13' x2='13' y1='-3' y2='42.441' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23E25E82'/%3E%3Cstop offset='1' stop-color='%23B74563'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E")!important;
}
message.vip flag,
message.subscriber flag
{
background-image: url("data:image/svg+xml,%3Csvg width='26' height='43' viewBox='0 0 26 43' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M26 41.4803C26 41.8458 25.8519 42.1631 25.6017 42.331C25.3528 42.4971 25.0681 42.4705 24.8383 42.2612L13.907 32.1446C13.3645 31.6412 12.6355 31.6412 12.093 32.1446L1.16168 42.2612C0.933176 42.4722 0.647223 42.5004 0.398291 42.331C0.149359 42.1631 0 41.8458 0 41.4803V0.513963C0 -1.42495 1.21147 -3 2.69996 -3H23.2962C24.786 -3 25.9962 -1.42329 25.9962 0.513963V41.4803H26Z' fill='url(%23paint0_linear_2116_38)' fill-opacity='0.8'/%3E%3Cdefs%3E%3ClinearGradient id='paint0_linear_2116_38' x1='13' y1='-3' x2='13' y2='42.4411' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%23D787AD'/%3E%3Cstop offset='1' stop-color='%23A6547B'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E%0A");
event main content::after {
position: absolute;
content: '';
width: 30px;
left: -30px;
bottom: -60px;
aspect-ratio: 40 / 73;
filter: drop-shadow(0 0 3px var(--color-1));
background-repeat: no-repeat;
background-size: cover;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='73' fill='none'%3E%3Cpath fill='%23fff' d='M37.56 31.713c-.16-.472.172-1.07.861-1.69a.724.724 0 0 0 .094-.964 7.8 7.8 0 0 0-9.814-2.392.723.723 0 0 1-1.035-.772 7.8 7.8 0 0 0-5.55-8.867.727.727 0 0 0-.875.44c-.377.968-.866 1.552-1.402 1.552-.524 0-1.004-.56-1.378-1.491a.722.722 0 0 0-.892-.414 7.801 7.801 0 0 0-5.262 8.775.723.723 0 0 1-1.035.773A7.8 7.8 0 0 0 1.038 29.72a.727.727 0 0 0 .209.956c.865.619 1.321 1.251 1.184 1.777-.134.508-.802.832-1.804.956a.722.722 0 0 0-.625.77 7.8 7.8 0 0 0 6.715 7.183c.578.08.828.774.434 1.205a7.8 7.8 0 0 0 .14 10.69.728.728 0 0 0 .834.153c.55-.263 1.016-.313 1.334-.1.307.204.441.627.423 1.203a.717.717 0 0 0 .48.7 7.8 7.8 0 0 0 9.016-3.035l.007-.01a.727.727 0 0 1 1.213 0l.007.01a7.8 7.8 0 0 0 9.943 2.645.726.726 0 0 0 .389-.772c-.117-.696-.026-1.22.307-1.476.306-.235.78-.215 1.353.017a.718.718 0 0 0 .852-.249 7.8 7.8 0 0 0-.617-9.776.723.723 0 0 1 .434-1.205 7.8 7.8 0 0 0 6.723-8.14.729.729 0 0 0-.67-.691c-.95-.067-1.596-.339-1.76-.819Zm-17.564 5.55-.005.023-.004-.024.028-.031-.019.031Z'/%3E%3Cpath fill='%23FFE8A9' d='M27.599 34.944a2.54 2.54 0 0 0-3.11-1.797l-1.967.527v-2.037a2.54 2.54 0 0 0-5.079 0v2.052l-1.985-.521a2.54 2.54 0 0 0-1.29 4.912l1.68.441-1.144 1.667a2.54 2.54 0 1 0 4.189 2.872l1.042-1.52 1.011 1.542a2.537 2.537 0 0 0 3.516.73 2.54 2.54 0 0 0 .731-3.515l-1.16-1.77 1.769-.473a2.54 2.54 0 0 0 1.797-3.11Z'/%3E%3Ccircle cx='20' cy='68.637' r='4' fill='%23fff'/%3E%3Ccircle cx='20' cy='4' r='4' fill='%23fff'/%3E%3C/svg%3E");
}