diff --git a/components/arcade/footer.js b/components/arcade/footer.js
new file mode 100644
index 00000000..b5c8116f
--- /dev/null
+++ b/components/arcade/footer.js
@@ -0,0 +1,43 @@
+import { Box, Heading, Text, Link } from 'theme-ui'
+import Footer from '../footer'
+
+const Description = () => (
+
+
+ A project by Hack Club .
+
+
+ Prelaunch site.
+
+
+ Hack Club is a registered 501(c)3 nonprofit organization that supports a
+ network of 20k+ technical high schoolers. We believe you learn best by
+ building so we're creating community and providing grants so you can make. In the past few years, we've{' '}
+
+ given away 100k+ in hardware grants
+
+ ,{' '}
+
+ hosted the world's longest hackathon on land
+
+ , and{' '}
+
+ brought 183 teenagers to SF for a hackathon
+
+ .
+
+
+)
+
+const ArcadeFooter = () => {
+ return (
+
+ )
+}
+
+export default ArcadeFooter
diff --git a/components/arcade/join.js b/components/arcade/join.js
new file mode 100644
index 00000000..ce2ed90e
--- /dev/null
+++ b/components/arcade/join.js
@@ -0,0 +1,320 @@
+import JSConfetti from 'js-confetti'
+import React, { useEffect, useRef, useState } from 'react'
+import { Box, Flex, Text } from 'theme-ui'
+
+/** @jsxImportSource theme-ui */
+const Join = ({ fold, last, showForm, setForm, formSent, setFormSent }) => {
+ const [email, setEmail] = useState('')
+ const [highschool, setHighschool] = useState(false)
+
+ let jsConfetti = useRef()
+
+ useEffect(() => {
+ jsConfetti.current = new JSConfetti()
+ }, [])
+
+ const handleFormSubmit = async e => {
+ e.preventDefault()
+
+ try {
+ const response = await fetch('/api/arcade/slack', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ userEmail: email
+ })
+ })
+
+ if (!response.ok) {
+ const errorData = await response.json()
+ console.error('Error:', errorData)
+ // Optionally handle the error here (e.g., show a message to the user)
+ return
+ }
+
+ setEmail('')
+ setFormSent(true)
+ jsConfetti.current.addConfetti({
+ confettiColors: [
+ // Hack Club colours!
+ '#09AFB4',
+ '#FF5C00'
+ ]
+ })
+ } catch (error) {
+ console.error('Error:', error)
+ }
+ }
+ return (
+
+ {showForm ? (
+ formSent ? (
+ fold ? (
+
+ Email from The Arcade arriving in your inbox soon!
+
+ ) : (
+
+ Free Stickers
+
+ )
+ ) : (
+
+ )
+ ) : (
+ {
+ setForm(true)
+ }}
+ target="_blank"
+ className="slackey"
+ sx={{
+ justifyContent: 'center',
+ alignItems: 'center',
+ textDecoration: 'none',
+ backgroundColor: '#FF5C00',
+ cursor: 'pointer',
+ color: '#FAEFD6',
+ width: 'fit-content',
+ paddingX: ['8px', '10px', '15px'],
+ paddingY: ['5px', '7px', '10px'],
+ fontSize: ['24px', '27px', '30px'],
+ borderRadius: '5px',
+ textAlign: 'center',
+ // margin: 'auto',
+ // mt: 3,
+ zIndex: 2,
+ transitionDuration: '0.3s',
+ '&:hover': {
+ transform: 'scale(1.05)'
+ }
+ }}
+ >
+ Join ARCADE!
+
+ )}
+
+ {fold ? (
+ showForm ? (
+ formSent ? (
+
+ Free Stickers
+
+ ) : (
+ <>>
+ )
+ ) : (
+
+ Free Stickers
+
+ )
+ ) : (
+ <>>
+ )}
+
+ )
+}
+
+export default Join
diff --git a/components/arcade/prizes.js b/components/arcade/prizes.js
new file mode 100644
index 00000000..0882da03
--- /dev/null
+++ b/components/arcade/prizes.js
@@ -0,0 +1,119 @@
+import React, { useState, useRef, useEffect } from 'react'
+import { Box, Button, Text, Flex, Grid, Card, Link } from 'theme-ui'
+import Balancer from 'react-wrap-balancer'
+import Quantity from './quantity'
+/** @jsxImportSource theme-ui */
+const Prizes = ({
+ img,
+ text,
+ subtext,
+ cost,
+ polaroidRotation,
+ ticketRotation,
+ link,
+ quantity,
+ onQuantityChange,
+ index,
+ ...props
+}) => {
+ return (
+
+
+
+
+
+ {text}
+
+ {/*
+ {text}
+ */}
+
+
+ {subtext}
+
+
+
+ {link ? (
+ <>
+
+
+ You can order {quantity} of these
+
+
+
+
+
+ Buy
+
+
+ >
+ ) : (
+ <>>
+ )}
+
+ {cost} {link ? '🎟️' : cost == 1 ? 'ticket' : 'tickets'}
+
+
+ )
+}
+
+export default Prizes
diff --git a/components/arcade/projects.js b/components/arcade/projects.js
new file mode 100644
index 00000000..97f2f2a0
--- /dev/null
+++ b/components/arcade/projects.js
@@ -0,0 +1,315 @@
+import React, { useState } from 'react'
+import styled from '@emotion/styled'
+import {
+ Box,
+ Button,
+ Container,
+ Flex,
+ Heading,
+ Card,
+ Grid,
+ Link as A,
+ Text,
+ Avatar,
+ Image
+} from 'theme-ui'
+import Photo from '../../components/photo'
+import NextImage from 'next/image'
+import Marquee from 'react-marquee-slider'
+import Photo1 from '../../public/winter/1.jpeg'
+import Photo2 from '../../public/winter/2.png'
+import Photo3 from '../../public/winter/3.jpeg'
+import Photo4 from '../../public/winter/4.jpeg'
+import Photo5 from '../../public/winter/5.jpeg'
+import Photo6 from '../../public/winter/6.jpeg'
+import Photo7 from '../../public/winter/7.jpeg'
+import Photo8 from '../../public/winter/8.jpeg'
+import Photo9 from '../../public/winter/9.jpeg'
+import Photo10 from '../../public/winter/10.jpeg'
+import Photo12 from '../../public/winter/12.jpeg'
+import Photo13 from '../../public/winter/13.jpeg'
+import Photo14 from '../../public/winter/14.jpeg'
+import Photo15 from '../../public/winter/15.jpeg'
+import Photo16 from '../../public/winter/16.jpeg'
+import Photo17 from '../../public/winter/17.jpeg'
+import Photo18 from '../../public/winter/18.jpeg'
+import Photo19 from '../../public/winter/19.jpeg'
+import Photo20 from '../../public/winter/20.jpeg'
+import Photo21 from '../../public/winter/21.jpeg'
+import Photo22 from '../../public/winter/22.jpeg'
+import Photo23 from '../../public/winter/23.jpeg'
+import Photo24 from '../../public/winter/24.jpeg'
+import Photo25 from '../../public/winter/25.jpeg'
+import Photo26 from '../../public/winter/26.jpeg'
+import Photo27 from '../../public/winter/27.jpeg'
+import Photo28 from '../../public/winter/28.jpeg'
+import Photo29 from '../../public/winter/29.jpeg'
+import Photo30 from '../../public/winter/30.jpeg'
+import Photo31 from '../../public/winter/31.png'
+
+/** @jsxImportSource theme-ui */
+
+const Header = styled(Box)`
+ background: url('/pattern.svg');
+`
+
+const Sheet = styled(Card)`
+ position: relative;
+ overflow: hidden;
+ border-radius: 8px;
+ width: 100%;
+ color: white;
+`
+Sheet.defaultProps = {
+ sx: {
+ bg: 'rgba(255, 255, 255, 0.875)',
+ p: [3, 4],
+ color: 'black',
+ width: 1,
+ mb: 4
+ }
+}
+
+Sheet.defaultProps = {
+ sx: {
+ maxWidth: '52rem',
+ fontSize: 3,
+ p: [4, 5],
+ color: 'white'
+ }
+}
+const PhotoRow = ({ photos }) => (
+
+
+
+ {photos.map((photo, index) => (
+
+ ))}
+
+
+
+
+ {photos.map((photo, index) => (
+
+ ))}
+
+
+
+)
+
+const Cards = ({ avatar, username, description, image }) => {
+ return (
+
+
+
+
+
+ @{username}
+
+
+
+ div': { width: 18, height: 18 }
+ }}
+ >
+ {description}
+
+
+ {/* */}
+
+
+ )
+}
+
+export default function Projects() {
+ const [count, setCount] = useState(0)
+
+ let list = [
+ 'Drawing robot',
+ '3D printer',
+ "DIY Electric Skateboard",
+ 'Pixel art display',
+ "Smart Garden",
+ 'CNC machine',
+ "Interactive LED Art",
+ "VR Escape Room",
+ "Image Recognition App",
+ 'DIY Camera',
+ "Multiplayer AR Game",
+ "Drone Swarm Choreography"
+ ]
+
+ if (count === list.length - 1) {
+ setCount(0)
+ }
+
+ let project_idea = list[count]
+
+ return (
+
+
+
+
+
+
+ You could be building a
+ setCount(count + 1)}
+ >
+
+ {project_idea}
+
+
+
+
+
+
+
+
+
+ See more projects →
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/components/arcade/quantity.js b/components/arcade/quantity.js
new file mode 100644
index 00000000..c78070c1
--- /dev/null
+++ b/components/arcade/quantity.js
@@ -0,0 +1,67 @@
+import React, { useState, useEffect } from 'react'
+/** @jsxImportSource theme-ui */
+const styled = `
+@import url('https://fonts.googleapis.com/css2?family=Slackey&family=Emblema+One&family=Gaegu&display=swap');
+
+.slackey {
+ font-family: "Slackey", sans-serif;
+ }
+
+ .gaegu {
+ font-family: "Gaegu", sans-serif;
+}
+
+body {
+ background-color: #FAEFD6;
+}
+
+`
+const Quantity = ({ lbl, numOptions, onQuantityChange, id }) => {
+ const [options, setOptions] = useState([])
+ const [selectedOption, setSelectedOption] = useState('')
+
+ useEffect(() => {
+ // Generate options based on the provided number
+ function generateArray(n) {
+ return Array.from({ length: n }, (_, i) => i + 1)
+ }
+
+ // Example usage:
+ const n = numOptions
+ const options = generateArray(n)
+
+ setOptions(options)
+ }, [numOptions]) // Dependency array includes numOptions to regenerate options if it changes
+
+ const handleChange = event => {
+ setSelectedOption(event.target.value)
+ onQuantityChange(id, parseInt(event.target.value))
+ }
+
+ return (
+ <>
+
+ {options.map(option => (
+
+ {option}
+
+ ))}
+
+
+ >
+ )
+}
+
+export default Quantity
diff --git a/components/arcade/rsvp.js b/components/arcade/rsvp.js
new file mode 100644
index 00000000..04df0dfe
--- /dev/null
+++ b/components/arcade/rsvp.js
@@ -0,0 +1,5 @@
+import { Box, Button, Text } from 'theme-ui'
+
+
+
+export default RSVP
\ No newline at end of file
diff --git a/components/arcade/shop-component.js b/components/arcade/shop-component.js
new file mode 100644
index 00000000..f1757775
--- /dev/null
+++ b/components/arcade/shop-component.js
@@ -0,0 +1,99 @@
+import React, { useState, useEffect } from 'react';
+import { Text, Grid } from 'theme-ui';
+import Prizes from './prizes';
+
+/** @jsxImportSource theme-ui */
+
+const styled = `
+@import url('https://fonts.googleapis.com/css2?family=Slackey&family=Emblema+One&family=Gaegu&display=swap');
+
+.slackey {
+ font-family: "Slackey", sans-serif;
+}
+
+.gaegu {
+ font-family: "Gaegu", sans-serif;
+}
+
+body {
+ background-color: #FAEFD6;
+}
+`;
+
+export default function ShopComponent({
+ availableItems,
+ userAirtableID = null,
+ hoursBalance = null
+}) {
+ // State to manage quantity for each item
+ const [quantities, setQuantities] = useState({});
+ const [pRotate, setPRotate] = useState(0)
+ const [tRotate, setTRotate] = useState(0)
+
+ // Function to update quantity for an item
+ const handleQuantityChange = (itemID, quantity) => {
+ setQuantities({ ...quantities, [itemID]: quantity });
+ };
+
+ function buyLink(itemID) {
+ const quantity = quantities[itemID] || 1; // Default quantity is 1 if not set
+ return `https://forms.hackclub.com/arcade-order?user_id=${userAirtableID}&item_id=${itemID}&quantity=${quantity}`;
+ }
+
+ const includeBuyLink = userAirtableID !== null;
+ useEffect(() => {
+ setPRotate(2 + Math.random() * 4) * (Math.random() > 0.5 ? 1 : -1)
+ setTRotate(5 + Math.random() * 14) * (Math.random() > 0.5 ? 1 : -1)
+ }, []);
+
+ return (
+ <>
+
+ Shop for {userAirtableID}
+
+
+ {availableItems
+ .sort((a, b) => a['Cost Hours'] - b['Cost Hours'])
+ .map((item) => (
+ handleQuantityChange(item.id, q)} // Pass handler to update quantity
+ />
+ ))}
+
+
+ >
+ );
+}
diff --git a/next.config.mjs b/next.config.mjs
index feef0c1e..b2961bdd 100755
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -28,6 +28,7 @@ const nextConfig = {
return config
},
async redirects() {
+
return [
{
source: '/bank/:path*',
@@ -190,6 +191,11 @@ const nextConfig = {
source: '/blot/',
destination: 'https://blot.hackclub.com',
permanent: false
+ },
+ {
+ source: '/slack',
+ destination: '/arcade?param=slack',
+ permanent: false
}
]
},
@@ -304,8 +310,8 @@ const nextConfig = {
destination: '/bin/selector/index.html'
},
{
- source: '/arcade/',
- destination: '/arcade/index.html'
+ source: '/arcade/power-hour',
+ destination: '/arcade/power-hour/index.html'
},
]
},
diff --git a/package.json b/package.json
index 0c393a52..3f2f4269 100644
--- a/package.json
+++ b/package.json
@@ -49,6 +49,7 @@
"geopattern": "^1.2.3",
"globby": "^11.0.4",
"graphql": "^16.8.1",
+ "howler": "^2.2.4",
"js-confetti": "^0.12.0",
"jszip": "^3.10.1",
"jszip-utils": "^0.1.0",
diff --git a/pages/api/arcade/[userAirtableID]/index.js b/pages/api/arcade/[userAirtableID]/index.js
new file mode 100644
index 00000000..cfcd8844
--- /dev/null
+++ b/pages/api/arcade/[userAirtableID]/index.js
@@ -0,0 +1,25 @@
+import AirtablePlus from "airtable-plus"
+
+export async function getArcadeUser(recordID) {
+ const airtable = new AirtablePlus({
+ apiKey: process.env.AIRTABLE_API_KEY,
+ baseID: 'app4kCWulfB02bV8Q',
+ tableName: "Users"
+ })
+
+ return await airtable.find(recordID)
+}
+
+export default async function handler(req, res) {
+ const { userAirtableID } = req.query
+
+ const user = await getArcadeUser(userAirtableID)
+
+ if (!user) {
+ return res.status(404).json({ error: "User not found" })
+ }
+
+ const hoursBalance = user.fields["Balance (Hours)"]
+
+ res.json({ userAirtableID, hoursBalance })
+}
\ No newline at end of file
diff --git a/pages/api/arcade/inventory.js b/pages/api/arcade/hack-hour/inventory.js
similarity index 100%
rename from pages/api/arcade/inventory.js
rename to pages/api/arcade/hack-hour/inventory.js
diff --git a/pages/api/arcade/openai.js b/pages/api/arcade/openai.js
new file mode 100644
index 00000000..3ed0e6f8
--- /dev/null
+++ b/pages/api/arcade/openai.js
@@ -0,0 +1,35 @@
+import OpenAI from 'openai'
+
+const sample = arr => arr[Math.floor(Math.random() * arr.length)]
+
+const messageStarters = [
+ 'you could build a',
+ 'what if you built a',
+ 'how about a',
+ 'you could make a',
+ "as a dino, i'd build a",
+ "since it's summer, i'd make a",
+ "i've been dreaming of creating a",
+ 'picture this:',
+ 'oh, oh, oh! a',
+ 'i dare you to make a',
+]
+
+const generateProjectIdea = async () => {
+ let prompt = `You are a software engineer that wants to bring joy through chaos. Something different every time. Please propose a funky simple project that will take under 6 hours to complete in 1 quick sentence. You can also suggest projects for a family member. Keep it at less than 15 words. The funkier, stupidier, and sillier your ideas the better. Your response must start with "${sample(messageStarters)}
+`
+ // expects OPENAI_API_KEY
+ const openai = new OpenAI(process.env.OPENAI_API_KEY)
+ const chatCompletion = await openai.chat.completions.create({
+ messages: [{ role: 'user', content: prompt }],
+ model: 'gpt-3.5-turbo'
+ })
+
+ return chatCompletion.choices[0].message.content
+}
+
+export default async function handler(req, res) {
+ const recommendation = await generateProjectIdea()
+
+ res.send({ recommendation })
+}
diff --git a/pages/api/arcade/shop.js b/pages/api/arcade/shop.js
new file mode 100644
index 00000000..ea5b6224
--- /dev/null
+++ b/pages/api/arcade/shop.js
@@ -0,0 +1,28 @@
+import AirtablePlus from "airtable-plus"
+
+export const shopParts = async () => {
+ const airtable = new AirtablePlus({
+ apiKey: process.env.AIRTABLE_API_KEY,
+ baseID: "app4kCWulfB02bV8Q",
+ tableName: "Shop Items"
+ })
+
+ const records = await airtable.read()
+ return records.map(record => ({id: record.id, ...record.fields}))
+}
+
+export default async function handler(req, res) {
+ const data = await shopParts()
+
+ const filteredData = data.filter(record => record["Enabled"]).map(record => {
+ return {
+ name: record['Name'],
+ smallName: record['Small Name'],
+ description: record['Description'],
+ hours: record['Cost Hours'],
+ imageURL: record['Image URL'],
+ }
+ })
+
+ res.json(filteredData)
+}
\ No newline at end of file
diff --git a/pages/api/arcade/slack.js b/pages/api/arcade/slack.js
new file mode 100644
index 00000000..65631d19
--- /dev/null
+++ b/pages/api/arcade/slack.js
@@ -0,0 +1,52 @@
+import AirtablePlus from 'airtable-plus'
+
+async function inviteToArcadius({ email }) {
+ const response = await fetch('https://arcadius.hackclub.com/slack-invite', {
+ body: JSON.stringify({
+ email
+ }),
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${process.env.SLACK_KEY}`
+ }
+ })
+
+ return response
+}
+async function inviteToAirtable({ email, ip }) {
+ const airtable = new AirtablePlus({
+ baseID: 'appaqcJtn33vb59Au',
+ apiKey: process.env.AIRTABLE_API_KEY,
+ tableName: 'Arcade Joins'
+ })
+ return await airtable.create({ 'Email': email, 'IP': ip })
+}
+
+export default async function handler(req, res) {
+ if (req.method === 'POST') {
+ try {
+ const data = req.body
+
+ const email = data.userEmail
+ const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress
+
+ const result = await Promise.all([
+ inviteToArcadius({ email }),
+ inviteToAirtable({ email, ip })
+ ])
+
+ if (result[0]?.response?.ok) {
+ res.json({ status: 200, message: 'Invitation sent!' })
+ } else {
+ const errorData = await result[0]?.response?.json()
+ res.json({ status: 400, error: errorData })
+ }
+ } catch (error) {
+ console.error(error)
+ res.json({ status: 400, error })
+ }
+ } else {
+ res.json({ status: 405, error: 'POST method required' })
+ }
+}
diff --git a/pages/arcade/[userAirtableID]/shop.js b/pages/arcade/[userAirtableID]/shop.js
new file mode 100644
index 00000000..63dc581f
--- /dev/null
+++ b/pages/arcade/[userAirtableID]/shop.js
@@ -0,0 +1,91 @@
+import ShopComponent from "../../../components/arcade/shop-component"
+import { getArcadeUser } from "../../api/arcade/[userAirtableID]"
+import { shopParts } from "../../api/arcade/shop"
+import { Image, Link, Text } from 'theme-ui'
+import { Balancer } from "react-wrap-balancer"
+import Meta from '@hackclub/meta'
+import Head from 'next/head'
+
+/** @jsxImportSource theme-ui */
+
+const styled = `
+@import url('https://fonts.googleapis.com/css2?family=Slackey&family=Emblema+One&family=Gaegu&display=swap');
+
+.slackey {
+ font-family: "Slackey", sans-serif;
+ }
+
+ .gaegu {
+ font-family: "Gaegu", sans-serif;
+}
+
+body {
+ background-color: #FAEFD6;
+}
+
+`
+
+export default function Shop({ availableItems, userAirtableID = null, hoursBalance = 0 }) {
+
+ return (
+ <>
+
+
+
+ Welcome to the shop
+
+
+ Your current balance is {Math.floor(hoursBalance)} 🎟️
+
+ >
+ )
+}
+
+export async function getStaticPaths() {
+ return {
+ paths: [],
+ fallback: 'blocking'
+ }
+}
+
+export async function getStaticProps({params}) {
+ const { userAirtableID } = params
+
+ const props = { userAirtableID }
+
+ await Promise.all([
+ shopParts().then(items => {
+ const availableItems = items.filter(item => item['Enabled']).map(item => ({
+ 'Name': item['Name'] || null,
+ 'Description': item['Description'] || null,
+ 'Cost Hours': item['Cost Hours'] || 0,
+ id: item.id,
+ 'Image URL': item['Image URL'] || null,
+ 'Max Order Quantity': item['Max Order Quantity'] || 1
+ }))
+ props.availableItems = availableItems
+ }),
+ getArcadeUser(userAirtableID).then(user => {
+ const hoursBalance = user.fields["Balance (Hours)"] || 0
+ props.hoursBalance = hoursBalance
+ })
+ ])
+
+ return { props }
+}
\ No newline at end of file
diff --git a/pages/arcade/index.js b/pages/arcade/index.js
new file mode 100644
index 00000000..541f1cf0
--- /dev/null
+++ b/pages/arcade/index.js
@@ -0,0 +1,1889 @@
+import React, { useState } from 'react'
+import { useRouter } from 'next/router'
+import Head from 'next/head'
+import Nav from '../../components/nav'
+import Meta from '@hackclub/meta'
+import { Box, Text, Flex, Grid, Card, Close, Divider, Heading } from 'theme-ui'
+import Image from 'next/image'
+import fs from 'fs'
+import path from 'path'
+import { startCase } from 'lodash'
+import Projects from '../../components/arcade/projects'
+import { Howl } from 'howler'
+import Ticker from 'react-ticker'
+import PageVisibility from 'react-page-visibility'
+import ArcadeFooter from '../../components/arcade/footer'
+import Balancer from 'react-wrap-balancer'
+import { Fade } from 'react-reveal'
+import Join from '../../components/arcade/join'
+import Announcement from '../../components/announcement'
+import Link from 'next/link'
+import { shopParts } from '../api/arcade/shop'
+/** @jsxImportSource theme-ui */
+
+const styled = `
+@import url('https://fonts.googleapis.com/css2?family=Slackey&family=Emblema+One&family=Gaegu&display=swap');
+body, html {
+ overflow-x: hidden;
+ }
+.slackey {
+ font-family: "Slackey", sans-serif;
+ }
+
+ .arcade {
+ text-shadow: -4px -4px#FAEFD6,-3px -3px #FAEFD6, -2px -2px #FAEFD6,
+ -2px -2px #FAEFD6, -1px -1px #FAEFD6, -1px -1px #FAEFD6,
+ -1px -1px #FAEFD6, 1px 1px #FAEFD6, 1px 1px #FAEFD6,
+ 1px 1px #FAEFD6, 2px 2px #FAEFD6, 4px 4px #FAEFD6,
+ 3px 3px #FAEFD6, -8px -8px #09AFB4, -6px -6px #09AFB4,
+ -5px -5px #09AFB4, -4px -4px #09AFB4, -3px -3px #09AFB4,
+ -2px -2px #09AFB4, 2px 2px #09AFB4, 3px 3px #09AFB4,
+ 5px 5px #09AFB4, 4px 4px #09AFB4, 7px 7px #09AFB4,
+ 6px 6px #09AFB4, 8px 8px #09AFB4, -8px -8px #09AFB4, 9px 9px #09AFB4, -9px -9px #09AFB4, 10px 10px #09AFB4, -10px -10px #09AFB4;
+ }
+
+ .arcade2 {
+ text-shadow: -4px -4px#FAEFD6,-3px -3px #FAEFD6, -2px -2px #FAEFD6,
+ -2px -2px #FAEFD6, -1px -1px #FAEFD6, -1px -1px #FAEFD6,
+ -1px -1px #FAEFD6, 1px 1px #FAEFD6, 1px 1px #FAEFD6,
+ 1px 1px #FAEFD6, 2px 2px #FAEFD6, 4px 4px #FAEFD6,
+ 3px 3px #FAEFD6, -8px -8px #09AFB4, -6px -6px #09AFB4,
+ -5px -5px #09AFB4, -4px -4px #09AFB4, -3px -3px #09AFB4,
+ -2px -2px #09AFB4, 2px 2px #09AFB4, 3px 3px #09AFB4,
+ 5px 5px #09AFB4, 4px 4px #09AFB4, 7px 7px #09AFB4,
+ 6px 6px #09AFB4;
+ }
+
+ .arcade3 {
+ text-shadow: -4px -4px#FAEFD6,-3px -3px #FAEFD6, -2px -2px #FAEFD6,
+ -2px -2px #FAEFD6, -1px -1px #FAEFD6, -1px -1px #FAEFD6,
+ -1px -1px #FAEFD6, 1px 1px #FAEFD6, 1px 1px #FAEFD6,
+ 1px 1px #FAEFD6, 2px 2px #FAEFD6, 4px 4px #FAEFD6,
+ 3px 3px #FAEFD6, -8px -8px #09AFB4, -6px -6px #09AFB4,
+ -5px -5px #09AFB4, -4px -4px #09AFB4, -3px -3px #09AFB4,
+ -2px -2px #09AFB4, 2px 2px #09AFB4, 3px 3px #09AFB4,
+ 5px 5px #09AFB4, 4px 4px #09AFB4, 7px 7px #09AFB4,
+ 6px 6px #09AFB4;
+ }
+ .emblema {
+ font-family: "Emblema One", system-ui;
+ }
+
+ .gaegu {
+ font-family: "Gaegu", sans-serif;
+ }
+
+ body {
+ background-color: #FAEFD6;
+ }
+
+ /* CSS from https://codepen.io/quadbaup/details/rKOKQv */
+.thought {
+ display: flex;
+ background-color: #F8F3F3;
+ padding: 20px;
+ border-radius: 30px;
+ min-width: 40px;
+ max-width: 180px;
+ opacity: 0;
+ height: 100px;
+ margin: 20px;
+ margin-left: -10px;
+ position: relative;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ /* text-align:center; */
+}
+
+.thought:before,
+.thought:after {
+ content: "";
+ background-color: #F8F3F3;
+ border-radius: 50%;
+ display: block;
+ position: absolute;
+ z-index: -1;
+}
+
+.thought:before {
+ width: 44px;
+ height: 44px;
+ top: -12px;
+ left: 28px;
+ box-shadow: -50px 30px 0 -12px #F8F3F3;
+}
+
+.thought:after {
+ bottom: -10px;
+ right: 26px;
+ width: 30px;
+ height: 30px;
+ box-shadow: 40px -34px 0 0 #F8F3F3,
+ -28px -6px 0 -2px #F8F3F3,
+ -24px 17px 0 -6px #F8F3F3,
+ -5px 25px 0 -10px #F8F3F3;
+}
+
+#generate-project-idea {
+ margin-top: -100px;
+}
+
+.talking {
+ animation: talking 1s infinite;
+}
+@keyframes talking {
+ 0% {
+ transform: translateY(0px);
+ }
+
+ 50% {
+ transform: translateY(-10px);
+ }
+
+ 100% {
+ transform: translateY(0px);
+ }
+}
+
+.floaty {
+ animation: float 6s ease-in-out infinite;
+}
+
+@keyframes float {
+
+ from,
+ to {
+ transform: translate(0%, -37%) rotate(-2deg);
+ }
+
+ 25% {
+ transform: translate(-2%, -40%) rotate(2deg);
+ }
+
+ 50% {
+ transform: translate(0%, -43%) rotate(-1deg);
+ }
+
+ 75% {
+ transform: translate(-1%, -40%) rotate(-1deg);
+ }
+}
+
+a {
+ color: inherit;
+}
+`
+
+const Powerups = ({
+ img,
+ text,
+ subtext,
+ fullName,
+ cost,
+ description,
+ fulfillmentDescription,
+ polaroidRotation,
+ ticketRotation,
+ extraTags,
+ ...props
+}) => {
+ const parsedFulfillmentDesc = fulfillmentDescription?.replace(
+ /\[(.*?)\]\((.*?)\)/g,
+ '$1 '
+ )
+ return (
+
+
+
+
+
+
+ {text}
+
+
+ {subtext}
+
+
+
+ {cost} {cost == 1 ? 'ticket' : 'tickets'}
+
+ {extraTags?.map((tag, i) => {
+ if (tag == 'Limited Supply') {
+ return (
+
+ Limited!
+
+ )
+ }
+ })}
+ {
+ document.getElementById(`${text}-info`).showModal()
+ }}
+ >
+ 📦
+
+
+ {
+ document.getElementById(`${text}-info`).close()
+ }}
+ />
+
+
+
+
+ {fullName}
+
+
+
+
+
+
+ {description}
+
+
+
+
+
+ {cost} {cost == 1 ? 'ticket' : 'tickets'}
+
+
+
+ )
+}
+
+const Intro = ({ title, num, text, img, third, ...props }) => {
+ return (
+
+
+ {title}
+
+
+ {text}
+
+
+ {num}
+
+
+
+ )
+}
+
+const Tickets = ({ title, num, text, link, bugEater, ...props }) => {
+ return (
+
+
+ {title}
+
+
+ {text}
+
+ {bugEater && (
+ <>
+
+
+ Click me for ideas!
+
+
+ Click me for ideas!
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+ )
+}
+
+const Sticker = ({ st }) => {
+ return (
+
+
+
+
+
+ {startCase(st.replace(/\.(svg|png)/, ''))}
+
+
+
+
+
+ )
+}
+
+const Item = ({ name, img, cost }) => {
+ return (
+
+
+ {cost}h
+
+
+
+ )
+}
+
+const FAQ = ({ question, answer }) => {
+ const parsedAnswer = answer?.replace(
+ /\[(.*?)\]\((.*?)\)/g,
+ '$1 '
+ )
+ return (
+
+
+ {question}
+
+
+
+
+ )
+}
+function getRandomInt(max) {
+ return Math.floor(Math.random() * max)
+}
+
+let yap_sounds = {
+ // ty caleb!
+ thinking: [
+ new Howl({ src: '/bin/yapping/thonk1.wav' }),
+ new Howl({ src: '/bin/yapping/thonk2.wav' }),
+ new Howl({ src: '/bin/yapping/thonk3.wav' })
+ ],
+ talking: {
+ // these sounds and most of the yapping code are adapted from https://github.com/equalo-official/animalese-generator
+ a: new Howl({ src: '/bin/yapping/a.wav', volume: 0.16 }),
+ b: new Howl({ src: '/bin/yapping/b.wav', volume: 0.16 }),
+ c: new Howl({ src: '/bin/yapping/c.wav', volume: 0.16 }),
+ d: new Howl({ src: '/bin/yapping/d.wav', volume: 0.16 }),
+ e: new Howl({ src: '/bin/yapping/e.wav', volume: 0.16 }),
+ f: new Howl({ src: '/bin/yapping/f.wav', volume: 0.16 }),
+ g: new Howl({ src: '/bin/yapping/g.wav', volume: 0.16 }),
+ h: new Howl({ src: '/bin/yapping/h.wav', volume: 0.16 }),
+ i: new Howl({ src: '/bin/yapping/i.wav', volume: 0.16 }),
+ j: new Howl({ src: '/bin/yapping/j.wav', volume: 0.16 }),
+ k: new Howl({ src: '/bin/yapping/k.wav', volume: 0.16 }),
+ l: new Howl({ src: '/bin/yapping/l.wav', volume: 0.16 }),
+ m: new Howl({ src: '/bin/yapping/m.wav', volume: 0.16 }),
+ n: new Howl({ src: '/bin/yapping/n.wav', volume: 0.16 }),
+ o: new Howl({ src: '/bin/yapping/o.wav', volume: 0.16 }),
+ p: new Howl({ src: '/bin/yapping/p.wav', volume: 0.16 }),
+ q: new Howl({ src: '/bin/yapping/q.wav', volume: 0.16 }),
+ r: new Howl({ src: '/bin/yapping/r.wav', volume: 0.16 }),
+ s: new Howl({ src: '/bin/yapping/s.wav', volume: 0.16 }),
+ t: new Howl({ src: '/bin/yapping/t.wav', volume: 0.16 }),
+ u: new Howl({ src: '/bin/yapping/u.wav', volume: 0.16 }),
+ v: new Howl({ src: '/bin/yapping/v.wav', volume: 0.16 }),
+ w: new Howl({ src: '/bin/yapping/w.wav', volume: 0.16 }),
+ x: new Howl({ src: '/bin/yapping/x.wav', volume: 0.16 }),
+ y: new Howl({ src: '/bin/yapping/y.wav', volume: 0.16 }),
+ z: new Howl({ src: '/bin/yapping/z.wav', volume: 0.16 }),
+ th: new Howl({ src: '/bin/yapping/th.wav', volume: 0.16 }),
+ sh: new Howl({ src: '/bin/yapping/sh.wav', volume: 0.16 }),
+ _: new Howl({ src: '/bin/yapping/_.wav', volume: 0.16 })
+ }
+}
+
+async function yap(text, letterCallback) {
+ text = text.toLowerCase()
+ const yap_queue = []
+ for (let i = 0; i < text.length; i++) {
+ const char = text[i]
+ try {
+ if (char === 's' && text[i + 1] === 'h') {
+ // test for 'sh' sound
+ yap_queue.push(yap_sounds.talking['sh'])
+ continue
+ } else if (char === 't' && text[i + 1] === 'h') {
+ // test for 'th' sound
+ yap_queue.push(yap_sounds.talking['th'])
+ continue
+ } else if (char === 'h' && (text[i - 1] === 's' || text[i - 1] === 't')) {
+ // test if previous letter was 's' or 't' and current letter is 'h'
+ yap_queue.push(yap_sounds.talking['_'])
+ continue
+ } else if (char === ',' || char === '?' || char === '.') {
+ yap_queue.push(yap_sounds.talking['_'])
+ continue
+ } else if (char === text[i - 1]) {
+ // skip repeat letters
+ yap_queue.push(yap_sounds.talking['_'])
+ continue
+ }
+ } catch (e) {
+ // who cares. pick up a foot ball
+ }
+ if (!char.match(/[a-zA-Z.]/)) {
+ yap_queue.push(yap_sounds.talking['_'])
+ continue // skip characters that are not letters or periods
+ }
+ yap_queue.push(yap_sounds.talking[char])
+ }
+
+ function next_yap() {
+ letterCallback(yap_queue.length)
+ if (yap_queue.length === 0) return
+ let noise = yap_queue.shift()
+ noise.rate(2 * (Math.random() * 0.5 + 3.5))
+ noise.once('end', next_yap)
+ noise.play()
+ }
+
+ next_yap()
+}
+
+async function generateProjectIdea() {
+ if (
+ document
+ .querySelector('#generate-project-idea')
+ .classList.contains('disabled')
+ ) {
+ return
+ }
+
+ yap_sounds.thinking[getRandomInt(yap_sounds.thinking.length)].play()
+ document.querySelector('#generate-project-idea').style.marginTop = '0px'
+ document.querySelector('#console').style.marginTop = '-50px'
+ document.querySelector('#console2').style.opacity = '0'
+ document.querySelector('#project-idea').style.opacity = '1'
+ document.querySelector('#generate-project-idea').classList.add('disabled')
+ document.querySelector('#project-idea').innerHTML =
+ '' + thinkingWords() + '...' + ' '
+ document.querySelector('#generate-project-idea').src =
+ 'https://cloud-g5g5sistf-hack-club-bot.vercel.app/1untitled_artwork_8_1.png'
+ let text = ''
+ const res = await fetch('/api/arcade/openai/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify()
+ })
+
+ const json = await res.json()
+ text = json.recommendation
+ document.querySelector('#project-idea').innerHTML = ''
+ document.querySelector('#generate-project-idea').src =
+ 'https://cloud-81d1s66l7-hack-club-bot.vercel.app/0untitled_artwork_9_1.png'
+ document.querySelector('#generate-project-idea').classList.remove('disabled')
+ // document.querySelector('#generate-project-idea').classList.add('talking')
+ yap(text, i => {
+ document.querySelector('#project-idea').innerHTML = text.slice(
+ 0,
+ Math.max(text.length - i + 1, 0)
+ )
+ })
+}
+
+function thinkingWords() {
+ const arr = [
+ 'thinking',
+ 'single neuron activated',
+ '2 braincells rubbing together',
+ 'ponderosourus',
+ 'contemplatosaurus',
+ 'dinosaur brain activated',
+ 'thinking about trash',
+ 'rummaging through my thoughts'
+ ]
+ return arr[Math.floor(Math.random() * arr.length)]
+}
+
+const Arcade = ({
+ stickers = [],
+ carousel = [],
+ highlightedItems = []
+}) => {
+ const [showComponent, setShowComponent] = useState(false)
+ const [showNum, setNum] = useState(false)
+ const [showForm, setForm] = useState(false)
+ const [formSent, setFormSent] = useState(false)
+ const [isRevealed, setIsRevealed] = useState(false)
+
+ const handleButtonClick = () => {
+ setIsRevealed(!isRevealed)
+ }
+
+ const router = useRouter()
+ const { query } = router
+
+ const slack = query.param
+
+ const generateRandomNumber = () => {
+ const newRandomNumber = Math.floor(Math.random() * stickers.length) // Generate a random number between 0 and 99
+ setNum(newRandomNumber)
+ }
+
+ const handleMouseEnter = () => {
+ setShowComponent(true)
+ }
+
+ const handleMouseLeave = () => {
+ setShowComponent(false)
+ }
+
+ const mouseEnter = () => {
+ handleMouseEnter()
+ generateRandomNumber()
+ }
+
+ const [pageIsVisible, setPageIsVisible] = useState(true)
+ const handleVisibilityChange = isVisible => {
+ setPageIsVisible(isVisible)
+ }
+ return (
+ <>
+
+
+
+
+
+
+
+
+ {slack == 'slack' ? (
+
+ ) : (
+ <>>
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ ARCADE
+
+
+ {/*
+
+ Build something cool.
+
+ */}
+
+
+ The summer is yours for the making
+ {/* Get free tools
+
+ & build something cool */}
+ {/* Get free tools to build something cool. */}
+ {/* */}
+ {/* This summer is yours. */}
+
+
+
+ {/*
+ This summer is yours.
+ */}
+
+
+
+ {showForm ? (
+ <>>
+ ) : (
+
+ For high schoolers (or younger).
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {/*
+ Calling high school makers: Join{' '}
+
+ ARCADE
+
+ .
+ */}
+
+ {/* What are you waiting for? */}
+ How to play
+
+
+
+
+
+
+ {/*
+
+ Hack. Rinse. Repeat.
+
+ */}
+
+
+
+ Join the{' '}
+
+ Hack Club Slack
+ {' '}
+ and use /hack in #arcade to log your hours! You earn a
+ ticket for each hour spent!
+ >
+ }
+ num="2"
+ img="/arcade/o1.png"
+ />
+
+
+
+
+
+
+
+ Make stuff. Get stuff. Repeat.
+
+
+
+
+ {pageIsVisible && (
+
+
+ {() => (
+
+ {carousel.map((item, i) => (
+
+ ))}
+
+ )}
+
+
+ )}
+
+
+
+
+
+ {/* */}
+
+ Get{' '}
+
+ {showComponent && }
+ free stickers
+ {' '}
+ and code with other high schoolers!
+
+ {/* */}
+
+
+
+
+
+
+ One hour at a time,
+
+
+ What will{' '}
+
+ you
+ {' '}
+ make this summer?
+
+
+
+ You could build an AR game, pixel art display, drawing robot, and more! Anytime you work on your project, start the hack hour timer. You earn a ticket for every hour you spend on your project.
+ Don't know where to start?
+
+
+
+ Boba drops:
+ {' '}
+ Build a website, get boba!
+
+
+
+ Wizard Orpheus:
+ {' '}
+ Build a text-based game with AI
+
+
+
+ The Bin:
+ {' '}
+ Build an online circuit, get the parts for free!
+
+
+
+ Sprig:
+ {' '}
+ Build a JS game, play it on your own console
+
+
+
+ OnBoard:
+ {' '}
+ Design a PCB, get a $100 grant
+
+
+ Blot: Write code.
+ Make art. Get a drawing machine.
+
+
+ Cider: Make a
+ mobile app, get an Apple Developer account
+
+
+
+ Easel:
+ {' '}
+ Write a programming language, receive fudge!
+
+
+ >}
+ num="Infinite"
+ sx={{
+ gridColumn: ['', 'span 2', 'span 2', 'span 2'],
+ h1: {
+ fontSize: [3, 4, 5]
+ },
+ p: {
+ fontSize: [2, 2, 3],
+ display: 'block',
+ pb: 2
+ },
+ minHeight: ['700px', '700px', '700px', 'auto']
+ }}
+ />
+ Click me for ideas!>
+ }
+ sx={{
+ '&ul>li': {
+ color: 'inherit'
+ },
+ gridColumn: ['span 2', 'span 2', 'span 2', 'span 1'],
+ minHeight: 'auto'
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Prizes
+ {' '}
+ to powerup your next project!
+
+
+
+ Redeem these with your tickets! For high schoolers (or younger)
+ only.
+
+ {/*
+ All physical items only fulfillable where Amazon can be shipped
+ unless otherwise stated.
+ */}
+
+ {highlightedItems.map((item, i) => (
+ 0.5 ? 1 : -1)
+ }
+ key={i}
+ />
+ ))}
+
+
+ This is just a{' '}
+
+ sneak peek
+
+ ...new items will be added over the summer!{' '}
+
+
+
+
+
+
+ F.A.Q.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* */}
+
+ Join{' '}
+
+ ARCADE
+
+ .
+
+ Build real projects. Share it with friends.
+
+ {/* */}
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export default Arcade
+
+export async function getStaticProps() {
+ const stickersDir = path.join(process.cwd(), 'public', 'stickers')
+ const stickers = fs
+ .readdirSync(stickersDir)
+ .filter(sticker => sticker !== 'hero.jpg')
+
+ const items = await shopParts()
+
+ const carousel = items
+ .map(record => ({
+ hours: record['Cost Hours'] || 0,
+ imageURL: record['Image URL'] || '',
+ enabledCarousel: record['Enabled Carousel'] || false,
+ }))
+ .filter(item => item.enabledCarousel)
+ .filter(item => item.imageURL !== '')
+
+ const highlightedItems = items
+ .filter(item => item['Enabled Highlight'])
+ .sort((_a, _b) => Math.random() - 0.5 > 0)
+ .map(record => ({
+ // id: record['ID'],
+ 'Image URL': record['Image URL'] || null,
+ 'Name': record['Name'] || null,
+ 'Small Name': record['Small Name'] || null,
+ 'Cost Hours': record['Cost Hours'] || null,
+ 'Description': record['Description'] || null,
+ 'Fulfillment Description': record['Fulfillment Description'] || null,
+ 'Extra tags': record['Extra tags'] || []
+ }))
+
+ return {
+ props: {
+ stickers,
+ highlightedItems,
+ carousel
+ }
+ }
+}
diff --git a/pages/index.js b/pages/index.js
index 22a0ef56..d1fd5150 100644
--- a/pages/index.js
+++ b/pages/index.js
@@ -203,6 +203,12 @@ function Page({
priority
gradient="linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0.45))"
/>
+
- )
-}
-
-export async function getStaticProps(context) {
- const currentPage = parseInt(context.params.page)
- const allProjects = await getAllOnboardProjects()
- const data = allProjects.slice((currentPage - 1) * 10, currentPage * 10)
- const projects = []
- for (const project of data) {
- projects.push(await getOnboardProject(project.name))
- }
- return {
- props: {
- projects,
- itemCount: allProjects.length,
- currentPage
- },
- revalidate: 120 // 2 minutes
- }
-}
-
-export async function getStaticPaths(_context) {
- const projectCount = await onboardProjectCount()
- const pages = Math.min(5, Math.ceil(projectCount / 10))
- const paths = Array(pages)
- .fill()
- .map((_, i) => ({ params: { page: (i + 1).toString() } }))
- return { paths, fallback: 'blocking' }
-}
diff --git a/pages/onboard/gallery/index.js b/pages/onboard/gallery/index.js
index ed290d34..332b53fb 100644
--- a/pages/onboard/gallery/index.js
+++ b/pages/onboard/gallery/index.js
@@ -26,4 +26,4 @@ export async function getStaticProps() {
},
revalidate: 120 // 2 minutes
}
-}
+}
\ No newline at end of file
diff --git a/pages/onboard/gallery/page.js b/pages/onboard/gallery/page.js
new file mode 100644
index 00000000..f755685b
--- /dev/null
+++ b/pages/onboard/gallery/page.js
@@ -0,0 +1,34 @@
+import { GalleryPage } from '../../../components/onboard/gallery-paginated'
+
+import { getAllOnboardProjects } from '../../api/onboard/p'
+import { getOnboardProject } from '../../api/onboard/p/[project]'
+import { onboardProjectCount } from '../../api/onboard/p/count'
+
+export default function Page({ projects, itemCount, currentPage }) {
+ return (
+ //
+ <>>
+ )
+}
+
+// export async function getStaticProps(context) {
+// const currentPage = parseInt(context.params.page)
+// const allProjects = await getAllOnboardProjects()
+// const data = allProjects.slice((currentPage - 1) * 10, currentPage * 10)
+// const projects = []
+// for (const project of data) {
+// projects.push(await getOnboardProject(project.name))
+// }
+// return {
+// props: {
+// projects,
+// itemCount: allProjects.length,
+// currentPage
+// },
+// revalidate: 120 // 2 minutes
+// }
+// }
\ No newline at end of file
diff --git a/public/arcade/3dPrinter.png b/public/arcade/3dPrinter.png
new file mode 100644
index 00000000..0c7725c3
Binary files /dev/null and b/public/arcade/3dPrinter.png differ
diff --git a/public/arcade/3dPrinting.png b/public/arcade/3dPrinting.png
new file mode 100644
index 00000000..481ba9e7
Binary files /dev/null and b/public/arcade/3dPrinting.png differ
diff --git a/public/arcade/Breadboard.png b/public/arcade/Breadboard.png
new file mode 100644
index 00000000..a5f4770d
Binary files /dev/null and b/public/arcade/Breadboard.png differ
diff --git a/public/arcade/Clock.png b/public/arcade/Clock.png
new file mode 100644
index 00000000..d19a3377
Binary files /dev/null and b/public/arcade/Clock.png differ
diff --git a/public/arcade/Flipper.png b/public/arcade/Flipper.png
new file mode 100644
index 00000000..49babdc0
Binary files /dev/null and b/public/arcade/Flipper.png differ
diff --git a/public/arcade/Framework.png b/public/arcade/Framework.png
new file mode 100644
index 00000000..fe465fa1
Binary files /dev/null and b/public/arcade/Framework.png differ
diff --git a/public/arcade/Logic.png b/public/arcade/Logic.png
new file mode 100644
index 00000000..50c418e2
Binary files /dev/null and b/public/arcade/Logic.png differ
diff --git a/public/arcade/Pi.png b/public/arcade/Pi.png
new file mode 100644
index 00000000..cfa20881
Binary files /dev/null and b/public/arcade/Pi.png differ
diff --git a/public/arcade/Prusa.png b/public/arcade/Prusa.png
new file mode 100644
index 00000000..7bef42d7
Binary files /dev/null and b/public/arcade/Prusa.png differ
diff --git a/public/arcade/Quest.png b/public/arcade/Quest.png
new file mode 100644
index 00000000..439cd6b4
Binary files /dev/null and b/public/arcade/Quest.png differ
diff --git a/public/arcade/Soldering.png b/public/arcade/Soldering.png
new file mode 100644
index 00000000..bb2621aa
Binary files /dev/null and b/public/arcade/Soldering.png differ
diff --git a/public/arcade/Sticker.png b/public/arcade/Sticker.png
new file mode 100644
index 00000000..6b9fb28f
Binary files /dev/null and b/public/arcade/Sticker.png differ
diff --git a/public/arcade/Untitled_Artwork 13 1.png b/public/arcade/Untitled_Artwork 13 1.png
new file mode 100644
index 00000000..b7910845
Binary files /dev/null and b/public/arcade/Untitled_Artwork 13 1.png differ
diff --git a/public/arcade/Yubikey.png b/public/arcade/Yubikey.png
new file mode 100644
index 00000000..0b82fab2
Binary files /dev/null and b/public/arcade/Yubikey.png differ
diff --git a/public/arcade/a1.png b/public/arcade/a1.png
new file mode 100644
index 00000000..025898d6
Binary files /dev/null and b/public/arcade/a1.png differ
diff --git a/public/arcade/a2.png b/public/arcade/a2.png
new file mode 100644
index 00000000..254260d4
Binary files /dev/null and b/public/arcade/a2.png differ
diff --git a/public/arcade/a3.png b/public/arcade/a3.png
new file mode 100644
index 00000000..d0c08304
Binary files /dev/null and b/public/arcade/a3.png differ
diff --git a/public/arcade/arcade_bg.png b/public/arcade/arcade_bg.png
new file mode 100644
index 00000000..e78f989b
Binary files /dev/null and b/public/arcade/arcade_bg.png differ
diff --git a/public/arcade/beige_bg.png b/public/arcade/beige_bg.png
new file mode 100644
index 00000000..bd605af7
Binary files /dev/null and b/public/arcade/beige_bg.png differ
diff --git a/public/arcade/blue_bg.svg b/public/arcade/blue_bg.svg
new file mode 100644
index 00000000..78efe3bf
--- /dev/null
+++ b/public/arcade/blue_bg.svg
@@ -0,0 +1,647 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/arcade/blue_bottom.svg b/public/arcade/blue_bottom.svg
new file mode 100644
index 00000000..4439728e
--- /dev/null
+++ b/public/arcade/blue_bottom.svg
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/arcade/blue_top.png b/public/arcade/blue_top.png
new file mode 100644
index 00000000..c85c2037
Binary files /dev/null and b/public/arcade/blue_top.png differ
diff --git a/public/arcade/blue_top.svg b/public/arcade/blue_top.svg
new file mode 100644
index 00000000..573e0ea1
--- /dev/null
+++ b/public/arcade/blue_top.svg
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/arcade/brown_bg.svg b/public/arcade/brown_bg.svg
new file mode 100644
index 00000000..2cdfd67a
--- /dev/null
+++ b/public/arcade/brown_bg.svg
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/arcade/idea.png b/public/arcade/idea.png
new file mode 100644
index 00000000..345f84f8
Binary files /dev/null and b/public/arcade/idea.png differ
diff --git a/public/arcade/o1.png b/public/arcade/o1.png
new file mode 100644
index 00000000..5b03c032
Binary files /dev/null and b/public/arcade/o1.png differ
diff --git a/public/arcade/o2.png b/public/arcade/o2.png
new file mode 100644
index 00000000..38fcbbb3
Binary files /dev/null and b/public/arcade/o2.png differ
diff --git a/public/arcade/o3.png b/public/arcade/o3.png
new file mode 100644
index 00000000..dce52ed8
Binary files /dev/null and b/public/arcade/o3.png differ
diff --git a/public/arcade/o4.png b/public/arcade/o4.png
new file mode 100644
index 00000000..ca033f82
Binary files /dev/null and b/public/arcade/o4.png differ
diff --git a/public/arcade/o5.png b/public/arcade/o5.png
new file mode 100644
index 00000000..908a4a33
Binary files /dev/null and b/public/arcade/o5.png differ
diff --git a/public/arcade/o6.png b/public/arcade/o6.png
new file mode 100644
index 00000000..36a6e4fb
Binary files /dev/null and b/public/arcade/o6.png differ
diff --git a/public/arcade/o7.png b/public/arcade/o7.png
new file mode 100644
index 00000000..407ce239
Binary files /dev/null and b/public/arcade/o7.png differ
diff --git a/public/arcade/data-loader.js b/public/arcade/power-hour/data-loader.js
similarity index 94%
rename from public/arcade/data-loader.js
rename to public/arcade/power-hour/data-loader.js
index a1e09b38..020addcf 100644
--- a/public/arcade/data-loader.js
+++ b/public/arcade/power-hour/data-loader.js
@@ -20,7 +20,7 @@ async function pullFromStorage() {
}
async function fetchData() {
- return await fetch('/api/arcade/inventory').then(d => d.json())
+ return await fetch('/api/arcade/hack-hour/inventory').then(d => d.json())
}
async function setToStorage(data) {
diff --git a/public/arcade/index.html b/public/arcade/power-hour/index.html
similarity index 100%
rename from public/arcade/index.html
rename to public/arcade/power-hour/index.html
diff --git a/public/arcade/style.css b/public/arcade/power-hour/style.css
similarity index 100%
rename from public/arcade/style.css
rename to public/arcade/power-hour/style.css
diff --git a/public/arcade/prizes.png b/public/arcade/prizes.png
new file mode 100644
index 00000000..e5c9d97c
Binary files /dev/null and b/public/arcade/prizes.png differ
diff --git a/public/arcade/r1.svg b/public/arcade/r1.svg
new file mode 100644
index 00000000..1d4f30ab
--- /dev/null
+++ b/public/arcade/r1.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/public/arcade/r2.svg b/public/arcade/r2.svg
new file mode 100644
index 00000000..20230b1a
--- /dev/null
+++ b/public/arcade/r2.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/public/arcade/r3.svg b/public/arcade/r3.svg
new file mode 100644
index 00000000..895e9e92
--- /dev/null
+++ b/public/arcade/r3.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/public/arcade/r4.png b/public/arcade/r4.png
new file mode 100644
index 00000000..0fa62f08
Binary files /dev/null and b/public/arcade/r4.png differ
diff --git a/public/arcade/r5.png b/public/arcade/r5.png
new file mode 100644
index 00000000..02d969e9
Binary files /dev/null and b/public/arcade/r5.png differ
diff --git a/public/arcade/r6.png b/public/arcade/r6.png
new file mode 100644
index 00000000..af5e052a
Binary files /dev/null and b/public/arcade/r6.png differ
diff --git a/public/arcade/r7.png b/public/arcade/r7.png
new file mode 100644
index 00000000..d9ebce95
Binary files /dev/null and b/public/arcade/r7.png differ
diff --git a/public/arcade/rp4.png b/public/arcade/rp4.png
new file mode 100644
index 00000000..ac46e26a
Binary files /dev/null and b/public/arcade/rp4.png differ
diff --git a/public/arcade/white_bg.svg b/public/arcade/white_bg.svg
new file mode 100644
index 00000000..912593f4
--- /dev/null
+++ b/public/arcade/white_bg.svg
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/arcade/yellow_bottom.svg b/public/arcade/yellow_bottom.svg
new file mode 100644
index 00000000..d3e6e386
--- /dev/null
+++ b/public/arcade/yellow_bottom.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/yarn.lock b/yarn.lock
index ff8e78dc..8fe3f021 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5321,6 +5321,11 @@ hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
dependencies:
react-is "^16.7.0"
+howler@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/howler/-/howler-2.2.4.tgz#bd3df4a4f68a0118a51e4bd84a2bfc2e93e6e5a1"
+ integrity sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w==
+
html-void-elements@^1.0.0:
version "1.0.5"
resolved "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz"