├── .eslintrc.json ├── public ├── grid.png ├── nft.gif ├── nft-fc.gif ├── steps │ ├── 1.png │ ├── 2.png │ ├── 3.png │ └── 4.png ├── devfolio.png ├── grid-white.png ├── og-image.png ├── base-god-ss.png ├── favicon_io │ ├── favicon.gif │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ └── site.webmanifest ├── onchain-m1.svg ├── onchain-m2.svg ├── onchain-m3.svg ├── onchain-m4.svg ├── onchain-tablet.svg ├── onchain-d1.svg ├── onchain-4k-1.svg ├── onchain-d2.svg └── onchain-4k-2.svg ├── src └── app │ ├── fonts │ ├── NyghtSerif-Bold.ttf │ ├── NyghtSerif-Medium.ttf │ ├── NyghtSerif-Regular.ttf │ └── fonts.ts │ ├── constants │ └── urls.ts │ ├── sections │ ├── TypographySection.tsx │ ├── FooterSection.tsx │ ├── MintStepsGridSection.tsx │ ├── VideoSection.tsx │ ├── HomeSection.tsx │ └── WhySection.tsx │ ├── components │ ├── Ui.tsx │ ├── ApplePeekArea.tsx │ ├── Heading.tsx │ ├── Button.tsx │ ├── AnimatedSection.tsx │ ├── Footer.tsx │ ├── Tooltip.tsx │ ├── StepItem.tsx │ ├── StepGrid.tsx │ ├── YoutubePlayer.tsx │ ├── AnimatedText.tsx │ └── OnchainTypography.tsx │ ├── globals.css │ ├── hooks │ └── useResponsive.tsx │ ├── utils │ └── shared.ts │ ├── layout.tsx │ ├── page.tsx │ ├── frog │ └── index.ts │ ├── styles │ └── app.css │ └── api │ └── [[...routes]] │ └── route.tsx ├── .env.example ├── postcss.config.mjs ├── .prettierrc.js ├── next.config.mjs ├── .gitignore ├── tailwind.config.ts ├── tsconfig.json ├── LICENSE ├── package.json └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /public/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/grid.png -------------------------------------------------------------------------------- /public/nft.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/nft.gif -------------------------------------------------------------------------------- /public/nft-fc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/nft-fc.gif -------------------------------------------------------------------------------- /public/steps/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/steps/1.png -------------------------------------------------------------------------------- /public/steps/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/steps/2.png -------------------------------------------------------------------------------- /public/steps/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/steps/3.png -------------------------------------------------------------------------------- /public/steps/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/steps/4.png -------------------------------------------------------------------------------- /public/devfolio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/devfolio.png -------------------------------------------------------------------------------- /public/grid-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/grid-white.png -------------------------------------------------------------------------------- /public/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/og-image.png -------------------------------------------------------------------------------- /public/base-god-ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/base-god-ss.png -------------------------------------------------------------------------------- /public/favicon_io/favicon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/favicon.gif -------------------------------------------------------------------------------- /public/favicon_io/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/favicon.ico -------------------------------------------------------------------------------- /src/app/fonts/NyghtSerif-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/src/app/fonts/NyghtSerif-Bold.ttf -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEYNAR_API_KEY=abcd # get from https://docs.neynar.com/ 2 | NEYNAR_SIGNER=abcd # get from https://docs.neynar.com/ 3 | -------------------------------------------------------------------------------- /public/favicon_io/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon_io/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/favicon-32x32.png -------------------------------------------------------------------------------- /src/app/fonts/NyghtSerif-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/src/app/fonts/NyghtSerif-Medium.ttf -------------------------------------------------------------------------------- /src/app/fonts/NyghtSerif-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/src/app/fonts/NyghtSerif-Regular.ttf -------------------------------------------------------------------------------- /public/favicon_io/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon_io/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/favicon_io/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devfolioco/supabald-jesse/HEAD/public/favicon_io/android-chrome-512x512.png -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | printWidth: 120, 4 | singleQuote: true, 5 | tabWidth: 2, 6 | useTabs: false, 7 | bracketSpacing: true, 8 | trailingComma: 'es5', 9 | endOfLine: 'auto', 10 | }; 11 | -------------------------------------------------------------------------------- /src/app/constants/urls.ts: -------------------------------------------------------------------------------- 1 | export const BLOG_URL = 'https://devfolio.co/blog/supabald-jesse/'; 2 | export const OPENSEA_COLLECTION = 'https://opensea.io/collection/supabald-jesse'; 3 | export const PROJECTS_URL = 'https://onchain-summer.devfolio.co/projects' 4 | -------------------------------------------------------------------------------- /public/favicon_io/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | async redirects() { 4 | return [ 5 | { 6 | source: '/token/:tokenID', 7 | destination: '/', 8 | permanent: false, 9 | }, 10 | ]; 11 | }, 12 | }; 13 | 14 | export default nextConfig; 15 | -------------------------------------------------------------------------------- /src/app/sections/TypographySection.tsx: -------------------------------------------------------------------------------- 1 | import OnchainTypography from '../components/OnchainTypography'; 2 | 3 | const TypographySection = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export default TypographySection; 12 | -------------------------------------------------------------------------------- /src/app/components/Ui.tsx: -------------------------------------------------------------------------------- 1 | import { inter } from '../fonts/fonts'; 2 | 3 | interface UiProps { 4 | children: React.ReactNode; 5 | className?: string; 6 | } 7 | 8 | const Ui = ({ children, className }: UiProps) => { 9 | return
{children}
; 10 | }; 11 | 12 | export default Ui; 13 | -------------------------------------------------------------------------------- /src/app/components/ApplePeekArea.tsx: -------------------------------------------------------------------------------- 1 | /* In Apple devices, scroll has elastic effect and you can scroll beneath the end of page. Here you can customize that area */ 2 | 3 | import { inter } from '../fonts/fonts'; 4 | 5 | const ApplePeekArea = () => { 6 | /* Our little easter egg */ 7 | return
Never Stop Building
; 8 | }; 9 | 10 | export default ApplePeekArea; 11 | -------------------------------------------------------------------------------- /src/app/components/Heading.tsx: -------------------------------------------------------------------------------- 1 | import { nyghtMedium } from '../fonts/fonts'; 2 | 3 | interface HeadingProps { 4 | children: React.ReactNode; 5 | className?: string; 6 | } 7 | 8 | const Heading = ({ children, className }: HeadingProps) => { 9 | return ( 10 |

11 | {children} 12 |

13 | ); 14 | }; 15 | 16 | export default Heading; 17 | -------------------------------------------------------------------------------- /src/app/fonts/fonts.ts: -------------------------------------------------------------------------------- 1 | import localFont from 'next/font/local'; 2 | import { Inter } from 'next/font/google'; 3 | 4 | export const nyghtRegular = localFont({ 5 | src: '../fonts/NyghtSerif-Regular.ttf', 6 | display: 'swap', 7 | }); 8 | 9 | export const nyghtBold = localFont({ 10 | src: '../fonts/NyghtSerif-Bold.ttf', 11 | display: 'swap', 12 | }); 13 | 14 | export const nyghtMedium = localFont({ 15 | src: '../fonts/NyghtSerif-Medium.ttf', 16 | display: 'swap', 17 | }); 18 | 19 | export const inter = Inter({ subsets: ['latin'] }); 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | .env 39 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary: #638596; 3 | --text-color: #fff; 4 | } 5 | 6 | html { 7 | scroll-behavior: smooth; 8 | } 9 | 10 | body { 11 | color: var(--text-color); 12 | background: var(--primary); 13 | overflow-x: hidden; 14 | } 15 | 16 | * { 17 | box-sizing: border-box; 18 | -webkit-font-smoothing: antialiased; 19 | -moz-osx-font-smoothing: grayscale; 20 | font-smooth: never; 21 | } 22 | 23 | @layer utilities { 24 | .text-balance { 25 | text-wrap: balance; 26 | } 27 | } 28 | 29 | @tailwind base; 30 | @tailwind components; 31 | @tailwind utilities; 32 | -------------------------------------------------------------------------------- /src/app/sections/FooterSection.tsx: -------------------------------------------------------------------------------- 1 | import Button from '../components/Button'; 2 | import Footer from '../components/Footer'; 3 | import OnchainTypography from '../components/OnchainTypography'; 4 | import { OPENSEA_COLLECTION } from '../constants/urls'; 5 | 6 | const FooterSection = () => { 7 | return ( 8 |
9 | 12 |
15 | ); 16 | }; 17 | 18 | export default FooterSection; 19 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss'; 2 | 3 | const config: Config = { 4 | content: [ 5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | screens: { 16 | '2k': '2560px', 17 | '4k': '3840px', 18 | }, 19 | }, 20 | }, 21 | plugins: [], 22 | }; 23 | export default config; 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "target": "ES2020", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /src/app/hooks/useResponsive.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useEffect, useState } from 'react'; 3 | 4 | const useResponsive = () => { 5 | const [width, setWidth] = useState(0); 6 | 7 | const handleWindowSizeChange = () => { 8 | setWidth(window.innerWidth); 9 | }; 10 | 11 | useEffect(() => { 12 | handleWindowSizeChange(); 13 | window.addEventListener('resize', handleWindowSizeChange); 14 | 15 | // Clean Up 16 | return () => { 17 | window.removeEventListener('resize', handleWindowSizeChange); 18 | }; 19 | }, []); 20 | 21 | return { 22 | isMobile: width <= 733, 23 | isTablet: width >= 744 && width <= 1024, 24 | isDesktop: width > 1024, 25 | is2K: width > 2648, 26 | }; 27 | }; 28 | 29 | export default useResponsive; 30 | -------------------------------------------------------------------------------- /src/app/utils/shared.ts: -------------------------------------------------------------------------------- 1 | let _APP_URL = 'http://localhost:3000'; 2 | 3 | if (process.env.VERCEL_ENV === 'production') { 4 | _APP_URL = `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`; 5 | } else if (process.env.VERCEL_ENV === 'preview' || process.env.VERCEL_ENV === 'development') { 6 | _APP_URL = `https://${process.env.VERCEL_URL}`; 7 | } 8 | 9 | export function isNumeric(str: string | number) { 10 | if (typeof str != 'string') return false; // we only process strings! 11 | return ( 12 | !isNaN(str as unknown as number) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)... 13 | !isNaN(parseFloat(str)) 14 | ); // ...and ensure strings of whitespace fail 15 | } 16 | 17 | export { _APP_URL as APP_URL }; 18 | -------------------------------------------------------------------------------- /src/app/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { nyghtBold } from '../fonts/fonts'; 3 | 4 | interface ButtonProps { 5 | children: React.ReactNode; 6 | className?: string; 7 | href?: string; 8 | variant: 'outlined' | 'primary' | 'secondary'; 9 | sameTab?: boolean; 10 | } 11 | 12 | const Button = ({ children, className, href = '/', variant, sameTab = false }: ButtonProps) => { 13 | return ( 14 | 19 | {children} 20 | 21 | ); 22 | }; 23 | 24 | export default Button; 25 | -------------------------------------------------------------------------------- /src/app/components/AnimatedSection.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { MotionProps, Variants, motion } from 'framer-motion'; 3 | 4 | const variants: Variants = { 5 | hidden: { 6 | opacity: 0, 7 | y: 50, 8 | }, 9 | visible: { 10 | opacity: 1, 11 | y: 0, 12 | transition: { 13 | duration: 0.4, 14 | }, 15 | }, 16 | }; 17 | 18 | const AnimatedSection = ({ children, ...props }: MotionProps & React.ComponentProps<'input'>) => { 19 | return ( 20 | 31 | {children} 32 | 33 | ); 34 | }; 35 | 36 | export default AnimatedSection; 37 | -------------------------------------------------------------------------------- /src/app/sections/MintStepsGridSection.tsx: -------------------------------------------------------------------------------- 1 | import AnimatedSection from '../components/AnimatedSection'; 2 | import Heading from '../components/Heading'; 3 | import StepGrid from '../components/StepGrid'; 4 | import Ui from '../components/Ui'; 5 | 6 | const MintStepsGridSection = () => { 7 | return ( 8 | 9 |
10 | Mint your SupaBald Jesse NFT 11 | 12 | For being based and boosting Base TVL, we've got a little reward for you! 13 | 14 | 15 | {/* The 4 step process to mint your NFT */} 16 | 17 |
18 |
19 | ); 20 | }; 21 | 22 | export default MintStepsGridSection; 23 | -------------------------------------------------------------------------------- /src/app/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { inter } from '../fonts/fonts'; 3 | 4 | const Footer = () => { 5 | return ( 6 |
9 |
10 | {`Made with <3 at`} 11 | 12 | Devfolio 13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 | Twitter / X 21 | 22 | 23 | Farcaster 24 | 25 |
26 |
27 | ); 28 | }; 29 | 30 | export default Footer; 31 | -------------------------------------------------------------------------------- /src/app/components/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | const Tooltip = ({ 2 | children, 3 | message, 4 | className, 5 | }: { 6 | children: React.ReactElement | string; 7 | message: React.ReactElement | string; 8 | className?: string; 9 | }) => { 10 | return ( 11 | 12 | {children} 13 | 14 | 15 | 16 | {message} 17 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default Tooltip; 24 | -------------------------------------------------------------------------------- /src/app/sections/VideoSection.tsx: -------------------------------------------------------------------------------- 1 | import AnimatedSection from '../components/AnimatedSection'; 2 | import Heading from '../components/Heading'; 3 | import { YoutubePlayer } from '../components/YoutubePlayer'; 4 | 5 | const VideoSection = () => { 6 | return ( 7 | 8 |
9 | 10 | We're not making this up. Hear it from Jesse himself! 11 | 12 | {/* This element adds invisible space for the next video element to be translated Y -50% */} 13 |
14 |
15 |
16 | 17 |
18 |
19 | ); 20 | }; 21 | 22 | export default VideoSection; 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Devfolio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supabald-jesse", 3 | "description": "The website that helps build the next based experience at the Onchain Summer Buildathon and make Jesse go bald", 4 | "repository": "https://github.com/devfolioco/supabald-jesse", 5 | "version": "1.0.0", 6 | "private": true, 7 | "scripts": { 8 | "dev": "next dev", 9 | "build": "next build", 10 | "start": "next start", 11 | "lint": "next lint", 12 | "format": "prettier --check --ignore-path .gitignore .", 13 | "format:fix": "prettier --write --ignore-path .gitignore ." 14 | }, 15 | "dependencies": { 16 | "@neynar/nodejs-sdk": "^1.27.0", 17 | "@vercel/analytics": "^1.3.1", 18 | "framer-motion": "^11.2.6", 19 | "frog": "^0.11.4", 20 | "hono": "^4.3.11", 21 | "next": "14.2.3", 22 | "react": "^18", 23 | "react-dom": "^18", 24 | "react-player": "^2.16.0", 25 | "satori": "^0.10.13", 26 | "uuid": "^9.0.1" 27 | }, 28 | "devDependencies": { 29 | "@types/node": "^20", 30 | "@types/react": "^18", 31 | "@types/react-dom": "^18", 32 | "@types/uuid": "^9.0.8", 33 | "eslint": "^8", 34 | "eslint-config-next": "14.2.3", 35 | "eslint-config-prettier": "^9.1.0", 36 | "eslint-plugin-prettier": "^5.1.3", 37 | "postcss": "^8", 38 | "prettier": "^3.2.5", 39 | "tailwindcss": "^3.4.1", 40 | "typescript": "^5" 41 | }, 42 | "packageManager": "yarn@1.22.22" 43 | } 44 | -------------------------------------------------------------------------------- /src/app/components/StepItem.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Variants, motion } from 'framer-motion'; 3 | import Image from 'next/image'; 4 | import Link from 'next/link'; 5 | import { nyghtBold } from '../fonts/fonts'; 6 | 7 | const variants: Variants = { 8 | hidden: { 9 | opacity: 0, 10 | y: 50, 11 | }, 12 | visible: { 13 | opacity: 1, 14 | y: 0, 15 | transition: { 16 | duration: 0.5, 17 | }, 18 | }, 19 | }; 20 | 21 | const StepItem = ({ step, label, image, href }: { step: number; label: string; image: string; href: string }) => { 22 | return ( 23 | 24 |
27 | {step} 28 |
29 | 30 | {label} 31 |
34 | {label} 35 |
36 | 37 |
38 | ); 39 | }; 40 | 41 | export default StepItem; 42 | -------------------------------------------------------------------------------- /src/app/components/StepGrid.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import StepItem from './StepItem'; 4 | import { Variants, motion } from 'framer-motion'; 5 | 6 | const containerVariants: Variants = { 7 | hidden: {}, 8 | visible: { 9 | transition: { 10 | duration: 0.1, 11 | staggerChildren: 0.3, 12 | type: 'spring', 13 | stiffness: 0.5, 14 | }, 15 | }, 16 | }; 17 | 18 | const StepGrid = () => { 19 | return ( 20 | 31 | 37 | 43 | 49 | 55 | 56 | ); 57 | }; 58 | 59 | export default StepGrid; 60 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Analytics } from '@vercel/analytics/react'; 2 | import type { Metadata } from 'next'; 3 | import './globals.css'; 4 | import Head from 'next/head'; 5 | import ApplePeekArea from './components/ApplePeekArea'; 6 | 7 | export const metadata: Metadata = { 8 | title: 'Let’s get Jesse bald!', 9 | description: 'Participate in the buildathon. Get more assets on Base. Help us get Jesse bald.', 10 | openGraph: { 11 | type: 'website', 12 | url: 'https://letsgetjessebald.com/', 13 | title: 'SupaBald Jesse', 14 | description: 'Build the next based experience at the Onchain Summer Buildathon and watch Jesse go bald ;)', 15 | images: '/og-image.png', 16 | }, 17 | twitter: { 18 | card: 'summary_large_image', 19 | title: 'SupaBald Jesse', 20 | description: 'Build the next based experience at the Onchain Summer Buildathon and watch Jesse go bald ;)', 21 | images: '/og-image.png', 22 | }, 23 | icons: { 24 | icon: '/favicon_io/favicon.gif', 25 | apple: '/favicon_io/apple-touch-icon.png', 26 | }, 27 | }; 28 | 29 | export default function RootLayout({ 30 | children, 31 | }: Readonly<{ 32 | children: React.ReactNode; 33 | }>) { 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | SupaBald Jesse 41 | 42 | 43 | {children} 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/app/sections/HomeSection.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import Ui from '../components/Ui'; 3 | import Button from '../components/Button'; 4 | import Heading from '../components/Heading'; 5 | import Link from 'next/link'; 6 | import { BLOG_URL } from '../constants/urls'; 7 | 8 | const HomeSection = ({ projectsUrl }: { projectsUrl: string }) => { 9 | return ( 10 |
11 | NFT 19 |
20 | 21 | Let’s get
22 | Jesse bald! 23 |
24 | 25 | Build the next based experience at the Onchain Summer Buildathon and watch{' '} 26 | 27 | Jesse 28 | {' '} 29 | go bald ;) 30 | 31 |
32 |
33 | 36 | 39 |
40 |
41 | ); 42 | }; 43 | 44 | export default HomeSection; 45 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import './styles/app.css'; 2 | import { getFrameMetadata } from 'frog/next'; 3 | import type { Metadata } from 'next'; 4 | import HomeSection from './sections/HomeSection'; 5 | import TypographySection from './sections/TypographySection'; 6 | import WhySection from './sections/WhySection'; 7 | import MintStepsGridSection from './sections/MintStepsGridSection'; 8 | import VideoSection from './sections/VideoSection'; 9 | import FooterSection from './sections/FooterSection'; 10 | 11 | import { APP_URL } from './utils/shared'; 12 | 13 | export async function generateMetadata(): Promise { 14 | const url = APP_URL; 15 | const frameMetadata = await getFrameMetadata(`${url}/api`); 16 | 17 | return { 18 | other: frameMetadata, 19 | }; 20 | } 21 | 22 | export default function Home({ searchParams }: { searchParams: { ref_code?: string } }) { 23 | const referralCode = searchParams.ref_code; 24 | const registrationUrl = `https://onchain-summer.devfolio.co${referralCode ? '?ref_code=' + referralCode : ''}`; 25 | const projectsUrl = `https://onchain-summer.devfolio.co/projects${referralCode ? '?ref_code=' + referralCode : ''}`; 26 | 27 | return ( 28 |
29 | {/* Let's get jesse bald */} 30 | 31 | 32 | {/* Animated Typography */} 33 | 34 | 35 | {/* Because that's what he said... */} 36 | 37 | 38 | {/* Mint your SupaBald Jesse NFT */} 39 | 40 | 41 | {/* We're not making this up. Hear it from Jesse himself! */} 42 | 43 | 44 | {/* Footer & Animated */} 45 | 46 |
47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/app/components/YoutubePlayer.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useEffect, useRef, useState } from 'react'; 3 | import ReactPlayer from 'react-player'; 4 | 5 | const YoutubePlayer = ({ url }: { url: string }) => { 6 | const [isMounted, setIsMounted] = useState(false); 7 | const videoContainerRef = useRef(null); 8 | 9 | useEffect(() => { 10 | if (!videoContainerRef.current) return; 11 | const resizeObserver = new ResizeObserver(() => { 12 | /* Store the updated height of the video container in --video-height css variable. (Used to translate the video element to -50% at Y-axis) */ 13 | document.documentElement.style.setProperty('--video-height', `${videoContainerRef.current?.clientHeight ?? 0}px`); 14 | }); 15 | resizeObserver.observe(videoContainerRef.current); 16 | return () => resizeObserver.disconnect(); 17 | }, [isMounted]); 18 | 19 | useEffect(() => { 20 | setIsMounted(true); 21 | }, []); 22 | 23 | if (!isMounted) { 24 | return null; 25 | } 26 | 27 | return ( 28 |
29 |
30 | 49 |
50 |
51 | ); 52 | }; 53 | 54 | export { YoutubePlayer }; 55 | -------------------------------------------------------------------------------- /src/app/frog/index.ts: -------------------------------------------------------------------------------- 1 | import { createSystem, defaultVars } from 'frog/ui'; 2 | 3 | // Read the fonts from local file system (Not working in Production) 4 | 5 | // import fs from 'fs'; 6 | // import path from 'path'; 7 | // import { fileURLToPath } from 'url'; 8 | 9 | // const nyghtSerifMedium = fs.readFileSync( 10 | // path.join(fileURLToPath(import.meta.url), '../../fonts/NyghtSerif-Medium.ttf') 11 | // ); 12 | // const nyghtSerifBold = fs.readFileSync(path.join(fileURLToPath(import.meta.url), '../../fonts/NyghtSerif-Bold.ttf')); 13 | 14 | const nyghtSerifMedium = await fetch( 15 | 'https://assets-devrel.s3.ap-south-1.amazonaws.com/OCS/NyghtSerif-Medium.ttf' 16 | ).then(res => res.arrayBuffer()); 17 | 18 | const nyghtSerifBold = await fetch('https://assets-devrel.s3.ap-south-1.amazonaws.com/OCS/NyghtSerif-Bold.ttf').then( 19 | res => res.arrayBuffer() 20 | ); 21 | 22 | export const { Box, Columns, Column, Divider, Heading, HStack, Icon, Image, Rows, Row, Spacer, Text, VStack, vars } = 23 | createSystem({ 24 | colors: { 25 | ...defaultVars.colors, 26 | text: '#F3F3F3', 27 | background: '#638596', 28 | castBackground: '#1B1423', 29 | castColor: '#C848FF', 30 | castGrey: '#8B99A4', 31 | }, 32 | fonts: { 33 | default: [ 34 | { 35 | name: 'Inter', 36 | source: 'google', 37 | weight: 300, 38 | }, 39 | { 40 | name: 'Inter', 41 | source: 'google', 42 | weight: 600, 43 | }, 44 | ], 45 | nyght: [ 46 | { 47 | name: 'NyghtSerif-Medium', 48 | data: nyghtSerifMedium, 49 | source: 'buffer', 50 | weight: 400, 51 | }, 52 | { 53 | name: 'NyghtSerif-Bold', 54 | data: nyghtSerifBold, 55 | source: 'buffer', 56 | weight: 700, 57 | }, 58 | ], 59 | }, 60 | }); 61 | -------------------------------------------------------------------------------- /public/onchain-m1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/sections/WhySection.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import AnimatedSection from '../components/AnimatedSection'; 3 | import Button from '../components/Button'; 4 | import Heading from '../components/Heading'; 5 | import Tooltip from '../components/Tooltip'; 6 | import Ui from '../components/Ui'; 7 | import { BLOG_URL } from '../constants/urls'; 8 | 9 | const baseGodScreenshot = ( 10 | Base God Message on Telegram 17 | ); 18 | 19 | const WhySection = ({ projectsUrl }: { projectsUrl: string }) => { 20 | return ( 21 | 22 |
23 | Because that’s what he said... 24 |
25 | 26 |

27 | In a galaxy not so far away, the{' '} 28 | 29 | Base God commune 30 | 31 | 32 | Base God commune 33 | {' '} 34 | to be precise, Jesse committed to shaving his head off when Base hits 10 billion in TVL. Join this quest 35 | to hold him accountable and claim a SupaBald Jesse NFT for your contribution. 36 |

37 |
38 |

39 | Participate in the buildathon, create breakout experiences, push Base TVL to the moon, and get Jesse bald. 40 |

41 |
42 | 43 |
44 | 47 | 50 |
51 |
52 |
53 |
54 | ); 55 | }; 56 | 57 | export default WhySection; 58 | -------------------------------------------------------------------------------- /public/onchain-m2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/components/AnimatedText.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Variant, useAnimation, useInView } from 'framer-motion'; 4 | import { useEffect, useRef } from 'react'; 5 | import { motion } from 'framer-motion'; 6 | 7 | type AnimatedTextProps = { 8 | text: string | string[]; 9 | el?: keyof JSX.IntrinsicElements; 10 | className?: string; 11 | once?: boolean; 12 | repeatDelay?: number; 13 | animation?: { 14 | hidden: Variant; 15 | visible: Variant; 16 | }; 17 | }; 18 | 19 | const defaultAnimations = { 20 | hidden: { 21 | opacity: 0, 22 | y: 20, 23 | }, 24 | visible: { 25 | opacity: 1, 26 | y: 0, 27 | transition: { 28 | duration: 0.1, 29 | }, 30 | }, 31 | }; 32 | 33 | export const AnimatedText = ({ 34 | text, 35 | className, 36 | once, 37 | repeatDelay, 38 | animation = defaultAnimations, 39 | }: AnimatedTextProps) => { 40 | const controls = useAnimation(); 41 | const textArray = Array.isArray(text) ? text : [text]; 42 | const ref = useRef(null); 43 | const isInView = useInView(ref, { amount: 0.5, once }); 44 | 45 | useEffect(() => { 46 | let timeout: NodeJS.Timeout; 47 | const show = () => { 48 | controls.start('visible'); 49 | if (repeatDelay) { 50 | timeout = setTimeout(async () => { 51 | await controls.start('hidden'); 52 | controls.start('visible'); 53 | }, repeatDelay); 54 | } 55 | }; 56 | 57 | if (isInView) { 58 | show(); 59 | } else { 60 | controls.start('hidden'); 61 | } 62 | 63 | return () => clearTimeout(timeout); 64 | }, [controls, isInView, repeatDelay]); 65 | 66 | return ( 67 |

68 | {textArray.join(' ')} 69 | 79 | {textArray.map((line, lineIndex) => ( 80 | 81 | {line.split(' ').map((word, wordIndex) => ( 82 | 83 | {word.split('').map((char, charIndex) => ( 84 | 85 | {char} 86 | 87 | ))} 88 |   89 | 90 | ))} 91 | 92 | ))} 93 | 94 |

95 | ); 96 | }; 97 | -------------------------------------------------------------------------------- /src/app/styles/app.css: -------------------------------------------------------------------------------- 1 | /* Background Styles */ 2 | 3 | .bg-primary { 4 | background-color: var(--primary); 5 | } 6 | 7 | .bg-grid { 8 | background-image: url('/grid.png'); 9 | } 10 | 11 | .bg-grid-white { 12 | background-image: url('/grid-white.png'); 13 | } 14 | 15 | /* Design Text Styles */ 16 | 17 | .hero-heading { 18 | color: #f3f3f3; 19 | font-style: normal; 20 | font-weight: normal; 21 | line-height: 110%; 22 | } 23 | 24 | .hero-heading-700 { 25 | color: #f3f3f3; 26 | font-weight: 400; 27 | line-height: 130%; 28 | } 29 | 30 | .ui-text { 31 | color: #f3f3f3; 32 | font-weight: 300; 33 | line-height: 150%; 34 | } 35 | 36 | /* Button and its variants */ 37 | 38 | .button { 39 | display: flex; 40 | justify-content: center; 41 | align-items: center; 42 | border-radius: 8px; 43 | line-height: 32px; 44 | transition: 0.2s all linear; 45 | } 46 | 47 | .button:hover { 48 | opacity: 0.8; 49 | } 50 | 51 | .primary-button { 52 | background-color: white; 53 | color: var(--primary); 54 | } 55 | 56 | .secondary-button { 57 | background-color: var(--primary); 58 | color: white; 59 | } 60 | 61 | .outlined-button { 62 | background-color: transparent; 63 | color: white; 64 | border: 2px solid #7d98a6; 65 | } 66 | 67 | /* Step Grid Component Styles */ 68 | 69 | .step-item .step-item-label { 70 | border: 3px solid #7d98a6; 71 | background: var(--primary); 72 | } 73 | 74 | .step-item a { 75 | width: 100%; 76 | } 77 | 78 | .step-item .step-item-id { 79 | background: #7da3b6; 80 | border-radius: 50%; 81 | display: flex; 82 | justify-content: center; 83 | align-items: center; 84 | box-shadow: 85 | 0px 1px 5px 0px rgba(3, 0, 92, 0.09), 86 | 0px 6px 16px 0px rgba(3, 0, 92, 0.12); 87 | position: relative; 88 | top: 21px; 89 | } 90 | 91 | /* Footer Styles */ 92 | 93 | .footer { 94 | color: #8e989c; 95 | font-style: normal; 96 | line-height: 24px; 97 | border-top: 1px solid #e4eaeb; 98 | } 99 | 100 | /* Translate Video Container to -50% at Y-axis */ 101 | 102 | .half-video-height { 103 | height: calc((var(--video-height)) / 2); 104 | } 105 | 106 | .transform-top-50-video { 107 | transform: translateY(calc(-1 * (var(--video-height) + 32px) / 2)); 108 | } 109 | 110 | .negative-margin-50-video { 111 | margin-bottom: calc(-1 * (var(--video-height)) / 2); 112 | } 113 | 114 | /* Other Styles */ 115 | 116 | .ios-peek { 117 | position: fixed; 118 | z-index: -1; 119 | bottom: 0; 120 | left: 0; 121 | padding-top: 50vh; 122 | width: 100vw; 123 | background: white; 124 | color: #aeb4b7; 125 | text-align: center; 126 | } 127 | 128 | .clip-bottom { 129 | clip-path: polygon(50% 0, 100% 100%, 0 100%); 130 | } 131 | -------------------------------------------------------------------------------- /public/onchain-m3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SupaBald Jesse 2 | 3 | ![Preview GIF](https://letsgetjessebald.com/nft-fc.gif) 4 | 5 | This repository contains the website and frame for the "Let's get Jesse Bald" campaign by [Devfolio](https://devfolio.co/) for the [Onchain Summer Buildathon](https://onchain-summer.devfolio.co/) 6 | 7 | [![Deployment](https://img.shields.io/website?url=https%3A%2F%2Fletsgetjessebald.com%2F&up_message=https%3A%2F%2Fletsgetjessebald.com%2F&logo=curl&label=Deployment)](https://letsgetjessebald.com/) 8 | 9 | [![Build Status](https://img.shields.io/github/deployments/devfolioco/supabald-jesse/production?logo=github&label=Build%20Status)](https://github.com/devfolioco/supabald-jesse/deployments/Production) 10 | 11 | # Getting Started 12 | 13 | ### Prerequisites 14 | 15 | - Node 18 + 16 | - Yarn 17 | 18 | ### Environment Variables 19 | 20 | - Create .env.local from .env.example 21 | - Get Neynar API Keys from https://docs.neynar.com 22 | 23 | ### Installation 24 | 25 | ```bash 26 | # Using yarn (Recommended) 27 | yarn 28 | yarn dev 29 | ``` 30 | 31 | # Tech Stack 32 | 33 | - NextJS with the App Router - https://nextjs.org/ 34 | - Frog.JS for Farcaster Frames - https://frog.fm/ 35 | - TailwindCSS - tailwindcss.com/ 36 | - Framer Motion - https://framer.com/motion/ 37 | - Prettier / ESlint - https://prettier.io/ https://eslint.org/ 38 | 39 | # Project Structure 40 | 41 | ```bash 42 | ├── .next/ 43 | ├── node_modules/ 44 | ├── public/ 45 | ├── src/ 46 | │ ├── app/ 47 | │ │ ├── api/ # Endpoints for the farcaster frames 48 | │ │ ├── components/ # all resuable small components 49 | │ │ ├── fonts/ 50 | │ │ ├── frog/ # contains frog ui for farcaster frames 51 | │ │ ├── hooks/ # common components logic as hooks 52 | │ │ ├── sections/ # Landing page sections 53 | │ │ ├── styles/ # all css 54 | │ │ ├── utils/ # common helper methods 55 | │ │ ├── globals.css 56 | │ │ ├── layout.tsx # html layout 57 | │ │ ├── page.tsx # main page 58 | ├── .env.local # local env 59 | ├── .eslintrc.json 60 | ├── .gitignore 61 | ├── .prettierrc.js 62 | ├── next-env.d.ts 63 | ├── next.config.mjs 64 | ├── package.json 65 | ├── postcss.config.mjs 66 | ├── README.md 67 | ├── tailwind.config.ts 68 | ├── tsconfig.json 69 | ├── yarn.lock 70 | 71 | ``` 72 | 73 | # Contributing 74 | 75 | Feel free to open [issues](https://github.com/devfolioco/supabald-jesse/issues/new/choose) and [pull requests](https://github.com/devfolioco/supabald-jesse/pulls)! 76 | 77 | ## Contributors 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 89 | 90 | 91 | ## License 92 | 93 | [![License](https://img.shields.io/github/license/devfolioco/supabald-jesse#reload)](https://github.com/devfolioco/supabald-jesse/blob/main/LICENSE) 94 | -------------------------------------------------------------------------------- /public/onchain-m4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/components/OnchainTypography.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import Image from 'next/image'; 3 | import useResponsive from '../hooks/useResponsive'; 4 | import { Variants, motion, useScroll, useTransform } from 'framer-motion'; 5 | 6 | const OnchainTypography = () => { 7 | const { isMobile, isTablet, isDesktop, is2K } = useResponsive(); 8 | 9 | /* Typography left and right animation on scroll */ 10 | const { scrollYProgress } = useScroll(); 11 | const scrollYProgressInverted = useTransform(scrollYProgress, [0, 1], [1, 0]); 12 | const objectPositionMoveLeft = useTransform(scrollYProgress, value => `${-1 * value * 1000}px 100%`); 13 | const objectPositionMoveRight = useTransform(scrollYProgressInverted, value => `${-1 * value * 1000}px 100%`); 14 | 15 | const variants: Variants = { 16 | hidden: { 17 | opacity: 0, 18 | }, 19 | visible: { 20 | opacity: 1, 21 | y: 0, 22 | transition: { 23 | duration: 0, 24 | }, 25 | }, 26 | }; 27 | 28 | return ( 29 | 40 | {/* All Desktop Screens */} 41 | {isDesktop && ( 42 | <> 43 | {/* Desktop 1080p */} 44 | {!is2K && ( 45 | <> 46 | 52 | 53 | 59 | 60 | )} 61 | 62 | {/* Desktop 2k */} 63 | {is2K && ( 64 | <> 65 | 71 | 72 | 78 | 79 | )} 80 | 81 | )} 82 | 83 | {/* Tablet */} 84 | {isTablet && ( 85 | Onchain Summer 93 | )} 94 | 95 | {/* Mobile */} 96 | {isMobile && ( 97 | <> 98 | Onchain Summer 106 | Onchain Summer 114 | Onchain Summer 122 | Onchain Summer 130 | 131 | )} 132 | 133 | ); 134 | }; 135 | 136 | export default OnchainTypography; 137 | -------------------------------------------------------------------------------- /public/onchain-tablet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/app/api/[[...routes]]/route.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource frog/jsx */ 2 | import { Button, FrameResponse, Frog, TextInput } from 'frog'; 3 | import { handle } from 'frog/next'; 4 | import { neynar as neynarMiddleware, NeynarUser as NeynarMiddlewareUser } from 'frog/middlewares'; 5 | import { devtools } from 'frog/dev'; 6 | import { serveStatic } from 'frog/serve-static'; 7 | import { v4 } from 'uuid'; 8 | 9 | import { NeynarAPIClient } from '@neynar/nodejs-sdk'; 10 | import type { User as NeynarUserV1 } from '@neynar/nodejs-sdk/build/neynar-api/v1'; 11 | import type { User as NeynarUserV2 } from '@neynar/nodejs-sdk/build/neynar-api/v2'; 12 | 13 | import { Box, Heading, Text, VStack, vars } from '../../frog'; 14 | 15 | import { APP_URL, isNumeric } from '../../utils/shared'; 16 | import { OPENSEA_COLLECTION, BLOG_URL, PROJECTS_URL } from '../../constants/urls'; 17 | 18 | const NEYNAR_API_KEY = process.env.NEYNAR_API_KEY ?? ''; 19 | const NEYNAR_SIGNER = process.env.NEYNAR_SIGNER ?? ''; 20 | 21 | const neynarClient = new NeynarAPIClient(NEYNAR_API_KEY); 22 | 23 | type State = { 24 | confirm: { 25 | interactor: NeynarMiddlewareUser; 26 | devfolio: NeynarUserV1; 27 | searchUser: NeynarUserV2; 28 | } | null; 29 | }; 30 | 31 | const app = new Frog<{ State: State }>({ 32 | basePath: '/api', 33 | browserLocation: '/:path', 34 | ui: { vars }, 35 | initialState: {}, 36 | headers: { 37 | 'cache-control': 'max-age=0', 38 | }, 39 | }).use( 40 | neynarMiddleware({ 41 | apiKey: NEYNAR_API_KEY, 42 | features: ['interactor'], 43 | }) 44 | ); 45 | 46 | // COMPONENTS START 47 | 48 | const ErrorResponse = (error: string): FrameResponse => { 49 | return { 50 | title: 'Error', 51 | image: ( 52 | 53 | 54 | 55 | Error occured :/ 56 | 57 | 58 | 59 | {error} 60 | 61 | 62 | 63 | ), 64 | intents: [Try again], 65 | }; 66 | }; 67 | 68 | // COMPONENTS END 69 | 70 | // FRAMES 71 | 72 | app.frame('/', c => { 73 | return c.res({ 74 | title: 'SupaBald Jesse', 75 | image: `${APP_URL}/nft-fc.gif`, 76 | intents: [ 77 | 78 | Mint your NFT 79 | , 80 | , 83 | 84 | View collection 85 | , 86 | ], 87 | }); 88 | }); 89 | 90 | // Disable Nomination 91 | app.frame('/nominate/:id', c => { 92 | return c.res({ 93 | title: 'SupaBald Jesse | Nominate', 94 | image: ( 95 | 96 | 97 | 98 | Buildathon has ended 99 | 100 | 101 | 102 | Here's how based it was! 103 | 104 | {/* 105 | // @ts-expect-error Ignore the error with
*/} 106 | 107 | Builders: 10,000+ 108 |
109 | Projects built: 1200+ 110 |
111 | SupaBald Jesse's claimed: 8850+ 112 |
113 |
114 |
115 | ), 116 | intents: [ 117 | 118 | 🛠 View projects 119 | , 120 | 121 | 📖 Read more 122 | , 123 | ], 124 | }); 125 | }); 126 | 127 | // app.frame('/nominate/:id', c => { 128 | // return c.res({ 129 | // title: 'SupaBald Jesse | Nominate', 130 | // image: ( 131 | // 132 | // 133 | // 134 | // Nominate a fren 135 | // 136 | 137 | // 138 | // Frens don’t let frens miss out on opportunities. Ask a fren to join you at the Onchain Summer Buildathon. 139 | // 140 | // 141 | // 142 | // ), 143 | // intents: [ 144 | // , 145 | // Back, 146 | // , 149 | // ], 150 | // }); 151 | // }); 152 | 153 | app.frame('/confirm/:id', async c => { 154 | const interactor = c.var.interactor; 155 | if (!interactor) { 156 | return c.res(ErrorResponse('Interactor is not set!')); 157 | } 158 | 159 | const devfolioLookupResponse = await neynarClient.lookupUserByUsername('devfolio').catch(() => false); 160 | if (typeof devfolioLookupResponse === 'boolean') { 161 | return c.res(ErrorResponse('Devfolio profile not found!')); 162 | } 163 | const devfolio = devfolioLookupResponse.result.user; 164 | 165 | let searchUser: NeynarUserV2 | undefined; 166 | 167 | if (!c.inputText) { 168 | return c.res(ErrorResponse('Farcaster username is required!')); 169 | } 170 | 171 | const isSearchInputNumber = isNumeric(c.inputText); 172 | 173 | if (isSearchInputNumber) { 174 | const searchUserByFIDResult = await neynarClient.fetchBulkUsers([Number(c.inputText)]).catch(() => false); 175 | 176 | if (typeof searchUserByFIDResult != 'boolean' && searchUserByFIDResult.users.length > 0) { 177 | searchUser = searchUserByFIDResult.users?.[0]; 178 | } 179 | } else { 180 | const searchUserByUsernameResult = await neynarClient.searchUser(c.inputText).catch(() => false); 181 | 182 | if (typeof searchUserByUsernameResult != 'boolean' && searchUserByUsernameResult?.result?.users?.length > 0) { 183 | searchUser = searchUserByUsernameResult?.result?.users?.[0]; 184 | } 185 | } 186 | 187 | if (!searchUser) { 188 | return c.res( 189 | ErrorResponse(`We could not find @${c.inputText} on Farcaster. Please double check the username and try again.`) 190 | ); 191 | } 192 | 193 | // Set the state 194 | const state = c.deriveState(previousState => { 195 | previousState.confirm = { 196 | devfolio: devfolio, 197 | interactor, 198 | searchUser, 199 | }; 200 | }); 201 | 202 | const confirmState = state.confirm; 203 | 204 | if (!confirmState) { 205 | return c.res(ErrorResponse('Invalid State')); 206 | } 207 | 208 | const cast = `🔵 gm @${confirmState.searchUser.username}. @${confirmState.interactor.username} thinks you're a super based builder, and has nominated you for the Onchain Summer Buildathon. 209 | 210 | Hop in, mint your SupaBald Jesse NFT, and just build it. LFG 211 | 212 | https://letsgetjessebald.com/`; 213 | 214 | await neynarClient.publishCast(NEYNAR_SIGNER, cast, { 215 | embeds: [{ url: 'https://letsgetjessebald.com/' }], 216 | }); 217 | 218 | return c.res({ 219 | title: 'SupaBald Jesse | Cast Sent', 220 | image: ( 221 | 222 | 223 | 224 | Cast sent! 225 | 226 | 227 | 228 | You’re based. 229 | 230 | 231 | 232 | ), 233 | intents: [ 234 | 👍, 235 | , 238 | ], 239 | }); 240 | }); 241 | 242 | app.frame('/cast', c => { 243 | const state = c.previousState; 244 | const confirmState = state.confirm; 245 | 246 | if (!confirmState) { 247 | return c.res(ErrorResponse('Invalid State')); 248 | } 249 | 250 | const cast = `🔵 gm @${confirmState.searchUser.username}. @${confirmState.interactor.username} thinks you're a super based builder, and has nominated you for the Onchain Summer Buildathon. 251 | 252 | Hop in, mint your SupaBald Jesse NFT, and just build it. LFG 253 | 254 | https://letsgetjessebald.com/`; 255 | 256 | neynarClient.publishCast(NEYNAR_SIGNER, cast, { 257 | embeds: [{ url: 'https://letsgetjessebald.com/' }], 258 | }); 259 | 260 | return c.res({ 261 | title: 'SupaBald Jesse | Cast Sent', 262 | image: ( 263 | 264 | 265 | 266 | Cast sent! 267 | 268 | 269 | 270 | You’re based. 271 | 272 | 273 | 274 | ), 275 | intents: [ 276 | 👍, 277 | , 280 | ], 281 | }); 282 | }); 283 | 284 | devtools(app, { serveStatic }); 285 | 286 | export const GET = handle(app); 287 | export const POST = handle(app); 288 | -------------------------------------------------------------------------------- /public/onchain-d1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/onchain-4k-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/onchain-d2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/onchain-4k-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | --------------------------------------------------------------------------------