5 Commits
Author SHA1 Message Date
Felicio Mununga fd6c9dddf6 hotfix: copy 2025-02-28 13:14:42 +01:00
Felicio Mununga deef802fd0 hotfix: copy 2025-02-28 11:04:59 +01:00
Jakub Kotula 200f6b63bd Remove tooltips (#44) 2025-02-27 11:07:53 +01:00
Jakub Kotula 79773c0b1e Fix accordion chevron hover (#42) 2025-02-26 12:00:31 +01:00
Jakub Kotula d9ce1f3b56 v2 (#40) 2025-02-25 16:26:03 +01:00
66 changed files with 1527 additions and 171 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ In this regard, we collect and process the following personal data from your use
* **IP address**: As part of such use of the Website we briefly process your IP address but we have no way of identifying you. We however have a legitimate interest in processing such IP addresses to ensure the technical functionality and enhance the security measures of the Website. This IP address is not stored by us over time.
* **Collection of email addresses**: We only collect your email address on our website for the purpose of sending you information in relation to the development of and the potential sale of Keycard Pro. Your email address is securely stored on our self-hosted servers, and we have implemented measures to protect such personal data by securing it with appropriate technical and organisational measures. We will not share such personal data to third parties without your consent, except as required by law. We will only collect and process your email address on the basis of you providing your consent for us to do so. We will retain your email address for as long as necessary to provide you with updates about Keycard Pro, unless you withdraw your consent or request deletion earlier
* **Collection of email addresses**: We only collect your email address on our website for the purpose of sending you information in relation to the development of and the potential sale of Keycard Shell. Your email address is securely stored on our self-hosted servers, and we have implemented measures to protect such personal data by securing it with appropriate technical and organisational measures. We will not share such personal data to third parties without your consent, except as required by law. We will only collect and process your email address on the basis of you providing your consent for us to do so. We will retain your email address for as long as necessary to provide you with updates about Keycard Shell, unless you withdraw your consent or request deletion earlier
## 3) Third party processing of personal data
Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 903 KiB

@@ -0,0 +1,118 @@
import { KEYCARD_PRODUCTS } from '~/app/_constants/shopify/products'
import { formatPrice } from '~/app/_utils/format-price'
import { Button } from '~components/button'
import { ButtonLink } from '~components/button-link'
import { BuyKeycardDialog } from '~components/buy-keycard-dialog'
import { GetNotifiedDialog } from '~components/get-notified-dialog'
import { Image } from '~components/image'
import { cx } from 'cva'
type Props = {
image: string
imageClassName?: string
name: string
secondName?: string
title: React.ReactNode
description: string
buttons: React.ReactNode
info: string
}
const Section = (props: Props) => {
const {
image,
name,
title,
description,
buttons,
info,
imageClassName,
secondName,
} = props
return (
<div className="relative w-full overflow-clip">
<div className="absolute inset-0 z-10 h-1/2 w-full bg-gradient-to-b from-dark-100 to-[transparent]" />
<Image
src={image}
alt={name}
width={900}
height={600}
draggable={false}
className={cx([
'pointer-events-none absolute left-1/2 z-20 max-w-[549px] -translate-x-1/2 select-none lg:top-6',
imageClassName,
])}
/>
<div className="flex size-full flex-col items-center justify-end rounded-28 border border-white-8 bg-white-4 px-5 pb-20 pt-[300px] lg:px-10 lg:pb-[120px] xl:px-[98px]">
<div className="container relative z-10 flex max-w-[434px] flex-col items-center justify-center px-2">
<p className="pb-2 text-24 font-600 text-white-95">
{name} <span className="font-200">{secondName}</span>
</p>
<h2 className="flex pb-4 text-center font-lora text-32 font-400 lg:text-48">
{title}
</h2>
<p className="pb-8 text-center text-20 font-300 text-white-80">
{description}
</p>
<div className="flex space-x-4">{buttons}</div>
<p className="pt-6 text-16 font-300 text-white-60">{info}</p>
</div>
</div>
</div>
)
}
const BuyCards = () => {
return (
<section className="grid grid-cols-1 gap-10 overflow-clip pb-2 pt-[160px] full-view-port lg:grid-cols-2 lg:flex-row lg:gap-2 lg:pt-[200px] lg:remove-full-view-port">
<Section
image="/assets/bottom-keycard.png"
name="keycard"
title={
<>
A card shaped <br /> hardware wallet
</>
}
description="Store and trade your crypto in a simple, secure and slim hardware wallet."
buttons={
<>
<BuyKeycardDialog>
<Button variant="primary">Buy Keycard</Button>
</BuyKeycardDialog>
<ButtonLink href="/keycard" variant="secondary">
Learn more
</ButtonLink>
</>
}
info={`From ${formatPrice({
amount: KEYCARD_PRODUCTS.ONE_CARD_SET.price,
})}`}
/>
<Section
image="/assets/bottom-keycard-shell.png"
imageClassName="pt-10 lg:pt-0 lg:-mb-20 lg:w-full"
name="keycard"
secondName="shell"
title={
<>
One device for all <br /> your Keycards
</>
}
description="Unrivalled security with an infinite number of removable Keycards, each with their own key."
buttons={
<>
<GetNotifiedDialog>
<Button>Get notified</Button>
</GetNotifiedDialog>
<ButtonLink href="/keycard-shell" variant="secondary">
Learn more
</ButtonLink>
</>
}
info="Coming 2025 &middot; Bundled with Keycard"
/>
</section>
)
}
export { BuyCards }
@@ -13,24 +13,26 @@ import { createElement } from 'react'
const features: Array<Feature> = [
{
name: 'Competitive pricing',
keycardShell: {
label: 'TBD',
},
keycard: {
label: formatPrice({
amount: KEYCARD_PRODUCTS.ONE_CARD_SET.price,
}),
},
tangem: {
label: formatPrice({
amount: 50,
}),
label: '€50',
},
ledger: {
label: formatPrice({
amount: 80,
}),
label: '€80',
},
},
{
name: 'Compatible with Ethereum and EVM-chains',
name: 'EVM chain compatible',
keycardShell: {
featured: true,
},
keycard: {
featured: true,
},
@@ -42,14 +44,39 @@ const features: Array<Feature> = [
},
},
{
name: 'Hardware-level protection',
// tooltip: 'Security certification level',
name: 'Bitcoin compatible',
keycardShell: {
featured: true,
},
keycard: {
featured: true,
},
tangem: {
featured: true,
},
ledger: {
featured: true,
},
},
{
name: 'Hardware level protection',
keycardShell: {
badge: {
text: 'EAL 6+',
gradient: true,
icon: FirmwareIcon,
},
},
keycard: { badge: { text: 'EAL 6+', gradient: true, icon: FirmwareIcon } },
tangem: { badge: { text: 'EAL 6+', gradient: true, icon: FirmwareIcon } },
ledger: { badge: { text: 'EAL 5+' } },
},
{
name: 'Mobile friendly',
tooltip: 'Compatible with mobile devices',
keycardShell: {
featured: true,
},
keycard: {
featured: true,
},
@@ -61,34 +88,25 @@ const features: Array<Feature> = [
},
},
{
name: 'Works on desktop',
name: 'Unlimited master keys',
keycardShell: {
featured: true,
},
keycard: {
featured: true,
},
tangem: {
featured: false,
},
ledger: {
featured: true,
},
},
{
name: 'Many cards, many keys',
keycard: {
featured: true,
// tooltip: 'No limit on master keys',
},
tangem: {
featured: true,
// tooltip: 'No limit on master keys',
},
ledger: {
featured: false,
},
// tooltip: 'No limit on master keys',
},
{
name: 'Easy back up of master keys',
keycardShell: {
featured: true,
},
keycard: {
featured: true,
},
@@ -101,7 +119,10 @@ const features: Array<Feature> = [
},
{
name: 'Open ecosystem',
// tooltip: 'Part of an open ecosystem',
tooltip: 'Part of an open ecosystem',
keycardShell: {
featured: true,
},
keycard: {
featured: true,
},
@@ -109,14 +130,17 @@ const features: Array<Feature> = [
featured: false,
},
ledger: {
featured: true,
featured: false,
},
},
{
name: 'Immutable, open-source secure element',
keycard: {
name: 'Open source secure element',
keycardShell: {
featured: true,
},
keycard: {
featured: false,
},
tangem: {
featured: false,
},
@@ -124,6 +148,130 @@ const features: Array<Feature> = [
featured: false,
},
},
{
name: "Secure element can't be upgraded",
keycardShell: {
featured: true,
},
keycard: {
featured: false,
},
tangem: {
featured: false,
},
ledger: {
featured: false,
},
},
{
name: 'QR transaction signing with any wallet',
tooltip: 'Sign transactions using QR codes',
keycardShell: {
featured: true,
},
keycard: {
featured: false,
},
tangem: {
featured: false,
},
ledger: {
featured: false,
},
},
{
name: 'Airgap capable',
tooltip: 'Can operate without direct connection',
keycardShell: {
featured: true,
},
keycard: {
featured: false,
},
tangem: {
featured: false,
},
ledger: {
featured: false,
},
},
{
name: 'Modular architecture',
keycardShell: {
featured: true,
},
keycard: {
featured: false,
},
tangem: {
featured: false,
},
ledger: {
featured: false,
},
},
{
name: 'Secure element has its own usages',
keycardShell: {
featured: true,
},
keycard: {
featured: 'n/a',
},
tangem: {
featured: 'n/a',
},
ledger: {
featured: false,
},
},
{
name: 'Can be used with future signing schemes',
tooltip: 'Future-proof design',
keycardShell: {
featured: true,
},
keycard: {
featured: 'n/a',
},
tangem: {
featured: 'n/a',
},
ledger: {
featured: false,
},
},
{
name: 'Removable battery',
tooltip: 'Battery can be replaced',
keycardShell: {
featured: true,
},
keycard: {
featured: 'n/a',
},
tangem: {
featured: 'n/a',
},
ledger: {
featured: false,
},
},
{
name: 'USBC + Camera',
keycardShell: {
featured: true,
},
keycard: {
featured: 'n/a',
},
tangem: {
featured: 'n/a',
},
ledger: {
featured: false,
},
},
] as const
type FeatureVariant = {
@@ -140,6 +288,7 @@ type FeatureVariant = {
type Feature = {
name: string
tooltip?: string
keycardShell: FeatureVariant
keycard: FeatureVariant
tangem: FeatureVariant
ledger: FeatureVariant
@@ -156,8 +305,7 @@ const FeatureInfo = ({
if (variant.featured === false)
return <RemoveIcon className="size-4 text-white-60" />
if (variant.featured === 'n/a') return <p className="text-white-60">n/a</p>
if (variant.label)
return <p className="font-400 text-white-95">{variant.label}</p>
if (variant.label) return <p className="text-white-95">{variant.label}</p>
if (variant.badge) {
return (
<Tag
@@ -171,36 +319,24 @@ const FeatureInfo = ({
}
}
const TooltipInfo = ({
variant,
}: {
variant: FeatureVariant | undefined | string
}) => {
if (typeof variant === 'string') return null
if (!variant || !variant.tooltip) return null
return (
<Tooltip label={variant.tooltip || ''}>
<div className="absolute left-[calc(50%+10px)] flex">
<InfoIcon className="ml-2 flex-shrink-0 text-white-40 transition-colors hover:text-white-60" />
</div>
</Tooltip>
)
}
const ComparisonTable = () => {
const products: Array<keyof Feature> = ['keycard', 'tangem', 'ledger']
const products: Array<keyof Feature> = [
'keycardShell',
'keycard',
'tangem',
'ledger',
]
return (
<section className="mx-auto max-w-[1352px] pt-[120px] text-white-95 lg:pt-[200px]">
<h2 className="px-3 font-lora text-32 text-white-95 min-[1512px]:px-0">
Going one step further
Going one step further.
</h2>
<div className="overflow-x-auto px-3 scrollbar-none min-[1512px]:px-0">
<div className="w-[684px] pt-14 md:w-full lg:w-full lg:pt-20">
<div className="grid grid-cols-[1.33fr,1fr,1fr,1fr] justify-items-center px-6 text-center font-lora text-24 font-400 text-white-90 lg:grid-cols-[2fr,1fr,1fr,1fr] lg:text-24">
<div className="grid grid-cols-[1.33fr,1fr,1fr,1fr,1fr] justify-items-center text-center font-lora text-24 font-400 text-white-90 lg:grid-cols-[2fr,1fr,1fr,1fr,1fr] lg:text-24">
<div className="justify-self-start p-4 pb-5 pl-6">Feature</div>
<div className="p-4 pb-5">Shell</div>
<div className="p-4 pb-5">Keycard</div>
<div className="p-4 pb-5">Tangem</div>
<div className="p-4 pb-5">Ledger</div>
@@ -211,7 +347,7 @@ const ComparisonTable = () => {
return (
<div
key={index}
className="grid grid-cols-[1.33fr,1fr,1fr,1fr] border-b border-dashed border-white-12 font-300 first:pt-2 last:border-b-0 last:pb-2 lg:grid-cols-[2fr,1fr,1fr,1fr]"
className="grid grid-cols-[1.33fr,1fr,1fr,1fr,1fr] border-b border-dashed border-white-12 font-300 first:pt-2 last:border-b-0 last:pb-2 lg:grid-cols-[2fr,1fr,1fr,1fr,1fr]"
>
<div className="w-full p-5 first:pl-0 lg:flex lg:items-center">
<span className="relative inline whitespace-normal">
@@ -232,7 +368,6 @@ const ComparisonTable = () => {
className="relative flex items-center justify-center last:pr-0"
>
<FeatureInfo variant={feature[product]} />
<TooltipInfo variant={feature[product]} />
</div>
))}
</div>
@@ -52,10 +52,11 @@ const Faqs = () => {
<div className="flex w-full flex-1 flex-col lg:w-auto">
<div className="flex justify-center pt-[120px] lg:hidden">
<Image
src="/assets/faqs-v1.png"
src="/assets/faqs-mobile.png"
alt="FAQ Background"
width={264}
height={488}
width={736}
height={736}
className="w-full"
/>
</div>
<h2 className="mt-14 font-lora text-32 text-white-95 lg:mt-0">
@@ -82,7 +83,7 @@ const Faqs = () => {
)}
>
{faq.question}
<div className="rounded-[10px] border border-white-12 bg-white-8 p-[6px]">
<div className="rounded-[10px] border border-white-12 bg-white-8 p-[6px] hover:border-white-20 hover:bg-white-12">
<ChevronDownIcon className="text-white-95 transition-transform duration-300 ease-[cubic-bezier(0.87,_0,_0.13,_1)] group-data-[state=open]:rotate-180" />
</div>
</Accordion.Trigger>
@@ -98,14 +99,15 @@ const Faqs = () => {
))}
</Accordion.Root>
</div>
<div className="flex w-full flex-col lg:w-auto lg:items-center">
<div className="flex flex-1 flex-col lg:w-auto lg:items-end">
<div className="relative hidden lg:block">
<div className="absolute right-[-245px] top-[-20px] z-10 h-[400px] w-full bg-gradient-to-tr from-[transparent] via-dark-100 via-[83%] to-dark-100" />
<Image
src="/assets/faqs-v1.png"
src="/assets/faqs.png"
alt="FAQ Background"
height={488}
width={264}
className="relative"
height={790}
width={1010}
className="relative right-[-245px] top-[-40px]"
/>
</div>
@@ -115,7 +117,8 @@ const Faqs = () => {
Still have questions?
</p>
<p className="text-16 font-300 text-white-80">
Reach out to our team or engage with us on X.
Reach out to our team or engage with our community on Discord or
X.
</p>
</div>
<div className="flex gap-3">
@@ -1,36 +1,55 @@
import { KEYCARD_PRODUCTS } from '~/app/_constants/shopify/products'
import { formatPrice } from '~/app/_utils/format-price'
import { Background } from '~components/3d/background'
import { Button } from '~components/button'
import { ButtonLink } from '~components/button-link'
import { BuyKeycardDialog } from '~components/buy-keycard-dialog'
import { Image } from '~components/image'
const Hero = () => {
return (
<section className="relative flex h-svh overflow-y-clip full-view-port lg:remove-full-view-port 2xl:h-[839px]">
<Background variant="homepage" />
<div className="pointer-events-none relative z-20 flex flex-1 flex-col justify-end px-5 pb-5 align-bottom lg:pb-[43px] xl:px-20">
<div className="flex flex-col items-start justify-between lg:flex-row">
<div className="pointer-events-auto flex max-w-[664px] flex-col lg:self-end">
<h1 className="flex font-lora text-32 font-400 text-white-95 lg:text-44">
Lightweight design, <br /> heavyweight security
</h1>
<p className="max-w-[435px] pb-8 pt-4 text-20 font-300 text-white-80">
Trade swiftly and safely back up your assets with Keycard hardware
wallets.
</p>
<div className="flex items-center gap-5">
<BuyKeycardDialog>
<Button variant="primary">Buy Keycard</Button>
</BuyKeycardDialog>
<p className="text-16 font-300 text-white-60">
From{' '}
{formatPrice({
amount: KEYCARD_PRODUCTS.ONE_CARD_SET.price,
})}
</p>
</div>
</div>
<section className="relative flex h-[calc(100svh-80px)] flex-col justify-end overflow-clip rounded-t-28 border border-white-8 bg-white-4 px-0 pt-0 full-view-port lg:h-auto lg:px-[72px] lg:py-20 lg:remove-full-view-port">
<Image
alt="Keycard"
src="/assets/hero-bg.png"
width={1600}
height={1600}
className="absolute bottom-0 right-0 z-0 hidden w-auto bg-cover bg-center lg:block"
priority
/>
<Image
alt="Keycard"
src="/assets/hero-bg-mobile.png"
width={1600}
height={1600}
className="absolute right-0 top-0 block w-full bg-cover bg-center lg:hidden"
priority
/>
<div className="relative z-10 flex max-w-[434px] flex-col px-5 lg:max-w-[500px] lg:pt-0">
<p className="pb-2 text-24 font-600 text-white-95">keycard</p>
<h1 className="flex pb-8 font-lora text-32 font-400 lg:pb-4 lg:text-48">
Lightweight design
<br />
heavyweight security
</h1>
<p className="pb-8 text-20 font-300 text-white-80">
Trade swiftly and safely back up your assets with Keycard hardware
wallets.
</p>
<div className="flex space-x-4">
<BuyKeycardDialog>
<Button variant="primary">Buy Keycard</Button>
</BuyKeycardDialog>
<ButtonLink href="/keycard" variant="secondary">
Learn more
</ButtonLink>
</div>
<p className="pb-8 pt-6 text-16 font-300 text-white-60 lg:pb-0">
From{' '}
{formatPrice({
amount: KEYCARD_PRODUCTS.ONE_CARD_SET.price,
})}
</p>
</div>
</section>
)
@@ -0,0 +1,140 @@
import { FirmwareIcon } from '@status-im/icons/20'
import { GithubIcon } from '@status-im/icons/social'
import { ButtonLink } from '~components/button-link'
import { Image } from '~components/image'
import { Tag } from '~components/tag'
import { InfinityIcon } from '~icons/infinity'
import { cx } from 'cva'
import { createElement } from 'react'
const FEATURES = [
{
title: 'Best-in-class security',
description:
'Keycard secure element has the highest level of certification provided by Common Criteria.',
image: '/assets/feature-keycard.png',
imageClassName: '!justify-start',
badge: { icon: FirmwareIcon, text: 'EAL 6+', gradient: true },
className: 'row-span-2 col-span-1',
},
{
title: 'Fully open source',
description:
'Open-source code on an open framework, making it the most open way to design a secure element.',
button: { icon: GithubIcon, text: 'View on GitHub' },
className: 'col-span-1',
},
{
title: 'Airgapped',
description:
'Through Keycard Shells camera or Keycards contactless nature, our products can be airgapped.',
image: '/assets/feature-keycard-shell.png',
badge: { text: '0 cables' },
className: 'row-span-2 col-span-1 flex-col-reverse',
},
{
title: 'Made to last',
description:
'Your keycard has a life expectancy of 25+ years, resists water and dust. It will still securely store your keys.',
badge: { text: '25+ years' },
className: 'col-span-1',
},
{
title: 'Easy to backup',
description:
'Securely store your private key on multiple cards. Encrypted, instead of the typical piece of paper.',
badge: { icon: InfinityIcon, text: 'backups' },
className: 'col-span-1',
},
{
title: 'Discreet',
description:
'With its light, small and discreet form factor your Keycard can go unnoticed in your wallet.',
badge: { text: 'Credit card format' },
className: 'col-span-1',
},
]
const UnderlinedWord = ({ children }: { children: React.ReactNode }) => (
<div className="relative inline-block">
<span className="relative z-10 [text-shadow:_-3px_2px_black,_4px_1px_black,_2px_0px_black,_3px_2px_black]">
{children}
</span>
<div className="absolute bottom-1 left-0 -z-10 h-px w-full bg-orange" />
</div>
)
const KeycardFeatures = () => {
return (
<section className="max-w-[1352px] px-3 pt-[120px] lg:mx-auto lg:pt-[200px] min-[1512px]:px-0">
<h2 className="max-w-[665px] font-lora text-32 text-white-95">
Join the <UnderlinedWord>open source</UnderlinedWord> revolution of the
most <UnderlinedWord>modular</UnderlinedWord> and{' '}
<UnderlinedWord>future proof</UnderlinedWord> hardware wallet system
ever conceived.
</h2>
<div className="grid grid-cols-1 gap-5 pt-20 lg:grid-cols-4 lg:gap-6">
{FEATURES.map((feature, index) => (
<div
key={index}
className={cx([
'relative flex flex-col justify-between overflow-clip rounded-28 bg-white-4 outline outline-1 outline-white-8',
feature.className,
])}
>
{feature.image && (
<div
className={cx([
'flex items-center justify-center',
feature.imageClassName,
])}
>
<Image
src={feature.image}
alt={feature.title}
className="rounded-12 object-cover"
width={500}
height={500}
/>
</div>
)}
<div className="p-6 pt-5">
<div>
<h3 className="mb-[6px] font-lora text-24 font-400">
{feature.title}
</h3>
{feature.description && (
<p className="mb-6 text-16 font-300 text-white-60">
{feature.description}
</p>
)}
{feature.badge && (
<Tag
gradient={feature.badge.gradient}
icon={
feature.badge.icon && createElement(feature.badge.icon)
}
>
{feature.badge.text}
</Tag>
)}
{feature.button && (
<ButtonLink
href="https://github.com/keycard-tech/keycard-pro"
className="font-500 [&_path]:hover:fill-white-dark"
variant="white"
icon={<feature.button.icon />}
>
{feature.button.text}
</ButtonLink>
)}
</div>
</div>
</div>
))}
</div>
</section>
)
}
export { KeycardFeatures }
@@ -1,42 +1,56 @@
import { Button } from '~components/button'
import { ButtonLink } from '~components/button-link'
import { GetNotifiedDialog } from '~components/get-notified-dialog'
import { Image } from '~components/image'
const KeycardShell = () => {
return (
<section className="relative mx-0 mt-[160px] flex flex-col items-center overflow-hidden rounded-28 border border-white-12 lg:mx-6 lg:mt-[200px] lg:flex-row lg:justify-end xl:mx-20">
<Image
src="/assets/bg-keycard-v1.png"
alt="Keycard Shell Hardware Wallet"
width={1400}
height={800}
className="absolute left-0 top-0 hidden size-full object-cover lg:block"
/>
<section className="relative flex h-[calc(100svh-80px)] flex-col justify-end overflow-hidden rounded-b-28 border border-t-0 border-white-8 bg-white-4 backdrop-blur-[20px] full-view-port lg:h-auto lg:flex-row lg:items-center lg:justify-normal lg:remove-full-view-port">
<div className="hidden max-h-[510px] flex-1 items-start justify-center overflow-hidden lg:flex">
<Image
src="/assets/keycard-shell.png"
alt="Keycard Shell Hardware Wallet"
width="758"
height="758"
className="relative left-0 top-[-86px] object-cover object-center xl:top-[-128px] xl:object-contain"
priority
/>
</div>
<Image
src="/assets/bg-keycard-mobile-v1.png"
src="/assets/keycard-shell-mobile.png"
alt="Keycard Shell Hardware Wallet"
width={414}
height={614}
className="relative left-0 top-0 block aspect-[414/614] w-full lg:hidden"
width="900"
height="600"
className="self-end lg:hidden"
priority
/>
<div className="absolute right-0 top-0 hidden h-full w-[270px] bg-gradient-to-r from-[transparent] to-dark-100 lg:block" />
<div className="relative z-10 mt-[-53%] flex w-full flex-col p-6 pt-0 lg:mt-0 lg:w-auto lg:py-[256px] lg:pr-[229px]">
<p className="flex min-w-[320px] pb-4 font-lora text-32 font-400">
Get ready for the
<br />
future of modular
<br />
hardware security
<div className="relative z-10 -mt-16 flex max-w-[506px] flex-col px-5 pb-8 pt-0 lg:mt-0 lg:py-20 lg:pl-0 lg:pr-[72px]">
<p className="pb-2 text-24 font-600 text-white-95">
keycard <span className="font-200">shell</span>
</p>
<p className="min-w-[320px] pb-8 text-16 font-300 text-white-60">
Revealing soon. Coming 2025
<p className="flex pb-8 font-lora text-32 font-400 lg:pb-4 lg:text-48">
One secure device,
<br />
infinite backups
</p>
<p className="pb-8 text-20 font-300 text-white-80">
Unrivalled security with an infinite number of removable Keycards,
each with their own key.
</p>
<div className="flex gap-4">
<GetNotifiedDialog>
<Button>Get notified</Button>
</GetNotifiedDialog>
<ButtonLink href="/keycard-shell" variant="secondary">
Learn more
</ButtonLink>
</div>
<p className="flex items-center gap-2 pt-6 text-16 font-300 text-white-60">
Coming 2025 <span className="size-1 rounded-full bg-white-40" />{' '}
Bundled with Keycard
</p>
<GetNotifiedDialog>
<Button>Get notified</Button>
</GetNotifiedDialog>
</div>
</section>
)
@@ -2,6 +2,10 @@ type Props = {
children: React.ReactNode
}
export default function KeycardLayout({ children }: Props) {
return <div className="m-auto w-full max-w-[1512px]">{children}</div>
export default function HomepageLayout({ children }: Props) {
return (
<div className="relative m-auto mt-20 w-full max-w-[1512px] lg:mt-[92px]">
{children}
</div>
)
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

+5 -13
View File
@@ -1,15 +1,11 @@
import { Metadata } from '~/app/_metadata'
import { AutoOpenBuyKeycardDialog } from './_components/auto-open-buy-keycard-dialog'
import { BuyCards } from './_components/buy-cards'
import { ComparisonTable } from './_components/comparison-table'
import { Customisation } from './_components/customisation'
import { Design } from './_components/design'
import { Faqs } from './_components/faqs'
import { Hero } from './_components/hero'
import { Integration } from './_components/integration'
import { KeycardFeatures } from './_components/keycard-features'
import { KeycardShell } from './_components/keycard-shell'
import { Prefooter } from './_components/pre-footer'
import { SecurityFeatures } from './_components/security-features'
import { UseCases } from './_components/use-cases'
export const metadata = Metadata({
title: {
@@ -22,15 +18,11 @@ export default function HomePage() {
<>
<AutoOpenBuyKeycardDialog />
<Hero />
<SecurityFeatures />
<Design />
<Integration />
<UseCases />
<ComparisonTable />
<KeycardShell />
<Customisation />
<KeycardFeatures />
<ComparisonTable />
<Faqs />
<Prefooter />
<BuyCards />
</>
)
}
@@ -0,0 +1,102 @@
import { Image } from '~components/image'
const WALLETS = [
{
name: 'Metamask',
network: 'Ethereum',
image: '/assets/keycard-shell/wallets/metamask.png',
},
{
name: 'imToken',
network: 'Ethereum',
image: '/assets/keycard-shell/wallets/im-token.png',
},
{
name: 'Rabby',
network: 'Ethereum',
image: '/assets/keycard-shell/wallets/rabbit.png',
},
{
name: 'Backpack',
network: 'Ethereum',
image: '/assets/keycard-shell/wallets/back-pack.png',
},
{
name: 'UniSat',
network: 'Bitcoin',
image: '/assets/keycard-shell/wallets/uni-sat.png',
},
{
name: 'Blue',
network: 'Bitcoin',
image: '/assets/keycard-shell/wallets/blue.png',
},
{
name: 'Sparrow',
network: 'Bitcoin',
image: '/assets/keycard-shell/wallets/sparrow.png',
},
{
name: 'Specter',
network: 'Bitcoin',
image: '/assets/keycard-shell/wallets/specter.png',
},
]
const AirGapped = () => {
return (
<section className="relative -order-1 grid items-center overflow-y-clip px-3 pb-[498px] pt-[120px] lg:mb-[100px] lg:grid-cols-2 lg:pb-[100px]">
<div className="relative order-last lg:order-first">
<div className="absolute right-1/4 h-[448px] translate-x-1/3 translate-y-14 lg:right-0 lg:h-[678px] lg:-translate-y-60 lg:translate-x-0">
<div className="absolute bottom-1 z-10 h-20 w-full bg-gradient-to-b from-[transparent] to-dark-100 lg:bottom-20 lg:h-64" />
<Image
alt="Hardware wallet device with QR display"
height={800}
width={1600}
className="w-[800px] max-w-[800px] lg:w-[1000px] lg:max-w-[1000px]"
src="/assets/keycard-shell/keycard-with-macbook.png"
/>
</div>
</div>
<div className="flex w-full flex-col justify-self-start lg:max-w-[549px] lg:pl-3 lg:pt-20">
<div>
<h2 className="mb-2 font-lora text-32">Fully airgapped</h2>
<p className="text-16 font-300 text-white-60">
Sign transactions on your favourite wallets through QR signing
</p>
</div>
<div className="mt-8 lg:mt-14">
<div className="mb-5 text-12 text-white-80">
TRANSACTION SIGNING WITH
</div>
<div className="grid grid-cols-2">
{WALLETS.map((wallet, index) => (
<div
key={index}
className="flex items-center space-x-3 border-b border-dashed border-white-20 py-3 even:border-l even:border-white-20 even:pl-3 [&:nth-child(-n+2)]:pt-0 [&:nth-last-child(-n+2)]:border-b-0 [&:nth-last-child(-n+2)]:pb-0"
>
<div className="flex size-10 items-center justify-center rounded-28 bg-dark-60">
<Image
src={wallet.image}
alt={`${wallet.name} icon`}
height={40}
width={40}
/>
</div>
<div>
<div className="font-500">{wallet.name}</div>
<div className="text-16 text-white-80">{wallet.network}</div>
</div>
</div>
))}
</div>
</div>
</div>
</section>
)
}
export { AirGapped }
@@ -0,0 +1,45 @@
import { Image } from '~components/image'
const Comparision = () => {
return (
<section className="mt-[120px] grid gap-0 overflow-clip rounded-28 border border-white-12 lg:mt-[200px] lg:grid-cols-2">
<div className="relative flex min-h-[480px] flex-col items-center justify-center lg:min-h-[840px]">
<Image
alt="Keycard Shell"
className="absolute left-0 top-0 z-0 size-full object-cover"
src="/assets/keycard-shell/comparision-left.png"
width={1480}
height={1680}
/>
<h2 className="absolute left-1/2 top-1/2 z-20 mx-auto w-full max-w-[328px] -translate-x-1/2 -translate-y-20 text-center font-lora text-32 lg:max-w-full">
Keep your device at home
</h2>
<p className="absolute bottom-8 mx-auto max-w-[423px] px-2 text-center text-16 font-300 text-white-80 lg:bottom-14">
Stored away on a drawer, or hiding in plain sight, Keycard Shell is
stateless without a Keycard.
</p>
</div>
<div className="relative flex min-h-[480px] flex-col items-center justify-center border-t border-white-12 md:border-l md:border-t-0 lg:min-h-[840px]">
<Image
alt="Keycard"
className="absolute left-0 top-0 size-full object-cover"
src="/assets/keycard-shell/comparision-right.png"
width={1480}
height={1680}
/>
<h2 className="absolute left-1/2 top-1/2 z-20 mx-auto w-full max-w-[328px] -translate-x-1/2 -translate-y-20 text-center font-lora text-32 lg:max-w-full">
Take your keys with you
</h2>
<p className="absolute bottom-8 mx-auto max-w-[434px] px-2 text-center text-16 font-300 text-white-80 lg:bottom-14">
Discreet and light, your Keycard fits perfectly in your wallet ready
to go, everywhere!
</p>
</div>
</section>
)
}
export { Comparision }
@@ -0,0 +1,77 @@
import { FirmwareIcon } from '@status-im/icons/20'
import { GithubIcon } from '@status-im/icons/social'
import { ButtonLink } from '~components/button-link'
import { Tag } from '~components/tag'
const FEATURES = [
{
name: 'Unparalleled security',
description: `Keycard's secure element has passed Common Criteria EAL6+ certification.`,
badge: 'EAL 6+',
},
{
name: 'Stateless',
description:
'Your information and private keys are never stored on the device.',
tags: ['Keys stored on Keycard'],
},
{
name: 'Clear security model',
description: `Your private keys will never leave Keycard's secure Element.`,
tags: ['bip-32', 'bip-39', 'bip-44'],
},
]
const FeaturesCard = () => {
return (
<section className="mx-9 px-3 pt-[144px] lg:pt-[48px]">
<div className="grid grid-cols-1 gap-8 rounded-28 border border-white-8 bg-white-4 p-6 pt-5 text-white-95 lg:grid-cols-2 lg:gap-6 lg:p-8 lg:pt-7 xl:grid-cols-4">
{FEATURES.map((feature, index) => (
<div key={index} className="flex flex-col">
<h3 className="pb-[6px] font-lora text-24 font-400">
{feature.name}
</h3>
<p className="pb-6 text-16 font-300 text-white-60">
{feature.description}
</p>
{Boolean(feature.badge) && (
<Tag gradient icon={<FirmwareIcon />}>
{feature.badge}
</Tag>
)}
{feature.tags && (
<div className="flex gap-2">
{feature.tags.map((tag, index) => (
<Tag key={index}>{tag}</Tag>
))}
</div>
)}
</div>
))}
<div className="flex flex-col gap-6">
<div>
<h2 className="pb-[6px] font-lora text-24 font-400 text-white-95">
Fully open source
</h2>
<p className="text-16 font-300 text-white-60">
Our software, hardware and construction are fully open source.
</p>
</div>
<ButtonLink
href="https://github.com/keycard-tech/keycard-pro"
className="font-500 [&_path]:hover:fill-white-dark"
variant="white"
icon={<GithubIcon />}
>
View on Github
</ButtonLink>
</div>
</div>
</section>
)
}
export { FeaturesCard }
@@ -0,0 +1,145 @@
import { Image } from '~components/image'
import { Tag } from '~components/tag'
import { Tooltip } from '~components/tooltip'
import { cx } from 'cva'
const features = [
{
title: 'Runs for ever',
description:
'With the use of a very common replaceable Nokia BL-4C battery.',
image: '/assets/keycard-shell/keycard-shell-feature-1.png',
badge: { text: '18 hours+' },
className: 'row-span-2 col-span-1 [&_img]:w-fit gap-0',
},
{
title: 'Ethereum support',
description: 'Works out of the box with your favourite Ethereum wallets.',
className: 'col-span-1',
icons: [
{
name: 'Metamask',
image: '/assets/keycard-shell/wallets/metamask.png',
},
{
name: 'Rabby',
image: '/assets/keycard-shell/wallets/rabbit.png',
},
{
name: 'imToken',
image: '/assets/keycard-shell/wallets/im-token.png',
},
{
name: 'Backpack',
image: '/assets/keycard-shell/wallets/back-pack.png',
},
],
},
{
title: 'Large display',
description:
'Verify the correctness of transaction data on the secure device.',
image: '/assets/keycard-shell/keycard-shell-feature-2.png',
badge: { text: '2 inches' },
className: 'row-span-2 col-span-1 flex-col-reverse gap-0 [&_img]:w-fit',
},
{
title: 'Air-gapped',
description:
'Send transactions through the air via QR code standard ERC-4527.',
badge: { text: 'ERC-4527' },
className: 'col-span-1',
},
{
title: 'Easy to backup',
description:
'Use multiple cards as backups, each protected by a unique PIN.',
badge: { text: '2 cards included with device' },
className: 'col-span-1',
},
{
title: 'Bitcoin support',
description:
'Works out of the box with your favourite Bitcoin wallets.',
className: 'col-span-1',
icons: [
{
name: 'Unisat',
image: '/assets/keycard-shell/wallets/uni-sat.png',
},
{
name: 'Sparrow',
image: '/assets/keycard-shell/wallets/sparrow.png',
},
{
name: 'Specter',
image: '/assets/keycard-shell/wallets/specter.png',
},
{
name: 'Blue',
image: '/assets/keycard-shell/wallets/blue.png',
},
],
},
]
const FeaturesGrid = () => {
return (
<section className="max-w-[1352px] px-3 pt-[120px] lg:mx-auto lg:pt-[200px] min-[1512px]:px-0">
<h2 className="max-w-[665px] font-lora text-32 text-white-95">
Convenient, secure and made to last
</h2>
<div className="grid grid-cols-1 gap-5 pt-14 lg:grid-cols-4 lg:gap-6">
{features.map((feature, index) => (
<div
key={index}
className={cx([
'relative flex flex-col justify-between overflow-clip rounded-28 bg-white-4 outline outline-1 outline-white-8',
feature.className,
])}
>
{feature.image && (
<Image
src={feature.image}
alt={feature.title}
className="w-full rounded-12"
width={640}
height={640}
/>
)}
<div className="p-6 pt-5">
<div>
<h3 className="mb-[6px] font-lora text-24 font-400">
{feature.title}
</h3>
<p className="mb-6 text-16 font-300 text-white-60">
{feature.description}
</p>
{feature.badge && <Tag>{feature.badge.text}</Tag>}
{feature.icons && (
<div className="flex items-center gap-3">
{feature.icons?.map((icon, index) => (
<Tooltip key={index} label={icon.name}>
<Image
src={icon.image}
alt={icon.name}
width={40}
height={40}
className="transition-opacity duration-200 hover:opacity-[80%]"
/>
</Tooltip>
))}
</div>
)}
</div>
</div>
</div>
))}
</div>
</section>
)
}
export { FeaturesGrid }
@@ -0,0 +1,144 @@
'use client'
import { ArrowLeftIcon, ArrowRightIcon } from '@status-im/icons/20'
import { Image } from '~components/image'
import { EmblaOptionsType } from 'embla-carousel'
import useEmblaCarousel from 'embla-carousel-react'
import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures'
import { useCallback, useEffect, useState } from 'react'
const features = [
{
name: 'Full visibility of seed phrase',
description:
'All the words clearly numbered and visible ready for you to write them down.',
image: '/assets/keycard-shell/keycard-shell-feature-card-1.png',
},
{
name: 'Human readable tx data',
description: 'Get a clear understanding of what youre actually signing.',
image: '/assets/keycard-shell/keycard-shell-feature-card-2.png',
},
{
name: 'Secure PIN for each card',
description:
'Switch your card, enter your pin, and youre ready to sign a transaction.',
image: '/assets/keycard-shell/keycard-shell-feature-card-3.png',
},
{
name: 'Compatible QR wallets',
description:
'Its embedded camera allows Keycard Shell to be used fully airgapped.',
image: '/assets/keycard-shell/keycard-shell-feature-card-4.png',
},
{
name: 'Block USB data transfer',
description:
'Add an extra layer of safety every time you connect your cable to charge.',
image: '/assets/keycard-shell/keycard-shell-feature-card-5.png',
},
{
name: 'Name your cards',
description: 'Easily identify your cards by giving them a unique name.',
image: '/assets/keycard-shell/keycard-shell-feature-card-6.png',
},
]
const FeaturesSlider = () => {
const options: EmblaOptionsType = {
align: 'start',
containScroll: 'trimSnaps',
dragFree: false,
}
const [emblaRef, emblaApi] = useEmblaCarousel(options, [
WheelGesturesPlugin(),
])
const [prevBtnEnabled, setPrevBtnEnabled] = useState(false)
const [nextBtnEnabled, setNextBtnEnabled] = useState(false)
const scrollPrev = useCallback(
() => emblaApi && emblaApi.scrollPrev(),
[emblaApi],
)
const scrollNext = useCallback(
() => emblaApi && emblaApi.scrollNext(),
[emblaApi],
)
const onSelect = useCallback(() => {
if (!emblaApi) return
setPrevBtnEnabled(emblaApi.canScrollPrev())
setNextBtnEnabled(emblaApi.canScrollNext())
}, [emblaApi])
useEffect(() => {
if (!emblaApi) return
onSelect()
emblaApi.on('select', onSelect)
emblaApi.on('reInit', onSelect)
return () => {
emblaApi.off('select', onSelect)
emblaApi.off('reInit', onSelect)
}
}, [emblaApi, onSelect])
return (
<div className="relative w-full pt-[130px] lg:pt-[200px]">
<div className="flex items-center justify-between">
<h2 className="max-w-[665px] px-3 font-lora text-32 text-white-95 lg:px-20">
Running on open source software{' '}
</h2>
<div className="hidden items-center gap-3 pr-20 lg:flex">
<button
className="rounded-12 border border-white-12 bg-white-8 p-[10px] text-white-95 opacity-[100%] transition-all hover:bg-white-12 disabled:opacity-[40%]"
disabled={!prevBtnEnabled}
onClick={scrollPrev}
aria-label="Previous item"
>
<ArrowLeftIcon />
</button>
<button
className="rounded-12 border border-white-12 bg-white-8 p-[10px] text-white-95 opacity-[100%] transition-all hover:bg-white-12 disabled:opacity-[40%]"
disabled={!nextBtnEnabled}
onClick={scrollNext}
aria-label="Next item"
>
<ArrowRightIcon />
</button>
</div>
</div>
<div className="relative w-full px-3 pt-14 lg:px-20" ref={emblaRef}>
<div className="flex justify-start gap-6">
{features.map((feature, index) => (
<div
key={index}
className="min-w-0 flex-[0_0_90%] select-none sm:flex-[0_0_320px]"
>
<div className="rounded-28 border border-white-8 bg-white-4 p-10">
<Image
src={feature.image}
alt={feature.name}
width={500}
height={500}
priority
/>
</div>
<div className="pt-8">
<p className="pb-[6px] text-left font-lora text-24 font-500 text-white-95">
{feature.name}
</p>
<p className="text-16 font-300 text-white-60">
{feature.description}
</p>
</div>
</div>
))}
</div>
</div>
</div>
)
}
export { FeaturesSlider }
@@ -0,0 +1,48 @@
import { Button } from '~components/button'
import { GetNotifiedDialog } from '~components/get-notified-dialog'
import { Image } from '~components/image'
const Hero = () => {
return (
<section className="relative flex h-svh overflow-y-clip full-view-port lg:remove-full-view-port 2xl:h-[839px]">
<Image
alt="Keycard Shell"
src="/assets/keycard-shell/bg-hero-keycard-shell.png"
width={986}
height={986}
priority
className="absolute left-10 top-[43%] z-0 h-auto w-fit -translate-y-1/2 scale-[120%] bg-contain bg-top bg-no-repeat md:scale-100 lg:left-[56%] lg:-translate-x-1/2"
/>
<div className="pointer-events-none relative z-20 flex flex-1 flex-col justify-end px-5 pb-5 align-bottom lg:pb-20 xl:px-20">
<div className="flex flex-col items-start justify-between lg:flex-row">
<div className="pointer-events-auto flex max-w-[664px] flex-col lg:self-end">
<p className="pb-2 text-24 font-600 text-white-95">
keycard <span className="font-200">shell</span>
</p>
<h1 className="flex font-lora text-32 font-400 text-white-95 lg:text-44">
One secure device,
<br />
infinite backups
</h1>
<p className="max-w-[435px] pb-8 pt-4 text-20 font-300 text-white-80">
Unrivalled security with an infinite number of removable Keycards,
each with their own key.
</p>
<div className="flex flex-col items-start gap-5 md:flex-row md:items-center">
<GetNotifiedDialog>
<Button variant="primary">Get notified</Button>
</GetNotifiedDialog>
<p className="flex items-center gap-2 text-16 font-300 text-white-60">
Coming 2025 <span className="size-1 rounded-full bg-white-40" />{' '}
Bundled with Keycard
</p>
</div>
</div>
</div>
</div>
<div className="absolute inset-x-0 bottom-0 z-10 h-1/2 bg-gradient-to-t from-[#010101] to-[transparent] lg:h-1/3" />
</section>
)
}
export { Hero }
@@ -0,0 +1,37 @@
import { Button } from '~components/button'
import { ButtonLink } from '~components/button-link'
import { BuyKeycardDialog } from '~components/buy-keycard-dialog'
import { Image } from '~components/image'
const Keycards = () => {
return (
<section className="relative mx-auto mt-[120px] min-h-[490px] max-w-[1352px] overflow-clip rounded-28 border border-white-12 lg:mt-0 lg:min-h-[840px]">
<Image
src="/assets/keycard-shell/bg-keycards.png"
alt="Keycards scattered around"
className="absolute left-0 top-0 size-full object-cover"
width={2704}
height={1680}
/>
<div className="relative z-10 pt-12 text-center lg:pt-20">
<p className="font-lora text-32 text-white-95">
Many use cases,
<br />
multiple Keycards.
</p>
<div className="flex items-center justify-center gap-4 pt-8">
<BuyKeycardDialog>
<Button variant="primary">Buy Keycard</Button>
</BuyKeycardDialog>
<ButtonLink href="/keycard" variant="secondary">
Learn more
</ButtonLink>
</div>
</div>
</section>
)
}
export { Keycards }
@@ -0,0 +1,49 @@
import { Button } from '~components/button'
import { GetNotifiedDialog } from '~components/get-notified-dialog'
import { Image } from '~components/image'
import { cx } from 'cva'
const Prefooter = () => {
return (
<section className="relative mb-2 mt-28 flex flex-col px-5 pb-20 pt-0 full-view-port lg:my-0 lg:mb-[160px] lg:flex-row lg:pb-0 lg:pt-[100px] lg:remove-full-view-port xl:gap-8 2xl:gap-16">
<div className="relative z-20 order-2 flex flex-col items-start pt-[365px] text-left lg:order-1 lg:pt-[100px] xl:pl-[290px]">
<p className="pb-2 text-24 font-600 text-white-95">
keycard <span className="font-200">shell</span>
</p>
<h2 className="flex pb-4 font-lora text-32 font-400 text-white-95 md:text-48">
One device for all
<br /> your Keycards
</h2>
<p className="max-w-[435px] pb-10 text-20 font-300 text-white-80">
Unrivalled security with an infinite number of removable Keycards,
each with their own key.
</p>
<div className="flex flex-col items-start gap-5 sm:min-w-[410px] sm:flex-row sm:items-center">
<GetNotifiedDialog>
<Button variant="primary">Get notified</Button>
</GetNotifiedDialog>
<p className="flex items-center gap-2 text-16 font-300 text-white-60 md:gap-2">
Coming 2025 <span className="size-1 rounded-full bg-white-40" />{' '}
Bundled with Keycard
</p>
</div>
</div>
<div className="order-1 flex lg:right-[-20px] lg:order-2">
<div className="absolute z-20 block h-[360px] w-full bg-gradient-to-b from-dark-100 to-[transparent] lg:h-[220px]" />
<Image
src="/assets/faqs.png"
alt="Keycard Shell"
width={791}
height={521}
draggable={false}
className={cx([
'pointer-events-none absolute left-[80%] top-0 z-0 h-auto max-w-[549px] -translate-x-1/2 select-none md:left-1/2 lg:relative lg:left-1/4 lg:top-12 lg:translate-x-0',
'aspect-[791/521] object-contain lg:scale-125',
])}
/>
</div>
</section>
)
}
export { Prefooter }
@@ -0,0 +1,7 @@
type Props = {
children: React.ReactNode
}
export default function KeycardShellLayout({ children }: Props) {
return <div className="m-auto w-full max-w-[1512px]">{children}</div>
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

@@ -0,0 +1,30 @@
import { Metadata } from '~/app/_metadata'
import { AirGapped } from './_components/air-gapped'
import { Comparision } from './_components/comparision'
import { FeaturesCard } from './_components/features-card'
import { FeaturesGrid } from './_components/features-grid'
import { FeaturesSlider } from './_components/features-slider'
import { Hero } from './_components/hero'
import { Keycards } from './_components/keycards'
import { Prefooter } from './_components/pre-footer'
export const metadata = Metadata({
title: 'Keycard Shell',
description:
'A modular hardware wallet that relies on Keycard as the secure element.',
})
export default async function KeycardShellPage() {
return (
<>
<Hero />
<FeaturesCard />
<FeaturesGrid />
<FeaturesSlider />
<Comparision />
<AirGapped />
<Keycards />
<Prefooter />
</>
)
}
@@ -21,7 +21,7 @@ const features = [
{
title: 'Water resistant',
description:
'Left Keycard in your jeans? It will survive the washing machine!',
'Resistant to most everyday spills, give it a wipe and youre good to go.',
image: '/assets/keycard/water-resistant.png',
},
]
@@ -204,6 +204,7 @@ const FeaturesAccordion = (props: Props) => {
alt={selected.title}
className={imageClassName}
quality={75}
priority
/>
</motion.div>
</AnimatePresence>
@@ -226,6 +227,7 @@ const FeaturesAccordion = (props: Props) => {
alt={selected.title}
className={imageClassName}
quality={75}
priority
/>
</motion.div>
</AnimatePresence>
@@ -0,0 +1,40 @@
import { KEYCARD_PRODUCTS } from '~/app/_constants/shopify/products'
import { formatPrice } from '~/app/_utils/format-price'
import { Background } from '~components/3d/background'
import { Button } from '~components/button'
import { BuyKeycardDialog } from '~components/buy-keycard-dialog'
const Hero = () => {
return (
<section className="relative flex h-svh overflow-y-clip full-view-port lg:remove-full-view-port 2xl:h-[839px]">
<Background variant="homepage" />
<div className="pointer-events-none relative z-20 flex flex-1 flex-col justify-end px-5 pb-5 align-bottom lg:pb-[43px] xl:px-20">
<div className="flex flex-col items-start justify-between lg:flex-row">
<div className="pointer-events-auto flex max-w-[664px] flex-col lg:self-end">
<p className="pb-2 text-24 font-600 text-white-95">keycard</p>
<h1 className="flex font-lora text-32 font-400 text-white-95 lg:text-44">
Lightweight design, <br /> heavyweight security
</h1>
<p className="max-w-[435px] pb-8 pt-4 text-20 font-300 text-white-80">
Trade swiftly and safely back up your assets with Keycard hardware
wallets.
</p>
<div className="flex items-center gap-5">
<BuyKeycardDialog>
<Button variant="primary">Buy Keycard</Button>
</BuyKeycardDialog>
<p className="text-16 font-300 text-white-60">
From{' '}
{formatPrice({
amount: KEYCARD_PRODUCTS.ONE_CARD_SET.price,
})}
</p>
</div>
</div>
</div>
</div>
</section>
)
}
export { Hero }
@@ -1,4 +1,6 @@
import { ExternalIcon } from '@status-im/icons/20'
import { ButtonLink } from '~components/button-link'
import { FeaturesDialog } from '~components/features-dialog'
import { Image } from '~components/image'
import { Link } from '~components/link'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '~components/tabs'
@@ -16,9 +18,8 @@ const featuresMobile = [
href="https://tokenlists.org/token-list?url=https://ipfs.io/ipns/tokens.uniswap.org"
className="underline decoration-1 underline-offset-2 transition-colors hover:text-orange"
>
Uniswap Labs default token list
Uniswap Labs default token list.
</Link>
.
</>
),
image: '/assets/keycard/slider/status-app-mobile-send.png',
@@ -32,7 +33,7 @@ const featuresMobile = [
},
{
title: 'Bridge',
description: 'Bridge assets across Ethereums most popular chains. ',
description: 'Bridge assets across Ethereums most popular chains.',
image: '/assets/keycard/slider/status-app-mobile-bridge.png',
imageMobile: '/assets/keycard/slider/iphone-bridge.png',
},
@@ -55,9 +56,8 @@ const featuresDesktop = [
href="https://tokenlists.org/token-list?url=https://ipfs.io/ipns/tokens.uniswap.org"
className="underline decoration-1 underline-offset-2 transition-colors hover:text-orange"
>
Uniswap Labs default token list
Uniswap Labs default token list.
</Link>
.
</>
),
image: '/assets/keycard/slider/status-app-desktop-send.png',
@@ -71,7 +71,7 @@ const featuresDesktop = [
},
{
title: 'Bridge',
description: 'Bridge assets across Ethereums most popular chains. ',
description: 'Bridge assets across Ethereums most popular chains.',
image: '/assets/keycard/slider/status-app-desktop-bridge.png',
imageMobile: '/assets/keycard/slider/status-app-desktop-bridge.png',
},
@@ -95,6 +95,30 @@ const featuresDesktop = [
},
]
const integrations = [
{
name: 'Enno Wallet',
description: 'Native Android Ethereum Wallet',
logo: '/assets/keycard/enno.png',
buttonText: 'Learn more',
href: 'https://ennowallet.com/',
},
{
name: 'WallETH',
description: 'Native Android Ethereum Wallet',
logo: '/assets/keycard/walleth.png',
buttonText: 'Learn more',
href: 'https://walleth.org/',
},
{
name: 'Logos Operators',
description: '5,000 Ordinals Collection',
logo: '/assets/keycard/logos-operators.png',
buttonText: 'Learn more',
href: 'https://dashboard.logos.co/',
},
]
const Integration = () => {
return (
<section className="mt-[200px]">
@@ -108,12 +132,12 @@ const Integration = () => {
alt="Status"
src="/assets/keycard/status-logo.png"
/>
<Link className="group" href="/">
<div className="flex items-center gap-1 rounded-16 border border-dashed border-[#FF640020] bg-[#FF640010] py-[5px] pl-4 pr-3 text-14 font-300 text-orange transition-colors group-hover:bg-[#FF640020]">
Keycard features in beta
<FeaturesDialog>
<button className="flex items-center gap-1 rounded-16 border border-dashed border-[#FF640020] bg-[#FF640010] py-[5px] pl-4 pr-3 text-14 font-300 text-orange transition-colors group-hover:bg-[#FF640020]">
Keycard features in beta - learn more
<ExternalIcon />
</div>
</Link>
</button>
</FeaturesDialog>
</div>
<h2 className="pt-1 font-lora text-32 font-400 text-white-95">
Seamless integration with Status
@@ -148,6 +172,63 @@ const Integration = () => {
</Tabs>
</div>
</div>
<div className="px-3 pt-20 md:px-12">
<h2 className="pb-4 pl-6 text-12 font-400 text-white-80 lg:pl-8">
ALSO INTEGRATED WITH
</h2>
<div className="grid grid-cols-1 gap-8 rounded-28 border border-white-8 bg-white-4 p-6 pt-5 text-white-95 md:grid-cols-2 lg:grid-cols-4 lg:gap-6 lg:p-8 lg:pt-9">
{integrations.map((integration, index) => (
<div key={index} className="flex flex-col">
<div className="mb-3 inline-flex items-center">
{integration.logo && (
<Image
src={integration.logo}
alt={`${integration.name} logo`}
width={24}
height={23}
className="mr-2"
/>
)}
<h3 className="font-lora text-24 font-400">
{integration.name}
</h3>
</div>
<p className="mb-6 text-16 font-300 text-white-80">
{integration.description}
</p>
<ButtonLink
href={integration.href}
variant="secondary"
className="group w-fit pr-2"
>
<span className="text-white-95">{integration.buttonText}</span>
<ExternalIcon />
</ButtonLink>
</div>
))}
<div className="flex flex-col gap-6">
<div>
<h2 className="font-lora text-24 font-500 text-white-95 lg:text-32">
Want to integrate?
</h2>
<p className="text-16 text-white-80">
Read our documentation or get in touch
</p>
</div>
<div className="flex gap-3">
<ButtonLink href="/docs">Read docs</ButtonLink>
<ButtonLink
href="mailto:support@keycard.tech"
variant="secondary"
>
Get in touch
</ButtonLink>
</div>
</div>
</div>
</div>
</section>
)
}
@@ -0,0 +1,55 @@
import { Button } from '~components/button'
import { ButtonLink } from '~components/button-link'
import { GetNotifiedDialog } from '~components/get-notified-dialog'
import { Image } from '~components/image'
const KeycardShell = () => {
return (
<section className="relative mx-0 mt-[160px] flex items-center overflow-hidden rounded-28 border border-white-12 lg:mx-6 lg:mt-[200px] xl:mx-20">
<Image
src="/assets/keycard/bg-keycard-shell.png"
alt="Keycard Shell Hardware Wallet"
width={1400}
height={800}
className="absolute left-0 top-0 hidden size-full object-cover md:block"
/>
<Image
src="/assets/keycard/bg-keycard-shell-mobile.png"
alt="Keycard Shell Hardware Wallet"
width={828}
height={1452}
className="absolute left-0 top-0 block size-full scale-[101%] object-cover md:hidden"
/>
<div className="relative z-10 flex flex-col p-6 pt-[400px] lg:py-[200px] lg:pl-[229px]">
<p className="pb-3 text-24 font-600 text-white-95">
keycard <span className="font-200">shell</span>
</p>
<p className="flex pb-2 font-lora text-32 font-400">
Multiple Keycards.
<br />
One device.
</p>
<p className="max-w-[320px] pb-6 text-16 font-300 text-white-80">
Use the same device for all your Keycards. Its airgapped, stateless
and modular.
</p>
<div className="flex gap-4">
<GetNotifiedDialog>
<Button>Get notified</Button>
</GetNotifiedDialog>
<ButtonLink href="/keycard-shell" variant="secondary">
Learn more
</ButtonLink>
</div>
<p className="flex items-center gap-2 pt-6 text-16 font-300 text-white-60 md:gap-2">
Coming 2025 <span className="size-1 rounded-full bg-white-40" />{' '}
Bundled with Keycard
</p>
</div>
</section>
)
}
export { KeycardShell }
@@ -8,12 +8,10 @@ import { cx } from 'cva'
const Prefooter = () => {
return (
<section className="relative flex flex-col overflow-hidden px-6 lg:flex-row lg:px-14 lg:pt-[200px] xl:pl-[309px]">
<div className="relative z-20 flex flex-1 flex-col items-start pb-[120px] pt-[320px] text-left lg:py-[200px]">
<p className="pb-2 text-24 font-600 text-orange">keycard</p>
<div className="relative z-20 flex flex-1 flex-col items-start pb-[120px] pt-[320px] text-left lg:min-w-[450px] lg:py-[200px]">
<p className="pb-2 text-24 font-600 text-white-95">keycard</p>
<h2 className="flex pb-4 font-lora text-32 font-400 text-white-95 md:text-48">
Get the card-shaped
<br />
hardware wallet
Get the card-shaped <br /> hardware wallet
</h2>
<p className="max-w-[435px] pb-10 text-20 font-300 text-white-80">
Store and trade your crypto in a simple, secure and slim hardware
@@ -24,7 +22,6 @@ const Prefooter = () => {
<Button variant="primary">Buy Keycard</Button>
</BuyKeycardDialog>
<p className="text-16 font-300 text-white-60">
{' '}
From{' '}
{formatPrice({
amount: KEYCARD_PRODUCTS.ONE_CARD_SET.price,
@@ -7,10 +7,9 @@ import { Tag } from '~components/tag'
const SecurityFeatures = () => {
return (
<section className="relative mt-[100px] overflow-clip px-3 lg:mt-[227px] lg:px-0">
<div className="relative overflow-clip rounded-28 border border-white-8 bg-white-4 px-6 md:px-2">
<div className="relative overflow-clip rounded-28 border border-white-8 bg-white-4 px-6 md:px-0">
<div className="flex flex-col items-center justify-between lg:flex-row">
<div className="relative -mx-6 flex-1 self-start">
<div className="absolute inset-0 z-10 bg-gradient-to-br from-dark-100 to-30%" />
<div className="-mx-6 flex-1 self-start lg:mx-0">
<Image
src="/assets/keycard/chip.png"
alt="Keycard security"
@@ -19,7 +18,7 @@ const SecurityFeatures = () => {
priority
/>
</div>
<div className="py-20 pb-6 md:pb-20 md:pl-6 lg:w-[49%] lg:pl-0 lg:pr-[70px]">
<div className="py-20 pb-6 md:pb-20 md:pl-6 lg:w-1/2 lg:pl-0 lg:pr-[72px]">
<h2 className="pb-[52px] font-lora text-32 lg:pb-14">
Proudly building in the open
</h2>
@@ -3,6 +3,7 @@ import { formatPrice } from '~/app/_utils/format-price'
import { Button } from '~components/button'
import { BuyKeycardDialog } from '~components/buy-keycard-dialog'
import { Image } from '~components/image'
import { Tooltip } from '~components/tooltip'
import { RecommendedIcon } from '~icons/recommended'
import { cx } from 'cva'
import { Card } from './card'
@@ -69,7 +70,9 @@ const UseCases = () => {
})}
{!!set.tag && (
<span className="flex size-5 items-center justify-center rounded-full bg-orange">
<RecommendedIcon />
<Tooltip label="Best deal">
<RecommendedIcon />
</Tooltip>
</span>
)}
</div>
@@ -0,0 +1,7 @@
type Props = {
children: React.ReactNode
}
export default function KeycardLayout({ children }: Props) {
return <div className="m-auto w-full max-w-[1512px]">{children}</div>
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

@@ -0,0 +1,30 @@
import { Metadata } from '~/app/_metadata'
import { Customisation } from './_components/customisation'
import { Design } from './_components/design'
import { Hero } from './_components/hero'
import { Integration } from './_components/integration'
import { KeycardShell } from './_components/keycard-shell'
import { Prefooter } from './_components/pre-footer'
import { SecurityFeatures } from './_components/security-features'
import { UseCases } from './_components/use-cases'
export const metadata = Metadata({
title: 'Keycard',
description:
'Store and trade your crypto with a simple, secure and slim hardware wallet.',
})
export default async function KeycardPage() {
return (
<>
<Hero />
<SecurityFeatures />
<Design />
<Integration />
<UseCases />
<KeycardShell />
<Customisation />
<Prefooter />
</>
)
}
+2 -2
View File
@@ -47,7 +47,7 @@ export default function StartPage() {
Learn how to create and store your keys directly on Keycard
</p>
<ButtonLink
href="https://status.app/help/profile/create-a-status-profile-using-keycard"
href="https://status.app/help/keycard/create-a-status-profile-using-keycard"
variant="secondary"
icon={<ExternalIcon />}
className="mt-auto"
@@ -63,7 +63,7 @@ export default function StartPage() {
Learn how to move your keys to Keycard
</p>
<ButtonLink
href="https://status.app/help/profile/secure-your-status-profile-or-wallet-with-keycard"
href="https://status.app/help/keycard/secure-your-status-profile-or-wallet-with-keycard"
variant="secondary"
icon={<ExternalIcon />}
className="mt-auto"
+2 -2
View File
@@ -14,11 +14,11 @@ const Footer = () => {
</Link>
</div>
<div className="m-auto max-w-[1512px]">
<div className="grid grid-cols-2 divide-x divide-dashed divide-white-20 lg:grid-cols-5 lg:pl-[152px] lg:pr-[72px] xl:pl-[282px]">
<div className="grid grid-cols-2 divide-x divide-dashed divide-white-20 lg:grid-cols-5 lg:pl-[152px] lg:pr-0 xl:pl-[282px]">
<div
key="empty"
title="empty"
className="border-b border-dashed border-white-20 lg:hidden"
className="col-span-2 h-[72px] border-b border-dashed border-white-20 lg:col-span-1 lg:border-0"
/>
{Object.entries(ROUTES).map(([title, links]) => (
+1 -1
View File
@@ -11,7 +11,7 @@ const Section = (props: Props) => {
const { title, routes } = props
return (
<div className="relative flex flex-col gap-6 border-b border-dashed border-white-20 p-6 lg:border-0 lg:pb-12 lg:pt-0 [&:nth-child(2)]:pt-0 lg:[&:nth-child(2)]:col-start-3">
<div className="relative flex flex-col gap-6 border-b border-dashed border-white-20 p-6 lg:border-0 lg:pb-12 lg:pt-0 lg:[&:nth-child(1)]:col-start-2">
<p className="text-12 uppercase text-white-80">{title}</p>
<ul className="grid gap-2">
{routes.map(route => {
+5 -5
View File
@@ -9,7 +9,7 @@ import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { Logo } from '../logo'
// import { MenuIcon } from './menu-icon'
import { MenuIcon } from './menu-icon'
import { Section } from './section'
const NAV_BAR_HEIGHT = 80
@@ -105,14 +105,14 @@ const NavBarMobile = () => {
</motion.div>
)}
</AnimatePresence>
{/* <button
<button
key={isOpen ? 'menu-open' : 'menu-closed'}
className="rounded-12 border border-white-12 bg-white-8 text-white-95 transition-colors hover:bg-white-12"
onClick={() => setIsOpen(!isOpen)}
aria-label="Menu"
>
<MenuIcon isOpen={isOpen} />
</button> */}
</button>
</div>
</div>
<AnimatePresence>
@@ -140,7 +140,7 @@ const NavBarMobile = () => {
className="flex h-full flex-col items-center justify-center pt-[200px]"
>
<ul className="flex flex-1 flex-col gap-4">
{/* {ROUTES.Products.map((item, index) => (
{ROUTES.Products.map((item, index) => (
<motion.li
key={item.name}
initial={{ opacity: 0, y: 20 }}
@@ -152,7 +152,7 @@ const NavBarMobile = () => {
{item.name}
</Link>
</motion.li>
))} */}
))}
<motion.li
key="button-keycard"
initial={{ opacity: 0, y: 20 }}
+36 -3
View File
@@ -1,5 +1,6 @@
'use client'
import { cva } from 'cva'
import {
motion,
useMotionValueEvent,
@@ -7,6 +8,7 @@ import {
useTransform,
} from 'framer-motion'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { useState } from 'react'
import { Button } from './button'
import { BuyKeycardDialog } from './buy-keycard-dialog'
@@ -14,7 +16,26 @@ import { Logo } from './logo'
const NAV_BAR_HEIGHT = 92
const internalLinkStyles = cva({
base: 'rounded-12 border border-[transparent] px-[14px] py-[7px] transition-colors hover:bg-white-8',
variants: {
isActive: {
true: 'bg-white-12',
false: '',
},
},
})
const links = [
{ href: '/keycard', label: 'Keycard' },
{
href: '/keycard-shell',
label: 'Keycard Shell',
},
]
const NavBar = () => {
const pathname = usePathname()
const [variant, setVariant] = useState<'primary' | 'secondary'>('secondary')
const { scrollY } = useScroll()
@@ -50,9 +71,21 @@ const NavBar = () => {
<Logo />
</Link>
<BuyKeycardDialog>
<Button variant={variant}>Buy Keycard</Button>
</BuyKeycardDialog>
<div className="flex items-center gap-4">
{links.map(({ href, label }) => (
<Link
key={href}
href={href}
className={internalLinkStyles({ isActive: pathname === href })}
>
{label}
</Link>
))}
<BuyKeycardDialog>
<Button variant={variant}>Buy Keycard</Button>
</BuyKeycardDialog>
</div>
</motion.nav>
)
}
+4 -4
View File
@@ -1,8 +1,8 @@
export const ROUTES = {
// Products: [
// { name: 'Keycard', href: '/keycard' },
// { name: 'Keycard Shell', href: '/keycard-shell' },
// ],
Products: [
{ name: 'Keycard', href: '/keycard' },
{ name: 'Keycard Shell', href: '/keycard-shell' },
],
Info: [
{ name: 'Get started', href: '/start' },
{ name: 'Documentation', href: '/docs/overview' },