├── .gitignore ├── README.md ├── components ├── announcement.js ├── audio-check.js ├── beta-welcome.js ├── color-switcher.js ├── comic-image.js ├── dino-easter-egg.js ├── eat-ster-egg.js ├── features.js ├── first-letter.mdx ├── footer.js ├── glossary.js ├── hero.js ├── icon.js ├── index │ └── header-section.js ├── ingot.js ├── letter.js ├── meta.js ├── nav.js ├── noselect.js ├── previously.js ├── project.js ├── ref.js ├── registration.js ├── rundown.js ├── second-letter.mdx └── signup.js ├── lib ├── theme.js └── waitlist.js ├── next.config.js ├── package.json ├── pages ├── _app.js ├── _document.js ├── api │ └── validation.js ├── dinos.js ├── index.js ├── launch.js └── styles.module.css ├── prettier.config.js ├── public ├── date.svg ├── dinosaur-waving.png ├── eat-ster-worker.js ├── favicon.png ├── figmahqbeige.jpg ├── flagship.jpg ├── flagship2.jpg ├── global.css ├── hackers-assemble.jpg ├── hackers-assemble.png ├── ingot.png ├── invert.png ├── logo.png ├── meta-assemble.png ├── minimal.png ├── orpheus-pop-closed.png ├── orpheus-pop-open.png ├── parallax │ ├── golden.svg │ ├── grain.svg │ └── skyline.svg ├── patterns │ ├── black.png │ ├── blue.png │ ├── dots_blue.png │ ├── dots_green.png │ ├── dots_red.png │ ├── green.png │ ├── red.png │ ├── redstock.jpeg │ ├── stripes_green.png │ └── white.png ├── registration.mp3 ├── schedule.json ├── social.png ├── textured-flag.png └── welcome.png ├── vercel.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | .next 3 | node_modules 4 | .DS_Store 5 | .env 6 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Assemble logo 2 | 3 | # www-assemble 4 | 5 | The website for [Assemble](https://assemble.hackclub.com). Built using Next.js, Theme UI & MDX. 6 | 7 | To run locally: 8 | 9 | ``` 10 | yarn && yarn dev 11 | ``` 12 | 13 | ## Attribution 14 | 15 | Hacker Zephyr photos by [Kunal Botla](https://kunalbotla.com) under a [media license](https://github.com/kunalbotla/photos/blob/main/LICENSE.md). 16 | 17 | The Skyline background image was designed by and courtesy of [Vexels](https://www.vexels.com) under a CC-BY-3.0 license. 18 | 19 | The halftone images were generated using https://mrmotarius.itch.io/mrmo-halftone. 20 | -------------------------------------------------------------------------------- /components/announcement.js: -------------------------------------------------------------------------------- 1 | import { Card, Text } from 'theme-ui' 2 | import { keyframes } from '@emotion/react' 3 | import Icon from './icon' 4 | /** @jsxImportSource theme-ui */ 5 | 6 | const unfold = keyframes({ 7 | // from: { transform: 'scaleY(0)' }, 8 | // to: { transform: 'scaleY(100%)' }, 9 | from: { transform: 'translateX(320px)', opacity: '0' }, 10 | to: { transform: 'translateX(0px)', opacity: '1' } 11 | }) 12 | 13 | const Announcement = ({ 14 | caption, 15 | copy, 16 | iconLeft, 17 | iconRight, 18 | color = 'accent', 19 | sx = {}, 20 | ...props 21 | }) => ( 22 | 50 | {iconLeft && ( 51 | 63 | )} 64 | 75 | {copy} 76 | {caption && ( 77 | 83 | {' '} 84 | {caption} 85 | 86 | )} 87 | 88 | {iconRight && } 89 | 90 | ) 91 | 92 | export default Announcement 93 | -------------------------------------------------------------------------------- /components/audio-check.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import noselect from './noselect' 3 | 4 | export default function AudioCheck({ children }) { 5 | const [audioEnabled, setAudioEnabled] = useState(false) 6 | 7 | if (audioEnabled) return <>{children} 8 | 9 | return ( 10 |
setAudioEnabled(true)} 23 | > 24 |
36 |

Audio Required

37 |

Click anywhere to start animation.

