Skip to content

Commit

Permalink
feat(laser-sunset): Improved performance and visual updates
Browse files Browse the repository at this point in the history
- Improved performance of the laser grid at the bottom half by using an infinite CSS animation.
- The sun and sun reflections now have a color gradient orange into magenta.
- Slightly increased size of stars in the sky.
- The donation total now also has a reflection to show it's part of the scene.
- New animation using the gdq static asset to simulate less than ideal reception. It's the 80s afterall.
- The background now slightly lights up while processing donations and subscriptions until they despawn off-screen.
  • Loading branch information
SushiElemental authored and VodBox committed Jun 29, 2024
1 parent 7e58f39 commit f1a0cf4
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 25 deletions.
8 changes: 8 additions & 0 deletions src/channels/laser-sunset/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ const CONFIG = {
perspectiveGrowth: 2.4,
colors: ['magenta', 'orchid', 'blueviolet', 'mediumpurple'],
},
Static: {
respawnPeriod: 32000,
respawnPeriodVariance: 4000,
ageMin: 5000,
ageMax: 8000,
heightMinPercent: 4,
heightMaxPercent: 10,
},
Timers: {
fpsInterval: 1000 / 60,
},
Expand Down
35 changes: 31 additions & 4 deletions src/channels/laser-sunset/functions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { FormattedDonation, TwitchSubscription } from '@gdq/types/tracker';
import { StarVisual, Overcast, DonationPopup, SubscriptionVisual } from './types';
import { StarVisual, Overcast, DonationPopup, SubscriptionVisual, Static } from './types';
import CONFIG from './config';

export const formatCurrency = (val: number) => {
Expand All @@ -22,6 +22,10 @@ export const randomRange = (min: number, max: number) => {
return min + Math.random() * (max - min);
};

export const staticOpacity = (yPosition: number) => {
return Math.max(0.1, Math.abs(yPosition - 50.0) / 100.0);
};

export const randomStarOpacity = () => {
return randomRange(CONFIG.Stars.opacityMin, CONFIG.Stars.opacityMax);
};
Expand All @@ -34,16 +38,16 @@ export const spawnStar = (number: number, xMin: number, xMax: number, yMin: numb
left: randomRange(xMin, xMax),
top: randomRange(yMin, yMax),
text: number % 2 == 0 ? '+' : '*',
color: 'RGB(' + clr + ',' + clr + ',' + clr + ')',
color: { r: clr, g: clr, b: clr },
opacity: opacity,
};
};

export const starStyle = (star: StarVisual) => {
export const starStyle = (star: StarVisual, highlight: boolean) => {
return {
left: star.left + '%',
top: star.top + '%',
color: star.color,
color: 'RGB(' + (highlight ? '0' : star.color.r) + ',' + star.color.g + ',' + star.color.b + ')',
opacity: star.opacity,
};
};
Expand Down Expand Up @@ -79,6 +83,16 @@ export const spawnCloud = (donationAmount: number, count: number): Overcast => {
};
};

export const spawnStatic = (): Static => {
return {
spawnDate: new Date(),
maxAge: CONFIG.Static.ageMin + Math.random() * (CONFIG.Static.ageMax - CONFIG.Static.ageMin),
height:
CONFIG.Static.heightMinPercent +
Math.random() * (CONFIG.Static.heightMaxPercent - CONFIG.Static.heightMinPercent),
};
};

export const cloudScreenspaceProps = (cloud: Overcast) => {
const now = new Date();
const timeVisible = Math.min(now.getTime() - cloud.received.getTime(), CONFIG.Cloud.despawnMs);
Expand Down Expand Up @@ -220,3 +234,16 @@ export const subscriptionReflectionStyle = (sub: SubscriptionVisual) => {
top: dsp.radius + pushDown + '%',
};
};

export const staticStyle = (staticOverlay: Static, staticNoiseSrc: string) => {
const maxHeight = CONFIG.Static.heightMaxPercent;
const now = new Date();
const age = now.getTime() - staticOverlay.spawnDate.getTime();
const top = -maxHeight + (age / staticOverlay.maxAge) * (100.0 + maxHeight);
return {
top: top + '%',
height: staticOverlay.height + '%',
opacity: staticOpacity(top),
backgroundImage: 'url(' + staticNoiseSrc + ')',
};
};
53 changes: 42 additions & 11 deletions src/channels/laser-sunset/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import { useActive } from '@gdq/lib/hooks/useActive';
import { useListenForFn } from '@gdq/lib/hooks/useListenForFn';
import TweenNumber from '@gdq/lib/components/TweenNumber';

import { StarVisual, SunReflectionLine, Overcast, DonationPopup, SubscriptionVisual } from './types';
import { StarVisual, SunReflectionLine, Overcast, DonationPopup, SubscriptionVisual, Static } from './types';
import * as fn from './functions';
import CONFIG from './config';
import './style.css';
import staticNoise from '@gdq/assets/static.gif';

