pineafan | b7c7974 | 2022-11-06 18:08:36 +0000 | [diff] [blame] | 1 | import React, { useEffect, useState } from "react"; |
pineafan | e0283a8 | 2022-02-13 10:05:56 +0000 | [diff] [blame] | 2 | import Styles from '../styles/Components/header.module.css'; |
pineafan | b18f019 | 2022-10-27 22:08:36 +0100 | [diff] [blame] | 3 | import NavStyles from '../styles/Components/navbar.module.css'; |
pineafan | 876af7d | 2021-10-14 20:31:21 +0100 | [diff] [blame] | 4 | import Head from 'next/head'; |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 5 | import { useReward } from "react-rewards"; |
| 6 | import ScrollContainer from 'react-indiana-drag-scroll' |
pineafan | a5ce910 | 2021-09-02 17:21:31 +0100 | [diff] [blame] | 7 | |
pineafan | b7c7974 | 2022-11-06 18:08:36 +0000 | [diff] [blame] | 8 | const 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 | |
| 14 | const 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 | |
| 22 | function 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 | |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 35 | function Header(props) { |
pineafan | 74f1674 | 2022-11-07 21:57:55 +0000 | [diff] [blame^] | 36 | const keys = Object.keys(props.effects ?? {}); |
| 37 | const changingSubtextEffect = keys.includes("changingSubtext"); |
| 38 | const typedEffect = keys.includes("type") || changingSubtextEffect; |
| 39 | |
| 40 | |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 41 | const [ clickTotal, setClickTotal ] = React.useState(0); |
pineafan | 74f1674 | 2022-11-07 21:57:55 +0000 | [diff] [blame^] | 42 | 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 | } |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 107 | |
| 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 | |
pineafan | b7c7974 | 2022-11-06 18:08:36 +0000 | [diff] [blame] | 129 | 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 | |
pineafan | b18f019 | 2022-10-27 22:08:36 +0100 | [diff] [blame] | 145 | 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 ( |
pineafan | b7c7974 | 2022-11-06 18:08:36 +0000 | [diff] [blame] | 154 | <a className={NavStyles.icon} key={index}><img alt="" className={NavStyles.icon} src={ |
pineafan | b18f019 | 2022-10-27 22:08:36 +0100 | [diff] [blame] | 155 | url[Math.floor(Math.random() * url.length)] |
| 156 | } /></a> |
| 157 | ) |
| 158 | }) |
| 159 | } |
| 160 | </div>, |
pineafan | b7c7974 | 2022-11-06 18:08:36 +0000 | [diff] [blame] | 161 | 5, "center" |
pineafan | b18f019 | 2022-10-27 22:08:36 +0100 | [diff] [blame] | 162 | ); |
| 163 | } |
| 164 | |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 165 | function confetti() { |
| 166 | if (!isAnimating && !isDisappointmentAnimating && props.index) { |
| 167 | setClickTotal(clickTotal + 1); |
pineafan | 9babd75 | 2022-10-21 21:47:52 +0100 | [diff] [blame] | 168 | if (clickTotal > 0 && Math.floor(Math.random() * 3) === 0) { |
pineafan | b7c7974 | 2022-11-06 18:08:36 +0000 | [diff] [blame] | 169 | showSubBarWithUrl(negative); |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 170 | disappointment(); |
| 171 | } else { |
pineafan | b7c7974 | 2022-11-06 18:08:36 +0000 | [diff] [blame] | 172 | showSubBarWithUrl(positive); |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 173 | reward(); |
| 174 | } |
| 175 | } |
pineafan | 9b1b68c | 2021-11-05 17:47:27 +0000 | [diff] [blame] | 176 | } |
pineafan | a5ce910 | 2021-09-02 17:21:31 +0100 | [diff] [blame] | 177 | |
pineafan | 74f1674 | 2022-11-07 21:57:55 +0000 | [diff] [blame^] | 178 | if (!hasRendered) startAnimations(); |
| 179 | |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 180 | return ( |
pineafan | 74f1674 | 2022-11-07 21:57:55 +0000 | [diff] [blame^] | 181 | <div className={Styles.header} style={{ |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 182 | margin: "0", |
| 183 | minHeight: props.fullscreen ? "calc(100vh - 42px)" : "calc(100vh - (4 * max(2em, 4vw)) - 1em)", |
| 184 | }} id={props.id ? props.id : null}> |
pineafan | 74f1674 | 2022-11-07 21:57:55 +0000 | [diff] [blame^] | 185 | <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> |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 192 | |
pineafan | 74f1674 | 2022-11-07 21:57:55 +0000 | [diff] [blame^] | 193 | <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" /> |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 196 | |
pineafan | 74f1674 | 2022-11-07 21:57:55 +0000 | [diff] [blame^] | 197 | <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} /> |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 205 | |
pineafan | 74f1674 | 2022-11-07 21:57:55 +0000 | [diff] [blame^] | 206 | <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> |
pineafan | a5ce910 | 2021-09-02 17:21:31 +0100 | [diff] [blame] | 223 | </div> |
pineafan | 74f1674 | 2022-11-07 21:57:55 +0000 | [diff] [blame^] | 224 | <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 | } |
pineafan | a5ce910 | 2021-09-02 17:21:31 +0100 | [diff] [blame] | 261 | </div> |
pineafan | a5ce910 | 2021-09-02 17:21:31 +0100 | [diff] [blame] | 262 | </div> |
pineafan | ff3d452 | 2022-05-06 19:51:02 +0100 | [diff] [blame] | 263 | </div> |
| 264 | ) |
pineafan | a5ce910 | 2021-09-02 17:21:31 +0100 | [diff] [blame] | 265 | } |
| 266 | |
| 267 | export default Header; |