add template
This commit is contained in:
33
src/components/BackgroundImage.jsx
Normal file
33
src/components/BackgroundImage.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import Image from 'next/image'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import backgroundImage from '@/images/background.jpg'
|
||||
|
||||
export function BackgroundImage({ className, position = 'left' }) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'absolute inset-0 overflow-hidden bg-indigo-50',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
className={clsx(
|
||||
'absolute top-0',
|
||||
position === 'left' &&
|
||||
'left-0 translate-x-[-55%] translate-y-[-10%] -scale-x-100 sm:left-1/2 sm:translate-x-[-98%] sm:translate-y-[-6%] lg:translate-x-[-106%] xl:translate-x-[-122%]',
|
||||
position === 'right' &&
|
||||
'left-full -translate-x-1/2 sm:left-1/2 sm:translate-x-[-20%] sm:translate-y-[-15%] md:translate-x-0 lg:translate-x-[5%] lg:translate-y-[4%] xl:translate-x-[27%] xl:translate-y-[-8%]',
|
||||
)}
|
||||
src={backgroundImage}
|
||||
alt=""
|
||||
width={918}
|
||||
height={1495}
|
||||
priority
|
||||
unoptimized
|
||||
/>
|
||||
<div className="absolute inset-x-0 top-0 h-40 bg-gradient-to-b from-white" />
|
||||
<div className="absolute inset-x-0 bottom-0 h-40 bg-gradient-to-t from-white" />
|
||||
</div>
|
||||
)
|
||||
}
|
15
src/components/Button.jsx
Normal file
15
src/components/Button.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import Link from 'next/link'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export function Button({ className, ...props }) {
|
||||
className = clsx(
|
||||
'inline-flex justify-center rounded-2xl bg-blue-600 p-4 text-base font-semibold text-white hover:bg-blue-500 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 active:text-white/70',
|
||||
className,
|
||||
)
|
||||
|
||||
return typeof props.href === 'undefined' ? (
|
||||
<button className={className} {...props} />
|
||||
) : (
|
||||
<Link className={className} {...props} />
|
||||
)
|
||||
}
|
10
src/components/Container.jsx
Normal file
10
src/components/Container.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import clsx from 'clsx'
|
||||
|
||||
export function Container({ className, ...props }) {
|
||||
return (
|
||||
<div
|
||||
className={clsx('mx-auto max-w-7xl px-4 sm:px-6 lg:px-8', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
7
src/components/DiamondIcon.jsx
Normal file
7
src/components/DiamondIcon.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export function DiamondIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 6 6" {...props}>
|
||||
<path d="M3 0L6 3L3 6L0 3Z" strokeWidth={2} strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
16
src/components/Footer.jsx
Normal file
16
src/components/Footer.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Container } from '@/components/Container'
|
||||
import { Logo } from '@/components/Logo'
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="flex-none py-16">
|
||||
<Container className="flex flex-col items-center justify-between md:flex-row">
|
||||
<Logo className="h-12 w-auto text-slate-900" />
|
||||
<p className="mt-6 text-base text-slate-500 md:mt-0">
|
||||
Copyright © {new Date().getFullYear()} DeceptiConf, LLC. All
|
||||
rights reserved.
|
||||
</p>
|
||||
</Container>
|
||||
</footer>
|
||||
)
|
||||
}
|
29
src/components/Header.jsx
Normal file
29
src/components/Header.jsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Button } from '@/components/Button'
|
||||
import { Container } from '@/components/Container'
|
||||
import { DiamondIcon } from '@/components/DiamondIcon'
|
||||
import { Logo } from '@/components/Logo'
|
||||
|
||||
export function Header() {
|
||||
return (
|
||||
<header className="relative z-50 flex-none lg:pt-11">
|
||||
<Container className="flex flex-wrap items-center justify-center sm:justify-between lg:flex-nowrap">
|
||||
<div className="mt-10 lg:mt-0 lg:grow lg:basis-0">
|
||||
<Logo className="h-12 w-auto text-slate-900" />
|
||||
</div>
|
||||
<div className="order-first -mx-4 flex flex-auto basis-full overflow-x-auto whitespace-nowrap border-b border-blue-600/10 py-4 font-mono text-sm text-blue-600 sm:-mx-6 lg:order-none lg:mx-0 lg:basis-auto lg:border-0 lg:py-0">
|
||||
<div className="mx-auto flex items-center gap-4 px-4">
|
||||
<p>
|
||||
<time dateTime="2022-04-04">04</time>-
|
||||
<time dateTime="2022-04-06">06 of April, 2022</time>
|
||||
</p>
|
||||
<DiamondIcon className="h-1.5 w-1.5 overflow-visible fill-current stroke-current" />
|
||||
<p>Los Angeles, CA</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden sm:mt-10 sm:flex lg:mt-0 lg:grow lg:basis-0 lg:justify-end">
|
||||
<Button href="#">Get your tickets</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</header>
|
||||
)
|
||||
}
|
50
src/components/Hero.jsx
Normal file
50
src/components/Hero.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { BackgroundImage } from '@/components/BackgroundImage'
|
||||
import { Button } from '@/components/Button'
|
||||
import { Container } from '@/components/Container'
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<div className="relative py-20 sm:pb-24 sm:pt-36">
|
||||
<BackgroundImage className="-bottom-14 -top-36" />
|
||||
<Container className="relative">
|
||||
<div className="mx-auto max-w-2xl lg:max-w-4xl lg:px-12">
|
||||
<h1 className="font-display text-5xl font-bold tracking-tighter text-blue-600 sm:text-7xl">
|
||||
<span className="sr-only">DeceptiConf - </span>A design conference
|
||||
for the dark side.
|
||||
</h1>
|
||||
<div className="mt-6 space-y-6 font-display text-2xl tracking-tight text-blue-900">
|
||||
<p>
|
||||
The next generation of web users are tech-savvy and suspicious.
|
||||
They know how to use dev tools, they can detect a phishing scam
|
||||
from a mile away, and they certainly aren’t accepting any checks
|
||||
from Western Union.
|
||||
</p>
|
||||
<p>
|
||||
At DeceptiConf you’ll learn about the latest dark patterns being
|
||||
developed to trick even the smartest visitors, and you’ll learn
|
||||
how to deploy them without ever being detected.
|
||||
</p>
|
||||
</div>
|
||||
<Button href="#" className="mt-10 w-full sm:hidden">
|
||||
Get your tickets
|
||||
</Button>
|
||||
<dl className="mt-10 grid grid-cols-2 gap-x-10 gap-y-6 sm:mt-16 sm:gap-x-16 sm:gap-y-10 sm:text-center lg:auto-cols-auto lg:grid-flow-col lg:grid-cols-none lg:justify-start lg:text-left">
|
||||
{[
|
||||
['Speakers', '18'],
|
||||
['People Attending', '2,091'],
|
||||
['Venue', 'Staples Center'],
|
||||
['Location', 'Los Angeles'],
|
||||
].map(([name, value]) => (
|
||||
<div key={name}>
|
||||
<dt className="font-mono text-sm text-blue-600">{name}</dt>
|
||||
<dd className="mt-0.5 text-2xl font-semibold tracking-tight text-blue-900">
|
||||
{value}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
12
src/components/Layout.jsx
Normal file
12
src/components/Layout.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Footer } from '@/components/Footer'
|
||||
import { Header } from '@/components/Header'
|
||||
|
||||
export function Layout({ children, showFooter = true }) {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="flex-auto">{children}</main>
|
||||
{showFooter && <Footer />}
|
||||
</>
|
||||
)
|
||||
}
|
22
src/components/Logo.jsx
Normal file
22
src/components/Logo.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
export function Logo(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 183 48" {...props}>
|
||||
<path
|
||||
fill="#3B82F6"
|
||||
fillRule="evenodd"
|
||||
d="M1.172 21.172a4 4 0 000 5.656l20 20a4 4 0 105.656-5.656l-20-20a4 4 0 00-5.656 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill="#93C5FD"
|
||||
fillRule="evenodd"
|
||||
d="M26.828 6.828a4 4 0 10-5.656-5.656l-19 19A3.987 3.987 0 015 19a3.98 3.98 0 012.827 1.172L10.657 23 26.828 6.828z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill="#0F172A"
|
||||
d="M52 32V15.2h5.736c1.968 0 3.584.352 4.848 1.056 1.28.688 2.224 1.664 2.832 2.928.624 1.248.936 2.72.936 4.416 0 1.696-.312 3.176-.936 4.44-.608 1.248-1.552 2.224-2.832 2.928-1.264.688-2.88 1.032-4.848 1.032H52zm3.072-2.64h2.52c1.408 0 2.52-.224 3.336-.672a3.958 3.958 0 001.752-1.968c.352-.864.528-1.904.528-3.12 0-1.2-.176-2.232-.528-3.096a3.944 3.944 0 00-1.752-1.992c-.816-.464-1.928-.696-3.336-.696h-2.52V29.36zm18.263 2.928c-1.2 0-2.264-.256-3.192-.768a5.559 5.559 0 01-2.184-2.16c-.529-.928-.793-2-.793-3.216 0-1.232.257-2.328.769-3.288a5.687 5.687 0 012.16-2.232c.927-.544 2.016-.816 3.264-.816 1.168 0 2.2.256 3.096.768a5.407 5.407 0 012.088 2.112c.511.88.767 1.864.767 2.952 0 .176-.008.36-.023.552 0 .192-.009.392-.025.6h-9.047c.063.928.383 1.656.96 2.184.591.528 1.303.792 2.136.792.623 0 1.143-.136 1.56-.408.431-.288.752-.656.96-1.104h3.12a5.68 5.68 0 01-1.128 2.064 5.423 5.423 0 01-1.92 1.44c-.753.352-1.609.528-2.569.528zm.024-9.984a3.23 3.23 0 00-1.992.648c-.577.416-.945 1.056-1.105 1.92h5.928c-.047-.784-.335-1.408-.864-1.872-.527-.464-1.183-.696-1.967-.696zm12.927 9.984c-1.216 0-2.288-.264-3.216-.792a5.851 5.851 0 01-2.208-2.208c-.528-.944-.792-2.024-.792-3.24 0-1.216.264-2.296.792-3.24A5.851 5.851 0 0183.07 20.6c.928-.528 2-.792 3.216-.792 1.52 0 2.8.4 3.84 1.2 1.04.784 1.704 1.872 1.992 3.264h-3.24a2.299 2.299 0 00-.96-1.344c-.464-.336-1.016-.504-1.656-.504-.848 0-1.568.32-2.16.96-.592.64-.888 1.528-.888 2.664 0 1.136.296 2.024.888 2.664.592.64 1.312.96 2.16.96.64 0 1.192-.16 1.656-.48.48-.32.8-.776.96-1.368h3.24c-.288 1.344-.952 2.424-1.992 3.24-1.04.816-2.32 1.224-3.84 1.224zm12.903 0c-1.2 0-2.264-.256-3.192-.768a5.559 5.559 0 01-2.184-2.16c-.528-.928-.792-2-.792-3.216 0-1.232.256-2.328.768-3.288a5.687 5.687 0 012.16-2.232c.928-.544 2.016-.816 3.264-.816 1.168 0 2.2.256 3.096.768a5.407 5.407 0 012.088 2.112c.512.88.768 1.864.768 2.952 0 .176-.008.36-.024.552 0 .192-.008.392-.024.6h-9.048c.064.928.384 1.656.96 2.184.592.528 1.304.792 2.136.792.624 0 1.144-.136 1.56-.408.432-.288.752-.656.96-1.104h3.12a5.68 5.68 0 01-1.128 2.064 5.423 5.423 0 01-1.92 1.44c-.752.352-1.608.528-2.568.528zm.024-9.984a3.23 3.23 0 00-1.992.648c-.576.416-.944 1.056-1.104 1.92h5.928c-.048-.784-.336-1.408-.864-1.872-.528-.464-1.184-.696-1.968-.696zm7.096 14.976V20.096h2.736l.336 1.704c.384-.528.888-.992 1.512-1.392.64-.4 1.464-.6 2.472-.6 1.12 0 2.12.272 3 .816a5.846 5.846 0 012.088 2.232c.512.944.768 2.016.768 3.216 0 1.2-.256 2.272-.768 3.216a5.894 5.894 0 01-2.088 2.208c-.88.528-1.88.792-3 .792-.896 0-1.68-.168-2.352-.504a4.24 4.24 0 01-1.632-1.416v6.912h-3.072zm6.408-7.68c.976 0 1.784-.328 2.424-.984.64-.656.96-1.504.96-2.544s-.32-1.896-.96-2.568c-.64-.672-1.448-1.008-2.424-1.008-.992 0-1.808.336-2.448 1.008-.624.656-.936 1.504-.936 2.544s.312 1.896.936 2.568c.64.656 1.456.984 2.448.984zM125.36 32c-1.248 0-2.248-.304-3-.912-.752-.608-1.128-1.688-1.128-3.24v-5.184h-2.04v-2.568h2.04l.36-3.192h2.712v3.192h3.216v2.568h-3.216v5.208c0 .576.12.976.36 1.2.256.208.688.312 1.296.312h1.488V32h-2.088zm5.014-13.752c-.56 0-1.024-.168-1.392-.504-.352-.336-.528-.76-.528-1.272s.176-.928.528-1.248c.368-.336.832-.504 1.392-.504.56 0 1.016.168 1.368.504.368.32.552.736.552 1.248s-.184.936-.552 1.272c-.352.336-.808.504-1.368.504zM128.838 32V20.096h3.072V32h-3.072zm12.518.288c-1.696 0-3.152-.36-4.368-1.08a7.383 7.383 0 01-2.808-3.048c-.656-1.312-.984-2.824-.984-4.536 0-1.712.328-3.224.984-4.536.656-1.312 1.592-2.336 2.808-3.072 1.216-.736 2.672-1.104 4.368-1.104 2.016 0 3.664.504 4.944 1.512 1.296.992 2.104 2.392 2.424 4.2h-3.384c-.208-.912-.656-1.624-1.344-2.136-.672-.528-1.568-.792-2.688-.792-1.552 0-2.768.528-3.648 1.584-.88 1.056-1.32 2.504-1.32 4.344 0 1.84.44 3.288 1.32 4.344.88 1.04 2.096 1.56 3.648 1.56 1.12 0 2.016-.24 2.688-.72.688-.496 1.136-1.176 1.344-2.04h3.384c-.32 1.728-1.128 3.08-2.424 4.056-1.28.976-2.928 1.464-4.944 1.464zm14.287 0c-1.152 0-2.192-.264-3.12-.792a5.957 5.957 0 01-2.184-2.184c-.528-.944-.792-2.032-.792-3.264 0-1.232.272-2.312.816-3.24a5.905 5.905 0 012.184-2.208c.928-.528 1.968-.792 3.12-.792 1.136 0 2.16.264 3.072.792a5.68 5.68 0 012.184 2.208c.544.928.816 2.008.816 3.24 0 1.232-.272 2.32-.816 3.264a5.727 5.727 0 01-2.184 2.184c-.928.528-1.96.792-3.096.792zm0-2.664c.8 0 1.496-.296 2.088-.888.592-.608.888-1.504.888-2.688 0-1.184-.296-2.072-.888-2.664-.592-.608-1.28-.912-2.064-.912-.816 0-1.52.304-2.112.912-.576.592-.864 1.48-.864 2.664 0 1.184.288 2.08.864 2.688.592.592 1.288.888 2.088.888zM163.046 32V20.096h2.712l.24 2.016a4.21 4.21 0 011.584-1.68c.704-.416 1.528-.624 2.472-.624 1.472 0 2.616.464 3.432 1.392.816.928 1.224 2.288 1.224 4.08V32h-3.072v-6.432c0-1.024-.208-1.808-.624-2.352-.416-.544-1.064-.816-1.944-.816-.864 0-1.576.304-2.136.912-.544.608-.816 1.456-.816 2.544V32h-3.072zm13.714 0v-9.336h-1.632v-2.568h1.632v-1.392c0-1.44.36-2.464 1.08-3.072.736-.608 1.728-.912 2.976-.912h1.32v2.616h-.84c-.528 0-.904.104-1.128.312-.224.208-.336.56-.336 1.056v1.392h2.568v2.568h-2.568V32h-3.072z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
70
src/components/Newsletter.jsx
Normal file
70
src/components/Newsletter.jsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import Image from 'next/image'
|
||||
|
||||
import { Button } from '@/components/Button'
|
||||
import { Container } from '@/components/Container'
|
||||
import backgroundImage from '@/images/background-newsletter.jpg'
|
||||
|
||||
function ArrowRightIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
d="m14 7 5 5-5 5M19 12H5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function Newsletter() {
|
||||
return (
|
||||
<section id="newsletter" aria-label="Newsletter">
|
||||
<Container>
|
||||
<div className="relative -mx-4 overflow-hidden bg-indigo-50 px-4 py-20 sm:-mx-6 sm:px-6 md:mx-0 md:rounded-5xl md:px-16 xl:px-24 xl:py-36">
|
||||
<Image
|
||||
className="absolute left-1/2 top-0 translate-x-[-10%] translate-y-[-45%] lg:translate-x-[-32%]"
|
||||
src={backgroundImage}
|
||||
alt=""
|
||||
width={919}
|
||||
height={1351}
|
||||
unoptimized
|
||||
/>
|
||||
<div className="relative mx-auto grid max-w-2xl grid-cols-1 gap-x-32 gap-y-14 xl:max-w-none xl:grid-cols-2">
|
||||
<div>
|
||||
<p className="font-display text-4xl font-medium tracking-tighter text-blue-900 sm:text-5xl">
|
||||
Stay up to date
|
||||
</p>
|
||||
<p className="mt-4 text-lg tracking-tight text-blue-900">
|
||||
Get updates on all of our events and be the first to get
|
||||
notified when tickets go on sale.
|
||||
</p>
|
||||
</div>
|
||||
<form>
|
||||
<h3 className="text-lg font-semibold tracking-tight text-blue-900">
|
||||
Sign up to our newsletter <span aria-hidden="true">↓</span>
|
||||
</h3>
|
||||
<div className="mt-5 flex rounded-3xl bg-white py-2.5 pr-2.5 shadow-xl shadow-blue-900/5 focus-within:ring-2 focus-within:ring-blue-900">
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
placeholder="Email address"
|
||||
aria-label="Email address"
|
||||
className="-my-2.5 flex-auto bg-transparent pl-6 pr-2.5 text-base text-slate-900 placeholder:text-slate-400 focus:outline-none"
|
||||
/>
|
||||
<Button type="submit">
|
||||
<span className="sr-only sm:not-sr-only">Sign up today</span>
|
||||
<span className="sm:hidden">
|
||||
<ArrowRightIcon className="h-6 w-6" />
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
319
src/components/Schedule.jsx
Normal file
319
src/components/Schedule.jsx
Normal file
@@ -0,0 +1,319 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { BackgroundImage } from '@/components/BackgroundImage'
|
||||
import { Container } from '@/components/Container'
|
||||
|
||||
const schedule = [
|
||||
{
|
||||
date: 'April 4',
|
||||
dateTime: '2022-04-04',
|
||||
summary:
|
||||
'The first day of the conference is focused on dark patterns for ecommerce.',
|
||||
timeSlots: [
|
||||
{
|
||||
name: 'Steven McHail',
|
||||
description: 'Not so one-time payments',
|
||||
start: '9:00AM',
|
||||
end: '10:00AM',
|
||||
},
|
||||
{
|
||||
name: 'Jaquelin Isch',
|
||||
description: 'The finer print',
|
||||
start: '10:00AM',
|
||||
end: '11:00AM',
|
||||
},
|
||||
{
|
||||
name: 'Dianne Guilianelli',
|
||||
description: 'Post-purchase blackmail',
|
||||
start: '11:00AM',
|
||||
end: '12:00PM',
|
||||
},
|
||||
{
|
||||
name: 'Lunch',
|
||||
description: null,
|
||||
start: '12:00PM',
|
||||
end: '1:00PM',
|
||||
},
|
||||
{
|
||||
name: 'Ronni Cantadore',
|
||||
description: 'Buy or die',
|
||||
start: '1:00PM',
|
||||
end: '2:00PM',
|
||||
},
|
||||
{
|
||||
name: 'Erhart Cockrin',
|
||||
description: 'In-person cancellation',
|
||||
start: '2:00PM',
|
||||
end: '3:00PM',
|
||||
},
|
||||
{
|
||||
name: 'Parker Johnson',
|
||||
description: 'The pay/cancel switcheroo',
|
||||
start: '3:00PM',
|
||||
end: '4:00PM',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
date: 'April 5',
|
||||
dateTime: '2022-04-05',
|
||||
summary:
|
||||
'Next we spend the day talking about deceiving people with technology.',
|
||||
timeSlots: [
|
||||
{
|
||||
name: 'Damaris Kimura',
|
||||
description: 'The invisible card reader',
|
||||
start: '9:00AM',
|
||||
end: '10:00AM',
|
||||
},
|
||||
{
|
||||
name: 'Ibrahim Frasch',
|
||||
description: 'Stealing fingerprints',
|
||||
start: '10:00AM',
|
||||
end: '11:00AM',
|
||||
},
|
||||
{
|
||||
name: 'Cathlene Burrage',
|
||||
description: 'Voting machines',
|
||||
start: '11:00AM',
|
||||
end: '12:00PM',
|
||||
},
|
||||
{
|
||||
name: 'Lunch',
|
||||
description: null,
|
||||
start: '12:00PM',
|
||||
end: '1:00PM',
|
||||
},
|
||||
{
|
||||
name: 'Rinaldo Beynon',
|
||||
description: 'Blackhat SEO that works',
|
||||
start: '1:00PM',
|
||||
end: '2:00PM',
|
||||
},
|
||||
{
|
||||
name: 'Waylon Hyden',
|
||||
description: 'Turning your audience into a botnet',
|
||||
start: '2:00PM',
|
||||
end: '3:00PM',
|
||||
},
|
||||
{
|
||||
name: 'Giordano Sagucio',
|
||||
description: 'Fly phishing',
|
||||
start: '3:00PM',
|
||||
end: '4:00PM',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
date: 'April 6',
|
||||
dateTime: '2022-04-06',
|
||||
summary:
|
||||
'We close out the event previewing new techniques that are still in development.',
|
||||
timeSlots: [
|
||||
{
|
||||
name: 'Andrew Greene',
|
||||
description: 'Neuralink dark patterns',
|
||||
start: '9:00AM',
|
||||
end: '10:00AM',
|
||||
},
|
||||
{
|
||||
name: 'Heather Terry',
|
||||
description: 'DALL-E for passports',
|
||||
start: '10:00AM',
|
||||
end: '11:00AM',
|
||||
},
|
||||
{
|
||||
name: 'Piers Wilkins',
|
||||
description: 'Quantum password cracking',
|
||||
start: '11:00AM',
|
||||
end: '12:00PM',
|
||||
},
|
||||
{
|
||||
name: 'Lunch',
|
||||
description: null,
|
||||
start: '12:00PM',
|
||||
end: '1:00PM',
|
||||
},
|
||||
{
|
||||
name: 'Gordon Sanderson',
|
||||
description: 'SkyNet is coming',
|
||||
start: '1:00PM',
|
||||
end: '2:00PM',
|
||||
},
|
||||
{
|
||||
name: 'Kimberly Parsons',
|
||||
description: 'Dark patterns for the metaverse',
|
||||
start: '2:00PM',
|
||||
end: '3:00PM',
|
||||
},
|
||||
{
|
||||
name: 'Richard Astley',
|
||||
description: 'Knowing the game and playing it',
|
||||
start: '3:00PM',
|
||||
end: '4:00PM',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function ScheduleTabbed() {
|
||||
let [tabOrientation, setTabOrientation] = useState('horizontal')
|
||||
|
||||
useEffect(() => {
|
||||
let smMediaQuery = window.matchMedia('(min-width: 640px)')
|
||||
|
||||
function onMediaQueryChange({ matches }) {
|
||||
setTabOrientation(matches ? 'vertical' : 'horizontal')
|
||||
}
|
||||
|
||||
onMediaQueryChange(smMediaQuery)
|
||||
smMediaQuery.addEventListener('change', onMediaQueryChange)
|
||||
|
||||
return () => {
|
||||
smMediaQuery.removeEventListener('change', onMediaQueryChange)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<TabGroup
|
||||
className="mx-auto grid max-w-2xl grid-cols-1 gap-y-6 sm:grid-cols-2 lg:hidden"
|
||||
vertical={tabOrientation === 'vertical'}
|
||||
>
|
||||
<TabList className="-mx-4 flex gap-x-4 gap-y-10 overflow-x-auto pb-4 pl-4 sm:mx-0 sm:flex-col sm:pb-0 sm:pl-0 sm:pr-8">
|
||||
{({ selectedIndex }) => (
|
||||
<>
|
||||
{schedule.map((day, dayIndex) => (
|
||||
<div
|
||||
key={day.dateTime}
|
||||
className={clsx(
|
||||
'relative w-3/4 flex-none pr-4 sm:w-auto sm:pr-0',
|
||||
dayIndex !== selectedIndex && 'opacity-70',
|
||||
)}
|
||||
>
|
||||
<DaySummary
|
||||
day={{
|
||||
...day,
|
||||
date: (
|
||||
<Tab className="ui-not-focus-visible:outline-none">
|
||||
<span className="absolute inset-0" />
|
||||
{day.date}
|
||||
</Tab>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
{schedule.map((day) => (
|
||||
<TabPanel
|
||||
key={day.dateTime}
|
||||
className="ui-not-focus-visible:outline-none"
|
||||
>
|
||||
<TimeSlots day={day} />
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
)
|
||||
}
|
||||
|
||||
function DaySummary({ day }) {
|
||||
return (
|
||||
<>
|
||||
<h3 className="text-2xl font-semibold tracking-tight text-blue-900">
|
||||
<time dateTime={day.dateTime}>{day.date}</time>
|
||||
</h3>
|
||||
<p className="mt-1.5 text-base tracking-tight text-blue-900">
|
||||
{day.summary}
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function TimeSlots({ day, className }) {
|
||||
return (
|
||||
<ol
|
||||
role="list"
|
||||
className={clsx(
|
||||
className,
|
||||
'space-y-8 bg-white/60 px-10 py-14 text-center shadow-xl shadow-blue-900/5 backdrop-blur',
|
||||
)}
|
||||
>
|
||||
{day.timeSlots.map((timeSlot, timeSlotIndex) => (
|
||||
<li
|
||||
key={timeSlot.start}
|
||||
aria-label={`${timeSlot.name} talking about ${timeSlot.description} at ${timeSlot.start} - ${timeSlot.end} PST`}
|
||||
>
|
||||
{timeSlotIndex > 0 && (
|
||||
<div className="mx-auto mb-8 h-px w-48 bg-indigo-500/10" />
|
||||
)}
|
||||
<h4 className="text-lg font-semibold tracking-tight text-blue-900">
|
||||
{timeSlot.name}
|
||||
</h4>
|
||||
{timeSlot.description && (
|
||||
<p className="mt-1 tracking-tight text-blue-900">
|
||||
{timeSlot.description}
|
||||
</p>
|
||||
)}
|
||||
<p className="mt-1 font-mono text-sm text-slate-500">
|
||||
<time dateTime={`${day.dateTime}T${timeSlot.start}-08:00`}>
|
||||
{timeSlot.start}
|
||||
</time>{' '}
|
||||
-{' '}
|
||||
<time dateTime={`${day.dateTime}T${timeSlot.end}-08:00`}>
|
||||
{timeSlot.end}
|
||||
</time>{' '}
|
||||
PST
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
)
|
||||
}
|
||||
|
||||
function ScheduleStatic() {
|
||||
return (
|
||||
<div className="hidden lg:grid lg:grid-cols-3 lg:gap-x-8">
|
||||
{schedule.map((day) => (
|
||||
<section key={day.dateTime}>
|
||||
<DaySummary day={day} />
|
||||
<TimeSlots day={day} className="mt-10" />
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Schedule() {
|
||||
return (
|
||||
<section id="schedule" aria-label="Schedule" className="py-20 sm:py-32">
|
||||
<Container className="relative z-10">
|
||||
<div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-4xl lg:pr-24">
|
||||
<h2 className="font-display text-4xl font-medium tracking-tighter text-blue-600 sm:text-5xl">
|
||||
Our three day schedule is jam-packed with brilliant, creative, evil
|
||||
geniuses.
|
||||
</h2>
|
||||
<p className="mt-4 font-display text-2xl tracking-tight text-blue-900">
|
||||
The worst people in our industry giving the best talks you’ve ever
|
||||
seen. Nothing will be recorded and every attendee has to sign an NDA
|
||||
to watch the talks.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
<div className="relative mt-14 sm:mt-24">
|
||||
<BackgroundImage position="right" className="-bottom-32 -top-40" />
|
||||
<Container className="relative">
|
||||
<ScheduleTabbed />
|
||||
<ScheduleStatic />
|
||||
</Container>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
294
src/components/Speakers.jsx
Normal file
294
src/components/Speakers.jsx
Normal file
@@ -0,0 +1,294 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useId, useState } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { Container } from '@/components/Container'
|
||||
import { DiamondIcon } from '@/components/DiamondIcon'
|
||||
import andrewGreeneImage from '@/images/avatars/andrew-greene.jpg'
|
||||
import cathleneBurrageImage from '@/images/avatars/cathlene-burrage.jpg'
|
||||
import damarisKimuraImage from '@/images/avatars/damaris-kimura.jpg'
|
||||
import dianneGuilianelliImage from '@/images/avatars/dianne-guilianelli.jpg'
|
||||
import erhartCockrinImage from '@/images/avatars/erhart-cockrin.jpg'
|
||||
import giordanoSagucioImage from '@/images/avatars/giordano-sagucio.jpg'
|
||||
import gordonSandersonImage from '@/images/avatars/gordon-sanderson.jpg'
|
||||
import heatherTerryImage from '@/images/avatars/heather-terry.jpg'
|
||||
import ibrahimFraschImage from '@/images/avatars/ibrahim-frasch.jpg'
|
||||
import jaquelinIschImage from '@/images/avatars/jaquelin-isch.jpg'
|
||||
import kimberlyParsonsImage from '@/images/avatars/kimberly-parsons.jpg'
|
||||
import parkerJohnsonImage from '@/images/avatars/parker-johnson.jpg'
|
||||
import piersWilkinsImage from '@/images/avatars/piers-wilkins.jpg'
|
||||
import richardAstley from '@/images/avatars/richard-astley.jpg'
|
||||
import rinaldoBeynonImage from '@/images/avatars/rinaldo-beynon.jpg'
|
||||
import ronniCantadoreImage from '@/images/avatars/ronni-cantadore.jpg'
|
||||
import stevenMchailImage from '@/images/avatars/steven-mchail.jpg'
|
||||
import waylonHydenImage from '@/images/avatars/waylon-hyden.jpg'
|
||||
|
||||
const days = [
|
||||
{
|
||||
name: 'Opening Day',
|
||||
date: 'April 4',
|
||||
dateTime: '2022-04-04',
|
||||
speakers: [
|
||||
{
|
||||
name: 'Steven McHail',
|
||||
role: 'Designer at Globex Corporation',
|
||||
image: stevenMchailImage,
|
||||
},
|
||||
{
|
||||
name: 'Jaquelin Isch',
|
||||
role: 'UX Design at InGen',
|
||||
image: jaquelinIschImage,
|
||||
},
|
||||
{
|
||||
name: 'Dianne Guilianelli',
|
||||
role: 'General Manager at Initech',
|
||||
image: dianneGuilianelliImage,
|
||||
},
|
||||
{
|
||||
name: 'Ronni Cantadore',
|
||||
role: 'Design Engineer at Weyland-Yutani',
|
||||
image: ronniCantadoreImage,
|
||||
},
|
||||
{
|
||||
name: 'Erhart Cockrin',
|
||||
role: 'Product Lead at Cyberdyne Systems',
|
||||
image: erhartCockrinImage,
|
||||
},
|
||||
{
|
||||
name: 'Parker Johnson',
|
||||
role: 'UI Designer at MomCorp',
|
||||
image: parkerJohnsonImage,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Speakers & Workshops',
|
||||
date: 'April 5',
|
||||
dateTime: '2022-04-05',
|
||||
speakers: [
|
||||
{
|
||||
name: 'Damaris Kimura',
|
||||
role: 'Senior Engineer at OCP',
|
||||
image: damarisKimuraImage,
|
||||
},
|
||||
{
|
||||
name: 'Ibrahim Frasch',
|
||||
role: 'Programmer at Umbrella Corp',
|
||||
image: ibrahimFraschImage,
|
||||
},
|
||||
{
|
||||
name: 'Cathlene Burrage',
|
||||
role: 'Frontend Developer at Buy n Large',
|
||||
image: cathleneBurrageImage,
|
||||
},
|
||||
{
|
||||
name: 'Rinaldo Beynon',
|
||||
role: 'Data Scientist at Rekall',
|
||||
image: rinaldoBeynonImage,
|
||||
},
|
||||
{
|
||||
name: 'Waylon Hyden',
|
||||
role: 'DevOps at RDA Corporation',
|
||||
image: waylonHydenImage,
|
||||
},
|
||||
{
|
||||
name: 'Giordano Sagucio',
|
||||
role: 'Game Developer at Soylent Corp',
|
||||
image: giordanoSagucioImage,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Interviews',
|
||||
date: 'April 6',
|
||||
dateTime: '2022-04-06',
|
||||
speakers: [
|
||||
{
|
||||
name: 'Andrew Greene',
|
||||
role: 'Frontend Developer at Ultratech',
|
||||
image: andrewGreeneImage,
|
||||
},
|
||||
{
|
||||
name: 'Heather Terry',
|
||||
role: 'Backend Developer at Xanatos Enterprises',
|
||||
image: heatherTerryImage,
|
||||
},
|
||||
{
|
||||
name: 'Piers Wilkins',
|
||||
role: 'Full stack Developer at BiffCo',
|
||||
image: piersWilkinsImage,
|
||||
},
|
||||
{
|
||||
name: 'Gordon Sanderson',
|
||||
role: 'Mobile Developer at Cobra Industries',
|
||||
image: gordonSandersonImage,
|
||||
},
|
||||
{
|
||||
name: 'Kimberly Parsons',
|
||||
role: 'Game Developer at Tyrell Corporation',
|
||||
image: kimberlyParsonsImage,
|
||||
},
|
||||
{
|
||||
name: 'Richard Astley',
|
||||
role: 'CEO at Roll Out',
|
||||
image: richardAstley,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function ImageClipPaths({ id, ...props }) {
|
||||
return (
|
||||
<svg aria-hidden="true" width={0} height={0} {...props}>
|
||||
<defs>
|
||||
<clipPath id={`${id}-0`} clipPathUnits="objectBoundingBox">
|
||||
<path d="M0,0 h0.729 v0.129 h0.121 l-0.016,0.032 C0.815,0.198,0.843,0.243,0.885,0.243 H1 v0.757 H0.271 v-0.086 l-0.121,0.057 v-0.214 c0,-0.032,-0.026,-0.057,-0.057,-0.057 H0 V0" />
|
||||
</clipPath>
|
||||
<clipPath id={`${id}-1`} clipPathUnits="objectBoundingBox">
|
||||
<path d="M1,1 H0.271 v-0.129 H0.15 l0.016,-0.032 C0.185,0.802,0.157,0.757,0.115,0.757 H0 V0 h0.729 v0.086 l0.121,-0.057 v0.214 c0,0.032,0.026,0.057,0.057,0.057 h0.093 v0.7" />
|
||||
</clipPath>
|
||||
<clipPath id={`${id}-2`} clipPathUnits="objectBoundingBox">
|
||||
<path d="M1,0 H0.271 v0.129 H0.15 l0.016,0.032 C0.185,0.198,0.157,0.243,0.115,0.243 H0 v0.757 h0.729 v-0.086 l0.121,0.057 v-0.214 c0,-0.032,0.026,-0.057,0.057,-0.057 h0.093 V0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function Speakers() {
|
||||
let id = useId()
|
||||
let [tabOrientation, setTabOrientation] = useState('horizontal')
|
||||
|
||||
useEffect(() => {
|
||||
let lgMediaQuery = window.matchMedia('(min-width: 1024px)')
|
||||
|
||||
function onMediaQueryChange({ matches }) {
|
||||
setTabOrientation(matches ? 'vertical' : 'horizontal')
|
||||
}
|
||||
|
||||
onMediaQueryChange(lgMediaQuery)
|
||||
lgMediaQuery.addEventListener('change', onMediaQueryChange)
|
||||
|
||||
return () => {
|
||||
lgMediaQuery.removeEventListener('change', onMediaQueryChange)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section
|
||||
id="speakers"
|
||||
aria-labelledby="speakers-title"
|
||||
className="py-20 sm:py-32"
|
||||
>
|
||||
<ImageClipPaths id={id} />
|
||||
<Container>
|
||||
<div className="mx-auto max-w-2xl lg:mx-0">
|
||||
<h2
|
||||
id="speakers-title"
|
||||
className="font-display text-4xl font-medium tracking-tighter text-blue-600 sm:text-5xl"
|
||||
>
|
||||
Speakers
|
||||
</h2>
|
||||
<p className="mt-4 font-display text-2xl tracking-tight text-blue-900">
|
||||
Learn from the experts on the cutting-edge of deception at the most
|
||||
sinister companies.
|
||||
</p>
|
||||
</div>
|
||||
<TabGroup
|
||||
className="mt-14 grid grid-cols-1 items-start gap-x-8 gap-y-8 sm:mt-16 sm:gap-y-16 lg:mt-24 lg:grid-cols-4"
|
||||
vertical={tabOrientation === 'vertical'}
|
||||
>
|
||||
<div className="relative -mx-4 flex overflow-x-auto pb-4 sm:mx-0 sm:block sm:overflow-visible sm:pb-0">
|
||||
<div className="absolute bottom-0 left-0.5 top-2 hidden w-px bg-slate-200 lg:block" />
|
||||
<TabList className="grid auto-cols-auto grid-flow-col justify-start gap-x-8 gap-y-10 whitespace-nowrap px-4 sm:mx-auto sm:max-w-2xl sm:grid-cols-3 sm:px-0 sm:text-center lg:grid-flow-row lg:grid-cols-1 lg:text-left">
|
||||
{({ selectedIndex }) => (
|
||||
<>
|
||||
{days.map((day, dayIndex) => (
|
||||
<div key={day.dateTime} className="relative lg:pl-8">
|
||||
<DiamondIcon
|
||||
className={clsx(
|
||||
'absolute left-[-0.5px] top-[0.5625rem] hidden h-1.5 w-1.5 overflow-visible lg:block',
|
||||
dayIndex === selectedIndex
|
||||
? 'fill-blue-600 stroke-blue-600'
|
||||
: 'fill-transparent stroke-slate-400',
|
||||
)}
|
||||
/>
|
||||
<div className="relative">
|
||||
<div
|
||||
className={clsx(
|
||||
'font-mono text-sm',
|
||||
dayIndex === selectedIndex
|
||||
? 'text-blue-600'
|
||||
: 'text-slate-500',
|
||||
)}
|
||||
>
|
||||
<Tab className="ui-not-focus-visible:outline-none">
|
||||
<span className="absolute inset-0" />
|
||||
{day.name}
|
||||
</Tab>
|
||||
</div>
|
||||
<time
|
||||
dateTime={day.dateTime}
|
||||
className="mt-1.5 block text-2xl font-semibold tracking-tight text-blue-900"
|
||||
>
|
||||
{day.date}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</TabList>
|
||||
</div>
|
||||
<TabPanels className="lg:col-span-3">
|
||||
{days.map((day) => (
|
||||
<TabPanel
|
||||
key={day.dateTime}
|
||||
className="grid grid-cols-1 gap-x-8 gap-y-10 ui-not-focus-visible:outline-none sm:grid-cols-2 sm:gap-y-16 md:grid-cols-3"
|
||||
unmount={false}
|
||||
>
|
||||
{day.speakers.map((speaker, speakerIndex) => (
|
||||
<div key={speakerIndex}>
|
||||
<div className="group relative h-[17.5rem] transform overflow-hidden rounded-4xl">
|
||||
<div
|
||||
className={clsx(
|
||||
'absolute bottom-6 left-0 right-4 top-0 rounded-4xl border transition duration-300 group-hover:scale-95 xl:right-6',
|
||||
[
|
||||
'border-blue-300',
|
||||
'border-indigo-300',
|
||||
'border-sky-300',
|
||||
][speakerIndex % 3],
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className="absolute inset-0 bg-indigo-50"
|
||||
style={{ clipPath: `url(#${id}-${speakerIndex % 3})` }}
|
||||
>
|
||||
<Image
|
||||
className="absolute inset-0 h-full w-full object-cover transition duration-300 group-hover:scale-110"
|
||||
src={speaker.image}
|
||||
alt=""
|
||||
priority
|
||||
sizes="(min-width: 1280px) 17.5rem, (min-width: 1024px) 25vw, (min-width: 768px) 33vw, (min-width: 640px) 50vw, 100vw"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="mt-8 font-display text-xl font-bold tracking-tight text-slate-900">
|
||||
{speaker.name}
|
||||
</h3>
|
||||
<p className="mt-1 text-base tracking-tight text-slate-500">
|
||||
{speaker.role}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
40
src/components/Sponsors.jsx
Normal file
40
src/components/Sponsors.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import Image from 'next/image'
|
||||
|
||||
import { Container } from '@/components/Container'
|
||||
import logoLaravel from '@/images/logos/laravel.svg'
|
||||
import logoMirage from '@/images/logos/mirage.svg'
|
||||
import logoStatamic from '@/images/logos/statamic.svg'
|
||||
import logoStaticKit from '@/images/logos/statickit.svg'
|
||||
import logoTransistor from '@/images/logos/transistor.svg'
|
||||
import logoTuple from '@/images/logos/tuple.svg'
|
||||
|
||||
const sponsors = [
|
||||
{ name: 'Transistor', logo: logoTransistor },
|
||||
{ name: 'Tuple', logo: logoTuple },
|
||||
{ name: 'StaticKit', logo: logoStaticKit },
|
||||
{ name: 'Mirage', logo: logoMirage },
|
||||
{ name: 'Laravel', logo: logoLaravel },
|
||||
{ name: 'Statamic', logo: logoStatamic },
|
||||
]
|
||||
|
||||
export function Sponsors() {
|
||||
return (
|
||||
<section id="sponsors" aria-label="Sponsors" className="py-20 sm:py-32">
|
||||
<Container>
|
||||
<h2 className="mx-auto max-w-2xl text-center font-display text-4xl font-medium tracking-tighter text-blue-900 sm:text-5xl">
|
||||
Current sponsorships for our workshops and speakers.
|
||||
</h2>
|
||||
<div className="mx-auto mt-20 grid max-w-max grid-cols-1 place-content-center gap-x-32 gap-y-12 sm:grid-cols-3 md:gap-x-16 lg:gap-x-32">
|
||||
{sponsors.map((sponsor) => (
|
||||
<div
|
||||
key={sponsor.name}
|
||||
className="flex items-center justify-center"
|
||||
>
|
||||
<Image src={sponsor.logo} alt={sponsor.name} unoptimized />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user