registerChannel('Laser Sunset', 15, LaserSunset, {
position: 'topRight',
Expand All @@ -34,9 +35,7 @@ export function LaserSunset(props: ChannelProps) {
const [donations, setDonations] = useState<DonationPopup[]>([]);
const [countSubscriptions, incrementSubscriptionsCount] = useReducer((x) => x + 1, 0);
const [subscriptions, setSubscriptions] = useState<SubscriptionVisual[]>([]);
const [lasersX, scrollLasers] = useReducer((x) => {
return x > CONFIG.Lasers.bgXmin ? x - CONFIG.Lasers.scrollSpeed : CONFIG.Lasers.bgXstart;
}, CONFIG.Lasers.bgXstart);
const [_, scrollLasers] = useReducer((x) => x + 1, 0);
const [clouds, setClouds] = useState<Overcast[]>([]);
const [sunReflections, setSunReflections] = useState<SunReflectionLine[]>([
{ xPosition: 50, marginTop: 2, width: 16 },
Expand All @@ -48,8 +47,9 @@ export function LaserSunset(props: ChannelProps) {
{ xPosition: 50, marginTop: 2, width: 6 },
{ xPosition: 50, marginTop: 2, width: 4 },
]);
const [staticOverlays, setStaticOverlays] = useState<Static[]>([]);

const lasersXstyle = `.Lasers:after { background-position-x: ${lasersX}px; }`;
const bgHighlightClass = donations.length || subscriptions.length ? ' highlight' : '';

const onDonationReceived = (donation: FormattedDonation) => {
if (!active || donations.length >= CONFIG.Donations.countMax) return;
Expand Down Expand Up @@ -131,6 +131,21 @@ export function LaserSunset(props: ChannelProps) {
return () => clearInterval(twinkleTimer);
}, [active]);

useEffect(() => {
if (!active) return;

const staticOverlaysTimer = setInterval(() => {
setTimeout(() => {
const staticOverlay = fn.spawnStatic();

setStaticOverlays((overlays) => [...overlays, staticOverlay]);
setTimeout(() => removeStaticOverlay(staticOverlay.spawnDate), staticOverlay.maxAge);
}, Math.random() * CONFIG.Static.respawnPeriodVariance);
}, CONFIG.Static.respawnPeriod);

return () => clearInterval(staticOverlaysTimer);
}, [active]);

const removeDonation = (date: Date) => {
setDonations((donos) => donos.filter((d) => d.received !== date));
props.unlock();
Expand All @@ -144,6 +159,10 @@ export function LaserSunset(props: ChannelProps) {
setSubscriptions((subscriptions) => subscriptions.filter((s) => s.received !== date));
};

const removeStaticOverlay = (date: Date) => {
setStaticOverlays((staticOverlays) => staticOverlays.filter((so) => so.spawnDate !== date));
};

const animateSunReflections = () => {
setSunReflections((lines) => {
return lines.map((line, index) => {
Expand All @@ -161,11 +180,10 @@ export function LaserSunset(props: ChannelProps) {

return (
<Container className="Container">
<style>{lasersXstyle}</style>
<Sky className="Sky">
<Sky className={'Sky' + bgHighlightClass}>
<Stars className="Stars">
{stars.map((s, index) => (
<Star className="Star" key={index} style={fn.starStyle(s)}>
<Star className="Star" key={index} style={fn.starStyle(s, false)}>
{s.text}
</Star>
))}
Expand All @@ -181,10 +199,13 @@ export function LaserSunset(props: ChannelProps) {
))}
</Clouds>
</Sky>
<Ocean className="Ocean">
<Ocean className={'Ocean' + bgHighlightClass}>
<Stars className="Stars reflection">
{stars.map((s, index) => (
<Star className="Star" key={index} style={fn.starStyle(s)}>
<Star
className="Star"
key={index}
style={fn.starStyle(s, donations.length > 0 || subscriptions.length > 0)}>
{s.text}
</Star>
))}
Expand All @@ -195,7 +216,7 @@ export function LaserSunset(props: ChannelProps) {
<SunReflections className="SunReflections">
{sunReflections.map((line, index) => (
<SunReflection
className="SunReflection"
className={'SunReflection SunReflection-' + index}
key={index}
style={{
width: line.width + '%',
Expand All @@ -204,6 +225,9 @@ export function LaserSunset(props: ChannelProps) {
}}></SunReflection>
))}
</SunReflections>
<TotalEl className="TotalEl reflection">
$<TweenNumber value={total?.raw} />
</TotalEl>
<SubscriptionsList className="SubscriptionsList">
{subscriptions.map((s, idx) => (
<Subscription
Expand Down Expand Up @@ -244,6 +268,11 @@ export function LaserSunset(props: ChannelProps) {
<TotalEl className="TotalEl">
$<TweenNumber value={total?.raw} />
</TotalEl>
<StaticList className="StaticList">
{staticOverlays.map((so, idx) => (
<StaticEl className="Static" key={idx} style={fn.staticStyle(so, staticNoise)}></StaticEl>
))}
</StaticList>
</Container>
);
}
Expand All @@ -267,3 +296,5 @@ export const Lasers = styled.div``;
export const LasersHorizon = styled.div``;
export const SunReflections = styled.div``;
export const SunReflection = styled.div``;
export const StaticList = styled.div``;
export const StaticEl = styled.div``;
87 changes: 78 additions & 9 deletions src/channels/laser-sunset/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
z-index: 101;
}

.TotalEl.reflection {
opacity: 0.3;
transform: translate(0%, 111%) rotateX(-230deg);
z-index: initial;
}

.DonationsList {
position: absolute;
width: 100%;
Expand Down Expand Up @@ -83,6 +89,11 @@
rgba(222, 33, 111, .5) 90%,
rgba(222, 33, 111, 1) 100%
);
transition: background-color 1s ease-in-out;
}

.Sky.highlight {
background-color: rgba(222, 33, 111, .1);
}

.Stars {
Expand All @@ -100,7 +111,7 @@

.Star {
font-family: gdqpixel;
font-size: 10px;
font-size: 13px;
color: #eee;
position: absolute;
left: 10%;
Expand Down Expand Up @@ -167,28 +178,30 @@
height: 200px;
border-radius: 100px;
box-shadow: 0 0 32px 0 #f09000;
background-color: #f09000;
background: linear-gradient(to bottom,rgba(240,144,0,1) 0%,rgba(240,144,0,1) 33%,rgba(240,0,240,1) 100%);
transform: translate(-50%, -35%);
}

.Sun-top {
clip-path: inset(-50px -50px 166px -50px);
clip-path: inset(-50px -50px 167px -50px);
}

.Sun-middle-top {
clip-path: inset(38px -50px 150px -50px);
}

.Sun-middle {
clip-path: inset(56px -50px 126px -50px);
clip-path: inset(57px -50px 126px -50px);
}

.Sun-middle-bottom {
box-shadow: 0 0 32px 0 #f0752d;
clip-path: inset(82px -50px 97px -50px);
}

.Sun-bottom {
clip-path: inset(112px -50px -50px -50px);
box-shadow: 0 0 32px 0 #f05d55;
clip-path: inset(112px -50px 77px -50px);
}

.Ocean {
Expand All @@ -198,6 +211,11 @@
height: 50%;
overflow: hidden;
background: #000;
transition: background-color 1s ease-in-out;
}

.Ocean.highlight {
background-color: rgba(0, 215, 215, .1);
}

.Ocean-background {
Expand All @@ -212,6 +230,15 @@
);
}

@keyframes laserScroll {
0% {
transform: perspective(200px) rotateX(38deg) scale(2,1) translateZ(0) translateX(0px);
}
100% {
transform: perspective(200px) rotateX(38deg) scale(2,1) translateZ(0) translateX(-120px);
}
}

.Lasers {
position: absolute;
overflow: hidden;
Expand All @@ -223,7 +250,7 @@

/* Laser grid background effect, taken from https://stackoverflow.com/questions/53416334/css-80s-tron-grid */
.Lasers:after {
transform: perspective(200px) rotateX(38deg) scale(2,1) translateZ(0);
transform: perspective(200px) rotateX(38deg) scale(2,1) translateZ(0) translateX(0%);
content: "";
display: block;
position: absolute;
Expand All @@ -247,6 +274,8 @@
background-image:
linear-gradient(to right, cyan 10px, transparent 0),
linear-gradient(to bottom, cyan 16px, transparent 0);
background-repeat: repeat;
animation: laserScroll 5s linear infinite;
}

.Lasers-horizon {
Expand Down Expand Up @@ -277,8 +306,48 @@
transition: width 1s cubic-bezier(0, .3, .5, 1), left 1s cubic-bezier(0, .3, .5, 1), margin-top 1s ease-in-out;
}

.Lamps {
.SunReflection-0 {
box-shadow: 0 0 16px 1px #f0585e;
background-color: #f0585ead;
}

.SunReflection-1 {
box-shadow: 0 0 16px 1px #f05d55;
background-color: #f05d55ad;
}

.SunReflection-2 {
box-shadow: 0 0 16px 1px #f06c3c;
background-color: #f06c3cad;
}

.SunReflection-3 {
box-shadow: 0 0 16px 1px #f07a25;
background-color: #f07a25ad;
}

.SunReflection-4 {
box-shadow: 0 0 16px 1px #f08b05;
background-color: #f08b05ad;
}

.StaticList {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
opacity: 0.8;
}

.Static {
position: absolute;
right: 2%;
bottom: 10%;
z-index: 101;
width: 100%;
height: 4%;
background-color: #999;
background-size: 75% 75%;
background-repeat: repeat;
border-top: 1px solid rgba(36, 36, 36, 0.3);
border-bottom: 1px solid rgba(150, 150, 150, 0.3);
opacity: 1;
}
Loading

0 comments on commit f1a0cf4

Please sign in to comment.