Merge branch 'main' into slack

This commit is contained in:
Toby Brown 2024-03-05 18:11:53 +00:00 committed by GitHub
commit d07109519f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
116 changed files with 2269 additions and 3758 deletions

View file

@ -1,6 +1,7 @@
import { Card, Text } from 'theme-ui'
import { Card, Text, Box } from 'theme-ui'
import { keyframes } from '@emotion/react'
import Icon from './icon'
import Image from 'next/image'
const unfold = keyframes({
from: { transform: 'scaleY(0)' },
@ -12,6 +13,8 @@ const Announcement = ({
copy,
iconLeft,
iconRight,
imgSrc,
imgAlt,
color = 'accent',
sx = {},
...props
@ -48,6 +51,16 @@ const Announcement = ({
sx={{ mr: [2, 3], ml: 2, color, display: ['none', 'block'] }}
/>
)}
{imgSrc && (
<Box sx={{ mr: [2,3], width: 32, flexShrink: 0 }}>
<Image
src={imgSrc}
alt={imgAlt}
width={32}
height={32}
/>
</Box>
)}
<Text
as="p"
sx={{ flex: '1 1 auto', strong: { display: ['inline', 'block'] } }}

View file

@ -11,7 +11,7 @@ It was a huge honor last month to have Elon [spend an hour in an ask-me-anything
When hackers see problems in the world, we dont blame someone else: we try to take them on to solve. Elon is very selective about the nonprofits he supports and Im proud Hack&nbsp;Club is one of them.
So…how will Hack&nbsp;Club invest $500,000? We want to use this to help 1,000 more students start and join Hack Clubs in their towns ([see the worldwide map](https://hackclub.com/map/)). For those already in Hack&nbsp;Club, we look to you to help us make a higher-quality experience. We plan to continue much of what were already doing (and [what I wrote about in January](https://zachinto2020.wordpress.com/2019/12/31/as-midnight-approaches/)): spending as little money as possible at all times, growing slowly, adding diverse staff to make Hack&nbsp;Club better (video game designers, software engineers, media producers, and more). We are pushing hard to try and make the [Hack&nbsp;Club Slack](https://hackclub.com/) the best place to be a teenager on the internet and expanding [HCB](https://hackclub.com/hcb/).
So…how will Hack&nbsp;Club invest $500,000? We want to use this to help 1,000 more students start and join Hack Clubs in their towns ([see the worldwide map](https://hackclub.com/map/)). For those already in Hack&nbsp;Club, we look to you to help us make a higher-quality experience. We plan to continue much of what were already doing (and [what I wrote about in January](https://zachinto2020.wordpress.com/2019/12/31/as-midnight-approaches/)): spending as little money as possible at all times, growing slowly, adding diverse staff to make Hack&nbsp;Club better (video game designers, software engineers, media producers, and more). We are pushing hard to try and make the [Hack&nbsp;Club Slack](https://hackclub.com/) the best place to be a teenager on the internet and expanding [HCB](https://hackclub.com/fiscal-sponsorship/).
Well be fully transparent in how we spend this money. One thing weve been working toward after winning the [Frank Grant](https://grant.frank.ly/) is open sourcing our finances. Hack&nbsp;Club HQ has been running on HCB since February, and starting today, [**you can see our finances publicly**](https://hcb.hackclub.com/hq). Through HCB, you can track how we spend every dollar of Elons gift. Soon, well also launch [Franks](https://frank.ly/) transparency tools on [hackclub.com](https://hackclub.com/).

View file

@ -4,7 +4,7 @@ In 2014, Hack Club was founded, and Tom joined as Hack Clubs first board memb
Tom and Theresa also helped fund [The Hacker Zephyr](https://hack.af/zephyrdoc), an epic, cross-country train hackathon taken by 42 teen hackers in the summer of 2021. Tom even hacked alongside Hack Clubbers onboard.
With this gift, we will continue to build the engineering team at Hack Club, including a Tech Lead for [HCB](https://hackclub.com/hcb), and new engineers to support clubs, the Hack Club online community, and events.
With this gift, we will continue to build the engineering team at Hack Club, including a Tech Lead for [HCB](https://hackclub.com/fiscal-sponsorship), and new engineers to support clubs, the Hack Club online community, and events.
One of our goals in 2022 is to improve Hack Club and to support more teenagers in joining the community. Thank you Tom and Theresa for helping make this possible.

View file

@ -10,7 +10,7 @@ Today, we're excited to announce Elon is donating $1 million to Hack Club.
This gift will help launch a number of ideas we've been discussing, including helping more in-person hackathons get off the ground, providing more direct 1:1 technical support on the [Hack Club Slack](https://hackclub.com/slack/), and starting up cool new projects like [The Hacker Zephyr](https://github.com/hackclub/the-hacker-zephyr). We also want to use his gift to help 1,000 more teenagers start and join Hack Clubs in their towns.
We will be spending every dollar as wisely as possible, growing thoughtfully, and adding diverse staff to make Hack Club better. We are pushing hard to try and make the Hack Club Slack the best place to be a teenager on the internet and expanding [HCB](https://hackclub.com/hcb/).
We will be spending every dollar as wisely as possible, growing thoughtfully, and adding diverse staff to make Hack Club better. We are pushing hard to try and make the Hack Club Slack the best place to be a teenager on the internet and expanding [HCB](https://hackclub.com/fiscal-sponsorship/).
Elon is very selective about the nonprofits he supports and we're proud Hack Club is one of them.

View file

@ -41,10 +41,7 @@ export default function Bio({ popup = true, spanTwo = false, ...props }) {
width={64}
height={64}
mr={3}
src={
img ||
require(`../public/team/${name.split(' ')[0].toLowerCase()}.jpg`)
}
src={img}
alt={name}
sx={{
overflow: 'hidden',

View file

@ -27,7 +27,7 @@ a lot of what weve already been doing (and [what I wrote about at the beginni
of the year](https://zachinto2020.wordpress.com/2019/12/31/as-midnight-approaches/)):
well spend as little money as possible at all times, and well hire a small
number of diverse staff from video game engineers to media producers to make
Hack Club better. We are pushing hard now to expand users of [HCB](https://hackclub.com/hcb/),
Hack Club better. We are pushing hard now to expand users of [HCB](https://hackclub.com/fiscal-sponsorship/),
and continuing to try and make the Hack Club Slack the best place to be a teenager on the intenet.
Well have a proper announcement in a few weeks, but one thing were doing after

View file

@ -0,0 +1,80 @@
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { Flex, Label, Text } from 'theme-ui'
export default function Field({
name,
label,
description,
col = true,
requiredFields,
children
}) {
const router = useRouter()
const isRequired = requiredFields.includes(name)
/* Fill in the field input element with the value from sessionStorage.
Note: the custom checkbox component does this in its own useEffect hook. */
useEffect(() => {
const value =
router.query[name] || sessionStorage.getItem('bank-signup-' + name)
if (value) {
const input = document.getElementById(name)
if (input) input.value = value
}
}, [router.query, name])
return (
<Flex
aria-required={isRequired}
sx={{
flexDirection: col ? 'column' : 'row',
alignItems: col ? 'flex-start' : 'center',
gap: 1,
width: '100%',
// Wrapper around Select
'> div': {
width: '100%'
},
'input, select, textarea': {
border: '1px solid',
borderColor: 'smoke',
outlineColor: 'blue',
'&:-webkit-autofill': {
boxShadow: '0 0 0 100px white inset !important',
WebkitTextFillColor: 'black !important'
}
}
}}
>
<Label
htmlFor={name}
sx={{
fontSize: 2,
flexDirection: 'row'
}}
>
{label}
{isRequired && (
<Text
as="span"
sx={{
color: 'red',
fontWeight: 'bold',
ml: 1
}}
title="Required"
>
*
</Text>
)}
</Label>
{children}
{description && (
<Text as="p" variant="caption">
{description}
</Text>
)}
</Flex>
)
}

View file

@ -0,0 +1,39 @@
import { forwardRef } from 'react'
import { Box, Container } from 'theme-ui'
const formContainer = forwardRef(({ children, ...props }, ref) => {
return (
<Box
ref={ref}
as="form"
sx={{
bg: 'snow',
px: [3, 5],
py: 5,
minHeight: '100dvb',
'&.has-errors div[aria-required="true"] input:placeholder-shown': {
borderColor: 'primary'
}
}}
{...props}
>
<Container
variant="copy"
sx={{
ml: 0,
display: 'flex',
flexDirection: 'column',
columnGap: 4,
rowGap: 3,
px: 0
}}
>
{children}
</Container>
</Box>
)
})
// https://stackoverflow.com/a/67993106/10652680
formContainer.displayName = 'formContainer'
export default formContainer

View file

@ -0,0 +1,66 @@
import { Box, Link, Heading } from 'theme-ui'
import Icon from '../../icon'
export default function HCBInfo() {
return (
<Box
sx={{
gridArea: 'info',
alignItems: 'start',
mark: { color: '#ec555c', bg: 'inherit' },
ul: { pl: [3, 0], color: 'muted', mb: 4 },
p: { color: 'muted', mb: 0 }
}}
>
<Heading variant="subheadline">
HCB is a{' '}
<Link
href="https://en.wikipedia.org/wiki/Fiscal_sponsorship"
target="_blank"
sx={{
display: 'inline-flex',
alignItems: 'flex-end',
gap: 1
}}
>
fiscal sponsor
<Icon glyph="external" size={24} aria-hidden />
</Link>
</Heading>
<ul>
<li>Nonprofit status.</li>
<li>Tax-deductable donations.</li>
</ul>
<Heading variant="subheadline">
HCB provides a financial platform.
</Heading>
<ul>
<li>A donations page and invoicing system.</li>
<li>Transfer money electronically.</li>
<li>Order cards for you and your team to make purchases.</li>
</ul>
<Heading variant="subheadline">HCB is not a bank.</Heading>
<ul>
<li>
We partner with{' '}
<Link href="https://column.com" target="_blank">
Column Bank
</Link>{' '}
to offer a bank account to fiscally-sponsored projects.
</li>
<li>
You can't deposit or withdraw cash. But you can receive any kind of
electronic payment!
</li>
</ul>
<Heading variant="subheadline">HCB is not for for-profits.</Heading>
<p>
If youre looking to set up a for-profit entity, consider{' '}
<Link href="https://stripe.com/atlas" target="_blank">
Stripe Atlas
</Link>
.
</p>
</Box>
)
}

View file

@ -1,15 +1,15 @@
import { useState, useEffect } from 'react'
import { Input, Textarea } from 'theme-ui'
import Checkbox from './checkbox'
import AddressInput from './address-input'
import { Input, Select, Textarea } from 'theme-ui'
// import Checkbox from './checkbox'
import Field from './field'
import AutofillColourFix from './autofill-colour-fix'
// This is using country-list instead of country-list-js as it has a smaller bundle size
import { getNames } from 'country-list'
export default function OrganizationInfoForm({ requiredFields }) {
const [org, setOrg] = useState('organization')
const [org, setOrg] = useState('Organization')
useEffect(() => {
if (navigator.language === 'en-GB') setOrg('organisation')
if (navigator.language === 'en-GB') setOrg('Organisation')
}, [])
return (
@ -23,7 +23,6 @@ export default function OrganizationInfoForm({ requiredFields }) {
name="eventName"
id="eventName"
placeholder="Shelburne School Hackathon"
sx={{ ...AutofillColourFix }}
/>
</Field>
<Field
@ -35,20 +34,27 @@ export default function OrganizationInfoForm({ requiredFields }) {
<Input
name="eventWebsite"
id="eventWebsite"
type="url"
inputMode="url"
placeholder="hackclub.com"
sx={{ ...AutofillColourFix }}
/>
</Field>
<Field
name="eventLocation"
label={`${org} location`}
description="If your organization runs online, put your own address."
label={`Primary country of operations`}
requiredFields={requiredFields}
>
<AddressInput isPersonalAddressInput={false} name="eventLocation" />
<Select name="eventLocation" id="eventLocation">
{getNames()
.sort()
.sort(item => (item === 'United States of America' ? -1 : 1))
.map(country => (
<option key={country} value={country}>
{country}
</option>
))}
</Select>
</Field>
<Field
{/* <Field
name="transparent"
label="Transparency mode"
col={false}
@ -61,11 +67,11 @@ export default function OrganizationInfoForm({ requiredFields }) {
requiredFields={requiredFields}
>
<Checkbox defaultChecked={true} name="transparent" />
</Field>
</Field> */}
<Field
name="eventDescription"
label={`Tell us about your ${org}!`}
description="1 or 2 sentences will suffice"
label={`Tell us about your ${org.toLowerCase()}`}
description="24 sentences will suffice."
requiredFields={requiredFields}
>
<Textarea
@ -73,9 +79,7 @@ export default function OrganizationInfoForm({ requiredFields }) {
id="eventDescription"
rows={3}
sx={{
resize: 'vertical',
width: '100%',
...AutofillColourFix
resize: 'vertical'
}}
/>
</Field>

View file

@ -1,18 +1,9 @@
import { Input, Flex, Label, Radio } from 'theme-ui'
import Checkbox from './checkbox'
import AddressInput from './address-input'
import { Input, Flex, Label, Radio, Grid, Select } from 'theme-ui'
import Field from './field'
import AutofillColourFix from './autofill-colour-fix'
import { useState } from 'react'
export default function PersonalInfoForm({
setValidationResult,
requiredFields
}) {
export default function PersonalInfoForm({ requiredFields }) {
const [selectedContactOption, setSelectedContactOption] = useState('Email')
const [email, setEmail] = useState(
window.sessionStorage.getItem('bank-signup-userEmail')
) // For display only, is not used for data submission.
return (
<>
@ -22,24 +13,14 @@ export default function PersonalInfoForm({
label="First name"
requiredFields={requiredFields}
>
<Input
name="firstName"
id="firstName"
placeholder="Fiona"
sx={{ ...AutofillColourFix }}
/>
<Input name="firstName" id="firstName" placeholder="Fiona" />
</Field>
<Field
name="lastName"
label="Last name"
requiredFields={requiredFields}
>
<Input
name="lastName"
id="lastName"
placeholder="Hacksworth"
sx={{ ...AutofillColourFix }}
/>
<Input name="lastName" id="lastName" placeholder="Hacksworth" />
</Field>
</Flex>
<Field name="userEmail" label="Email" requiredFields={requiredFields}>
@ -48,10 +29,78 @@ export default function PersonalInfoForm({
id="userEmail"
type="email"
placeholder="fiona@hackclub.com"
onInput={e => setEmail(e.target.value)}
sx={{ ...AutofillColourFix }}
/>
</Field>
<Field
name="contactOption"
label="Preferred contact channel"
requiredFields={requiredFields}
>
<Grid
columns={[null, 2]}
sx={{
rowGap: 2,
columnGap: 4,
width: '100%'
}}
>
<Label
sx={{
display: 'flex',
flexDirection: 'row'
}}
>
<Radio
name="contactOption"
value="Email"
defaultChecked={true}
onInput={() => setSelectedContactOption('Email')}
/>
Email
</Label>
<Grid
sx={{
columnGap: 0,
rowGap: 2,
gridTemplateColumns: 'auto 1fr'
}}
>
<Label
sx={{
display: 'contents',
'~ div > label': { fontSize: 1 }
}}
>
<Radio
name="contactOption"
value="Slack"
onInput={() => setSelectedContactOption('Slack')}
/>
Hack Club Slack
</Label>
{selectedContactOption === 'Slack' ? (
<>
<div />
<Field
label="Your Hack Club Slack username"
name="slackUsername"
requiredFields={requiredFields}
>
<Input
name="slackUsername"
id="slackUsername"
placeholder="FionaH"
autocomplete="off"
data-1p-ignore
autoFocus
/>
</Field>
</>
) : null}
</Grid>
</Grid>
</Field>
<Field
name="userPhone"
label="Phone"
@ -62,23 +111,31 @@ export default function PersonalInfoForm({
name="userPhone"
id="userPhone"
type="tel"
placeholder="(123) 456-7890"
sx={{ ...AutofillColourFix }}
placeholder="1-855-625-HACK"
/>
</Field>
<Field
name="userBirthday"
label="Birthday"
label="Birth year"
requiredFields={requiredFields}
>
<Input
name="userBirthday"
id="userBirthday"
type="date"
sx={{ ...AutofillColourFix }}
/>
<Select name="userBirthday" id="userBirthday" defaultValue="">
<option value="" disabled>
Select a year
</option>
{/* show a century of years starting from 13 years ago */}
{Array.from({ length: 98 }, (_, i) => {
const year = new Date().getFullYear() - 13 - i
return (
<option key={year} value={year}>
{year}
</option>
)
})}
</Select>
</Field>
<Field
{/* <Field
name="referredBy"
label="Who were you referred by?"
requiredFields={requiredFields}
@ -87,7 +144,6 @@ export default function PersonalInfoForm({
name="referredBy"
id="referredBy"
placeholder="Max"
sx={{ ...AutofillColourFix }}
/>
</Field>
<Field
@ -111,67 +167,17 @@ export default function PersonalInfoForm({
/>
</Field>
<Field
name="contactOption"
label="Preferred contact channel"
description="So we know where to message you about your application!"
requiredFields={requiredFields}
>
<Flex sx={{ gap: 4 }}>
<Label
sx={{
display: 'flex',
flexDirection: 'row'
}}
>
<Radio
name="contactOption"
value="Email"
defaultChecked={true}
onInput={() => setSelectedContactOption('Email')}
/>
Email
</Label>
<Label
sx={{
display: 'flex',
flexDirection: 'row'
}}
>
<Radio
name="contactOption"
value="Slack"
onInput={() => setSelectedContactOption('Slack')}
/>
Slack
</Label>
</Flex>
{selectedContactOption === 'Slack' ? (
<Field name="slackUsername" requiredFields={requiredFields}>
<Input
name="slackUsername"
id="slackUsername"
placeholder="Your name in the Hack Club Slack"
sx={{ ...AutofillColourFix }}
/>
</Field>
) : selectedContactOption === 'Email' ? (
<div>
We'll use {email ?? 'whatever you put for your email above!'}
</div>
) : null}
</Field>
*/}
<Field
name="accommodations"
label="Accessibility needs"
description="Please specify any accommodations, accessibility needs, or other important information so we can support you during onboarding and while using HCB"
description="Please specify any accommodations, accessibility needs, or other important information so we can support you during onboarding and while using HCB."
requiredFields={requiredFields}
>
<Input
name="accommodations"
id="accommodations"
placeholder="I use a screen reader/I need increased text size during onboarding"
sx={{ ...AutofillColourFix }}
/>
</Field>
</>

View file

@ -0,0 +1,65 @@
async function sendApplication() {
// Get the form data from sessionStorage
const data = {}
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i)
if (key.startsWith('bank-signup-')) {
data[key.replace('bank-signup-', '')] = sessionStorage.getItem(key)
}
}
console.dir('Sending data:', data)
// Send the data
try {
return fetch('/api/fiscal-sponsorship/apply', {
method: 'POST',
cors: 'no-cors',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
} catch (error) {
console.error(error)
}
}
export function onSubmit({
event,
router,
form,
requiredFields,
formError,
setFormError,
setIsSubmitting
}) {
event.preventDefault()
/* Don't return from inside the loop since
we want all input values to be saved every time */
let wasError = false
const formData = new FormData(form.current)
// Save form data
formData.forEach((value, key) => {
sessionStorage.setItem('bank-signup-' + key, value)
// Check if there are empty required fields.
if (
((!value || value.trim() === '') && requiredFields.includes(key)) ||
(formData.get('contactOption') === 'slack' &&
(!formData.get('slackUsername') != null ||
formData.get('slackUsername') === '')) // I'm so sorry for this
) {
setFormError('Please fill out all required fields.')
wasError = true
}
})
if (wasError) return
if (!formError) {
setIsSubmitting(true)
sendApplication().then(() => {
router.push('/fiscal-sponsorship/apply/success')
})
}
return
}

View file

@ -13,8 +13,8 @@ export default function Watermark() {
if (!shineRef.current || !svgRef.current) return
const svgWidth = svgRef.current.clientWidth / 100
const svgFromTop = svgRef.current.getBoundingClientRect().top
const svgFromLeft = svgRef.current.getBoundingClientRect().left
// const svgFromTop = svgRef.current.getBoundingClientRect().top
// const svgFromLeft = svgRef.current.getBoundingClientRect().left
shineRef.current.style.top = `${clientY / svgWidth + 6.2}px`
shineRef.current.style.left = `${clientX / svgWidth + 9.2}px`
@ -58,7 +58,7 @@ export default function Watermark() {
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: '#1d181f',
backgroundColor: 'snow',
clipPath: 'url(#my-clip-path)'
}}
>
@ -69,7 +69,7 @@ export default function Watermark() {
width: '2px',
height: '2px',
borderRadius: '50%',
backgroundColor: 'red',
backgroundColor: 'primary',
filter: 'blur(2px)'
}}
/>

View file

@ -1,6 +1,6 @@
import { Card, Badge as ThemeBadge, Box, Heading, Text, Image } from 'theme-ui'
import { Organization } from '../../../pages/hcb/climate'
import Tilt from '../../../components/tilt'
import { Organization } from '../../../pages/fiscal-sponsorship/climate'
import Tilt from '../../tilt'
import Icon from '@hackclub/icons'
import Tooltip from '../tooltip'

View file

@ -0,0 +1,129 @@
import { Box, Heading, Link, Text, Container, Grid } from 'theme-ui'
import Icon from '../icon'
import { Balancer } from 'react-wrap-balancer'
import Image from 'next/image'
import imgLaptop from '../../public/fiscal-sponsorship/laptop.png'
export default function Features() {
return (
<Box sx={{ pt: 5, pb: [5, 6], bg: 'snow' }}>
<Container>
<Heading as="h2" variant="title" sx={{ mb: 3, maxWidth: 'copyUltra' }}>
<Balancer>
Powerful financial tools built by our nonprofit, for yours.
</Balancer>
</Heading>
<Text as="p" variant="lead" sx={{ color: 'slate', maxWidth: '52ch' }}>
Unlike other fiscal sponsors, we dont license software from
for-profit entities. Since day one, weve built beautiful, self-serve
software to empower you to raise and spend money without
administrative hassle.
</Text>
<Grid columns={[null, 2, 3]} sx={{ mt: 4, rowGap: 3, columnGap: 4 }}>
<Module
icon="bank-account"
name="Receive foundation grants"
body="with tax-deductible 501(c)(3) status."
/>
{/* Send money & reimburse via check, ACH, bank wire, PayPal, & more.
Operate globally with a US Entity.
Issue physical & virtual debit cards to your team.
Get 24 hour support on weekdays.
Pay team members with built-in payroll.
Embed a custom donation form on your website.
We file all your taxes automatically, including form 990. " */}
<Module
icon="card"
name="Issue physical & virtual debit cards"
body="with receipt tracking & Apple Pay."
/>
<Module
icon="web"
name="Operate globally"
body="with a U.S. legal entity."
/>
<Module
icon="payment-transfer"
name="Send money & reimburse"
body="via check, ACH, bank wire, PayPal, & more."
/>
<Module
icon="explore"
name="Make your finances transparent"
body="to your team and optionally, public."
/>
<Module
icon="docs"
name="We file all your taxes"
body="automatically, including form 990."
/>
<Module
icon="admin"
name="Pay team members"
body="with built-in payroll."
/>
<Module
icon="support"
name="Accept donations of any size"
body="with a custom, embeddable online form."
/>
<Module
icon="leader"
name="Get 24-hour support"
body="on weekdays with a dedicated point of contact."
/>
</Grid>
</Container>
<Container variant="copy" sx={{ mt: [4, 5] }}>
<Laptop
href="https://hcb.hackclub.com/reboot"
title="See Reboots finances in public"
/>
</Container>
</Box>
)
}
function Module({ icon, name, body }) {
return (
<Box sx={{ display: 'flex', alignItems: 'start' }}>
<Icon
size={48}
glyph={icon}
sx={{ flexShrink: 0, marginRight: 3, color: 'primary' }}
/>
<Text
as="p"
sx={{
color: 'slate',
lineHeight: '1.375',
fontSize: 20,
m: 0
}}
>
<Balancer>
<Text as="strong" color="slate">
{name}
</Text>{' '}
{body}
</Balancer>
</Text>
</Box>
)
}
function Laptop({ href, title, sx }) {
return (
<Link href={href} title={title} sx={{ textAlign: 'center' }}>
<Image
src={imgLaptop}
alt="Laptop"
style={{ width: '100%', height: 'auto' }}
unoptimized
/>
<Text variant="caption" as="p" sx={{ color: 'primary', mt: 2 }}>
See <i>Reboot</i>s finances in Transparency Mode
</Text>
</Link>
)
}

View file

@ -1,10 +1,10 @@
import { Button, Text, Image, Flex } from 'theme-ui'
import Icon from '../icon'
import Icon from '../../icon'
import Link from 'next/link'
export default function ApplyButton() {
return (
<Link href="/hcb/apply" passHref legacyBehavior>
<Link href="/fiscal-sponsorship/apply" passHref legacyBehavior>
<Button
variant="ctaLg"
as="a"

View file

@ -10,7 +10,7 @@ export default function Features() {
<Box sx={{ py: 5 }}>
<Box as="a" href="#testimonials">
<Image
src="/hcb/meet-teams-using-hcb.svg"
src="/fiscal-sponsorship/meet-teams-using-hcb.svg"
alt="yeah"
width={200}
height={100}
@ -65,7 +65,7 @@ export default function Features() {
target="_blank"
>
<NextImage
src="/hcb/poseidon-dashboard.png"
src="/fiscal-sponsorship/poseidon-dashboard.png"
alt="iPad"
width={500}
height={300}
@ -245,11 +245,11 @@ export default function Features() {
</Flex>
</Card>
</Tilt> */}
<Module
{/* <Module
icon="rep"
name="No start-up costs"
body="All fees waived on your first $25k until September 1st, 2023. Then, just 7% of revenue (as compared to 10-14% charged by other fiscal sponsors). "
/>
/> */}
</Masonry>
</Container>
<Container

View file

@ -103,7 +103,7 @@ export default function Signup() {
} else {
setEventName('')
}
} catch (e) {}
} catch (e) { }
}, 200)
)
@ -119,7 +119,7 @@ export default function Signup() {
const handleSubmit = async e => {
e.preventDefault()
await fetch('/api/hcb/demo', {
await fetch('/api/fiscal-sponsorship/demo', {
method: 'POST',
body: JSON.stringify({
eventName,
@ -144,7 +144,7 @@ export default function Signup() {
<Base
id="form"
method="POST"
action="/api/hcb/demo"
action="/api/fiscal-sponsorship/demo"
onSubmit={handleSubmit}
>
<Grid sx={{ gridTemplateColumns: '1fr 2fr', alignItems: 'center' }}>

View file

@ -1,5 +1,4 @@
import { Box, Link, Text, Heading, Flex } from 'theme-ui'
import Timeline from './timeline'
import Stats from './stats'
import ApplyButton from './apply-button'
@ -26,7 +25,6 @@ export default function Start({ stats }) {
</Text>
</Flex>
<Stats stats={stats} />
<Timeline />
<Flex
sx={{ flexDirection: 'column', textAlign: 'center', gap: 4, mx: 3 }}
>

View file

@ -56,7 +56,7 @@ const Content = () => (
debit cards, a domain name, stickers, and more.`}
/>
</List>
<NextLink href="/hcb" passHref>
<NextLink href="/fiscal-sponsorship" passHref>
<Button as="a" variant="outlineLg" sx={{ width: [null, null, 500] }}>
Apply&nbsp;
<Box as="span" sx={{ display: ['none', 'inline', ''] }}>
@ -153,7 +153,7 @@ const Static = () => (
sx={{
position: 'relative',
overflow: 'hidden',
backgroundImage: `url('/hcb/bg.webp')`,
backgroundImage: `url('/fiscal-sponsorship/bg.webp')`,
backgroundSize: 'cover'
}}
>

View file

@ -1,179 +0,0 @@
import { useEffect, useRef, useState, useCallback } from 'react'
import { Box, Flex, Input, Text } from 'theme-ui'
import FlexCol from '../../flex-col'
import AutofillColourFix from './autofill-colour-fix'
import { geocode, search } from '../../../lib/hcb/apply/address-validation'
import Icon from '../../icon'
const approvedCountries = [
'AT',
'FI',
'FR',
'DE',
'GR',
'ES',
'IT',
'SE',
'TR',
'GB',
'NO',
'UA',
'BR',
'CO',
'US',
'CA',
'MX',
'JP',
'PH',
'MY',
'SG'
]
export default function AutoComplete({ name, isPersonalAddressInput }) {
const input = useRef()
const [predictions, setPredictions] = useState(null)
const [countryCode, setCountryCode] = useState(null)
const optionClicked = async prediction => {
input.current.value = prediction.name
// Needs to match the shape of the event object because onInput takes an event object.
await onInput({ target: { value: prediction.name } })
setPredictions(null)
}
const clickOutside = e => {
if (input.current && !input.current.contains(e.target)) {
setPredictions(null)
}
}
const onInput = useCallback(
async e => {
if (!e.target.value) return
const value = e.target.value
setPredictions(value ? (await search(value)).results : null)
if (isPersonalAddressInput) return
geocode(value)
.then(res => {
const country = res?.results[0]?.country
const countryCode = res?.results[0]?.countryCode
setCountryCode(countryCode)
sessionStorage.setItem('bank-signup-eventCountry', country)
sessionStorage.setItem('bank-signup-eventCountryCode', countryCode)
})
.catch(err => console.error(err))
},
[isPersonalAddressInput]
)
//TODO: Close suggestions view when focus is lost via tabbing.
//TODO: Navigate suggestions with arrow keys.
useEffect(() => {
const inputEl = input.current
if (!inputEl) return
document.addEventListener('click', clickOutside)
inputEl.addEventListener('input', onInput)
inputEl.addEventListener('focus', onInput)
return () => {
document.removeEventListener('click', clickOutside)
inputEl.removeEventListener('input', onInput)
inputEl.removeEventListener('focus', onInput)
}
}, [onInput])
return (
<Box sx={{ position: 'relative', width: '100%' }}>
<FlexCol flexDirection="column" position="relative" width="100%" gap="2">
<Input
ref={input}
name={name}
id={name}
placeholder="Shelburne, VT"
autoComplete="off"
sx={{ ...AutofillColourFix }}
/>
<Box>
{/* {String(countryCode)} */}
{countryCode && !approvedCountries.includes(countryCode) && (
<Flex sx={{ alignItems: 'center' }}>
<Icon
glyph="sad"
size="2.5rem"
sx={{ color: 'red', mr: 1, flexShrink: 0 }}
/>
<Text
as="label"
htmlFor={name}
sx={{
color: 'red'
// fontWeight: 'medium',
}}
>
Currently, we only have first-class support for organizations in
select countries.
<br />
If you're somewhere else, you can still use HCB!
<br />
Please contact us at hcb@hackclub.com
</Text>
</Flex>
)}
</Box>
</FlexCol>
{predictions && predictions.length > 0 && (
<Box
sx={{
background: '#47454f',
border: '1px solid #696675',
width: '100%',
p: 3,
borderRadius: '4px',
position: 'absolute',
bottom: 'calc(100% + 0.5em)'
}}
>
<FlexCol gap={1}>
{predictions.map((prediction, idx) => (
<>
<Text
as="button"
onClick={() => optionClicked(prediction)}
sx={{
cursor: 'pointer',
border: 'none',
background: 'none',
color: '#d1cbe7',
'&:hover': {
color: 'white'
},
fontFamily: 'inherit',
fontSize: 'inherit',
textAlign: 'inherit'
}}
key={idx}
>
{prediction.name}
</Text>
{idx < predictions.length - 1 && (
<hr
style={{
width: '100%',
color: '#8492a6'
}}
/>
)}
</>
))}
</FlexCol>
</Box>
)}
</Box>
)
}

View file

@ -1,42 +0,0 @@
import { Box, Button, Flex, Text } from 'theme-ui'
import Icon from '../../icon'
export default function AlertModal({ formError, setFormError }) {
if (!formError) return null
const close = () => setFormError(null)
return (
<Box>
<Box
onClick={close}
sx={{
position: 'fixed',
inset: 0,
background: '#000000',
opacity: 0.5,
zIndex: 1000
}}
/>
<Flex
sx={{
flexDirection: 'column',
alignItems: 'center',
gap: 3,
background: '#252429',
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 1001,
padding: 4,
borderRadius: 'default'
}}
>
<Text variant="title">Oops!</Text>
<Text variant="lead">{formError}</Text>
<Button onClick={close}>Dismiss</Button>
</Flex>
</Box>
)
}

View file

@ -1,10 +0,0 @@
//TODO: Move to main theme
const autofillColourFix = {
'&:-webkit-autofill': {
boxShadow: '0 0 0 100px #252429 inset !important',
WebkitTextFillColor: 'white'
}
}
export default autofillColourFix

View file

@ -1,37 +0,0 @@
import { useEffect, useState } from 'react'
import Icon from '../../icon'
import { useRouter } from 'next/router'
export default function Checkbox({ name, defaultChecked = false, size = 38 }) {
const [checked, setChecked] = useState(defaultChecked)
const toggle = () => setChecked(!checked)
const router = useRouter()
/* Fill in the field with the value from sessionStorage.
For other input elements, the value is set in the Field component,
but these checkboxes hold their state in useState rather than in the DOM. */
useEffect(() => {
const value = router.query[name] || sessionStorage.getItem('bank-signup-' + name)
if (value) {
const input = document.getElementById(name)
input && setChecked(!!value)
}
}, [router.query, name])
return (
<>
<input aria-hidden="true" type="hidden" value={checked} name={name} />
<Icon
glyph={checked ? 'checkmark' : 'checkbox'}
size={size}
id={name}
name={name}
aria-checked={checked}
role="checkbox"
tabindex="0"
onClick={() => toggle()}
onKeyDown={e => e.key === 'Enter' && toggle()}
/>
</>
)
}

View file

@ -1,69 +0,0 @@
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { Box, Flex, Label, Text } from 'theme-ui'
import FlexCol from '../../flex-col'
export default function Field({
name,
label,
description,
col = true,
requiredFields,
children
}) {
const router = useRouter()
const isRequired =
requiredFields[parseInt(router.query.step) - 1].includes(name)
/* Fill in the field input element with the value from sessionStorage.
Note: the custom checkbox component does this in its own useEffect hook. */
useEffect(() => {
const value = router.query[name] || sessionStorage.getItem('bank-signup-' + name)
if (value) {
const input = document.getElementById(name)
if (input) input.value = value
}
}, [router.query, name])
return (
<FlexCol gap={2} width={'100%'}>
<Flex
sx={{
flexDirection: col ? 'column' : 'row',
alignItems: col ? 'flex-start' : 'center',
gap: 2
}}
>
<Flex sx={{ alignItems: 'center', gap: 2 }}>
<Label
htmlFor={name}
sx={{
textTransform: 'capitalize',
fontSize: 3,
width: 'fit-content'
}}
>
{label}
</Label>
{isRequired && (
<Box
sx={{
backgroundColor: 'muted',
padding: '4px 6px',
borderRadius: '999px',
lineHeight: '1',
fontSize: 14
}}
>
Required
</Box>
)}
</Flex>
{children}
</Flex>
{description && (
<Text sx={{ color: 'muted', fontSize: 1 }}>{description}</Text>
)}
</FlexCol>
)
}

View file

@ -1,29 +0,0 @@
import { forwardRef } from 'react'
import { Box } from 'theme-ui'
const formContainer = forwardRef(({ children }, ref) => {
return (
<Box
ref={ref}
as="form"
sx={{
height: '100%',
width: ['100%', null, null, '50ch'],
flex: '1',
overflowY: ['none', null, null, 'auto'],
pr: [0, null, '2ch'],
pl: [0, null, 1],
pb: [0, null, 3],
display: 'flex',
flexDirection: 'column',
gap: 4
}}
>
{children}
</Box>
)
})
// https://stackoverflow.com/a/67993106/10652680
formContainer.displayName = 'formContainer'
export default formContainer

View file

@ -1,89 +0,0 @@
import { Box, Flex, Link, Text } from 'theme-ui'
import Icon from '../../icon'
import FlexCol from '../../flex-col'
export default function HCBInfo() {
return (
<Box>
<FlexCol gap={4}>
<FlexCol gap={4}>
<Text sx={{ fontSize: 36 }}>
What HCB <i>is</i>
</Text>
<FlexCol gap={3} ml={3}>
<FlexCol gap={2}>
<Flex sx={{ alignItems: 'center', gap: 2 }}>
<Link
color="white"
href="/hcb/fiscal-sponsorship"
target="_blank"
sx={{
fontSize: 3,
display: 'inline-flex',
alignItems: 'flex-end',
gap: 1
}}
>
A fiscal sponsor
<Icon glyph="external" />
</Link>
</Flex>
<Text sx={{ color: 'muted' }}>
<ul>
<li>Nonprofit status.</li>
<li>Tax-deductable donations.</li>
</ul>
</Text>
</FlexCol>
<FlexCol gap={2}>
<Text sx={{ fontSize: 3 }}>A financial platform</Text>
<Text sx={{ color: 'muted' }}>
<ul>
<li>A donations page and invoicing system.</li>
<li>Transfer money electronically.</li>
<li>Order cards for you and your team to make purchases.</li>
</ul>
</Text>
</FlexCol>
</FlexCol>
</FlexCol>
<FlexCol gap={4}>
<Text sx={{ fontSize: 36 }}>
What HCB <i>is not</i>
</Text>
<FlexCol gap={3} ml={3}>
<FlexCol gap={2}>
<Text sx={{ fontSize: 3 }}>
A bank!{' '}
<Text sx={{ color: 'muted', fontSize: 2 }}>(we're better)</Text>
</Text>
<Text sx={{ color: 'muted' }}>
<ul>
<li>
Rather than setting up a standard bank account, you'll get a
restricted fund within Hack Club accounts.
</li>
<li>
You can't deposit or withdraw cash. But you can receive any
kind of electronic payment!
</li>
</ul>
</Text>
</FlexCol>
<FlexCol gap={2}>
<Text sx={{ fontSize: 3 }}>For-profit</Text>
<Text sx={{ color: 'muted' }}>
<ul>
<li>
If youre a for-profit entity, then HCB is not for you.
Consider setting up a business.
</li>
</ul>
</Text>
</FlexCol>
</FlexCol>
</FlexCol>
</FlexCol>
</Box>
)
}

View file

@ -1,178 +0,0 @@
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { Button, Flex, Text, Spinner } from 'theme-ui'
async function sendApplication() {
// Get the form data from sessionStorage
const data = {}
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i)
if (key.startsWith('bank-signup-')) {
data[key.replace('bank-signup-', '')] = sessionStorage.getItem(key)
}
}
console.dir('Sending data:', data)
// Send the data
try {
const res = await fetch('/api/hcb/apply', {
method: 'POST',
cors: 'no-cors',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
} catch (error) {
console.error(error)
}
}
function NavIcon({ isBack }) {
const style = {
height: '1em',
fill: 'white',
margin: 0,
flexShrink: 0
}
return isBack ? (
<svg style={style} viewBox="10.73 7.72 9.27 16.53">
<g>
<path d="M19.768,23.89c0.354,-0.424 0.296,-1.055 -0.128,-1.408c-1.645,-1.377 -5.465,-4.762 -6.774,-6.482c1.331,-1.749 5.1,-5.085 6.774,-6.482c0.424,-0.353 0.482,-0.984 0.128,-1.408c-0.353,-0.425 -0.984,-0.482 -1.409,-0.128c-1.839,1.532 -5.799,4.993 -7.2,6.964c-0.219,0.312 -0.409,0.664 -0.409,1.054c0,0.39 0.19,0.742 0.409,1.053c1.373,1.932 5.399,5.462 7.2,6.964l0.001,0.001c0.424,0.354 1.055,0.296 1.408,-0.128Z"></path>
</g>
</svg>
) : (
<svg style={style} viewBox="12.75 7.72 9.25 16.53">
<g>
<path d="M12.982,23.89c-0.354,-0.424 -0.296,-1.055 0.128,-1.408c1.645,-1.377 5.465,-4.762 6.774,-6.482c-1.331,-1.749 -5.1,-5.085 -6.774,-6.482c-0.424,-0.353 -0.482,-0.984 -0.128,-1.408c0.353,-0.425 0.984,-0.482 1.409,-0.128c1.839,1.532 5.799,4.993 7.2,6.964c0.219,0.312 0.409,0.664 0.409,1.054c0,0.39 -0.19,0.742 -0.409,1.053c-1.373,1.932 -5.399,5.462 -7.2,6.964l-0.001,0.001c-0.424,0.354 -1.055,0.296 -1.408,-0.128Z"></path>
</g>
</svg>
)
}
export default function NavButton({
isBack,
form,
clickHandler,
requiredFields,
setFormError
}) {
const router = useRouter()
const [spinner, setSpinner] = useState(false)
useEffect(() => {
setSpinner(false)
}, [router.query.step])
const minStep = 1
const maxStep = 3
const click = async () => {
setSpinner(true)
let step = parseInt(router.query.step)
async function setStep(s) {
await router.push(
{
pathname: router.pathname,
query: { ...router.query, step: s }
},
undefined,
{}
)
}
if (!step) {
// Set the step query param to minStep if it's not there.
await setStep(minStep)
} else if (step === minStep && isBack) {
await router.push('/hcb')
return
} else if (step < minStep) {
// Set the step query param to minStep if it's lower than that.
await setStep(minStep)
}
/* Don't return from inside the loop since
we want all input values to be saved every time */
let wasError = false
const formData = new FormData(form.current)
// Save form data
formData.forEach((value, key) => {
sessionStorage.setItem('bank-signup-' + key, value)
// Check if there are empty required fields.
if (
(!isBack &&
(!value || value.trim() === '') &&
requiredFields[step - 1].includes(key)) ||
(!isBack &&
formData.get('contactOption') === 'slack' &&
!formData.get('slackUsername')) // I'm so sorry for this
) {
setFormError('Please fill all required fields')
wasError = true
setSpinner(false)
}
})
if (wasError) return
// Run the parent's click handler for this button.
if (clickHandler) await clickHandler()
if (step >= maxStep && !isBack) {
await sendApplication()
await router.push('/hcb/apply/success')
return
} else {
step += isBack ? -1 : 1
}
await setStep(step)
}
return (
<Button
variant={isBack ? 'outline' : 'ctaLg'}
sx={{
color: 'white',
width: '100%',
maxWidth: isBack ? '8rem' : '10rem',
position: 'relative'
}}
onClick={click}
>
<Flex
sx={{
flexDirection: isBack ? 'row' : 'row-reverse',
justifyContent: 'center',
placeItems: 'center',
fontSize: isBack ? 2 : 4,
gap: [2, null, null, 3]
}}
>
<NavIcon isBack={isBack} />
<Text
sx={{
textTransform: 'none',
fontWeight: 'bold'
}}
>
{isBack ? 'Back' : 'Next'}
</Text>
</Flex>
{!isBack && spinner && (
<Spinner
sx={{
height: '32px',
color: 'white',
position: 'absolute',
right: '-0.3rem',
margin: '0 !important'
}}
/>
)}
</Button>
)
}

View file

@ -1,85 +0,0 @@
import { useRouter } from 'next/router'
import { Box, Flex, Text } from 'theme-ui'
import FlexCol from '../../flex-col'
function StepIcon({ completed, number }) {
let strokeColour = completed ? '#33d6a6' : '#8492a6'
let fillColour = completed ? '#33d6a6' : 'none'
return (
<Box sx={{ position: 'relative' }}>
<svg
style={{ translate: '0 1px' }}
width="40"
height="40"
viewBox="0 0 40 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M20 38C36.5 38 38 36.5 38 20C38 3.5 36.5 2 20 2C3.5 2 2 3.5 2 20C2 36.5 3.5 38 20 38Z"
stroke={strokeColour}
fill={fillColour}
stroke-width="3"
/>
</svg>
<Flex
sx={{
height: '100%',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
inset: '0'
}}
>
<Text
sx={{
color: 'white',
fontSize: 2
}}
>
{number}
</Text>
</Flex>
</Box>
)
}
function Step({ number, label, completed }) {
return (
<Flex sx={{ lineHeight: '1', alignItems: 'center', gap: '4' }}>
<StepIcon completed={completed} number={number + 1} />
<Text
sx={{
fontSize: '3',
display: ['none', null, null, 'block']
}}
>
{label}
</Text>
</Flex>
)
}
export default function Progress() {
const router = useRouter()
const step = parseInt(router.query.step)
const labels = ['Intro', 'Organization info', 'Personal info']
return (
<Flex
sx={{
gap: 3,
translate: [0, null, null, '-1rem 0'],
flexDirection: ['row', null, null, 'column']
}}
>
{labels.map((label, i) => (
<Step number={i} label={label} completed={step > i} key={i} />
))}
</Flex>
)
}

View file

@ -1,213 +0,0 @@
import { Box, Container, Heading, Text, Link, Grid } from 'theme-ui'
import Run from './run'
import { Fade } from 'react-reveal'
import Icon from '../icon'
export default function Everything({ fee, partner = false }) {
return (
<>
<Box
sx={{
pt: 6,
pb: [3, 6],
marginTop: 6,
bg: 'darker'
}}
>
<Container mb={[4, 5]} px={3} sx={{ textAlign: 'center' }}>
<Heading variant="ultratitle" sx={{ color: 'smoke' }}>
Everything youll&nbsp;need.
</Heading>
</Container>
<Container px={[3, null, 5]}>
<List>
{Object.entries({
'Legal entity with 501(c)(3) status': 'briefcase',
'We do your taxes': 'checkmark',
'Share access with your whole team': 'member-add',
'Backed by a bank account under the hood': 'bank-account',
'Instant invoice sending': 'transactions',
'Real-time dashboard of finances': 'analytics',
'Transaction data export': 'download',
'Record shared notes on transactions': 'docs',
'24-hour response support': 'clock',
'Reimbursement process': 'enter'
// 'Instant deposits': 'bolt'
}).map(([item, icon = 'enter']) => (
<ListItem key={item} icon={icon}>
{item}
</ListItem>
))}
{Object.entries({
'Physical check sending & voiding': '',
'Online direct deposit & ACH transfers': '',
'Generate attendee legal waivers': '',
'Virtual debit cards (with Apple & Google Pay)': '',
'Debit card transaction paper trail': '',
'Transparency Mode': ''
}).map(([item, date]) => (
<ListItem
key={item}
icon={
item.includes('signup')
? 'bolt'
: item.includes('card')
? 'card'
: item.includes('Transparency')
? 'explore'
: item.includes('Physical')
? 'email'
: 'enter'
}
>
{item}
</ListItem>
))}
{!partner
? Object.entries({
'Online, embeddable donation form': ''
}).map(([item, date]) => (
<ListItem
key={item}
icon={
item.startsWith('Instant')
? 'bolt'
: item.includes('form')
? 'link'
: 'enter'
}
>
{item}
</ListItem>
))
: ''}
</List>
</Container>
<Run />
<Container px={3} mt={4}>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
flexWrap: 'wrap',
alignItems: 'center',
textAlign: 'center'
}}
>
<Text sx={{ fontSize: 32, mr: 2 }}>You pay just</Text>
<Percentage fee={fee} />
<Text sx={{ fontSize: 32, ml: 2 }}>
of revenue. No upfront costs.
</Text>
</Box>
<Container
variant="copy"
sx={{
textAlign: 'center',
fontSize: 18,
lineHeight: '1.25',
letterSpacing: '-.03ch',
marginTop: 4,
marginBottom: 5
}}
>
<Container variant="narrow">
<Text sx={{ color: 'muted', lineHeight: 1.375 }}>
HCB is a{' '}
<Link
color="primary"
href="https://en.wikipedia.org/wiki/Fiscal_sponsorship"
hoverline
>
fiscal sponsor
</Link>
. Other fiscal sponsors' fees typically vary between 7-14%
of&nbsp;revenue. Hack Club is a 501(c)(3) nonprofit.
</Text>
</Container>
</Container>
</Container>
</Box>
</>
)
}
function List({ children }) {
return (
<Container>
<ol
style={{
paddingLeft: 0,
listStyle: 'none'
}}
>
<Grid gap={2} columns={[1, 1, '1fr 1fr']}>
{children}
</Grid>
</ol>
</Container>
)
}
function ListItem({ icon = 'enter', start, ...props }) {
return (
<Fade left>
<li
style={{
lineHeight: 1.25,
breakInside: 'avoid',
display: 'flex',
alignItems: 'center',
paddingBottom: 4,
marginBottom: 8
}}
>
{start || (
<Icon
glyph={icon}
sx={{ color: 'muted', marginRight: 2 }}
size={32}
mr={2}
/>
)}
<Text sx={{ fontSize: 24, color: 'smoke' }} {...props} />
</li>
</Fade>
)
}
function Percentage({ fee }) {
return (
<Box
sx={{
display: 'flex',
alignItems: 'center',
bg: 'slate',
color: 'green',
width: [fee.length === 1 ? 70 : 80, fee.length === 1 ? 128 : 138],
height: [fee.length === 1 ? 70 : 80, fee.length === 1 ? 128 : 138],
borderRadius: 'circle',
fontWeight: 'bold',
justifyContent: 'center',
boxShadow: '0 8px 32px rgba(255, 255, 255, 0.125)',
fontSize: [48, 84],
'&:after': {
content: '"%"',
fontSize: [24, 40],
fontWeight: 'normal',
marginRight: fee.length === 1 ? -2 : 0,
marginLeft: [null, fee.length === 1 ? 2 : 0],
color: 'muted'
}
}}
>
{fee}
</Box>
)
}
const recent = dt => {
const past = new Date()
past.setMonth(past.getMonth() - 2)
return new Date(dt) > past
}

View file

@ -1,279 +0,0 @@
import { Box, Heading, Link, Text, Container, Grid } from 'theme-ui'
import Icon from '../icon'
export default function Features({ partner = false }) {
return (
<Box sx={{ py: 6 }}>
<Container>
<Text variant="heading" sx={{ fontSize: 50 }}>
A full-stack toolkit for organizing anything.
</Text>
<br />
<br />
<Text sx={{ color: 'muted', maxWidth: '48', fontSize: 28 }}>
Invoice sponsors, issue debit cards to your team, and view transaction
history.
<br />
Ongoing support so you can focus on organizing, not the paperwork.
</Text>
<br />
<br />
</Container>
<Container>
<Grid gap={4} columns={[1, null, 3]}>
<Box>
<Module
icon="bank-account"
name="Fund"
body={
<>A fund under the hood with a custom, beautiful dashboard.</>
}
/>
<ModuleDetails>
<Document
name="501(c)(3) nonprofit status"
cost="Become part of Hack Club's legal entity, getting the benefits of our tax status."
/>
<Document
name="Tax filings (990, end-of-year)"
cost="We handle all filings with the IRS, so you can focus on your event, not hiring CPAs."
/>
</ModuleDetails>
</Box>
<Laptop
href="https://hcb.hackclub.com/the-innovation-circuit"
title="See The Innovation Circuits finances in public"
sx={{
gridColumn: [null, null, 'span 2'],
gridRow: [null, null, 'span 2']
}}
/>
<Module
icon="card"
name="Debit cards"
body={
<>
Issue physical debit cards to all your teammates, backed by{' '}
<Link
href="https://stripe.com/issuing"
color="smoke"
hoverline
target="_blank"
>
Stripe
</Link>
.
</>
}
/>
<Module
icon="analytics"
name="Balance &amp; history"
body="Check real-time account balance + transaction history online anytime."
/>
{/* <Module
icon="bolt"
name="Instant deposits"
body="Receive donations and invoice payments instantly once they're paid."
/> */}
{/* <Module
icon="payment"
name="Built-in invoicing"
body={
<>
Accept sponsor payments with instant invoicing, powered by{' '}
<Link
href="https://stripe.com/invoicing"
color="smoke"
hoverline
target="_blank"
>
Stripe
</Link>
.
</>
}
/> */}
<Module
icon="docs"
name="Pre-written forms"
body="Download liability + photo forms custom written by expert lawyers."
/>
<Module
icon="payment-transfer"
name="Transfer money"
body="Flexible money transfer options including ACH, check, and PayPal."
/>
<Module
icon="explore"
name="Transparency Mode"
body="If youd like, show your finances on public pages for full transparency."
/>
<Module
icon="email"
name="Postal"
body={
<>
Send email newsletters for free using our hosted instance of{' '}
<Link
href="https://sendy.co/"
color="smoke"
hoverline
target="_blank"
>
Sendy
</Link>
.
</>
}
/>
{!partner && (
<Module
icon="friend"
name="Donation Page"
body="Receive donations through a custom, online embeddable form."
/>
)}
<Module
icon="flag"
name="PVSA Awards"
body={
<>
Issue the{' '}
<Link
href="https://presidentialserviceawards.gov"
color="smoke"
hoverline
target="_blank"
>
President's Volunteer Service Award
</Link>{' '}
to your volunteers.
</>
}
/>
{!partner && (
<Module
icon="web"
name="Free Domains"
body="We'll pay up to $20 for your organization's domain name for a year."
/>
)}
<Module
icon="support"
name="Support anytime"
body="Well never leave you hanging with 24hr response time on weekdays."
/>
</Grid>
</Container>
<Container
variant="copy"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
textAlign: 'center'
}}
>
<Text
variant="lead"
sx={{
color: 'muted',
fontSize: 3,
marginTop: [4, 5]
}}
>
Have more questions? <br /> Check out the{' '}
<Link
href="https://hcb.hackclub.com/faq"
target="_blank"
rel="noreferrer"
hoverline
>
HCB FAQ
</Link>
.
</Text>
</Container>
</Box>
)
}
function Module({ icon, name, body }) {
return (
<Box sx={{ display: 'flex', alignItems: 'start' }}>
<Icon
size={48}
glyph={icon}
sx={{ flexShrink: 0, marginRight: 3, color: 'primary' }}
/>
<Box>
<Heading sx={{ color: 'snow', lineHeight: '1.5' }}>{name}</Heading>
<Text sx={{ color: 'muted', lineHeight: '1.375', fontSize: 17 }}>
{body}
</Text>
</Box>
</Box>
)
}
function ModuleDetails({ children }) {
return (
<Box
sx={{
bg: '#252429',
color: 'smoke',
mt: 4,
ml: 0,
py: 3,
px: 2,
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.0625)',
borderRadius: 'default'
}}
>
{children}
</Box>
)
}
function Document({ name, cost }) {
return (
<Box sx={{ display: 'flex' }}>
<Icon
size={28}
mr={1}
glyph="payment"
sx={{ flexShrink: 0, color: 'green' }}
/>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Text fontSize={2}>{name}</Text>
{cost && (
<Text fontSize={1} color="muted" style={{ lineHeight: '1.375' }}>
{cost}
</Text>
)}
</Box>
</Box>
)
}
function Laptop({ href, title, sx }) {
return (
<Link href={href} title={title} sx={sx}>
<Box
sx={{
display: 'block',
width: '100%',
height: '100%',
minHeight: '16rem',
backgroundSize: 'auto 115%',
backgroundImage: "url('/hcb/laptop-dark.png')",
backgroundPosition: 'center top',
backgroundRepeat: 'no-repeat'
}}
></Box>
</Link>
)
}

View file

@ -1,236 +0,0 @@
import {
Box,
Button,
Heading,
Link,
Flex,
Text,
Container,
Badge
} from 'theme-ui'
import Fade from 'react-reveal/Fade'
import ScrollHint from '../scroll-hint'
import Image from 'next/image'
import hero from '../../public/hcb/bg.webp'
export default function Landing({ showButton = true, eventsCount }) {
return (
<>
<Slide>
<Vignette />
<Box
sx={{
position: 'absolute',
flexDirection: 'column',
justifyContent: 'center',
bottom: 5,
mx: 'auto',
width: '100%'
}}
>
<Box
sx={{
zIndex: '100',
paddingTop: '96px'
}}
>
<Fade duration={625} bottom>
<Container
variant="container"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
textAlign: 'center'
}}
>
<Heading
variant="ultratitle"
sx={{
marginBottom: 4,
textShadow: '0 0 16px rgba(0, 0, 0, 1)',
letterSpacing: '-0.02em',
'@media screen and (max-height: 600px)': {
lineHeight: 0.875
},
'@media screen and (min-height: 610px)': {
lineHeight: 1.125
}
}}
as="h1"
>
<Underline>Become a nonprofit</Underline> with HCB
</Heading>
<Flex
sx={{
gap: 3,
alignItems: 'center',
justifyContent: 'center',
marginBottom: 4
}}
>
<img
src="/hcb/hcb-icon-icon-dark.png"
alt="HCB Icon"
height={64}
sx={{
margin: 'auto'
}}
/>
<Text as="h2" sx={{ fontSize: 4 }}>
HCB by
</Text>
<img
src="https://assets.hackclub.com/flag-standalone.svg"
alt="hack club flag"
height={48}
/>
</Flex>
<Container variant="copy">
<Text
variant="lead"
sx={{
textShadow: '0 3px 6px rgba(0, 0, 0, 0.5)',
'@media screen and (max-height: 600px)': {
lineHeight: 1
}
}}
>
The team behind the{' '}
<Link
href="https://innovationcircuit.com"
target="_blank"
color="inherit"
bold
hoverline
>
Innovation Circuit
</Link>{' '}
is one of {Math.round((eventsCount - 50) / 100) * 100}+
teams who use <strong>HCB</strong> to run world-class
organizations, hackathons, and clubs.
</Text>
</Container>
</Container>
</Fade>
</Box>
<br />
<Box
sx={{
display: 'flex',
justifyContent: 'center',
marginBottom: 3
}}
>
{showButton && (
<>
<Button
variant="ctaLg"
as="a"
href="#apply"
style={{ zIndex: '100' }}
>
Apply Now
</Button>
<Button
variant="outlineLg"
as="a"
href="https://hcb.hackclub.com"
target="_blank"
style={{ zIndex: '100' }}
ml={3}
>
Sign in
</Button>
</>
)}
</Box>
<ScrollHint />
</Box>
<Box
sx={{
position: 'absolute',
bottom: 3,
right: 2,
display: ['none', 'block']
}}
>
<Badge
variant="pill"
sx={{
zIndex: '1',
bg: 'muted',
color: 'steel',
fontWeight: 'normal'
}}
>
Singapore
</Badge>
</Box>
</Slide>
</>
)
}
function Underline({ children }) {
return (
<span
style={{
backgroundImage: 'url(/underline-red.svg)',
backgroundRepeat: 'no-repeat',
backgroundSize: '100% 1rem',
backgroundPosition: 'bottom center'
}}
>
{children}
</span>
)
}
function Slide({ children }) {
return (
<Box
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'end',
width: '100vw',
backgroundSize: 'cover',
backgroundColor: '#000000',
boxShadow: 'inset 0 0 4rem 1rem rgba(0, 0, 0, 0.5)',
backgroundPosition: 'center',
backgroundSize: 'cover',
width: '100%',
minHeight: '100vh',
position: 'relative'
}}
>
<Image
src={hero}
layout="fill"
objectFit="cover"
alt="Dark room with a stage and students sitting below"
placeholder="blur"
priority
/>
{children}
</Box>
)
}
function Vignette() {
return (
<Box
style={{
backgroundImage:
'linear-gradient(to bottom,rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.25) 25%,rgba(0, 0, 0, 0.6) 50%, rgba(0, 0, 0, 0.7) 100%)',
height: '100vh',
left: '0',
right: '0',
position: 'absolute',
zIndex: '0'
}}
></Box>
)
}

View file

@ -1,203 +0,0 @@
import { Box, Image, Text, Heading, Container, Grid, Link } from 'theme-ui'
import { Slide } from 'react-reveal'
import Stat from '../stat'
import kebabCase from 'lodash/kebabCase'
const orgs = [
{
logo: '/hcb/nonprofits/girlgenius.png',
name: 'Girl Genius',
director: 'Chloe Yan',
role: 'Executive Director',
budget: 5,
website: 'girlgeniusmag.tech',
description:
'Girl Genius Magazine is a fully student-run publication inspiring the next generation of female and non-binary leaders in STEAM. Their journalism and inclusive online community are dedicated to breaking down techs lingering gender barriers. Becoming fiscally sponsored allowed them to publish more issues, host over 40 workshops, organize a conference, and reach a global audience of 11k readers (and counting).'
},
{
logo: '/hcb/nonprofits/techshift.png',
transparency: 'techshift',
name: 'TechShift',
director: 'Daniel Jin',
role: 'Co-Executive Director',
budget: 100,
website: 'techshift.org',
description:
'Founded in 2017, TechShift supports a network of 30+ student-run chapters across 3 continents leading initiatives at the intersection of technology and social impact. With the help of HCB, they are bringing about a more equitable technological future through their mentorship programs, community partnerships, microgrants, and the STEM For Social Good Toolkit.'
},
{
logo: '/hcb/nonprofits/projectboom.jpg',
transparency: 'projectboom',
name: 'Project Boom',
director: 'Kunal Botla',
role: 'Founder',
budget: '5.6',
budgetLabel: 'raised',
website: 'projectboom.org',
url: 'https://projectboom.org/',
description:
'Project Boom is a student-led organization with a simple mission: getting computers to those who need them. Instead of becoming e-waste, old machines are given new life to deserving students worldwide. Joining HCB provided Project Boom with a platform to easily accept and manage donations, helping them to repair and ship more computers than ever before.'
},
{
logo: '/hcb/nonprofits/executebig.png',
name: 'Execute Big',
director: 'Mingjie Jiang',
role: 'Co-Executive Director',
budget: '4',
budgetLabel: 'funded',
website: 'executebig.org',
description:
'Execute Big began by using leftover hackathon funds to provide travel grants for students. HCB helped make possible their array of grants, fellowships, and innovative programs to share computer science with students nationally. Now their own 501(c)(3) nonprofit, they leverage existing resources to make STEM activities accessible to everyone.'
}
]
export default function Nonprofits() {
return (
<Container
sx={{ pt: 6, pb: [2, null, 5], mx: 'auto', px: [null, null, 4] }}
>
<Container
variant="copy"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
textAlign: 'center',
pb: 3
}}
>
<Heading variant="title">Nonprofit? No problem.</Heading>
<Text variant="lead" color="muted">
HCB is a powerful, safe, and easy-to-use money thing, whether you're
receiving your first donation or spending $100,000 a year.
</Text>
</Container>
<Grid
gap={4}
sx={{
gridTemplateColumns: ['100%', null, null, '1fr 1fr']
}}
>
{orgs.map(org => {
const id = kebabCase(org.name)
return (
<Organization
logo={org.logo}
name={org.name}
budget={org.budget}
budgetLabel={org.budgetLabel}
website={org.website}
description={org.description}
url={org.url}
key={id}
/>
)
})}
</Grid>
</Container>
)
}
function Organization({
logo,
name,
budget,
budgetLabel,
website,
url,
description
}) {
return (
<Slide bottom>
<Box
sx={{
backgroundColor: 'darkless',
color: 'smoke',
borderRadius: 'extra',
mx: 'auto'
}}
>
<Container sx={{ padding: 0, margin: 0 }}>
<Box p={[3, null, 4]}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}}
>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Image
src={logo}
alt={`${name} logo`}
sx={{
height: '4rem',
width: '4rem',
objectFit: 'cover',
borderRadius: 'default'
}}
/>
<Box sx={{ ml: 3 }}>
<Text
color="white"
variant="headline"
sx={{
fontSize: [28, null, 38],
lineHeight: 1,
letterSpacing: -0.1
}}
>
{name}
</Text>
<br />
<Link
href={url || `https://${website}`}
sx={{
textDecoration: 'none',
color: 'muted',
'&:hover': { textDecoration: 'underline' }
}}
>
{website}
</Link>
</Box>
</Box>
<DetailStat
value={`$${budget}k`}
label={budgetLabel ?? 'budget'}
/>
</Box>
<br />
<About>{description}</About>
</Box>
</Container>
</Box>
</Slide>
)
}
function DetailStat({ value, label }) {
return (
<Box sx={{ px: 0, mb: [3, 0], ml: [-1, 0], mx: 3 }}>
<Stat value={value} label={label} sm />
</Box>
)
}
function About({ children }) {
return (
<Text
sx={{
fontSize: 2,
color: 'snow',
textIndent: '-.375em',
lineHeight: 'caption',
fontSize: 18
}}
>
{children}
</Text>
)
}

View file

@ -1,82 +0,0 @@
import { Box, Text, Container } from 'theme-ui'
import { Fade } from 'react-reveal'
import Icon from '../icon'
export default function Run() {
return (
<>
<Container
variant="container"
sx={{
display: 'flex',
flexDirection: ['column', null, 'row'],
alignItems: 'center',
width: ['100%', '100%', '100%', '85%'],
bg: '#252429',
color: 'smoke',
px: 4,
borderRadius: 'default',
position: 'relative'
}}
>
<Container maxWidth={28} sx={{ mx: 0, py: 4 }}>
<Text variant="heading" sx={{ fontSize: 48 }}>
HCB doesnt stop at closing ceremony.
</Text>
<br />
<Text variant="lead" sx={{ color: 'muted', fontSize: 28 }}>
Receiving and managing money is just the start. HCB helps you handle
ongoing obligations while youre organizing.
</Text>
</Container>
<List>
<ListItem
icon="docs"
body="We handle ongoing tax filings including end-of-year taxes"
/>
<ListItem
icon="payment-docs"
body="Our accountants regularly reconcile your books"
/>
<ListItem
icon="history"
body="You always have access to historical financial data"
/>
</List>
</Container>
</>
)
}
function List({ children }) {
return (
<Box>
<ol style={{ listStyle: 'none', paddingLeft: 0 }}>{children}</ol>
</Box>
)
}
function ListItem({ icon, body }) {
return (
<Fade bottom>
<li
style={{
lineHeight: 1.25,
display: 'flex',
alignItems: 'center',
pl: 0
}}
>
<Icon
glyph={icon}
size={45}
sx={{ color: 'primary', flexShrink: 'none', flexShrink: 0, mr: 2 }}
/>
<Text fontSize={[32, 48]} variant="lead">
{body}
</Text>
</li>
</Fade>
)
}

View file

@ -1,255 +0,0 @@
import {
Box,
Avatar,
Button,
Image,
Text,
Heading,
Container,
Grid,
Link
} from 'theme-ui'
import { Slide } from 'react-reveal'
import Stat from '../stat'
import kebabCase from 'lodash/kebabCase'
const events = [
{
transparency: 'hackpenn',
name: 'Hack Pennsylvania',
location: 'State College, PA',
organizer: 'Joy Liu',
budget: 15,
attendees: 115,
testimonial:
'For me, HCB unlocked organizing hackathons. Even after as a club leader, raising money seemed insurmountable. HCB directly enabled organizing events in my community with event bank accounts [sic] & a supportive community. I couldnt recommend it more highly.'
},
{
name: 'Teenhacks LI',
location: 'Long Island, NY',
organizer: 'Wesley Pergament',
budget: 35,
attendees: 300,
testimonial:
'For our hackathon, HCB has given us the tools to make sure our organization is professional with sponsors. HCB and their team have created an easily manageable resource to make sure any event is run successfully. We would highly recommend any organization be a part of the Hack Club ecosystem.'
},
{
transparency: 'mahacks',
name: 'MAHacks',
location: 'Boston, MA',
organizer: 'Kat Huang',
budget: 10,
attendees: 70,
testimonial:
'HCB removed the barriers to starting fundraising for MAHacks. In mere days, vs months of nonprofit paperwork, HCB enabled my team to invoice sponsors professionally and manage our finances on a clear, up-to-date dashboard. I highly recommend using HCB & joining the Hack Club community.'
},
{
transparency: 'dv-hacks',
name: 'DV Hacks',
location: 'Santa Clara, CA',
organizer: 'Khushi Wadhwa',
budget: 12,
attendees: 150,
testimonial:
'HCB is an essential platform for any hackathon organizer! It made us look both professional and credible in front of our sponsors and it relieved us of legal/financial burdens. HCB was there for us every step of the way and for a first-year hackathon, that support was priceless.'
}
]
export default function Testimonials() {
return (
<>
<Box>
<Container
variant="copy"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
textAlign: 'center'
}}
>
<Heading variant="title">
The best events across the country run on HCB.
</Heading>
<Text variant="lead" color="muted">
Everywhere from Philadelphia to Phoenix to Portland, HCB powers
events of all sizes.
</Text>
</Container>
<Container
sx={{
color: 'smoke',
px: [null, null, 4],
mx: 'auto',
mt: 2,
borderRadius: 0,
position: 'relative'
}}
>
<Grid
gap={4}
sx={{
gridTemplateColumns: ['100%', null, null, '1fr 1fr']
}}
>
{events.map(event => {
const id = kebabCase(event.name)
return <Event {...event} img={`/hcb/events/${id}.jpg`} key={id} />
})}
</Grid>
</Container>
</Box>
</>
)
}
function Event({
img,
name,
location,
budget,
attendees,
organizer,
testimonial,
transparency
}) {
return (
<Slide bottom>
<Box
sx={{
backgroundColor: 'darkless',
color: 'smoke',
borderRadius: 'extra',
mx: 'auto'
}}
>
<Container sx={{ padding: 0, margin: 0 }}>
<Image
src={img}
alt={location}
sx={{
maxHeight: '20rem',
objectFit: 'cover',
width: '100%',
borderRadius: 'default',
mb: -3
}}
/>
<Box p={[3, null, 4]}>
<Box
sx={{
display: 'flex',
flexDirection: ['column', 'row', 'row'],
alignItems: ['baseline', 'center'],
justifyContent: 'space-between',
marginBottom: -3
}}
>
<Text
color="white"
variant="headline"
sx={{ fontSize: [48, null, 30], letterSpacing: -0.1 }}
>
{name}
</Text>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
my: 0,
ml: -2,
pl: 0
}}
>
<DetailStat value={attendees} label="attendees" />
<DetailStat value={`$${budget}k`} label="budget" />
</Box>
</Box>
<br />
<Quote>"{testimonial}"</Quote>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
marginTop: ['0px', 3]
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
mt: ['16px', '0px']
}}
>
<Avatar
src={`/hackers/${organizer.split(' ')[0].toLowerCase()}.jpg`}
size={48}
mr={2}
alt="Photo of ${organizer}"
/>
<Text
color="white"
sx={{
fontSize: 19,
display: 'flex',
flexDirection: 'column'
}}
>
<Text sx={{ fontWeight: 'bold', lineHeight: 1.125 }}>
{organizer}
</Text>
<Text>Lead Organizer</Text>
</Text>
</Box>
{transparency && (
<Link
href={`https://hcb.hackclub.com/${transparency}`}
target="_blank"
rel="noreferrer"
sx={{ mt: ['16px', '0px'] }}
>
<Button
mt={[null, null, 4, 0]}
ml={[0, 'auto']}
sx={{ textTransform: 'none' }}
variant="primary"
title="🎶 take a look, it's in our books 🎵"
>
See Finances
</Button>
</Link>
)}
</Box>
</Box>
</Container>
</Box>
</Slide>
)
}
function DetailStat({ value, label }) {
return (
<Box sx={{ px: 0, mb: [3, 0], ml: [-1, 0], mx: 3 }}>
<Stat value={value} label={label} sm />
</Box>
)
}
function Quote({ children }) {
return (
<Text
sx={{
fontSize: 2,
color: 'muted',
textIndent: '-.375em',
lineHeight: 'caption',
fontSize: 18
}}
>
{children}
</Text>
)
}

View file

@ -1,72 +0,0 @@
import { Flex, Text, Image, Box, Container } from 'theme-ui'
import Slide from 'react-reveal'
function Step({ stepIndex, label }) {
return (
<Flex
sx={{
flexDirection: ['row', null, 'column'],
flex: '1 0 0',
alignItems: 'center',
maxWidth: ['24rem', null, '12rem'],
gap: 3
}}
>
<Image
src={`/hcb/timeline-steps/step${stepIndex}.svg`}
sx={{ flexShrink: 0 }}
alt=""
/>
<Text
variant="lead"
sx={{
textAlign: ['left', null, 'center'],
margin: '0px !important'
}}
>
{label}
</Text>
</Flex>
)
}
export default function Timeline() {
const labels = [
'Register your organization for HCB',
'Explore the interface in Playground mode',
'Hop on an intro call with our team',
'Start fundraising!'
]
const stepSideLength = 64
return (
<Slide>
<Flex
sx={{
flexDirection: ['column', null, 'row'],
justifyContent: 'space-between',
gap: 4,
maxWidth: ['300px', null, '1200px'],
mx: 'auto',
position: 'relative'
}}
>
{labels.map((label, idx) => (
<Step stepIndex={idx + 1} label={label} key={idx} />
))}
<Box
sx={{
border: 'solid #8492a6',
borderWidth: '3px 3px 0 0',
position: 'absolute',
top: stepSideLength / 2,
left: '10%', // TODO: make this dynamic
right: ['auto', null, '10%'],
bottom: [stepSideLength / 2, null, 'auto'],
zIndex: -1
}}
/>
</Flex>
</Slide>
)
}

View file

@ -8,7 +8,7 @@ Alongside the soccer players, theater kids, and band geeks, we aspire to create
**Job Description:**
[HCB](https://hackclub.com/hcb/) is our in-house financial software used by 1,500 Hack Clubbers to financially power their Hack Clubs, hackathons, and student-organized nonprofits. When teenagers use HCB, we act as their backing financial and legal entity, allowing them to leverage our 501(c)(3) nonprofit status to receive donations and use our beautiful in-house software to manage and spend their funds.
[HCB](https://hackclub.com/fiscal-sponsorship/) is our in-house financial software used by 1,500 Hack Clubbers to financially power their Hack Clubs, hackathons, and student-organized nonprofits. When teenagers use HCB, we act as their backing financial and legal entity, allowing them to leverage our 501(c)(3) nonprofit status to receive donations and use our beautiful in-house software to manage and spend their funds.
As our 7th full-time staff member, your role will be to end-to-end own the human operations essential to HCB's success, including onboarding video calls with new Hack Clubbers signing up, daily backend transaction categorization and management through our custom software, and providing a world-class customer success experience over Slack and email to Hack Clubbers. In addition to yourself, in this role you will lead 3 current part-time Hack Clubbers - and grow this team - to help you accomplish HCB's human operations.

View file

@ -52,18 +52,12 @@ const Root = styled(Box)`
}
`
export const Content = styled(Box)`
export const Content = styled(Container)`
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1024px;
margin: auto;
position: relative;
z-index: 2;
padding-left: ${theme.space[3]}px;
@media (min-width: ${theme.breakpoints[2]}em) {
padding: 0 ${theme.space[4]}px;
}
`
const hoverColor = name =>
@ -74,7 +68,7 @@ const hoverColor = name =>
slate: 'black',
black: 'slate',
primary: 'error'
})[name] || 'black'
}[name] || 'black')
const slide = keyframes({
from: { transform: 'translateY(-25%)', opacity: 0 },
@ -99,7 +93,6 @@ const layout = props =>
font-weight: bold;
font-size: ${theme.fontSizes[2]}px;
width: 100vw;
max-width: 18rem;
&:not(:last-child) {
border-bottom: 1px solid rgba(48, 48, 48, 0.125);
}
@ -114,8 +107,7 @@ const layout = props =>
justify-content: flex-end;
}
a {
font-size: ${theme.fontSizes[1]}px;
text-transform: uppercase;
font-size: 18px;
&:hover {
color: ${theme.colors[hoverColor(props.color)]};
}
@ -125,7 +117,7 @@ const NavBar = styled(Box)`
display: none;
${layout};
a {
margin-left: ${theme.space[3]}px;
margin-left: ${theme.space[1]}px;
padding: ${theme.space[3]}px;
text-decoration: none;
@media (min-width: 56em) {
@ -135,11 +127,12 @@ const NavBar = styled(Box)`
`
const Navigation = props => (
// REMINDER: This should be no more than 7 links :)
<NavBar role="navigation" {...props}>
<NextLink href="/clubs" passHref>
<Link>Clubs</Link>
</NextLink>
<NextLink href="/hcb" passHref>
<NextLink href="/fiscal-sponsorship" passHref>
<Link>Fiscal&nbsp;Sponsorship</Link>
</NextLink>
<NextLink href="/hackathons" passHref>
@ -149,7 +142,7 @@ const Navigation = props => (
<Link>Community</Link>
</NextLink>
<Link href="https://scrapbook.hackclub.com/">Scrapbook</Link>
<Link href="https://jams.hackclub.com/">Workshops</Link>
<Link href="https://toolbox.hackclub.com/">Toolbox</Link>
<NextLink href="/onboard" passHref>
<Link>OnBoard</Link>
</NextLink>
@ -205,13 +198,13 @@ function Header({ unfixed, color, bgColor, dark, fixed, ...props }) {
const baseColor = dark
? color || 'white'
: color === 'white' && scrolled
? 'black'
: color
? 'black'
: color
const toggleColor = dark
? color || 'snow'
: toggled || (color === 'white' && scrolled)
? 'slate'
: color
? 'slate'
: color
return (
<Root

256
components/onboard/recap.js Normal file
View file

@ -0,0 +1,256 @@
import { useEffect, useRef } from "react"
import usePrefersReducedMotion from '../../lib/use-prefers-reduced-motion'
import { Box, Grid } from 'theme-ui'
import sleep from "../../lib/sleep"
const dimBg = '#151515'
// "LET'S RECAP" pixel art (exported from Piskel)
// Original: https://doggo.ninja/fiK0nk.piskel
const recapWidth = 71
const recapHeight = 10
const recapPixels = [
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff
]
const Recap = () => {
const prefersReducedMotion = usePrefersReducedMotion()
// Fancy lights animation
const lightsScrollTrigger = useRef()
const lightsAnimated = useRef(false)
useEffect(() => {
let canceled = false
const setAtIndex = (i, color) => {
if (canceled) return
// Going outside of React for performance
const el = document.getElementById(`pixel-${i}`)
if (!el) return
if (recapPixels[i]) {
el.style.background = color
el.style.boxShadow = `0 0 10px ${color}`
} else {
el.style.background = dimBg
el.style.boxShadow = 'none'
}
}
const setAll = color => {
for (let i = 0; i < recapPixels.length; i++) setAtIndex(i, color)
}
const animate = async () => {
if (lightsAnimated.current) return
lightsAnimated.current = true
// Illuminate lights in diagonal lines starting with only top left.
for (
let curColumn = 0;
curColumn < recapWidth + recapHeight;
curColumn++
) {
for (
let offset = curColumn;
offset >= Math.max(0, curColumn - recapHeight);
offset--
) {
const i = curColumn * recapWidth + offset - offset * recapWidth
setAtIndex(i, '#ffffff')
if (!recapPixels[i]) await sleep(4)
if (canceled) return
}
// await sleep(2); if (canceled) return
}
// Flash the lights twice
await sleep(600)
if (canceled) return
setAll(dimBg)
await sleep(80)
if (canceled) return
setAll('#aaaaaa')
await sleep(20)
if (canceled) return
setAll(dimBg)
await sleep(30)
if (canceled) return
setAll('#aaaaaa')
await sleep(100)
if (canceled) return
setAll(dimBg)
await sleep(200)
if (canceled) return
// Animate rainbow 2-column increments
for (let x = 0; x < recapWidth; x++) {
const color = `hsl(${(x * 360) / recapWidth}, 100%, 65%)`
for (let y = 0; y < recapHeight; y++) {
const i = y * recapWidth + x
setAtIndex(i, color)
}
if (x % 2 === 1) await sleep(35)
}
}
if (prefersReducedMotion) {
if (!lightsAnimated.current) setAll('#ffffff')
return () => (canceled = true)
} else {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) animate()
},
{ threshold: 0.5 }
)
observer.observe(lightsScrollTrigger.current)
return () => {
canceled = true
observer.disconnect()
}
}
}, [prefersReducedMotion])
return (
<Grid
ref={lightsScrollTrigger}
gap={['2px', '3px', '4px']}
columns={recapWidth}
sx={{ width: '100%', maxWidth: 800 }}
>
{recapPixels.map((_, i) => (
<Box
id={`pixel-${i}`}
key={i}
sx={{ bg: dimBg, paddingTop: '100%' }}
/>
))}
</Grid>
)
}
export default Recap

View file

@ -34,7 +34,7 @@ const Stat = ({
as="span"
sx={{
color,
fontSize: lg ? [5, 6, 7] : sm ? [3, 4] : [4, 5, 6],
fontSize: lg ? [5, 6, 7] : sm ? [3, 4] : [5, 6],
fontWeight: 'heading',
letterSpacing: 'title',
my: 0
@ -78,7 +78,7 @@ const Stat = ({
as="span"
variant="caption"
sx={{
fontSize: lg ? [1, 2, 3] : [0, 1],
fontSize: lg ? [1, 2, 3] : 1,
letterSpacing: 'headline',
textTransform: 'uppercase'
}}

View file

@ -88,7 +88,7 @@ export default function Signup() {
const handleSubmit = async e => {
e.preventDefault()
await fetch('/api/hcb/demo', {
await fetch('/api/fiscal-sponsorship/demo', {
method: 'POST',
body: JSON.stringify({
eventName,
@ -109,7 +109,7 @@ export default function Signup() {
<Base
id="form"
method="POST"
action="/api/hcb/demo"
action="/api/fiscal-sponsorship/demo"
onSubmit={handleSubmit}
>
<Field

View file

@ -17,15 +17,6 @@
"img": "https://cloud-eg2ex8nol-hack-club-bot.vercel.app/0favicon-on-light-removebg-preview.png",
"link": "https://cpu.land"
},
{
"background": "linear-gradient(to bottom right, #91C2F4, #FFB6E6, #F3E4C6)",
"titleColor": "#000",
"descriptionColor": "#000",
"title": "Wonderland",
"description": "How would you and your friends use a 🥕 carrot or a 🧸 stuffed animal in a hackathon project?",
"img": "/wonderland/logo.svg",
"link": "https://wonderland.hackclub.com"
},
{
"background": "#000",
"titleColor": "green",

View file

@ -1,83 +0,0 @@
async function getOrRefreshToken() {
// Get the token from localStorage
const token = JSON.parse(localStorage.getItem('mapkit-token'))
if (token) {
// If the token is still valid, return it
if (Date.now() < token.refreshBefore) return token.accessToken
} else {
// The token is invalid or doesn't exist, so get a new one
// This is a MapKit master token, restricted to https://hackclub.com
const master =
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkNSQkg2S1VLTEIifQ.eyJpc3MiOiJQNlBWMlI5NDQzIiwiaWF0IjoxNjc5NjY3NjIyLCJleHAiOjE3MTEyMzg0MDB9.E6g9QPdbEWLgF6tdcL0YfX8NescYnwKhQpXdiyRitNm7-Oot-3VH0ze9xUd8xkOzuS_-7KeWy4bfYTD-2yX7Sg'
// Get a MapKit server token
const res = await fetch('https://maps-api.apple.com/v1/token', {
headers: { Authorization: `Bearer ${master}` }
})
const resJson = await res.json()
// Set the token's expiration time to 10 seconds before the actual expiration time
resJson.refreshBefore =
Date.now() + resJson.expiresInSeconds * 1_000 - 10_000
// Save the token to localStorage
localStorage.setItem('mapkit-token', JSON.stringify(resJson))
return resJson.accessToken
}
}
//TODO: Limit the number of retries
export async function search(query) {
if (!query || !query.trim()) return
const token = await getOrRefreshToken()
const res = await fetch(`https://maps-api.apple.com/v1/search?q=${query}`, {
headers: { Authorization: `Bearer ${token}` }
})
const resJson = await res.json()
if (resJson.error) {
if (resJson.error.message === 'Not Authorized') {
// The token is invalid, so remove it from localStorage
localStorage.removeItem('mapkit-token')
// Try again
console.warn('MapKit token expired, refreshing')
return search(query)
} else {
throw new Error(resJson.error.message)
}
}
return resJson
}
export async function geocode(query) {
if (!query || !query.trim()) return
const token = await getOrRefreshToken()
const res = await fetch(`https://maps-api.apple.com/v1/geocode?q=${query}`, {
headers: { Authorization: `Bearer ${token}` }
})
const resJson = await res.json()
if (resJson.error) {
if (resJson.error.message === 'Not Authorized') {
// The token is invalid, so remove it from localStorage
localStorage.removeItem('mapkit-token')
// Try again
console.warn('MapKit token expired, refreshing')
return geocode(query)
} else {
throw new Error(resJson.error.message)
}
}
return resJson
}

4
lib/sleep.js Normal file
View file

@ -0,0 +1,4 @@
// Beloved classic utility function :3
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
export default sleep

View file

@ -15,7 +15,7 @@ const nextConfig = {
'scrapbook.hackclub.com',
'assets.hackclub.com',
'v5.airtableusercontent.com',
''
'hcb.hackclub.com'
],
remotePatterns: [
{
@ -34,6 +34,16 @@ const nextConfig = {
destination: '/hcb/:path*',
permanent: true
},
{
source: '/hcb/fiscal-sponsorship/',
destination: '/fiscal-sponsorship/about/',
permanent: false
},
{
source: '/hcb/:path*',
destination: '/fiscal-sponsorship/:path*',
permanent: false
},
{ source: '/grant/', destination: '/hackathons/grant', permanent: false },
{
source: '/sprig/',

View file

@ -12,8 +12,8 @@
"format": "prettier --write ."
},
"dependencies": {
"@apollo/client": "^3.8.8",
"@emotion/react": "^11.11.1",
"@apollo/client": "^3.9.5",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@github/time-elements": "^4.0.0",
"@hackclub/icons": "^0.0.14",
@ -21,9 +21,9 @@
"@hackclub/meta": "1.1.32",
"@hackclub/theme": "^0.3.3",
"@mdx-js/loader": "^1.6.22",
"@next/mdx": "^14.0.3",
"@next/mdx": "^14.1.0",
"@octokit/auth-app": "^6.0.1",
"@octokit/core": "^5.0.1",
"@octokit/core": "^5.1.0",
"@octokit/rest": "^20.0.2",
"@sendgrid/mail": "^8.1.1",
"@vercel/kv": "^1.0.1",
@ -32,10 +32,11 @@
"airtable-plus": "^1.0.4",
"animated-value": "^0.2.4",
"animejs": "^3.2.2",
"axios": "^1.6.2",
"axios": "^1.6.7",
"cookies-next": "^4.0.0",
"country-list": "^2.3.0",
"country-list-js": "^3.1.8",
"cursor-effects": "^1.0.12",
"cursor-effects": "^1.0.15",
"date-fns": "^2.30.0",
"devtools-detect": "^4.0.1",
"form-data": "^4.0.0",
@ -67,10 +68,10 @@
"react-ticker": "^1.3.2",
"react-tooltip": "^4.5.1",
"react-tsparticles": "^2.12.2",
"react-use-websocket": "^4.5.0",
"react-use-websocket": "^4.7.0",
"react-wrap-balancer": "^1.1.0",
"recharts": "2.1.12",
"styled-components": "^6.1.1",
"styled-components": "^6.1.8",
"swr": "^2.2.4",
"theme-ui": "^0.14",
"tinytime": "^0.2.6",
@ -79,8 +80,8 @@
"yarn": "^1.22.21"
},
"devDependencies": {
"eslint": "8.55.0",
"eslint-config-next": "14.0.3",
"prettier": "^3.2.2"
"eslint": "8.57.0",
"eslint-config-next": "14.1.0",
"prettier": "^3.0.3"
}
}

View file

@ -508,7 +508,7 @@ const Page = () => (
desc={
<>
Use our 501(c)(3) status and a restricted fund with{' '}
<Link href="/hcb">HCB</Link> to fundraise, accept donations, and
<Link href="/fiscal-sponsorship">HCB</Link> to fundraise, accept donations, and
buy things!
</>
}

View file

@ -27,7 +27,7 @@ _(To summarize: were a donor-supported nonprofit so outside of planned grant
[Hack Club](https://hackclub.com/) is an independent nonprofit, supported by donors, with a responsibility to our supporters to spend our budget directly on our core programs. Therefore, as much as wed like to, **were not in a position to provide financial support to hackathons/other nonprofits**. We occasionally run grant programs, like the [$500 grant for IRL high school hackathons](https://hackclub.com/hackathons/grant). We wish you the best of luck in your sponsorship search—we recommend [Megan Cuis “Meginar”](https://youtu.be/tOmXzA4reTY) and [Lachlan Campbells Flagship talk](https://notebook.lachlanjc.com/2020-01-19_how_to_start_your_first_hackathon/) if youre looking for advice.
If youre looking for [fiscal sponsorship](https://en.wikipedia.org/wiki/Fiscal_sponsorship) (aka becoming a legal nonprofit), weve got your back—[HCB](https://hackclub.com/hcb/) will set you up with a nonprofit fund, legal entity, debit cards for your team, automated taxes/accounting, G Suite, an online donation form, ACH/check sending/receiving, discounts on stickers & software for your team, and great support. There are no upfront fees. Sign up at <https://hackclub.com/hcb/>.
If youre looking for [fiscal sponsorship](https://en.wikipedia.org/wiki/Fiscal_sponsorship) (aka becoming a legal nonprofit), weve got your back—[HCB](https://hackclub.com/fiscal-sponsorship/) will set you up with a nonprofit fund, legal entity, debit cards for your team, automated taxes/accounting, G Suite, an online donation form, ACH/check sending/receiving, discounts on stickers & software for your team, and great support. There are no upfront fees. Sign up at <https://hackclub.com/fiscal-sponsorship/>.
## If youre running a hackathon…

View file

@ -24,7 +24,7 @@ A totally transparent nonprofit not only shows others how to do it. It also incr
While a bunch of transactions in a bank account is great, I want to summarize Hack Club's spending in May 2020 publicly. This is something that I wish other nonprofits did when Hack Club was first getting started.
The below summary was calculated from HQ's export from [HCB](https://hackclub.com/hcb/). You can see our full current account at https://hcb.hackclub.com/hq/ using HCB's new [Transparency Mode](https://twitter.com/hackclub/status/1262471150963130374).
The below summary was calculated from HQ's export from [HCB](https://hackclub.com/fiscal-sponsorship/). You can see our full current account at https://hcb.hackclub.com/hq/ using HCB's new [Transparency Mode](https://twitter.com/hackclub/status/1262471150963130374).
### Revenue (total $20,621.78)

View file

@ -0,0 +1,493 @@
import { Box, Card, Container, Flex, Link, Text } from 'theme-ui'
import { useEffect, useRef, useState } from 'react'
import { keyframes } from '@emotion/react'
import FlexCol from '../../components/flex-col'
import Meta from '@hackclub/meta'
import Head from 'next/head'
import ForceTheme from '../../components/force-theme'
import Nav from '../../components/nav'
import Footer from '../../components/footer'
import Icon from '../../components/icon'
import Tilt from '../../components/tilt'
function Bullet({ glow = true, icon, href, children }) {
let effectColours = [
'#ec3750',
'#ff8c37',
'#f1c40f',
'#33d6a6',
'#5bc0de',
'#338eda',
'#a633d6'
]
function keyframeGenerator(spread, blur, colours, opacity = 0.5) {
let hexOpacity = Math.max(Math.min(Math.round(opacity * 255), 255), 0)
.toString(16)
.padStart(2, '0')
let final = {}
for (let i = 0; i <= 100; i++) {
let baseX = Math.sin((i * Math.PI) / 50) // 50 keyframes for each pi radians
let baseY = -Math.cos((i * Math.PI) / 50)
// Ensure no scientific notation
const roundFactor = 1_000_000
baseX = Math.round(baseX * roundFactor) / roundFactor
baseY = Math.round(baseY * roundFactor) / roundFactor
let boxShadow = ''
for (let c = 0; c < colours.length; c++) {
// Rotate by 2pi / colours.length * c radians
let x =
baseX * Math.cos((2 * Math.PI * c) / colours.length) -
baseY * Math.sin((2 * Math.PI * c) / colours.length)
let y =
baseX * Math.sin((2 * Math.PI * c) / colours.length) +
baseY * Math.cos((2 * Math.PI * c) / colours.length)
boxShadow += `${x * spread}px ${y * spread}px ${blur}px ${
colours[c]
}${hexOpacity},`
}
// Remove trailing comma
boxShadow = boxShadow.slice(0, -1)
final[i + '%'] = { boxShadow }
}
return final
}
const shadowSpread = glow ? 5 : 0
const shadowBlur = glow ? 10 : 0
const animatedBoxShadow = keyframes(
keyframeGenerator(shadowSpread, shadowBlur, effectColours)
)
const borderWidth = '2px'
return (
<Tilt>
<Flex
as="a"
{...(href && { href })}
target="_blank"
sx={{
display: 'flex',
alignItems: 'center',
gap: 2,
width: '20rem',
borderWidth,
borderRadius: '8px !important',
p: '8px !important',
textDecoration: 'none',
color: 'inherit',
backgroundColor: 'var(--theme-ui-colors-darkless)',
'&:hover::after': {
content: '""',
position: 'absolute',
inset: 0,
boxShadow: `linear-gradient(60deg, ${effectColours[0]}, ${effectColours[1]}, ${effectColours[2]}, ${effectColours[3]}, ${effectColours[4]})`,
borderRadius: 'inherit',
zIndex: -1,
animation: `${animatedBoxShadow} 5s ease infinite`
}
}}
>
<Icon glyph={icon} size={42} sx={{ color: 'red', flexShrink: 0 }} />
<Box sx={{ textAlign: 'left' }}>{children}</Box>
{href && (
<Icon
glyph="external-fill"
size={32}
sx={{
position: 'absolute',
top: 0,
right: 0,
translate: '50% -50%',
color: 'muted'
}}
/>
)}
</Flex>
</Tilt>
)
}
function BulletBox({ padding = '2rem', children }) {
return (
<Box
as="ul"
sx={{
width: '100%',
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
gridGap: '2rem',
padding
}}
>
{children}
</Box>
)
}
function Section({ id, children }) {
return (
<Flex as="section" id={id} sx={{ flexDirection: 'column', pt: 5 }}>
{children}
</Flex>
)
}
export default function FiscalSponsorship() {
const gridRef = useRef()
const glowRef = useRef()
const scrollPos = useRef(0)
const mousePos = useRef([0, 0])
const setGlowMaskPosition = () => {
const finalPos = [
-mousePos.current[0],
-mousePos.current[1] + scrollPos.current
]
glowRef.current.style.maskPosition = `${finalPos[0]}px ${finalPos[1]}px`
glowRef.current.style.WebkitMaskPosition = `${finalPos[0]}px ${finalPos[1]}px`
}
useEffect(() => {
const handleScroll = e => {
scrollPos.current = -window.scrollY / 10
gridRef.current.style.transform = `translateY(${scrollPos.current}px)`
setGlowMaskPosition()
}
const handleMouseMove = e => {
const x = e.clientX
const y = e.clientY
glowRef.current.style.left = x + 'px'
glowRef.current.style.top = y + 'px'
mousePos.current = [x, y]
setGlowMaskPosition()
}
window.addEventListener('scroll', handleScroll)
window.addEventListener('mousemove', handleMouseMove)
return () => {
window.removeEventListener('scroll', handleScroll)
window.removeEventListener('mousemove', handleMouseMove)
}
}, [])
return (
<Box as="main" key="main" sx={{ position: 'relative' }}>
<style>
{`* {
scroll-behavior: smooth;
}
p {
text-wrap: balance;
}
`}
</style>
<Box
ref={gridRef}
sx={{
position: 'fixed',
inset: 0,
height: '1000%',
zIndex: -2,
backgroundSize: '20px 20px',
backgroundImage: `linear-gradient(to right, #23262D 1px, transparent 1px),
linear-gradient(to bottom, #23262D 1px, transparent 1px) `,
backgroundPosition: 'top left',
maskImage: `linear-gradient(180deg, transparent 0%, white 3%)`,
opacity: 0.5
}}
/>
<Box
ref={glowRef}
sx={{
pointerEvents: 'none',
zIndex: -2,
position: 'fixed',
top: '0',
left: '0',
width: '20rem',
height: '20rem',
background: 'red',
opacity: 0.2,
borderRadius: '50%',
filter: 'blur(2rem)',
translate: '-50% -50%',
// Mask it to the grid background
maskImage: `linear-gradient(to right, #23262D 1px, transparent 1px),
linear-gradient(to bottom, #23262D 1px, transparent 1px) `,
maskSize: '20px 20px, 20px 20px, cover',
maskPosition: '0px 0px'
}}
/>
<Nav dark />
<ForceTheme theme="dark" />
<Meta
as={Head}
title="Fiscal Sponsorship"
description="What is fiscal sponsorship?"
image="/fiscal-sponsorship/og-image.png"
>
<title>Fiscal Sponsorship | HCB</title>
</Meta>
<Container
variant="copy"
sx={{ mt: 6, display: 'flex', flexDirection: 'column', gap: 3 }}
>
<FlexCol gap={4}>
<Text variant="title" as="h1">
Fiscal sponsorship:
<br />
<Text
sx={{
color: 'red',
textShadow: '0 0 50px var(--theme-ui-colors-red)'
}}
>
501(c)(3)&nbsp;nonprofit{' '}
</Text>
status in just 24 hours
</Text>
</FlexCol>
<Text variant="headline" as="p" sx={{ fontWeight: 'body' }}>
Organizing an event, project, or organization to serve the public good
or your community? Consider fiscal sponsorship before the pain of
paperwork distracts you from your goals.
</Text>
<FlexCol gap={1} alignItems="center">
<Text variant="headline">Jump to:</Text>
<BulletBox padding="0">
<Link sx={{ fontSize: 2 }} href="#costs-and-perks">
Costs and perks of 501(c)(3) status
</Link>
<Link sx={{ fontSize: 2 }} href="#what-is">
How fiscal sponsorship works
</Link>
<Link sx={{ fontSize: 2 }} href="#requirements">
Requirements for fiscal sponsorship
</Link>
<Link sx={{ fontSize: 2 }} href="#partner">
HCB, the #1 fiscal sponsor
</Link>
</BulletBox>
</FlexCol>
<Section id="costs-and-perks">
<Text variant="title" as="h2">
Why organizers go after 501(c)(3) status
</Text>
<Text variant="lead">
Every year, 1.6 million nonprofits in the U.S. apply for and renew
501(c)(3) status through the IRS for charitable recognition and tax
exemption for their funding. It can take anywhere from 2-12 months
to hear a decision back from the IRS, and in general, nonprofit
organizers should be prepared for:
</Text>
<BulletBox>
<Bullet glow={false} icon="sad">
$3,000 in <b>up-front costs</b>, from
<Link href="https://www.irs.gov/charities-non-profits/form-1023-and-1023-ez-amount-of-user-fee">
{' '}
forms{' '}
</Link>
to state fees to support from legal counsel
</Bullet>
<Bullet glow={false} icon="sad">
The potential for the IRS to <b>reject</b> an application
</Bullet>
<Bullet glow={false} icon="sad">
<b>Hiring</b> bookkeepers and accountants to prepare taxes and
provide upkeep annually to stay in good standing
</Bullet>
<Bullet glow={false} icon="sad">
<b>Closing costs</b> averaging around $5,000 if you lose or
terminate status
</Bullet>
</BulletBox>
</Section>
<Text variant="lead">
Though its expensive and time consuming to apply, being a
legally-recognized 501(c)(3) nonprofit in the U.S., your organization
gains:
</Text>
<BulletBox>
<Bullet icon="payment">
The ability to receive <b>tax deductible donations</b> from
sponsors.
</Bullet>
<Bullet icon="member-add">
Reduced taxable income for your U.S. supporters, which
<b> incentivizes giving</b>.
</Bullet>
<Bullet icon="leader">
<b>Exemption</b> from U.S. federal income tax and unemployment tax.
</Bullet>
<Bullet icon="bolt">
Potential exemption from state income, sales, and employment taxes.
</Bullet>
<Bullet icon="email">
Potential for reduced rates on postage, marketing, advertising,
legal counsel, and more.
</Bullet>
</BulletBox>
<Text variant="lead">
Unfortunately between the costs and time needed to organize a
nonprofit, many charitable initiatives are prevented from exiting an
idea phase or progressing at a pace originally hoped. Imagine how much
more valuable impact could happen on the world if these barriers
didnt exist.
</Text>
<Text variant="lead">
As it turns out, theres an alternative route for startups,
student-led initiatives, or anyone looking to avoid a headache with
the IRS to obtain all the benefits of 501(c)(3) status. Thats where
fiscal sponsorship comes in.
</Text>
<Section id="what-is">
<Text variant="title" as="h2">
Fiscal sponsorship?
</Text>
<Text variant="lead">
By legally working with an existing nonprofit offering fiscal
sponsorship, projects and events can claim most of the legal
benefits of individual 501(c)(3) status. Piggy-backing off this
existing status, organizations also gain access to resources from
their fiscal sponsor like:
</Text>
<BulletBox>
<Bullet icon="docs">
Bookkeeping and administration to ensure that all paperwork and
taxes are filed
</Bullet>
<Bullet icon="bag">
Fully established HR and benefits, which can vary by the fiscal
sponsor
</Bullet>
<Bullet icon="admin">
Waived responsibility to organize a board of directors
</Bullet>
<Bullet icon="payment">
Fully transparent operational fees, typically ranging from 7-12%
that prevent you from paying typical operating costs.
</Bullet>
<Bullet icon="door-leave">
The ability to terminate your fiscal sponsorship agreement and
file for separate tax-exempt status at any point.
</Bullet>
</BulletBox>
<Text variant="lead">
If youre brand new to nonprofit organizing or unsure where your
project will take you, fiscal sponsorship is a great tool to help
manage your finances and gauge whether becoming an independent
nonprofit down the line is practical or financially feasible.
</Text>
</Section>
<Section id="requirements">
<Text variant="title" as="h2">
Requirements for fiscal sponsorship
</Text>
<Text variant="lead">
Depending on the fiscal sponsor you choose, requirements for working
together can vary. Fiscal sponsors generally ask that your
nonprofits goals be similar to theirs. They also usually ask that
your organization or event commits to remaining charitable in nature
and refrains from activities that may result in loss of 501(c)(3)
status.
</Text>
</Section>
<Section id="partner">
<Text variant="title" as="h2">
HCB, the #1 fiscal sponsor
</Text>
<Text variant="lead">
While many fiscal sponsors require that their partners relate to
their mission in similar ways, at HCB, weve built our
infrastructure to support hundreds of causes in all areas of
charitability.
</Text>
<Text variant="lead">
Check out some of the resources weve built our fiscal sponsorship
foundation on:
</Text>
<BulletBox>
<Bullet icon="bank-account">
A beautiful web interface to manage finances
</Bullet>
<Bullet icon="transactions">
Fee-free invoicing, ACH or check transfers, and reimbursements
</Bullet>
<Bullet icon="link">
A customizable and embeddable donations URL
</Bullet>
<Bullet icon="card-add">
An account & routing number to connect to external platforms, like
Shopify and GoFundMe
</Bullet>
<Bullet icon="purse">
Perks like PVSA certification, newsletter software, and 1Password
credits
</Bullet>
</BulletBox>
<Text variant="lead">
Looking for nonprofit status and not a religious or political
organization? Wed love to meet you and chat about working together.
Feel free to apply
<Link href="https://hackclub.com/fiscal-sponsorship/#apply">
{' '}
here{' '}
</Link>
or
<Link href="mailto:hcb@hackclub.com"> email our team </Link>if you
have more questions about fiscal sponsorship!
</Text>
</Section>
<Text variant="lead">
At its core, Hack Club is a nonprofit encouraging students to learn
how to code by building and making cool things. HCB was built out by
teenagers at Hack&nbsp;Club and continues to be a real-world space
that high schoolers can contribute to every day.
</Text>
</Container>
<Box
sx={{
height: '100px',
position: 'relative',
width: '100%',
overflow: 'hidden',
'&::after': {
content: '""',
width: '500%',
height: '100%',
position: 'absolute',
translate: '-50% 100%',
boxShadow: '0 -64px 64px #17171d'
}
}}
/>
<Footer dark />
</Box>
)
}

View file

@ -0,0 +1,149 @@
import { useState, useRef } from 'react'
import { useRouter } from 'next/router'
import { Box, Text, Flex, Heading, Grid, Alert, Button } from 'theme-ui'
import ForceTheme from '../../../components/force-theme'
import Head from 'next/head'
import Meta from '@hackclub/meta'
import { onSubmit } from '../../../components/fiscal-sponsorship/apply/submit'
import Watermark from '../../../components/fiscal-sponsorship/apply/watermark'
import FormContainer from '../../../components/fiscal-sponsorship/apply/form-container'
import HCBInfo from '../../../components/fiscal-sponsorship/apply/hcb-info'
import OrganizationInfoForm from '../../../components/fiscal-sponsorship/apply/org-form'
import PersonalInfoForm from '../../../components/fiscal-sponsorship/apply/personal-form'
import Icon from '@hackclub/icons'
import Link from 'next/link'
export default function Apply() {
const router = useRouter()
const formContainer = useRef()
const [formError, setFormError] = useState(null)
const [isSubmitting, setIsSubmitting] = useState(false)
const requiredFields = [
'eventName',
'eventLocation',
'eventDescription',
'firstName',
'lastName',
'userEmail',
'userBirthday',
'slackUsername'
]
return (
<>
<Meta as={Head} title="Apply for HCB" />
<ForceTheme theme="light" />
<Grid
columns={[null, null, 2]}
sx={{
gap: 0,
width: '100%',
minHeight: '100vh',
alignItems: 'start'
}}
>
<Flex
sx={{
flexDirection: 'column',
justifyContent: 'space-between',
px: [3, 5],
py: 5,
gap: [4, 5],
height: [null, '100svh'],
position: [null, null, 'sticky'],
top: 0,
overflowY: [null, null, 'auto']
}}
>
{/* vertically align h1 to top of form */}
<Box as="header" sx={{ mt: [null, null, -24] }}>
<Link href="/fiscal-sponsorship" passHref legacyBehavior>
<Text
as="a"
variant="subheadline"
sx={{
mb: 3,
gap: 2,
display: 'flex',
alignItems: 'center',
color: 'muted',
textDecoration: 'none',
':hover': { color: 'primary' }
}}
>
<Icon
size={24}
glyph="inserter"
style={{ transform: 'rotate(180deg)' }}
/>
Back
</Text>
</Link>
<Heading as="h1" variant="title">
Apply to join
<br />
<Flex sx={{ alignItems: 'center', gap: 3 }}>
<img
src="/fiscal-sponsorship/hcb-icon-small.png"
width={48}
height={48}
alt="HCB logo"
style={{
width: '1em',
height: '1em',
verticalAlign: 'baseline'
}}
/>{' '}
HCB
</Flex>
</Heading>
</Box>
<HCBInfo />
</Flex>
<FormContainer
ref={formContainer}
className={formError ? 'has-errors' : null}
onSubmit={event =>
onSubmit({
event,
router,
form: formContainer,
setFormError,
setIsSubmitting,
requiredFields
})
}
>
<Heading as="h2" variant="headline" sx={{ mb: -2 }}>
Your organization
</Heading>
<OrganizationInfoForm requiredFields={requiredFields} />
<Heading as="h2" variant="headline" sx={{ mb: -2 }}>
Personal details
</Heading>
<PersonalInfoForm requiredFields={requiredFields} />
{formError && <Alert bg="primary">{formError}</Alert>}
<Button
variant="ctaLg"
type="submit"
disabled={isSubmitting}
sx={{
backgroundImage: theme => theme.util.gx('cyan', 'blue'),
'&:disabled': {
background: 'muted',
cursor: 'not-allowed',
transform: 'none !important'
},
width: 'fit-content'
}}
>
{isSubmitting ? 'Submitting…' : 'Submit'}
</Button>
</FormContainer>
</Grid>
<Watermark />
</>
)
}

View file

@ -0,0 +1,75 @@
import { useEffect } from 'react'
import { Box, Container, Text, Link, Flex, Image } from 'theme-ui'
import JSConfetti from 'js-confetti'
import Icon from '../../../components/icon'
import { Balancer } from 'react-wrap-balancer'
function fireConfetti() {
const jsConfetti = new JSConfetti()
jsConfetti.addConfetti({
confettiColors: [
// Hack Club colours!
'#ec3750',
'#ff8c37',
'#f1c40f',
'#33d6a6',
'#5bc0de',
'#338eda',
'#a633d6'
]
})
}
export default function ApplicationSuccess() {
useEffect(() => {
fireConfetti()
}, [])
return (
<Container
variant="narrow"
sx={{
height: '100svb',
textAlign: 'center',
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
py: 5,
pt: [null, null, 6],
gap: 4
}}
>
<header>
<Image
src="/fiscal-sponsorship/apply/party-orpheus.svg"
alt="Dinosaur partying"
onClick={fireConfetti}
sx={{ width: '40%' }}
/>
<Text as="h1" variant="title" mt={4}>
Thanks for applying!
</Text>
<Text as="p" variant="lead">
<Balancer>
Well review your application and get back to you within two
business days.
</Balancer>
</Text>
</header>
<footer>
<Text as="h2" variant="subheadline">
Questions about your application?
</Text>
<Text as="p" fontSize={2} color="muted">
You can always email us at{' '}
<Link href="mailto:hcb@hackclub.com" color="blue">
hcb@hackclub.com
</Link>
.
</Text>
</footer>
</Container>
)
}

View file

@ -16,7 +16,7 @@ import Footer from '../../../components/footer'
import MSparkles from '../../../components/sparkles/money'
import { Text, Button, Card } from 'theme-ui'
import Icon from '@hackclub/icons'
import OrganizationCard, { Badge } from '../../../components/hcb/directory/card'
import OrganizationCard, { Badge } from '../../../components/fiscal-sponsorship/directory/card'
import Zoom from 'react-reveal/Zoom'
import fuzzysort from 'fuzzysort'
import ScrollHint from '../../../components/scroll-hint'
@ -25,7 +25,7 @@ import { useEffect, useState } from 'react'
import NextLink from 'next/link'
import { kebabCase, intersection } from 'lodash'
import theme from '@hackclub/theme'
import Tooltip from '../../../components/hcb/tooltip'
import Tooltip from '../../../components/fiscal-sponsorship/tooltip'
const styles = `
html {
@ -153,8 +153,8 @@ const FilterPanel = ({ filter, mobile }) => {
cursor: mobile ? 'pointer' : 'default',
':hover': mobile
? {
color: 'blue'
}
color: 'blue'
}
: {}
}}
onClick={() => setHiddenOnMobile(!hiddenOnMobile)}
@ -245,7 +245,7 @@ const FilterPanel = ({ filter, mobile }) => {
textDecoration: 'none',
color:
currentSelections.length === baseData.length ||
!currentSelections.includes(item.id)
!currentSelections.includes(item.id)
? 'black'
: 'primary',
transition: 'color 0.2s',
@ -325,8 +325,8 @@ const RegionPanel = ({ currentRegion, mobile }) => {
cursor: mobile ? 'pointer' : 'default',
':hover': mobile
? {
color: 'blue'
}
color: 'blue'
}
: {}
}}
onClick={() => setHiddenOnMobile(!hiddenOnMobile)}
@ -350,7 +350,7 @@ const RegionPanel = ({ currentRegion, mobile }) => {
display: hiddenOnMobile ? 'none' : 'flex'
}}
>
<NextLink scroll={false} href={'/hcb/climate'}>
<NextLink scroll={false} href={'/fiscal-sponsorship/climate'}>
<Flex
sx={{
alignItems: 'center',
@ -403,7 +403,7 @@ const RegionPanel = ({ currentRegion, mobile }) => {
<NextLink
key={idx}
scroll={false}
href={`/hcb/climate/organizations-in-${kebabCase(item.label)}`}
href={`/fiscal-sponsorship/climate/organizations-in-${kebabCase(item.label)}`}
>
<Flex
sx={{
@ -485,7 +485,7 @@ export default function ClimatePage({ rawOrganizations, pageRegion }) {
const region = pageRegion
// useEffect(() => {
// // history.pushState(null, null, `/hcb/climate/organizations-in-${region.toLowerCase().split(' ').join('-')}`);
// // history.pushState(null, null, `/fiscal-sponsorship/climate/organizations-in-${region.toLowerCase().split(' ').join('-')}`);
// }, [region]);
const [modalOrganization, setModalOrganization] = useState(null)
@ -965,7 +965,7 @@ export default function ClimatePage({ rawOrganizations, pageRegion }) {
viewBox="0 0 512 512"
>
<img
src="/hcb/climate/earth-on-hcb.svg"
src="/fiscal-sponsorship/climate/earth-on-hcb.svg"
alt=""
height="82px"
/>
@ -1135,7 +1135,7 @@ export default function ClimatePage({ rawOrganizations, pageRegion }) {
return (
currentBadges.length === badges.length ||
intersection(organizationBadgeIds, currentBadges).length ===
currentBadges.length
currentBadges.length
)
})
.map(organization => (
@ -1401,7 +1401,7 @@ export async function fetchRawClimateOrganizations() {
while (lastLength >= 100) {
const json = await fetch(
'https://hcb.hackclub.com/api/v3/directory/organizations?per_page=100&page=' +
page
page
).then(res => res.json())
lastLength = json.length
page++

View file

@ -15,13 +15,12 @@ import ForceTheme from '../../components/force-theme'
import Nav from '../../components/nav'
import Footer from '../../components/footer'
import Icon from '../../components/icon'
import Features from '../../components/hcb/first/features'
import Features from '../../components/fiscal-sponsorship/first/features'
import Form from '../../components/hcb/first/form'
import Testimonials from '../../components/hcb/first/testimonials'
import Steps from '../../components/hcb/first/steps'
import Start from '../../components/hcb/start'
import Testimonials from '../../components/fiscal-sponsorship/first/testimonials'
import Start from '../../components/fiscal-sponsorship/first/start'
import theme from '@hackclub/theme'
import { Balancer } from 'react-wrap-balancer'
export default function First({ stats }) {
return (
@ -35,7 +34,7 @@ export default function First({ stats }) {
as={Head}
title="Finanical Toolkit for FIRST Teams"
description="HCB is the ultimate financial tool for FRC, FTC, and FLL teams to receive grants, fundraise, make purchases, and so much more."
image="/hcb/first/og-image.png"
image="/fiscal-sponsorship/first/og-image.png"
>
<title>Financial Toolkit for FIRST Teams | HCB</title>
</Meta>
@ -58,36 +57,39 @@ export default function First({ stats }) {
alignItems: 'center'
}}
>
<Box
<Container
sx={{
width: '100%',
mx: 'auto',
px: '16px',
px: 3,
backdropFilter: 'blur(1.5px)'
}}
>
<Heading
as="h1"
variant="ultratitle"
sx={{
textAlign: 'center',
mt: [5, null, 6],
textShadow: '0 0 16px rgba(0, 0, 0, 1)'
textShadow: '0 0 16px rgba(0, 0, 0, 1)',
maxWidth: 'container'
}}
as="h1"
variant="ultratitle"
>
The ultimate financial tool for{' '}
<Text
as="span"
sx={{
WebkitTextStroke: theme => theme.colors.blue,
WebkitTextStrokeWidth: '1px',
WebkitTextFillColor: theme => theme.colors.white,
textShadow: theme => `0 0 12px ${theme.colors.blue}`
}}
>
FRC, FTC, and FLL teams
</Text>
.
<Balancer>
The ultimate financial tool for{' '}
<Text
as="span"
sx={{
WebkitTextStroke: theme => theme.colors.blue,
WebkitTextStrokeWidth: '1px',
WebkitTextFillColor: theme => theme.colors.white,
textShadow: theme => `0 0 12px ${theme.colors.blue}`
}}
>
FRC, FTC, and FLL teams
</Text>
.
</Balancer>
</Heading>
<Badge
variant="pill"
@ -123,20 +125,14 @@ export default function First({ stats }) {
<Icon glyph="checkmark" size={28} color="#33d6A6" />
<Text sx={{ ml: 1 }}>Receive grants</Text>
</Box>
<Box
as="span"
sx={{ display: 'flex', flexDirection: 'row', mr: 4 }}
>
<Icon glyph="checkmark" size={28} color="#33d6A6" />
<Text sx={{ ml: 1 }}>Debit cards</Text>
</Box>
<Box as="span" sx={{ display: 'flex', flexDirection: 'row' }}>
<Icon glyph="checkmark" size={28} color="#33d6A6" />
<Text sx={{ ml: 1 }}>No start-up costs</Text>
<Text sx={{ ml: 1 }}>Debit cards</Text>
</Box>
</Box>
</Badge>
<Container
as="p"
sx={{
fontSize: [2, 3, 3],
textAlign: 'center',
@ -169,7 +165,7 @@ export default function First({ stats }) {
}}
variant="ctaLg"
as="a"
href="/hcb/first/Hack_Club_Bank_for_FIRST_Teams.pdf"
href="/fiscal-sponsorship/first/Hack_Club_Bank_for_FIRST_Teams.pdf"
// @exu3: to edit this PDF, use this Figma file https://www.figma.com/file/LgadOH1lHUBOejA3vaNGgm/Hack-Club-Bank-for-FIRST-Teams-One-Pager?node-id=0%3A1&t=ZtkN2a3aw2IojFvi-1
// message me on Slack if you need edit access
target="_blank"
@ -177,7 +173,7 @@ export default function First({ stats }) {
Download this page
</Button>
</Box>
</Box>
</Container>
</Box>
<Features />

View file

@ -0,0 +1,587 @@
import {
Box,
Card,
Container,
Flex,
Link as UILink,
Heading,
Text,
Grid,
Button
} from 'theme-ui'
import { Balancer } from 'react-wrap-balancer'
import Meta from '@hackclub/meta'
import Head from 'next/head'
import ForceTheme from '../../components/force-theme'
import Nav from '../../components/nav'
import Footer from '../../components/footer'
import Stat from '../../components/stat'
import Tilt from '../../components/tilt'
import Photo from '../../components/photo'
import Image from 'next/image'
import Link from 'next/link'
import OuternetImgFile from '../../public/home/outernet-110.jpg'
import Features from '../../components/fiscal-sponsorship/features'
const organizations = [
{
id: 'org_MpJurQ',
object: 'directory_organization',
name: 'Reboot',
description:
'Publishing techno-optimism, through newsletters, magazines, and events.',
slug: 'reboot',
website: '',
transparent: true,
location: {
readable: 'Bay Area, CA, USA',
country_code: 'US',
country: 'United States',
continent: 'North America'
},
category: 'nonprofit',
missions: [],
climate: false,
partners: {},
logo: '/fiscal-sponsorship/reboot.png',
background_image: '/fiscal-sponsorship/reboot-bg.jpg',
donation_link: 'https://hcb.hackclub.com/donations/start/reboot'
},
{
id: 'org_raices',
object: 'directory_organization',
name: 'Raices Cyber',
description:
'Empowering the Hispanic and Latino cyber and technology community.',
slug: 'raices-cyber-org',
website: 'https://raicescyber.org',
transparent: false,
location: {
readable: 'Philadelphia, PA, USA',
country_code: 'US',
country: 'United States',
continent: 'North America'
},
category: 'nonprofit',
logo: '/fiscal-sponsorship/raices.png',
background_image: '/fiscal-sponsorship/raices-bg.jpg',
donation_link: 'https://hcb.hackclub.com/donations/start/raices-cyber-org'
},
{
id: 'org_XDundl',
object: 'directory_organization',
name: 'Fridays For Future Uganda',
description: 'Leading the environmental justice fight in Uganda.',
slug: 'fridays-for-future-uganda',
website: 'http://www.fridaysforfutureug.earth/',
transparent: true,
location: {
readable: 'Uganda',
country_code: 'UG',
country: 'Uganda',
continent: 'Africa'
},
category: 'nonprofit',
missions: [],
climate: true,
partners: {
'128_collective': {
funded: false,
recommended: true
}
},
logo: '/fiscal-sponsorship/fff-uganda.png',
background_image:
'https://hcb.hackclub.com/storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBc3pJIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--854fedfb94c579a004bd6c8284e55db7b640fa4f/FFF%20Uganda%20photo.jpeg',
donation_link:
'https://hcb.hackclub.com/donations/start/fridays-for-future-uganda'
},
{
id: 'org_a29uVj',
object: 'directory_organization',
name: 'Hack Club HQ',
description: 'This is us! We run our operations on HCB.',
slug: 'hq',
website: 'https://hackclub.com',
transparent: true,
location: {
readable: 'Vermont, USA',
country_code: 'US',
country: 'United States',
continent: 'North America'
},
category: 'hack_club_hq',
missions: [],
climate: false,
partners: {},
logo: 'https://hcb.hackclub.com/storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa3hFIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--2455b72ac87867a346ac5ef38ae2dfdb504af3eb/icon-rounded.png',
background_image:
'https://hcb.hackclub.com/storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaDJrIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--84f67f8b9bd9b31e6703b5bf399f48dd2059e1ad/189933158-9f00ceaf-7f61-4bef-9911-4cf4a14e0e4d.png',
donation_link: 'https://hcb.hackclub.com/donations/start/hq'
}
]
export default function Page() {
return (
<>
<Meta
as={Head}
title="Fiscal Sponsorship"
description="Start your nonprofit with our fiscal sponsorship program, HCB: a 501(c)(3) legal entity, bank account, automatic taxes & accounting, and best-in-class app."
image="/fiscal-sponsorship/og-image.png"
/>
<ForceTheme theme="light" />
<Nav />
<Box
as="header"
sx={{
position: 'relative',
pt: 6,
pb: [4, 5],
bg: 'rgb(104, 41, 205)',
backgroundImage:
'radial-gradient(ellipse at 5% 5%, #ec555c 0%, rgba(236,85,92,0) 75%),radial-gradient(ellipse at 95% 5%, #dc71a1 0%, rgba(220,113,161,0) 75%),radial-gradient(ellipse at 95% 95%, #fcc8bf 0%, rgba(252,200,191,0) 75%),radial-gradient(ellipse at 5% 95%, #ffce33 0%, rgba(255,206,51,0) 75%)'
}}
>
<Box
sx={{
position: 'absolute',
inset: 0,
height: '100%',
zIndex: 0,
backgroundSize: '48px 48px',
backgroundImage: `linear-gradient(to right, #fcc8bf 1px, transparent 1px),
linear-gradient(to bottom, #fcc8bf 1px, transparent 1px)`,
backgroundPosition: 'top center',
maskImage:
'linear-gradient(to bottom, transparent 0%, hsl(0deg 0% 100% / 50%) 10%, white 100%)',
opacity: 0.18
}}
/>
<Container
sx={{
color: 'white',
position: 'relative',
zIndex: 1,
svg: {
position: 'absolute',
right: [3, 0],
bottom: 0,
width: [114, 200, 300],
height: [114, 200, 300],
opacity: 0.5
}
}}
>
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 31C28 31 31 28 31 16C31 4 28 1 16 1C4 1 1 4 1 16C1 28 4 31 16 31Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeDasharray="6 4"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16.0003 8.018C15.9593 8.032 15.8963 8.056 15.8063 8.096C15.5673 8.199 15.2832 8.347 14.9183 8.556C14.1762 8.98 13.2473 9.579 12.2023 10.317C10.9954 11.1683 10.4485 11.5969 9.76695 12.1311L9.76693 12.1311L9.76692 12.1311C9.26201 12.5269 8.68316 12.9806 7.70725 13.707C7.31725 14.098 6.68325 14.098 6.29325 13.707C5.90225 13.317 5.90225 12.683 6.29325 12.293C8.5005 10.5 8.89925 10.201 11.0483 8.683C12.1283 7.921 13.1373 7.27 13.9263 6.819C14.3273 6.59 14.7063 6.394 15.0033 6.264C15.2653 6.149 15.6423 6 16.0003 6C16.3583 6 16.7353 6.149 16.9973 6.264C17.2943 6.394 17.6733 6.59 18.0743 6.819C18.8632 7.27 19.8723 7.921 20.9523 8.683C23.1012 10.201 23.5 10.5 25.7073 12.293C26.0983 12.683 26.0983 13.317 25.7073 13.707C25.3173 14.098 24.6833 14.098 24.2932 13.707C23.3173 12.9806 22.7385 12.5269 22.2336 12.1311C21.552 11.5969 21.0051 11.1683 19.7983 10.317C18.7533 9.579 17.8243 8.98 17.0823 8.556C16.7173 8.347 16.4333 8.199 16.1943 8.096C16.1043 8.056 16.0413 8.032 16.0003 8.018ZM7 24C7 23.448 7.448 23 8 23H24C24.552 23 25 23.448 25 24C25 24.552 24.552 25 24 25H8C7.448 25 7 24.552 7 24ZM15 21C15 21.552 15.448 22 16 22C16.552 22 17 21.552 17 21V14C17 13.448 16.552 13 16 13C15.448 13 15 13.448 15 14V21ZM21 22C20.448 22 20 21.552 20 21V14C20 13.448 20.448 13 21 13C21.552 13 22 13.448 22 14V21C22 21.552 21.552 22 21 22ZM10 21C10 21.552 10.448 22 11 22C11.552 22 12 21.552 12 21V14C12 13.448 11.552 13 11 13C10.448 13 10 13.448 10 14V21Z"
fill="currentColor"
/>
</svg>
<Heading
as="h1"
variant="title"
sx={{
fontSize: [5, 6, null, 7],
// magic number to align with the grid
mt: -3,
lineHeight: [null, null, 1.03],
span: {
WebkitTextStroke: 'currentColor',
WebkitTextStrokeWidth: ['2px', '3px'],
WebkitTextFillColor: 'transparent',
display: 'block'
}
}}
>
<span>The foundation</span> of your nonprofit.
</Heading>
<Text as="p" variant="lead" sx={{ my: [3, 4] }}>
<Balancer>
Start your nonprofit with{' '}
<strong>our fiscal sponsorship program, HCB</strong>: a 501(c)(3)
legal entity, bank account, automatic taxes & accounting, and
best-in-class software.
</Balancer>
</Text>
<Flex
sx={{
flexDirection: ['column', 'row'],
mt: [3, 4],
gap: [3, 4],
alignItems: ['start', 'center']
}}
>
<Link href="/fiscal-sponsorship/apply" passHref legacyBehavior>
<Button
as="a"
variant="lg"
sx={{
bg: 'blue',
backgroundImage: theme => theme.util.gx('cyan', 'blue'),
lineHeight: 0.9
}}
>
Apply now
</Button>
</Link>
<Button
as="a"
href="https://hcb.hackclub.com"
variant="outline"
sx={{ color: 'white' }}
>
Sign in
</Button>
</Flex>
</Container>
</Box>
<Box id="organizations" as="section" sx={{ py: [4, 5] }}>
<Container sx={{}}>
{/* <Text as="p" variant="headline" sx={{ mt: 0 }}>
Powering nonprofits at every scale
</Text> */}
<Flex
sx={{
flexWrap: 'wrap',
rowGap: 3,
columnGap: [4, 5],
mb: 4
}}
>
<Stat value="$20M+" label="processed transactions" reversed />
<Stat value="1500+" label="projects" reversed />
<Stat value="2018" label="serving nonprofits since" reversed />
</Flex>
<Grid columns={[1, 2]} gap={[3, 4]} sx={{ mt: 4 }}>
{organizations
// .map(org => new Organization(org))
.map(organization => (
<Tilt key={organization.id}>
<Card
as="a"
href={
organization.transparent
? `https://hcb.hackclub.com/${organization.slug}`
: organization.donation_link
}
sx={{
justifyContent: 'center',
alignItems: 'center',
minHeight: 128,
color: 'white',
cursor: 'pointer',
textShadow: '0 1px 4px rgba(0, 0, 0, 0.5)',
textDecoration: 'none',
backgroundColor: 'black',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
borderRadius: 'extra',
overflow: 'hidden',
position: 'relative',
p: 3,
height: '100%',
display: 'grid',
gridTemplateColumns: '64px 1fr',
columnGap: 3,
rowGap: 2
}}
style={{
backgroundImage: `linear-gradient(rgba(0,0,0,0.375) 0%, rgba(0,0,0,0.5) 75%), url('${organization.background_image}')`
}}
>
<Image
src={organization.logo}
alt={`${organization.name} logo`}
loading="lazy"
width={64}
height={64}
sx={{
objectFit: 'contain',
borderRadius: 'extra'
}}
/>
<div>
<Heading
as="h3"
sx={{
fontSize: [3, 4],
m: 0,
overflowWrap: 'anywhere',
width: '100%',
display: 'block'
}}
>
{organization.name}
</Heading>
<Text
variant="caption"
sx={{
color: 'white',
opacity: 0.875
}}
>
{organization.location.readable}
</Text>
</div>
<Text as="p" sx={{ gridColumn: ['span 2', '2'] }}>
<Balancer>{organization.description}</Balancer>
</Text>
</Card>
</Tilt>
))}
</Grid>
</Container>
</Box>
<Features />
<Box id="fees" as="section" sx={{ position: 'relative', py: 5 }}>
<Container>
<Grid columns={[null, null, 2]} gap={[4, 5]}>
<div>
<Text
variant="title"
sx={{ color: 'blue', mb: 2, lineHeight: 0.96 }}
>
One simple, transparent fee:
<br />
7% of income.
</Text>
<Text
as="p"
variant="caption"
color="slate"
sx={{ maxWidth: '52ch' }}
>
This fee goes directly to Hack Club's operations staff, including teen interns working under mentors. This allows us to deliver
best-in-class software and support, grow sustainably, while also providing paid career training for young people from diverse backgrounds.
</Text>
</div>
<Text
as="p"
variant="headline"
sx={{
width: 'fit-content',
gridRow: ['1', 'auto'],
'@supports (-webkit-background-clip: text)': {
backgroundRepeat: 'no-repeat',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundImage:
'linear-gradient(to right, #f06844 0%, #ee4c54 25%, #d45e95 50%, #9c6ca6 75%, #6583c1 100%) !important'
},
'@supports (-webkit-background-clip: text) and (background: linear-gradient(to right in oklch, white, black)':
{
backgroundImage:
'linear-gradient(to right in oklch, #f06844 0%, #ee4c54 25%, #d45e95 50%, #9c6ca6 75%, #6583c1 100%) !important'
}
}}
style={{ margin: 0 }}
>
No legal fees.
<br />
No startup fees.
<br />
No transaction fees.
<br />
No card issuing fees.
<br />
No subscription fees.
<br />
No check deposit fees.
<br />
No credit card processing fees.
</Text>
</Grid>
</Container>
</Box>
<Box as="section" bg="snow" sx={{ py: 5 }}>
<Container>
<Grid columns={[null, null, 2]} gap={[4, 5]}>
<div>
<Heading
variant="headline"
as="h2"
sx={{ marginBlockStart: [null, 4] }}
>
<Balancer>
The fiscal sponsor of&nbsp;choice for the best funders.
</Balancer>
</Heading>
<Flex
sx={{
alignItems: 'center',
gap: [3, 4],
mt: 4,
img: {
width: [72, 128],
height: [72, 128],
objectFit: 'contain'
}
}}
>
{['128.png', 'ycjf.png', 'first.png'].map(file => (
<img
key={file}
src={`/fiscal-sponsorship/${file}`}
width={128}
height={128}
loading="lazy"
alt={file.split('.')[0].toUpperCase()}
/>
))}
</Flex>
</div>
<Card sx={{ maxWidth: 'copy', ml: [null, -4], textAlign: 'left' }}>
<Text
as="blockquote"
variant="lead"
sx={{
mt: '0 !important',
color: 'slate',
textIndent: '-0.33em'
}}
>
HCBs Climate fiscal sponsorship program removes funding
barriers with a blend of youth-centered, tech-savvy services and
a deep commitment to authentic youth empowerment.
</Text>
<Text
as="p"
variant="caption"
sx={{ color: 'muted', mt: 3, textIndent: '-1.5ch' }}
>
<Text as="strong" color="slate">
Kate Goss
</Text>
, Executive Director,{' '}
<UILink href="https://128collective.org">
128&nbsp;Collective
</UILink>
</Text>
</Card>
</Grid>
</Container>
</Box>
<Container>
<Grid
columns={[null, null, 2]}
gap={[4, 5]}
sx={{ py: 5, p: { fontSize: 2, '&:last-child': { mb: 0 } } }}
>
<Link href="https://outernet.hackclub.com/">
<Photo
src={OuternetImgFile}
alt="Each year, 1000s of teenagers attend Hack Club events like this"
showAlt
/>
</Link>
<div>
<Heading as="h2" variant="headline">
Built by Hack Club
</Heading>
<p>
As{' '}
<Link href="/" passHref legacyBehavior>
<UILink>Hack Club</UILink>
</Link>{' '}
grew, we needed a way to empower our members. We currently have
over 30,000 high schoolers involved in Hack Club with over 400
clubs around the world.
</p>
<p>
We started HCB in 2018 to support teen-led clubs and hackathons. After
showing it to our educational partners, we knew we had tapped into
something much larger. Today, HCB removes financial and
legal barriers for thousands doing good in their community.
</p>
</div>
</Grid>
</Container>
<Box
as="section"
id="apply"
sx={{
py: 6,
px: 3,
backgroundImage:
'radial-gradient(ellipse at 5% 5%, #e86494 0%, rgba(232,100,148,0) 75%),radial-gradient(ellipse at 95% 5%, #e86494 0%, rgba(232,100,148,0) 75%),radial-gradient(ellipse at 95% 95%, #baa8d3 0%, rgba(186,168,211,0) 75%),radial-gradient(ellipse at 5% 95%, #fa9f69 0%, rgba(250,159,105,0) 75%)',
position: 'relative'
}}
>
<Box
sx={{
position: 'absolute',
inset: 0,
height: '100%',
zIndex: 0,
backgroundSize: '48px 48px',
backgroundImage: `linear-gradient(to right, #fcc8bf 1px, transparent 1px),
linear-gradient(to bottom, #fcc8bf 1px, transparent 1px)`,
backgroundPosition: 'top left',
maskImage: `linear-gradient(180deg, transparent 0%, white 3%)`,
opacity: 0.1
}}
/>
<Flex
sx={{
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
gap: 3
}}
>
<Link href="/fiscal-sponsorship/apply" passHref legacyBehavior>
<Button
as="a"
variant="lg"
sx={{
bg: 'white',
mixBlendMode: 'screen',
color: 'black !important',
fontSize: [58, 96],
width: ['100%', 'auto'],
py: 4,
px: [4, null, 6],
lineHeight: 0.9,
textTransform: 'none'
}}
>
Apply now
</Button>
</Link>
<Text as="p" variant="lead" sx={{ color: 'white' }}>
<Balancer>No startup fees, no&nbsp;minimum balance.</Balancer>
</Text>
</Flex>
</Box>
<Footer />
</>
)
}

View file

@ -140,7 +140,7 @@ const HackathonGrant = () => {
}}
>
Hack Club is providing $500 grants (and waiving{' '}
<Link href="/hcb" target="_blank">
<Link href="/fiscal-sponsorship" target="_blank">
HCB
</Link>{' '}
fees) to <a sx={{ whiteSpace: 'nowrap' }}>in-person</a>{' '}
@ -344,7 +344,7 @@ const HackathonGrant = () => {
Transparency Mode
</Link>
. Sign up for{' '}
<Link href="/hcb" target="_blank">
<Link href="/fiscal-sponsorship" target="_blank">
HCB
</Link>{' '}
before applying.

View file

@ -1,139 +0,0 @@
import { useEffect, useState, useRef } from 'react'
import { useRouter } from 'next/router'
import { Box, Flex, Text } from 'theme-ui'
import ForceTheme from '../../../components/force-theme'
import Head from 'next/head'
import Meta from '@hackclub/meta'
import FlexCol from '../../../components/flex-col'
import Progress from '../../../components/hcb/apply/progress'
import NavButton from '../../../components/hcb/apply/nav-button'
import Watermark from '../../../components/hcb/apply/watermark'
import FormContainer from '../../../components/hcb/apply/form-container'
import HCBInfo from '../../../components/hcb/apply/hcb-info'
import OrganizationInfoForm from '../../../components/hcb/apply/org-form'
import PersonalInfoForm from '../../../components/hcb/apply/personal-form'
import AlertModal from '../../../components/hcb/apply/alert-modal'
import { geocode } from '../../../lib/hcb/apply/address-validation'
const valiadateAddress = async step => {
// Validate the address
if (step === 3) {
// Get the raw personal address input
const userAddress = sessionStorage.getItem('bank-signup-userAddress')
if (!userAddress) return
const result = await geocode(userAddress)
const addrComp = type => result.results[0]?.structuredAddress[type] ?? ''
sessionStorage.setItem(
'bank-signup-addressLine1',
addrComp('fullThoroughfare')
)
sessionStorage.setItem('bank-signup-addressCity', addrComp('locality'))
sessionStorage.setItem(
'bank-signup-addressState',
addrComp('administrativeArea')
)
sessionStorage.setItem('bank-signup-addressZip', addrComp('postCode'))
sessionStorage.setItem(
'bank-signup-addressCountry',
result.results[0]?.country ?? ''
)
sessionStorage.setItem(
'bank-signup-addressCountryCode',
result.results[0]?.countryCode ?? ''
)
}
}
export default function Apply() {
const router = useRouter()
const [step, setStep] = useState(1)
const formContainer = useRef()
const [formError, setFormError] = useState(null)
const requiredFields = [
[],
['eventName', 'eventLocation'],
['firstName', 'lastName', 'userEmail', 'userBirthday', 'contactOption']
]
useEffect(() => {
console.error(`Form error: ${formError}`)
if (!router.isReady) return
setStep(parseInt(router.query.step))
// Set the query url parameter to 1 if it's not present
if (!step || step < 1) {
router.replace(
{
pathname: router.pathname,
query: { ...router.query, step: 1 }
},
undefined,
{}
)
}
}, [formError, router, step])
return (
<>
<Meta as={Head} title="Apply for HCB" />
<ForceTheme theme="dark" />
<Box
sx={{
display: 'grid',
gap: 5,
gridTemplateAreas: [
'"title" "form" "form" "nav"',
null,
null,
'"title form" "title form" "nav form"'
],
height: ['auto', null, null, '100vh'],
p: [4, 5]
}}
>
<Box sx={{ gridArea: 'title' }}>
<FlexCol gap={[4, null, null, '20vh']}>
<Text variant="title">
Let's get you
<br />
set up on HCB.
</Text>
<Progress />
</FlexCol>
</Box>
<Box sx={{ gridArea: 'form', overflowY: 'auto' }}>
<FormContainer ref={formContainer}>
{step === 1 && <HCBInfo />}
{step === 2 && <OrganizationInfoForm requiredFields={requiredFields} />}
{step === 3 && <PersonalInfoForm requiredFields={requiredFields} />}
</FormContainer>
</Box>
<Flex
sx={{
gridArea: 'nav',
alignSelf: 'end',
alignItems: 'flex-end',
justifyContent: 'space-between'
}}
>
<NavButton isBack={true} form={formContainer} />
<NavButton
isBack={false}
form={formContainer}
setFormError={setFormError}
requiredFields={requiredFields}
clickHandler={() => valiadateAddress(step)}
/>
</Flex>
</Box>
<AlertModal formError={formError} setFormError={setFormError} />
<Watermark />
</>
)
}

View file

@ -1,129 +0,0 @@
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import {
Box,
Button,
Card,
Container,
Divider,
Text,
Link,
Flex,
Image
} from 'theme-ui'
import ForceTheme from '../../../components/force-theme'
import JSConfetti from 'js-confetti'
import Icon from '../../../components/icon'
import FlexCol from '../../../components/flex-col'
function Option({ icon, label, link }) {
const color =
icon === 'email' ? '#338eda' : icon === 'slack' ? '#a633d6' : '#ec3750'
return (
<Button
variant="outline"
as="a"
href={link}
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
width: 'fit-content',
color,
borderColor: color
}}
>
<Flex sx={{ alignItems: 'center', gap: [0, null, 1], px: 2 }}>
<Icon
glyph={icon}
sx={{ width: [32, null, 46], height: [32, null, 46] }}
/>
<Text sx={{ fontSize: [2, null, 3] }}>{label}</Text>
</Flex>
</Button>
)
}
export default function ApplicationSuccess() {
const router = useRouter()
useEffect(() => {
const jsConfetti = new JSConfetti()
jsConfetti.addConfetti({
confettiColors: [
// Hack Club colours!
'#ec3750',
'#ff8c37',
'#f1c40f',
'#33d6a6',
'#5bc0de',
'#338eda',
'#a633d6'
]
})
}, [router])
return (
<Container variant="copy">
<ForceTheme theme="dark" />
<FlexCol
height="100vh"
textAlign="center"
alignItems="center"
justifyContent="space-between"
py={5}
gap={4}
>
<FlexCol gap={4} alignItems="center">
<Image
src="/hcb/apply/party-orpheus.svg"
alt="Dinosaur partying"
sx={{ width: '40%' }}
/>
<FlexCol gap={2}>
<Text variant="title">Thanks for applying!</Text>
<Text variant="lead">
Head on over to HCB and explore the dashboard
</Text>
</FlexCol>
</FlexCol>
<FlexCol gap={4} width="100%">
<Text sx={{ fontSize: [3, null, 4] }}>
Questions about your application?
</Text>
<Flex
sx={{
flexDirection: ['column', null, 'row'],
justifyContent: 'space-evenly',
alignItems: 'center',
gap: [3, null, 0]
}}
>
<Option icon="email" label="Mail" link="mailto:hcb@hackclub.com">
hcb@hackclub.com
</Option>
<Option
icon="slack"
label="Slack"
link="https://hackclub.slack.com/channels/hcb"
>
#hcb
</Option>
<Option icon="help" label="FAQ" link="https://hcb.hackclub.com/faq">
FAQ
</Option>
</Flex>
</FlexCol>
<Button as="a" href="https://hcb.hackclub.com">
<Flex sx={{ alignItems: 'center', px: [2, null, 3], py: 2 }}>
<Icon glyph="bank-account" size={36} />
<Text sx={{ fontSize: 3 }}>Head to HCB!</Text>
</Flex>
</Button>
</FlexCol>
</Container>
)
}

View file

@ -1,485 +0,0 @@
import { Box, Card, Container, Flex, Link, Text } from 'theme-ui'
import { useEffect, useRef, useState } from 'react'
import { keyframes } from '@emotion/react'
import FlexCol from '../../components/flex-col'
import Meta from '@hackclub/meta'
import Head from 'next/head'
import ForceTheme from '../../components/force-theme'
import Nav from '../../components/nav'
import Footer from '../../components/footer'
import Icon from '../../components/icon'
import Tilt from '../../components/tilt'
function Bullet({ glow = true, icon, href, children }) {
let effectColours = [
'#ec3750',
'#ff8c37',
'#f1c40f',
'#33d6a6',
'#5bc0de',
'#338eda',
'#a633d6'
]
function keyframeGenerator(spread, blur, colours, opacity = 0.5) {
let hexOpacity = Math.max(Math.min(Math.round(opacity * 255), 255), 0)
.toString(16)
.padStart(2, '0')
let final = {}
for (let i = 0; i <= 100; i++) {
let baseX = Math.sin((i * Math.PI) / 50) // 50 keyframes for each pi radians
let baseY = -Math.cos((i * Math.PI) / 50)
// Ensure no scientific notation
const roundFactor = 1_000_000
baseX = Math.round(baseX * roundFactor) / roundFactor
baseY = Math.round(baseY * roundFactor) / roundFactor
let boxShadow = ''
for (let c = 0; c < colours.length; c++) {
// Rotate by 2pi / colours.length * c radians
let x =
baseX * Math.cos((2 * Math.PI * c) / colours.length) -
baseY * Math.sin((2 * Math.PI * c) / colours.length)
let y =
baseX * Math.sin((2 * Math.PI * c) / colours.length) +
baseY * Math.cos((2 * Math.PI * c) / colours.length)
boxShadow += `${x * spread}px ${y * spread}px ${blur}px ${
colours[c]
}${hexOpacity},`
}
// Remove trailing comma
boxShadow = boxShadow.slice(0, -1)
final[i + '%'] = { boxShadow }
}
return final
}
const shadowSpread = glow ? 5 : 0
const shadowBlur = glow ? 10 : 0
const animatedBoxShadow = keyframes(
keyframeGenerator(shadowSpread, shadowBlur, effectColours)
)
const borderWidth = '2px'
return (
<Tilt>
<Flex
as="a"
{...(href && { href })}
target="_blank"
sx={{
display: 'flex',
alignItems: 'center',
gap: 2,
width: '20rem',
borderWidth,
borderRadius: '8px !important',
p: '8px !important',
textDecoration: 'none',
color: 'inherit',
backgroundColor: 'var(--theme-ui-colors-darkless)',
'&:hover::after': {
content: '""',
position: 'absolute',
inset: 0,
boxShadow: `linear-gradient(60deg, ${effectColours[0]}, ${effectColours[1]}, ${effectColours[2]}, ${effectColours[3]}, ${effectColours[4]})`,
borderRadius: 'inherit',
zIndex: -1,
animation: `${animatedBoxShadow} 5s ease infinite`
}
}}
>
<Icon glyph={icon} size={42} sx={{ color: 'red', flexShrink: 0 }} />
<Box sx={{ textAlign: 'left' }}>{children}</Box>
{href && (
<Icon
glyph="external-fill"
size={32}
sx={{
position: 'absolute',
top: 0,
right: 0,
translate: '50% -50%',
color: 'muted'
}}
/>
)}
</Flex>
</Tilt>
)
}
function BulletBox({ padding = '2rem', children }) {
return (
<Box
as="ul"
sx={{
width: '100%',
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
gridGap: '2rem',
padding
}}
>
{children}
</Box>
)
}
function Section({ id, children }) {
return (
<Flex as="section" id={id} sx={{ flexDirection: 'column', pt: 5 }}>
{children}
</Flex>
)
}
export default function FiscalSponsorship() {
const gridRef = useRef()
const glowRef = useRef()
const scrollPos = useRef(0)
const mousePos = useRef([0, 0])
const setGlowMaskPosition = () => {
const finalPos = [
-mousePos.current[0],
-mousePos.current[1] + scrollPos.current
]
glowRef.current.style.maskPosition = `${finalPos[0]}px ${finalPos[1]}px`
glowRef.current.style.WebkitMaskPosition = `${finalPos[0]}px ${finalPos[1]}px`
}
useEffect(() => {
const handleScroll = e => {
scrollPos.current = -window.scrollY / 10
gridRef.current.style.transform = `translateY(${scrollPos.current}px)`
setGlowMaskPosition()
}
const handleMouseMove = e => {
const x = e.clientX
const y = e.clientY
glowRef.current.style.left = x + 'px'
glowRef.current.style.top = y + 'px'
mousePos.current = [x, y]
setGlowMaskPosition()
}
window.addEventListener('scroll', handleScroll)
window.addEventListener('mousemove', handleMouseMove)
return () => {
window.removeEventListener('scroll', handleScroll)
window.removeEventListener('mousemove', handleMouseMove)
}
}, [])
return (
<Box as="main" key="main" sx={{ position: 'relative' }}>
<style>
{`* {
scroll-behavior: smooth;
}
p {
text-wrap: balance;
}
`}
</style>
<Box
ref={gridRef}
sx={{
position: 'fixed',
inset: 0,
height: '1000%',
zIndex: -2,
backgroundSize: '20px 20px',
backgroundImage: `linear-gradient(to right, #23262D 1px, transparent 1px),
linear-gradient(to bottom, #23262D 1px, transparent 1px) `,
backgroundPosition: 'top left',
maskImage: `linear-gradient(180deg, transparent 0%, white 3%)`
}}
/>
<Box
ref={glowRef}
sx={{
pointerEvents: 'none',
zIndex: -2,
position: 'fixed',
top: '0',
left: '0',
width: '20rem',
height: '20rem',
background: 'red',
opacity: 0.2,
borderRadius: '50%',
filter: 'blur(2rem)',
translate: '-50% -50%',
// Mask it to the grid background
maskImage: `linear-gradient(to right, #23262D 1px, transparent 1px),
linear-gradient(to bottom, #23262D 1px, transparent 1px) `,
maskSize: '20px 20px, 20px 20px, cover',
maskPosition: '0px 0px'
}}
/>
<Nav dark />
<ForceTheme theme="dark" />
<Meta
as={Head}
title="Fiscal Sponsorship"
description="What is fiscal sponsorship?"
image="/hcb/og-image.png"
>
<title>Fiscal Sponsorship | HCB</title>
</Meta>
<Container sx={{ textAlign: 'center', mt: 6 }}>
<FlexCol gap={4} alignItems="center">
<FlexCol gap={4} alignItems="center">
<Text variant="ultratitle">Fiscal sponsorship</Text>
<Text variant="title">
<Text
sx={{
color: 'red',
textShadow: '0 0 50px var(--theme-ui-colors-red)'
}}
>
501(c)(3)&nbsp;nonprofit{' '}
</Text>
status in just 24 hours for your mission
</Text>
</FlexCol>
<Text variant="headline">
Organizing an event, project, or organization to serve the public
good or your community? Consider fiscal sponsorship before the pain
of paperwork distracts you from your goals.
</Text>
<FlexCol gap={1} alignItems="center">
<Text variant="headline">Jump to:</Text>
<BulletBox padding="0">
<Link sx={{ fontSize: 2 }} href="#costs-and-perks">
Costs and perks of 501(c)(3) status
</Link>
<Link sx={{ fontSize: 2 }} href="#what-is">
How fiscal sponsorship works
</Link>
<Link sx={{ fontSize: 2 }} href="#requirements">
Requirements for fiscal sponsorship
</Link>
<Link sx={{ fontSize: 2 }} href="#partner">
HCB, the #1 fiscal sponsor
</Link>
</BulletBox>
</FlexCol>
<Section id="costs-and-perks">
<Text variant="title">
Why organizers go after 501(c)(3) status
</Text>
<Text variant="lead">
Every year, 1.6 million nonprofits in the U.S. apply for and renew
501(c)(3) status through the IRS for charitable recognition and
tax exemption for their funding. It can take anywhere from 2-12
months to hear a decision back from the IRS, and in general,
nonprofit organizers should be prepared for:
</Text>
<BulletBox>
<Bullet glow={false} icon="sad">
$3,000 in <b>up-front costs</b>, from
<Link href="https://www.irs.gov/charities-non-profits/form-1023-and-1023-ez-amount-of-user-fee">
{' '}
forms{' '}
</Link>
to state fees to support from legal counsel
</Bullet>
<Bullet glow={false} icon="sad">
The potential for the IRS to <b>reject</b> an application
</Bullet>
<Bullet glow={false} icon="sad">
<b>Hiring</b> bookkeepers and accountants to prepare taxes and
provide upkeep annually to stay in good standing
</Bullet>
<Bullet glow={false} icon="sad">
<b>Closing costs</b> averaging around $5,000 if you lose or
terminate status
</Bullet>
</BulletBox>
</Section>
<Text variant="lead">
Legal status sounds great and all, but why go through the hassle of
applying when its so expensive and time consuming?
</Text>
<Text variant="lead">
Because as a legally recognized 501(c)(3) nonprofit in the U.S.,
your organization gains access to loads of legal tax benefits like:
</Text>
<BulletBox>
<Bullet icon="payment">
The ability to receive <b>tax deductible donations</b> from
sponsors.
</Bullet>
<Bullet icon="member-add">
Reduced taxable income for your U.S. supporters, which
<b> incentivizes giving</b>.
</Bullet>
<Bullet icon="leader">
<b>Exemption</b> from U.S. federal income tax and unemployment
tax.
</Bullet>
<Bullet icon="bolt">
Potential exemption from state income, sales, and employment
taxes.
</Bullet>
<Bullet icon="email">
Potential for reduced rates on postage, marketing, advertising,
legal counsel, and more.
</Bullet>
</BulletBox>
<Text variant="lead">
Unfortunately between the costs and time needed to organize a
nonprofit, many charitable initiatives are prevented from exiting an
idea phase or progressing at a pace originally hoped. Imagine how
much more valuable impact could happen on the world if these
barriers didnt exist.
</Text>
<Text variant="lead">
As it turns out, theres an alternative route for startups,
student-led initiatives, or anyone looking to avoid a headache with
the IRS to obtain all the benefits of 501(c)(3) status. Thats where
fiscal sponsorship comes in.
</Text>
<Section id="what-is">
<Text variant="title">Fiscal Sponsorship?</Text>
<Text variant="lead">
By legally working with an existing nonprofit offering fiscal
sponsorship, projects and events can claim most of the legal
benefits of individual 501(c)(3) status. Piggy-backing off this
existing status, organizations also gain access to resources from
their fiscal sponsor like:
</Text>
<BulletBox>
<Bullet icon="docs">
Bookkeeping and administration to ensure that all paperwork and
taxes are filed
</Bullet>
<Bullet icon="bag">
Fully established HR and benefits, which can vary by the fiscal
sponsor
</Bullet>
<Bullet icon="admin">
Waived responsibility to organize a board of directors
</Bullet>
<Bullet icon="payment">
Fully transparent operational fees, typically ranging from 7-12%
that prevent you from paying typical operating costs.
</Bullet>
<Bullet icon="door-leave">
The ability to terminate your fiscal sponsorship agreement and
file for separate tax-exempt status at any point.
</Bullet>
</BulletBox>
<Text variant="lead">
If youre brand new to nonprofit organizing or unsure where your
project will take you, fiscal sponsorship is a great tool to help
manage your finances and gauge whether becoming an independent
nonprofit down the line is practical or financially feasible.
</Text>
</Section>
<Section id="requirements">
<Text variant="title">Requirements for Fiscal Sponsorship</Text>
<Text variant="lead">
Depending on the fiscal sponsor you choose, requirements for
working together can vary. Fiscal sponsors generally ask that your
nonprofits goals be similar to theirs. They also usually ask that
your organization or event commits to remaining charitable in
nature and refrains from activities that may result in loss of
501(c)(3) status.
</Text>
</Section>
<Section id="partner">
<Text variant="title">HCB, the #1 fiscal sponsor</Text>
<Text variant="lead">
While many fiscal sponsors require that their partners relate to
their mission in similar ways, at HCB, weve built our
infrastructure to support hundreds of causes in all areas of
charitability.
</Text>
<Text variant="lead">
Check out some of the resources weve built our fiscal sponsorship
foundation on:
</Text>
<BulletBox>
<Bullet icon="bank-account">
A beautiful web interface to manage finances
</Bullet>
<Bullet icon="transactions">
Fee-free invoicing, ACH or check transfers, and reimbursements
</Bullet>
<Bullet icon="link">
A customizable and embeddable donations URL
</Bullet>
<Bullet icon="card-add">
A fun and routing number to connect to external platforms, like
Shopify and GoFundMe
</Bullet>
<Bullet icon="purse">
Perks like PVSA certification, newsletter software, and
1Password credits
</Bullet>
</BulletBox>
<Text variant="lead">
Looking for nonprofit status and not a religious or political
organization? Wed love to meet you and chat about working
together. Feel free to apply
<Link href="https://hackclub.com/hcb/#apply"> here </Link>or
<Link href="mailto:hcb@hackclub.com"> email our team </Link>if you
have more questions about fiscal sponsorship!
</Text>
</Section>
<Text variant="lead">
At its core, Hack Club is a nonprofit encouraging students to learn
how to code by building and making cool things. HCB was built out by
teenagers at Hack&nbsp;Club and continues to be a real-world space
that high schoolers can contribute to every day.
</Text>
</FlexCol>
</Container>
<Box
sx={{
height: '100px',
position: 'relative',
width: '100%',
overflow: 'hidden',
'&::after': {
content: '""',
width: '500%',
height: '100%',
position: 'absolute',
translate: '-50% 100%',
boxShadow: '0 -64px 64px #17171d'
}
}}
/>
<Footer dark />
</Box>
)
}

View file

@ -1,71 +0,0 @@
import { Box } from 'theme-ui'
import Meta from '@hackclub/meta'
import Head from 'next/head'
import ForceTheme from '../../components/force-theme'
import Nav from '../../components/nav'
import Footer from '../../components/footer'
import Landing from '../../components/hcb/landing'
import Features from '../../components/hcb/features'
import Testimonials from '../../components/hcb/testimonials'
import Everything from '../../components/hcb/everything'
import Start from '../../components/hcb/start'
import Nonprofits from '../../components/hcb/nonprofits'
const styles = `
html {
scroll-behavior: smooth;
}
::selection {
background-color: #e42d42;
color: #ffffff;
text-shadow: none;
}
input:-webkit-autofill {
-webkit-text-fill-color: white;
}
`
export default function Bank({ stats }) {
return (
<>
<Box as="main" key="main">
<Nav dark />
<ForceTheme theme="dark" />
<Meta
as={Head}
title="Fiscal Sponsorship"
description="HCB is the largest fiscal sponsor of teen-led organizations in the US. Get a 501(c)(3) status-backed fund optimized for events, nonprofits, and more."
image="/hcb/og-image.png"
>
<title>Fiscal Sponsorship HCB</title>
</Meta>
<style>{styles}</style>
<Box>
<Landing eventsCount={stats.events_count} showButton />
<Features />
<Testimonials />
<Nonprofits />
<Everything fee="7" />
<Start stats={stats} />
</Box>
</Box>
<Footer dark key="footer" email="hcb@hackclub.com" />
</>
)
}
export async function getStaticProps(context) {
const res = await fetch(`https://hcb.hackclub.com/stats`)
const stats = await res.json()
return {
props: {
stats
},
revalidate: 10
}
}

View file

@ -11,7 +11,7 @@ const Page = () => (
as={Head}
title="HCB Operations Associate"
description="Hack Club is a hiring a HCB Operations Associate as the 8th full-time member of our team in Burlington, Vermont."
image="https://workshop-cards.hackclub.com/hcb%20Ops%20Associate%20%40%20Hack%20Club.png?fontSize=175px&brand=HQ"
image="https://workshop-cards.hackclub.com/fiscal-sponsorship%20Ops%20Associate%20%40%20Hack%20Club.png?fontSize=175px&brand=HQ"
/>
<ForceTheme theme="light" />
<Nav />

View file

@ -11,7 +11,7 @@ const Page = () => (
as={Head}
title="HCB Operations Lead"
description="Hack Club is a hiring a HCB Operations Lead as the 7th full-time member of our team in Burlington, Vermont."
image="https://workshop-cards.hackclub.com/hcb%20Ops%20Lead%20%40%20Hack%20Club.png?fontSize=175px&brand=HQ"
image="https://workshop-cards.hackclub.com/fiscal-sponsorship%20Ops%20Lead%20%40%20Hack%20Club.png?fontSize=175px&brand=HQ"
/>
<ForceTheme theme="light" />
<Nav />

View file

@ -51,7 +51,7 @@ const Page = () => (
<Text as="p" mb={4}>
Starting a nonprofit is hard, so we built{' '}
<Link href="/hcb" target="_blank">
<Link href="/fiscal-sponsorship" target="_blank">
HCB
</Link>{' '}
for our community of {formatted}+ teenage programmers. Within 24 hours

View file

@ -7,8 +7,11 @@ import Footer from '../../components/footer'
import FadeIn from '../../components/fade-in'
import Sparkles from '../../components/sparkles'
import Tilt from '../../components/tilt'
import Recap from '../../components/onboard/recap'
import usePrefersReducedMotion from '../../lib/use-prefers-reduced-motion'
import { useEffect, useRef, useState } from 'react'
import sleep from '../../lib/sleep'
import Announcement from '../../components/announcement'
/**
* @type {import('theme-ui').ThemeUIStyleObject}
@ -23,135 +26,6 @@ const traceSx = {
const dimBg = '#151515'
// Beloved classic utility function :3
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
// "LET'S RECAP" pixel art (exported from Piskel)
// Original: https://doggo.ninja/fiK0nk.piskel
const recapWidth = 71
const recapHeight = 10
const recapPixels = [
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0xffffffff, 0xffffffff
]
const slackLink = '/slack/?event=onboard'
const stickerButtonText = 'Click 4 Stickers'
@ -255,106 +129,6 @@ const ShipPage = () => {
return () => observer.disconnect()
}, [])
// Fancy lights animation
const lightsScrollTrigger = useRef()
const lightsAnimated = useRef(false)
useEffect(() => {
let canceled = false
const setAtIndex = (i, color) => {
if (canceled) return
// Going outside of React for performance
const el = document.getElementById(`pixel-${i}`)
if (!el) return
if (recapPixels[i]) {
el.style.background = color
el.style.boxShadow = `0 0 10px ${color}`
} else {
el.style.background = dimBg
el.style.boxShadow = 'none'
}
}
const setAll = color => {
for (let i = 0; i < recapPixels.length; i++) setAtIndex(i, color)
}
const animate = async () => {
if (lightsAnimated.current) return
lightsAnimated.current = true
// Illuminate lights in diagonal lines starting with only top left.
for (
let curColumn = 0;
curColumn < recapWidth + recapHeight;
curColumn++
) {
for (
let offset = curColumn;
offset >= Math.max(0, curColumn - recapHeight);
offset--
) {
const i = curColumn * recapWidth + offset - offset * recapWidth
setAtIndex(i, '#ffffff')
if (!recapPixels[i]) await sleep(4)
if (canceled) return
}
// await sleep(2); if (canceled) return
}
// Flash the lights twice
await sleep(600)
if (canceled) return
setAll(dimBg)
await sleep(80)
if (canceled) return
setAll('#aaaaaa')
await sleep(20)
if (canceled) return
setAll(dimBg)
await sleep(30)
if (canceled) return
setAll('#aaaaaa')
await sleep(100)
if (canceled) return
setAll(dimBg)
await sleep(200)
if (canceled) return
// Animate rainbow 2-column increments
for (let x = 0; x < recapWidth; x++) {
const color = `hsl(${(x * 360) / recapWidth}, 100%, 65%)`
for (let y = 0; y < recapHeight; y++) {
const i = y * recapWidth + x
setAtIndex(i, color)
}
if (x % 2 === 1) await sleep(35)
}
}
if (prefersReducedMotion) {
if (!lightsAnimated.current) setAll('#ffffff')
return () => (canceled = true)
} else {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) animate()
},
{ threshold: 0.5 }
)
observer.observe(lightsScrollTrigger.current)
return () => {
canceled = true
observer.disconnect()
}
}
}, [prefersReducedMotion])
return (
<>
@ -362,7 +136,7 @@ const ShipPage = () => {
as={Head}
name="OnBoard"
description={`We'll pay manufacturing costs for any high schooler (or younger!) who designs a circuit board.`}
image="https://cloud-ji9c1qxfx-hack-club-bot.vercel.app/03_card.png"
image="https://cloud-2x7lcsf1p-hack-club-bot.vercel.app/0onboard-og-card-2024feb19-075x.png"
/>
<style>{`
@ -425,9 +199,18 @@ const ShipPage = () => {
position: 'relative'
}}
>
<Box sx={{ pt: [3, 6]}}>
<Announcement
copy="Steve Wozniak, Apple co-founder, about OnBoard"
caption="Im so glad young people can create PCBs online."
imgSrc="https://cloud-iddh16j0r-hack-club-bot.vercel.app/0stevew.png"
imgAlt="A picture of Steve Wozniak who is a co-founder of Apple."
color="primary"
/>
</Box>
<Flex
sx={{
pt: 80,
pt: [3,1],
width: '100%',
maxWidth: 'layout',
alignItems: 'center'
@ -1059,21 +842,7 @@ const ShipPage = () => {
Let's Recap
</Heading>
<Grid
ref={lightsScrollTrigger}
gap={['2px', '3px', '4px']}
columns={recapWidth}
sx={{ width: '100%', maxWidth: 800 }}
>
{recapPixels.map((_, i) => (
<Box
id={`pixel-${i}`}
key={i}
sx={{ bg: dimBg, paddingTop: '100%' }}
/>
))}
</Grid>
<Recap />
<Grid
width={300}
gap={4}

View file

@ -6,7 +6,7 @@ import Footer from '../components/footer'
import Bio from '../components/bio'
import ForceTheme from '../components/force-theme'
export default function Team() {
export default function Team({ team }) {
return (
<>
<Box as="main" key="main">
@ -142,130 +142,18 @@ export default function Team() {
>
Hacker Resources Team
</Text>
<Grid columns={[1, null, 2]} gap={2}>
<Bio
name="Kara Massie"
teamRole="Production Lead"
text="Before joining Hack Club, Kara was a lead producer at Activision, shipping Crash Bandicoot N. Sane Trilogy and Bungie's Destiny 2 expansions. Shes deeply committed to inclusivity in gaming and tech spaces, and is beyond thrilled to be part of an org with kindness at its core. She has lived in 3 countries and names her pets after vegetables."
img="/team/kara.png"
pronouns="she/her"
<Grid columns={[1, null, 2]} gap={2}>
{ team.current?.filter(member => member.department === "HQ").map(member => (
<Bio
img={member.avatar}
name={member.name}
teamRole={member.role}
text={member.bio}
pronouns={member.pronouns}
key={member.name}
/>
<Bio
name="Leo McElroy"
teamRole="Clubs Engineering Lead"
text="Leo builds digital systems, physical tools, and communities to help people express themselves and pursue their curiosity. He's created tools for democratizing personal automation (including programming languages for designing stuff), travelled the world visiting makerspaces on a Watson Fellowship, and created and ran a few makerspaces himself."
img="/team/leo.png"
pronouns="he/him"
/>
<Bio
name="Lexi Mattick"
teamRole="Clubs Engineering"
text="Always driven by curiosity for how things work, Lexi fell in love with Hack Club in 2019 after joining a Hack Night call and discovering like-minded individuals. She spends her time programming, making music, and studying for her private pilot license; at Hack Club, she spends her time working on whatever fantastic project is happening in the present moment."
img="https://media.kognise.dev/other-avatars/bean-man.jpg"
pronouns="she/her"
/>
<Bio
name="Shawn Malluwa-Wadu"
img="https://cloud-8u876lgxi-hack-club-bot.vercel.app/0shawn.png"
teamRole="Local Cowboy"
text="Shawn Malluwa (@Shawn M.) is a Hack clubber from Maryland who joined in 2022 around the launch of Sprig and is now heavily involved in refining hardware designs for various HQ projects! Hes also the face and voice of a bunch of our social media videos, and works to share the process of making with the world. In his free time, Shawn loves to create Art across various mediums, particularly comics and animation."
pronouns="he/him"
/>
<Bio
name="Faisal Sayed"
teamRole="Engineering"
img="https://ca.slack-edge.com/T0266FRGM-U014ND5P1N2-78db6630a13d-512"
text="Faisal Sayed (@fayd) has been associated with Hack Club for 3 years and loves building open-source projects that bring joy. During the first workshop-bounty-program back in 2020, Faisal was heavily involved in creating & reviewing numerous programming workshops. At HQ, He works with Graham on HQ Engineering and infrastructure. Outside of Hack Club, Faisal likes working on his side-projects like Firefiles and tmdr."
pronouns="he/him"
/>
<Bio
name="Deven Jadhav"
teamRole="Events"
text="Deven is a Hack Clubber from India who enjoys building meaningful things at the intersections of art and technology. He also loves music and plays the guitar & drums! Along with this, he also likes talking to strangers over the internet and having interesting & deep conversations. He is also a sucker for nature photography and enjoys hikes and treks into the wild!"
img="https://github.com/devenjadhav.png"
pronouns="he/him"
/>
<Bio
name="Hugo Hu"
teamRole="Mail Coordinator & Engineering"
text="Hugo is a Hack Clubber from NYC who joined during the summer of 2020 for Summer of Making, and he then went on the Hacker Zephyr in 2021. He's a lover of all things mail and logistics related, and does hardware engineering and procurement work for projects like Sprig and Blot. In his free time, he's building up his courage to pet random dogs, listening to outdated music, and designing fun projects with hardware."
img="https://ca.slack-edge.com/T0266FRGM-U017EPB6LE9-84f26d2a184c-512"
pronouns="he/him"
/>
<Bio
name="Graham Darcey"
teamRole="Creative Technologist"
text="Originally from Vermont, Graham has worked as a full-stack software engineer in Silicon Valley for over 20 years, most recently at Uber where he worked on their core routing services and map data platform. He recently moved back east, and currently resides in Shelburne VT. Graham's hobbies include gaming, gamedev, cooking with his wife, and playing joyfully with his three year old daughter."
img="/team/graham.jpg"
pronouns="he/him"
/>
<Bio
img="/team/chris.jpg"
name="Chris Walker"
teamRole="Hacker Resources"
text="Chris started programming games in middle school, a hobby that developed into a deep passion for educational software. In 2013 he accepted a Thiel Fellowship and moved to San Francisco, where he watched Hack Club grow from an early stage. He worked on Hack Clubs learning resources & clubs program for two years."
pronouns="he/him"
/>
<Bio
name="Dieter Schoening"
teamRole="Media Creation"
text="Dieter grew up in South Carolina where he started the adventure of content creation. Now he is helping with our social media and projects to get more teens interested in Hack Club. Fun facts: He likes virtual reality development, boba, hiking, entrepreneurship"
img="/team/deet.jpg"
pronouns="He/Him"
/>
<Bio
name="Woody Keppel"
teamRole="Event Alchemist"
text={`Woody is a film actor, musician, comedian, band leader, event producer, and convener of fun. He founded Vermonts Festival of Fools, The Feast of Fools, The Hawaiian Vaudeville Festival, and the artist retreat & concert venue known as Mt. Foolery. For Woody, “putting on events has always been one of my great pleasures. Ive also had the privilege of sharing my time with the elderly as well as mentoring middle & high schools students in Vermont. Being part of the Hack Club community has opened my eyes & heart to so much that is possible. Its a great adventure were all on, and were here to light the way for each other. Shine on!”`}
img="/team/woody.jpg"
pronouns="he/him"
/>
<Bio
img="/team/josias.jpg"
name="Josias Aurel"
teamRole="Engineering"
text="Josias Aurel (@Josias Aurel) has been associated with Hack Club for about 3 years, working on a variety of projects including Sinerider. He has organized events such as the TiC Summit and TiC Hackathon in his local town of Yaoundé, Cameroon. He is a curiosity-driven coder who likes to take on interesting challenges and who is interested in machine learning and systems programming. He'll be working very closely with Graham over the next year on a variety of projects. Outside of tech he likes going on hikes with friends and eating vegetables."
pronouns="he/him"
/>
<Bio
img="https://cloud-p623dki6o-hack-club-bot.vercel.app/0img_2668.jpg"
name="Nila Palmo Ram"
teamRole="Engineering Assistant"
text="Nila absolutely loves coding and is all about making tech awesome while experimenting on how to keep it ethical and humanistic. Over at Hack Club, she's on a mission to empower more girl Hack Clubbers by guiding them in organizing Hackathons, collaborating on special projects, and fostering connections amongst them. Alongside Christina, she's also busy drumming up funds for Hack Club, always on the lookout for new donors. When she's not in front of the screen, you'll find her out by the water, diving into all sorts of aquatic adventures."
pronouns="she/her"
/>
<Bio
img="https://cloud-diex6x51t-hack-club-bot.vercel.app/01671553183325__1_.jpeg"
name="Arpan Pandey"
teamRole="Clubs Operations & Engineering"
text="Arpan Pandey (@A) is a Hack Clubber from India who joined Hack Club about 1.5 years ago. He is a passionate programmer and loves to build things, especially for clubs. He has created and maintained Jams API, Clubs Directory and many other projects for clubs. He also onboards and supports clubs through their Hack Club Journey. He is also the person to send out mails to Hack Clubbers in India. He loves Harry Potter and is a proud Gryffindor. You'll also find him playing around with electronics and hardware, and he is also a licensed HAM (KC1TPD). He is very much interested in having deep conversations with people and loves to make new friends. Here is his favorite quote: “It does not do to dwell on dreams and forget to live.”"
pronouns="he/him"
/>
<Bio
img="https://cloud-cwim853sk-hack-club-bot.vercel.app/0screenshot_2023-12-12_at_4.15.57_pm.png"
name="Thomas Stubblefield"
teamsRole="Software Engineer & Clubs Lead"
text="Thomas is a Hack Clubber from South Carolina who led a Hack Club at his high school and is now building software to make the experience of being a part of and leading a club better. He currently leads the clubs program. He loves to build side projects, make tea, and hike. Thomas lives his life by three sayings: time will tell, in life we are always learning, and bum bum bummm (a friendly melody he hums daily)."
pronouns="he/him"
/>
<Bio
img="https://cloud-rb1s4ys4w-hack-club-bot.vercel.app/0pfp.jpg"
name="Sahiti Dasari"
teamRole="Clubs Operations & Engineering"
text="Sahiti's Hack Club journey kicked off when she stumbled upon resources to start her own high school coding club, which later led to her running a county-wide hackathon. These days, she's an active member of the Clubs Operations & Engineering team and has previously interned with Hack Club for philanthropy and communications. She strives to create technology tools and resources for clubs, such as the Hack Club Jams initiative and Club Leader onboarding. Beyond programming, Sahiti loves all things finance, business, and literature. Her mission is to make an impact by spreading opportunities.
... .... . .----. ... / .- .-.. ... --- / ..-. .-.. ..- . -. - / .. -. / -- --- .-. ... . / -.-. --- -.. . -.-.--"
pronouns="she/her"
/>
<Bio
img="https://assets.devlucas.page/images/profile.jpg"
name="Lucas Honda"
teamRole="Engineering"
text="Lucas is a 14 year old Hack Clubber from Sao Paulo, Brazil. Since joining the Hack Club, he has been fascinated by Sprig and is currently leading Sprig App Review Team, and working to make it the best it possibly can be. He loves all aspects of aviation, and scours the internet/skies looking for and investigating flying machines. He spends a good portion of his time with his dog, a happy and playful dog."
pronouns="he/him"
href="https://page.devlucas.page"
video="https://www.youtube.com/embed/vuLtlzMMW6o?si=v-Dbn2fSGvTyXlbY"
/>
</Grid>
))}
</Grid>
</Box>
</Box>
<Box>
@ -285,106 +173,18 @@ export default function Team() {
>
HCB Team
</Text>
<Grid
columns={[1, null, 2]}
gap={2}
sx={{ height: 'fit-content' }}
>
<Bio
img="/team/max.jpg"
name="Max Wofford"
teamRole="Tech & Creative Lead"
text="After teaching himself to code in junior year of high school, Max joined a group of nomadic hackers in Costa Rica to experience coding in a real-world setting. He has been with Hack Club since day one and is now working full-time in Vermont to grow the movement."
pronouns="he/him"
<Grid columns={[1, null, 2]} gap={2}>
{ team.current?.filter(member => member.department === "HCB").map(member => (
<Bio
img={member.avatar}
name={member.name}
teamRole={member.role}
text={member.bio}
pronouns={member.pronouns}
key={member.name}
/>
<Bio
name="Melanie Smith"
teamRole="Director of Operations"
text="Melanie grew up in northern New England where she obtained a degree in Marine Biology. She then spent several years running a pet store with 20+ employees. In Feb 2021, she joined the HCB team as the Operations Lead. Now as Director of Operations, she is responsible for leading the team in vision and growth."
img="/team/mel.png"
pronouns="she/her"
/>
<Bio
name="Caleb Denio"
teamRole="Engineering"
text="Caleb enjoys the simple things in life: making music, drinking lattes, and programming. At HCB, he engineers features."
img="/team/caleb.jpg"
pronouns="he/him"
/>
<Bio
name="Liv Cook"
teamRole="Jr Project Manager"
text="Supporting hackathon organizers and makers worldwide is Livs favorite part about being at Hack Club. Being a part of the HCB team for over two years now, Liv also strives to make sure everyone has the best experience possible on the platform and that team projects are on track. She graduated from the University of Vermont with a degree in Healthcare Systems and Policy and enjoys traveling, writing, and jokes. #LivLaughLove Her current favorite song is:"
img="/team/liv.png"
pronouns="she/her"
video="https://www.youtube-nocookie.com/embed/MtN1YnoL46Q?si=FJcJN7kMptzBaGn4"
/>
<Bio
name="Gary Tou"
teamRole="Engineering Manager"
text="Gary is a software engineer from Seattle and loves photography! After using HCB to launch a nonprofit organization, Gary joined Hack Club to make the product that enabled him to do great things even greater for others."
img="https://assets.garytou.com/profile/GaryTou.jpg"
pronouns="he/him"
href="https://garytou.com"
/>
<Bio
name="Daisy Reyes"
teamRole="Operations Associate"
text="Daisy has a passion for growing and maintaining positive relationships with all of the members of Hack Club and thats her favorite part about being on the HCB team. Daisy especially loves onboarding and helping FIRST teams navigate HCB so that they can excel in their own goals. She grew up in Vermont on a dairy farm and graduated from The University of Vermont with her bachelors in Animal Science. She loves animals of all types, crocheting, board games, and traveling."
img="https://ca.slack-edge.com/T0266FRGM-U046V3EK56W-b9777e33eece-512"
pronouns="she/her"
/>
<Bio
name="Ben Dixon"
teamRole="Engineering"
text="Coming all the way from drizzly England, Ben reconnected with his adoration for teaching people about programming through the computer graphics demoscene during lockdown; firmly believing “HLSL is basically pseudocode”. At Hack Club, Ben designs and implements snazzy new features at HCB, along with raiding their granola bars."
img="https://ca.slack-edge.com/T0266FRGM-U03DFNYGPCN-d76abb1ba329-512"
pronouns="he/him"
video="https://www.youtube-nocookie.com/embed/POv-3yIPSWc?si=25WKed0HkazCZZOz"
/>
<Bio
name="Hunter Goodenough"
teamRole="Operations Associate"
text="Hunter is a jack of all trades with a particular passion for creating and supporting communities. He is an ardent hobbyist and is always trying out new things. He is a newer hire at HCB (Having previously worked in both the Restaurant and Medical Technology industries) and is excited to join the community and is looking forward to participating in various Hack Club projects and events."
img="https://ca.slack-edge.com/T0266FRGM-U05RDPEKGA3-647435768a53-512"
pronouns="he/him"
/>
<Bio
name="Bence Beres"
teamRole="Bookkeeper"
text="Bence is a true bureaucrat who doesnt leave any documents unturned. Having made a sharp U-turn after college to switch from his burgeoning career in the world of political science towards the thrilling and life altering adventures of the world of Accounting, Bence understands that knowing Excel is a greatly underappreciated life skill."
img="/team/bence.png"
pronouns="he/him"
/>
<Bio
name="Kris Hoadley"
teamRole="Bookkeeper"
text="Kris is a native Vermonter and accounting nerd with the need to make all of life balance. Numbers? Give her numbers anytime."
img="/team/kris.png"
pronouns="she/her"
/>
<Bio
name="Paul Spitler"
teamRole="Partnerships Lead"
text="Before joining Hack Club Paul (a native Shelburnite) was working in the e-commerce space in NYC but has moved back to his homeland a few years ago. His role at Hack Club will be building out new partnerships and although he has no idea how to code, hes hoping to learn over his career. Paul enjoys playing hockey, being outdoors with his wife and dog and any kind of boards sports."
img="/team/paul.png"
pronouns="he/him"
/>
<Bio
name="Arianna Martinelli"
teamRole="Operations"
text="Arianna (a current freshman at Carnegie-Mellon University and a former Hack Club leader from Kentucky) loves onboarding all our cool organizations and making HCB more accessible. When shes not learning about how humans and computers can work together, shes making memes and decorating the world with Hack Club stickers."
img="https://cloud-oubklmp6c-hack-club-bot.vercel.app/0arianna_profile_photo.png"
pronouns="she/her"
/>
<Bio
name="Shubham Panth"
teamRole="Operations"
text="Shubham, a self-taught coder from the tranquil terrains of Sweden, has been weaving through C# and Unity3D since 2017. After utilizing HCB to catapult his own developer dreams, he pivoted to help others, ensuring that every young dreamers journey through HCB is as seamless and spirited as his own coding adventures."
img="https://ca.slack-edge.com/T0266FRGM-U014E8132DB-8b1a8e7a1a41-512"
pronouns="he/him"
/>
</Grid>
))}
</Grid>
</Box>
</Box>
</Grid>
@ -405,52 +205,18 @@ export default function Team() {
>
Community Team
</Text>
<Grid columns={[1, 2, null, 4]} gap={2}>
<Bio
name="Toby Brown"
teamRole="Storytelling"
text={`From a young age, Toby had a fascination with anything electronic. As a toddler, he would show far more interest in the 20-year-old air conditioning unit in the corner of the room than in anyone trying to talk to him. This fascination eventually led him to coding; and at the age of 6, Toby built his first website. While most sane people would probably describe this website as "atrocious", 6-year-old Toby was completely hooked. Nowadays, Toby does Storytelling at Hack Club, and is a self-proclaimed pizza eating expert.`}
href="https://tobyb.dev"
img="https://ca.slack-edge.com/T0266FRGM-U02C9DQ7ZL2-a57a3718241a-512"
pronouns="he/him"
/>
<Bio
name="Mutammim"
teamRole="Moderation & Events"
img="https://ca.slack-edge.com/T0266FRGM-U021VLF7880-2bf2660768cc-512"
pronouns="he/him"
/>
<Bio
name="Faisal Sayed"
teamRole="Moderation & Events"
img="https://github.com/faisalsayed10.png"
pronouns="he/him"
/>
<Bio
name="Sahiti Dasari"
teamRole="Moderation & Events"
img="https://cloud-hmya58lt9-hack-club-bot.vercel.app/0img_3143.jpg"
pronouns="she/her"
/>
<Bio
name="Gaurav Pandey"
teamRole="Moderation & Events"
img="https://ca.slack-edge.com/T0266FRGM-U043Q05KFAA-95e93fd7beff-512"
pronouns="he/him"
/>
<Bio
name="Arav Narula"
teamRole="Moderation & Events"
img="https://ca.slack-edge.com/T0266FRGM-U01MPHKFZ7S-7b67dc7c40fb-512"
pronouns="he/him"
/>
<Bio
name="Arpan Pandey"
teamRole="Moderation & Events"
img="https://ca.slack-edge.com/T0266FRGM-U0409FSKU82-e912a98d0ead-512"
pronouns="he/him"
/>
</Grid>
<Grid columns={[1, 2, null, 4]} gap={2}>
{ team.current?.filter(member => member.department === "Community").map(member => (
<Bio
img={member.avatar}
name={member.name}
teamRole={member.role}
text={member.bio}
pronouns={member.pronouns}
key={member.name}
/>
))}
</Grid>
</Box>
<br />
<Box sx={{ textAlign: 'center', mt: 2, mb: [3, 4] }}>
@ -692,3 +458,12 @@ When not busy juggling different tasks he takes up, he enjoys tinkering & buildi
</>
)
}
export const getServerSideProps = async () => {
try {
const team = await fetch("https://internal.hackclub.com/team").then((res) => res.json())
return { props: { team } }
} catch (e) {
return { props: { team: [] }}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

Before

Width:  |  Height:  |  Size: 273 B

After

Width:  |  Height:  |  Size: 273 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View file

Before

Width:  |  Height:  |  Size: 5.4 MiB

After

Width:  |  Height:  |  Size: 5.4 MiB

View file

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View file

Before

Width:  |  Height:  |  Size: 602 KiB

After

Width:  |  Height:  |  Size: 602 KiB

View file

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 157 KiB

View file

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View file

Before

Width:  |  Height:  |  Size: 592 KiB

After

Width:  |  Height:  |  Size: 592 KiB

View file

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 6.9 MiB

After

Width:  |  Height:  |  Size: 6.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

Some files were not shown because too many files have changed in this diff Show more