blob: b89c0e06f3b7f6462a6240528a0d2c5552b41fa5 [file] [log] [blame]
pineafanb7c79742022-11-06 18:08:36 +00001import React, { useEffect, useState } from "react";
pineafane0283a82022-02-13 10:05:56 +00002import Styles from '../styles/Components/header.module.css';
pineafanb18f0192022-10-27 22:08:36 +01003import NavStyles from '../styles/Components/navbar.module.css';
pineafan876af7d2021-10-14 20:31:21 +01004import Head from 'next/head';
pineafanff3d4522022-05-06 19:51:02 +01005import { useReward } from "react-rewards";
6import ScrollContainer from 'react-indiana-drag-scroll'
pineafana5ce9102021-09-02 17:21:31 +01007
pineafanb7c79742022-11-06 18:08:36 +00008const positive = [
9 "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f389.svg",
10 "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f388.svg",
11 "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f973.svg",
12];
13
14const negative = [
15 "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f614.svg",
16 "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f61e.svg",
17 "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f613.svg",
18 "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f625.svg",
19 "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f623.svg",
20];
21
22function preloadImage (src) {
23 return new Promise((resolve, reject) => {
24 const img = new Image()
25 img.onload = function() {
26 resolve(img)
27 }
28 img.onerror = img.onabort = function() {
29 reject(src)
30 }
31 img.src = src
32 })
33}
34
pineafanff3d4522022-05-06 19:51:02 +010035function Header(props) {
pineafan74f16742022-11-07 21:57:55 +000036 const keys = Object.keys(props.effects ?? {});
37 const changingSubtextEffect = keys.includes("changingSubtext");
38 const typedEffect = keys.includes("type") || changingSubtextEffect;
39
40
pineafanff3d4522022-05-06 19:51:02 +010041 const [ clickTotal, setClickTotal ] = React.useState(0);
pineafan74f16742022-11-07 21:57:55 +000042 const [ currText, setCurrentText ] = React.useState(typedEffect ? "" : (props.subtext ?? ""));
43 const [ hasRendered, setHasRendered ] = React.useState(false);
44
45 function typeText(currentText, toType) {
46 let count = 0;
47 let interval = setInterval(() => {
48 setCurrentText(currentText + toType.substring(0, count));
49 count++;
50 if (count > toType.length) {
51 clearInterval(interval);
52 }
53 }, props.effects.type ?? 50);
54 return currentText + toType
55 }
56
57 function unTypeText(currentText, amount) {
58 let count = 0;
59 const permanentText = currentText.slice(0, currentText.length - amount);
60 let toRemove = currentText.slice(currentText.length - amount, currentText.length);
61 let interval = setInterval(() => {
62 toRemove = toRemove.slice(0, toRemove.length - 1);
63 setCurrentText(permanentText + toRemove);
64 count++;
65 if (count >= amount) {
66 clearInterval(interval);
67 }
68 }, 500 / amount);
69 return permanentText;
70 }
71
72 function startAnimations() {
73 setHasRendered(true);
74 if (!changingSubtextEffect && typedEffect) return typeText("", props.subtext);
75 if (changingSubtextEffect && typedEffect) {
76 let lastPicked = -1;
77 function selectNextString() {
78 if (Math.floor(Math.random() * 100) === 0) {
79 return props.effects.changingSubtext.rare[Math.floor(Math.random() * props.effects.changingSubtext.rare.length)];
80 }
81 let selectedIndex = lastPicked
82 while (selectedIndex === lastPicked) {
83 selectedIndex = Math.floor(Math.random() * props.effects.changingSubtext.common.length);
84 }
85 lastPicked = selectedIndex;
86 return props.effects.changingSubtext.common[selectedIndex];
87 }
88 let nextString = selectNextString();
89 let currentText = typeText("", props.subtext + nextString);
90 setTimeout(() => {
91 function next() {
92 setTimeout(() => {
93 setTimeout(() => {
94 nextString = selectNextString();
95 currentText = typeText(currentText, nextString);
96 }, 1000);
97 currentText = unTypeText(currentText, nextString.length);
98 }, 4000);
99 }
100 next()
101 setInterval(() => {
102 next()
103 }, 5000);
104 }, 3000);
105 }
106 }
pineafanff3d4522022-05-06 19:51:02 +0100107
108 const { reward: reward, isAnimating: isAnimating } = useReward('headerConfetti', 'confetti', {
109 elementSize: 10,
110 elementCount: 150,
111 startVelocity: 35,
112 lifetime: 300,
113 decay: 0.95,
114 spread: 170,
115 position: "absolute",
116 colors: ["#F27878", "#E5AB71", "#E5DC71", "#A1CC65", "#68D49E", "#71AFE5", "#6576CC", "#8D58B2", "#BF5E9F"]
117 });
118 const { reward: disappointment, isAnimating: isDisappointmentAnimating } = useReward('disappointmentConfetti', 'confetti', {
119 elementSize: 25,
120 elementCount: 1,
121 startVelocity: 25,
122 lifetime: 350,
123 decay: 0.95,
124 spread: 0,
125 position: "absolute",
126 colors: ["#E5AB71"]
127 });
128
pineafanb7c79742022-11-06 18:08:36 +0000129 const all = positive.concat(negative)
130 const [imagesPreloaded, setImagesPreloaded] = useState(false)
131 useEffect(() => {
132 let isCancelled = false
133 async function effect() {
134 if (isCancelled) { return }
135 const imagesPromiseList = []
136 for (const i of all) { imagesPromiseList.push(preloadImage(i)) }
137 await Promise.all(imagesPromiseList)
138 if (isCancelled) { return }
139 setImagesPreloaded(true)
140 };
141 effect()
142 return () => { isCancelled = true }
143 }, [all])
144
pineafanb18f0192022-10-27 22:08:36 +0100145 function showSubBarWithUrl(url) {
146 const screenWidth = window.innerWidth;
147 let amount = screenWidth - 50;
148 amount = Math.floor(amount / 42) - 1;
149 props.showSubBar(
150 <div className={NavStyles.container}>
151 {
152 Array.from(Array(amount)).map((i, index) => {
153 return (
pineafanb7c79742022-11-06 18:08:36 +0000154 <a className={NavStyles.icon} key={index}><img alt="" className={NavStyles.icon} src={
pineafanb18f0192022-10-27 22:08:36 +0100155 url[Math.floor(Math.random() * url.length)]
156 } /></a>
157 )
158 })
159 }
160 </div>,
pineafanb7c79742022-11-06 18:08:36 +0000161 5, "center"
pineafanb18f0192022-10-27 22:08:36 +0100162 );
163 }
164
pineafanff3d4522022-05-06 19:51:02 +0100165 function confetti() {
166 if (!isAnimating && !isDisappointmentAnimating && props.index) {
167 setClickTotal(clickTotal + 1);
pineafan9babd752022-10-21 21:47:52 +0100168 if (clickTotal > 0 && Math.floor(Math.random() * 3) === 0) {
pineafanb7c79742022-11-06 18:08:36 +0000169 showSubBarWithUrl(negative);
pineafanff3d4522022-05-06 19:51:02 +0100170 disappointment();
171 } else {
pineafanb7c79742022-11-06 18:08:36 +0000172 showSubBarWithUrl(positive);
pineafanff3d4522022-05-06 19:51:02 +0100173 reward();
174 }
175 }
pineafan9b1b68c2021-11-05 17:47:27 +0000176 }
pineafana5ce9102021-09-02 17:21:31 +0100177
pineafan74f16742022-11-07 21:57:55 +0000178 if (!hasRendered) startAnimations();
179
pineafanff3d4522022-05-06 19:51:02 +0100180 return (
pineafan74f16742022-11-07 21:57:55 +0000181 <div className={Styles.header} style={{
pineafanff3d4522022-05-06 19:51:02 +0100182 margin: "0",
183 minHeight: props.fullscreen ? "calc(100vh - 42px)" : "calc(100vh - (4 * max(2em, 4vw)) - 1em)",
184 }} id={props.id ? props.id : null}>
pineafan74f16742022-11-07 21:57:55 +0000185 <div className={Styles.container} style={{minHeight: props.fullscreen ? "calc(100vh - 42px)" : "calc(100vh - (4 * max(2em, 4vw)) - 1em)",}}>
186 {imagesPreloaded}
187 <div className={Styles.backgroundGradient} style={{
188 backgroundImage: `linear-gradient(69.44deg, #${props.gradient[0]} 0%, #${props.gradient[1]} 100%)`,
189 }} />
190 <Head>
191 <title>{props.name}</title>
pineafanff3d4522022-05-06 19:51:02 +0100192
pineafan74f16742022-11-07 21:57:55 +0000193 <meta name="apple-mobile-web-app-capable" content="yes" />
194 <meta name="mobile-web-app-capable" content="yes" />
195 <meta name="viewport" content="width=device-width, minimal-ui" />
pineafanff3d4522022-05-06 19:51:02 +0100196
pineafan74f16742022-11-07 21:57:55 +0000197 <meta name="title" content={props.name} />
198 <meta name="og:title" content={props.name} />
199 <meta name="description" content={props.embedDescription ? props.embedDescription : props.subtext} />
200 <meta name="og:description" content={props.embedDescription ? props.embedDescription : props.subtext} />
201 <meta name="author" content="Clicks" />
202 <meta name="og:author" content="Clicks" />
203 <meta name="image" content={props.embedImage} />
204 <meta name="og:image" content={props.embedImage} />
pineafanff3d4522022-05-06 19:51:02 +0100205
pineafan74f16742022-11-07 21:57:55 +0000206 <meta name="theme-color" content={"#000000"} />
207 <meta name="og-color" content={"#" + props.gradient[1]} />
208 <meta name="msapplication-TileColor" content={"#000000"} />
209 </Head>
210 <img draggable={false} alt="" className={Styles.backgroundImage} src={`https://assets.clicks.codes/${props.wave}.svg`} />
211 <div id="headerConfetti" />
212 <div id="disappointmentConfetti" />
213 <div className={Styles.panel}>
214 <div className={Styles.titleContainer}>
215 <div onClick={confetti}>
216 {
217 props.customImage ?
218 <img height="64px" width="64px" alt={props.name} className={Styles.headerImage} style={{borderRadius: props.roundImage ? "100vw" : "0"}} src={props.customImage} />
219 : <></>
220 }
221 </div>
222 <h1 className={Styles.title}>{props.name}</h1>
pineafana5ce9102021-09-02 17:21:31 +0100223 </div>
pineafan74f16742022-11-07 21:57:55 +0000224 <div className={Styles.textBar}>
225 <p className={Styles.subtext + " " + (props.buttons.length ? Styles.subtextExtra : null)}>{currText}</p>
226 { typedEffect ? <div className={Styles.typedEffect} /> : <></> }
227 </div>
228 <a href="#skipNav" id="skipNav" style={{display: "none"}} />
229 { props.buttons.length ?
230 <div className={Styles.buttonOverflow}>
231 <img className={Styles.indicator + " " + Styles.leftArrow} draggable={false} alt="" src={`https://assets.clicks.codes/web/icons/arrow.svg`}/>
232 <ScrollContainer
233 vertical={false}
234 horizontal={true}
235 hideScrollbars={true}
236 nativeMobileScroll={true}
237 style={{borderRadius: "10px", height: "fit-content"}}
238 >
239 <div className={Styles.buttonLayout}>
240 {
241 props.buttons ? props.buttons.map((button, index) => {
242 return <a
243 key={index}
244 className={Styles.button}
245 style={{ backgroundColor: `#${button.color}`, color: `#${button.buttonText}` }}
246 href={button.link}
247 onClick={
248 button.onClick ? button.onClick : () => { if (button.id) { props.callback(button.id) } }}
249 target={button.target ? "_blank" : null}
250 draggable={false}
251 rel="noreferrer">
252 {button.text}
253 </a>
254 }) : null
255 }
256 </div>
257 </ScrollContainer>
258 <img className={Styles.indicator + " " + Styles.rightArrow} draggable={false} alt="" src={`https://assets.clicks.codes/web/icons/arrow.svg`}/>
259 </div> : <></>
260 }
pineafana5ce9102021-09-02 17:21:31 +0100261 </div>
pineafana5ce9102021-09-02 17:21:31 +0100262 </div>
pineafanff3d4522022-05-06 19:51:02 +0100263 </div>
264 )
pineafana5ce9102021-09-02 17:21:31 +0100265}
266
267export default Header;