-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Adding channel with a V A P O R W A V E scenery
- Loading branch information
1 parent
b94f25d
commit 1553ed6
Showing
6 changed files
with
1,017 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/** | ||
* @file Configuration values - mainly for changing colors, fps, animations. | ||
* Many of the settings are now balanced to look good enough and will break if put to extremes. | ||
* Handle with care. | ||
*/ | ||
|
||
const CONFIG = { | ||
Donations: { | ||
despawnMs: 5000, | ||
fontSizeMin: 20, | ||
fontSizeMax: 50, | ||
lockTimeMs: 100, | ||
colors: [ | ||
'cyan', | ||
'magenta', | ||
'orchid', | ||
'blueviolet', | ||
'mediumpurple', | ||
'mediumvioletred', | ||
'mediumspringgreen', | ||
'deepskyblue', | ||
], | ||
}, | ||
Subscriptions: { | ||
despawnMs: 6000, | ||
spawnPercent: -10, | ||
despawnPercent: 110, | ||
slowdownPercent: 48, | ||
speedupPercent: 42, | ||
topMinPercent: 5, | ||
topMaxPercent: 80, | ||
lockTimeMs: 500, | ||
minSize: 0.6, | ||
maxSize: 1, | ||
maxMonths: 12, | ||
colors: ['ghostwhite', 'mintcream', 'pink', 'lavender', 'hotpink', 'salmon', 'mistyrose'], | ||
}, | ||
Stars: { | ||
countX: 10, | ||
countY: 7, | ||
brightnessMin: 120, | ||
brightnessMax: 240, | ||
opacityMin: 0.3, | ||
opacityMax: 1.0, | ||
twinkleMs: 400, | ||
}, | ||
Lasers: { | ||
bgXstart: 67, | ||
bgXmin: -53, | ||
scaleXdefault: 2, | ||
}, | ||
Cloud: { | ||
despawnMs: 10000, | ||
xMargin: 5, | ||
sizeMinPercent: 3, | ||
sizeMaxPercent: 20, | ||
sizeDonationMin: 5, | ||
sizeDonationMax: 1000, | ||
perspectiveGrowth: 2.4, | ||
colors: ['magenta', 'orchid', 'blueviolet', 'mediumpurple'], | ||
}, | ||
Thumping: { | ||
scale: 1.05, | ||
intervalMs: 800, | ||
durationMs: 90, | ||
}, | ||
Timers: { | ||
fpsInterval: 1000 / 60, | ||
}, | ||
Titles: { | ||
// See https://en.wikipedia.org/wiki/Combining_Diacritical_Marks | ||
diacriticalMarks: [ | ||
'', // none | ||
'\u0307', // ̇ Dot | ||
'\u0320', // ̠ Minus sign below | ||
'\u032A', // ̪ Bridge below | ||
'\u0332', // ̲ Low line | ||
'\u0333', // ̳ Double low line | ||
'\u0359', // ͙ Asterisk below | ||
], | ||
}, | ||
}; | ||
|
||
export default CONFIG; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
import type { FormattedDonation, TwitchSubscription } from '@gdq/types/tracker'; | ||
import { StarVisual, Overcast, DonationPopup, SubscriptionVisual } from './types'; | ||
import CONFIG from './config'; | ||
|
||
export const formatCurrency = (val: number) => { | ||
return val.toLocaleString('en-US', { | ||
style: 'currency', | ||
currency: 'USD', | ||
maximumFractionDigits: 2, | ||
}); | ||
}; | ||
|
||
export const vaporifyText = (text: string) => { | ||
const marks = CONFIG.Titles.diacriticalMarks; | ||
return text | ||
.split('') | ||
.map((character) => character.replace(/([^\s])/i, (c) => c + marks[c.charCodeAt(0) % marks.length])) | ||
.join(''); | ||
}; | ||
|
||
export const easeIn = (number: number, total: number, scaleMin = 1.0, scaleMax = 1.0) => { | ||
return scaleMin + (number / total) * (scaleMax - scaleMin); | ||
}; | ||
|
||
export const easeOut = (number: number, total: number, scaleMin = 1.0, scaleMax = 1.0) => { | ||
return scaleMin + (scaleMax - scaleMin) * easeIn(number, total, 0.0, 1.0); | ||
}; | ||
|
||
export const randomRange = (min: number, max: number) => { | ||
return min + Math.random() * (max - min); | ||
}; | ||
|
||
export const subscriptionFlyby = (number: number, total: number, x0: number, x1: number, x2: number, x3: number) => { | ||
const progress = number / total; | ||
return ( | ||
Math.pow(1 - progress, 3) * x0 + | ||
3 * Math.pow(1 - progress, 2) * progress * x1 + | ||
3 * (1 - progress) * Math.pow(progress, 2) * x2 + | ||
Math.pow(progress, 3) * x3 | ||
); | ||
}; | ||
|
||
export const randomStarOpacity = () => { | ||
return randomRange(CONFIG.Stars.opacityMin, CONFIG.Stars.opacityMax); | ||
}; | ||
|
||
export const spawnStar = (number: number, xMin: number, xMax: number, yMin: number, yMax: number): StarVisual => { | ||
const clr = randomRange(CONFIG.Stars.brightnessMin, CONFIG.Stars.brightnessMax); | ||
const opacity = randomStarOpacity(); | ||
|
||
return { | ||
left: randomRange(xMin, xMax), | ||
top: randomRange(yMin, yMax), | ||
text: number % 2 == 0 ? '+' : '*', | ||
color: 'RGB(' + clr + ',' + clr + ',' + clr + ')', | ||
opacity: opacity, | ||
}; | ||
}; | ||
|
||
export const starStyle = (star: StarVisual) => { | ||
return { | ||
left: star.left + '%', | ||
top: star.top + '%', | ||
color: star.color, | ||
opacity: star.opacity, | ||
}; | ||
}; | ||
|
||
export const spawnDonation = (baseProps: FormattedDonation, count: number): DonationPopup => { | ||
const angleDegs = count % 2 ? (count % 36) * 10 : ((count + 18) % 36) * 10; | ||
const angleRads = (angleDegs / 360) * Math.PI * 2.0; | ||
|
||
return { | ||
...baseProps, | ||
renderedAmount: formatCurrency(baseProps.rawAmount), | ||
angle: angleRads, | ||
radius: 2 + Math.random() * 5, | ||
color: CONFIG.Donations.colors[count % CONFIG.Donations.colors.length], | ||
received: new Date(), | ||
}; | ||
}; | ||
|
||
export const spawnCloud = (donationAmount: number, count: number): Overcast => { | ||
const clamped = Math.min(Math.max(donationAmount, CONFIG.Cloud.sizeDonationMin), CONFIG.Cloud.sizeDonationMax); | ||
const amountRatio = (clamped - CONFIG.Cloud.sizeDonationMin) / CONFIG.Cloud.sizeDonationMax; | ||
const sizeMin = CONFIG.Cloud.sizeMinPercent; | ||
const sizeMax = CONFIG.Cloud.sizeMaxPercent; | ||
const sideLength = sizeMin + amountRatio * (sizeMax - sizeMin); | ||
|
||
return { | ||
left: CONFIG.Cloud.xMargin + Math.random() * (100 - CONFIG.Cloud.xMargin * 2), | ||
width: sideLength, | ||
height: sideLength * 1.5, | ||
backgroundColor: CONFIG.Cloud.colors[count % CONFIG.Cloud.colors.length], | ||
received: new Date(), | ||
}; | ||
}; | ||
|
||
export const cloudScreenspaceProps = (cloud: Overcast) => { | ||
const now = new Date(); | ||
const timeVisible = Math.min(now.getTime() - cloud.received.getTime(), CONFIG.Cloud.despawnMs); | ||
const ageRatio = timeVisible / CONFIG.Cloud.despawnMs; | ||
const visibilityWindow = 100 + CONFIG.Cloud.sizeMaxPercent; | ||
const age = ageRatio * visibilityWindow; | ||
|
||
return { | ||
left: cloud.left, | ||
bottom: age, | ||
width: cloud.width + cloud.width * ageRatio * CONFIG.Cloud.perspectiveGrowth, | ||
height: cloud.height, | ||
backgroundColor: cloud.backgroundColor, | ||
now: now, | ||
age: age, | ||
rotate: age * 28, | ||
}; | ||
}; | ||
|
||
export const cloudStyle = (cloud: Overcast) => { | ||
const csp = cloudScreenspaceProps(cloud); | ||
|
||
return { | ||
left: csp.left + '%', | ||
bottom: csp.bottom + '%', | ||
width: csp.width + '%', | ||
height: csp.height + '%', | ||
backgroundColor: csp.backgroundColor, | ||
rotate: 'x -' + csp.rotate + 'deg', | ||
}; | ||
}; | ||
|
||
export const cloudReflectionStyle = (cloud: Overcast) => { | ||
const csp = cloudScreenspaceProps(cloud); | ||
|
||
return { | ||
left: csp.left + '%', | ||
top: csp.bottom + '%', | ||
width: csp.width + '%', | ||
height: csp.height + '%', | ||
backgroundColor: csp.backgroundColor, | ||
rotate: 'x ' + csp.rotate + 'deg', | ||
}; | ||
}; | ||
|
||
export const donationScreenspaceProps = (donation: DonationPopup) => { | ||
const now = new Date(); | ||
const age = now.getTime() - donation.received.getTime(); | ||
const radius = donation.radius + age / 50.0; | ||
|
||
return { | ||
x: 50 + Math.cos(donation.angle) * radius, | ||
y: 50 + Math.sin(donation.angle) * radius, | ||
now: now, | ||
age: age, | ||
radius: radius, | ||
color: donation.color, | ||
fontSize: easeIn(age, CONFIG.Donations.despawnMs, CONFIG.Donations.fontSizeMin, CONFIG.Donations.fontSizeMax), | ||
}; | ||
}; | ||
|
||
export const donationStyle = (donation: DonationPopup) => { | ||
const dsp = donationScreenspaceProps(donation); | ||
return { left: dsp.x + '%', top: dsp.y + '%', fontSize: dsp.fontSize + 'px', color: dsp.color }; | ||
}; | ||
|
||
export const donationReflectionStyle = (donation: DonationPopup) => { | ||
const dsp = donationScreenspaceProps(donation); | ||
const age = dsp.age * 2; | ||
const pushDown = 10 + (age / CONFIG.Donations.despawnMs) * 120; | ||
return { | ||
...donation, | ||
left: dsp.x + '%', | ||
top: dsp.radius + pushDown + '%', | ||
}; | ||
}; | ||
|
||
export const spawnSubscription = (sub: TwitchSubscription, count: number): SubscriptionVisual => { | ||
// The y position is based on the months subscribed. When I tested subscriptions they always had "months" set to 1. | ||
// To test layout use months value below based on number of subscriptions: | ||
// const months = Math.floor(count) % CONFIG.Subscriptions.maxMonths; | ||
const months = Math.min(sub.months, CONFIG.Subscriptions.maxMonths); | ||
const ageRatio = months / CONFIG.Subscriptions.maxMonths; | ||
const top = CONFIG.Subscriptions.topMinPercent + ageRatio * CONFIG.Subscriptions.topMaxPercent; | ||
|
||
return { | ||
left: CONFIG.Subscriptions.spawnPercent, | ||
top: top, | ||
zoom: | ||
CONFIG.Subscriptions.minSize + (top / 100) * (CONFIG.Subscriptions.maxSize - CONFIG.Subscriptions.minSize), | ||
user_name: sub.user_name, | ||
display_name: sub.display_name, | ||
months: sub.months, | ||
context: sub.context, | ||
color: CONFIG.Subscriptions.colors[count % CONFIG.Subscriptions.colors.length], | ||
received: new Date(), | ||
}; | ||
}; | ||
|
||
export const subscriptionScreenspaceProps = (sub: SubscriptionVisual) => { | ||
const now = new Date(); | ||
const age = now.getTime() - sub.received.getTime(); | ||
|
||
return { | ||
now: now, | ||
age: age, | ||
left: subscriptionFlyby( | ||
age, | ||
CONFIG.Subscriptions.despawnMs, | ||
CONFIG.Subscriptions.spawnPercent, | ||
CONFIG.Subscriptions.slowdownPercent, | ||
CONFIG.Subscriptions.speedupPercent, | ||
CONFIG.Subscriptions.despawnPercent, | ||
), | ||
top: sub.top, | ||
zoom: sub.zoom, | ||
color: sub.color, | ||
}; | ||
}; | ||
|
||
export const subscriptionStyle = (sub: SubscriptionVisual) => { | ||
const ssp = subscriptionScreenspaceProps(sub); | ||
return { | ||
left: ssp.left + '%', | ||
top: ssp.top + '%', | ||
color: ssp.color, | ||
zIndex: Math.floor(ssp.top), | ||
zoom: ssp.zoom, | ||
}; | ||
}; |
Oops, something went wrong.