diff --git a/apps/web/package.json b/apps/web/package.json index 1dea5e859..46c04e555 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -42,6 +42,7 @@ "flatpickr": "^4.6.13", "formik": "^2.4.6", "framer-motion": "^12.12.1", + "graphql-request": "^7.1.2", "ioredis": "^5.8.1", "next": "^15.5.9", "nextjs-progressbar": "^0.0.16", @@ -49,6 +50,7 @@ "react": "^19.2.1", "react-dom": "^19.2.1", "react-mde": "^11.5.0", + "remove-markdown": "^0.6.3", "sablier": "^2.0.1", "sharp": "^0.34.2", "swr": "^2.3.3", diff --git a/apps/web/public/builderlogo.png b/apps/web/public/builderlogo.png new file mode 100644 index 000000000..1b06d6e5c Binary files /dev/null and b/apps/web/public/builderlogo.png differ diff --git a/apps/web/public/filled.svg b/apps/web/public/filled.svg index 067ace775..a7c13cb79 100644 --- a/apps/web/public/filled.svg +++ b/apps/web/public/filled.svg @@ -2,8 +2,8 @@ @@ -11,12 +11,12 @@ diff --git a/apps/web/public/home/collective-dark.svg b/apps/web/public/home/collective-dark.svg new file mode 100644 index 000000000..44c48739c --- /dev/null +++ b/apps/web/public/home/collective-dark.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/apps/web/public/home/creation-dark.svg b/apps/web/public/home/creation-dark.svg new file mode 100644 index 000000000..b429a88d5 --- /dev/null +++ b/apps/web/public/home/creation-dark.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/apps/web/public/home/of-dark.svg b/apps/web/public/home/of-dark.svg new file mode 100644 index 000000000..feef73033 --- /dev/null +++ b/apps/web/public/home/of-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/web/public/home/possibilities-dark.svg b/apps/web/public/home/possibilities-dark.svg new file mode 100644 index 000000000..0b10cfcbb --- /dev/null +++ b/apps/web/public/home/possibilities-dark.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/home/the-dark.svg b/apps/web/public/home/the-dark.svg new file mode 100644 index 000000000..d1780b681 --- /dev/null +++ b/apps/web/public/home/the-dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/web/public/home/unlock-dark.svg b/apps/web/public/home/unlock-dark.svg new file mode 100644 index 000000000..7f58d1af0 --- /dev/null +++ b/apps/web/public/home/unlock-dark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/apps/web/public/noggles-square-dark.svg b/apps/web/public/noggles-square-dark.svg new file mode 100644 index 000000000..2eae203f8 --- /dev/null +++ b/apps/web/public/noggles-square-dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/apps/web/public/noggles-square.svg b/apps/web/public/noggles-square.svg new file mode 100644 index 000000000..c4979bf04 --- /dev/null +++ b/apps/web/public/noggles-square.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/apps/web/public/outlined.svg b/apps/web/public/outlined.svg index 11a537134..bb2158668 100644 --- a/apps/web/public/outlined.svg +++ b/apps/web/public/outlined.svg @@ -1,6 +1,6 @@ diff --git a/apps/web/public/why-dark.svg b/apps/web/public/why-dark.svg new file mode 100644 index 000000000..dfe0d4bd2 --- /dev/null +++ b/apps/web/public/why-dark.svg @@ -0,0 +1,51 @@ + + image + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/src/components/HiddenDaoDisclosure.css.ts b/apps/web/src/components/HiddenDaoDisclosure.css.ts index 12a728b56..782ff3d53 100644 --- a/apps/web/src/components/HiddenDaoDisclosure.css.ts +++ b/apps/web/src/components/HiddenDaoDisclosure.css.ts @@ -1,4 +1,4 @@ -import { color } from '@buildeross/zord' +import { color, vars } from '@buildeross/zord' import { style } from '@vanilla-extract/css' export const hiddenDaoDisclosure = style({ @@ -23,6 +23,9 @@ export const hiddenDaoDisclosureTrigger = style({ '&:hover': { backgroundColor: color.background2, }, + 'html[data-theme-mode="dark"] &:hover': { + backgroundColor: vars.color.neutralHover, + }, }, }) diff --git a/apps/web/src/components/HiddenDaoDisclosure.tsx b/apps/web/src/components/HiddenDaoDisclosure.tsx index b912e923f..5a8e7649f 100644 --- a/apps/web/src/components/HiddenDaoDisclosure.tsx +++ b/apps/web/src/components/HiddenDaoDisclosure.tsx @@ -43,7 +43,7 @@ export const HiddenDaoDisclosure: React.FC = ({ isOpen ? hiddenDaoDisclosureChevronOpen : hiddenDaoDisclosureChevronClosed, ]} > - + {`Hidden DAOs (${count})`} diff --git a/apps/web/src/components/HoldersSection/HoldersList.css.ts b/apps/web/src/components/HoldersSection/HoldersList.css.ts index ffd516928..d474e2978 100644 --- a/apps/web/src/components/HoldersSection/HoldersList.css.ts +++ b/apps/web/src/components/HoldersSection/HoldersList.css.ts @@ -1,12 +1,13 @@ +import { vars } from '@buildeross/zord' import { style } from '@vanilla-extract/css' export const holderLink = style({ transition: 'background-color 0.2s ease-in-out', selectors: { '&:hover': { - backgroundColor: 'rgba(0, 0, 0, 0.1)', + backgroundColor: vars.color.background2, }, '&:focus-visible': { - backgroundColor: 'rgba(0, 0, 0, 0.1)', + backgroundColor: vars.color.background2, }, }, }) diff --git a/apps/web/src/components/ProposalLink.tsx b/apps/web/src/components/ProposalLink.tsx index 9e30118b8..0c524ab8e 100644 --- a/apps/web/src/components/ProposalLink.tsx +++ b/apps/web/src/components/ProposalLink.tsx @@ -35,7 +35,7 @@ export const ProposalLink = ({ proposal, chainId }: ProposalLinkProps) => { style={{ fontSize: '14px' }} > Proposal {proposal.proposalNumber}: {proposal.title} - + diff --git a/apps/web/src/layouts/CreateDaoLayout/Nav.styles.css.ts b/apps/web/src/layouts/CreateDaoLayout/Nav.styles.css.ts index 71d5cf372..05fb3c8a7 100644 --- a/apps/web/src/layouts/CreateDaoLayout/Nav.styles.css.ts +++ b/apps/web/src/layouts/CreateDaoLayout/Nav.styles.css.ts @@ -1,5 +1,5 @@ import * as z from '@buildeross/constants/layers' -import { atoms } from '@buildeross/zord' +import { atoms, color } from '@buildeross/zord' import { style } from '@vanilla-extract/css' export const NavContainer = style([ @@ -29,7 +29,7 @@ export const NavWrapper = style([ export const uploadNotificationWrapper = style({ '@media': { '(max-width: 768px)': { - background: '#fff', + background: color.background1, bottom: 0, paddingTop: 5, paddingBottom: 5, @@ -41,3 +41,12 @@ export const navLogo = style({ zIndex: z.NAV_LAYER, position: 'relative', }) + +export const navLogoGlyph = style({ + transition: 'filter 0.2s ease-in-out', + selectors: { + 'html[data-theme-mode="dark"] &': { + filter: 'invert(1)', + }, + }, +}) diff --git a/apps/web/src/layouts/CreateDaoLayout/Nav.tsx b/apps/web/src/layouts/CreateDaoLayout/Nav.tsx index 253682740..bbde47c7b 100644 --- a/apps/web/src/layouts/CreateDaoLayout/Nav.tsx +++ b/apps/web/src/layouts/CreateDaoLayout/Nav.tsx @@ -7,7 +7,7 @@ import React from 'react' import NogglesLogo from '../assets/builder-framed.svg' import TestnetLogo from '../assets/testnet.svg' import { NavMenu } from '../DefaultLayout/NavMenu' -import { NavContainer, navLogo, NavWrapper } from './Nav.styles.css' +import { NavContainer, navLogo, navLogoGlyph, NavWrapper } from './Nav.styles.css' export const Nav = () => { const isMounted = useIsMounted() @@ -21,8 +21,7 @@ export const Nav = () => { { @@ -29,8 +35,7 @@ export const Nav = () => { = ({ placement="bottom-end" close={activeDropdown !== MenuType.CHAIN_MENU} onOpenChange={(open) => onOpenMenu(open, MenuType.CHAIN_MENU)} + wrapperClassName={navPopUpWrapper} trigger={ = ({ height={'x10'} px="x2" className={chainPopUpButton} - style={isWrongNetwork ? { borderColor: '#F03232' } : undefined} + style={isWrongNetwork ? { borderColor: vars.color.negative } : undefined} > @@ -160,7 +167,7 @@ export const ChainMenu: React.FC = ({ - + @@ -169,6 +176,8 @@ export const ChainMenu: React.FC = ({ {isWrongNetwork && selectedChain && ( = ({ )} {PUBLIC_DEFAULT_CHAINS.map((chain, i, chains) => ( onChainChange(chain.id)} - cursor={ - hasNetwork - ? isSelectedChain(chain.id) - ? undefined - : 'not-allowed' - : 'pointer' - } height={'x10'} px="x4" mb={i !== chains.length - 1 ? 'x2' : undefined} align={'center'} justify={'space-between'} + disabled={hasNetwork && !isSelectedChain(chain.id)} + aria-current={selectedChain.id === chain.id ? 'location' : undefined} > diff --git a/apps/web/src/layouts/DefaultLayout/NavMenu/NavMenu.tsx b/apps/web/src/layouts/DefaultLayout/NavMenu/NavMenu.tsx index 98b07bc19..021c2e6aa 100644 --- a/apps/web/src/layouts/DefaultLayout/NavMenu/NavMenu.tsx +++ b/apps/web/src/layouts/DefaultLayout/NavMenu/NavMenu.tsx @@ -5,6 +5,7 @@ import { useAccount } from 'wagmi' import { ConnectButton } from '../ConnectButton' import { ChainMenu } from './ChainMenu' import { ProfileMenu } from './ProfileMenu' +import { ThemeToggle } from './ThemeToggle' import { MenuType } from './types' export const NavMenu = () => { @@ -27,6 +28,7 @@ export const NavMenu = () => { return ( + = ({ const onDisconnect = useWalletDisconnect() - const renderConnectedUser = () => ( + const renderConnectedUserCommon = ({ isStatic = false }: { isStatic?: boolean }) => ( <> - + = ({ ) - const renderUserContent = (isMobileFullscreen = false) => ( + const renderConnectedUser = () => renderConnectedUserCommon({ isStatic: false }) + + const renderConnectedUserStatic = () => renderConnectedUserCommon({ isStatic: true }) + + const renderUserContent = (isMobileFullscreen = false, showCreateButton = true) => ( <> {daos.length > 0 && ( <> @@ -260,10 +270,7 @@ export const ProfileMenu: React.FC = ({ style={ isMobileFullscreen ? { - maxHeight: '50vh', width: '100%', - overflow: 'auto', - WebkitOverflowScrolling: 'touch', } : { width: '100%', @@ -300,11 +307,13 @@ export const ProfileMenu: React.FC = ({ )} - - - + {showCreateButton && ( + + + + )} ) @@ -457,7 +466,7 @@ export const ProfileMenu: React.FC = ({ position: 'relative', zIndex: NAV_BUTTON_LAYER + 1, borderRadius: '50%', - borderColor: 'rgba(0, 0, 0, 0.5)', + borderColor: vars.color.border, }} className={daoButton} > @@ -499,7 +508,7 @@ export const ProfileMenu: React.FC = ({ width: '100vw', height: '100vh', zIndex: MOBILE_PROFILE_MENU_LAYER - 1, - backgroundColor: 'rgba(0, 0, 0, 0.5)', + backgroundColor: vars.color.backdrop, }} onClick={() => onSetActiveDropdown(undefined)} /> @@ -508,23 +517,54 @@ export const ProfileMenu: React.FC = ({ direction={'column'} py={'x4'} px={'x8'} - gap={'x3'} backgroundColor="background1" - className={mobileMenuSlideIn} + className={[mobileMenuSlideIn, navPopUpWrapper]} style={{ width: '100vw', + height: '100dvh', maxHeight: '100vh', position: 'fixed', top: 0, left: 0, zIndex: MOBILE_PROFILE_MENU_LAYER, paddingTop: '80px', + paddingBottom: '16px', + boxSizing: 'border-box', shadow: 'medium', }} > - {address ? renderConnectedUser() : } - {renderNavLinks()} - {address && renderUserContent(true)} + {address && renderConnectedUserStatic()} + + {!address && } + {renderNavLinks()} + {address && renderUserContent(true, false)} + + {address && ( + + + + + + )} ) @@ -564,6 +604,7 @@ export const ProfileMenu: React.FC = ({ trigger={triggerElement} close={activeDropdown !== MenuType.PROFILE_MENU} onOpenChange={handleOpenMenu} + wrapperClassName={navPopUpWrapper} > {address ? renderConnectedUser() : } diff --git a/apps/web/src/layouts/DefaultLayout/NavMenu/ThemeToggle.tsx b/apps/web/src/layouts/DefaultLayout/NavMenu/ThemeToggle.tsx new file mode 100644 index 000000000..f502b2881 --- /dev/null +++ b/apps/web/src/layouts/DefaultLayout/NavMenu/ThemeToggle.tsx @@ -0,0 +1,37 @@ +import { Box, Flex, Text } from '@buildeross/zord' +import React from 'react' +import { useThemeMode } from 'src/theme/AppThemeProvider' + +import { themeToggleButton } from '../Nav.styles.css' + +export const ThemeToggle = () => { + const [isMounted, setIsMounted] = React.useState(false) + const { mode, toggleMode } = useThemeMode() + const isDarkMode = mode === 'dark' + + React.useEffect(() => { + setIsMounted(true) + }, []) + + if (!isMounted) { + return + } + + return ( + + + + ) +} diff --git a/apps/web/src/layouts/HomeLayout/Footer.css.ts b/apps/web/src/layouts/HomeLayout/Footer.css.ts index 3d6724de6..da5b9a1a1 100644 --- a/apps/web/src/layouts/HomeLayout/Footer.css.ts +++ b/apps/web/src/layouts/HomeLayout/Footer.css.ts @@ -1,13 +1,31 @@ +import { vars } from '@buildeross/zord' import { style } from '@vanilla-extract/css' +const darkFooterSurfaceHover = vars.color.background2 +const darkFooterBorder = vars.color.border + +export const homeFooterWrapper = style({ + background: vars.color.onNeutral, +}) + export const getStartedButton = style({ fontSize: 26, - background: 'rgba(255,255,255,.3)', + background: vars.color.background1, + color: vars.color.text1, + border: `1px solid ${vars.color.border}`, selectors: { + 'html[data-theme-mode="dark"] &': { + background: vars.color.background1, + color: vars.color.text1, + borderColor: vars.color.background1, + }, '&:hover': { - background: 'rgba(255,255,255,.35)', + background: vars.color.neutralHover, cursor: 'pointer', }, + 'html[data-theme-mode="dark"] &:hover': { + background: vars.color.neutralHover, + }, }, '@media': { '(max-width: 768px)': { @@ -20,13 +38,31 @@ export const getStartedButton = style({ export const homeFooterLinks = style({ selectors: { '&:hover': { - color: '#fff', + color: 'inherit', opacity: 0.8, cursor: 'pointer', }, }, }) +export const homeFooterSocialIcon = style({ + transition: 'background-color 0.1s ease-in-out', + selectors: { + '&:hover': { + background: vars.color.neutralHover, + cursor: 'pointer', + }, + 'html[data-theme-mode="dark"] &': { + background: vars.color.background1, + color: vars.color.text1, + fill: vars.color.text1, + }, + 'html[data-theme-mode="dark"] &:hover': { + background: darkFooterSurfaceHover, + }, + }, +}) + export const homeFooterInnerWrapper = style({ '@media': { '(max-width: 768px)': { @@ -65,8 +101,13 @@ export const footerRightWrapper = style({ alignItems: 'center', paddingTop: 32, marginTop: 32, - borderTop: '2px solid #333333', + borderTop: `2px solid ${vars.color.border}`, paddingRight: 0, }, }, + selectors: { + 'html[data-theme-mode="dark"] &': { + borderColor: darkFooterBorder, + }, + }, }) diff --git a/apps/web/src/layouts/HomeLayout/Footer.tsx b/apps/web/src/layouts/HomeLayout/Footer.tsx index 714276d84..0d02cda51 100644 --- a/apps/web/src/layouts/HomeLayout/Footer.tsx +++ b/apps/web/src/layouts/HomeLayout/Footer.tsx @@ -9,11 +9,20 @@ import { getStartedButton, homeFooterInnerWrapper, homeFooterLinks, + homeFooterSocialIcon, + homeFooterWrapper, } from './Footer.css' export const Footer = () => { return ( - + { @@ -83,6 +93,7 @@ export const Footer = () => { backgroundColor="background1" borderRadius="round" p="x2" + className={homeFooterSocialIcon} /> { backgroundColor="background1" borderRadius="round" p="x2" + className={homeFooterSocialIcon} /> diff --git a/apps/web/src/modules/about/AboutPage.css.ts b/apps/web/src/modules/about/AboutPage.css.ts new file mode 100644 index 000000000..e92323d49 --- /dev/null +++ b/apps/web/src/modules/about/AboutPage.css.ts @@ -0,0 +1,1601 @@ +import { theme } from '@buildeross/zord' +import { globalStyle, keyframes, style } from '@vanilla-extract/css' + +const focusRing = { + outline: `2px solid ${theme.colors.focusRing}`, + outlineOffset: '2px', +} + +const standardBorder = `2px solid ${theme.colors.border}` +const standardBorderThin = `1px solid ${theme.colors.border}` +const softBlueBackground = `color-mix(in srgb, ${theme.colors.focusRing} 14%, ${theme.colors.background1})` +const softBlueBorder = theme.colors.focusRing + +const marqueeScroll = keyframes({ + '0%': { + transform: 'translateX(0)', + }, + '100%': { + transform: 'translateX(-50%)', + }, +}) + +export const page = style({ + width: '100%', + padding: '48px 16px 96px', + boxSizing: 'border-box', + background: theme.colors.background1, + overflowX: 'clip', + '@media': { + 'screen and (min-width: 768px)': { + padding: '64px 24px 120px', + }, + }, +}) + +export const container = style({ + maxWidth: '1180px', + margin: '0 auto', + width: '100%', + minWidth: 0, +}) + +export const centeredImageWrap = style({ + display: 'flex', + justifyContent: 'center', +}) + +export const centeredImage = style({ + width: '280px', + maxWidth: '100%', + height: 'auto', + display: 'block', + selectors: { + 'html[data-theme-mode="dark"] &': { + filter: 'invert(1)', + }, + }, +}) + +export const heroVennWrap = style({ + width: '100%', + height: '100%', + minHeight: '220px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: '12px', +}) + +export const heroVennImage = style({ + width: '100%', + maxWidth: '260px', + height: 'auto', + display: 'block', +}) + +const darkModeSelector = 'html[data-theme-mode="dark"] &' + +export const aboutLightOnly = style({ + display: 'block !important', + selectors: { + [darkModeSelector]: { + display: 'none !important', + }, + }, +}) + +export const aboutDarkOnly = style({ + display: 'none !important', + selectors: { + [darkModeSelector]: { + display: 'block !important', + }, + }, +}) + +export const section = style({ + marginTop: '72px', + '@media': { + 'screen and (min-width: 768px)': { + marginTop: '96px', + }, + }, +}) + +export const sectionHeader = style({ + display: 'flex', + flexDirection: 'column', + gap: '12px', + marginBottom: '28px', + maxWidth: '720px', +}) + +export const eyebrow = style({ + display: 'inline-flex', + width: 'fit-content', + borderRadius: '999px', + padding: '4px 10px', + background: theme.colors.background2, + border: standardBorderThin, + fontSize: '12px', + lineHeight: '16px', + letterSpacing: '0.04em', + textTransform: 'uppercase', + color: theme.colors.text3, + fontWeight: 700, +}) + +export const sectionTitle = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '28px', + lineHeight: 1.04, + whiteSpace: 'normal', + overflowWrap: 'anywhere', + color: theme.colors.text1, + '@media': { + 'screen and (min-width: 768px)': { + fontSize: '40px', + whiteSpace: 'nowrap', + overflowWrap: 'normal', + }, + }, +}) + +export const sectionTitleOnly = style({ + marginBottom: '12px', +}) + +export const sectionCopy = style({ + fontSize: '16px', + lineHeight: 1.55, + color: theme.colors.text3, +}) + +export const introCopyNoWrap = style({ + fontSize: '16px', + lineHeight: 1.55, + color: theme.colors.text3, + whiteSpace: 'normal', + '@media': { + 'screen and (min-width: 960px)': { + whiteSpace: 'nowrap', + }, + }, +}) + +export const hero = style({ + display: 'grid', + gap: '28px', + alignItems: 'stretch', + '@media': { + 'screen and (min-width: 980px)': { + gridTemplateColumns: 'minmax(0, 1.1fr) minmax(360px, 0.9fr)', + gap: '40px', + }, + }, +}) + +export const heroCopy = style({ + display: 'flex', + flexDirection: 'column', + gap: '20px', + paddingTop: '8px', + minWidth: 0, +}) + +export const heroTitle = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '36px', + lineHeight: 1, + letterSpacing: '-0.03em', + color: theme.colors.text1, + maxWidth: '760px', + overflowWrap: 'anywhere', + '@media': { + 'screen and (min-width: 768px)': { + fontSize: '52px', + }, + }, +}) + +export const heroText = style({ + maxWidth: '640px', + fontSize: '16px', + lineHeight: 1.6, + color: theme.colors.text3, + '@media': { + 'screen and (min-width: 768px)': { + fontSize: '18px', + lineHeight: 1.65, + }, + }, +}) + +export const heroHighlightList = style({ + display: 'grid', + gap: '10px', + margin: 0, + padding: 0, + listStyle: 'none', + marginTop: '-4px', +}) + +export const heroHighlight = style({ + display: 'flex', + alignItems: 'flex-start', + gap: '10px', + fontSize: '14px', + lineHeight: 1.5, + color: theme.colors.text2, +}) + +export const heroHighlightDot = style({ + width: '8px', + height: '8px', + borderRadius: '999px', + background: theme.colors.text1, + flexShrink: 0, + marginTop: '6px', +}) + +export const heroActions = style({ + display: 'flex', + flexDirection: 'column', + gap: '12px', + alignItems: 'stretch', + '@media': { + 'screen and (min-width: 640px)': { + flexDirection: 'row', + alignItems: 'center', + }, + }, +}) + +export const heroPanel = style({ + position: 'relative', + overflow: 'hidden', + borderRadius: '16px', + border: standardBorder, + background: theme.colors.background2, + padding: '14px', + minHeight: 'auto', + boxShadow: 'none', + '@media': { + 'screen and (min-width: 768px)': { + padding: '16px', + minHeight: '420px', + }, + }, +}) + +export const heroPanelGlow = style({ + position: 'absolute', + inset: 0, + pointerEvents: 'none', + background: 'transparent', +}) + +export const montageGrid = style({ + position: 'relative', + zIndex: 1, + display: 'grid', + gap: '12px', + gridTemplateColumns: '1fr', + gridTemplateAreas: '"primary" "side" "footer"', + '@media': { + 'screen and (min-width: 541px)': { + gridTemplateColumns: '1.05fr 0.95fr', + gridTemplateAreas: '"primary side" "footer footer"', + }, + }, +}) + +export const montageCard = style({ + borderRadius: '12px', + border: standardBorder, + background: theme.colors.background1, + padding: '16px', + boxShadow: 'none', + minWidth: 0, +}) + +export const montagePrimary = style({ + gridArea: 'primary', + minHeight: 'unset', +}) + +export const montageSide = style({ + gridArea: 'side', + minHeight: '180px', + '@media': { + 'screen and (min-width: 768px)': { + minHeight: '220px', + }, + }, +}) + +export const montageSecondary = style({ + gridArea: 'secondary', + minHeight: '150px', +}) + +export const montageFooter = style({ + gridArea: 'footer', + minHeight: '120px', + padding: '10px', + '@media': { + 'screen and (min-width: 768px)': { + minHeight: '180px', + }, + }, +}) + +export const logoMarquee = style({ + width: '100%', + height: '100%', + minHeight: '96px', + overflow: 'hidden', + borderRadius: '10px', + background: theme.colors.background1, + '@media': { + 'screen and (min-width: 768px)': { + minHeight: '160px', + }, + }, +}) + +export const logoMarqueeTrack = style({ + position: 'relative', + overflow: 'hidden', + width: '100%', + height: '100%', + selectors: { + '&::before': { + content: '', + position: 'absolute', + inset: 0, + background: + 'linear-gradient(90deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 10%, rgba(255, 255, 255, 0) 90%, rgba(255, 255, 255, 1) 100%)', + zIndex: 2, + pointerEvents: 'none', + }, + }, +}) + +export const logoMarqueeInner = style({ + display: 'flex', + alignItems: 'center', + gap: '12px', + width: 'max-content', + minWidth: '100%', + height: '100%', + padding: '14px 0', + animation: `${marqueeScroll} 28s linear infinite`, + '@media': { + 'screen and (min-width: 768px)': { + gap: '18px', + padding: '22px 0', + }, + }, +}) + +export const logoMarqueeItem = style({ + width: '64px', + height: '64px', + borderRadius: '12px', + overflow: 'hidden', + background: theme.colors.background1, + boxShadow: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flexShrink: 0, + '@media': { + 'screen and (min-width: 768px)': { + width: '86px', + height: '86px', + borderRadius: '22px', + }, + }, +}) + +export const logoMarqueeImage = style({ + width: '100%', + height: '100%', + objectFit: 'cover', + display: 'block', +}) + +export const montageLabel = style({ + fontSize: '12px', + letterSpacing: '0.08em', + textTransform: 'uppercase', + color: theme.colors.text4, + fontWeight: 700, +}) + +export const montageValue = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '24px', + lineHeight: 1, + whiteSpace: 'pre-line', + color: theme.colors.text1, + '@media': { + 'screen and (min-width: 768px)': { + marginTop: '10px', + fontSize: '28px', + }, + }, +}) + +export const montageBody = style({ + marginTop: '10px', + fontSize: '14px', + lineHeight: 1.55, + color: theme.colors.text3, +}) + +export const daoMiniList = style({ + display: 'grid', + gap: '10px', + marginTop: '14px', +}) + +export const daoMiniCard = style({ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + gap: '12px', + padding: '12px 14px', + borderRadius: '16px', + background: theme.colors.background2, +}) + +export const daoMiniAvatar = style({ + width: '40px', + height: '40px', + borderRadius: '14px', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + fontWeight: 700, + fontSize: '14px', + color: theme.colors.text1, +}) + +export const heroFooterStat = style({ + display: 'flex', + flexDirection: 'column', + gap: '6px', +}) + +export const heroFooterValue = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '24px', + color: theme.colors.text1, +}) + +export const statGrid = style({ + display: 'grid', + width: '100%', + gap: '16px', + gridTemplateColumns: 'repeat(1, minmax(0, 1fr))', + '@media': { + 'screen and (min-width: 640px)': { + gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', + }, + 'screen and (min-width: 1080px)': { + gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', + }, + }, +}) + +export const statCard = style({ + position: 'relative', + overflow: 'hidden', + minHeight: '178px', + borderRadius: '12px', + border: standardBorder, + background: theme.colors.background1, + padding: '22px', + boxShadow: 'none', + transition: 'border-color 0.15s ease, background-color 0.15s ease', + selectors: { + '&:hover': { + borderColor: softBlueBorder, + backgroundColor: softBlueBackground, + }, + }, +}) + +export const statAccent = style({ + position: 'absolute', + top: '18px', + right: '18px', + width: '54px', + height: '54px', + borderRadius: '12px', + opacity: 1, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontSize: '28px', + lineHeight: 1, +}) + +export const statAccentDao = style({ background: theme.colors.background2 }) +export const statAccentTreasury = style({ background: theme.colors.positive }) +export const statAccentAuction = style({ background: theme.colors.warning }) +export const statAccentProposal = style({ background: theme.colors.focusRing }) +export const statAccentMembers = style({ background: theme.colors.negative }) +export const statAccentTokens = style({ background: theme.colors.focusRing }) + +export const statLabel = style({ + position: 'relative', + zIndex: 1, + fontSize: '13px', + fontWeight: 700, + letterSpacing: '0.06em', + textTransform: 'uppercase', + color: theme.colors.text4, +}) + +export const statValue = style({ + position: 'relative', + zIndex: 1, + marginTop: '18px', + fontFamily: 'ptRoot, sans-serif', + fontSize: '36px', + lineHeight: 1, + color: theme.colors.text1, + '@media': { + 'screen and (min-width: 768px)': { + fontSize: '42px', + }, + }, +}) + +export const statDetail = style({ + position: 'relative', + zIndex: 1, + marginTop: '18px', + maxWidth: '28ch', + fontSize: '14px', + lineHeight: 1.55, + color: theme.colors.text3, +}) + +export const sectionTopRow = style({ + display: 'flex', + flexDirection: 'column', + gap: '18px', + alignItems: 'flex-start', + justifyContent: 'space-between', + marginBottom: '28px', + '@media': { + 'screen and (min-width: 768px)': { + flexDirection: 'row', + alignItems: 'flex-end', + }, + }, +}) + +export const sectionInlineRow = style({ + display: 'flex', + flexDirection: 'column', + gap: '18px', + alignItems: 'flex-start', + justifyContent: 'space-between', + marginBottom: '28px', + '@media': { + 'screen and (min-width: 960px)': { + flexDirection: 'row', + alignItems: 'center', + }, + }, +}) + +export const sectionInlineCopy = style({ + maxWidth: '760px', + fontSize: '16px', + lineHeight: 1.55, + color: theme.colors.text3, + margin: 0, +}) + +export const tabs = style({ + display: 'flex', + flexWrap: 'wrap', + gap: '10px', + '@media': { + 'screen and (max-width: 520px)': { + width: '100%', + flexWrap: 'nowrap', + gap: '6px', + }, + }, +}) + +export const tabButton = style({ + minHeight: '42px', + borderRadius: '999px', + border: standardBorderThin, + background: theme.colors.background1, + padding: '10px 16px', + fontSize: '14px', + fontWeight: 700, + color: theme.colors.text1, + transition: 'background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease', + selectors: { + '&:hover': { + cursor: 'pointer', + borderColor: softBlueBorder, + backgroundColor: softBlueBackground, + }, + '&:focus-visible': focusRing, + }, + '@media': { + 'screen and (max-width: 520px)': { + flex: '1 1 0', + minWidth: 0, + minHeight: '38px', + padding: '8px 6px', + fontSize: '13px', + }, + }, +}) + +export const activeTabButton = style({ + background: softBlueBackground, + borderColor: softBlueBorder, + color: theme.colors.text1, + selectors: { + '&:hover': { + borderColor: softBlueBorder, + backgroundColor: softBlueBackground, + color: theme.colors.text1, + }, + }, +}) + +export const daoGrid = style({ + display: 'grid', + width: '100%', + gap: '16px', + gridTemplateColumns: 'repeat(1, minmax(0, 1fr))', + '@media': { + 'screen and (min-width: 700px)': { + gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', + }, + 'screen and (min-width: 1080px)': { + gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', + }, + }, +}) + +export const mobileStatsHeading = style({ + display: 'block', + margin: '28px 0 14px', + fontFamily: 'ptRoot, sans-serif', + fontSize: '24px', + lineHeight: 1.1, + color: theme.colors.text1, + '@media': { + 'screen and (min-width: 768px)': { + display: 'none', + }, + }, +}) + +export const statsBlock = style({ + marginTop: '28px', + '@media': { + 'screen and (max-width: 767px)': { + marginTop: 0, + }, + 'screen and (min-width: 768px)': { + marginTop: '48px', + }, + }, +}) + +export const daoCard = style({ + display: 'flex', + flexDirection: 'column', + gap: '18px', + minHeight: '320px', + borderRadius: '12px', + border: standardBorder, + background: theme.colors.background1, + padding: '18px', + boxShadow: 'none', + minWidth: 0, + color: 'inherit', + textDecoration: 'none', + transition: 'border-color 0.15s ease, background-color 0.15s ease', + selectors: { + '&:hover': { + borderColor: softBlueBorder, + backgroundColor: softBlueBackground, + cursor: 'pointer', + }, + '&:focus-visible': focusRing, + }, + '@media': { + 'screen and (min-width: 768px)': { + minHeight: '360px', + }, + }, +}) + +export const daoTop = style({ + display: 'flex', + justifyContent: 'space-between', + gap: '12px', + alignItems: 'flex-start', +}) + +export const daoChainBadge = style({ + width: '34px', + height: '34px', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + flexShrink: 0, +}) + +export const daoChainBadgeImage = style({ + width: '20px', + height: '20px', + objectFit: 'contain', + display: 'block', +}) + +export const daoIdentity = style({ + display: 'flex', + gap: '12px', + alignItems: 'flex-start', + minWidth: 0, +}) + +export const daoAvatar = style({ + width: '52px', + height: '52px', + borderRadius: '999px', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + fontFamily: 'ptRoot, sans-serif', + fontSize: '18px', + fontWeight: 700, + flexShrink: 0, + overflow: 'hidden', +}) + +export const daoAvatarSurfaceA = style({ + background: theme.colors.background2, + color: theme.colors.text1, +}) +export const daoAvatarSurfaceB = style({ + background: `color-mix(in srgb, ${theme.colors.focusRing} 22%, ${theme.colors.background1})`, + color: theme.colors.text1, +}) +export const daoAvatarSurfaceC = style({ + background: theme.colors.positive, + color: theme.colors.text1, +}) +export const daoAvatarSurfaceD = style({ + background: theme.colors.warning, + color: theme.colors.text1, +}) + +export const daoAvatarImage = style({ + width: '100%', + height: '100%', + borderRadius: 'inherit', + objectFit: 'cover', + display: 'block', +}) + +export const daoName = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '20px', + lineHeight: 1.05, + color: theme.colors.text1, + overflowWrap: 'anywhere', +}) + +export const daoDescription = style({ + marginTop: '6px', + fontSize: '14px', + lineHeight: 1.6, + color: theme.colors.text3, +}) + +globalStyle(`${daoDescription} p`, { + margin: 0, +}) + +globalStyle(`${daoDescription} strong`, { + fontWeight: 700, + color: theme.colors.text2, +}) + +export const badge = style({ + display: 'inline-flex', + width: 'fit-content', + borderRadius: '999px', + border: standardBorderThin, + padding: '6px 10px', + fontSize: '12px', + fontWeight: 700, +}) + +export const daoSignal = style({ + display: 'grid', + gap: '6px', + marginTop: 'auto', + padding: '14px 16px', + borderRadius: '10px', + background: theme.colors.background2, +}) + +export const daoSignalLabel = style({ + fontSize: '12px', + textTransform: 'uppercase', + letterSpacing: '0.06em', + color: theme.colors.text4, + fontWeight: 700, +}) + +export const daoSignalValue = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '18px', + lineHeight: 1.2, + whiteSpace: 'normal', + overflowWrap: 'anywhere', + color: theme.colors.text1, +}) + +export const cardLink = style({ + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'space-between', + gap: '10px', + color: theme.colors.text1, + fontSize: '14px', + fontWeight: 700, + textDecoration: 'none', + selectors: { + '&:hover': { + opacity: 0.7, + }, + '&:focus-visible': focusRing, + }, +}) + +export const scrollRow = style({ + display: 'grid', + gap: '16px', + gridAutoFlow: 'column', + gridAutoColumns: 'minmax(84vw, 1fr)', + overflowX: 'auto', + paddingBottom: '8px', + scrollSnapType: 'x mandatory', + selectors: { + '&::-webkit-scrollbar': { + height: '10px', + }, + '&::-webkit-scrollbar-thumb': { + background: theme.colors.border, + borderRadius: '999px', + }, + }, + '@media': { + 'screen and (min-width: 640px)': { + gridAutoColumns: 'minmax(280px, 1fr)', + }, + 'screen and (min-width: 1080px)': { + gridAutoColumns: 'minmax(280px, 320px)', + }, + }, +}) + +export const coiningCard = style({ + display: 'flex', + flexDirection: 'column', + gap: '16px', + minHeight: '320px', + scrollSnapAlign: 'start', + borderRadius: '12px', + border: standardBorder, + background: theme.colors.background1, + padding: '18px', + boxShadow: 'none', + minWidth: 0, + color: 'inherit', + textDecoration: 'none', + transition: 'border-color 0.15s ease, background-color 0.15s ease', + selectors: { + '&:hover': { + borderColor: softBlueBorder, + backgroundColor: softBlueBackground, + cursor: 'pointer', + }, + '&:focus-visible': focusRing, + }, + '@media': { + 'screen and (min-width: 768px)': { + minHeight: '360px', + }, + }, +}) + +export const coiningPreview = style({ + position: 'relative', + minHeight: '180px', + borderRadius: '10px', + overflow: 'hidden', + padding: '16px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + border: standardBorderThin, + '@media': { + 'screen and (min-width: 768px)': { + minHeight: '220px', + }, + }, +}) + +export const coiningPreviewSurfaceA = style({ background: theme.colors.accent }) +export const coiningPreviewSurfaceB = style({ background: theme.colors.warning }) +export const coiningPreviewSurfaceC = style({ background: theme.colors.positive }) +export const coiningPreviewSurfaceD = style({ background: theme.colors.background2 }) + +export const coiningPreviewTop = style({ + display: 'flex', + alignItems: 'flex-start', + justifyContent: 'space-between', + gap: '12px', +}) + +export const coiningPreviewMark = style({ + alignSelf: 'flex-start', + display: 'inline-flex', + borderRadius: '999px', + background: theme.colors.background1, + border: standardBorderThin, + padding: '6px 10px', + fontSize: '12px', + fontWeight: 700, + color: theme.colors.text1, +}) + +export const coiningNetworkBadge = style({ + width: '34px', + height: '34px', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + flexShrink: 0, +}) + +export const coiningPreviewTitle = style({ + maxWidth: '12ch', + fontFamily: 'ptRoot, sans-serif', + fontSize: '26px', + lineHeight: 0.95, + color: theme.colors.text1, + overflowWrap: 'anywhere', + '@media': { + 'screen and (min-width: 768px)': { + fontSize: '32px', + }, + }, +}) + +export const coiningMeta = style({ + display: 'grid', + gap: '6px', + flex: 1, +}) + +export const coiningTitle = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '22px', + lineHeight: 1.05, + color: theme.colors.text1, +}) + +export const mutedText = style({ + fontSize: '14px', + lineHeight: 1.6, + color: theme.colors.text3, +}) + +export const amountPill = style({ + display: 'inline-flex', + width: 'fit-content', + borderRadius: '999px', + padding: '7px 12px', + background: theme.colors.background2, + color: theme.colors.text1, + border: standardBorderThin, + fontSize: '13px', + fontWeight: 700, +}) + +export const droposalList = style({ + display: 'grid', + gap: '14px', +}) + +export const droposalCard = style({ + display: 'grid', + gap: '14px', + borderRadius: '12px', + border: standardBorder, + background: theme.colors.background1, + padding: '18px', + boxShadow: 'none', + color: 'inherit', + textDecoration: 'none', + transition: 'border-color 0.15s ease, background-color 0.15s ease', + selectors: { + '&:hover': { + borderColor: softBlueBorder, + backgroundColor: softBlueBackground, + cursor: 'pointer', + }, + '&:focus-visible': focusRing, + }, + '@media': { + 'screen and (min-width: 880px)': { + gridTemplateColumns: 'minmax(0, 1fr) auto', + alignItems: 'center', + }, + }, +}) + +export const droposalMeta = style({ + display: 'flex', + flexWrap: 'wrap', + gap: '10px', + alignItems: 'center', +}) + +export const statusBadge = style({ + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + minWidth: '88px', + borderRadius: '999px', + border: standardBorderThin, + padding: '7px 12px', + fontSize: '12px', + fontWeight: 700, + background: 'transparent', +}) + +export const daoBadge = style({ + background: '#F7F3E8', + color: '#4F4738', + borderColor: theme.colors.text1, +}) + +export const droposalTitle = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '20px', + lineHeight: 1.05, + color: theme.colors.text1, + overflowWrap: 'anywhere', + '@media': { + 'screen and (min-width: 768px)': { + fontSize: '24px', + }, + }, +}) + +export const droposalSummary = style({ + fontSize: '15px', + lineHeight: 1.65, + color: theme.colors.text3, + maxWidth: '72ch', +}) + +export const droposalAside = style({ + display: 'grid', + gap: '12px', + justifyItems: 'start', + '@media': { + 'screen and (min-width: 880px)': { + justifyItems: 'end', + textAlign: 'right', + }, + }, +}) + +export const stepsGrid = style({ + display: 'grid', + gap: '16px', + gridTemplateColumns: 'repeat(1, minmax(0, 1fr))', + '@media': { + 'screen and (min-width: 860px)': { + gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', + }, + }, +}) + +export const stepCard = style({ + minHeight: '170px', + borderRadius: '12px', + border: standardBorder, + background: theme.colors.background1, + padding: '22px', + boxShadow: 'none', +}) + +export const stepHeader = style({ + display: 'flex', + alignItems: 'flex-start', + gap: '16px', +}) + +export const stepMarker = style({ + display: 'inline-flex', + minWidth: '48px', + height: '48px', + padding: '0 16px', + borderRadius: '10px', + alignItems: 'center', + justifyContent: 'center', + background: theme.colors.focusRing, + border: 'none', + color: theme.colors.background1, + fontFamily: 'ptRoot, sans-serif', + fontSize: '18px', + flexShrink: 0, +}) + +export const stepTitle = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '28px', + lineHeight: 1.02, + color: theme.colors.text1, +}) + +export const stepBody = style({ + marginTop: '14px', + fontSize: '15px', + lineHeight: 1.65, + color: theme.colors.text3, +}) + +export const valueGrid = style({ + display: 'grid', + gap: '16px', + gridTemplateColumns: 'repeat(1, minmax(0, 1fr))', + '@media': { + 'screen and (min-width: 700px)': { + gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', + }, + 'screen and (min-width: 1080px)': { + gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', + }, + }, +}) + +export const featureStrip = style({ + position: 'relative', + display: 'grid', + width: '100%', + maxWidth: '100%', + alignSelf: 'stretch', + gap: '18px', + gridTemplateColumns: 'repeat(1, minmax(0, 1fr))', + '@media': { + 'screen and (min-width: 700px)': { + gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', + }, + 'screen and (min-width: 1080px)': { + gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', + gap: '24px', + }, + }, +}) + +export const featureItem = style({ + display: 'grid', + gridTemplateColumns: '42px minmax(0, 1fr)', + gap: '12px', + alignItems: 'center', + minWidth: 0, + '@media': { + 'screen and (min-width: 700px)': { + gridTemplateColumns: '42px minmax(0, 1fr)', + justifyItems: 'stretch', + alignItems: 'center', + gap: '12px', + }, + }, +}) + +export const featureIconBadge = style({ + position: 'relative', + zIndex: 1, + width: '42px', + height: '42px', + borderRadius: '14px', + background: theme.colors.background2, + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + color: theme.colors.text1, +}) + +globalStyle(`${featureIconBadge} svg`, { + width: '20px', + height: '20px', + display: 'block', +}) + +export const featureLabel = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '17px', + lineHeight: 1.2, + fontWeight: 700, + color: theme.colors.text1, + overflowWrap: 'anywhere', +}) + +export const useCaseGrid = style({ + display: 'grid', + width: '100%', + maxWidth: '100%', + alignSelf: 'stretch', + gap: '16px', + gridTemplateColumns: 'repeat(1, minmax(0, 1fr))', + '@media': { + 'screen and (min-width: 640px)': { + gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', + }, + 'screen and (min-width: 900px)': { + gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', + }, + 'screen and (min-width: 1180px)': { + gridTemplateColumns: 'repeat(5, minmax(0, 1fr))', + }, + }, +}) + +export const valueCard = style({ + minHeight: '150px', + borderRadius: '12px', + border: standardBorder, + background: theme.colors.background1, + padding: '20px', + boxShadow: 'none', + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + justifyContent: 'space-between', +}) + +export const compactValueCard = style({ + minHeight: '96px', + borderRadius: '12px', + border: standardBorder, + background: theme.colors.background1, + padding: '16px', + boxShadow: 'none', + display: 'flex', + flexDirection: 'column', + gap: '10px', + minWidth: 0, + '@media': { + 'screen and (min-width: 768px)': { + minHeight: '108px', + padding: '18px', + }, + }, +}) + +export const valueTitle = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '24px', + lineHeight: 1.05, + color: theme.colors.text1, +}) + +export const compactValueTitle = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '18px', + lineHeight: 1.2, + color: theme.colors.text1, + display: '-webkit-box', + overflow: 'hidden', + WebkitLineClamp: 3, + WebkitBoxOrient: 'vertical', +}) + +export const compactValueEmoji = style({ + fontSize: '28px', + lineHeight: 1, +}) + +export const compactValueImage = style({ + width: '32px', + height: '32px', + objectFit: 'contain', + display: 'block', +}) + +export const activityList = style({ + display: 'grid', + gap: '14px', +}) + +export const activityCard = style({ + display: 'grid', + gap: '10px', + borderRadius: '12px', + border: standardBorder, + background: theme.colors.background1, + padding: '18px', + '@media': { + 'screen and (min-width: 820px)': { + gridTemplateColumns: '140px minmax(0, 1fr)', + alignItems: 'start', + gap: '18px', + }, + }, +}) + +export const activityMeta = style({ + display: 'inline-flex', + width: 'fit-content', + borderRadius: '999px', + padding: '6px 10px', + fontSize: '12px', + fontWeight: 700, + background: theme.colors.background2, + color: theme.colors.text3, + border: standardBorderThin, +}) + +export const activityTitle = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '22px', + lineHeight: 1.08, + color: theme.colors.text1, +}) + +export const finalCta = style({ + position: 'relative', + overflow: 'hidden', + borderRadius: '16px', + border: standardBorder, + background: theme.colors.background2, + color: theme.colors.text1, + padding: '30px 22px', + boxShadow: 'none', + '@media': { + 'screen and (min-width: 768px)': { + padding: '40px 34px', + }, + }, +}) + +export const finalCtaGlow = style({ + position: 'absolute', + inset: 0, + pointerEvents: 'none', + background: 'transparent', +}) + +export const finalCtaContent = style({ + position: 'relative', + zIndex: 1, + display: 'grid', + gap: '24px', + alignItems: 'center', + minWidth: 0, + '@media': { + 'screen and (min-width: 980px)': { + gridTemplateColumns: 'minmax(0, 1fr) auto', + gap: '32px', + }, + }, +}) + +export const finalCtaTitle = style({ + fontFamily: 'ptRoot, sans-serif', + fontSize: '32px', + lineHeight: 1, + whiteSpace: 'normal', + overflowWrap: 'anywhere', + '@media': { + 'screen and (min-width: 768px)': { + fontSize: '52px', + whiteSpace: 'nowrap', + overflowWrap: 'normal', + }, + }, +}) + +export const finalChecklist = style({ + marginTop: '12px', + marginBottom: 0, + padding: 0, + listStyle: 'none', + display: 'grid', + gap: '10px', +}) + +export const finalChecklistItem = style({ + display: 'flex', + alignItems: 'flex-start', + gap: '10px', + fontSize: '15px', + lineHeight: 1.55, + color: theme.colors.text3, + '@media': { + 'screen and (min-width: 768px)': { + alignItems: 'center', + fontSize: '17px', + lineHeight: 1.6, + }, + }, +}) + +export const finalChecklistMarker = style({ + minWidth: '24px', + color: theme.colors.text1, + fontWeight: 700, +}) + +export const finalActions = style({ + display: 'flex', + flexDirection: 'column', + gap: '12px', + alignItems: 'stretch', + '@media': { + 'screen and (min-width: 768px)': { + flexDirection: 'row', + alignItems: 'center', + }, + 'screen and (min-width: 980px)': { + justifyContent: 'flex-end', + }, + }, +}) + +export const aboutCtaButton = style({ + borderRadius: '999px', + transition: 'background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease', + selectors: { + '&:not([disabled]):hover': { + borderColor: theme.colors.focusRing, + backgroundColor: `color-mix(in srgb, ${theme.colors.focusRing} 14%, ${theme.colors.background1})`, + }, + '&:focus-visible': focusRing, + }, +}) + +globalStyle(`${aboutCtaButton}.zord-button-primary:not([disabled]):hover`, { + borderColor: theme.colors.focusRing, + backgroundColor: `color-mix(in srgb, ${theme.colors.focusRing} 82%, ${theme.colors.text1})`, + color: theme.colors.background1, +}) + +globalStyle(`${aboutCtaButton}.zord-button-outline:not([disabled]):hover`, { + borderColor: theme.colors.focusRing, + backgroundColor: `color-mix(in srgb, ${theme.colors.focusRing} 14%, ${theme.colors.background1})`, + color: theme.colors.text1, +}) + +globalStyle(`${aboutCtaButton}.zord-button-ghost:not([disabled]):hover`, { + borderColor: theme.colors.focusRing, + backgroundColor: `color-mix(in srgb, ${theme.colors.focusRing} 16%, ${theme.colors.background1})`, + color: theme.colors.text1, +}) + +export const subLink = style({ + color: theme.colors.text3, + fontSize: '14px', + fontWeight: 700, + textDecoration: 'none', + width: 'fit-content', + selectors: { + '&:hover': { + color: theme.colors.text1, + }, + '&:focus-visible': focusRing, + }, +}) + +export const heroSubLink = style({ + color: theme.colors.text3, + fontSize: '14px', + fontWeight: 700, + textDecoration: 'none', + selectors: { + '&:hover': { + color: theme.colors.text1, + }, + '&:focus-visible': focusRing, + }, +}) + +export const helperText = style({ + fontSize: '14px', + lineHeight: 1.55, + color: theme.colors.text3, +}) + +export const governanceCopy = style({ + maxWidth: '720px', + fontSize: '17px', + lineHeight: 1.6, + color: theme.colors.text3, +}) + +export const governanceLink = style({ + color: theme.colors.focusRing, + textDecoration: 'underline', +}) + +globalStyle( + `html[data-theme-mode="dark"] ${page} :is(${sectionTitle}, ${heroTitle}, ${montageValue}, ${heroFooterValue}, ${statValue}, ${tabButton}, ${activeTabButton}, ${mobileStatsHeading}, ${daoName}, ${daoSignalValue}, ${coiningTitle}, ${amountPill}, ${droposalTitle}, ${stepTitle}, ${featureItem})`, + { + color: theme.colors.text1, + } +) + +globalStyle( + `html[data-theme-mode="dark"] ${page} :is(${sectionCopy}, ${introCopyNoWrap}, ${heroText}, ${heroHighlight}, ${montageBody}, ${statLabel}, ${statDetail}, ${sectionInlineCopy}, ${daoDescription}, ${daoSignalLabel}, ${mutedText}, ${droposalSummary}, ${stepBody}, ${subLink}, ${heroSubLink}, ${helperText}, ${governanceCopy})`, + { + color: theme.colors.text3, + } +) + +globalStyle(`html[data-theme-mode="dark"] ${page} :is(${heroHighlightDot})`, { + background: theme.colors.text1, +}) + +globalStyle( + `html[data-theme-mode="dark"] ${page} ${aboutCtaButton}:not([disabled]):hover`, + { + borderColor: theme.colors.focusRing, + backgroundColor: `color-mix(in srgb, ${theme.colors.focusRing} 30%, ${theme.colors.background1})`, + color: theme.colors.text1, + } +) + +globalStyle( + `html[data-theme-mode="dark"] ${page} ${aboutCtaButton}.zord-button-primary:not([disabled]):hover`, + { + borderColor: theme.colors.focusRing, + backgroundColor: `color-mix(in srgb, ${theme.colors.focusRing} 70%, ${theme.colors.text1})`, + color: theme.colors.background1, + } +) + +globalStyle(`html[data-theme-mode="dark"] ${page} :is(${daoMiniCard}, ${stepCard})`, { + background: theme.colors.background2, +}) + +globalStyle(`html[data-theme-mode="dark"] ${page} ${heroPanel}`, { + background: theme.colors.background1, +}) + +globalStyle(`html[data-theme-mode="dark"] ${page} :is(${coiningPreviewTitle})`, { + color: theme.colors.background1, +}) + +globalStyle(`html[data-theme-mode="dark"] ${logoMarqueeTrack}::before`, { + background: + 'linear-gradient(90deg, rgba(31, 32, 36, 1) 0%, rgba(31, 32, 36, 0) 10%, rgba(31, 32, 36, 0) 90%, rgba(31, 32, 36, 1) 100%)', +}) diff --git a/apps/web/src/modules/about/AboutPage.tsx b/apps/web/src/modules/about/AboutPage.tsx new file mode 100644 index 000000000..a22209557 --- /dev/null +++ b/apps/web/src/modules/about/AboutPage.tsx @@ -0,0 +1,149 @@ +import { useChainStore } from '@buildeross/stores' +import { Box } from '@buildeross/zord' +import React from 'react' + +import { + centeredImage, + centeredImageWrap, + container, + governanceCopy, + governanceLink, + page, + section, +} from './AboutPage.css' +import { AboutFinalCta } from './components/AboutFinalCta' +import { AboutHero } from './components/AboutHero' +import { CoiningHighlightsSection } from './components/CoiningHighlightsSection' +import { DroposalHighlightsSection } from './components/DroposalHighlightsSection' +import { FeaturedDaoSection } from './components/FeaturedDaoSection' +import { ProposalHighlightsSection } from './components/ProposalHighlightsSection' +import { SectionIntro } from './components/SectionIntro' +import { WhatIsBuilderSection } from './components/WhatIsBuilderSection' +import { WhatYouCanBuildSection } from './components/WhatYouCanBuildSection' +import { WhyBuilderSection } from './components/WhyBuilderSection' +import { heroHighlights } from './data' +import { useAboutDaoTabs } from './useAboutDaoTabs' +import { useAboutShowcase } from './useAboutShowcase' +import { useAboutSnapshotStats } from './useAboutSnapshotStats' + +export const AboutPageView: React.FC = () => { + const chain = useChainStore((x) => x.chain) + const { data: tabsData, isLoading } = useAboutDaoTabs(chain.slug) + const { data: snapshotData } = useAboutSnapshotStats() + const { data: showcaseData } = useAboutShowcase() + const heroLogos = React.useMemo(() => { + if (!tabsData) return [] + + const seen = new Set() + + return Object.values(tabsData) + .flat() + .filter((dao) => dao.recentAuctionImage) + .filter((dao) => { + if (!dao.recentAuctionImage || seen.has(dao.recentAuctionImage)) return false + seen.add(dao.recentAuctionImage) + return true + }) + .slice(0, 12) + .map((dao) => ({ + id: dao.id, + name: dao.name, + imageUrl: dao.recentAuctionImage as string, + })) + }, [tabsData]) + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This public good tooling and the Nouns Builder Protocol are maintained and + governed by{' '} + + Builder DAO + + . Learn more about the DAO's vision and mission{' '} + + here + + . + + + + + + + + + + + + + + + + ) +} diff --git a/apps/web/src/modules/about/components/AboutFinalCta.tsx b/apps/web/src/modules/about/components/AboutFinalCta.tsx new file mode 100644 index 000000000..f1d8e28f1 --- /dev/null +++ b/apps/web/src/modules/about/components/AboutFinalCta.tsx @@ -0,0 +1,80 @@ +import { Box, Button, Text } from '@buildeross/zord' +import Link from 'next/link' +import React from 'react' + +import { + aboutCtaButton, + finalActions, + finalChecklist, + finalChecklistItem, + finalChecklistMarker, + finalCta, + finalCtaContent, + finalCtaGlow, + finalCtaTitle, +} from '../AboutPage.css' + +export const AboutFinalCta: React.FC = () => { + return ( + + + + + Start your DAO today + + + + Upload your art + + + + Set your parameters + + + + Launch your first auction + + + + + + + + + + + + ) +} diff --git a/apps/web/src/modules/about/components/AboutHero.tsx b/apps/web/src/modules/about/components/AboutHero.tsx new file mode 100644 index 000000000..df786a2b2 --- /dev/null +++ b/apps/web/src/modules/about/components/AboutHero.tsx @@ -0,0 +1,140 @@ +import { Box, Button, Text } from '@buildeross/zord' +import Link from 'next/link' +import React from 'react' + +import { + aboutCtaButton, + aboutDarkOnly, + aboutLightOnly, + hero, + heroActions, + heroCopy, + heroHighlight, + heroHighlightDot, + heroHighlightList, + heroPanel, + heroPanelGlow, + heroText, + heroTitle, + heroVennImage, + heroVennWrap, + logoMarquee, + logoMarqueeImage, + logoMarqueeInner, + logoMarqueeItem, + logoMarqueeTrack, + montageBody, + montageCard, + montageFooter, + montageGrid, + montagePrimary, + montageSide, + montageValue, +} from '../AboutPage.css' + +type AboutHeroProps = { + heroHighlights: string[] + heroLogos: Array<{ + id: string + name: string + imageUrl: string + }> +} + +export const AboutHero: React.FC = ({ heroHighlights, heroLogos }) => { + const marqueeLogos = heroLogos.length > 0 ? [...heroLogos, ...heroLogos] : [] + + return ( + + + + The easiest way to build communities onchain + + + Nouns Builder lets anyone launch a DAO where membership, funding, content and + governance all happen in one fully transparent, onchain system. + + + + {heroHighlights.map((item) => ( + + + {item} + + ))} + + + + + + + + + + + + + + {`Launch.\nFund.\nCollaborate.`} + + The onchain operating system for decentralized communities. + + + + + + + + + + + + + + + {marqueeLogos.map((logo, index) => ( + + + + ))} + + + + + + + + ) +} diff --git a/apps/web/src/modules/about/components/CoiningCard.tsx b/apps/web/src/modules/about/components/CoiningCard.tsx new file mode 100644 index 000000000..f468c13aa --- /dev/null +++ b/apps/web/src/modules/about/components/CoiningCard.tsx @@ -0,0 +1,60 @@ +import { Box, Text } from '@buildeross/zord' +import Link from 'next/link' +import React from 'react' + +import { + coiningCard, + coiningMeta, + coiningNetworkBadge, + coiningPreview, + coiningPreviewMark, + coiningPreviewTitle, + coiningPreviewTop, + coiningTitle, + daoChainBadgeImage, + mutedText, +} from '../AboutPage.css' +import { CoiningHighlight } from '../types' +import { getChainLogoSrc } from '../utils' + +type CoiningCardProps = { + item: CoiningHighlight +} + +export const CoiningCard: React.FC = ({ item }) => { + const chainLogoSrc = getChainLogoSrc(item.chainLabel) + + return ( + + + + {item.amount} + {chainLogoSrc ? ( + + + + ) : ( + {item.chainLabel} + )} + + {item.previewLabel} + + + + {item.title} + + By {item.creator} for {item.dao} + + + + ) +} diff --git a/apps/web/src/modules/about/components/CoiningHighlightsSection.tsx b/apps/web/src/modules/about/components/CoiningHighlightsSection.tsx new file mode 100644 index 000000000..c3ecec7c3 --- /dev/null +++ b/apps/web/src/modules/about/components/CoiningHighlightsSection.tsx @@ -0,0 +1,34 @@ +import { Box } from '@buildeross/zord' +import React from 'react' + +import { scrollRow } from '../AboutPage.css' +import { coiningHighlights } from '../data' +import { CoiningHighlight } from '../types' +import { CoiningCard } from './CoiningCard' +import { SectionIntro } from './SectionIntro' + +type CoiningHighlightsSectionProps = { + items?: CoiningHighlight[] +} + +export const CoiningHighlightsSection: React.FC = ({ + items, +}) => { + const highlights = items?.length ? items : coiningHighlights + + return ( + + + + + {highlights.map((item) => ( + + ))} + + + ) +} diff --git a/apps/web/src/modules/about/components/DaoCard.tsx b/apps/web/src/modules/about/components/DaoCard.tsx new file mode 100644 index 000000000..217b0ab3d --- /dev/null +++ b/apps/web/src/modules/about/components/DaoCard.tsx @@ -0,0 +1,80 @@ +import { FallbackImage } from '@buildeross/ui' +import { Box, Flex, Text } from '@buildeross/zord' +import Link from 'next/link' +import React from 'react' + +import { + daoAvatar, + daoAvatarImage, + daoAvatarSurfaceA, + daoAvatarSurfaceB, + daoAvatarSurfaceC, + daoAvatarSurfaceD, + daoCard, + daoChainBadge, + daoChainBadgeImage, + daoDescription, + daoIdentity, + daoName, + daoSignal, + daoSignalLabel, + daoSignalValue, + daoTop, +} from '../AboutPage.css' +import { AboutDao } from '../types' +import { getChainLogoSrc, toPlainText } from '../utils' + +type DaoCardProps = { + dao: AboutDao +} + +export const DaoCard: React.FC = ({ dao }) => { + const plainDescription = toPlainText(dao.description) + const chainLogoSrc = getChainLogoSrc(dao.chainName) || dao.chainIcon + + const avatarSurfaceClass = [ + daoAvatarSurfaceA, + daoAvatarSurfaceB, + daoAvatarSurfaceC, + daoAvatarSurfaceD, + ][Number(dao.id.replace(/\D/g, '')) % 4 || 0] + + return ( + + + + + {dao.imageUrl ? ( + + ) : ( + dao.initials + )} + + + {dao.name} + {plainDescription} + + + {chainLogoSrc ? ( + + + + ) : null} + + + + {dao.signalLabel} + {dao.signalValue} + + + ) +} diff --git a/apps/web/src/modules/about/components/DroposalHighlightsSection.tsx b/apps/web/src/modules/about/components/DroposalHighlightsSection.tsx new file mode 100644 index 000000000..e9bbdd252 --- /dev/null +++ b/apps/web/src/modules/about/components/DroposalHighlightsSection.tsx @@ -0,0 +1,139 @@ +import { getProposalStateColorStyle } from '@buildeross/proposal-ui' +import { ProposalState } from '@buildeross/sdk/contract' +import { Box, Text } from '@buildeross/zord' +import Link from 'next/link' +import React from 'react' + +import { + badge, + daoBadge, + daoChainBadge, + daoChainBadgeImage, + droposalAside, + droposalCard, + droposalList, + droposalMeta, + droposalSummary, + droposalTitle, + mutedText, + statusBadge, +} from '../AboutPage.css' +import { dropHighlights } from '../data' +import { DroposalHighlight } from '../types' +import { getChainLogoSrc } from '../utils' +import { SectionIntro } from './SectionIntro' + +const statusStyleByType: Record< + DroposalHighlight['status'], + { borderColor: string; color: string; backgroundColor: string } +> = { + Active: { + ...getProposalStateColorStyle(ProposalState.Active), + backgroundColor: `color-mix(in srgb, ${getProposalStateColorStyle(ProposalState.Active).borderColor} 12%, transparent)`, + }, + Succeeded: { + ...getProposalStateColorStyle(ProposalState.Succeeded), + backgroundColor: `color-mix(in srgb, ${getProposalStateColorStyle(ProposalState.Succeeded).borderColor} 12%, transparent)`, + }, + Queued: { + ...getProposalStateColorStyle(ProposalState.Queued), + backgroundColor: `color-mix(in srgb, ${getProposalStateColorStyle(ProposalState.Queued).borderColor} 12%, transparent)`, + }, + Defeated: { + ...getProposalStateColorStyle(ProposalState.Defeated), + backgroundColor: `color-mix(in srgb, ${getProposalStateColorStyle(ProposalState.Defeated).borderColor} 12%, transparent)`, + }, + Executed: { + ...getProposalStateColorStyle(ProposalState.Executed), + backgroundColor: `color-mix(in srgb, ${getProposalStateColorStyle(ProposalState.Executed).borderColor} 12%, transparent)`, + }, + Trending: { + ...getProposalStateColorStyle(ProposalState.Succeeded), + backgroundColor: `color-mix(in srgb, ${getProposalStateColorStyle(ProposalState.Succeeded).borderColor} 12%, transparent)`, + }, + Live: { + ...getProposalStateColorStyle(ProposalState.Succeeded), + backgroundColor: `color-mix(in srgb, ${getProposalStateColorStyle(ProposalState.Succeeded).borderColor} 12%, transparent)`, + }, + Recent: { + ...getProposalStateColorStyle(ProposalState.Active), + backgroundColor: `color-mix(in srgb, ${getProposalStateColorStyle(ProposalState.Active).borderColor} 12%, transparent)`, + }, +} + +type DroposalHighlightsSectionProps = { + items?: DroposalHighlight[] + eyebrowText?: string + title?: string + copy?: string + linkLabel?: string + showStatusBadge?: boolean +} + +export const DroposalHighlightsSection: React.FC = ({ + items, + eyebrowText = 'Drops', + title = 'Drops turn releases into onchain distribution', + copy = 'Launch collectible drops that turn media, editions, and releases into distribution, ownership, and treasury growth for decentralized communities.', + linkLabel = 'View drop', + showStatusBadge = true, +}) => { + const highlights = items?.length ? items : dropHighlights + + return ( + + + + + {highlights.map((proposal) => { + const chainLogoSrc = getChainLogoSrc(proposal.category) + + return ( + + + + {proposal.dao} + {showStatusBadge ? ( + + {proposal.status} + + ) : null} + {chainLogoSrc ? ( + + + + ) : ( + {proposal.category} + )} + + + {proposal.title} + + + {proposal.summary} + + + + + {proposal.amount} + + + ) + })} + + + ) +} diff --git a/apps/web/src/modules/about/components/EcosystemActivitySection.tsx b/apps/web/src/modules/about/components/EcosystemActivitySection.tsx new file mode 100644 index 000000000..e1d9a7ac1 --- /dev/null +++ b/apps/web/src/modules/about/components/EcosystemActivitySection.tsx @@ -0,0 +1,40 @@ +import { Box, Text } from '@buildeross/zord' +import React from 'react' + +import { + activityCard, + activityList, + activityMeta, + activityTitle, + mutedText, +} from '../AboutPage.css' +import { ecosystemActivity } from '../data' +import { SectionIntro } from './SectionIntro' + +export const EcosystemActivitySection: React.FC = () => { + return ( + + + + + {ecosystemActivity.map((item) => ( + + {item.meta} + + + {item.title} + + + {item.detail} + + + + ))} + + + ) +} diff --git a/apps/web/src/modules/about/components/EcosystemStatGrid.tsx b/apps/web/src/modules/about/components/EcosystemStatGrid.tsx new file mode 100644 index 000000000..fe2acab7d --- /dev/null +++ b/apps/web/src/modules/about/components/EcosystemStatGrid.tsx @@ -0,0 +1,56 @@ +import { Box, Text } from '@buildeross/zord' +import React from 'react' + +import { + statAccent, + statAccentAuction, + statAccentDao, + statAccentMembers, + statAccentProposal, + statAccentTokens, + statAccentTreasury, + statCard, + statDetail, + statGrid, + statLabel, + statValue, +} from '../AboutPage.css' +import { AboutStat } from '../types' + +type EcosystemStatGridProps = { + stats: AboutStat[] +} + +export const EcosystemStatGrid: React.FC = ({ stats }) => { + const getAccentClass = (id: string) => { + switch (id) { + case 'daos': + return statAccentDao + case 'treasury': + return statAccentTreasury + case 'auctions': + return statAccentAuction + case 'proposals': + return statAccentProposal + case 'members': + return statAccentMembers + case 'tokens': + return statAccentTokens + default: + return statAccentDao + } + } + + return ( + + {stats.map((stat) => ( + + {stat.icon} + {stat.label} + {stat.value} + {stat.detail} + + ))} + + ) +} diff --git a/apps/web/src/modules/about/components/FeaturedDaoSection.tsx b/apps/web/src/modules/about/components/FeaturedDaoSection.tsx new file mode 100644 index 000000000..c637be2c7 --- /dev/null +++ b/apps/web/src/modules/about/components/FeaturedDaoSection.tsx @@ -0,0 +1,88 @@ +import { Box, Text } from '@buildeross/zord' +import React from 'react' + +import { + activeTabButton, + daoGrid, + mobileStatsHeading, + sectionInlineCopy, + sectionInlineRow, + sectionTitle, + sectionTitleOnly, + statsBlock, + tabButton, + tabs, +} from '../AboutPage.css' +import { AboutDaoTabKey, AboutDaoTabsResponse, AboutStat } from '../types' +import { DaoCard } from './DaoCard' +import { EcosystemStatGrid } from './EcosystemStatGrid' + +type FeaturedDaoSectionProps = { + tabsData?: AboutDaoTabsResponse + stats?: AboutStat[] + isLoading: boolean +} + +const tabOrder: { id: AboutDaoTabKey; label: string }[] = [ + { id: 'featured', label: 'Featured' }, + { id: 'trending', label: 'Trending' }, + { id: 'new', label: 'New' }, + { id: 'active', label: 'Active' }, +] + +export const FeaturedDaoSection: React.FC = ({ + tabsData, + stats, + isLoading: _isLoading, +}) => { + const [tab, setTab] = React.useState('featured') + + const displayedDaos = React.useMemo(() => { + return tabsData?.[tab] ?? [] + }, [tab, tabsData]) + + return ( + + + A thriving ecosystem + + + + + Nouns Builder is already powering a growing network of decentralized + communities. + + + + {tabOrder.map((item) => { + const isActive = item.id === tab + return ( + + ) + })} + + + + + {displayedDaos.map((dao) => ( + + ))} + + + + Protocol Stats + + + + + + + ) +} diff --git a/apps/web/src/modules/about/components/HowItWorksSection.tsx b/apps/web/src/modules/about/components/HowItWorksSection.tsx new file mode 100644 index 000000000..4d3d569ae --- /dev/null +++ b/apps/web/src/modules/about/components/HowItWorksSection.tsx @@ -0,0 +1,35 @@ +import { Box, Text } from '@buildeross/zord' +import React from 'react' + +import { + stepBody, + stepCard, + stepHeader, + stepMarker, + stepsGrid, + stepTitle, +} from '../AboutPage.css' +import { builderSteps } from '../data' +import { SectionIntro } from './SectionIntro' + +export const HowItWorksSection: React.FC = () => { + return ( + + + + + {builderSteps.map((step) => ( + + + {step.marker} + + {step.title} + + + {step.body} + + ))} + + + ) +} diff --git a/apps/web/src/modules/about/components/ProposalHighlightsSection.tsx b/apps/web/src/modules/about/components/ProposalHighlightsSection.tsx new file mode 100644 index 000000000..b494f4a42 --- /dev/null +++ b/apps/web/src/modules/about/components/ProposalHighlightsSection.tsx @@ -0,0 +1,25 @@ +import React from 'react' + +import { proposalHighlights } from '../data' +import { DroposalHighlight } from '../types' +import { DroposalHighlightsSection } from './DroposalHighlightsSection' + +type ProposalHighlightsSectionProps = { + items?: DroposalHighlight[] +} + +export const ProposalHighlightsSection: React.FC = ({ + items, +}) => { + const highlights = items?.length ? items : proposalHighlights + + return ( + + ) +} diff --git a/apps/web/src/modules/about/components/SectionIntro.tsx b/apps/web/src/modules/about/components/SectionIntro.tsx new file mode 100644 index 000000000..75aa29d0c --- /dev/null +++ b/apps/web/src/modules/about/components/SectionIntro.tsx @@ -0,0 +1,28 @@ +import { Box, Text } from '@buildeross/zord' +import React from 'react' + +import { eyebrow, sectionCopy, sectionHeader, sectionTitle } from '../AboutPage.css' + +type SectionIntroProps = { + eyebrowText: string + title: string + copy?: string + copyClassName?: string +} + +export const SectionIntro: React.FC = ({ + eyebrowText, + title, + copy, + copyClassName, +}) => { + return ( + + {eyebrowText} + + {title} + + {copy ? {copy} : null} + + ) +} diff --git a/apps/web/src/modules/about/components/WhatIsBuilderSection.tsx b/apps/web/src/modules/about/components/WhatIsBuilderSection.tsx new file mode 100644 index 000000000..298b88146 --- /dev/null +++ b/apps/web/src/modules/about/components/WhatIsBuilderSection.tsx @@ -0,0 +1,43 @@ +import { Box, Text } from '@buildeross/zord' +import React from 'react' + +import { + mutedText, + stepHeader, + stepMarker, + valueCard, + valueGrid, + valueTitle, +} from '../AboutPage.css' +import { builderFeatures } from '../data' +import { SectionIntro } from './SectionIntro' + +export const WhatIsBuilderSection: React.FC = () => { + return ( + + + + + {builderFeatures.map((item) => ( + + + + {item.marker} + + + {item.title} + + + + {item.body} + + + ))} + + + ) +} diff --git a/apps/web/src/modules/about/components/WhatYouCanBuildSection.tsx b/apps/web/src/modules/about/components/WhatYouCanBuildSection.tsx new file mode 100644 index 000000000..0051a388f --- /dev/null +++ b/apps/web/src/modules/about/components/WhatYouCanBuildSection.tsx @@ -0,0 +1,62 @@ +import { Box, Text } from '@buildeross/zord' +import React from 'react' + +import { + aboutDarkOnly, + aboutLightOnly, + compactValueCard, + compactValueEmoji, + compactValueImage, + compactValueTitle, + useCaseGrid, +} from '../AboutPage.css' +import { builderUseCases } from '../data' +import { SectionIntro } from './SectionIntro' + +export const WhatYouCanBuildSection: React.FC = () => { + return ( + + + + + {builderUseCases.map((item) => ( + + {item.id === 'media-collectives' ? ( + <> + + + + ) : item.imageSrc ? ( + + ) : item.emoji ? ( + {item.emoji} + ) : null} + + {item.title} + + + ))} + + + ) +} diff --git a/apps/web/src/modules/about/components/WhyBuilderSection.tsx b/apps/web/src/modules/about/components/WhyBuilderSection.tsx new file mode 100644 index 000000000..1be68577a --- /dev/null +++ b/apps/web/src/modules/about/components/WhyBuilderSection.tsx @@ -0,0 +1,118 @@ +import { Box, Text } from '@buildeross/zord' +import React from 'react' + +import { + featureIconBadge, + featureItem, + featureLabel, + featureStrip, + section, +} from '../AboutPage.css' +import { builderValueProps } from '../data' +import type { BuilderValueProp } from '../types' +import { SectionIntro } from './SectionIntro' + +type FeatureIcon = 'rocket' | 'treasury' | 'governance' | 'creative' + +const featureIconsById: Record = { + 'launch-fast': 'rocket', + 'treasury-auctions': 'treasury', + 'governance-day-one': 'governance', + 'creative-output': 'creative', +} + +const FeatureIconSvg = ({ icon }: { icon: FeatureIcon }) => { + switch (icon) { + case 'rocket': + return ( + + ) + case 'treasury': + return ( + + ) + case 'governance': + return ( + + ) + case 'creative': + return ( + + ) + } +} + +const FeatureItem = ({ item }: { item: BuilderValueProp }) => ( + + + + + + {item.title} + + +) + +export const WhyBuilderSection: React.FC = () => { + return ( + + + + + {builderValueProps.map((item) => ( + + ))} + + + ) +} diff --git a/apps/web/src/modules/about/data.ts b/apps/web/src/modules/about/data.ts new file mode 100644 index 000000000..1ea94ba78 --- /dev/null +++ b/apps/web/src/modules/about/data.ts @@ -0,0 +1,284 @@ +import { + ActivityItem, + BuilderFeature, + BuilderStep, + BuilderValueProp, + CoiningHighlight, + DroposalHighlight, +} from './types' + +// TODO: Replace seeded showcase data with an aggregated ecosystem endpoint once +// cross-DAO ranking and coining feeds are available for the public about page. +export const coiningHighlights: CoiningHighlight[] = [ + { + id: 'studio-coin', + title: 'Studio Coin', + creator: '0x7d4f...19ae', + dao: 'Studio Nouns', + chainLabel: 'Base', + amount: '$1.8M market cap', + href: '/explore?search=studio%20nouns', + eyebrow: 'DAO paired coin', + surface: 'linear-gradient(135deg, #F9E7FF 0%, #D9EEFF 100%)', + previewLabel: 'Creator pair', + }, + { + id: 'camp-coin', + title: 'Camp Coin', + creator: '0x4ac2...d6bf', + dao: 'Camp Nouns', + chainLabel: 'Base', + amount: '$940K market cap', + href: '/explore?search=camp%20nouns', + eyebrow: 'DAO paired coin', + surface: 'linear-gradient(135deg, #FFF0CC 0%, #FFD9BF 100%)', + previewLabel: 'Content pair', + }, + { + id: 'builder-coin', + title: 'Builder Coin', + creator: '0x8cf4...b2da', + dao: 'BuilderDAO', + chainLabel: 'Base', + amount: '$1.2M market cap', + href: '/explore?search=builderdao', + eyebrow: 'DAO paired coin', + surface: 'linear-gradient(135deg, #DFFFF0 0%, #D5F8FF 100%)', + previewLabel: 'Onchain market', + }, + { + id: 'fest-coin', + title: 'Fest Coin', + creator: '0x1ce9...42f0', + dao: 'Nouns Fest', + chainLabel: 'Ethereum', + amount: '$680K market cap', + href: '/explore?search=nouns%20fest', + eyebrow: 'DAO paired coin', + surface: 'linear-gradient(135deg, #E2EBFF 0%, #EDE8FF 100%)', + previewLabel: 'Content pair', + }, +] + +export const dropHighlights: DroposalHighlight[] = [ + { + id: 'open-edition-jam-drop', + title: 'Open Edition Jam', + dao: 'Studio Nouns', + amount: 'ETH 18.6 sold', + status: 'Live', + summary: + 'A live edition drop distributing creative work onchain with sales flowing to the DAO treasury.', + href: '/explore?search=studio%20nouns', + category: 'Base', + }, + { + id: 'summer-camp-poster-drop', + title: 'Summer Camp Poster Pack', + dao: 'Camp Nouns', + amount: 'ETH 9.4 sold', + status: 'Recent', + summary: + 'A collectible poster release used to distribute media and fund creative output through Builder.', + href: '/explore?search=camp%20nouns', + category: 'Base', + }, + { + id: 'governance-zine-drop', + title: 'Governance Zine 001', + dao: 'BuilderDAO', + amount: 'ETH 12.1 sold', + status: 'Recent', + summary: + 'An editorial drop that packages governance content into onchain ownership and treasury formation.', + href: '/explore?search=builderdao', + category: 'Base', + }, + { + id: 'signal-radio-drop', + title: 'Signal Radio Session', + dao: 'Nouns Fest', + amount: 'ETH 6.8 sold', + status: 'Live', + summary: + 'A media drop extending event programming into onchain distribution, ownership, and funding.', + href: '/explore?search=nouns%20fest', + category: 'Ethereum', + }, +] + +export const proposalHighlights: DroposalHighlight[] = [ + { + id: 'operator-tooling', + title: 'Operator tooling sprint for treasury reporting', + dao: 'BuilderDAO', + amount: '42 voters', + status: 'Queued', + summary: + 'Shared treasury analytics and operator tooling improve governance workflows across Builder DAOs.', + href: '/explore?search=builderdao', + category: 'Base', + }, + { + id: 'festival-grants', + title: 'Mini grants for local Nouns Fest activations', + dao: 'Nouns Fest', + amount: '18 voters', + status: 'Succeeded', + summary: + 'Regional teams get budget for activations, documentation, and post-event publishing.', + href: '/explore?search=nouns%20fest', + category: 'Ethereum', + }, + { + id: 'creator-retainer', + title: 'Quarterly creative retainer for media releases', + dao: 'Studio Nouns', + amount: '24 voters', + status: 'Active', + summary: + 'Sustains ongoing visual output with transparent milestones and release cadence.', + href: '/explore?search=studio%20nouns', + category: 'Base', + }, + { + id: 'builder-residency', + title: 'Residency round for new onchain builders', + dao: 'Far House', + amount: '30 voters', + status: 'Defeated', + summary: + 'Funds a cohort of builders experimenting with coining, drops, and governance flows.', + href: '/explore?search=far%20house', + category: 'Optimism', + }, + { + id: 'archive-pipeline', + title: 'Archive and indexing pipeline for DAO memory', + dao: 'Prop House', + amount: '12 voters', + status: 'Executed', + summary: + 'Creates searchable records for proposal outcomes, experiments, and funded work.', + href: '/explore?search=prop%20house', + category: 'Ethereum', + }, +] + +export const builderSteps: BuilderStep[] = [ + { + id: 'launch', + title: 'Launch your DAO', + body: 'Spin up a DAO with auctions, governance, and treasury built in.', + marker: '1', + }, + { + id: 'grow', + title: 'Grow your treasury', + body: 'Use auctions and coining to generate ongoing funding.', + marker: '2', + }, + { + id: 'fund', + title: 'Fund ideas', + body: 'Use proposals to allocate capital to builders, events, and experiments.', + marker: '3', + }, +] + +export const builderFeatures: BuilderFeature[] = [ + { + id: 'auctions', + marker: '1', + title: 'Auctions bring in new members and fund the treasury', + body: 'Ownership and capital contribution in one simple mechanic.', + }, + { + id: 'coining', + marker: '2', + title: 'Members participate in collaborative content creation', + body: 'Create content as coins and droposals to turn media into funding utilizing built in distribution methods.', + }, + { + id: 'proposals', + marker: '3', + title: 'The treasury funds ideas', + body: 'Members vote on proposals to fund work and allocate capital, all transparently onchain.', + }, +] + +export const builderValueProps: BuilderValueProp[] = [ + { + id: 'launch-fast', + title: 'Launch a DAO in minutes', + }, + { + id: 'treasury-auctions', + title: 'Built-in treasury formation through auctions', + }, + { + id: 'governance-day-one', + title: 'Governance included from day one', + }, + { + id: 'creative-output', + title: 'Native support for creative output', + }, +] + +export const ecosystemActivity: ActivityItem[] = [ + { + id: 'launches', + title: 'New launches keep expanding the graph', + detail: + 'Fresh DAOs continue showing up with tighter scopes: media, local communities, product teams, and events.', + meta: 'Recent launches', + tone: 'launch', + }, + { + id: 'coining', + title: 'Coining is broadening what a DAO can publish', + detail: + 'Posts are becoming funding surfaces for essays, visuals, audio, and experiments instead of just announcements.', + meta: 'Creator momentum', + tone: 'coin', + }, + { + id: 'droposals', + title: 'Droposals are routing treasury into clearer outcomes', + detail: + 'Teams are packaging work with tighter scope, deliverables, and faster governance cycles.', + meta: 'Governance patterns', + tone: 'governance', + }, + { + id: 'protocol', + title: 'Builder keeps shipping new primitives', + detail: + 'The protocol is evolving around creation flows, treasury tooling, content, and governance ergonomics.', + meta: 'Protocol motion', + tone: 'protocol', + }, +] + +export const heroHighlights = [ + 'Each new member joins through an auction.', + 'Every auction funds a shared treasury.', + 'Token holders propose and vote on how that capital is used.', +] + +export const builderUseCases: BuilderValueProp[] = [ + { + id: 'media-collectives', + title: 'Community owned brands', + imageSrc: '/noggles-square.svg', + }, + { id: 'creative-communities', title: 'Creative and media collectives', emoji: '🎨' }, + { id: 'events-local-groups', title: 'Local and event-based groups', emoji: '🎪' }, + { id: 'protocol-teams', title: 'Protocol teams', emoji: '⚙️' }, + { + id: 'experimental-governance', + title: 'Subculture communities', + emoji: '🛹', + }, +] diff --git a/apps/web/src/modules/about/index.ts b/apps/web/src/modules/about/index.ts new file mode 100644 index 000000000..3564b71bf --- /dev/null +++ b/apps/web/src/modules/about/index.ts @@ -0,0 +1 @@ +export * from './AboutPage' diff --git a/apps/web/src/modules/about/types.ts b/apps/web/src/modules/about/types.ts new file mode 100644 index 000000000..6798eb298 --- /dev/null +++ b/apps/web/src/modules/about/types.ts @@ -0,0 +1,108 @@ +export type AboutStat = { + id: string + label: string + value: string + detail: string + icon: string +} + +export type DaoCategory = 'featured' | 'trending' | 'new' | 'active' + +export type AboutDao = { + id: string + name: string + description: string + signalLabel: string + signalValue: string + href: string + badge: string + initials: string + imageUrl?: string | null + recentAuctionImage?: string | null + chainIcon?: string | null + chainName?: string | null + category: DaoCategory +} + +export type CoiningHighlight = { + id: string + title: string + creator: string + dao: string + chainLabel: string + amount: string + href: string + eyebrow: string + surface: string + previewLabel: string +} + +export type DroposalStatus = + | 'Active' + | 'Succeeded' + | 'Queued' + | 'Defeated' + | 'Executed' + | 'Trending' + | 'Live' + | 'Recent' + +export type DroposalHighlight = { + id: string + title: string + dao: string + amount: string + status: DroposalStatus + summary: string + href: string + category: string +} + +export type BuilderStep = { + id: string + title: string + body: string + marker: string +} + +export type BuilderValueProp = { + id: string + title: string + body?: string + emoji?: string + imageSrc?: string +} + +export type ActivityItem = { + id: string + title: string + detail: string + meta: string + tone: 'launch' | 'coin' | 'governance' | 'protocol' +} + +export type AboutDaoTabKey = 'featured' | 'trending' | 'new' | 'active' + +export type AboutDaoTabsResponse = { + featured: AboutDao[] + trending: AboutDao[] + new: AboutDao[] + active: AboutDao[] +} + +export type AboutSnapshotResponse = { + stats: AboutStat[] +} + +export type AboutShowcaseResponse = { + coining: CoiningHighlight[] + drops: DroposalHighlight[] + proposals: DroposalHighlight[] +} + +export type BuilderFeature = { + id: string + marker: string + title: string + body: string +} diff --git a/apps/web/src/modules/about/useAboutDaoTabs.ts b/apps/web/src/modules/about/useAboutDaoTabs.ts new file mode 100644 index 000000000..b74b20e26 --- /dev/null +++ b/apps/web/src/modules/about/useAboutDaoTabs.ts @@ -0,0 +1,60 @@ +import { SWR_KEYS } from '@buildeross/constants/swrKeys' +import useSWR from 'swr' + +import { AboutDaoTabsResponse } from './types' + +type HttpError = Error & { status?: number; body?: unknown } + +const fetcher = async ( + [, network]: readonly [string, string], + { signal }: { signal?: AbortSignal } = {} +): Promise => { + const response = await fetch( + `/api/about/dao-tabs?network=${encodeURIComponent(network)}`, + { + signal, + headers: { Accept: 'application/json' }, + } + ) + + const text = await response.text() + let body: unknown = {} + + try { + body = text ? JSON.parse(text) : {} + } catch { + body = text + } + + if (!response.ok) { + const err: HttpError = new Error( + typeof body === 'object' && body && 'error' in body + ? String((body as { error?: string }).error || response.statusText) + : response.statusText + ) + err.status = response.status + err.body = body + throw err + } + + return body as AboutDaoTabsResponse +} + +export const useAboutDaoTabs = (network?: string) => { + const swrKey = network ? ([SWR_KEYS.EXPLORE, network] as const) : null + + const { data, error, isLoading, isValidating } = useSWR< + AboutDaoTabsResponse, + HttpError + >(swrKey, fetcher, { + revalidateOnFocus: false, + revalidateOnReconnect: true, + dedupingInterval: 60000, + }) + + return { + data, + error, + isLoading: !!swrKey && (isLoading || isValidating), + } +} diff --git a/apps/web/src/modules/about/useAboutShowcase.ts b/apps/web/src/modules/about/useAboutShowcase.ts new file mode 100644 index 000000000..a92849c61 --- /dev/null +++ b/apps/web/src/modules/about/useAboutShowcase.ts @@ -0,0 +1,44 @@ +import useSWR from 'swr' + +import { AboutShowcaseResponse } from './types' + +type HttpError = Error & { status?: number; body?: unknown } + +const fetcher = async ( + [url]: readonly [string], + { signal }: { signal?: AbortSignal } = {} +): Promise => { + const response = await fetch(url, { + signal, + headers: { Accept: 'application/json' }, + }) + + const text = await response.text() + const body = text ? JSON.parse(text) : {} + + if (!response.ok) { + const err: HttpError = new Error(body?.error || response.statusText) + err.status = response.status + err.body = body + throw err + } + + return body as AboutShowcaseResponse +} + +export const useAboutShowcase = () => { + const { data, error, isLoading, isValidating } = useSWR< + AboutShowcaseResponse, + HttpError + >(['/api/about/showcase'] as const, fetcher, { + revalidateOnFocus: false, + revalidateOnReconnect: true, + dedupingInterval: 60000, + }) + + return { + data, + error, + isLoading: isLoading || isValidating, + } +} diff --git a/apps/web/src/modules/about/useAboutSnapshotStats.ts b/apps/web/src/modules/about/useAboutSnapshotStats.ts new file mode 100644 index 000000000..e1edd82d1 --- /dev/null +++ b/apps/web/src/modules/about/useAboutSnapshotStats.ts @@ -0,0 +1,54 @@ +import useSWR from 'swr' + +import { AboutSnapshotResponse } from './types' + +type HttpError = Error & { status?: number; body?: unknown } + +const fetcher = async ( + [url]: readonly [string], + { signal }: { signal?: AbortSignal } = {} +): Promise => { + const response = await fetch(url, { + signal, + headers: { Accept: 'application/json' }, + }) + + const text = await response.text() + let body: unknown = {} + + try { + body = text ? JSON.parse(text) : {} + } catch { + body = text + } + + if (!response.ok) { + const err: HttpError = new Error( + typeof body === 'object' && body && 'error' in body + ? String((body as { error?: string }).error || response.statusText) + : response.statusText + ) + err.status = response.status + err.body = body + throw err + } + + return body as AboutSnapshotResponse +} + +export const useAboutSnapshotStats = () => { + const { data, error, isLoading, isValidating } = useSWR< + AboutSnapshotResponse, + HttpError + >(['/api/about/snapshot'] as const, fetcher, { + revalidateOnFocus: false, + revalidateOnReconnect: true, + dedupingInterval: 60000, + }) + + return { + data, + error, + isLoading: isLoading || isValidating, + } +} diff --git a/apps/web/src/modules/about/utils.ts b/apps/web/src/modules/about/utils.ts new file mode 100644 index 000000000..68f890261 --- /dev/null +++ b/apps/web/src/modules/about/utils.ts @@ -0,0 +1,34 @@ +import { PUBLIC_DEFAULT_CHAINS } from '@buildeross/constants/chains' +import removeMd from 'remove-markdown' + +// Normalize chain labels for fuzzy matching/deduplication: +// lowercase, replace non-alphanumerics with spaces, then collapse/trim whitespace. +const normalizeChainLabel = (value?: string | null) => + (value || '') + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, ' ') + .trim() + +const CHAIN_ICON_BY_LABEL = new Map( + PUBLIC_DEFAULT_CHAINS.flatMap((chain) => { + const labels = [chain.name, chain.slug].filter(Boolean).map(normalizeChainLabel) + const fallbackLabels = labels.flatMap((label) => { + if (!label.includes('sepolia')) return [] + + return [label.replace(/\s*sepolia$/, '').trim()] + }) + + return [...labels, ...fallbackLabels] + .filter(Boolean) + .map((label) => [label, chain.icon] as const) + }) +) + +export const getChainLogoSrc = (chainName?: string | null) => + CHAIN_ICON_BY_LABEL.get(normalizeChainLabel(chainName)) || null + +export const toPlainText = (value?: string | null) => + removeMd(value || '') + .replace(/\s+/g, ' ') + .trim() diff --git a/apps/web/src/modules/coin/CoinDetail/CoinCommentForm.css.ts b/apps/web/src/modules/coin/CoinDetail/CoinCommentForm.css.ts index c5171a9e3..b9f98c63f 100644 --- a/apps/web/src/modules/coin/CoinDetail/CoinCommentForm.css.ts +++ b/apps/web/src/modules/coin/CoinDetail/CoinCommentForm.css.ts @@ -4,7 +4,11 @@ import { style } from '@vanilla-extract/css' export const commentFormContainer = style([ atoms({ w: '100%', - backgroundColor: 'background2', + p: 'x4', + backgroundColor: 'background1', + borderColor: 'border', + borderStyle: 'solid', + borderWidth: 'thin', borderRadius: 'curved', mb: 'x5', }), diff --git a/apps/web/src/modules/coin/CoinDetail/CoinComments.css.ts b/apps/web/src/modules/coin/CoinDetail/CoinComments.css.ts index 33dd250c7..a55c94cff 100644 --- a/apps/web/src/modules/coin/CoinDetail/CoinComments.css.ts +++ b/apps/web/src/modules/coin/CoinDetail/CoinComments.css.ts @@ -18,7 +18,8 @@ export const commentCard = style([ borderRadius: 'curved', borderWidth: 'thin', borderStyle: 'solid', - borderColor: 'borderOnImage', + borderColor: 'border', + backgroundColor: 'background1', py: 'x2', px: 'x4', }), diff --git a/apps/web/src/modules/coin/CoinDetail/CoinComments.tsx b/apps/web/src/modules/coin/CoinDetail/CoinComments.tsx index 5238464ae..ce802c76b 100644 --- a/apps/web/src/modules/coin/CoinDetail/CoinComments.tsx +++ b/apps/web/src/modules/coin/CoinDetail/CoinComments.tsx @@ -56,7 +56,7 @@ const CommentCard: React.FC<{ nameStyle={{ fontSize: '16px' }} mobileTapBehavior="toggle" /> - + {timeAgo} diff --git a/apps/web/src/modules/coin/CoinDetail/CoinDetail.css.ts b/apps/web/src/modules/coin/CoinDetail/CoinDetail.css.ts index d3524b51c..240f799e5 100644 --- a/apps/web/src/modules/coin/CoinDetail/CoinDetail.css.ts +++ b/apps/web/src/modules/coin/CoinDetail/CoinDetail.css.ts @@ -41,6 +41,9 @@ export const coinInfoPanel = style([ p: 'x6', borderRadius: 'curved', backgroundColor: 'background2', + borderColor: 'border', + borderWidth: 'thin', + borderStyle: 'solid', }), ]) @@ -49,6 +52,9 @@ export const swapPanel = style([ p: 'x6', borderRadius: 'curved', backgroundColor: 'background2', + borderColor: 'border', + borderWidth: 'thin', + borderStyle: 'solid', }), { '@media': { diff --git a/apps/web/src/modules/coin/CoinDetail/CoinInfo.tsx b/apps/web/src/modules/coin/CoinDetail/CoinInfo.tsx index 417c9c15e..995cd5f1f 100644 --- a/apps/web/src/modules/coin/CoinDetail/CoinInfo.tsx +++ b/apps/web/src/modules/coin/CoinDetail/CoinInfo.tsx @@ -332,7 +332,7 @@ export const CoinInfo = ({ )} {daoName} - + diff --git a/apps/web/src/modules/dashboard/CreateActions.css.ts b/apps/web/src/modules/dashboard/CreateActions.css.ts index fdceef342..35328a602 100644 --- a/apps/web/src/modules/dashboard/CreateActions.css.ts +++ b/apps/web/src/modules/dashboard/CreateActions.css.ts @@ -1,4 +1,3 @@ -import { vars } from '@buildeross/zord' import { style } from '@vanilla-extract/css' export const actionButtons = style({ @@ -9,7 +8,3 @@ export const actionButtons = style({ }, }, }) - -export const daoButton = style({ - borderColor: `${vars.color.border} !important`, -}) diff --git a/apps/web/src/modules/dashboard/CreateActions.tsx b/apps/web/src/modules/dashboard/CreateActions.tsx index a834b2d2d..cf59ab9d4 100644 --- a/apps/web/src/modules/dashboard/CreateActions.tsx +++ b/apps/web/src/modules/dashboard/CreateActions.tsx @@ -3,7 +3,7 @@ import { Button, Flex } from '@buildeross/zord' import Link from 'next/link' import React, { useState } from 'react' -import { actionButtons, daoButton } from './CreateActions.css' +import { actionButtons } from './CreateActions.css' import { DaoSelectorModal } from './DaoSelectorModal' export interface CreateActionsProps { @@ -35,11 +35,14 @@ export const CreateActions: React.FC = ({ userAddress }) => Create Proposal - - - + setShowTooltip(false)} > { dao.proposals.some( (proposal) => proposal.state === ProposalState.Active && - !proposal.votes.some((vote) => vote.voter === address.toLowerCase()) + !proposal.votes.some( + (vote: { voter: string }) => vote.voter === address.toLowerCase() + ) ) ) }, [sortedDaos, address]) @@ -315,7 +317,7 @@ export const Dashboard: React.FC = () => { ? `${totalProposals} active proposal${totalProposals !== 1 ? 's' : ''}` : 'No active proposals' } - description={{proposalList}} + description={{proposalList}} titleFontSize={18} mb={'x0'} showWarning={hasProposalsNeedingVote} diff --git a/apps/web/src/modules/dashboard/MobileCreateMenu.tsx b/apps/web/src/modules/dashboard/MobileCreateMenu.tsx index 858757a01..729434789 100644 --- a/apps/web/src/modules/dashboard/MobileCreateMenu.tsx +++ b/apps/web/src/modules/dashboard/MobileCreateMenu.tsx @@ -47,7 +47,7 @@ export const MobileCreateMenu: React.FC = ({ userAddress diff --git a/apps/web/src/modules/explore/ExploreSortMenu.tsx b/apps/web/src/modules/explore/ExploreSortMenu.tsx index ea5d469fa..9c98d3189 100644 --- a/apps/web/src/modules/explore/ExploreSortMenu.tsx +++ b/apps/web/src/modules/explore/ExploreSortMenu.tsx @@ -1,5 +1,6 @@ import { Auction_OrderBy } from '@buildeross/sdk/subgraph' -import { Flex, Select } from '@buildeross/zord' +import { DropdownSelect } from '@buildeross/ui/DropdownSelect' +import { Flex } from '@buildeross/zord' import { useRouter } from 'next/router' import React from 'react' @@ -30,12 +31,13 @@ export const ExploreSortMenu: React.FC = () => { }, []) const handleSortChange = React.useCallback( - (e: React.ChangeEvent) => { + (selection: string) => { push({ pathname, query: { ...query, - orderBy: selectionToOrderBy(e.target.value), + sortKey: selectionToOrderBy(selection), + orderBy: selectionToOrderBy(selection), }, }) }, @@ -56,17 +58,18 @@ export const ExploreSortMenu: React.FC = () => { return ( - + ({ + label: value, + value, + }))} + value={defaultSort} + onChange={handleSortChange} + customLabel={defaultSort} + positioning="absolute" + height="x10" + minWidth="120px" + /> ) } diff --git a/apps/web/src/modules/explore/ExploreToolbar.tsx b/apps/web/src/modules/explore/ExploreToolbar.tsx index ea7cb2faa..17b052294 100644 --- a/apps/web/src/modules/explore/ExploreToolbar.tsx +++ b/apps/web/src/modules/explore/ExploreToolbar.tsx @@ -28,7 +28,7 @@ export const ExploreToolbar: React.FC = ({ align={'center'} style={{ maxWidth: 912 }} > - + {title} @@ -44,7 +44,8 @@ export const ExploreToolbar: React.FC = ({ mx={'x4'} aria-current={pathname === '/explore' ? 'page' : undefined} style={{ - borderBottom: pathname === '/explore' ? `2px solid black` : `0px`, + borderBottom: + pathname === '/explore' ? `2px solid ${vars.color.text1}` : `0px`, }} > Explore @@ -57,7 +58,8 @@ export const ExploreToolbar: React.FC = ({ mx={'x4'} aria-current={pathname === '/mydaos' ? 'page' : undefined} style={{ - borderBottom: pathname === '/mydaos' ? `2px solid black` : `0px`, + borderBottom: + pathname === '/mydaos' ? `2px solid ${vars.color.text1}` : `0px`, }} > My DAOs @@ -70,7 +72,8 @@ export const ExploreToolbar: React.FC = ({ mx={'x4'} aria-current={pathname === '/favorites' ? 'page' : undefined} style={{ - borderBottom: pathname === '/favorites' ? `2px solid black` : `0px`, + borderBottom: + pathname === '/favorites' ? `2px solid ${vars.color.text1}` : `0px`, }} > Favorites diff --git a/apps/web/src/modules/explore/SearchInput.css.ts b/apps/web/src/modules/explore/SearchInput.css.ts index 47c04d09d..5fcb3ea3e 100644 --- a/apps/web/src/modules/explore/SearchInput.css.ts +++ b/apps/web/src/modules/explore/SearchInput.css.ts @@ -1,3 +1,4 @@ +import { vars } from '@buildeross/zord' import { style } from '@vanilla-extract/css' export const searchInputWrapper = style({ @@ -9,21 +10,26 @@ export const searchInputWrapper = style({ export const searchInputStyle = style({ height: 64, width: '100%', - backgroundColor: '#F2F2F2', + backgroundColor: vars.color.background2, borderRadius: '12px', fontSize: 16, paddingLeft: 16, paddingRight: 80, // Space for search icon boxSizing: 'border-box', - border: '2px solid #fff', + border: `2px solid ${vars.color.background1}`, + color: vars.color.text1, selectors: { '&:focus': { outline: 'none', - backgroundColor: '#FFF', - borderColor: '#E6E6E6', + backgroundColor: vars.color.background1, + borderColor: vars.color.border, + }, + '&:focus-visible': { + outline: `2px solid ${vars.color.focusRing}`, + outlineOffset: '2px', }, '&::placeholder': { - color: '#B3B3B3', + color: vars.color.text3, }, }, }) @@ -80,7 +86,7 @@ export const helperTextStyle = style({ left: 16, marginTop: 4, fontSize: 14, - color: '#808080', + color: vars.color.text3, transition: 'opacity 0.2s ease-in-out', pointerEvents: 'none', whiteSpace: 'nowrap', diff --git a/apps/web/src/modules/home/FAQ.tsx b/apps/web/src/modules/home/FAQ.tsx index 811cd049a..2104ef9ca 100644 --- a/apps/web/src/modules/home/FAQ.tsx +++ b/apps/web/src/modules/home/FAQ.tsx @@ -50,7 +50,7 @@ export const FAQ = () => { target="_blank" rel="noreferrer noopener" > - + Artwork starter kit diff --git a/apps/web/src/modules/home/Home.css.ts b/apps/web/src/modules/home/Home.css.ts index 3e2bfd1f7..c75e048e8 100644 --- a/apps/web/src/modules/home/Home.css.ts +++ b/apps/web/src/modules/home/Home.css.ts @@ -1,4 +1,4 @@ -import { atoms } from '@buildeross/zord' +import { atoms, vars } from '@buildeross/zord' import { style } from '@vanilla-extract/css' export const marqueeButton = style({ @@ -6,8 +6,8 @@ export const marqueeButton = style({ width: 280, selectors: { '&:hover': { - color: '#fff', - backgroundColor: 'rgba(0,0,0,.8)', + color: vars.color.onAccent, + backgroundColor: vars.color.accentHover, }, }, '@media': { diff --git a/apps/web/src/modules/home/Homepage.css.ts b/apps/web/src/modules/home/Homepage.css.ts index c6cf71008..84d55d038 100644 --- a/apps/web/src/modules/home/Homepage.css.ts +++ b/apps/web/src/modules/home/Homepage.css.ts @@ -1,4 +1,4 @@ -import { atoms } from '@buildeross/zord' +import { atoms, vars } from '@buildeross/zord' import { style } from '@vanilla-extract/css' export const heroBannerGrid = style([ @@ -122,7 +122,7 @@ export const heroBannerCreateDaoButton = style({ selectors: { '&:hover': { cursor: 'pointer', - backgroundColor: '#808080', + backgroundColor: vars.color.ghostHover, }, }, }) diff --git a/apps/web/src/modules/home/Marquee.css.ts b/apps/web/src/modules/home/Marquee.css.ts index eae488230..4bb918e1f 100644 --- a/apps/web/src/modules/home/Marquee.css.ts +++ b/apps/web/src/modules/home/Marquee.css.ts @@ -17,14 +17,40 @@ const getMobileStyle = (width: string) => { } export const Unlock = style([baseStyles, getMobileStyle('130px')]) +export const UnlockDark = style([baseStyles, getMobileStyle('130px')]) export const PurpleGalaxy = style([baseStyles, getMobileStyle('34px')]) export const The = style([baseStyles, getMobileStyle('62px')]) +export const TheDark = style([baseStyles, getMobileStyle('62px')]) export const BlueWheel = style([baseStyles, getMobileStyle('36px')]) export const Possibilities = style([baseStyles, getMobileStyle('225px')]) +export const PossibilitiesDark = style([baseStyles, getMobileStyle('225px')]) export const Of = style([baseStyles, getMobileStyle('38px')]) +export const OfDark = style([baseStyles, getMobileStyle('38px')]) export const GreenClover = style([baseStyles, getMobileStyle('36px')]) export const Collective = style([baseStyles, getMobileStyle('180px')]) +export const CollectiveDark = style([baseStyles, getMobileStyle('180px')]) export const PurpleStar = style([baseStyles, getMobileStyle('36px')]) export const BlueSun = style([baseStyles, getMobileStyle('36px')]) export const NounsGlasses = style([baseStyles, getMobileStyle('62px')]) export const Creation = style([baseStyles, getMobileStyle('157px')]) +export const CreationDark = style([baseStyles, getMobileStyle('157px')]) + +const darkWordSelector = 'html[data-theme-mode="dark"] &' + +export const lightWord = style({ + display: 'block', + selectors: { + [darkWordSelector]: { + display: 'none', + }, + }, +}) + +export const darkWord = style({ + display: 'none', + selectors: { + [darkWordSelector]: { + display: 'block', + }, + }, +}) diff --git a/apps/web/src/modules/home/Marquee.tsx b/apps/web/src/modules/home/Marquee.tsx index eb031a44b..ada653cb7 100644 --- a/apps/web/src/modules/home/Marquee.tsx +++ b/apps/web/src/modules/home/Marquee.tsx @@ -1,28 +1,47 @@ +'use client' + import { atoms, Flex } from '@buildeross/zord' import { motion } from 'framer-motion' import Image from 'next/image' -import React from 'react' import { BlueSun, BlueWheel, Collective, + CollectiveDark, Creation, + CreationDark, + darkWord, GreenClover, + lightWord, NounsGlasses, Of, + OfDark, Possibilities, + PossibilitiesDark, PurpleGalaxy, PurpleStar, The, + TheDark, Unlock, + UnlockDark, } from './Marquee.css' export const Marquee = () => { return ( - {'unlock'} + {'unlock'} + {''} { - {'the'} + {'the'} + {''} { {'possibilities'} + {''} - {'of'} + {'of'} + {''} { className={GreenClover} /> - {'collective'} + {'collective'} + {''} { alt={'small nouns glasses logo'} className={NounsGlasses} /> - {'creation'} + {'creation'} + {''} ) diff --git a/apps/web/src/modules/playground/PlaygroundPage.tsx b/apps/web/src/modules/playground/PlaygroundPage.tsx index 396502ee5..33bfb79e7 100644 --- a/apps/web/src/modules/playground/PlaygroundPage.tsx +++ b/apps/web/src/modules/playground/PlaygroundPage.tsx @@ -49,7 +49,7 @@ export const PlaygroundPage: React.FC = () => { const isCustomView = view === 'custom' return ( - + = ({