@@ -0,0 +1,2 @@ | |||||
@tesseract-design:registry=http://localhost:4873 | |||||
@modal-sh:registry=http://localhost:4873 |
@@ -4,13 +4,18 @@ import * as WebNavigationReact from '@tesseract-design/web-navigation-react'; | |||||
import * as WebFreeformReact from '@tesseract-design/web-freeform-react'; | import * as WebFreeformReact from '@tesseract-design/web-freeform-react'; | ||||
import * as WebFormattedReact from '@tesseract-design/web-formatted-react'; | import * as WebFormattedReact from '@tesseract-design/web-formatted-react'; | ||||
import * as WebActionReact from '@tesseract-design/web-action-react'; | import * as WebActionReact from '@tesseract-design/web-action-react'; | ||||
import {Brand} from '@/components/molecules/Brand'; | |||||
export interface ContactCtaBannerProps { | export interface ContactCtaBannerProps { | ||||
onSubmit?: React.FormEventHandler<HTMLFormElement>; | onSubmit?: React.FormEventHandler<HTMLFormElement>; | ||||
visible?: boolean; | |||||
hasBrand?: boolean; | |||||
} | } | ||||
export const ContactCtaBanner: React.FC<ContactCtaBannerProps> = ({ | export const ContactCtaBanner: React.FC<ContactCtaBannerProps> = ({ | ||||
onSubmit, | onSubmit, | ||||
visible: visibleProp = false, | |||||
hasBrand = false, | |||||
}) => { | }) => { | ||||
const [visible, setVisible] = React.useState<boolean>(); | const [visible, setVisible] = React.useState<boolean>(); | ||||
const autofocusRef = React.useRef<HTMLInputElement>(null); | const autofocusRef = React.useRef<HTMLInputElement>(null); | ||||
@@ -19,17 +24,17 @@ export const ContactCtaBanner: React.FC<ContactCtaBannerProps> = ({ | |||||
const openContactForm: React.MouseEventHandler<HTMLAnchorElement> = (e) => { | const openContactForm: React.MouseEventHandler<HTMLAnchorElement> = (e) => { | ||||
e.preventDefault(); | e.preventDefault(); | ||||
setVisible(true); | setVisible(true); | ||||
}; | |||||
React.useEffect(() => { | |||||
window.setTimeout(() => { | window.setTimeout(() => { | ||||
autofocusRef.current?.focus(); | autofocusRef.current?.focus(); | ||||
if (messageRef.current && messageRef.current.style.height === '0px') { | |||||
messageRef.current.style.height = '12rem'; | |||||
} | |||||
}); | }); | ||||
}; | |||||
}, [visible]); | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
setVisible(false); | |||||
}, []); | |||||
setVisible(visibleProp); | |||||
}, [visibleProp]); | |||||
return ( | return ( | ||||
<div className="pt-8 sm:pt-16 pb-8 sm:pb-16 flex items-center relative"> | <div className="pt-8 sm:pt-16 pb-8 sm:pb-16 flex items-center relative"> | ||||
@@ -40,6 +45,13 @@ export const ContactCtaBanner: React.FC<ContactCtaBannerProps> = ({ | |||||
span="wide" | span="wide" | ||||
className="relative" | className="relative" | ||||
> | > | ||||
{hasBrand && ( | |||||
<h1 className="text-center mt-0 mb-16 before:text-7xl sm:before:text-8xl before:block before:content-['I_am.'] text-base leading-none"> | |||||
<span className="text-4xl"> | |||||
<Brand/> | |||||
</span> | |||||
</h1> | |||||
)} | |||||
<div className="flex flex-wrap flex-row items-center sm:justify-between gap-6 sm:gap-12"> | <div className="flex flex-wrap flex-row items-center sm:justify-between gap-6 sm:gap-12"> | ||||
<p className="m-0"> | <p className="m-0"> | ||||
<strong | <strong | ||||
@@ -85,6 +97,8 @@ export const ContactCtaBanner: React.FC<ContactCtaBannerProps> = ({ | |||||
block | block | ||||
border | border | ||||
ref={autofocusRef} | ref={autofocusRef} | ||||
autoComplete="off" | |||||
autoFocus | |||||
/> | /> | ||||
</div> | </div> | ||||
<div> | <div> | ||||
@@ -104,6 +118,7 @@ export const ContactCtaBanner: React.FC<ContactCtaBannerProps> = ({ | |||||
block | block | ||||
border | border | ||||
required | required | ||||
rows={8} | |||||
/> | /> | ||||
</div> | </div> | ||||
<div className="col-span-2 text-right"> | <div className="col-span-2 text-right"> | ||||
@@ -150,13 +150,12 @@ export const Footer = () => ( | |||||
</dl> | </dl> | ||||
</div> | </div> | ||||
<div className="text-center"> | <div className="text-center"> | ||||
© | |||||
{' '} | |||||
<span className="text-2xl mx-3"> | |||||
<span className="text-2xl mx-3 block mb-4"> | |||||
<Brand/> | <Brand/> | ||||
</span> | </span> | ||||
© | |||||
{' '} | {' '} | ||||
{formatYear(2023, new Date().getFullYear())}. | |||||
{formatYear(2023, new Date().getFullYear())} | |||||
</div> | </div> | ||||
</Layouts.Basic.ContentContainer> | </Layouts.Basic.ContentContainer> | ||||
</footer> | </footer> | ||||
@@ -30,13 +30,6 @@ export const MainLandingSection: React.FC<MainLandingSectionProps> = ({ | |||||
<div | <div | ||||
className="sm:py-24 flex items-center min-h-full relative overflow-hidden" | className="sm:py-24 flex items-center min-h-full relative overflow-hidden" | ||||
> | > | ||||
<div | |||||
className="fixed top-0 left-0 w-full h-full" | |||||
> | |||||
<div className="w-[125%] h-[125%] absolute -top-[12.5%] -left-[12.5%] opacity-25"> | |||||
<BackgroundGrid images={backgroundImages} /> | |||||
</div> | |||||
</div> | |||||
<div | <div | ||||
className="absolute top-0 left-0 w-full h-full bg-shade opacity-25" | className="absolute top-0 left-0 w-full h-full bg-shade opacity-25" | ||||
/> | /> | ||||
@@ -46,9 +39,9 @@ export const MainLandingSection: React.FC<MainLandingSectionProps> = ({ | |||||
> | > | ||||
<div className="flex flex-col sm:flex-row sm:items-center gap-8 sm:gap-16"> | <div className="flex flex-col sm:flex-row sm:items-center gap-8 sm:gap-16"> | ||||
<h1 className="text-center m-0 before:text-7xl sm:before:text-8xl before:block before:content-['I_am.'] text-base leading-none"> | <h1 className="text-center m-0 before:text-7xl sm:before:text-8xl before:block before:content-['I_am.'] text-base leading-none"> | ||||
<span className="text-4xl"> | |||||
<Brand/> | |||||
</span> | |||||
<span className="text-4xl"> | |||||
<Brand/> | |||||
</span> | |||||
</h1> | </h1> | ||||
<div className="w-full sm:w-auto flex-auto flex flex-row sm:flex-col gap-2 h-64 sm:h-64"> | <div className="w-full sm:w-auto flex-auto flex flex-row sm:flex-col gap-2 h-64 sm:h-64"> | ||||
<div className="w-0 sm:w-auto flex-auto"> | <div className="w-0 sm:w-auto flex-auto"> | ||||
@@ -6,13 +6,17 @@ import {EnvisionSection, EnvisionSectionProps} from '@/components/molecules/Envi | |||||
import {Footer} from '@/components/molecules/Footer'; | import {Footer} from '@/components/molecules/Footer'; | ||||
import {FeaturedProjectSection, FeaturedProjectSectionProps} from '@/components/molecules/FeaturedProjectSection'; | import {FeaturedProjectSection, FeaturedProjectSectionProps} from '@/components/molecules/FeaturedProjectSection'; | ||||
import {ContactCtaBanner} from '@/components/molecules/ContactCtaBanner'; | import {ContactCtaBanner} from '@/components/molecules/ContactCtaBanner'; | ||||
import {BackgroundGrid} from '@/components/molecules/BackgroundGrid'; | |||||
export interface IndexLayoutProps { | export interface IndexLayoutProps { | ||||
backgroundImages: MainLandingSectionProps['backgroundImages']; | backgroundImages: MainLandingSectionProps['backgroundImages']; | ||||
makeSectionData: MakeSectionProps['data']; | |||||
envisionSectionData: EnvisionSectionProps['data']; | |||||
makeSectionData?: MakeSectionProps['data']; | |||||
envisionSectionData?: EnvisionSectionProps['data']; | |||||
featuredProjectData?: FeaturedProjectSectionProps[]; | featuredProjectData?: FeaturedProjectSectionProps[]; | ||||
onSubmit?: React.FormEventHandler<HTMLFormElement>; | onSubmit?: React.FormEventHandler<HTMLFormElement>; | ||||
contactVisible?: boolean; | |||||
hasMainLandingSection?: boolean; | |||||
contactHasBrand?: boolean; | |||||
} | } | ||||
export const IndexLayout: React.FC<IndexLayoutProps> = ({ | export const IndexLayout: React.FC<IndexLayoutProps> = ({ | ||||
@@ -21,18 +25,36 @@ export const IndexLayout: React.FC<IndexLayoutProps> = ({ | |||||
envisionSectionData, | envisionSectionData, | ||||
featuredProjectData = [], | featuredProjectData = [], | ||||
onSubmit, | onSubmit, | ||||
contactVisible = false, | |||||
hasMainLandingSection = false, | |||||
contactHasBrand = false, | |||||
}) => ( | }) => ( | ||||
<Layouts.Basic.Root className="contents"> | <Layouts.Basic.Root className="contents"> | ||||
<MainLandingSection | |||||
backgroundImages={backgroundImages} | |||||
/> | |||||
<div id="start" /> | |||||
<MakeSection | |||||
data={makeSectionData} | |||||
/> | |||||
<EnvisionSection | |||||
data={envisionSectionData} | |||||
/> | |||||
<div | |||||
className="fixed top-0 left-0 w-full h-full" | |||||
> | |||||
<div className="w-[125%] h-[125%] absolute -top-[12.5%] -left-[12.5%] opacity-25"> | |||||
<BackgroundGrid images={backgroundImages} /> | |||||
</div> | |||||
</div> | |||||
{hasMainLandingSection && ( | |||||
<> | |||||
<MainLandingSection | |||||
backgroundImages={backgroundImages} | |||||
/> | |||||
<div id="start" /> | |||||
</> | |||||
)} | |||||
{Array.isArray(makeSectionData) && ( | |||||
<MakeSection | |||||
data={makeSectionData} | |||||
/> | |||||
)} | |||||
{Array.isArray(envisionSectionData) && ( | |||||
<EnvisionSection | |||||
data={envisionSectionData} | |||||
/> | |||||
)} | |||||
{featuredProjectData.map((featuredProjectProps) => ( | {featuredProjectData.map((featuredProjectProps) => ( | ||||
<FeaturedProjectSection | <FeaturedProjectSection | ||||
key={featuredProjectProps.title} | key={featuredProjectProps.title} | ||||
@@ -41,6 +63,8 @@ export const IndexLayout: React.FC<IndexLayoutProps> = ({ | |||||
))} | ))} | ||||
<ContactCtaBanner | <ContactCtaBanner | ||||
onSubmit={onSubmit} | onSubmit={onSubmit} | ||||
visible={contactVisible} | |||||
hasBrand={contactHasBrand} | |||||
/> | /> | ||||
<Footer/> | <Footer/> | ||||
</Layouts.Basic.Root> | </Layouts.Basic.Root> | ||||
@@ -0,0 +1,8 @@ | |||||
export namespace effects { | |||||
export namespace huePulsate { | |||||
export const propertyName = '--color-primary'; | |||||
export const initialColorHueDegrees = 320; | |||||
export const colorSaturationPercentage = 35; | |||||
export const colorLightnessPercentage = 66; | |||||
} | |||||
} |
@@ -0,0 +1,14 @@ | |||||
import * as React from 'react'; | |||||
import { getFormValues } from '@theoryofnekomata/formxtra'; | |||||
export const useContactForm = () => { | |||||
const processContactForm: React.FormEventHandler<HTMLFormElement> = (e) => { | |||||
e.preventDefault(); | |||||
const values = getFormValues(e.currentTarget); | |||||
console.log(values); | |||||
}; | |||||
return React.useMemo(() => ({ | |||||
processContactForm | |||||
}), []); | |||||
}; |
@@ -21,7 +21,7 @@ export interface UseHuePulsateOptions { | |||||
propertyName: string; | propertyName: string; | ||||
} | } | ||||
export const useHuePulsate = (options : UseHuePulsateOptions) => { | |||||
export const useHuePulsate = (options: UseHuePulsateOptions) => { | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
const { | const { | ||||
animationDuration = DEFAULT_ANIMATION_DURATION, | animationDuration = DEFAULT_ANIMATION_DURATION, | ||||
@@ -0,0 +1,35 @@ | |||||
import {NextPage} from 'next'; | |||||
import {useHuePulsate} from '@/hooks/effects'; | |||||
import * as config from '@/config'; | |||||
import * as React from 'react'; | |||||
import {IndexLayout} from '@/components/organisms/IndexLayout'; | |||||
const IMAGE_CHOICES = [ | |||||
'/images/3cd237361eada7fd30eb96d42d55ec00.jpg', | |||||
'/images/5ace16248237a96f6dbbcc16a3c385fe.jpg', | |||||
'/images/6af0fdc5eafb86f8b540e3d322c3a007.jpg', | |||||
'/images/8f5e3d92d2da0a760b312a9387219447.jpg', | |||||
'/images/162cab44da8420c50900d8f11c6da6fd.jpg', | |||||
'/images/2626c3485668a19730c64a7ee672a214.jpg', | |||||
'/images/9802f260887e9044312fb7d88547fb09.jpg', | |||||
'/images/a3a56f54a11c76f46f4392b748f3026a.png', | |||||
'/images/a96d02d8cc43cc3b9f1e77c43dfa5644.png', | |||||
'/images/d69ab7f4e5747b5b8f297e4ebe9770f5.png', | |||||
'/images/d1454662da2a4a8e77cd4c98eda6d662.png', | |||||
'/images/e4753cf6a55db6b7ab5037ed0fd4a3fe.jpg', | |||||
'/images/fe3050a31d3fb86accf26cc4bebec102.png', | |||||
]; | |||||
const ContactPage: NextPage = () => { | |||||
useHuePulsate(config.effects.huePulsate); | |||||
return ( | |||||
<IndexLayout | |||||
backgroundImages={IMAGE_CHOICES} | |||||
contactVisible | |||||
contactHasBrand | |||||
/> | |||||
) | |||||
}; | |||||
export default ContactPage; |
@@ -1,8 +1,9 @@ | |||||
import type { NextPage } from 'next'; | import type { NextPage } from 'next'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import {useHuePulsate} from '@/hooks/effects'; | import {useHuePulsate} from '@/hooks/effects'; | ||||
import {useContactForm} from '@/hooks/contact'; | |||||
import {IndexLayout} from '@/components/organisms/IndexLayout'; | import {IndexLayout} from '@/components/organisms/IndexLayout'; | ||||
import {getFormValues} from '@theoryofnekomata/formxtra'; | |||||
import * as config from '@/config'; | |||||
const IMAGE_CHOICES = [ | const IMAGE_CHOICES = [ | ||||
'/images/3cd237361eada7fd30eb96d42d55ec00.jpg', | '/images/3cd237361eada7fd30eb96d42d55ec00.jpg', | ||||
@@ -122,18 +123,9 @@ const featuredProjectData = [ | |||||
]; | ]; | ||||
const IndexPage: NextPage = () => { | const IndexPage: NextPage = () => { | ||||
useHuePulsate({ | |||||
propertyName: '--color-primary', | |||||
initialColorHueDegrees: 320, | |||||
colorSaturationPercentage: 35, | |||||
colorLightnessPercentage: 66, | |||||
}); | |||||
useHuePulsate(config.effects.huePulsate); | |||||
const processContactForm: React.FormEventHandler<HTMLFormElement> = (e) => { | |||||
e.preventDefault(); | |||||
const values = getFormValues(e.currentTarget); | |||||
console.log(values); | |||||
}; | |||||
const { processContactForm } = useContactForm(); | |||||
return ( | return ( | ||||
<IndexLayout | <IndexLayout | ||||
@@ -142,6 +134,7 @@ const IndexPage: NextPage = () => { | |||||
envisionSectionData={envisionSectionData} | envisionSectionData={envisionSectionData} | ||||
featuredProjectData={featuredProjectData} | featuredProjectData={featuredProjectData} | ||||
onSubmit={processContactForm} | onSubmit={processContactForm} | ||||
hasMainLandingSection | |||||
/> | /> | ||||
); | ); | ||||
}; | }; | ||||