├── .github
└── workflows
│ └── pages.yml
├── .gitignore
├── .replit
├── .vscode
└── settings.json
├── README.md
├── components
├── Auth.js
├── Inputs
│ ├── Select.js
│ ├── Select.module.css
│ ├── Text.js
│ ├── Text.module.css
│ ├── Title.js
│ └── Title.module.css
├── Modal.js
└── Sidebar.js
├── docs
├── 404.html
├── _next
│ └── static
│ │ ├── 63naq6OZNUwEmbBG38Fkb
│ │ ├── _buildManifest.js
│ │ └── _ssgManifest.js
│ │ ├── chunks
│ │ ├── 7bad3855-0bb9e0fb6954a08c.js
│ │ ├── framework-9b5d6ec4444c80fa.js
│ │ ├── main-3123a443c688934f.js
│ │ ├── pages
│ │ │ ├── _app-fd9d1b23c3421e47.js
│ │ │ ├── _error-7397496ca01950b1.js
│ │ │ └── index-c7c2ac66f3a267fe.js
│ │ ├── polyfills-c67a75d1b6f99dc8.js
│ │ └── webpack-7ee66019f7f6d30f.js
│ │ └── css
│ │ ├── 5599a730af22f4f4.css
│ │ └── bd23b02b3e2210dc.css
├── favicon.ico
├── index.html
├── orange.png
├── orange.svg
├── vercel.svg
└── white.svg
├── lib
├── caSchools.js
├── questions.js
└── schoolsList.js
├── next.config.js
├── package.json
├── pages
├── _app.js
├── api
│ ├── discord
│ │ └── link.js
│ ├── donor.js
│ ├── email
│ │ └── [city]
│ │ │ └── [email].js
│ ├── hello.js
│ ├── posts.js
│ ├── register.js
│ ├── scanner.js
│ ├── scrapbookdelete.js
│ ├── scrapbookpost.js
│ ├── shippost.js
│ ├── ships.js
│ ├── v2.js
│ └── webhook.js
├── files
│ └── [file].js
├── index.js
├── register-old.js
├── register.js
├── registration
│ ├── discord-success.js
│ └── success.js
├── schedule.js
├── scrapbook.js
└── ships.js
├── public
├── favicon-old-2.png
├── favicon-old.png
├── favicon-vercel.ico
├── favicon.png
├── flyer2.pdf
├── items
│ └── PressRelease-03-13-2023.pdf
├── logo-full-light.png
├── map.png
├── orange.png
├── orange.svg
├── press
│ └── 1.pdf
├── prospectus.pdf
├── social.png
├── sponsor-assets
│ ├── bank.svg
│ └── vercel.svg
├── thumbnail.png
├── vercel.svg
└── white.svg
├── replit.nix
├── replit.sh
├── styles
├── Home.module.css
├── Register.module.css
└── globals.css
├── utils
├── cssUtils.js
├── useMedia.js
└── useProtection.js
└── yarn.lock
/.github/workflows/pages.yml:
--------------------------------------------------------------------------------
1 | # Sample workflow for building and deploying a Next.js site to GitHub Pages
2 | #
3 | # To get started with Next.js see: https://nextjs.org/docs/getting-started
4 | #
5 | name: Deploy Next.js site to Pages
6 |
7 | on:
8 | # Runs on pushes targeting the default branch
9 | push:
10 | branches: ["main"]
11 |
12 | # Allows you to run this workflow manually from the Actions tab
13 | workflow_dispatch:
14 |
15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
16 | permissions:
17 | contents: read
18 | pages: write
19 | id-token: write
20 |
21 | # Allow one concurrent deployment
22 | concurrency:
23 | group: "pages"
24 | cancel-in-progress: true
25 |
26 | jobs:
27 | # Build job
28 | build:
29 | runs-on: ubuntu-latest
30 | steps:
31 | - name: Checkout
32 | uses: actions/checkout@v3
33 | - name: Detect package manager
34 | id: detect-package-manager
35 | run: |
36 | if [ -f "${{ github.workspace }}/yarn.lock" ]; then
37 | echo "::set-output name=manager::yarn"
38 | echo "::set-output name=command::install"
39 | echo "::set-output name=runner::yarn"
40 | exit 0
41 | elif [ -f "${{ github.workspace }}/package.json" ]; then
42 | echo "::set-output name=manager::npm"
43 | echo "::set-output name=command::ci"
44 | echo "::set-output name=runner::npx --no-install"
45 | exit 0
46 | else
47 | echo "Unable to determine packager manager"
48 | exit 1
49 | fi
50 | - name: Setup Node
51 | uses: actions/setup-node@v3
52 | with:
53 | node-version: "16"
54 | cache: ${{ steps.detect-package-manager.outputs.manager }}
55 | - name: Setup Pages
56 | uses: actions/configure-pages@v2
57 | with:
58 | # Automatically inject basePath in your Next.js configuration file and disable
59 | # server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized).
60 | #
61 | # You may remove this line if you want to manage the configuration yourself.
62 | static_site_generator: next
63 | - name: Restore cache
64 | uses: actions/cache@v3
65 | with:
66 | path: |
67 | .next/cache
68 | # Generate a new cache whenever packages or source files change.
69 | key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
70 | # If source files changed but packages didn't, rebuild from a prior cache.
71 | restore-keys: |
72 | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
73 | - name: Install dependencies
74 | run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
75 | - name: Build with Next.js
76 | run: ${{ steps.detect-package-manager.outputs.runner }} next build
77 | - name: Static HTML export with Next.js
78 | run: ${{ steps.detect-package-manager.outputs.runner }} next export
79 | - name: Upload artifact
80 | uses: actions/upload-pages-artifact@v1
81 | with:
82 | path: ./out
83 |
84 | # Deployment job
85 | deploy:
86 | environment:
87 | name: github-pages
88 | url: ${{ steps.deployment.outputs.page_url }}
89 | runs-on: ubuntu-latest
90 | needs: build
91 | steps:
92 | - name: Deploy to GitHub Pages
93 | id: deployment
94 | uses: actions/deploy-pages@v1
95 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 | .env
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
--------------------------------------------------------------------------------
/.replit:
--------------------------------------------------------------------------------
1 |
2 | hidden = [".config", "package-lock.json"]
3 | run = "./replit.sh"
4 |
5 | [[hints]]
6 | regex = "Error \\[ERR_REQUIRE_ESM\\]"
7 | message = "We see that you are using require(...) inside your code. We currently do not support this syntax. Please use 'import' instead when using external modules. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)"
8 |
9 | [nix]
10 | channel = "stable-22_11"
11 |
12 | [env]
13 | XDG_CONFIG_HOME = "/home/runner/.config"
14 | PATH = "/home/runner/$REPL_SLUG/.config/npm/node_global/bin:/home/runner/$REPL_SLUG/node_modules/.bin"
15 | npm_config_prefix = "/home/runner/$REPL_SLUG/.config/npm/node_global"
16 |
17 | [gitHubImport]
18 | requiredFiles = [".replit", "replit.nix", ".config", "package.json", "package-lock.json"]
19 |
20 | [packager]
21 | language = "nodejs"
22 |
23 | [packager.features]
24 | packageSearch = true
25 | guessImports = true
26 | enabledForHosting = false
27 |
28 | [unitTest]
29 | language = "nodejs"
30 |
31 | [debugger]
32 | support = true
33 |
34 | [debugger.interactive]
35 | transport = "localhost:0"
36 | startCommand = [ "dap-node" ]
37 |
38 | [debugger.interactive.initializeMessage]
39 | command = "initialize"
40 | type = "request"
41 |
42 | [debugger.interactive.initializeMessage.arguments]
43 | clientID = "replit"
44 | clientName = "replit.com"
45 | columnsStartAt1 = true
46 | linesStartAt1 = true
47 | locale = "en-us"
48 | pathFormat = "path"
49 | supportsInvalidatedEvent = true
50 | supportsProgressReporting = true
51 | supportsRunInTerminalRequest = true
52 | supportsVariablePaging = true
53 | supportsVariableType = true
54 |
55 | [debugger.interactive.launchMessage]
56 | command = "launch"
57 | type = "request"
58 |
59 | [debugger.interactive.launchMessage.arguments]
60 | args = []
61 | console = "externalTerminal"
62 | cwd = "."
63 | environment = []
64 | pauseForSourceMap = false
65 | program = "./index.js"
66 | request = "launch"
67 | sourceMaps = true
68 | stopOnEntry = false
69 | type = "pwa-node"
70 |
71 | [languages]
72 |
73 | [languages.javascript]
74 | pattern = "**/{*.js,*.jsx,*.ts,*.tsx}"
75 |
76 | [languages.javascript.languageServer]
77 | start = "typescript-language-server --stdio"
78 |
79 | [deployment]
80 | run = ["sh", "-c", "node index.js"]
81 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "workbench.colorCustomizations": {
3 | "activityBar.background": "#302A42",
4 | "titleBar.activeBackground": "#433B5C",
5 | "titleBar.activeForeground": "#FCFCFD"
6 | }
7 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hack OC Website
2 |
3 | Website for Orange County's first high school hackathon!
4 |
5 | Built with next.js and deployed on Vercel
6 |
--------------------------------------------------------------------------------
/components/Auth.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | export function useDeviceId () {
4 | const [deviceId, setDeviceId] = useState(null);
5 |
6 | useEffect(() => {
7 | if (localStorage.getItem('deviceId')) {
8 | setDeviceId(localStorage.getItem('deviceId'));
9 | } else {
10 | const newDeviceId = Math.random().toString(36).substr(2, 9);
11 | localStorage.setItem('deviceId', newDeviceId);
12 | setDeviceId(newDeviceId);
13 | }
14 | }, []);
15 |
16 | const loading = deviceId === null;
17 |
18 | const waitForDeviceId = () => new Promise((resolve, reject) => {
19 | if (deviceId) resolve(deviceId);
20 | else {
21 | const interval = setInterval(() => {
22 | if (deviceId) {
23 | clearInterval(interval);
24 | resolve(deviceId);
25 | }
26 | }, 100);
27 | }
28 | });
29 |
30 | return [deviceId, waitForDeviceId, loading];
31 | }
--------------------------------------------------------------------------------
/components/Inputs/Select.js:
--------------------------------------------------------------------------------
1 | import { useState, uesEffet, useEffect, useRef } from 'react';
2 | import styles from './Select.module.css';
3 |
4 | function defaultValidate (chips) {
5 | return chips?.length;
6 | }
7 |
8 | function isVisible (ele, container) {
9 | if (!container) container = ele.parentElement;
10 |
11 | const eleTop = ele.offsetTop;
12 | const eleBottom = eleTop + ele.clientHeight;
13 |
14 | const containerTop = container.scrollTop;
15 | const containerBottom = containerTop + container.clientHeight;
16 |
17 | return (
18 | (eleTop >= containerTop && eleBottom <= containerBottom) ||
19 | (eleTop < containerTop && containerTop < eleBottom) ||
20 | (eleTop < containerBottom && containerBottom < eleBottom)
21 | );
22 | }
23 |
24 | function scrollParentToChild (child, parent) {
25 | if (!parent) parent = child.parentElement;
26 | let parentRect = parent.getBoundingClientRect();
27 | let parentViewableArea = {
28 | height: parent.clientHeight,
29 | width: parent.clientWidth
30 | };
31 |
32 | let childRect = child.getBoundingClientRect();
33 | let isViewable = (childRect.top >= parentRect.top) && (childRect.bottom <= parentRect.top + parentViewableArea.height);
34 |
35 | if (!isViewable) {
36 | const scrollTop = childRect.top - parentRect.top;
37 | const scrollBot = childRect.bottom - parentRect.bottom;
38 | if (Math.abs(scrollTop) < Math.abs(scrollBot)) parent.scrollTop += scrollTop;
39 | else parent.scrollTop += scrollBot;
40 | }
41 | }
42 |
43 | const modify = str => str.toLowerCase().split('').filter(a => `abcdefghijklmnopqrstuvwxyz1234567890`.includes(a)).join('') ?? '';
44 |
45 | function looseMatch (str1, str2) {
46 | return modify(str1) == modify(str2);
47 | }
48 |
49 | export function Chip ({ chipData: chip, forceUpdate, chips, setChips, multiSelect, presetChips, localData, isCustom, isSelected, resetSelected, selectThisChip, setActiveColor, setLocalData, startEdits }) {
50 | const selfRef = useRef(null);
51 |
52 | useEffect(() => {
53 | if (!isVisible(selfRef.current)) {
54 | scrollParentToChild(selfRef.current);
55 | }
56 | window.looseMatch = looseMatch;
57 | }, [isSelected]);
58 |
59 | return (
60 |
e.preventDefault()} onMouseUp={e => {
61 | if (isCustom) {
62 | e.preventDefault();
63 | resetSelected(0);
64 | let theseChips = JSON.parse(JSON.stringify(chips));
65 | theseChips.push({
66 | name: localData,
67 | color: chip.color,
68 | id: Math.floor(Math.random() * 10000) + '-' + Date.now()
69 | });
70 | setChips(theseChips);
71 | setActiveColor(Math.random() * 6 | 0);
72 | setLocalData('');
73 | forceUpdate();
74 | startEdits();
75 | } else {
76 | e.preventDefault();
77 | resetSelected(0);
78 | let index = 0;
79 | presetChips.forEach((c, i) => (c.name == chip.name ? index = i : 0));
80 | let thesePresetChips = JSON.parse(JSON.stringify(presetChips));
81 | let theseChips = chips;
82 | let thisChip = thesePresetChips.splice(index, 1)[0];
83 | if (multiSelect) theseChips = theseChips.concat([{
84 | name: thisChip.name,
85 | color: thisChip.color,
86 | id: Math.floor(Math.random() * 10000) + '-' + Date.now()
87 | }]);
88 | else theseChips = [
89 | {
90 | name: thisChip.name,
91 | color: thisChip.color,
92 | id: Math.floor(Math.random() * 10000) + '-' + Date.now()
93 | }
94 | ];
95 | setChips(theseChips);
96 | forceUpdate();
97 | startEdits();
98 | setLocalData('');
99 | }
100 | }}>
101 | {isCustom && 'Add '} {chip.name} ×
102 |
103 | );
104 | }
105 |
106 | export default function Select (props) {
107 | const { setValue, multi, options = [], special, validate: _validate, name, description, help, placeholder, data, setData, initialData, wrapperClass, width, margin, type, id: _id, required, dontDisplayAll } = props;
108 |
109 | const displayAll = !dontDisplayAll;
110 |
111 | const [selected, setSelected] = useState(0);
112 |
113 | const id = _id ?? name?.toLowerCase()?.split(' ')?.join('-')?.split('')?.filter(a => `abcdefghijklmnopqrstuvwxyz1234567890-_`.includes(a))?.join('') ?? 'error';
114 | const validate = data => (_validate ?? defaultValidate)() && (required ? data?.length : true);
115 |
116 | const [localData, setLocalData] = useState(initialData ?? '');
117 | const [valid, setValid] = useState(false);
118 | const [partiallyValid, setPartiallyValid] = useState(false);
119 |
120 | const inputRef = useRef(null);
121 | const dropdownRef = useRef(null);
122 |
123 | const custom = options.filter(opt => opt?.custom).length > 0;
124 | const multiSelect = multi;
125 |
126 | const presetChips = options.filter(opt => !opt?.custom).map((opt, i) => ({
127 | name: opt,
128 | color: i % 7,
129 | }));
130 |
131 | const [displayedPresetChips, setDisplayedPresetChips] = useState(presetChips);
132 |
133 | const [chips, setChips] = useState([
134 | ]);
135 |
136 | useEffect(() => {
137 | setValue(name, chips.map(c => c.name));
138 | }, [chips]);
139 |
140 | const [activeColor, setActiveColor] = useState(0);
141 |
142 | const [counter, setCounter] = useState(Math.random() * 6 | 0);
143 |
144 | useEffect(() => {
145 | let excluded = presetChips.filter(presetChip => !chips.map(chip => chip.name).includes(presetChip.name));
146 | if (localData?.length) {
147 | setDisplayedPresetChips(
148 | excluded.filter(chip => (
149 | modify(chip.name).includes(modify(localData))
150 | || !modify(localData).split('').filter((a, i) => chip.name.toLowerCase().split(' ')[i]?.[0] !== a).length
151 | ))
152 | );
153 | } else {
154 | setDisplayedPresetChips(excluded);
155 | }
156 | }, [chips, localData, counter]);
157 |
158 | function forceUpdate () {
159 | setCounter(counter + 1);
160 | }
161 |
162 | const [editing, setEditing] = useState(false);
163 |
164 | function startEdits () {
165 | setEditing(true);
166 | }
167 |
168 | function finishEdits () {
169 | setEditing(false);
170 | }
171 |
172 | function resetSelected () {
173 | setSelected(0);
174 | }
175 |
176 | useEffect(() => {
177 | let validationStatus = validate(chips);
178 | setValid(!editing && validationStatus);
179 | setPartiallyValid(editing && validationStatus);
180 | }, [chips, editing, counter]);
181 |
182 | const displayedChips = [
183 | ...(displayedPresetChips.filter(chip => looseMatch(chip.name, localData))[0] ? [displayedPresetChips.filter(chip => looseMatch(chip.name, localData))[0]] : []).map(chip => ({ chip })),
184 | ...displayedPresetChips.filter(chip => !looseMatch(chip.name, localData)).map(chip => ({ chip })),
185 | ...((localData?.length && custom) ? [{
186 | name: localData?.trim(),
187 | color: activeColor
188 | }] : []).map(chip => ({ chip, isCustom: true }))
189 | ];
190 |
191 | return (
192 | <>
193 | l).join(' ')} style={{
194 | width: width ?? '300px',
195 | margin: margin ?? '0px',
196 | boxSizing: 'border-box'
197 | }}>
198 |
{name} {required && * } {help && ? }
199 |
{description}
200 |
(e.preventDefault(), inputRef.current.focus(), dropdownRef.current.scrollTop = 0, startEdits(), setSelected(0))} tabIndex={-1} onKeyDown={e => {
201 | if (e.key == 'ArrowDown') {
202 | setSelected((selected + 1 + displayedChips.length) % displayedChips.length);
203 | } else if (e.key == 'ArrowUp') {
204 | setSelected((selected - 1 + displayedChips.length) % displayedChips.length);
205 | }
206 | }}>
207 | {chips.map(chip => (
208 | e.preventDefault()} data-color={chip.color} className={styles.chip} key={chip.id + chip.name + chip.color}>{chip.name} {
209 | let index = 0;
210 | chips.forEach((c, i) => (c.id == chip.id ? index = i : 0));
211 | let theseChips = JSON.parse(JSON.stringify(chips));
212 | theseChips.splice(index, 1);
213 | setChips(theseChips);
214 | }}>×
215 | ))}
216 | {
217 | setLocalData(e.target.value);
218 | setSelected(0);
219 | if (setData instanceof Function) setData(e.target.value);
220 | }} onKeyDown={e => {
221 | if (e.key == 'Backspace' && e.target.value?.length == 0) {
222 | let theseChips = JSON.parse(JSON.stringify(chips));
223 | theseChips.pop();
224 | startEdits();
225 | setChips(theseChips);
226 | } else if ((e.key == 'Enter' || e.key == ',')) {
227 | let activeChip = displayedChips[selected]?.chip;
228 | if (!activeChip) return;
229 | if (e.key == ',') e.preventDefault();
230 | let theseChips = JSON.parse(JSON.stringify(chips));
231 | if (!displayedPresetChips.length && !custom) return;
232 | if (multiSelect) theseChips.push({
233 | name: activeChip.name,
234 | color: activeChip.color,
235 | id: localData.toLowerCase().split(' ').join('-')
236 | });
237 | else theseChips = [
238 | {
239 | name: activeChip.name,
240 | color: activeChip.color,
241 | id: localData.toLowerCase().split(' ').join('-')
242 | }
243 | ];
244 | setActiveColor(Math.random() * 6 | 0);
245 | setChips(theseChips);
246 | e.target.value = '';
247 | setLocalData('');
248 | startEdits();
249 | resetSelected();
250 | if (setData instanceof Function) setData('');
251 | }
252 | }}
253 | name={id} id={id} type={type} ref={inputRef} value={localData}>
254 |
255 |
✓
256 |
inputRef.current.focus()} onMouseDown={startEdits}>
257 | {displayAll ? displayedChips.map(({ chip, isCustom }, i) =>
setSelected(i),
268 | setActiveColor,
269 | setLocalData,
270 | startEdits
271 | }} />) : displayedChips.filter(({ isCustom }, i) => isCustom || i < 6 && localData?.length).map(({ chip, isCustom }, i) => setSelected(i),
282 | setActiveColor,
283 | setLocalData,
284 | startEdits
285 | }} />)}
286 | {!displayAll && !localData?.length && Type to search.
}
287 |
288 |
289 | >
290 | )
291 | }
--------------------------------------------------------------------------------
/components/Inputs/Select.module.css:
--------------------------------------------------------------------------------
1 | .wrapper div.content > * {
2 | outline: none;
3 | display: inline-block;
4 | border: none;
5 | font-size: 1em;
6 | font-weight: 500;
7 | font-family: system-ui, Inter;
8 | }
9 |
10 | .wrapper div.content input {
11 | flex-grow: 1;
12 | min-width: 100px;
13 | flex: 0 0 auto;
14 | }
15 |
16 | .wrapper div.content {
17 | width: 100%;
18 | border-radius: 0.25em;
19 | border-color: #d3d3d4;
20 | transition: 0.2s;
21 | border-width: 2px;
22 | border-style: solid;
23 | font-size: 1em;
24 | display: flex;
25 | flex-wrap: wrap;
26 | gap: 0.5rem;
27 | padding: 0.5rem;
28 | font-weight: 500;
29 | font-family: system-ui, Inter;
30 | z-index: 20;
31 | position: relative;
32 | }
33 |
34 | .wrapper div.content:hover {
35 | border-color: #b7b7b8;
36 | }
37 |
38 | .wrapper.isValid:not(:focus-within) > span {
39 | filter: opacity(1);
40 | }
41 |
42 | .wrapper.isValid:focus-within > span {
43 | filter: opacity(0.6) grayscale(0.6);
44 | }
45 |
46 | .wrapper.isPartiallyValid > span {
47 | filter: opacity(0.6) grayscale(0.6);
48 | }
49 |
50 | .wrapper div.content[type="date"] + span {
51 | transform: translate(-26px, -1px);
52 | }
53 |
54 | .wrapper label {
55 | margin-bottom: 0.1em;
56 | font-weight: 600;
57 | }
58 |
59 | .wrapper p {
60 | margin-top: 0em;
61 | margin-bottom: 0.5em;
62 | color: #767676;
63 | font-size: 14px;
64 | font-weight: 500;
65 | }
66 |
67 | .wrapper div.content:focus-within {
68 | outline: none;
69 | border-color: var(--orange);
70 | box-shadow: 0 0 0 2px rgba(var(--orange-3-values), 0.2)
71 | }
72 |
73 | .wrapper {
74 | min-width: 200px;
75 | font-family: system-ui, Inter;
76 | position: relative;
77 | }
78 |
79 | .content:not(:has(> .chip)):not(:focus-within), .content:not(:has(> .chip)):not(:focus-within) * {
80 | cursor: pointer!important;
81 | }
82 |
83 | .content:has(> .chip) {
84 | cursor: text;
85 | }
86 |
87 | .content:has(> .chip)::after {
88 | content: '▼';
89 | position: absolute;
90 | top: 50%;
91 | font-weight: 200;
92 | right: 10px;
93 | transform: translateY(-50%) scale(0.75);
94 | color: #767676;
95 | filter: opacity(0);
96 | transition: 0.2s all;
97 | }
98 |
99 | .content:not(:has(> .chip))::after {
100 | content: '▼';
101 | position: absolute;
102 | top: 50%;
103 | font-weight: 200;
104 | right: 10px;
105 | transform: translateY(-50%) scale(0.75);
106 | color: #767676;
107 | filter: opacity(1)!important;
108 | transition: 0.2s all;
109 | }
110 |
111 | .content:focus-within {
112 | cursor: text;
113 | }
114 |
115 | .wrapper > .content:has(> .chip) ~ span {
116 | bottom: 12px;
117 | right: 12px;
118 | }
119 |
120 | .wrapper > span {
121 | color: #21ca71;
122 | font-weight: 700;
123 | font-size: 20px;
124 | position: absolute;
125 | display: inline-block;
126 | filter: opacity(0);
127 | right: 10px;
128 | bottom: 8px;
129 | pointer-events: none;
130 | transition: 0.1s filter;
131 | }
132 |
133 | .wrapper div.content::placeholder {
134 | color: rgb(157, 157, 157);
135 | opacity: 1;
136 | }
137 |
138 | .wrapper > label > span {
139 | width: 20px;
140 | height: 20px;
141 | padding: 4px;
142 | cursor: help;
143 | border-radius: 10px;
144 | box-sizing: border-box;
145 | font-size: 10px;
146 | vertical-align: middle;
147 | display: inline-block;
148 | position: relative;
149 | transform: translateY(-2px);
150 | }
151 |
152 | .wrapper > label > span, .chip, .wrapper > .content {
153 | -webkit-touch-callout: none;
154 | -webkit-user-select: none;
155 | -khtml-user-select: none;
156 | -moz-user-select: none;
157 | -ms-user-select: none;
158 | user-select: none;
159 | }
160 |
161 | .wrapper > .content > * {
162 | -webkit-touch-callout: all;
163 | -webkit-user-select: all;
164 | -khtml-user-select: all;
165 | -moz-user-select: all;
166 | -ms-user-select: all;
167 | user-select: all;
168 | }
169 |
170 | .wrapper > label > span > span {
171 | width: 12px;
172 | height: 12px;
173 | display: inline-block;
174 | background: rgba(51, 124, 227, 0.769);
175 | border-radius: 6px;
176 | color: white;
177 | font-size: 10px;
178 | font-weight: bold;
179 | text-align: center;
180 | position: relative;
181 | outline: none;
182 | }
183 |
184 | .wrapper > label > span > span:after {
185 | opacity: 0;
186 | color: white;
187 | letter-spacing: 0;
188 | line-height: 1.375;
189 | transform: translateY(50%);
190 | box-shadow: 0 0 2px 0 #00000010,
191 | 0 4px 8px 0 #00000020;
192 | content: attr(aria-label);
193 | z-index: 10;
194 | left: 100%;
195 | bottom: 50%;
196 | min-height: 20px;
197 | position: absolute;
198 | padding: 0.25rem 0.75rem;
199 | background-color: #000000df;
200 | border-radius: 6px;
201 | font-family: Inter;
202 | font-size: 14px;
203 | max-width: 240px;
204 | font-weight: 500;
205 | transition: 0.15s all ease;
206 | width: max-content;
207 | height: min-content;
208 | right: 0;
209 | pointer-events: none;
210 | word-wrap: break-word;
211 | text-align: center;
212 | margin-left: 0.5rem;
213 | }
214 |
215 | .wrapper > label > span:hover > span:after,
216 | .wrapper > label > span:active > span:after,
217 | .wrapper > label > span:focus > span:after,
218 | .wrapper > label > span:focus-within > span:after {
219 | outline: none;
220 | z-index: 9;
221 | opacity: 1;
222 | }
223 |
224 | .wrapper > label > span:focus-within {
225 | background: rgba(51, 124, 227, 0.2);
226 | }
227 |
228 | .dropdown {
229 | position: absolute;
230 | top: calc( 100% + 2px );
231 | left: 0%;
232 | width: 100%;
233 | border-radius: 0.25em;
234 | border-color: #d3d3d4;
235 | transition: 0.15s ease-out;
236 | border-width: 2px;
237 | border-style: solid;
238 | font-size: 1em;
239 | cursor: default;
240 | font-weight: 500;
241 | font-family: Inter;
242 | overflow-y: hidden;
243 | z-index: 30;
244 | background-color: white;
245 | height: 0px;
246 | pointer-events: none;
247 | filter: opacity(0);
248 | /* box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; */
249 | /* box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; */
250 | box-shadow: rgba(50, 50, 93, 0.25) 0px 30px 60px -12px, rgba(0, 0, 0, 0.3) 0px 18px 36px -18px;
251 | }
252 |
253 | .wrapper:focus-within > div.content ~ .dropdown {
254 | display: block;
255 | height: 220px;
256 | top: 100%;
257 | overflow-y: auto;
258 | filter: opacity(1);
259 |
260 | pointer-events: all;
261 |
262 | }
263 |
264 | .wrapper:focus-within > div.content ~ .dropdown:has(> .chipOption:nth-child(3)) {
265 | height: 220px;
266 | }
267 |
268 | .wrapper:focus-within > div.content ~ .dropdown:not(:has(> .chipOption:nth-child(3))) {
269 | height: 150px;
270 | }
271 |
272 | .chip {
273 | background: #ffe2dd;
274 | border-radius: 12px;
275 | padding: 0.25rem 0.4rem;
276 | font-size: 0.85rem;
277 | padding-right: 24px;
278 | position: relative;
279 | cursor: default;
280 | }
281 |
282 | .chip[data-color="1"] {
283 | background: #fbdec9;
284 | }
285 |
286 | .chip[data-color="2"] {
287 | background: #feecc8;
288 | }
289 |
290 | .chip[data-color="3"] {
291 | background: #dbeddb;
292 | }
293 |
294 | .chip[data-color="4"] {
295 | background: #d3e5ef;
296 | }
297 |
298 | .chip[data-color="5"] {
299 | background: #e8deee;
300 | }
301 |
302 | .chip[data-color="6"] {
303 | background: #f5e1e9;
304 | }
305 |
306 | .chip .close {
307 | position: absolute;
308 | right: 0.25rem;
309 | top: 50%;
310 | transform: translateY(-50%);
311 | vertical-align: middle;
312 | color: rgba(26, 26, 26, 0.651);
313 | height: 1.25rem;
314 | width: 1.25rem;
315 | border-radius: 50%;
316 | display: flex;
317 | justify-content: center;
318 | align-items: center;
319 | cursor: pointer;
320 | transition: 0.2s all;
321 | }
322 |
323 | .dropdown .chip .close {
324 | display: none;
325 | }
326 |
327 | .dropdown .chip {
328 | padding-right: 0.4rem;
329 | display: inline-block;
330 | border-radius: 12px;
331 | cursor: pointer;
332 | }
333 |
334 | .chip .close:hover {
335 | color: rgba(10, 10, 10, 0.922);
336 | }
337 |
338 | .chipOption {
339 | display: block;
340 | padding: 0.65rem 0.4rem;
341 | cursor: pointer;
342 | }
343 |
344 | .dropdown:not(:hover):not(> .selectedChip) .chipOption:first-child {
345 | background: #efefef;
346 | }
347 |
348 | .selectedChip {
349 | background: #efefef;
350 | }
351 |
352 | .dropdown:not(> .selectedChip) .chipOption:hover {
353 | background: #efefef;
354 | }
355 |
356 | .dropdown:not(> .selectedChip) .chipOption:first-child:hover, .chipOption:first-child.selectedChip {
357 | background: #efefef;
358 | }
359 |
360 | .chipOption:active {
361 | background: #dfdfde!important;
362 | }
--------------------------------------------------------------------------------
/components/Inputs/Text.js:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router';
2 | import { useState, uesEffet, useEffect, useRef } from 'react';
3 | import styles from './Text.module.css';
4 |
5 | function defaultValidate (text) {
6 | return text?.length;
7 | }
8 |
9 | export default function Text (props) {
10 | const { setValue, validate: _validate, name, description, help, placeholder, data, setData, initialData, wrapperClass, width, margin, type, id: _id, required } = props;
11 |
12 | const id = _id ?? name?.toLowerCase()?.split(' ')?.join('-')?.split('')?.filter(a => `abcdefghijklmnopqrstuvwxyz1234567890-_`.includes(a))?.join('') ?? 'error';
13 | const validate = data => (_validate ? _validate(data) : true) && (required ? data?.length : true);
14 |
15 | const [localData, setLocalData] = useState(initialData ?? '');
16 | const [valid, setValid] = useState(false);
17 | const [partiallyValid, setPartiallyValid] = useState(false);
18 | const ref = useRef();
19 | useEffect(() => {
20 | setValue(name, localData);
21 | }, [localData]);
22 |
23 | function updateEmail (newEmail) {
24 | // ref.current.value = newEmail;
25 | setLocalData(newEmail);
26 | setPartiallyValid(validate(newEmail));
27 | setValid(validate(newEmail));
28 |
29 | }
30 |
31 | var router = useRouter();
32 | useEffect(() => {
33 |
34 | if (router.query.email && name?.toLowerCase?.() === 'email') {
35 | updateEmail(router.query.email);
36 | }
37 | }, [router.query.email])
38 |
39 | return (
40 | <>
41 | l).join(' ')} style={{
42 | width: width ?? '300px',
43 | margin: margin ?? '0px',
44 | boxSizing: 'border-box'
45 | }}>
46 |
{name} {required && * } {help && ? }
47 |
{description}
48 |
{
49 | console.log(e.target.value);
50 | console.log(validate, validate(e.target.value));
51 | console.log(_validate(e.target.value));
52 | setLocalData(e.target.value);
53 | if (setData instanceof Function) setData(e.target.value);
54 | setPartiallyValid(validate(e.target.value));
55 | }} onBlur={() => {
56 | setValid(validate(localData));
57 | }} onFocus={() => {
58 | setValid(false);
59 | }} placeholder={placeholder}>
60 |
✓
61 |
62 | >
63 | )
64 | }
--------------------------------------------------------------------------------
/components/Inputs/Text.module.css:
--------------------------------------------------------------------------------
1 | .wrapper input {
2 | width: 100%;
3 | border-radius: 0.25em;
4 | border-color: #d3d3d4;
5 | transition: 0.2s;
6 | border-width: 2px;
7 | border-style: solid;
8 | padding: 0.5em;
9 | font-size: 1em;
10 | font-weight: 500;
11 | font-family: system-ui, Inter;
12 | }
13 |
14 | .wrapper input:hover {
15 | border-color: #b7b7b8;
16 | }
17 |
18 | .wrapper.isValid > span {
19 | filter: opacity(1);
20 | }
21 |
22 | .wrapper.isPartiallyValid > span {
23 | filter: opacity(0.6) grayscale(0.6);
24 | }
25 |
26 | .wrapper input[type="date"] + span {
27 | transform: translate(-26px, -1px);
28 | }
29 |
30 | .wrapper label {
31 | margin-bottom: 0.1em;
32 | font-weight: 600;
33 | }
34 |
35 | .wrapper p {
36 | margin-top: 0em;
37 | margin-bottom: 0.5em;
38 | color: #767676;
39 | font-size: 14px;
40 | font-weight: 500;
41 | }
42 |
43 | .wrapper input:focus {
44 | outline: none;
45 | border-color: var(--orange);
46 | box-shadow: 0 0 0 2px rgba(var(--orange-3-values), 0.2)
47 | }
48 |
49 | .wrapper {
50 | min-width: 200px;
51 | font-family: system-ui, Inter;
52 | position: relative;
53 | }
54 |
55 | .wrapper > span {
56 | color: #21ca71;
57 | font-weight: 700;
58 | font-size: 20px;
59 | position: absolute;
60 | display: inline-block;
61 | filter: opacity(0);
62 | right: 10px;
63 | bottom: 8px;
64 | pointer-events: none;
65 | transition: 0.1s;
66 | }
67 |
68 | .wrapper input::placeholder {
69 | color: rgb(157, 157, 157);
70 | opacity: 1;
71 | }
72 |
73 | .wrapper > label > span {
74 | width: 20px;
75 | height: 20px;
76 | padding: 4px;
77 | cursor: help;
78 | border-radius: 10px;
79 | box-sizing: border-box;
80 | font-size: 10px;
81 | vertical-align: middle;
82 | display: inline-block;
83 | position: relative;
84 | transform: translateY(-2px);
85 | -webkit-touch-callout: none; /* iOS Safari */
86 | -webkit-user-select: none; /* Safari */
87 | -khtml-user-select: none; /* Konqueror HTML */
88 | -moz-user-select: none; /* Old versions of Firefox */
89 | -ms-user-select: none; /* Internet Explorer/Edge */
90 | user-select: none; /* Non-prefixed version, currently
91 | supported by Chrome, Edge, Opera and Firefox */
92 |
93 | }
94 |
95 | .wrapper > label > span > span {
96 | width: 12px;
97 | height: 12px;
98 | display: inline-block;
99 | background: rgba(51, 124, 227, 0.769);
100 | border-radius: 6px;
101 | color: white;
102 | font-size: 10px;
103 | font-weight: bold;
104 | text-align: center;
105 | position: relative;
106 | outline: none;
107 | }
108 |
109 | .wrapper > label > span > span:after {
110 | opacity: 0;
111 | color: white;
112 | letter-spacing: 0;
113 | line-height: 1.375;
114 | transform: translateY(50%);
115 | box-shadow: 0 0 2px 0 #00000010,
116 | 0 4px 8px 0 #00000020;
117 | content: attr(aria-label);
118 | z-index: 10;
119 | left: 100%;
120 | bottom: 50%;
121 | min-height: 20px;
122 | position: absolute;
123 | padding: 0.25rem 0.75rem;
124 | background-color: #000000df;
125 | border-radius: 6px;
126 | font-family: Inter;
127 | font-size: 14px;
128 | max-width: 240px;
129 | font-weight: 500;
130 | transition: 0.15s all ease;
131 | width: max-content;
132 | height: min-content;
133 | right: 0;
134 | pointer-events: none;
135 | word-wrap: break-word;
136 | text-align: center;
137 | margin-left: 0.5rem;
138 | }
139 |
140 | .wrapper > label > span:hover > span:after,
141 | .wrapper > label > span:active > span:after,
142 | .wrapper > label > span:focus > span:after,
143 | .wrapper > label > span:focus-within > span:after {
144 | outline: none;
145 | z-index: 9;
146 | opacity: 1;
147 | }
148 |
149 | .wrapper > label > span:focus-within {
150 | background: rgba(51, 124, 227, 0.2);
151 | }
--------------------------------------------------------------------------------
/components/Inputs/Title.js:
--------------------------------------------------------------------------------
1 | import { useState, uesEffet, useEffect } from 'react';
2 | import styles from './Title.module.css';
3 |
4 | function defaultValidate (text) {
5 | return text?.length;
6 | }
7 |
8 | export default function Title (props) {
9 | const { validate: _validate, name, description, help, placeholder, data, setData, initialData, wrapperClass, width, margin, type, id: _id, required } = props;
10 |
11 | const id = _id ?? name?.toLowerCase()?.split(' ')?.join('-')?.split('')?.filter(a => `abcdefghijklmnopqrstuvwxyz1234567890-_`.includes(a))?.join('') ?? 'error';
12 | const validate = data => (_validate ?? defaultValidate)() && (required ? data?.length : true);
13 |
14 | const [localData, setLocalData] = useState(initialData ?? '');
15 | const [valid, setValid] = useState(false);
16 | const [partiallyValid, setPartiallyValid] = useState(false);
17 |
18 | return (
19 | <>
20 | l).join(' ')} style={{
21 | width: width ?? '300px',
22 | margin: margin ?? '0px',
23 | boxSizing: 'border-box'
24 | }}>
25 |
{name} {help && ? }
26 |
{description}
27 |
28 | >
29 | )
30 | }
--------------------------------------------------------------------------------
/components/Inputs/Title.module.css:
--------------------------------------------------------------------------------
1 | .wrapper input {
2 | width: 100%;
3 | border-radius: 0.25em;
4 | border-color: #d3d3d4;
5 | transition: 0.2s;
6 | border-width: 2px;
7 | border-style: solid;
8 | padding: 0.5em;
9 | font-size: 1em;
10 | font-weight: 500;
11 | font-family: system-ui, Inter;
12 | }
13 |
14 | .wrapper input:hover {
15 | border-color: #b7b7b8;
16 | }
17 |
18 | .wrapper.isValid > span {
19 | filter: opacity(1);
20 | }
21 |
22 | .wrapper.isPartiallyValid > span {
23 | filter: opacity(0.6) grayscale(0.6);
24 | }
25 |
26 | .wrapper input[type="date"] + span {
27 | transform: translate(-26px, -1px);
28 | }
29 |
30 | .wrapper label {
31 | margin-bottom: 0.1em;
32 | font-weight: 600;
33 | cursor: text;
34 | }
35 |
36 | .wrapper p {
37 | margin-top: 0em;
38 | margin-bottom: 0.5em;
39 | color: #767676;
40 | font-size: 14px;
41 | font-weight: 500;
42 | }
43 |
44 | .wrapper input:focus {
45 | outline: none;
46 | border-color: var(--orange);
47 | box-shadow: 0 0 0 2px rgba(var(--orange-3-values), 0.2)
48 | }
49 |
50 | .wrapper {
51 | min-width: 200px;
52 | font-family: system-ui, Inter;
53 | position: relative;
54 | }
55 |
56 | .wrapper > span {
57 | color: #21ca71;
58 | font-weight: 700;
59 | font-size: 20px;
60 | position: absolute;
61 | display: inline-block;
62 | filter: opacity(0);
63 | right: 10px;
64 | bottom: 8px;
65 | pointer-events: none;
66 | transition: 0.1s;
67 | }
68 |
69 | .wrapper input::placeholder {
70 | color: rgb(157, 157, 157);
71 | opacity: 1;
72 | }
73 |
74 | .wrapper > label > span {
75 | width: 20px;
76 | height: 20px;
77 | padding: 4px;
78 | cursor: help;
79 | border-radius: 10px;
80 | box-sizing: border-box;
81 | font-size: 10px;
82 | vertical-align: middle;
83 | display: inline-block;
84 | position: relative;
85 | transform: translateY(-2px);
86 | -webkit-touch-callout: none; /* iOS Safari */
87 | -webkit-user-select: none; /* Safari */
88 | -khtml-user-select: none; /* Konqueror HTML */
89 | -moz-user-select: none; /* Old versions of Firefox */
90 | -ms-user-select: none; /* Internet Explorer/Edge */
91 | user-select: none; /* Non-prefixed version, currently
92 | supported by Chrome, Edge, Opera and Firefox */
93 |
94 | }
95 |
96 | .wrapper > label > span > span {
97 | width: 12px;
98 | height: 12px;
99 | display: inline-block;
100 | background: rgba(51, 124, 227, 0.769);
101 | border-radius: 6px;
102 | color: white;
103 | font-size: 10px;
104 | font-weight: bold;
105 | text-align: center;
106 | position: relative;
107 | outline: none;
108 | }
109 |
110 | .wrapper > label > span > span:after {
111 | opacity: 0;
112 | color: white;
113 | letter-spacing: 0;
114 | line-height: 1.375;
115 | transform: translateY(50%);
116 | box-shadow: 0 0 2px 0 #00000010,
117 | 0 4px 8px 0 #00000020;
118 | content: attr(aria-label);
119 | z-index: 10;
120 | left: 100%;
121 | bottom: 50%;
122 | min-height: 20px;
123 | position: absolute;
124 | padding: 0.25rem 0.75rem;
125 | background-color: #000000df;
126 | border-radius: 6px;
127 | font-family: Inter;
128 | font-size: 14px;
129 | max-width: 240px;
130 | font-weight: 500;
131 | transition: 0.15s all ease;
132 | width: max-content;
133 | height: min-content;
134 | right: 0;
135 | pointer-events: none;
136 | word-wrap: break-word;
137 | text-align: center;
138 | margin-left: 0.5rem;
139 | }
140 |
141 | .wrapper > label > span:hover > span:after,
142 | .wrapper > label > span:active > span:after,
143 | .wrapper > label > span:focus > span:after,
144 | .wrapper > label > span:focus-within > span:after {
145 | outline: none;
146 | z-index: 9;
147 | opacity: 1;
148 | }
149 |
150 | .wrapper > label > span:focus-within {
151 | background: rgba(51, 124, 227, 0.2);
152 | }
--------------------------------------------------------------------------------
/components/Modal.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export default function Modal ({ visible, setVisible, children, hideCloseButton }) {
4 | if (visible) return (
5 | <>
6 | {
16 | setVisible(false);
17 | }}>
18 |
19 |
20 |
34 | {!hideCloseButton &&
35 |
{
52 | setVisible(false);
53 | }}>
54 | ×
55 |
56 | }
57 | {children}
58 |
59 | >
60 | );
61 | return ( <> > )
62 | }
--------------------------------------------------------------------------------
/components/Sidebar.js:
--------------------------------------------------------------------------------
1 | import Icon from "@hackclub/icons";
2 | import styles from "../styles/Home.module.css";
3 | import { useEffect, useState } from "react";
4 |
5 |
6 | export default function Sidebar () {
7 | const [donor, setDonor] = useState('');
8 | useEffect(() => {
9 | fetch('/api/donor').then(res => res.text()).then(setDonor);
10 | if (!localStorage.getItem('hackoc-analytics')) {
11 | fetch('https://ip.yodacode.xyz').then(res => res.json()).then(({ geo }) => {
12 | splitbee.user.set({
13 | city: geo.city
14 | });
15 | localStorage.setItem('hackoc-analytics', true);
16 | });
17 | }
18 | }, []);
19 | return (
20 |
119 | );
120 | }
--------------------------------------------------------------------------------
/docs/404.html:
--------------------------------------------------------------------------------
1 | 404: This page could not be found 404
This page could not be found.
--------------------------------------------------------------------------------
/docs/_next/static/63naq6OZNUwEmbBG38Fkb/_buildManifest.js:
--------------------------------------------------------------------------------
1 | self.__BUILD_MANIFEST={__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":["static/chunks/7bad3855-0bb9e0fb6954a08c.js","static/css/5599a730af22f4f4.css","static/chunks/pages/index-c7c2ac66f3a267fe.js"],"/_error":["static/chunks/pages/_error-7397496ca01950b1.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB()
--------------------------------------------------------------------------------
/docs/_next/static/63naq6OZNUwEmbBG38Fkb/_ssgManifest.js:
--------------------------------------------------------------------------------
1 | self.__SSG_MANIFEST=new Set,self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
--------------------------------------------------------------------------------
/docs/_next/static/chunks/pages/_app-fd9d1b23c3421e47.js:
--------------------------------------------------------------------------------
1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[888],{1118:function(a,b,c){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return c(2373)}])},2373:function(a,b,c){"use strict";function d(a,b,c){return b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}c.r(b),c.d(b,{default:function(){return f}});var e=c(5893);c(906);var f=function(a){var b=a.Component,c=a.pageProps;return(0,e.jsxs)(e.Fragment,{children:[(0,e.jsx)("link",{rel:"preconnect",href:"https://fonts.googleapis.com"}),(0,e.jsx)("link",{rel:"preconnect",href:"https://fonts.gstatic.com",crossOrigin:"true"}),(0,e.jsx)("link",{href:"https://fonts.googleapis.com/css2?family=Readex+Pro:wght@200;300;400;500;600;700&display=swap",rel:"stylesheet"}),(0,e.jsx)(b,function(a){for(var b=1;b0&&a[f-1][2]>e;f--)a[f]=a[f-1];a[f]=[c,d,e];return}for(var h=1/0,f=0;f=e&&Object.keys(g.O).every(function(a){return g.O[a](c[j])})?c.splice(j--,1):(i=!1,eHack OC Hack OC Orange County 's First Post-Pandemic Hackathon
What's Hack OC? Hack OC is the first in-person high school hackathon after the pandemic in Orange County, California. We're inviting you and anyone else to participate in 10 hours of coding , building , learning , and sharing .
What's a "hackathon"? Hackathons are in-person coding events where teenagers come together to learn new skills, create fun projects, and make memories, typically for a day or two. There's also food, snacks, and drinks to fuel your creativity.
--------------------------------------------------------------------------------
/docs/orange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/docs/orange.png
--------------------------------------------------------------------------------
/docs/orange.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/docs/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/lib/questions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Full Name ✅
3 | * Ponouns ✅
4 | * Technical Skill Level ✅
5 | * Dietery Restrictions ✅
6 | * Parent/Guardian Name ✅
7 | * Parent/Guardian Email ✅
8 | * Email ✅
9 | * Shirt Size ✅
10 | * Date of Birth + Grade ✅
11 | * Vaccination Status ✅
12 | * Tabs or Spaces
13 | * Pineapple on Pizza
14 | */
15 |
16 | import caSchools from './caSchools.js';
17 | const schoolsList = caSchools.map(school => school.city ? `${school.school} - ${school.city}` : school.school ?? '');
18 |
19 | export const questions = {
20 | fullName: {
21 | name: 'Full Name',
22 | type: 'text',
23 | required: true,
24 | placeholder: 'Jane Doe',
25 | description: '',
26 | help: '',
27 | verify: () => true
28 | },
29 | email: {
30 | name: 'Email',
31 | type: 'email',
32 | required: true,
33 | placeholder: 'jane@example.com',
34 | description: 'We\'ll reach out to you via email for your vaccination status, event waivers, and more.',
35 | help: '',
36 | verify: email => /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(email)
37 | },
38 | pronouns: {
39 | name: 'Pronouns',
40 | type: 'text',
41 | required: false,
42 | placeholder: 'they/them',
43 | description: '',
44 | help: '',
45 | verify: () => true
46 | },
47 | parentName: {
48 | name: 'Parent Name',
49 | type: 'text',
50 | required: true,
51 | placeholder: 'John Doe',
52 | description: '',
53 | help: '',
54 | verify: () => true
55 | },
56 | parentEmail: {
57 | name: 'Parent Email',
58 | type: 'email',
59 | required: true,
60 | placeholder: 'john@example.com',
61 | description: '',
62 | help: '',
63 | verify: email => /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(email)
64 | },
65 | dateOfBirth: {
66 | name: 'Birthday',
67 | type: 'date',
68 | required: true,
69 | description: 'All high school & upper-middle-school aged students are welcome to come!',
70 | help: '',
71 | verify: () => true
72 | },
73 | shirtSize: {
74 | name: 'T-Shirt Size',
75 | special: 'select',
76 | options: [
77 | 'Extra Small',
78 | 'Small',
79 | 'Medium',
80 | 'Large',
81 | 'Extra Large'
82 | ],
83 | required: true,
84 | description: 'You won\'t want to miss our awesome swag!',
85 | help: '',
86 | verify: () => true
87 | },
88 | skillLevel: {
89 | name: 'Skill Level',
90 | special: 'select',
91 | options: [
92 | 'Beginner: I have never coded before or just started learning',
93 | 'Intermediate: I have taken CS classes OR worked on small individual projects',
94 | 'Advanced: I\'m comfortable with my skill set and can work on a project without much guidance'
95 | ],
96 | required: true,
97 | description: 'How would you describe your technical skills? Everyone\'s welcome at the event, but this helps us gauge what resources we need to provide.',
98 | help: '',
99 | verify: () => true
100 | },
101 | dietaryRestrictions: {
102 | name: 'Dietary Restrictions',
103 | special: 'multiSelect',
104 | options: [
105 | 'Vegetarian',
106 | 'Vegan',
107 | 'Kosher',
108 | 'Halal',
109 | 'Dairy-free',
110 | 'Gluten-free',
111 | 'Nut allergy',
112 | { custom: true }
113 | ],
114 | dontDisplayAll: false,
115 | placeholder: 'Select or add...',
116 | required: false,
117 | description: 'Leave blank if none.',
118 | help: '',
119 | verify: () => true
120 | },
121 | school: {
122 | name: 'School',
123 | special: 'select',
124 | options: [ ...schoolsList, { custom: true } ],
125 | dontDisplayAll: true,
126 | required: true,
127 | description: '',
128 | help: '',
129 | verify: () => true
130 | },
131 | referrer: {
132 | name: 'Referrer',
133 | type: 'text',
134 | required: false,
135 | description: 'If someone told you about Hack OC, put their name here!',
136 | help: '',
137 | verify: () => true
138 | },
139 | // vaccinationNote: {
140 | // name: 'Full Vaccination Required',
141 | // special: 'text',
142 | // description: `Hack OC requires all attendees to be fully vaccinated. By submitting this registration form, you are confirming that you have received two (2) doses of the Pfizer-BioNTech or Moderna COVID-19 immunization, or one (1) dose of the J&J/Janssen COVID-19 immunization. We will be in touch with you to verify by requiring a photo of your physical vaccine card or a SMART digital vaccine card.`
143 | // }
144 | }
145 |
146 | export const sections = [
147 | {
148 | title: 'Basic Information',
149 | description: 'Let\'s start off with the basics',
150 | questions: [
151 | questions.fullName,
152 | questions.email,
153 | questions.dateOfBirth,
154 | questions.pronouns
155 | ]
156 | },
157 | {
158 | title: 'Parent/Guardian Contact',
159 | description: 'Contact infromation for your parent/guardian',
160 | questions: [
161 | questions.parentName,
162 | questions.parentEmail
163 | ]
164 | },
165 | {
166 | title: 'Your Profile',
167 | description: 'Let\'s get to know you a bit better',
168 | questions: [
169 | questions.shirtSize,
170 | questions.skillLevel,
171 | questions.dietaryRestrictions,
172 | questions.school,
173 | questions.referrer
174 | ]
175 | }
176 | ];
177 |
178 | export default { questions, sections };
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 |
3 | const campaigns = {
4 | capo: 'https://hackoc.org/?utm_source=poster&utm_medium=physical&utm_campaign=capo&utm_content=qr',
5 | ihs: 'https://hackoc.org/?utm_source=poster&utm_medium=physical&utm_campaign=ihs&utm_content=qr',
6 | test: 'https://example.com/?utm_source=poster&utm_medium=physical&utm_campaign=test&utm_content=qr'
7 | };
8 |
9 | const releases = {
10 | '2023-03-13': '1.pdf'
11 | };
12 |
13 | const nextConfig = {
14 | reactStrictMode: true,
15 | swcMinify: true,
16 | rewrites: async () => {
17 | return [
18 | {
19 | source: "/bee.js",
20 | destination: "https://cdn.splitbee.io/sb.js",
21 |
22 | },
23 | {
24 | source: "/_hive/:slug",
25 | destination: "https://hive.splitbee.io/:slug",
26 | },
27 | {
28 | source: '/registration/link-discord/callback',
29 | destination: '/api/discord/link',
30 | },
31 | {
32 | source: '/s/:id',
33 | destination: '/scrapbook',
34 | },
35 | {
36 | source: '/h/:id',
37 | destination: '/ships',
38 | },
39 | ...Object.entries(releases).map(([ name, file ]) => ({
40 | source: `/press/releases/${name}`,
41 | destination: `/press/${file}`,
42 | }))
43 | ];
44 | },
45 | redirects: async () => {
46 | return [
47 | {
48 | source: '/discord',
49 | destination: 'https://discord.gg/cPfHW8SQ5B',
50 | basePath: false,
51 | permanent: false
52 | },
53 | {
54 | source: '/lastchance',
55 | destination: 'https://hackoc.org/?utm_source=twitter&utm_campaign=lastchance&utm_medium=link&utm_content=lastchance',
56 | basePath: false,
57 | permanent: false
58 | },
59 | {
60 | source: '/event/1',
61 | destination: 'https://hackoc.org/discord',
62 | basePath: false,
63 | permanent: false
64 | },
65 | {
66 | source: '/branding',
67 | destination: 'https://www.figma.com/file/R1sQCpxFQqQ8laUmySwoZ3/Hack-OC-Branding-%7C-Public-Facing?node-id=0%3A1&t=sCjA6w0V7Q2b639x-1',
68 | basePath: false,
69 | permanent: false
70 | },
71 | {
72 | source: '/gh/:repo',
73 | destination: 'https://github.com/hackoc/:repo',
74 | basePath: false,
75 | permanent: false
76 | },
77 | {
78 | source: '/github/:repo',
79 | destination: 'https://github.com/hackoc/:repo',
80 | basePath: false,
81 | permanent: false
82 | },
83 | {
84 | source: '/finances',
85 | destination: 'https://bank.hackclub.com/hackoc',
86 | basePath: false,
87 | permanent: false
88 | },
89 | {
90 | source: '/bank',
91 | destination: 'https://hackoc.org/finances',
92 | basePath: false,
93 | permanent: false
94 | },
95 | {
96 | source: '/money',
97 | destination: 'https://hackoc.org/finances',
98 | basePath: false,
99 | permanent: false
100 | },
101 | {
102 | source: '/github',
103 | destination: 'https://github.com/hackoc',
104 | basePath: false,
105 | permanent: false
106 | },
107 | {
108 | source: '/donate',
109 | destination: 'https://bank.hackclub.com/donations/start/hackoc',
110 | basePath: false,
111 | permanent: false
112 | },
113 | {
114 | source: '/contact',
115 | destination: 'mailto:team@hackoc.org',
116 | basePath: false,
117 | permanent: false
118 | },
119 | {
120 | source: '/prospectus',
121 | destination: 'https://hackoc.org/prospectus.pdf',
122 | basePath: false,
123 | permanent: false
124 | },
125 | {
126 | source: '/sponsorship-prospectus',
127 | destination: 'https://hackoc.org/prospectus.pdf',
128 | basePath: false,
129 | permanent: false
130 | },
131 | {
132 | source: '/swag',
133 | destination: "https://forms.gle/78DevocjpseXDSeFA",
134 | basePath: false,
135 | permanent: false
136 | },
137 | {
138 | source: '/email',
139 | destination: 'mailto:team@hackoc.org',
140 | basePath: false,
141 | permanent: false
142 | },
143 | {
144 | source: '/vote',
145 | destination: 'https://forms.gle/mM6ezbSRbWGJhfxb9',
146 | basePath: false,
147 | permanent: false
148 | },
149 | {
150 | source: '/flyer1',
151 | destination: 'https://drive.google.com/file/d/1VOOkyHptTRwy-sA1JrVfYu-Vu4Y0MEyw/view',
152 | basePath: false,
153 | permanent: false
154 | },
155 | ...Object.entries(campaigns).map(([campaign, url]) => ({
156 | source: `/c/${campaign}`,
157 | destination: url,
158 | basePath: false,
159 | permanent: false
160 | }))
161 | ]
162 | }
163 | }
164 |
165 | module.exports = nextConfig
166 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hackoc",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "pages-build": "next build && next export && rm -rf docs && mv out docs"
11 | },
12 | "dependencies": {
13 | "@fillout/react": "^3.0.0",
14 | "@hackclub/icons": "^0.0.9",
15 | "@splitbee/web": "^0.3.0",
16 | "@vercel/analytics": "^0.1.11",
17 | "mongodb": "^4.13.0",
18 | "next": "12.2.5",
19 | "node-fetch": "^3.3.0",
20 | "react": "18.2.0",
21 | "react-dom": "18.2.0",
22 | "react-google-recaptcha": "^2.1.0",
23 | "react-masonry-css": "^1.0.16",
24 | "short-unique-id": "^4.4.4"
25 | },
26 | "devDependencies": {
27 | "eslint": "8.22.0",
28 | "eslint-config-next": "12.2.5"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css'
2 | import Head from 'next/head'
3 | import Script from 'next/script'
4 | import { useEffect } from 'react'
5 | import splitbee from '@splitbee/web';
6 | import { Analytics } from "@vercel/analytics/react";
7 |
8 |
9 | function MyApp({ Component, pageProps }) {
10 | useEffect(() => {
11 | window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }
12 | splitbee.init({
13 | scriptUrl: "/bee.js",
14 | apiUrl: "/_hive",
15 | });
16 | }, []);
17 | return (
18 | <>
19 |
20 | {/* dangerous innerhtml script */}
21 |
26 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
46 |
51 |
52 |
53 | >
54 | )
55 | }
56 |
57 | export default MyApp
58 |
--------------------------------------------------------------------------------
/pages/api/discord/link.js:
--------------------------------------------------------------------------------
1 | export default function link (req, res) {
2 | const { data } = req.query;
3 |
4 | res.send(/*html*/`
5 |
6 |
7 |
8 | Redirecting...
9 |
10 |
11 | Redirecting...
12 | If you are not redirected, click here .
13 |
24 |
25 |
26 | `);
27 | }
--------------------------------------------------------------------------------
/pages/api/donor.js:
--------------------------------------------------------------------------------
1 | export default async function (req, res) {
2 | const response = await fetch('https://bank.hackclub.com/api/v3/organizations/hackoc/donations');
3 | const json = await response.json();
4 | res.send(
5 | json.filter(a =>a?.memo?.includes?.("Donation from"))
6 | .sort(() => Math.random() - 0.5)[0]?.donor?.name
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/pages/api/email/[city]/[email].js:
--------------------------------------------------------------------------------
1 | export default async function (req, res) {
2 | await fetch(process.env.SLACK_WEBHOOK, {
3 | method: 'POST',
4 | headers: {
5 | 'Content-Type': 'application/json'
6 | },
7 | body: JSON.stringify({
8 | email: req.query.email,
9 | ip_city: req.query.city
10 | })
11 | });
12 | res.send('OK')
13 | }
--------------------------------------------------------------------------------
/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function handler(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/pages/api/posts.js:
--------------------------------------------------------------------------------
1 | import ShortUniqueId from "short-unique-id";
2 | import fetch from "node-fetch";
3 |
4 | const sleep = () => new Promise((resolve) => {
5 | setTimeout(() => {
6 | resolve();
7 | }, 350);
8 | });
9 |
10 | export function dbConnect () {
11 | return new Promise((resolve, reject) => {
12 | const { MongoClient, ServerApiVersion } = require('mongodb');
13 | const uri = process.env.MONGODB_URI;
14 | const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
15 | client.connect(err => {
16 | if (err) return reject(err);
17 | resolve(client);
18 | // perform actions on the collection object
19 | });
20 | });
21 |
22 | }
23 |
24 |
25 | export default async function handler(req, res) {
26 | const client = await dbConnect();
27 |
28 | const collection = client.db("primary").collection("scrapbook");
29 |
30 | const posts = (await collection.find().toArray());
31 | const registrations = (await client.db("primary").collection("users").find().toArray());
32 | const users = {};
33 | registrations.forEach(registration => {
34 | users[registration['Discord Tag']] = registration['Full Name']
35 | });
36 | res.json(posts.map(post => {
37 | if (users[post.name]) post.name = users[post.name];
38 | return post;
39 | }));
40 | client.close();
41 | }
42 |
--------------------------------------------------------------------------------
/pages/api/register.js:
--------------------------------------------------------------------------------
1 | import fetch from "node-fetch";
2 |
3 | const sleep = () => new Promise((resolve) => {
4 | setTimeout(() => {
5 | resolve();
6 | }, 350);
7 | });
8 |
9 | export function dbConnect () {
10 | return new Promise((resolve, reject) => {
11 | const { MongoClient, ServerApiVersion } = require('mongodb');
12 | const uri = process.env.MONGODB_URI;
13 | const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
14 | client.connect(err => {
15 | if (err) return reject(err);
16 | resolve(client);
17 | // perform actions on the collection object
18 | });
19 | });
20 |
21 | }
22 |
23 | async function email (email, name) {
24 | const token = `Bearer ${process.env.MAIL_KEY}`;
25 | const res = await fetch('https://api.hackoc.org/mail/v1/authed/deliver/register', {
26 | method: 'POST',
27 | headers: {
28 | 'Content-Type': 'application/json',
29 | 'Authorization': token
30 | },
31 | body: JSON.stringify({
32 | data: {
33 | firstName: name.split(' ')[0],
34 | email
35 | }
36 | })
37 | });
38 | const json = await res.json();
39 | console.log(json);
40 | return json;
41 | }
42 |
43 |
44 | export default async function handler(req, res) {
45 | const dbPromise = dbConnect();
46 | const { body, method } = req;
47 |
48 | // Extract the email and captcha code from the request body
49 | const { data, captcha } = body;
50 |
51 | if (method === "POST") {
52 | // If email or captcha are missing return an error
53 | if (!captcha || !data) {
54 | return res.status(422).json({
55 | message: "Unproccesable request, please provide the required fields",
56 | });
57 | }
58 |
59 | try {
60 | // Ping the google recaptcha verify API to verify the captcha code you received
61 | const response = await fetch(
62 | `https://www.google.com/recaptcha/api/siteverify?secret=${process.env.RECAPTCHA_SECRET_KEY}&response=${captcha}`,
63 | {
64 | headers: {
65 | "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
66 | },
67 | method: "POST",
68 | }
69 | );
70 | const captchaValidation = await response.json();
71 | /**
72 | * The structure of response from the veirfy API is
73 | * {
74 | * "success": true|false,
75 | * "challenge_ts": timestamp, // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
76 | * "hostname": string, // the hostname of the site where the reCAPTCHA was solved
77 | * "error-codes": [...] // optional
78 | }
79 | */
80 | if (captchaValidation.success) {
81 | // Replace this with the API that will save the data received
82 | // to your backend
83 | const client = await dbPromise;
84 | const collection = client.db("primary").collection("users");
85 | const existingRecord = (await collection.findOne({
86 | Email: data["Email"]
87 | }));
88 | if (existingRecord) return res.status(422).json({ message: "This email has already registered for Hack OC." });
89 | console.log(await collection.insertOne(data));
90 | client.close();
91 | // Return 200 if everything is successful
92 | try {
93 | await email(data["Email"], data["Full Name"]);
94 | await fetch(process.env.LATE_WEBHOOK, {
95 | method: 'POST',
96 | body: JSON.stringify({
97 | Email: data["Email"],
98 | Name: data["Full Name"],
99 | })
100 | });
101 | } catch (err) {
102 |
103 | return res.status(422).json({ message: "We had trouble sending you an email. Please report this error." });
104 | }
105 | return res.status(200).send("OK");
106 | }
107 |
108 | return res.status(422).json({
109 | message: "Unproccesable request, Invalid captcha code",
110 | });
111 | } catch (error) {
112 | console.log(error);
113 | return res.status(422).json({ message: "Something went wrong" });
114 | }
115 | }
116 | // Return 404 if someone pings the API with a method other than
117 | // POST
118 | return res.status(404).send("Invalid method. please use POST");
119 | }
120 |
--------------------------------------------------------------------------------
/pages/api/scanner.js:
--------------------------------------------------------------------------------
1 | import { dbConnect } from "./register";
2 |
3 | export default async function scanner (req, res) {
4 | const { data } = req.query;
5 |
6 | const dbPromise = dbConnect();
7 |
8 | const client = await dbPromise;
9 | const collection = client.db("primary").collection("users");
10 | const existingRecord = (await collection.findOne({
11 | Email: data["Email"]
12 | }));
13 | if (existingRecord) return res.status(422).json({ message: "This email has already registered for Hack OC." });
14 | console.log(await collection.insertOne(data));
15 | client.close();
16 |
17 |
18 | res.send("OK");
19 | }
--------------------------------------------------------------------------------
/pages/api/scrapbookdelete.js:
--------------------------------------------------------------------------------
1 | import ShortUniqueId from "short-unique-id";
2 | import fetch from "node-fetch";
3 |
4 | const sleep = () => new Promise((resolve) => {
5 | setTimeout(() => {
6 | resolve();
7 | }, 350);
8 | });
9 |
10 | export function dbConnect () {
11 | return new Promise((resolve, reject) => {
12 | const { MongoClient, ServerApiVersion } = require('mongodb');
13 | const uri = process.env.MONGODB_URI;
14 | const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
15 | client.connect(err => {
16 | if (err) return reject(err);
17 | resolve(client);
18 | // perform actions on the collection object
19 | });
20 | });
21 |
22 | }
23 |
24 |
25 | export default async function handler(req, res) {
26 | if (req.body.token !== process.env.PRIVATE_TOKEN) return res.send("oops");
27 | const client = await dbConnect();
28 |
29 |
30 |
31 | const collection = client.db("primary").collection("scrapbook");
32 |
33 | const query = req.body.id ? {
34 | shortId: req.body.id
35 |
36 | } : {
37 | discord: req.body.discord
38 | }
39 |
40 | const response = (await collection.deleteOne(query));
41 | res.json({
42 | ok: true
43 | });
44 | client.close();
45 | }
46 |
--------------------------------------------------------------------------------
/pages/api/scrapbookpost.js:
--------------------------------------------------------------------------------
1 | import ShortUniqueId from "short-unique-id";
2 | import fetch from "node-fetch";
3 |
4 | const sleep = () => new Promise((resolve) => {
5 | setTimeout(() => {
6 | resolve();
7 | }, 350);
8 | });
9 |
10 | export function dbConnect () {
11 | return new Promise((resolve, reject) => {
12 | const { MongoClient, ServerApiVersion } = require('mongodb');
13 | const uri = process.env.MONGODB_URI;
14 | const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
15 | client.connect(err => {
16 | if (err) return reject(err);
17 | resolve(client);
18 | // perform actions on the collection object
19 | });
20 | });
21 |
22 | }
23 |
24 | const uid = new ShortUniqueId({ length: 10 });
25 |
26 | export default async function handler(req, res) {
27 | if (req.body.token !== process.env.PRIVATE_TOKEN) return res.send("oops");
28 | const client = await dbConnect();
29 | const shortId = uid();
30 |
31 |
32 |
33 | const collection = client.db("primary").collection("scrapbook");
34 |
35 | const response = (await collection.insertOne({
36 | name: req.body.name,
37 | avatar: req.body.avatar,
38 | user: req.body.user,
39 | message: req.body.message,
40 | image: req.body.image,
41 | shortId,
42 | discord: req.body.discord
43 |
44 | }));
45 | res.json({
46 | mongoDbId: response.insertedId,
47 | shortId
48 | });
49 | client.close();
50 | }
51 |
--------------------------------------------------------------------------------
/pages/api/shippost.js:
--------------------------------------------------------------------------------
1 | import ShortUniqueId from "short-unique-id";
2 | import fetch from "node-fetch";
3 |
4 | const sleep = () => new Promise((resolve) => {
5 | setTimeout(() => {
6 | resolve();
7 | }, 350);
8 | });
9 |
10 | export function dbConnect () {
11 | return new Promise((resolve, reject) => {
12 | const { MongoClient, ServerApiVersion } = require('mongodb');
13 | const uri = process.env.MONGODB_URI;
14 | const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
15 | client.connect(err => {
16 | if (err) return reject(err);
17 | resolve(client);
18 | // perform actions on the collection object
19 | });
20 | });
21 |
22 | }
23 |
24 | const uid = new ShortUniqueId({ length: 10 });
25 |
26 | export default async function handler(req, res) {
27 | if (req.body.token !== process.env.PRIVATE_TOKEN) return res.send("oops");
28 | const client = await dbConnect();
29 | const shortId = uid();
30 |
31 |
32 |
33 | const collection = client.db("primary").collection("ship");
34 |
35 | const response = (await collection.insertOne({
36 | name: req.body.name,
37 | avatar: req.body.avatar,
38 | user: req.body.user,
39 | message: req.body.message,
40 | image: req.body.image,
41 | shortId,
42 | title: req.body.title,
43 | discord: req.body.discord
44 |
45 | }));
46 | res.json({
47 | mongoDbId: response.insertedId,
48 | shortId
49 | });
50 | client.close();
51 | }
52 |
--------------------------------------------------------------------------------
/pages/api/ships.js:
--------------------------------------------------------------------------------
1 | import ShortUniqueId from "short-unique-id";
2 | import fetch from "node-fetch";
3 |
4 | const sleep = () => new Promise((resolve) => {
5 | setTimeout(() => {
6 | resolve();
7 | }, 350);
8 | });
9 |
10 | export function dbConnect () {
11 | return new Promise((resolve, reject) => {
12 | const { MongoClient, ServerApiVersion } = require('mongodb');
13 | const uri = process.env.MONGODB_URI;
14 | const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
15 | client.connect(err => {
16 | if (err) return reject(err);
17 | resolve(client);
18 | // perform actions on the collection object
19 | });
20 | });
21 |
22 | }
23 |
24 |
25 | export default async function handler(req, res) {
26 | const client = await dbConnect();
27 |
28 | const collection = client.db("primary").collection("ship");
29 |
30 | const posts = (await collection.find().toArray());
31 |
32 | console.log(posts);
33 | const registrations = (await client.db("primary").collection("users").find().toArray());
34 | const users = {};
35 | registrations.forEach(registration => {
36 | users[registration['Discord Tag']] = registration['Full Name']
37 | });
38 | res.json(posts.map(post => {
39 | if (users[post.name]) post.name = users[post.name];
40 | return post;
41 | }));
42 | client.close();
43 | }
44 |
--------------------------------------------------------------------------------
/pages/api/v2.js:
--------------------------------------------------------------------------------
1 | export default async function (req, res) {
2 | const { email, city } = req.body;
3 | const response = await fetch('https://api.airtable.com/v0/appYlvRWZObGXXGOh/Emails', {
4 | method: 'POST',
5 | headers: {
6 | Authorization: `Bearer ${process.env.AIRTABLE_KEY}`,
7 | 'Content-Type': 'application/json'
8 | },
9 | body: JSON.stringify({
10 | records: [
11 | {
12 | fields: {
13 | Email: email,
14 | City: city
15 | }
16 | }
17 | ]
18 | })
19 | });
20 | const data = await response.json();
21 | console.log(data);
22 | res.send('OK');
23 | }
--------------------------------------------------------------------------------
/pages/api/webhook.js:
--------------------------------------------------------------------------------
1 | export default async function (req, res) {
2 | const response = await fetch('http://mail2.hackoc.org/v1/webhook').then(response => response.text());
3 | res.send(response);
4 | }
--------------------------------------------------------------------------------
/pages/files/[file].js:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'next/router'
2 |
3 | export function Header ({ fileName }) {
4 | return (
5 |
11 |
13 |
14 |
15 |
18 |
29 |
30 | )
31 | }
32 |
33 | export default function FileViewer () {
34 |
35 | const router = useRouter();
36 | const { file } = router.query;
37 |
38 | return (
39 | <>
40 |
41 |
46 | >
47 | )
48 |
49 |
50 | }
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import styles from '../styles/Home.module.css'
3 | import Icon from '@hackclub/icons'
4 | import Modal from '../components/Modal'
5 | import { useEffect, useState } from 'react';
6 | import splitbee from '@splitbee/web';
7 | import with$, { withNodrag } from '../utils/cssUtils';
8 | import useMedia, { useViewport } from '../utils/useMedia';
9 | import Sidebar from '../components/Sidebar';
10 | import { FilloutStandardEmbed } from "@fillout/react";
11 |
12 | const meta_desc = "Orange County's high school hackathon";
13 | const theme_color = '#FA7B33';
14 | const social_image = 'https://hc-cdn.hel1.your-objectstorage.com/s/v3/777fe734e848f3b800c7ea600764edf9ddd5c2ac_open-graph.png';
15 |
16 | const regex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
17 |
18 | // const timelapseId = "9x00RCb1N7WTpAl6cIN0000Kult00vyzslROW6A1RblWwxM"
19 |
20 | const timelapseId = "402YMZJfp6kW02302E3r1RMe013Ub9AqlPwzr4VjD00HO7ME"
21 |
22 | export function BaseCard ({ children, style }) {
23 | return (
24 |
30 | {children}
31 |
32 | )
33 | }
34 |
35 | export function ImageCard ({ style, src, alt, caption, left }) {
36 | return (
37 |
42 |
49 | {caption && {caption}
}
57 |
58 | )
59 | }
60 |
61 | export function Card ({ title, icon, children, style }) {
62 | return (
63 |
64 |
65 |
73 |
74 |
75 | {title}
80 |
83 |
84 |
85 | {children}
88 |
89 |
90 | )
91 | }
92 |
93 | export default function Home({ formParams }) {
94 | const [modal, setModal] = useState(false);
95 | const handleFormEnter = () => {
96 | if (regex.test(email) && !loading) {
97 | return window.location.href = '/register?email=' + encodeURIComponent(email);
98 |
99 | setLoading(true);
100 | fetch('https://ip.yodacode.xyz').then(res => res.json()).then(({ geo }) => {
101 | splitbee.track("Email Subscribe", {
102 | email,
103 | city: geo.city
104 | });
105 | fetch('/api/v2', {
106 | method: 'POST',
107 | headers: {
108 | 'Content-Type': 'application/json'
109 | },
110 | body: JSON.stringify({
111 | email,
112 | city: geo.city
113 | })
114 | }).then(() => {
115 | fetch('/api/webhook').then(() => {
116 | setSubmitted(true);
117 | });
118 | });
119 | })
120 | }
121 | };
122 | const [email, setEmail] = useState('');
123 | const [submitted, setSubmitted] = useState(false);
124 | const [loading, setLoading] = useState(false);
125 |
126 | const { width, height } = useMedia();
127 |
128 | const cardSize = '380px';
129 | const cardGap = '20px';
130 |
131 | return (
132 | <>
133 |
134 |
147 |
148 |
149 | Hack OC 2025
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
178 |
188 |
191 |
Coming Soon
194 |
195 |
196 |
197 | >
198 | )
199 | }
200 |
201 | export function getServerSideProps(context) {
202 | const { req } = context;
203 | const { headers } = req;
204 |
205 | return {
206 | props: {
207 | formParams: {
208 | est_lat: headers['x-vercel-ip-latitude'],
209 | est_long: headers['x-vercel-ip-longitude'],
210 | est_city: decodeURIComponent(headers['x-vercel-ip-city'])
211 | }
212 | },
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/pages/register-old.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import styles from '../styles/Register.module.css'
3 | import Icon from '@hackclub/icons'
4 | import Modal from '../components/Modal'
5 | import Text from '../components/Inputs/Text.js';
6 | import Title from '../components/Inputs/Title.js';
7 | import Select from '../components/Inputs/Select.js';
8 | import { questions, sections } from '../lib/questions.js';
9 | import { createRef, useEffect, useState } from 'react';
10 |
11 | const meta_desc = "Orange County's first high school coding event since the pandemic. Join us for 12 hours of hacking, workshops, & friendship.";
12 | const theme_color = '#FA7B33';
13 | const social_image = '/social.png';
14 |
15 | import ReCAPTCHA from "react-google-recaptcha";
16 | import useProtection from '../utils/useProtection';
17 | import splitbee from '@splitbee/web';
18 |
19 | const regex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
20 |
21 | const timelapseId = "9x00RCb1N7WTpAl6cIN0000Kult00vyzslROW6A1RblWwxM"
22 |
23 | // const timelapseId = "402YMZJfp6kW02302E3r1RMe013Ub9AqlPwzr4VjD00HO7ME"
24 |
25 | export default function Register() {
26 |
27 | const [modal, setModal] = useState(false);
28 | const recaptchaRef = createRef();
29 |
30 | const [formData, setFormData] = useState({});
31 |
32 | function setValue (name, value) {
33 | let newFormData = JSON.parse(JSON.stringify(formData))
34 | newFormData[name] = value;
35 | setFormData(newFormData);
36 | }
37 |
38 |
39 | function validate () {
40 | const v = Object.values(questions);
41 | let valid = true;
42 | let missing = [];
43 | for (let i = 0; i < v.length; i++) {
44 | const { required, verify, name } = v[i];
45 | const value = formData[name];
46 | const isValid = (
47 | (required ? (value instanceof Array ? value.length : value) : true)
48 | &&
49 | (verify?.(value))
50 | );
51 | console.log(value, name, isValid)
52 | if (!isValid && v[i].special !== 'text') {
53 | valid = false;
54 | missing.push(name)
55 | }
56 | }
57 | return {valid,missing};
58 | }
59 |
60 | const [valid, setValid] = useState(validate());
61 |
62 | useEffect(() => {
63 | setValid(validate());
64 | }, [formData]);
65 |
66 | const [key, setKey] = useState('');
67 | const [showForm, setShowForm] = useState(true);
68 | const [captchaCode, setCaptchaCode] = useState(null);
69 | const [discordTag, setDiscordTag] = useState('');
70 | const [discordId, setDiscordId] = useState('');
71 |
72 | useEffect(() => {
73 | const interval = setInterval(() => {
74 | const tag = localStorage.getItem('hoc-discord-tag');
75 | const id = localStorage.getItem('hoc-discord-id');
76 | const ts = localStorage.getItem('hoc-discord-ts');
77 |
78 | if (tag && id && ts && (Date.now() - ts < 1000 * 5)) {
79 | setDiscordTag(tag);
80 | setDiscordId(id);
81 | }
82 | }, 1000);
83 | return () => clearInterval(interval);
84 | }, []);
85 |
86 |
87 | useEffect(() => {
88 | try {
89 | rdt('track', 'Lead', {
90 | });
91 |
92 | } catch (err) {}
93 | }, []);
94 |
95 | useEffect(() => {
96 | console.log('Discord stuff changed');
97 | }, discordTag, discordId)
98 |
99 | // const handleSubmit = (event) => {
100 | // event.preventDefault();
101 | // // Execute the reCAPTCHA when the form is submitted
102 | // recaptchaRef.current.execute();
103 | // };
104 | const handleSubmit = async () => {
105 | // If the reCAPTCHA code is null or undefined indicating that
106 | // the reCAPTCHA was expired then return early
107 | if (!captchaCode) {
108 | return;
109 | }
110 | try {
111 | await splitbee.user.set({
112 | ...Object.fromEntries(Object.entries(formData).map(([k,v]) => ['FormData' + k.split(' ').join(''), v]))
113 | });
114 | const response = await fetch("/api/register", {
115 | method: "POST",
116 | body: JSON.stringify({ data: {
117 | ...formData,
118 | ['Discord Tag']: discordTag,
119 | ['Discord ID']: discordId,
120 | }, captcha: captchaCode }),
121 | headers: {
122 | "Content-Type": "application/json",
123 | },
124 | });
125 | if (response.ok) {
126 |
127 | // If the response is ok than show the success alert
128 | window.location.href = '/registration/success?name=' + encodeURIComponent(formData['Full Name']);
129 | } else {
130 | // Else throw an error with the message returned
131 | // from the API
132 | const error = await response.json();
133 | throw new Error(error.message)
134 | }
135 | } catch (error) {
136 | alert(error?.message || "Something went wrong");
137 | } finally {
138 | // Reset the reCAPTCHA when the request has failed or succeeeded
139 | // so that it can be executed again if user submits another email.
140 | }
141 | };
142 |
143 |
144 | const [email, setEmail] = useState('');
145 | const [submitted, setSubmitted] = useState(false);
146 | useEffect(() => {
147 | const params = new Proxy(new URLSearchParams(window.location.search), {
148 | get: (searchParams, prop) => searchParams.get(prop),
149 | });
150 | if (params?.email) setEmail(params?.email);
151 | if (params?.key) setKey(params?.key);
152 | setShowForm(process.env.NODE_ENV !== "production" || key == process.env.NEXT_PUBLIC_KEY);
153 | }, []);
154 | if (false) return (
155 | <>
156 |
161 |
162 |
Hack OC
163 |
164 |
165 |
166 |
''} hideCloseButton={true}>
167 |
180 |
Registrations will open soon!
181 |
In the meantime, check out our homepage .
182 |
183 |
184 |
185 | >
186 | );
187 | const isDiscordMissing = !discordTag;
188 | const missingLength = valid.missing.length + (isDiscordMissing ? 1 : 0);
189 |
190 | return (
191 | <>
192 |
193 | Register for Hack OC
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
215 |
216 |
217 |
218 | Hack
224 | OC
225 |
232 |
233 | {' '}Registration
234 |
235 | May 6th • Anduril HQ • 9am-9pm
239 |
417 |
418 |
419 | >
420 | )
421 | }
--------------------------------------------------------------------------------
/pages/register.js:
--------------------------------------------------------------------------------
1 | import { FilloutFullScreenEmbed } from "@fillout/react";
2 | import Head from "next/head";
3 |
4 | const meta_desc = "Orange County's high school hackathon";
5 | const theme_color = '#FA7B33';
6 | const social_image = 'https://hc-cdn.hel1.your-objectstorage.com/s/v3/777fe734e848f3b800c7ea600764edf9ddd5c2ac_open-graph.png';
7 |
8 | export default function Register () {
9 | return (
10 |
11 |
12 |
13 |
Hack OC 2025 | Registration
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | )
31 | }
--------------------------------------------------------------------------------
/pages/registration/discord-success.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import { useEffect, useState } from 'react';
3 | import Modal from '../../components/Modal';
4 |
5 |
6 | export default function Success () {
7 | const [name, setName] = useState('');
8 | const [timeLeft, setTimeLeft] = useState(5);
9 | const [error, setError] = useState(false);
10 |
11 | useEffect(() => {
12 |
13 | const info = JSON.parse(atob(new URLSearchParams(window.location.search).get('data')));
14 |
15 | if (!info) return setError(true);
16 | window.localStorage.setItem('hoc-discord-id', info.discordId);
17 | window.localStorage.setItem('hoc-discord-tag', info.discordTag);
18 | window.localStorage.setItem('hoc-discord-ts', Date.now());
19 |
20 | setName(info.discordTag);
21 | }, []);
22 |
23 | useEffect(() => {
24 | const start = Date.now();
25 | const timer = setInterval(() => {
26 | let t = Math.floor(5 - (Date.now() - start) / 1000);
27 | setTimeLeft(t);
28 | if (t <= 0) window.close();
29 | }, 50);
30 |
31 | return () => clearTimeout(timer);
32 | }, []);
33 |
34 | return (
35 | <>
36 |
41 |
42 |
Hack OC
43 |
44 |
45 |
46 |
''} hideCloseButton={true}>
47 |
61 |
62 |
63 | {error ? <>
64 |
There was an error.
65 |
Please try again. This tab will close in {timeLeft} seconds .
66 | > : <>
67 |
You've been linked to {name} !
68 |
Return to the previous tab to finish registration. This tab will close in {timeLeft} seconds .
69 | >}
70 |
71 |
72 |
73 | >
74 | );
75 |
76 | }
--------------------------------------------------------------------------------
/pages/registration/success.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import { useEffect, useState } from 'react';
3 | import Modal from '../../components/Modal';
4 |
5 | function sendEmail () {
6 | // fetch('http://mail2.hackoc.org')
7 | }
8 |
9 | export default function Success () {
10 | const [name, setName] = useState('');
11 |
12 | useEffect(() => {
13 | const newName = new URLSearchParams(window.location.search).get('name');
14 | setName(newName);
15 | try {
16 | rdt('track', 'SignUp', {
17 | "name": newName
18 | });
19 | } catch (err) {
20 | console.log(err);
21 | }
22 | }, []);
23 | return (
24 | <>
25 |
30 |
31 |
Hack OC
32 |
33 |
34 |
35 |
''} hideCloseButton={true}>
36 |
50 |
51 |
You're registered{name ? `, ${name}` : ''}!
52 |
Next, you'll need to sign the event waiver. We'll reach out to you when we're ready.
53 |
In the meantime, join our Discord server to meet other hackers. Announcements and judging will happen through Discord, so it's important you join ahead of time.
54 |
55 |
56 |
57 | >
58 | );
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/pages/schedule.js:
--------------------------------------------------------------------------------
1 | export default function Schedule () {
2 | return (
3 |
10 | )
11 | }
--------------------------------------------------------------------------------
/pages/scrapbook.js:
--------------------------------------------------------------------------------
1 | import Masonry from 'react-masonry-css'
2 |
3 | import Head from 'next/head'
4 | import styles from '../styles/Home.module.css'
5 | import Icon from '@hackclub/icons'
6 | import Modal from '../components/Modal'
7 | import { useEffect, useRef, useState } from 'react';
8 | import splitbee from '@splitbee/web';
9 | import with$, { withNodrag } from '../utils/cssUtils';
10 | import useMedia, { useViewport } from '../utils/useMedia';
11 | import Sidebar from '../components/Sidebar';
12 |
13 | const meta_desc = "Orange County's first high school coding event since the pandemic. Join us for 12 hours of hacking, workshops, & friendship.";
14 | const theme_color = '#FA7B33';
15 | const social_image = '/social.png';
16 |
17 | const regex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
18 |
19 | // const timelapseId = "9x00RCb1N7WTpAl6cIN0000Kult00vyzslROW6A1RblWwxM"
20 |
21 | const timelapseId = "402YMZJfp6kW02302E3r1RMe013Ub9AqlPwzr4VjD00HO7ME"
22 |
23 | export function usePosts () {
24 | const [posts, setPosts] = useState(null);
25 |
26 | useEffect(() => {
27 | fetch('/api/posts').then(res => res.json()).then(posts => {
28 | setPosts(posts);
29 | });
30 | }, []);
31 |
32 | return [posts ?? [], posts == null];
33 | }
34 |
35 | export function BaseCard ({ children, style }) {
36 | return (
37 |
43 | {children}
44 |
45 | )
46 | }
47 |
48 | export function Update ({ post, id }) {
49 | const [overwrite, setOverwrite] = useState(false);
50 | const ref = useRef();
51 | useEffect(() => {
52 | if (post.shortId == id) {
53 | ref.current.scrollIntoView({ behavior: 'smooth' });
54 | setTimeout(() => {
55 | setOverwrite(true);
56 | }, 100);
57 | }
58 | }, [post.shortId, id])
59 |
60 | return (
61 |
62 |
74 |
80 |
81 |
87 | {post.name ?? 'Unknown Author'}
94 |
95 |
96 | {post.message}
97 |
98 |
106 |
107 |
112 |
{new Date(parseInt(post._id.substring(0, 8), 16) * 1000).toLocaleString('en-US', {weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true})}
113 |
114 |
115 |
116 | )
117 | }
118 |
119 | export default function Home() {
120 | const [modal, setModal] = useState(false);
121 | const [posts, loading] = usePosts();
122 | const [id, setId] = useState(null);
123 |
124 | useEffect(() => {
125 | const { pathname } = window.location;
126 | if (pathname.startsWith('/s/')) {
127 | setId(pathname.substring(3));
128 | }
129 | }, []);
130 |
131 |
132 | const { width, height } = useMedia();
133 |
134 | return (
135 | <>
136 |
137 |
150 |
151 |
156 |
157 |
Hack OC - Orange County's high school hackathon
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
222 |
223 | {/*
231 |
239 | Progress updates from Hack OC
242 |
243 |
252 | Hacekrs were given 12 hours to build something cool. Here's what they made!
253 |
254 |
255 |
256 | */}
257 |
258 | {/* UNCOMMENT AFTER EVENT!!!!! */}
259 |
260 | {/* 3 columns on large screens, 2 on medium, 1 on small */}
261 |
262 | {(() => {
263 | if (loading) {
264 | return (
265 |
266 |
267 | Loading...
268 |
269 |
270 | )
271 | } else {
272 | return (
273 |
286 |
287 | {posts.map((post, index) => {
288 | return (
289 |
290 | )
291 | })}
292 |
293 |
294 | )
295 | }
296 |
297 | })()}
298 |
346 |
347 |
348 |
359 |
360 | Hack OC is fiscally sponsored by The Hack Foundation.
361 | Nonprofit EIN: 81-2908499.
362 |
363 |
380 |
381 |
382 | >
383 | )
384 | }
385 |
--------------------------------------------------------------------------------
/pages/ships.js:
--------------------------------------------------------------------------------
1 | import Masonry from 'react-masonry-css'
2 |
3 | import Head from 'next/head'
4 | import styles from '../styles/Home.module.css'
5 | import Icon from '@hackclub/icons'
6 | import Modal from '../components/Modal'
7 | import { useEffect, useRef, useState } from 'react';
8 | import splitbee from '@splitbee/web';
9 | import with$, { withNodrag } from '../utils/cssUtils';
10 | import useMedia, { useViewport } from '../utils/useMedia';
11 | import Sidebar from '../components/Sidebar';
12 |
13 | const meta_desc = "Orange County's first high school coding event since the pandemic. Join us for 12 hours of hacking, workshops, & friendship.";
14 | const theme_color = '#FA7B33';
15 | const social_image = '/social.png';
16 |
17 | const regex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
18 |
19 | // const timelapseId = "9x00RCb1N7WTpAl6cIN0000Kult00vyzslROW6A1RblWwxM"
20 |
21 | const timelapseId = "402YMZJfp6kW02302E3r1RMe013Ub9AqlPwzr4VjD00HO7ME"
22 |
23 | export function usePosts () {
24 | const [posts, setPosts] = useState(null);
25 |
26 | useEffect(() => {
27 | fetch('/api/ships').then(res => res.json()).then(posts => {
28 | setPosts(posts);
29 | });
30 | }, []);
31 |
32 | return [posts ?? [], posts == null];
33 | }
34 |
35 | export function BaseCard ({ children, style }) {
36 | return (
37 |
43 | {children}
44 |
45 | )
46 | }
47 |
48 | export function Update ({ post, id }) {
49 | const [overwrite, setOverwrite] = useState(false);
50 | const ref = useRef();
51 | useEffect(() => {
52 | if (post.shortId == id) {
53 | ref.current.scrollIntoView({ behavior: 'smooth' });
54 | setTimeout(() => {
55 | setOverwrite(true);
56 | }, 100);
57 | }
58 | }, [post.shortId, id])
59 |
60 | return (
61 |
62 |
74 |
80 |
81 |
87 | {post.name ?? 'Unknown Author'}
94 |
95 |
96 | {post.message}
97 |
98 |
99 |
107 |
108 |
113 |
{new Date(parseInt(post._id.substring(0, 8), 16) * 1000).toLocaleString('en-US', {weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true})}
114 |
115 |
116 |
117 | )
118 | }
119 |
120 | export default function Home() {
121 | const [modal, setModal] = useState(false);
122 | const [posts, loading] = usePosts();
123 | const [id, setId] = useState(null);
124 |
125 | useEffect(() => {
126 | const { pathname } = window.location;
127 | if (pathname.startsWith('/s/')) {
128 | setId(pathname.substring(3));
129 | }
130 | }, []);
131 |
132 |
133 | const { width, height } = useMedia();
134 |
135 | return (
136 | <>
137 |
138 |
151 |
152 |
157 |
158 |
Hack OC - Orange County's high school hackathon
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
223 |
224 | {/*
232 |
240 | Progress updates from Hack OC
243 |
244 |
253 | Hacekrs were given 12 hours to build something cool. Here's what they made!
254 |
255 |
256 |
257 | */}
258 |
259 | {/* UNCOMMENT AFTER EVENT!!!!! */}
260 |
261 | {/* 3 columns on large screens, 2 on medium, 1 on small */}
262 |
263 | {(() => {
264 | if (loading) {
265 | return (
266 |
267 |
268 | Loading...
269 |
270 |
271 | )
272 | } else {
273 | return (
274 |
287 |
288 | {posts.map((post, index) => {
289 | return (
290 |
291 | )
292 | })}
293 |
294 |
295 | )
296 | }
297 |
298 | })()}
299 |
347 |
348 |
349 |
360 |
361 | Hack OC is fiscally sponsored by The Hack Foundation.
362 | Nonprofit EIN: 81-2908499.
363 |
364 |
381 |
382 |
383 | >
384 | )
385 | }
386 |
--------------------------------------------------------------------------------
/public/favicon-old-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/favicon-old-2.png
--------------------------------------------------------------------------------
/public/favicon-old.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/favicon-old.png
--------------------------------------------------------------------------------
/public/favicon-vercel.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/favicon-vercel.ico
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/favicon.png
--------------------------------------------------------------------------------
/public/flyer2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/flyer2.pdf
--------------------------------------------------------------------------------
/public/items/PressRelease-03-13-2023.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/items/PressRelease-03-13-2023.pdf
--------------------------------------------------------------------------------
/public/logo-full-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/logo-full-light.png
--------------------------------------------------------------------------------
/public/map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/map.png
--------------------------------------------------------------------------------
/public/orange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/orange.png
--------------------------------------------------------------------------------
/public/orange.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/press/1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/press/1.pdf
--------------------------------------------------------------------------------
/public/prospectus.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/prospectus.pdf
--------------------------------------------------------------------------------
/public/social.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/social.png
--------------------------------------------------------------------------------
/public/sponsor-assets/bank.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/sponsor-assets/vercel.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/public/thumbnail.png
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/public/white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/replit.nix:
--------------------------------------------------------------------------------
1 | { pkgs }: {
2 | deps = [
3 | pkgs.nodejs-18_x
4 | pkgs.nodePackages.typescript-language-server
5 | pkgs.yarn
6 | pkgs.replitPackages.jest
7 | ];
8 | }
--------------------------------------------------------------------------------
/replit.sh:
--------------------------------------------------------------------------------
1 | yarn
2 | yarn dev
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | }
3 |
4 | .main {
5 | min-height: 100vh;
6 | }
7 |
8 | .content {
9 | width: 100%;
10 | padding: 5rem 2rem;
11 | font-size: 24px;
12 | }
13 |
14 | .content h2, .content a {
15 | color: var(--orange);
16 | }
17 |
18 | .content a {
19 | text-decoration: underline;
20 | }
21 |
22 | @media (min-width: 600px) {
23 | .content {
24 | padding: 5rem 5rem;
25 | }
26 | .prizes {
27 | padding: 15px 50px;
28 | }
29 | .prizes h1 {
30 | font-size: 50px!important;
31 | }
32 | .prizes h2 {
33 | font-size: 24px!important;
34 | }
35 | }
36 |
37 | @media (min-width: 800px) {
38 | .content {
39 | padding: 2rem 8rem;
40 | }
41 | .prizes {
42 | padding: 20px 70px;
43 | }
44 | .prizes > h1 {
45 | font-size: 60px!important;
46 | }
47 | }
48 |
49 | .prizes {
50 | padding: 10px 30px;
51 | }
52 | .prizes h1 {
53 | font-size: 40px;
54 | }
55 | .prizes h2 {
56 | font-size: 20px;
57 | font-weight: 400;
58 | color: #19191c;
59 | }
60 |
61 | .footer {
62 | display: flex;
63 | flex: 1;
64 | padding: 2rem 0;
65 | border-top: 1px solid #eaeaea;
66 | justify-content: center;
67 | align-items: center;
68 | }
69 |
70 | .footer a {
71 | display: flex;
72 | justify-content: center;
73 | align-items: center;
74 | flex-grow: 1;
75 | }
76 |
77 | .title a {
78 | color: #0070f3;
79 | text-decoration: none;
80 | }
81 |
82 | .title a:hover,
83 | .title a:focus,
84 | .title a:active {
85 | text-decoration: underline;
86 | }
87 |
88 | .title {
89 | margin: 0;
90 | line-height: 1.15;
91 | font-size: 4rem;
92 | }
93 |
94 | .title,
95 | .description {
96 | text-align: center;
97 | }
98 |
99 | .description {
100 | margin: 1.2rem 0;
101 | line-height: 1.5;
102 | font-size: 1.5rem;
103 | }
104 |
105 | .code {
106 | background: #fafafa;
107 | border-radius: 5px;
108 | padding: 0.75rem;
109 | font-size: 1.1rem;
110 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
111 | Bitstream Vera Sans Mono, Courier New, monospace;
112 | }
113 |
114 | .content {
115 | overflow: hidden;
116 | }
117 |
118 | @media (min-width: 800px) {
119 | .innerContent {
120 | max-width: 60vw;
121 | }
122 | .title {
123 | transform: scale(160%);
124 | }
125 | }
126 |
127 | .innerContent span {
128 | font-weight: bold;
129 | }
130 |
131 | .innerContent span:nth-child(3n+1) {
132 | color: rgb(196, 59, 91);
133 | }
134 |
135 | .innerContent span:nth-child(3n+2) {
136 | color: rgb(28, 155, 94);
137 | }
138 |
139 | .innerContent h2 {
140 | margin-block-start: 0.37em;
141 | margin-block-end: 0.37em;
142 | }
143 |
144 | .innerContent span:nth-child(3n+3) {
145 | color: rgb(45, 74, 136);
146 | }
147 |
148 | @media (min-width: 1200px) {
149 | .main {
150 | width: calc(100vw - 350px);
151 | }
152 |
153 | .bottomFooter {
154 | width: calc(100vw - 350px);
155 | }
156 |
157 | .sponsors {
158 | position: fixed;
159 | top: 0px;
160 | right: 0px;
161 | width: 335px;
162 | height: 100%;
163 | background: black;
164 | color: white;
165 | padding: 30px;
166 | z-index: 24;
167 | }
168 | }
169 |
170 | @media (max-width: 400px) {
171 | .description {
172 | margin-top: 0.2rem;
173 | font-size: 1.2rem;
174 | }
175 | .inputCenter {
176 | margin-top: 3.4rem!important;
177 | }
178 | }
179 |
180 | @media (max-width: 1199px) {
181 | .sponsors {
182 | background: black;
183 | color: white;
184 | padding: 30px;
185 | }
186 | }
187 |
188 | .grid {
189 | display: flex;
190 | align-items: center;
191 | justify-content: center;
192 | flex-wrap: wrap;
193 | max-width: 800px;
194 | }
195 |
196 | .card {
197 | margin: 1rem;
198 | padding: 1.5rem;
199 | text-align: left;
200 | color: inherit;
201 | text-decoration: none;
202 | border: 1px solid #eaeaea;
203 | border-radius: 10px;
204 | transition: color 0.15s ease, border-color 0.15s ease;
205 | max-width: 300px;
206 | }
207 |
208 | .card:hover,
209 | .card:focus,
210 | .card:active {
211 | color: #0070f3;
212 | border-color: #0070f3;
213 | }
214 |
215 | .card h2 {
216 | margin: 0 0 1rem 0;
217 | font-size: 1.5rem;
218 | }
219 |
220 | .card p {
221 | margin: 0;
222 | font-size: 1.25rem;
223 | line-height: 1.5;
224 | }
225 |
226 | .logo {
227 | height: 1em;
228 | margin-left: 0.5rem;
229 | }
230 |
231 | @media (max-width: 600px) {
232 | .grid {
233 | width: 100%;
234 | flex-direction: column;
235 | }
236 | }
237 |
238 | @media (prefers-color-scheme: dark) {
239 | .card,
240 | .footer {
241 | border-color: #222;
242 | }
243 | .code {
244 | background: #111;
245 | }
246 | .logo img {
247 | filter: invert(1);
248 | }
249 | }
250 |
251 | .button {
252 | width: 100%;
253 | padding: 10px;
254 | font-size: 24px;
255 | border-radius: 8px;
256 | background-color: var(--orange);
257 | border: none;
258 | color: white;
259 | cursor: pointer;
260 | font-family: var(--font-stack);
261 | margin-bottom: 16px;
262 | transition: 0.3s all;
263 | }
264 |
265 | .button:hover {
266 | transform: translateY(-4px);
267 | }
268 |
269 | .altButton {
270 | width: 100%;
271 | padding: 10px;
272 | font-size: 24px;
273 | border-radius: 8px;
274 | background: black;
275 | margin-bottom: 16px;
276 | border: 2px solid var(--orange);
277 | color: white;
278 | cursor: pointer;
279 | font-family: var(--font-stack);
280 | transition: 0.3s transform, 0.08s box-shadow;
281 | }
282 |
283 | .altButton:hover {
284 | transform: translateY(-4px);
285 | }
286 |
287 | .altButton:active {
288 | box-shadow: 0 0 0 3px rgba(var(--orange-3-values), 0.5);
289 | }
290 |
291 | .input {
292 |
293 | width: 100%;
294 | padding: 10px;
295 | font-size: 24px;
296 | border-radius: 8px;
297 | background-color: var(--orange);
298 | border: none;
299 | color: white;
300 | cursor: pointer;
301 | font-family: var(--font-stack);
302 | margin-bottom: 16px;
303 | transition: 0.3s all;
304 | outline: none;
305 | border: 2px solid var(--orange)
306 | }
307 |
308 | .input:focus, .input:focus-within {
309 | border: 2px solid white;
310 | }
311 |
312 | .button:hover {
313 | box-shadow: 0px 2px 20px #00000099;
314 | }
315 |
316 | .scaleHover:hover, .sponsor:hover {
317 | transform: scale(1.07);
318 | }
319 |
320 | .sponsor:not(.sponsor-special) {
321 | transition: 0.2s all;
322 | margin-bottom: 1rem;
323 | margin-right: 1rem;
324 | max-width: 256px;
325 | }
--------------------------------------------------------------------------------
/styles/Register.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | }
3 |
4 | .main {
5 | padding-top: 4rem;
6 | min-height: 100vh;
7 | }
8 |
9 | .content {
10 | width: 100%;
11 | padding: 5rem 2rem;
12 | font-size: 24px;
13 | }
14 |
15 | .content h2, .content a {
16 | color: var(--orange);
17 | }
18 |
19 | .content a {
20 | text-decoration: underline;
21 | }
22 |
23 | @media (min-width: 600px) {
24 | .content {
25 | padding: 5rem 5rem;
26 | }
27 | }
28 |
29 | @media (min-width: 800px) {
30 | .content {
31 | padding: 2rem 10rem;
32 | }
33 | }
34 |
35 | .footer {
36 | display: flex;
37 | flex: 1;
38 | padding: 2rem 0;
39 | border-top: 1px solid #eaeaea;
40 | justify-content: center;
41 | align-items: center;
42 | }
43 |
44 | .footer a {
45 | display: flex;
46 | justify-content: center;
47 | align-items: center;
48 | flex-grow: 1;
49 | }
50 |
51 | .title a {
52 | color: #0070f3;
53 | text-decoration: none;
54 | }
55 |
56 | .title a:hover,
57 | .title a:focus,
58 | .title a:active {
59 | text-decoration: underline;
60 | }
61 |
62 | .title {
63 | margin: 0;
64 | line-height: 1.15;
65 | font-size: 2rem;
66 | font-weight: 600;
67 | }
68 |
69 | .title,
70 | .description {
71 | text-align: center;
72 | }
73 |
74 | .description {
75 | margin: 1.2rem 0;
76 | line-height: 1.5;
77 | font-size: 1.5rem;
78 | }
79 |
80 | .code {
81 | background: #fafafa;
82 | border-radius: 5px;
83 | padding: 0.75rem;
84 | font-size: 1.1rem;
85 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
86 | Bitstream Vera Sans Mono, Courier New, monospace;
87 | }
88 |
89 | .content {
90 | overflow: hidden;
91 | }
92 |
93 | @media (min-width: 800px) {
94 | .innerContent {
95 | max-width: 60vw;
96 | }
97 | .title {
98 | transform: scale(160%);
99 | }
100 | }
101 |
102 | .innerContent span {
103 | font-weight: bold;
104 | }
105 |
106 | .innerContent span:nth-child(3n+1) {
107 | color: rgb(196, 59, 91);
108 | }
109 |
110 | .innerContent span:nth-child(3n+2) {
111 | color: rgb(28, 155, 94);
112 | }
113 |
114 | .innerContent h2 {
115 | margin-block-start: 0.37em;
116 | margin-block-end: 0.37em;
117 | }
118 |
119 | .innerContent span:nth-child(3n+3) {
120 | color: rgb(45, 74, 136);
121 | }
122 |
123 | @media (min-width: 1200px) {
124 | .main {
125 | width: calc(100vw);
126 | } .sponsors {
127 | position: fixed;
128 | top: 0px;
129 | right: 0px;
130 | width: 350px;
131 | height: 100%;
132 | background: black;
133 | color: white;
134 | padding: 30px;
135 | z-index: 24;
136 | }
137 | }
138 |
139 | @media (max-width: 400px) {
140 | .description {
141 | margin-top: 0.2rem;
142 | font-size: 1.2rem;
143 | }
144 | .inputCenter {
145 | margin-top: 3.4rem!important;
146 | }
147 | }
148 |
149 | @media (max-width: 1199px) {
150 | .sponsors {
151 | background: black;
152 | color: white;
153 | padding: 30px;
154 | }
155 | }
156 | .sponsors {
157 |
158 | }
159 |
160 | .grid {
161 | display: flex;
162 | align-items: center;
163 | justify-content: center;
164 | flex-wrap: wrap;
165 | max-width: 800px;
166 | }
167 |
168 | .card {
169 | margin: 1rem;
170 | padding: 1.5rem;
171 | text-align: left;
172 | color: inherit;
173 | text-decoration: none;
174 | border: 1px solid #eaeaea;
175 | border-radius: 10px;
176 | transition: color 0.15s ease, border-color 0.15s ease;
177 | max-width: 300px;
178 | }
179 |
180 | .card:hover,
181 | .card:focus,
182 | .card:active {
183 | color: #0070f3;
184 | border-color: #0070f3;
185 | }
186 |
187 | .card h2 {
188 | margin: 0 0 1rem 0;
189 | font-size: 1.5rem;
190 | }
191 |
192 | .card p {
193 | margin: 0;
194 | font-size: 1.25rem;
195 | line-height: 1.5;
196 | }
197 |
198 | .logo {
199 | height: 1em;
200 | margin-left: 0.5rem;
201 | }
202 |
203 | @media (max-width: 600px) {
204 | .grid {
205 | width: 100%;
206 | flex-direction: column;
207 | }
208 | }
209 |
210 | @media (prefers-color-scheme: dark) {
211 | .card,
212 | .footer {
213 | border-color: #222;
214 | }
215 | .code {
216 | background: #111;
217 | }
218 | .logo img {
219 | filter: invert(1);
220 | }
221 | }
222 |
223 | .button {
224 | width: 100%;
225 | padding: 10px;
226 | font-size: 24px;
227 | border-radius: 8px;
228 | background-color: var(--orange);
229 | border: none;
230 | color: white;
231 | cursor: pointer;
232 | font-family: var(--font-stack);
233 | margin-bottom: 16px;
234 | transition: 0.3s all;
235 | }
236 |
237 | .button:hover {
238 | transform: translateY(-4px);
239 | }
240 |
241 | .altButton {
242 | width: 100%;
243 | padding: 10px;
244 | font-size: 24px;
245 | border-radius: 8px;
246 | background: black;
247 | margin-bottom: 16px;
248 | border: 2px solid var(--orange);
249 | color: white;
250 | cursor: pointer;
251 | font-family: var(--font-stack);
252 | transition: 0.3s all;
253 | }
254 |
255 | .altButton:hover {
256 | transform: translateY(-4px);
257 | }
258 |
259 | .input {
260 |
261 | width: 100%;
262 | padding: 10px;
263 | font-size: 24px;
264 | border-radius: 8px;
265 | background-color: var(--orange);
266 | border: none;
267 | color: white;
268 | cursor: pointer;
269 | font-family: var(--font-stack);
270 | margin-bottom: 16px;
271 | transition: 0.3s all;
272 | outline: none;
273 | border: 2px solid var(--orange)
274 | }
275 |
276 | .input:focus, .input:focus-within {
277 | border: 2px solid white;
278 | }
279 |
280 | .button:hover {
281 | box-shadow: 0px 2px 20px #00000099;
282 | }
283 |
284 | .scaleHover:hover, .sponsor:hover {
285 | transform: scale(1.07);
286 | }
287 |
288 | .sponsor:not(.sponsor-special) {
289 | transition: 0.2s all;
290 | margin-bottom: 1rem;
291 | margin-right: 1rem;
292 | max-width: 256px;
293 | }
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | * {
2 | --orange: #fa7b33;
3 | --orange-3-values: 250, 123, 51;
4 | --font-stack: 'Readex Pro', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
5 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
6 | }
7 |
8 | html,
9 | body {
10 | padding: 0;
11 | margin: 0;
12 | font-family: var(--font-stack);
13 | scroll-behavior: smooth;
14 | }
15 |
16 | a {
17 | color: inherit;
18 | text-decoration: none;
19 | }
20 |
21 | * {
22 | box-sizing: border-box;
23 | }
24 |
25 | .noselect {
26 | -webkit-touch-callout: none; /* iOS Safari */
27 | -webkit-user-select: none; /* Safari */
28 | -khtml-user-select: none; /* Konqueror HTML */
29 | -moz-user-select: none; /* Old versions of Firefox */
30 | -ms-user-select: none; /* Internet Explorer/Edge */
31 | user-select: none; /* Non-prefixed version, currently
32 | supported by Chrome, Edge, Opera and Firefox */
33 | }
34 |
35 | .color-orange {
36 | color: #FA7B33;
37 | }
38 |
39 | .color-charcoal {
40 | color: #222;
41 | }
42 |
43 | .background-charcoal {
44 | background: rgba(34, 34, 34, 0.8);
45 | }
46 |
47 | .color-white {
48 | color: white;
49 | }
50 |
51 | .tooltipped:after {
52 | opacity: 0;
53 | color: white;
54 | letter-spacing: 0;
55 | line-height: 1.375;
56 | transform: translateY(50%);
57 | box-shadow: 0 0 2px 0 #00000010,
58 | 0 4px 8px 0 #00000020;
59 | content: attr(aria-label);
60 | z-index: 10;
61 | left: 100%;
62 | bottom: 50%;
63 | min-height: 20px;
64 | position: absolute;
65 | padding: 0.25rem 0.75rem;
66 | background-color: #000000df;
67 | border-radius: 6px;
68 | font-family: Inter;
69 | font-size: 14px;
70 | max-width: 240px;
71 | font-weight: 500;
72 | transition: 0.15s all ease;
73 | width: max-content;
74 | height: min-content;
75 | right: 0;
76 | pointer-events: none;
77 | word-wrap: break-word;
78 | text-align: center;
79 | margin-left: 0.5rem;
80 | }
81 |
82 | .tooltipped:hover:after {
83 | outline: none;
84 | z-index: 9;
85 | opacity: 1;
86 | }
87 |
--------------------------------------------------------------------------------
/utils/cssUtils.js:
--------------------------------------------------------------------------------
1 | export const styles = {
2 | noselect: {
3 | userSelect: 'none',
4 | MozUserSelect: 'none',
5 | WebkitUserSelect: 'none',
6 | msUserSelect: 'none'
7 | },
8 |
9 | nodrag: {
10 | userDrag: 'none',
11 | MozUserDrag: 'none',
12 | WebkitUserDrag: 'none',
13 | msUserDrag: 'none'
14 | }
15 | };
16 |
17 | export const noselect = styles.noselect;
18 | export const nodrag = styles.nodrag;
19 |
20 | export function with$ (...args) {
21 | let last = args[args.length - 1];
22 |
23 | for (let i = 0; i < args.length - 1; i++) {
24 | const arg = args[i];
25 | if (typeof arg === 'string') {
26 | last = {
27 | ...last,
28 | ...styles[arg]
29 | };
30 | } else {
31 | last = {
32 | ...last,
33 | ...arg
34 | };
35 | }
36 | }
37 |
38 | return last;
39 | }
40 |
41 | export function withNoselect (styles) {
42 | return with$('noselect', styles);
43 | }
44 |
45 | export function withNodrag (styles) {
46 | return with$('nodrag', styles);
47 | }
48 |
49 | export default with$;
--------------------------------------------------------------------------------
/utils/useMedia.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | export function useViewport () {
4 | const [viewport, setViewport] = useState({
5 | width: 0,
6 | height: 0
7 | });
8 |
9 | useEffect(() => {
10 | function handleResize () {
11 | setViewport({
12 | width: window.innerWidth,
13 | height: window.innerHeight
14 | });
15 | }
16 |
17 | window.addEventListener('resize', handleResize);
18 | handleResize();
19 |
20 | return () => window.removeEventListener('resize', handleResize);
21 | }, []);
22 |
23 | return viewport;
24 | }
25 |
26 | export function breakpoint$ (one, breakpoint, two, amount) {
27 | if (amount < breakpoint) return one;
28 | else return two;
29 | }
30 |
31 | export function useMedia () {
32 | const viewport = useViewport();
33 |
34 | function width (one, breakpoint, two) {
35 | return breakpoint$(one, breakpoint, two, viewport.width);
36 | }
37 |
38 | function height (one, breakpoint, two) {
39 | return breakpoint$(one, breakpoint, two, viewport.height);
40 | }
41 |
42 | return { width, height, viewport };
43 | }
44 |
45 | export default useMedia;
--------------------------------------------------------------------------------
/utils/useProtection.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 |
3 | // view https://www.youtube.com/watch?v=pZwvrxVavnQ for additional context, consent before exiting is important
4 | export function useProtection (showWarning = true) {
5 | return useEffect(() => {
6 | window.onbeforeunload = () => showWarning;
7 | }, []);
8 | }
9 |
10 | export default useProtection;
11 |
--------------------------------------------------------------------------------