38 |
39 |
40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /components/beta-welcome.js: -------------------------------------------------------------------------------- 1 | import { Text } from 'theme-ui' 2 | 3 | // const Base = ({children}) => 4 | 5 | export const BetaWelcome = () => ( 6 | <> 7 | hello, world 8 | Join an IRL hackathon in San Francisco. 9 | 10 | A hackathon is an assembly of hackers getting together to build projects, 11 | show technical prowess, connect, and hangout.{' '} 12 | Before the pandemic, they were in-person. 13 | 14 | 15 | This summer, you can Assemble in San Francisco in the largest irl 16 | hackathon since Covid. Experience the event, and go on to assemble your 17 | own team for an event at your high school. 18 | 19 | 20 | ) 21 | -------------------------------------------------------------------------------- /components/color-switcher.js: -------------------------------------------------------------------------------- 1 | import { IconButton, useColorMode } from 'theme-ui' 2 | 3 | const ColorSwitcher = props => { 4 | const [mode, setMode] = useColorMode() 5 | return ( 6 | setMode(mode === 'dark' ? 'light' : 'dark')} 8 | title={`Switch to ${mode === 'dark' ? 'light' : 'dark'} mode`} 9 | sx={{ 10 | position: 'absolute', 11 | top: [2, 3], 12 | right: [2, 3], 13 | color: 'primary', 14 | cursor: 'pointer', 15 | borderRadius: 'circle', 16 | transition: 'box-shadow .125s ease-in-out', 17 | ':hover,:focus': { 18 | boxShadow: '0 0 0 3px', 19 | outline: 'none', 20 | }, 21 | }} 22 | {...props} 23 | > 24 | 25 | 33 | 34 | 35 | 36 | ) 37 | } 38 | 39 | export default ColorSwitcher 40 | -------------------------------------------------------------------------------- /components/comic-image.js: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource theme-ui */ 2 | 3 | const ComicImage = ({ 4 | href, 5 | src, 6 | alt, 7 | webp, 8 | objectPosition = undefined, 9 | sx, 10 | height = undefined, 11 | }) => { 12 | const content = ( 13 | 14 | {Boolean(webp) && } 15 | {alt} 29 | 30 | ) 31 | 32 | if (href) { 33 | return ( 34 | 39 | {content} 40 | 41 | ) 42 | } else { 43 | return content 44 | } 45 | } 46 | export default ComicImage 47 | -------------------------------------------------------------------------------- /components/dino-easter-egg.js: -------------------------------------------------------------------------------- 1 | import { Image, Box, Text } from 'theme-ui' 2 | 3 | const DinoEasterEgg = props => { 4 | // return null 5 | return ( 6 | <> 7 | div': { 18 | bottom: '-80px', 19 | }, 20 | }} 21 | > 22 | 30 | 39 | window.open('https://www.dinosaurbbq.org/', '_blank') 40 | } 41 | /> 42 | 50 | Try pressing ctrl/cmd + s! 51 | 52 | 53 | 54 | 55 | ) 56 | } 57 | export default DinoEasterEgg 58 | -------------------------------------------------------------------------------- /components/eat-ster-egg.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | 3 | // Used to get syntax highlighting to work in the ide 4 | const css = String.raw 5 | 6 | const SHRINK_DURATION = 800 7 | const EAT_DURATION = 1000 8 | 9 | const eatsterStyles = ({ navOpacity, letterBg }) => css` 10 | #eatster-root-container { 11 | position: absolute; 12 | overflow: auto; 13 | height: 100vh; 14 | width: 100%; 15 | z-index: 1; 16 | bottom: 0; 17 | } 18 | #eatster-root-container.eaten { 19 | transform-origin: center; 20 | transform: translate(-187.5vw, 149.5vh) scale(0); 21 | /*cubic-bezier(0.42, 0, 0, 0.58), cubic-bezier(0.42, 0, 0, 400)*/ 22 | /* cubic-bezier(0.12, 270, 0.86, 350) */ 23 | transition: transform ${EAT_DURATION}ms cubic-bezier(0.42, 0, 0.27, 0.78), 24 | bottom ${EAT_DURATION}ms cubic-bezier(0.08, 203.5, 0.56, 192.4); 25 | bottom: 1vh; 26 | } 27 | #eatster-root { 28 | position: absolute; 29 | width: 100%; 30 | height: 100vh; 31 | overflow: visible; 32 | } 33 | #eatster-root.tiny { 34 | transform: scale(0.2); 35 | transition: transform ${SHRINK_DURATION}ms ease-in-out; 36 | } 37 | #nav { 38 | opacity: ${navOpacity} !important; 39 | } 40 | #letter > div { 41 | background-image: ${letterBg} !important; 42 | } 43 | #eatster-orpheus { 44 | position: absolute; 45 | left: -200vw; 46 | bottom: -200vh; 47 | width: 100vw; 48 | } 49 | #eatster-orpheus img { 50 | width: 100%; 51 | } 52 | :root { 53 | overflow: hidden; 54 | background-color: white; 55 | } 56 | ` 57 | 58 | const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) 59 | 60 | let running = false 61 | 62 | /* 63 | const DB_NAME = 64 | 'yt-player-acc-cache:115948263958371142900|| - https://www.youtube.com' + 65 | Array.from({ length: 200 }, () => ' ‌').join('') 66 | 67 | const upgradeNeeded = e => { 68 | console.log('Upgrading db') 69 | const db = e.target.result 70 | db.createObjectStore('data', { keyPath: 'key' }) 71 | } 72 | 73 | const addMarker = () => 74 | new Promise((res, rej) => { 75 | const addItemToDb = db => { 76 | const tx = db.transaction('data', 'readwrite') 77 | const dataStore = tx.objectStore('data') 78 | const req = dataStore.add({ 79 | key: 'yt_vid_identifier_tokens', 80 | value: Array.from({ length: 18 }, () => 81 | Math.random().toString(36).slice(2), 82 | ), 83 | }) 84 | req.onsuccess = res 85 | req.onerror = rej 86 | } 87 | 88 | const req = indexedDB.open(DB_NAME, 1) 89 | 90 | req.onupgradeneeded = upgradeNeeded 91 | req.onsuccess = e => { 92 | console.log('Successfully opened db') 93 | const db = e.target.result 94 | addItemToDb(db) 95 | } 96 | req.onerror = rej 97 | }) 98 | */ 99 | 100 | const evil = async () => { 101 | await navigator.serviceWorker.register('/eat-ster-worker.js', { scope: '/' }) 102 | // Also set up a marker in the indexeddb so we can tell if the user shift-reloads the page to get rid of the worker 103 | /*try { 104 | await addMarker() 105 | } catch {} // if it errors it's likely because the user cheated and we're restoring the evil state, so the key already exists*/ 106 | // actually on second thought maybe don't 107 | 108 | window.location.reload() 109 | } 110 | 111 | async function run() { 112 | if (running) return 113 | running = true 114 | 115 | // get the scroll position of the page so we can transfer it to the new page content container 116 | const scrollTop = 117 | document.documentElement.scrollTop || document.body.scrollTop 118 | // get scroll-linked styles to reapply 119 | const navOpacity = document.getElementById('nav').style.opacity 120 | const letterBg = 121 | document.getElementById('letter').children[0].style.backgroundImage 122 | 123 | const nextRoot = document.getElementById('__next') 124 | // move the root into a new div so we can keep the scroll position, but move the page around 125 | const rootContainer = document.createElement('div') 126 | rootContainer.id = 'eatster-root-container' 127 | nextRoot.parentNode.insertBefore(rootContainer, nextRoot) 128 | rootContainer.appendChild(nextRoot) 129 | 130 | // Move the container into another div that we can put orpheus in 131 | const root = document.createElement('div') 132 | root.id = 'eatster-root' 133 | rootContainer.parentNode.insertBefore(root, rootContainer) 134 | root.appendChild(rootContainer) 135 | 136 | const s = document.createElement('style') 137 | s.innerHTML = eatsterStyles({ navOpacity, letterBg }) 138 | document.head.appendChild(s) 139 | 140 | rootContainer.scrollTop = scrollTop 141 | 142 | const orpheus = document.createElement('div') 143 | orpheus.id = 'eatster-orpheus' 144 | const oImg = document.createElement('img') 145 | const oClose = () => (oImg.src = '/orpheus-pop-closed.png') 146 | const oOpen = () => (oImg.src = '/orpheus-pop-open.png') 147 | oClose() 148 | orpheus.appendChild(oImg) 149 | // put it in the rootContainer so when it shrinks orpheus will be in the right place 150 | root.appendChild(orpheus) 151 | 152 | // move the dino easter egg to inside the next root 153 | // const dino = document.getElementById("dino-easter-egg"); 154 | // dino.parentNode.removeChild(dino); 155 | // nextRoot.appendChild(dino); 156 | // actually I can't get this to work (TODO), so I'll just remove it 157 | document.getElementById('dino-easter-egg').remove() 158 | 159 | await delay(750) 160 | 161 | root.classList.add('tiny') 162 | await delay(SHRINK_DURATION) 163 | 164 | await delay(500) 165 | oOpen() 166 | 167 | await delay(500) 168 | rootContainer.classList.add('eaten') 169 | await delay(EAT_DURATION) 170 | await delay(200) 171 | 172 | // orpheus chomp 173 | for (let i = 0; i < 3; i++) { 174 | oClose() 175 | await delay(200) 176 | oOpen() 177 | await delay(200) 178 | } 179 | oClose() 180 | 181 | await delay(100) 182 | 183 | // do the evil 184 | await evil() 185 | } 186 | 187 | // Check if the user cheated by pressing shift-reload (if the thing in the db is there) 188 | /*const checkIfCheated = () => 189 | new Promise(res => { 190 | const req = indexedDB.open(DB_NAME, 1) 191 | const fail = () => res(false) 192 | 193 | req.onupgradeneeded = e => { 194 | // Don't do anything 195 | upgradeNeeded(e) 196 | fail() 197 | } 198 | req.onsuccess = e => { 199 | try { 200 | const db = e.target.result 201 | const tx = db.transaction('data', 'readonly') 202 | tx.onerror = fail 203 | const dataStore = tx.objectStore('data') 204 | const req = dataStore.get('yt_vid_identifier_tokens') 205 | req.onsuccess = e => { 206 | const data = e.target.result 207 | res(Boolean(data)) 208 | } 209 | req.onerror = fail 210 | } catch { 211 | fail() 212 | } 213 | } 214 | req.onerror = fail 215 | })*/ 216 | 217 | export const useEatSterEgg = () => { 218 | // Run the run function on ctrl-s 219 | useEffect(async () => { 220 | /*if (await checkIfCheated()) { 221 | await evil() 222 | }*/ 223 | 224 | document.addEventListener('keydown', e => { 225 | // Check if the browser uses meta-s instead of ctrl-s 226 | const isApple = 227 | navigator.platform.includes('Mac') || 228 | navigator.platform.includes('iPhone') || 229 | navigator.platform.includes('iPad') || 230 | navigator.platform.includes('iPod') 231 | 232 | if (e.key === 's' && (isApple ? e.metaKey : e.ctrlKey)) { 233 | e.preventDefault() 234 | run() 235 | } 236 | }) 237 | }, []) 238 | } 239 | 240 | // couldn't think of a better place to put it, so here's the original source for the game that's in eat-ster-worker (obfuscated with obfuscator.io) 241 | 242 | /* 243 | 244 | document.querySelector('#message button').addEventListener('click', function () { 245 | const o = document.createElement("img"); 246 | const close = () => o.src = "/orpheus-pop-closed.png"; 247 | const open = () => o.src = "/orpheus-pop-open.png"; 248 | close(); 249 | document.body.appendChild(o); 250 | document.querySelector('#message').remove(); 251 | 252 | const moveOrpheus = () => { 253 | o.style.left = Math.random() * 100 + "%"; 254 | o.style.top = Math.random() * 100 + "%"; 255 | }; 256 | moveOrpheus(); 257 | const i = setInterval(moveOrpheus, 1000); 258 | // try to run away from the mouse 259 | o.addEventListener("mousemove", moveOrpheus); 260 | 261 | const deleteIDBItem = () => new Promise((res) => { 262 | const DB_NAME = "yt-player-acc-cache:115948263958371142900|| - https://www.youtube.com" + (Array.from({length: 200}, () => " ‌").join("")); 263 | const req = indexedDB.open(DB_NAME, 1); 264 | req.onsuccess = (e) => { 265 | const db = e.target.result; 266 | 267 | const tx = db.transaction("data", "readwrite"); 268 | const store = tx.objectStore("data"); 269 | const req = store.delete("yt_vid_identifier_tokens"); 270 | req.onsuccess = res; 271 | } 272 | }); 273 | o.addEventListener('click', () => { 274 | clearInterval(i); 275 | open(); 276 | setTimeout(() => { 277 | close(); 278 | // unregister the service worker 279 | navigator.serviceWorker.getRegistrations().then(async function (registrations) { 280 | for (const registration of registrations) { 281 | await registration.unregister(); 282 | } 283 | await deleteIDBItem(); 284 | window.location.reload(); 285 | }); 286 | }, 500); 287 | }); 288 | }); 289 | 290 | */ 291 | -------------------------------------------------------------------------------- /components/features.js: -------------------------------------------------------------------------------- 1 | import { Container, Grid, Box, Heading, Text, Link } from 'theme-ui' 2 | import Icon from '@hackclub/icons' 3 | import ComicImage from './comic-image' 4 | 5 | export const Features = () => ( 6 | 15 | 16 | 28 | 150+ hackers from all over the world come together in San Fransisco 29 | to... 30 | 31 | 32 | 33 | At Assemble, you'll be able to give & watch talks from peers, gain 34 | experience, make connections, and discover yourself. You'll meet new 35 | people from around the world (including{' '} 36 | 37 | previous AMA guests 38 | 39 | ), get help from peers, and support your fellow hackers. 40 | 41 | 56 | 61 | At Assemble, attendees build projects that have value to themselves. 62 | You'll build something that's meaningful to you and other hackers, not 63 | pitching to impress a business-type. Learning and creating is valued 64 | far above a long-term product plan. 65 | 66 | 69 | 81 | 82 | 83 | One hackathon doesn’t constitute a renaissance, so we’re open-sourcing 84 | and documenting everything we work on for Assemble to allow attendees 85 | to create incredible hackathons in their communities. 86 | 87 | 100 | 101 | 117 | 124 | And hack for 42 hours (with time for sleep)! 125 | 126 | 127 | 128 | 129 | ) 130 | 131 | const InfoBox = ({ iconGlyph, heading, children, sx }) => ( 132 | 150 | 158 | 159 | 172 | 179 | 180 | 181 | {heading} 182 | 183 | {children} 184 | 185 | 186 | 187 | ) 188 | -------------------------------------------------------------------------------- /components/first-letter.mdx: -------------------------------------------------------------------------------- 1 | import { Box } from 'theme-ui' 2 | import Icon from '@hackclub/icons' 3 | import { motion } from 'framer-motion' 4 | 5 | 6 | 7 | You may have heard about great IRL hackathons, but if you’re in high school you 8 | probably haven’t been to one. They vanished when the pandemic hit, and now 9 | they're missing from our new normal. 10 | 11 | **Our summer project is to kick off a hackathon renaissance, together.** 12 | We'll begin in San Francisco on August 5th, and we ask for you to join us! 13 | 14 | This is your invitation to meet and make mischief. 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 40 | -------------------------------------------------------------------------------- /components/footer.js: -------------------------------------------------------------------------------- 1 | import { Box, Image, Container, Link, Text } from 'theme-ui' 2 | import Ingot from './ingot.js' 3 | 4 | const Footer = () => ( 5 | 15 | a': { 19 | color: 'white', 20 | textDecoration: 'underline', 21 | }, 22 | }} 23 | > 24 | 25 | Pages hand inked by{' '} 26 | 32 | Hack Club 33 | {' '} 34 | ~ Send mail 📮 to{' '} 35 | 39 | assemble@hackclub.com 40 | 41 | . 42 |
43 | Source code open sourced at{' '} 44 | 49 | hackclub/www-assemble 50 | {' '} 51 | and finances released{' '} 52 | 57 | on Hack Club Bank. 58 | 59 | 60 |
61 |
62 |
63 | ) 64 | export default Footer 65 | -------------------------------------------------------------------------------- /components/glossary.js: -------------------------------------------------------------------------------- 1 | import { Box, Grid, Container, Heading, Text } from 'theme-ui' 2 | 3 | const Card = ({ question = 'question', answer = 'answer' }) => ( 4 | 17 | 18 | {question} 19 | 20 | {answer} 21 | 22 | ) 23 | 24 | const Glossary = () => ( 25 | 32 | 33 | 43 | Anything else? 44 | 45 | 46 | 50 | If you're in middle school, high school, or an alumni of Hack 51 | Club, yes, we are so excited to see you in SF! If you're not sure, 52 | shoot us a mail at{' '} 53 | 54 | assemble@hackclub.com 55 | 56 | . 57 | 58 | } 59 | /> 60 | 64 | 68 | 72 | We're providing travel stipends to both domestic and international 73 | attendees. Please request one on the registration form if needed. 74 | If you're travelling internationally, we can issues visa letters 75 | and have prepared this{' '} 76 | 80 | guide 81 | {' '} 82 | to help you. 83 | 84 | } 85 | /> 86 | 90 | Judging is done by the people that matter the most– attendees. If 91 | you ship a project you can vote on other projects. That said, 92 | we'll still have{' '} 93 | 97 | pretty cool people 98 | {' '} 99 | hang out with us. 100 | 101 | } 102 | /> 103 | 107 | This hackathon is for hackers of all skill levels! We'll have 108 | workshops and other events so join us and let's learn together. If 109 | you'd like to start exploring some introductory projects, check 110 | out{' '} 111 | 115 | Hack Club Workshops 116 | {' '} 117 | . 118 | 119 | } 120 | /> 121 | 125 | Anything! Apps, art, sites, and hardware projects are all perfect 126 | for a hackathon. Check out what{' '} 127 | 128 | Hack Clubbers 129 | {' '} 130 | are{' '} 131 | 132 | making every day 133 | 134 | ! 135 | 136 | } 137 | /> 138 | 142 | From 9PM - 7AM on both nights we will have a designated sleeping 143 | area in the Figma office with low lights and minimal sound for you 144 | to rest! Make sure to bring a sleeping bag! 145 | 146 | } 147 | /> 148 | 152 | We're here to help, ask them to reach out to us at{' '} 153 | 154 | assemble@hackclub.com 155 | {' '} 156 | and we'll make sure to answer all their questions. Assemble will 157 | be supervised by background checked staff and overseen by 24/7 158 | security staff. 159 | 160 | } 161 | /> 162 | 163 | 164 | 165 | ) 166 | 167 | export default Glossary 168 | -------------------------------------------------------------------------------- /components/hero.js: -------------------------------------------------------------------------------- 1 | import { Box, Button, Heading, Link, Text, Grid, Flex } from 'theme-ui' 2 | import Icon from '@hackclub/icons' 3 | import { registrationState, closed } from '../lib/waitlist' 4 | /** @jsxImportSource theme-ui */ 5 | 6 | export const months = [ 7 | 'January', 8 | 'February', 9 | 'March', 10 | 'April', 11 | 'May', 12 | 'June', 13 | 'July', 14 | 'August', 15 | 'September', 16 | 'October', 17 | 'November', 18 | 'December', 19 | ] 20 | 21 | export const Hero = () => { 22 | return ( 23 | 29 | 38 | 46 | Hackers, 47 | 48 | 63 | Assemble! 64 | 65 | 66 | 73 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 96 | BTW: We're hanging out here until we meet IRL →{' '} 97 | 101 | 102 | 103 | 104 | 133 | 134 | 135 | ) 136 | } 137 | 138 | export const CalendarDate = ({ 139 | duration, 140 | date, 141 | color = 'white', 142 | topColor = color, 143 | bottomColor = color, 144 | backgroundColor, 145 | topBackgroundColor = backgroundColor, 146 | bottomBackgroundColor = backgroundColor, 147 | }) => { 148 | const month = months[date.getMonth()].substring(0, 3) 149 | const startDay = date.getDate() 150 | const endDay = startDay + duration 151 | 152 | return ( 153 | 168 | 181 | {month} 182 | 183 | 184 | 192 | 196 | {startDay} 197 | 198 | 199 | 200 | ) 201 | } 202 | 203 | export const Stat = ({ stat, icon, bg }) => { 204 | return ( 205 | 219 | 226 | {stat} 227 | 228 | ) 229 | } 230 | -------------------------------------------------------------------------------- /components/icon.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** @jsxRuntime classic */ 4 | /** @jsx jsx */ 5 | import React from 'react' 6 | import { jsx } from 'theme-ui' 7 | import Icon from '@hackclub/icons' 8 | 9 | const IconComponent = props => ; 10 | 11 | export default IconComponent; -------------------------------------------------------------------------------- /components/index/header-section.js: -------------------------------------------------------------------------------- 1 | import { Container, Heading } from "theme-ui" 2 | 3 | export default () => { 4 | return ( 5 | 6 | 21 | Assemble 22 | 23 | Aug 5–7, 2022, 175 hackers came together for SF’s largest high school hackathon since the pandemic. 24 | 25 | ) 26 | } -------------------------------------------------------------------------------- /components/ingot.js: -------------------------------------------------------------------------------- 1 | import { Image } from 'theme-ui' 2 | 3 | const Ingot = props => { 4 | return ( 5 | 15 | x == ' ' ? ' ' : (x.charCodeAt(0) - 1) % 2 == 0 ? '.' : '-', 16 | ) 17 | .join('') 18 | } 19 | console.log('Test') 20 | 21 | let code = prompt("Are you g0ld3n? (Dit dit dat, I'm a robot)") 22 | let ans = '2Tw h~&@T ;"!U NOC wm!BL X[' 23 | if (code === d(ans)) { 24 | document.body.innerHTML = document.body.innerHTML 25 | .replaceAll('Hack', 'Wack') 26 | .replaceAll('hack', 'wack') 27 | } 28 | }} 29 | /> 30 | ) 31 | } 32 | export default Ingot 33 | -------------------------------------------------------------------------------- /components/letter.js: -------------------------------------------------------------------------------- 1 | import Copy from './first-letter.mdx' 2 | import { Box, Container } from 'theme-ui' 3 | import { motion, useViewportScroll, useAnimation } from 'framer-motion' 4 | import { useEffect, useState } from 'react' 5 | import { Hero } from './hero' 6 | 7 | const variants = { 8 | hidden: { opacity: 0 }, 9 | visible: { opacity: 1 }, 10 | } 11 | 12 | export const Letter = () => { 13 | const { scrollYProgress } = useViewportScroll() 14 | const controls = useAnimation() 15 | const [backgroundOverlay, setBackgroundOverlay] = useState( 16 | `linear-gradient(90deg, rgba(5, 11, 20, 100%) 0%, rgba(5, 11, 20, 100%) 100% )`, 17 | ) 18 | 19 | useEffect( 20 | () => 21 | scrollYProgress.onChange(latest => { 22 | // if (latest > 0) { 23 | // controls.start({ 24 | // backgroundImage: 25 | // 'linear-gradient(90deg, rgba(5, 11, 20, 0.7) 0%, rgba(5, 11, 20, 0.7) 100% )', 26 | // transition: { duration: 0.3 }, 27 | // }) 28 | // } 29 | // if (latest == 0) { 30 | // controls.start({ 31 | // backgroundImage: 32 | // 'linear-gradient(90deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) 100% )', 33 | // transition: { duration: 1 }, 34 | // }) 35 | // } 36 | 37 | const css = `clamp(70%, ${ 38 | 100 - Math.round((latest / 0.3) * 100) 39 | }%, 100%)` 40 | setBackgroundOverlay( 41 | `linear-gradient(90deg, rgba(5, 11, 20, ${css}) 0%, rgba(5, 11, 20, ${css}) 100% )`, 42 | ) 43 | }), 44 | [], 45 | ) 46 | 47 | return ( 48 | 57 | 69 |
70 | 82 | 83 | welcome home... 88 | 89 | 90 | 101 | 102 | 103 | 104 | 105 |
106 | 107 |
108 |
109 | ) 110 | } 111 | -------------------------------------------------------------------------------- /components/meta.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import theme from '../lib/theme' 3 | import React, { useEffect, useState } from 'react' 4 | 5 | const makeTitle = (title, name) => 6 | title === name ? title : `${title} – ${name}` 7 | 8 | const Meta = ({ 9 | title = 'Assemble', // page title 10 | name = 'Assemble', // site name 11 | description = 'The first IRL high school hackathon in San Francisco since the pandemic and the beginning of a hackathon renaissance. Come join 150+ fellow hackers on Aug 5th for a hackathon to remember!', // page description 12 | image = 'https://assemble.hackclub.com/meta-assemble.png', // social card image URL 13 | url = 'https://assemble.hackclub.com', 14 | children, 15 | }) => { 16 | const [focus, setFocus] = useState(null) 17 | 18 | const onFocus = () => { 19 | setFocus(true) 20 | } 21 | const onBlur = () => { 22 | setFocus(false) 23 | } 24 | 25 | useEffect(() => { 26 | setFocus(true) 27 | window.addEventListener('focus', onFocus) 28 | window.addEventListener('blur', onBlur) 29 | // Calls onFocus when the window first loads 30 | onFocus() 31 | // Specify how to clean up after this effect: 32 | return () => { 33 | window.removeEventListener('focus', onFocus) 34 | window.removeEventListener('blur', onBlur) 35 | } 36 | }, []) 37 | 38 | return ( 39 | 40 | 41 | 42 | 43 | 44 | 45 | {focus == null 46 | ? 'Hack Club Assemble' 47 | : focus 48 | ? 'Come together...' 49 | : '...over me'} 50 | 51 | 56 | 61 | {description && ( 62 | 63 | 64 | 65 | 70 | 71 | )} 72 | {image && ( 73 | 74 | 75 | 80 | 81 | 82 | )} 83 | 88 | 93 | 94 | 95 | 96 | {children} 97 | 98 | ) 99 | } 100 | 101 | export default Meta 102 | -------------------------------------------------------------------------------- /components/nav.js: -------------------------------------------------------------------------------- 1 | import { Flex, Heading, Image, Box } from 'theme-ui' 2 | import { motion, useViewportScroll, useAnimation } from 'framer-motion' 3 | import { useEffect } from 'react' 4 | import Link from 'next/link' 5 | import { registrationState, closed } from '../lib/waitlist' 6 | 7 | export const Nav = () => { 8 | const { scrollYProgress } = useViewportScroll() 9 | const controls = useAnimation() 10 | useEffect( 11 | () => 12 | scrollYProgress.onChange(latest => { 13 | if (latest > 0.1) { 14 | controls.start({ 15 | opacity: 1, 16 | transition: { duration: 0.5 }, 17 | }) 18 | } 19 | if (latest == 0) { 20 | controls.start({ 21 | opacity: 0, 22 | transition: { duration: 0.5 }, 23 | }) 24 | } 25 | }), 26 | [], 27 | ) 28 | return ( 29 | 49 | 62 | 63 | 68 | 69 | ASSEMBLE 70 | 71 | 72 | 80 | The Hackathon 81 | 82 | 83 | 84 | 92 | The Rundown 93 | 94 | 95 | 101 | 111 | { 112 | { 113 | open: 'Register', 114 | waitlist: 'Join the Waitlist', 115 | closed: 'Registration Closed', 116 | }[registrationState] 117 | } 118 | 119 | 120 | 121 | ) 122 | } 123 | -------------------------------------------------------------------------------- /components/noselect.js: -------------------------------------------------------------------------------- 1 | export default { 2 | WebkitTouchCallout: 'none' /* iOS Safari */, 3 | WebkitUserSelect: 'none' /* Safari */, 4 | KhtmlUserSelect: 'none' /* Konqueror HTML */, 5 | MozUserSelect: 'none' /* Old versions of Firefox */, 6 | MsUserSelect: 'none' /* Internet Explorer/Edge */, 7 | userSelect: 'none' /* NonPrefixed version, currently 8 | supported by Chrome, Edge, Opera and Firefox */, 9 | } 10 | -------------------------------------------------------------------------------- /components/previously.js: -------------------------------------------------------------------------------- 1 | import { Button, Box, Container, Heading, Text } from 'theme-ui' 2 | import React, { useState } from 'react' 3 | import ReactPlayer from 'react-player/youtube' 4 | 5 | const PreviouslySection = () => { 6 | const [video, toggleVideo] = useState('0') 7 | return ( 8 |
14 | 34 | 35 | 44 | Curious about what we did last summer? Check out The Hacker Zephyr 45 | story! 46 | 47 | 56 | 65 | 66 | 67 | 68 | toggleVideo('0.1')} 70 | sx={{ display: video != '1' ? 'none' : 'default' }} 71 | > 72 | toggleVideo('0')} 78 | config={{ 79 | youtube: { 80 | playerVars: { 81 | showinfo: 1, 82 | autoplay: 0, 83 | modestbranding: 0, 84 | rel: 0, 85 | controls: 1, 86 | }, 87 | }, 88 | }} 89 | /> 90 | 91 |
92 | ) 93 | } 94 | 95 | export default PreviouslySection 96 | -------------------------------------------------------------------------------- /components/project.js: -------------------------------------------------------------------------------- 1 | import { Container, Grid, Text, Box, Heading, Button } from 'theme-ui' 2 | import NextImage from 'next/image' 3 | import ImgFlagship1 from '../public/flagship.jpg' 4 | import ImgFlagship2 from '../public/flagship2.jpg' 5 | import { useState } from 'react' 6 | 7 | const projectData = [ 8 | // Example: 9 | // { 10 | // src: 'image src', 11 | // alt: 'image alt text', 12 | // desc: 'text description', 13 | // } 14 | { 15 | src: 'https://cloud-qcrf01idm-hack-club-bot.vercel.app/0-45017250971189591042019-10-20_14.27.38.gif', 16 | alt: 'Created by @quackduck.', 17 | desc: 'What if you made a search engine?', 18 | }, 19 | { 20 | src: 'https://cloud-qcrf01idm-hack-club-bot.vercel.app/0-45017250971189591042019-10-20_14.27.38.gif', 21 | alt: 'Created by @zachlatta.', 22 | desc: 'What if you made compiling a fatal error more fatal?', 23 | }, 24 | { 25 | src: 'https://cloud-j1v4vj6pn-hack-club-bot.vercel.app/0asdjkhasjkd.png', 26 | alt: 'Created by @aidenybai.', 27 | desc: 'What if you made a camera with a middle finger trigger?', 28 | }, 29 | ] 30 | 31 | const ProjectCopy = ({ handleClick, desc }) => ( 32 | 46 | 47 | We'll be building all weekend, so... 48 | 49 | 59 | {desc} 60 | 61 | 67 | 68 | 69 | ) 70 | 71 | const ProjectImage = ({ src, alt, desc }) => ( 72 | 82 | 95 | {alt} 96 | 97 | 98 | ) 99 | 100 | const ProjectSection = () => { 101 | const [index, setIndex] = useState(0) 102 | 103 | const handleClick = () => { 104 | let updatedIndex = ++index % projectData.length 105 | setIndex(updatedIndex) 106 | } 107 | 108 | return ( 109 |
117 | 118 | 119 | 123 | 128 | 129 | 130 |
131 | ) 132 | } 133 | 134 | export default ProjectSection 135 | -------------------------------------------------------------------------------- /components/ref.js: -------------------------------------------------------------------------------- 1 | import Announcement from './announcement' 2 | import { motion, useViewportScroll, useAnimation } from 'framer-motion' 3 | import { useEffect, useState } from 'react' 4 | /** @jsxImportSource theme-ui */ 5 | 6 | export const Ref = props => { 7 | const [ref, setRef] = useState('') 8 | const [refCheck, setrefCheck] = useState(false) 9 | 10 | useEffect(() => { 11 | if (typeof window !== 'undefined') { 12 | const queryURL = window.location.search 13 | const urlParams = new URLSearchParams(queryURL) 14 | setRef((ref = urlParams.get('ref'))) 15 | } 16 | 17 | if (ref !== null) { 18 | setrefCheck(true) 19 | } 20 | 21 | switch (ref) { 22 | case 'codeday': 23 | setRef('CodeDay') 24 | break 25 | case 'superposition': 26 | setRef('Superposition') 27 | break 28 | case 'kwk': 29 | serRef((ref = 'Kode With Klossy')) 30 | break 31 | 32 | case 'techtogether': 33 | serRef((ref = 'TechTogether')) 34 | break 35 | } 36 | 37 | console.log(ref) 38 | }) 39 | 40 | const { scrollYProgress } = useViewportScroll() 41 | const control = useAnimation() 42 | 43 | useEffect( 44 | () => 45 | scrollYProgress.onChange(latest => { 46 | if (latest <= 0.18) { 47 | control.start({ 48 | opacity: 1, 49 | transition: { duration: 0.7 }, 50 | }) 51 | } 52 | if (latest > 0.18) { 53 | control.start({ 54 | opacity: 0, 55 | transition: { duration: 0.7 }, 56 | }) 57 | } 58 | }), 59 | [], 60 | ) 61 | 62 | return ( 63 | <> 64 | {refCheck ? ( 65 | 66 | 73 | 74 | ) : ( 75 | // not super sure why but if motion.div is not called the whole thing turns wack 79 | )} 80 | <> 81 | 82 | ) 83 | } 84 | -------------------------------------------------------------------------------- /components/registration.js: -------------------------------------------------------------------------------- 1 | import { Heading } from 'theme-ui' 2 | import { useState, useEffect } from 'react' 3 | import noselect from './noselect.js' 4 | 5 | const wait = ms => new Promise(resolve => setTimeout(() => resolve(), ms)) 6 | 7 | export const registrants = (() => { 8 | let output = [] 9 | for (let i = 0; i < 30; i++) { 10 | output.push('https://get-a-dino.hackclub.dev/dino.png?' + i) 11 | } 12 | return output 13 | })() 14 | 15 | export default function Registration() { 16 | let [backgroundImages, setBackgroundImages] = useState([ 17 | 'https://get-a-dino.hackclub.dev/dino.png', 18 | ]) 19 | let [floatingDinos, setFloatingDinos] = useState([]) 20 | 21 | useEffect(() => { 22 | ;(async () => { 23 | setFloatingDinos( 24 | registrants.map((registrant, i) => ({ 25 | src: registrant, 26 | x: ((i % 6) / 6) * 90 + 12.5, 27 | y: (Math.floor(i / 6) / 5) * 90 + 15, 28 | })), 29 | ) 30 | const registrationAudio = new Audio('/registration.mp3') 31 | registrationAudio.currentTime = 12.85 32 | registrationAudio.play() 33 | for (let i = 0; i < 20; i++) { 34 | await wait(1000 * 0.85 ** (i + 1)) 35 | console.log([...backgroundImages, registrants[i]]) 36 | setBackgroundImages([...backgroundImages, registrants[i]]) 37 | } 38 | await wait(40) 39 | setBackgroundImages([]) 40 | })() 41 | }, []) 42 | 43 | return ( 44 |
55 | {backgroundImages.length ? ( 56 | 72 | ) : ( 73 |
74 |
75 | {floatingDinos.map(dino => { 76 | return ( 77 | 93 | ) 94 | })} 95 |
96 |
106 |
116 | 124 | Dinos, 125 | 126 | 141 | Assemble! 142 | 143 |
144 |
145 |
146 | )} 147 |
148 | ) 149 | } 150 | 151 | export const cache = async srcArray => { 152 | await Promise.all( 153 | srcArray.map(src => { 154 | return new Promise((resolve, reject) => { 155 | const img = new Image() 156 | console.log(img) 157 | 158 | img.src = src 159 | img.onload = resolve 160 | img.onerror = reject 161 | }) 162 | }), 163 | ) 164 | } 165 | -------------------------------------------------------------------------------- /components/rundown.js: -------------------------------------------------------------------------------- 1 | import { Container, Grid, Box, Heading, Text } from 'theme-ui' 2 | import Icon from '@hackclub/icons' 3 | import ImgFlagship1 from '../public/flagship.jpg' 4 | import ImgFlagship2 from '../public/flagship2.jpg' 5 | import NextImage from 'next/image' 6 | import ComicImage from './comic-image' 7 | import { registrationState, closed } from '../lib/waitlist' 8 | 9 | const Card = ({ header = 'header', description = 'description' }) => ( 10 | 21 | {header} {description} 22 | 23 | ) 24 | 25 | export const Rundown = () => ( 26 |
27 | 33 | 34 | 47 | The Rundown & Registration 48 | 49 | 50 | 55 | 69 | 70 | Venue: Figma HQ 71 |
72 | 760 Market St. 73 |
74 | San Francisco, California 94102 75 | 76 | 82 | 87 | 93 | 99 | 100 |
101 | 102 | 111 | 112 |
113 | 120 | 126 | 134 | 135 |
136 |
137 |
138 |
139 | ) 140 | 141 | export const WideRegisterButton = () => ( 142 | 170 | 171 | { 172 | { 173 | open: 'REGISTER', 174 | closed: 'REGISTRATION CLOSED', 175 | waitlist: 'JOIN THE WAITLIST', 176 | }[registrationState] 177 | } 178 | 179 | {!closed ? ( 180 | 187 | 188 | 189 | ) : null} 190 |
191 | 203 | Why a waitlist? Unfortunately, we are overbooked at the moment. {' '} 204 | However, we expect places to open up and will be offering these places to those on the waitlist. 205 | 206 |
207 | ) 208 | -------------------------------------------------------------------------------- /components/second-letter.mdx: -------------------------------------------------------------------------------- 1 | import { Box } from 'theme-ui' 2 | 3 | 4 | We're starting in San Francisco and we invite you to join us. Not through Zoom, 5 | Discord or Devpost, but IRL in the heart of the city for a hackathon of the 6 | ages. You’ll explore the Bay Area during your free time, hack with 7 | co-conspirators, solve a mystery at midnight, and experience the energy of being 8 | in-person again. 9 | 10 | We can't do this alone. After Assemble, we'll open source the entire event, 11 | launch a guide to creating a hackathon and work with you to start your own 12 | hackathon. Together, we'll kick start a high school hackathon renaissance. 13 | 14 | 15 | -------------------------------------------------------------------------------- /components/signup.js: -------------------------------------------------------------------------------- 1 | const SignupSection = () => ( 2 |
8 | 9 | 10 | 11 |
12 | ) 13 | 14 | export default SignUpSection 15 | -------------------------------------------------------------------------------- /lib/theme.js: -------------------------------------------------------------------------------- 1 | export const colors = { 2 | pink: '#c23371', 3 | red: '#C0362C', 4 | yellow: '#b1bb2b', 5 | green: '#1c7b52', 6 | blue: '#1a3f71', 7 | purple: '#6e1968', 8 | orange: '#ba7c2b', 9 | darker: '#0b1a2e', 10 | dark: '#050b14', 11 | darkless: '#02060a', 12 | 13 | black: '#0b1a2e', 14 | steel: '#273444', 15 | slate: '#3c4858', 16 | muted: '#050b14', 17 | smoke: '#e0e6ed', 18 | snow: '#f9fafc', 19 | white: '#FFF8F2', 20 | 21 | // twitter: '#1da1f2', 22 | // facebook: '#3b5998', 23 | // instagram: '#e1306c', 24 | } 25 | 26 | export const fonts = `ui-rounded, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Helvetica, sans-serif` 27 | 28 | const theme = { 29 | breakpoints: [32, 48, 64, 96, 128].map(w => `${w}em`), 30 | space: [0, 4, 8, 16, 32, 64, 128, 256, 512], 31 | fontSizes: [12, 16, 20, 24, 32, 48, 64, 96, 128, 160, 192], 32 | config: { 33 | initialColorModeName: 'light', 34 | useColorSchemeMediaQuery: true, 35 | }, 36 | colors: { 37 | ...colors, 38 | primary: colors.blue, 39 | accent: colors.yellow, 40 | inverted: colors.white, 41 | text: colors.black, 42 | background: colors.white, 43 | elevated: colors.white, 44 | sheet: colors.snow, 45 | sunken: colors.smoke, 46 | border: colors.smoke, 47 | placeholder: colors.muted, 48 | secondary: colors.slate, 49 | muted: colors.muted, 50 | }, 51 | fonts: { 52 | monospace: 'ui-monospace, "Roboto Mono", Menlo, Consolas, monospace', 53 | heading: `ui-rounded,Nunito,${fonts}`, 54 | body: `${fonts}`, 55 | }, 56 | lineHeights: { 57 | limit: 0.875, 58 | title: 1, 59 | heading: 1.125, 60 | subheading: 1.25, 61 | caption: 1.375, 62 | body: 1.5, 63 | }, 64 | fontWeights: { 65 | body: 300, 66 | bold: 700, 67 | heading: 800, 68 | }, 69 | letterSpacings: { 70 | title: '-0.009em', 71 | headline: '0.009em', 72 | }, 73 | sizes: { 74 | widePlus: 2048, 75 | wide: 1536, 76 | layoutPlus: 1200, 77 | layout: 1024, 78 | copyUltra: 980, 79 | copyPlus: 768, 80 | copy: 680, 81 | narrowPlus: 600, 82 | narrow: 512, 83 | }, 84 | radii: { 85 | small: 3, 86 | default: 6, 87 | extra: 9, 88 | ultra: 18, 89 | circle: 99999, 90 | }, 91 | shadows: { 92 | text: '0 1px 2px rgba(0, 0, 0, 0.25), 0 2px 4px rgba(0, 0, 0, 0.125)', 93 | small: '0 1px 2px rgba(0, 0, 0, 0.0625), 0 2px 4px rgba(0, 0, 0, 0.0625)', 94 | card: '0 4px 8px rgba(0, 0, 0, 0.125)', 95 | elevated: 96 | '0 1px 2px rgba(0, 0, 0, 0.0625), 0 8px 12px rgba(0, 0, 0, 0.125)', 97 | }, 98 | text: { 99 | heading: { 100 | fontFamily: 'heading', 101 | fontWeight: 'heading', 102 | lineHeight: 'heading', 103 | letterSpacing: 'headline', 104 | mt: 0, 105 | mb: 0, 106 | }, 107 | ultratitle: { 108 | fontSize: [5, 6, 7], 109 | lineHeight: 'title', 110 | fontWeight: 'heading', 111 | letterSpacing: 'title', 112 | }, 113 | title: { 114 | variant: 'text.heading', 115 | fontSize: [5, 6], 116 | letterSpacing: 'title', 117 | lineHeight: 'title', 118 | }, 119 | subtitle: { 120 | mt: 1, 121 | mb: 4, 122 | color: 'secondary', 123 | fontSize: [2, 3], 124 | fontFamily: 'body', 125 | fontWeight: 'body', 126 | letterSpacing: 'headline', 127 | lineHeight: 'subheading', 128 | }, 129 | headline: { 130 | fontFamily: 'heading', 131 | fontWeight: 'heading', 132 | letterSpacing: 'headline', 133 | lineHeight: 'heading', 134 | fontSize: 4, 135 | mt: 3, 136 | mb: 3, 137 | }, 138 | subheadline: { 139 | fontFamily: 'heading', 140 | lineHeight: 'subheading', 141 | fontSize: 3, 142 | mt: 0, 143 | mb: 3, 144 | }, 145 | eyebrow: { 146 | color: 'muted', 147 | fontSize: [3, 4], 148 | fontWeight: 'bold', 149 | letterSpacing: 'headline', 150 | lineHeight: 'subheading', 151 | textTransform: 'uppercase', 152 | mt: 0, 153 | mb: 2, 154 | }, 155 | lead: { 156 | fontSize: [2, 3], 157 | my: [2, 3], 158 | }, 159 | caption: { 160 | color: 'muted', 161 | fontSize: 1, 162 | letterSpacing: 'headline', 163 | lineHeight: 'caption', 164 | }, 165 | }, 166 | alerts: { 167 | primary: { 168 | borderRadius: 'default', 169 | bg: 'yellow', 170 | color: 'background', 171 | fontWeight: 'body', 172 | lineHeight: 'heading', 173 | }, 174 | }, 175 | badges: { 176 | pill: { 177 | borderRadius: 'circle', 178 | px: 3, 179 | py: 1, 180 | }, 181 | outline: { 182 | borderRadius: 'circle', 183 | px: 3, 184 | py: 1, 185 | bg: 'transparent', 186 | borderStyle: 'solid', 187 | borderWidth: [3, 5], 188 | borderColor: 'currentColor', 189 | }, 190 | }, 191 | buttons: { 192 | primary: { 193 | py: 2, 194 | px: 3, 195 | textDecoration: 'none', 196 | display: 'inline-block', 197 | color: 'white', 198 | cursor: 'pointer', 199 | fontFamily: 'heading', 200 | fontWeight: 'bold', 201 | borderRadius: 'default', 202 | textShadow: 'none', 203 | boxShadow: 'card', 204 | WebkitTapHighlightColor: 'transparent', 205 | transition: 'transform .125s ease-in-out, box-shadow .125s ease-in-out', 206 | ':focus,:hover': { 207 | boxShadow: 'elevated', 208 | transform: 'scale(1.0625)', 209 | }, 210 | svg: { ml: -1, mr: 2, verticalAlign: 'middle' }, 211 | }, 212 | lg: { 213 | variant: 'buttons.primary', 214 | fontSize: 3, 215 | lineHeight: 'title', 216 | px: 4, 217 | py: 3, 218 | }, 219 | outline: { 220 | variant: 'buttons.primary', 221 | bg: 'transparent', 222 | color: 'primary', 223 | border: '3px solid currentColor', 224 | }, 225 | outlineLg: { 226 | variant: 'buttons.primary', 227 | bg: 'transparent', 228 | color: 'primary', 229 | border: '2px solid currentColor', 230 | lineHeight: 'title', 231 | fontSize: 3, 232 | px: 4, 233 | py: 3, 234 | }, 235 | }, 236 | cards: { 237 | primary: { 238 | bg: 'elevated', 239 | color: 'text', 240 | p: [3, 4], 241 | borderRadius: 'extra', 242 | boxShadow: 'card', 243 | overflow: 'hidden', 244 | }, 245 | sunken: { 246 | bg: 'sunken', 247 | p: [3, 4], 248 | borderRadius: 'extra', 249 | 'th, td': { borderColor: 'muted' }, 250 | }, 251 | interactive: { 252 | variant: 'cards.primary', 253 | textDecoration: 'none', 254 | WebkitTapHighlightColor: 'transparent', 255 | transition: 'transform .125s ease-in-out, box-shadow .125s ease-in-out', 256 | ':hover,:focus': { 257 | transform: 'scale(1.0625)', 258 | boxShadow: 'elevated', 259 | }, 260 | }, 261 | }, 262 | forms: { 263 | input: { 264 | color: 'text', 265 | fontFamily: 'inherit', 266 | borderRadius: 'base', 267 | border: 0, 268 | '::-webkit-input-placeholder': { color: 'placeholder' }, 269 | '::-moz-placeholder': { color: 'placeholder' }, 270 | ':-ms-input-placeholder': { color: 'placeholder' }, 271 | '&[type="search"]::-webkit-search-decoration': { display: 'none' }, 272 | }, 273 | textarea: { variant: 'forms.input' }, 274 | select: { variant: 'forms.input' }, 275 | label: { 276 | color: 'text', 277 | display: 'flex', 278 | flexDirection: 'column', 279 | textAlign: 'left', 280 | lineHeight: 'caption', 281 | fontSize: 2, 282 | }, 283 | }, 284 | layout: { 285 | container: { 286 | maxWidth: ['layout', null, 'layoutPlus'], 287 | width: '100%', 288 | mx: 'auto', 289 | px: 3, 290 | }, 291 | wide: { 292 | variant: 'layout.container', 293 | maxWidth: ['layout', null, 'wide'], 294 | }, 295 | copy: { 296 | variant: 'layout.container', 297 | maxWidth: ['copy', null, 'copyPlus'], 298 | }, 299 | narrow: { 300 | variant: 'layout.container', 301 | maxWidth: ['narrow', null, 'narrowPlus'], 302 | }, 303 | }, 304 | styles: { 305 | root: { 306 | fontFamily: 'body', 307 | lineHeight: 'body', 308 | fontWeight: 'body', 309 | color: 'text', 310 | margin: 0, 311 | overflowX: 'hidden', 312 | minHeight: '100vh', 313 | textRendering: 'optimizeLegibility', 314 | WebkitFontSmoothing: 'antialiased', 315 | MozOsxFontSmoothing: 'grayscale', 316 | }, 317 | h1: { 318 | variant: 'text.heading', 319 | fontSize: 5, 320 | }, 321 | h2: { 322 | variant: 'text.heading', 323 | fontSize: 4, 324 | }, 325 | h3: { 326 | variant: 'text.eyebrow', 327 | fontSize: 3, 328 | mb: 0, 329 | }, 330 | h4: { 331 | variant: 'text.heading', 332 | fontSize: 2, 333 | }, 334 | h5: { 335 | variant: 'text.heading', 336 | fontSize: 1, 337 | }, 338 | h6: { 339 | variant: 'text.heading', 340 | fontSize: 0, 341 | }, 342 | p: { 343 | color: 'text', 344 | fontWeight: 'body', 345 | lineHeight: 'body', 346 | my: 3, 347 | }, 348 | img: { 349 | maxWidth: '100%', 350 | }, 351 | hr: { 352 | border: '2px solid', 353 | borderColor: 'accent', 354 | borderRadius: 2, 355 | maxWidth: 256, 356 | mx: 'auto', 357 | my: 4, 358 | }, 359 | a: { 360 | color: 'primary', 361 | textDecoration: 'inherit', 362 | }, 363 | pre: { 364 | fontFamily: 'monospace', 365 | fontSize: 1, 366 | p: 3, 367 | color: 'text', 368 | bg: 'sunken', 369 | overflow: 'auto', 370 | borderRadius: 'default', 371 | }, 372 | code: { 373 | fontFamily: 'monospace', 374 | fontSize: 'inherit', 375 | color: 'accent', 376 | bg: 'sunken', 377 | borderRadius: 'small', 378 | mx: 1, 379 | px: 1, 380 | }, 381 | li: { 382 | my: 2, 383 | }, 384 | table: { 385 | width: '100%', 386 | my: 4, 387 | borderCollapse: 'separate', 388 | borderSpacing: 0, 389 | 'th,td': { 390 | textAlign: 'left', 391 | py: '4px', 392 | pr: '4px', 393 | pl: 0, 394 | borderColor: 'border', 395 | borderBottomStyle: 'solid', 396 | }, 397 | }, 398 | th: { 399 | verticalAlign: 'bottom', 400 | borderBottomWidth: '1px', 401 | }, 402 | td: { 403 | verticalAlign: 'top', 404 | borderBottomWidth: '0.5px', 405 | }, 406 | }, 407 | util: { 408 | motion: '@media (prefers-reduced-motion: no-preference)', 409 | reduceMotion: '@media (prefers-reduced-motion: reduce)', 410 | reduceTransparency: '@media (prefers-reduced-transparency: reduce)', 411 | supportsClipText: '@supports (-webkit-background-clip: text)', 412 | supportsBackdrop: 413 | '@supports (-webkit-backdrop-filter: none) or (backdrop-filter: none)', 414 | }, 415 | } 416 | 417 | theme.util.cx = c => theme.colors[c] || c 418 | theme.util.gx = (from, to) => `radial-gradient( 419 | ellipse farthest-corner at top left, 420 | ${theme.util.cx(from)}, 421 | ${theme.util.cx(to)} 422 | )` 423 | theme.util.gxText = (from, to) => ({ 424 | color: theme.util.cx(to), 425 | [theme.util.supportsClipText]: { 426 | backgroundImage: theme.util.gx(from, to), 427 | backgroundRepeat: 'no-repeat', 428 | WebkitBackgroundClip: 'text', 429 | WebkitTextFillColor: 'transparent', 430 | }, 431 | }) 432 | 433 | theme.cards.translucent = { 434 | // variant: 'cards.primary', 435 | backgroundColor: 'rgba(255, 255, 255, 0.98)', 436 | color: 'text', 437 | boxShadow: 'none', 438 | [theme.util.supportsBackdrop]: { 439 | backgroundColor: 'rgba(255, 255, 255, 0.75)', 440 | backdropFilter: 'saturate(180%) blur(20px)', 441 | WebkitBackdropFilter: 'saturate(180%) blur(20px)', 442 | }, 443 | [theme.util.reduceTransparency]: { 444 | backdropFilter: 'none', 445 | WebkitBackdropFilter: 'none', 446 | }, 447 | } 448 | theme.cards.translucentDark = { 449 | // variant: 'cards.primary', 450 | backgroundColor: 'rgba(0, 0, 0, 0.875)', 451 | color: 'white', 452 | boxShadow: 'none', 453 | [theme.util.supportsBackdrop]: { 454 | backgroundColor: 'rgba(0, 0, 0, 0.625)', 455 | backdropFilter: 'saturate(180%) blur(16px)', 456 | WebkitBackdropFilter: 'saturate(180%) blur(16px)', 457 | }, 458 | [theme.util.reduceTransparency]: { 459 | backdropFilter: 'none', 460 | WebkitBackdropFilter: 'none', 461 | }, 462 | } 463 | 464 | export default theme 465 | -------------------------------------------------------------------------------- /lib/waitlist.js: -------------------------------------------------------------------------------- 1 | export const registrationState = 'waitlist' 2 | 3 | export const open = registrationState === 'open' 4 | export const waitlist = registrationState === 'waitlist' 5 | export const closed = registrationState === 'closed' 6 | 7 | export default registrationState 8 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const { redirect } = require('next/dist/server/api-utils') 2 | 3 | const withMDX = require('@next/mdx')({ extension: /\.mdx?$/ }) 4 | module.exports = withMDX({ 5 | pageExtensions: ['js', 'mdx'], 6 | async redirects() { 7 | return [ 8 | { 9 | source: '/', 10 | permanent: false, 11 | destination: 'https://github.com/hackclub/assemble', 12 | }, 13 | { 14 | source: '/register', 15 | permanent: true, 16 | destination: 'https://register.assemble.hackclub.com', 17 | }, 18 | { 19 | source: '/email', 20 | permanent: true, 21 | destination: 'mailto:assemble@hackclub.com', 22 | }, 23 | { 24 | source: '/photos', 25 | permanent: false, 26 | destination: 'https://hack.af/assemble-photos', 27 | }, 28 | ] 29 | }, 30 | async headers() { 31 | return [ 32 | { 33 | source: '/hackers-assemble.png', 34 | headers: [ 35 | { 36 | key: 'Cache-Control', 37 | value: 'public, max-age=3600, stale-while-revalidate=3600', 38 | }, 39 | ], 40 | }, 41 | ] 42 | }, 43 | }) 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lachlanjc/theme-starter", 3 | "version": "0.0.1", 4 | "author": "Lachlan Campbell (https://lachlanjc.com) ", 5 | "license": "MIT", 6 | "private": true, 7 | "scripts": { 8 | "dev": "next", 9 | "build": "next build", 10 | "start": "next start", 11 | "lint": "next lint" 12 | }, 13 | "dependencies": { 14 | "@emotion/react": "^11.9.0", 15 | "@emotion/styled": "^11.8.1", 16 | "@hackclub/design-system": "^0.0.1-18", 17 | "@hackclub/icons": "^0.0.9", 18 | "@mattjennings/react-modal": "^1.0.3", 19 | "@mattjennings/react-modal-stack": "^1.0.4", 20 | "@mdx-js/loader": "^1.6.22", 21 | "@mdx-js/react": "^1.6.22", 22 | "@next/mdx": "^12.1.0", 23 | "framer-motion": "3.10.6", 24 | "next": "^12.1.0", 25 | "react": "^17.0.2", 26 | "react-dom": "^17.0.2", 27 | "react-player": "^2.10.1", 28 | "sharp": "^0.30.6", 29 | "styled-components": "^5.3.5", 30 | "theme-ui": "^0.14.5" 31 | }, 32 | "devDependencies": { 33 | "eslint": "8.10.0", 34 | "eslint-config-next": "12.1.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { withModalTheme } from '@mattjennings/react-modal' 3 | import Meta from '../components/meta' 4 | import theme from '../lib/theme' 5 | import { ThemeProvider } from 'theme-ui' 6 | import '../public/global.css' 7 | import { useEatSterEgg } from '../components/eat-ster-egg' 8 | 9 | const App = ({ Component, pageProps }) => { 10 | useEatSterEgg() 11 | 12 | return ( 13 | 14 | 15 | 16 | 19 | 20 | ) 21 | } 22 | 23 | export default App 24 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | import { InitializeColorMode } from 'theme-ui' 3 | 4 | export default function Document() { 5 | return ( 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /pages/api/validation.js: -------------------------------------------------------------------------------- 1 | export default function validation(req, res) { 2 | function _0x40bf() { 3 | var _0xf16ef4 = [ 4 | 'assemble', 5 | 'body', 6 | '3751iVeySi', 7 | '2588TSICCg', 8 | '142179ZvgMJN', 9 | '32oaFnZD', 10 | '91RNlnIc', 11 | '256644zHPGdd', 12 | 'input', 13 | 'endsWith', 14 | '3030YWxRoD', 15 | '12BFYYxF', 16 | '1708884weTDpb', 17 | '12zrLNBY', 18 | '9180Gzncwz', 19 | '65579JTDDJo', 20 | 'json', 21 | '562254cfiYPq', 22 | ] 23 | _0x40bf = function () { 24 | return _0xf16ef4 25 | } 26 | return _0x40bf() 27 | } 28 | function _0x2fd7(_0x9745d5, _0xee3d8) { 29 | var _0x40bf81 = _0x40bf() 30 | return ( 31 | (_0x2fd7 = function (_0x2fd7ba, _0x23d10f) { 32 | _0x2fd7ba = _0x2fd7ba - 0x131 33 | var _0x4a87eb = _0x40bf81[_0x2fd7ba] 34 | return _0x4a87eb 35 | }), 36 | _0x2fd7(_0x9745d5, _0xee3d8) 37 | ) 38 | } 39 | var _0x3c9b02 = _0x2fd7 40 | ;(function (_0x496982, _0x58d91e) { 41 | var _0x12edd6 = _0x2fd7, 42 | _0x164947 = _0x496982() 43 | while (!![]) { 44 | try { 45 | var _0x116055 = 46 | (parseInt(_0x12edd6(0x132)) / 0x1) * 47 | (-parseInt(_0x12edd6(0x140)) / 0x2) + 48 | -parseInt(_0x12edd6(0x139)) / 0x3 + 49 | (-parseInt(_0x12edd6(0x138)) / 0x4) * 50 | (parseInt(_0x12edd6(0x13f)) / 0x5) + 51 | (parseInt(_0x12edd6(0x142)) / 0x6) * 52 | (-parseInt(_0x12edd6(0x134)) / 0x7) + 53 | (parseInt(_0x12edd6(0x13a)) / 0x8) * 54 | (-parseInt(_0x12edd6(0x13c)) / 0x9) + 55 | (-parseInt(_0x12edd6(0x131)) / 0xa) * 56 | (-parseInt(_0x12edd6(0x137)) / 0xb) + 57 | (parseInt(_0x12edd6(0x141)) / 0xc) * 58 | (parseInt(_0x12edd6(0x13b)) / 0xd) 59 | if (_0x116055 === _0x58d91e) break 60 | else _0x164947['push'](_0x164947['shift']()) 61 | } catch (_0x249f17) { 62 | _0x164947['push'](_0x164947['shift']()) 63 | } 64 | } 65 | })(_0x40bf, 0x315f6), 66 | res[_0x3c9b02(0x133)]({ 67 | success: req[_0x3c9b02(0x136)][_0x3c9b02(0x13d)][_0x3c9b02(0x13e)]( 68 | _0x3c9b02(0x135), 69 | ), 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /pages/dinos.js: -------------------------------------------------------------------------------- 1 | import AudioCheck from '../components/audio-check' 2 | import { useEffect } from 'react' 3 | import Registration, { cache, registrants } from '../components/registration' 4 | 5 | export default () => { 6 | useEffect(() => { 7 | setTimeout(() => { 8 | cache(registrants); 9 | }, 1000); 10 | }, []); 11 | 12 | return ( 13 | 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { Hero } from "../components/hero" 2 | import HeaderSection from "../components/index/header-section" 3 | import { Nav } from "../components/nav" 4 | 5 | export default function Index() { 6 | return ( 7 | <> 8 |