Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd6c9dddf6 | ||
|
|
deef802fd0 | ||
|
|
200f6b63bd | ||
|
|
79773c0b1e | ||
|
|
d9ce1f3b56 | ||
|
|
0b666ba5bf | ||
|
|
adec63d51c | ||
|
|
be68e34701 | ||
|
|
45b608a45c | ||
|
|
0fd7e2310a | ||
|
|
59298982ef | ||
|
|
22f3362609 | ||
|
|
f58fb1b0ab |
@@ -1 +1 @@
|
||||
pnpx lint-staged
|
||||
pnpm dlx lint-staged
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
20.18.0
|
||||
@@ -0,0 +1,7 @@
|
||||
; https://pnpm.io/npmrc#node-options
|
||||
; why: https://github.com/vercel/next.js/discussions/70423
|
||||
node-options=--network-family-autoselection-attempt-timeout=1000
|
||||
; https://pnpm.io/npmrc#node-version
|
||||
node-version=20.18.0
|
||||
; https://pnpm.io/npmrc#engine-strict
|
||||
engine-strict=true
|
||||
@@ -25,7 +25,7 @@
|
||||
// note: config https://github.com/microsoft/vscode-js-debug/blob/e620efe1841742402a332b532df2aa2ec95361f6/OPTIONS.md
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "yarn dev -- --port 3030",
|
||||
"command": "NODE_OPTIONS='' pnpm dev --port 3030",
|
||||
"serverReadyAction": {
|
||||
"pattern": "\\s- Local:\\s+(https?://\\S+)",
|
||||
"action": "startDebugging",
|
||||
|
||||
@@ -5,9 +5,9 @@ title: Card Setup
|
||||
|
||||
# Preparing the card
|
||||
|
||||
Keycard ships with all applets fully installed but not initialized. The most convenient way to initialize and manage the card is to install the [Keycard Connect](https://github.com/status-im/keycard-connect) Android app. It allows to fully initialize a card with pairing key, PIN, PUK and BIP39 seed. It also provides basic functionality like changing credentials, changing or removing keys and clear pairing slots. The last functionality is especially useful if you are integrating with Keycard and accidentally fill all pairing slots. Using the "Unpair others" button will remove all pairing except the Keycard Connect's one.
|
||||
Keycard ships with all applets fully installed but not initialized. The most convenient way to initialize and manage the card is to install the [Keycard Connect](https://github.com/keycard-tech/keycard-connect) Android app. It allows to fully initialize a card with pairing key, PIN, PUK and BIP39 seed. It also provides basic functionality like changing credentials, changing or removing keys and clear pairing slots. The last functionality is especially useful if you are integrating with Keycard and accidentally fill all pairing slots. Using the "Unpair others" button will remove all pairing except the Keycard Connect's one.
|
||||
|
||||
In some cases you might want to reset the applet status completely for testing purposes. This is done by reinstalling the applet, which can be accomplished using the [Keycard CLI](https://github.com/status-im/keycard-cli). To use the Keycard CLI, a USB reader is needed to be able to communicate with the card. The repository contains example scripts for applet reinstallation.
|
||||
In some cases you might want to reset the applet status completely for testing purposes. This is done by reinstalling the applet, which can be accomplished using the [Keycard CLI](https://github.com/keycard-tech/keycard-cli). To use the Keycard CLI, a USB reader is needed to be able to communicate with the card. The repository contains example scripts for applet reinstallation.
|
||||
|
||||
## Card requirements
|
||||
|
||||
|
||||
@@ -30,6 +30,5 @@ The current version of the applet is 3.1. This documentation applies to said ver
|
||||
You are at the right place to find informations about Keycard and get you going.
|
||||
|
||||
If you need more and want to discuss:
|
||||
- for live discussion and help, go to [Keycard Status channel](https://join.status.im/chat/public/status-keycard)
|
||||
- to check or raise issues/bugs/feature requests please do so in our [GitHub repos](https://keycard.tech/docs/resources.html)
|
||||
- check our latest Keycard news on [Discuss](https://discuss.status.im/c/keycard)
|
||||
- to check or raise issues/bugs/feature requests please do so in our [GitHub repos](resources)
|
||||
- get in touch with the team by [email](mailto:support@keycard.tech)
|
||||
|
||||
@@ -9,15 +9,15 @@ Here you find the links to all repositories and tools related to Keycard develop
|
||||
|
||||
## GitHub repositories
|
||||
|
||||
* [Javacard applet](https://github.com/status-im/status-keycard)
|
||||
* [Java SDK](https://github.com/status-im/status-keycard-java)
|
||||
* [Go SDK](https://github.com/status-im/keycard-go/)
|
||||
* [Swift SDK](https://github.com/status-im/Keycard.swift)
|
||||
* [Keycard on Ledger](https://github.com/status-im/keycard-ledger)
|
||||
* [React Native library](https://github.com/status-im/react-native-status-keycard)
|
||||
* [Go Status library](https://github.com/status-im/status-keycard-go)
|
||||
* [Javacard applet](https://github.com/keycard-tech/status-keycard)
|
||||
* [Java SDK](https://github.com/keycard-tech/status-keycard-java)
|
||||
* [Go SDK](https://github.com/keycard-tech/keycard-go/)
|
||||
* [Swift SDK](https://github.com/keycard-tech/Keycard.swift)
|
||||
* [Keycard on Ledger](https://github.com/keycard-tech/keycard-ledger)
|
||||
* [React Native library](https://github.com/keycard-tech/react-native-status-keycard)
|
||||
* [Go Status library](https://github.com/keycard-tech/status-keycard-go)
|
||||
|
||||
## Tools
|
||||
|
||||
* [Keycard CLI](https://github.com/status-im/keycard-cli)
|
||||
* [Keycard Desktop](https://github.com/choppu/keycard-desktop)
|
||||
* [Keycard CLI](https://github.com/keycard-tech/keycard-cli)
|
||||
* [Keycard Desktop](https://github.com/choppu/keycard-desktop)
|
||||
|
||||
@@ -33,10 +33,10 @@ When however the data to be signed contains `uint256 amount` and `address curren
|
||||
|
||||

|
||||
|
||||
If the currency field address is considered to be the address of an ERC20 token. If this address is zero, the amount is interpreted as plain ETH. This transaction format is used by the [Keycard Payment Network](https://github.com/status-im/payment-network-apps) project.
|
||||
If the currency field address is considered to be the address of an ERC20 token. If this address is zero, the amount is interpreted as plain ETH. This transaction format is used by the [Keycard Payment Network](https://github.com/keycard-tech/payment-network-apps) project.
|
||||
|
||||
Another example of a custom screen, is the one displayed for the [Keycard Redeem](https://github.com/status-im/keycard-redeem) project. In this case we recognize the presence of the fields `address recipient` and `bytes32 code` field as a redeem request and we display the recipient field address clearly in the signature screen. If the recipient field in account managed by Status, instead of the plain address, the account name is shown. See screenshot below
|
||||
Another example of a custom screen, is the one displayed for the [Keycard Redeem](https://github.com/keycard-tech/keycard-redeem) project. In this case we recognize the presence of the fields `address recipient` and `bytes32 code` field as a redeem request and we display the recipient field address clearly in the signature screen. If the recipient field in account managed by Status, instead of the plain address, the account name is shown. See screenshot below
|
||||
|
||||

|
||||
|
||||
One thing to keep in mind however, is that these custom screens are only there to provide better UX for some specific use case we are aware of. Any unrecognized message format can still be displayed generically and signed. If you are implementing a web3 browser you can adopt any strategy you prefer on this. Conversely, if you are developing SmartContract and dApps, you might want consider naming the fields of the message in a way that will trigger a specific interface on a browser. For example if your use case scenario involve transferring a certain `amount` of `currency` in a transaction, naming the message fields like that will show an easy to understand screen in Status.
|
||||
One thing to keep in mind however, is that these custom screens are only there to provide better UX for some specific use case we are aware of. Any unrecognized message format can still be displayed generically and signed. If you are implementing a web3 browser you can adopt any strategy you prefer on this. Conversely, if you are developing SmartContract and dApps, you might want consider naming the fields of the message in a way that will trigger a specific interface on a browser. For example if your use case scenario involve transferring a certain `amount` of `currency` in a transaction, naming the message fields like that will show an easy to understand screen in Status.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Keycard Presale Website - Privacy Policy
|
||||
title: Keycard - Privacy Policy
|
||||
---
|
||||
|
||||
This Privacy Policy is intended to inform users of our approach to privacy in respect of this website (“**Website**”). In this regard, if you are visiting our Website, this Privacy Policy applies to you.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Keycard Presale - Terms of Use
|
||||
title: Keycard - Terms of Use
|
||||
---
|
||||
|
||||
These website terms of use (“**Website Terms of Use**”) are entered into by you and us, and they govern your access and use of this Website, including any content and functionality contained in the Website.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type * as React from 'react'
|
||||
|
||||
// why: https://github.com/mdx-js/mdx/issues/2487#issuecomment-2407661869
|
||||
// why: https://react.dev/blog/2024/04/25/react-19-upgrade-guide#the-jsx-namespace-in-typescript
|
||||
declare global {
|
||||
namespace JSX {
|
||||
// type ElementClass = React.JSX.ElementClass
|
||||
// type Element = React.JSX.Element
|
||||
type IntrinsicElements = React.JSX.IntrinsicElements
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'framer-motion' {
|
||||
export interface MotionProps {
|
||||
className?: string
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,11 @@ const config = {
|
||||
hostname: 'demo.ghost.io',
|
||||
pathname: '/**',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'news.keycard.tech',
|
||||
pathname: '/**',
|
||||
},
|
||||
],
|
||||
},
|
||||
transpilePackages: [
|
||||
|
||||
@@ -16,11 +16,19 @@
|
||||
"@radix-ui/react-accordion": "^1.2.1",
|
||||
"@radix-ui/react-checkbox": "^1.1.2",
|
||||
"@radix-ui/react-dialog": "^1.1.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
||||
"@radix-ui/react-tabs": "^1.1.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.3",
|
||||
"@react-three/drei": "^9.117.2",
|
||||
"@react-three/fiber": "9.0.0-rc.1",
|
||||
"@react-three/postprocessing": "^2.16.3",
|
||||
"@status-im/icons": "^1.0.2",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tanstack/react-query": "^5.59.17",
|
||||
"@tryghost/content-api": "^1.11.21",
|
||||
"@types/three": "^0.170.0",
|
||||
"@types/unist": "^3.0.3",
|
||||
"@vercel/analytics": "^1.4.1",
|
||||
"cva": "1.0.0-beta.1",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"embla-carousel-react": "^8.3.1",
|
||||
@@ -29,13 +37,16 @@
|
||||
"github-slugger": "^2.0.0",
|
||||
"google-auth-library": "^9.14.2",
|
||||
"google-spreadsheet": "^4.1.4",
|
||||
"next": "15.0.2",
|
||||
"next": "15.0.3",
|
||||
"next-mdx-remote": "^5.0.0",
|
||||
"react": "^18.3.1",
|
||||
"react": "19.0.0-rc.1",
|
||||
"react-children-utilities": "^2.10.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dom": "19.0.0-rc.1",
|
||||
"react-error-boundary": "^5.0.0",
|
||||
"react-hook-form": "^7.53.1",
|
||||
"server-only": "^0.0.1",
|
||||
"three": "^0.170.0",
|
||||
"three-stdlib": "^2.34.0",
|
||||
"ts-pattern": "^5.5.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
@@ -44,13 +55,13 @@
|
||||
"@types/d3-time-format": "^4.0.3",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
|
||||
"@types/tryghost__content-api": "^1.3.17",
|
||||
"@types/unist": "^3.0.3",
|
||||
"embla-carousel": "^8.3.1",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-next": "15.0.2",
|
||||
"eslint-config-next": "15.0.3",
|
||||
"eslint-plugin-tailwindcss": "^3.17.5",
|
||||
"husky": "^9.1.6",
|
||||
"lint-staged": "^15.2.10",
|
||||
@@ -70,6 +81,9 @@
|
||||
"unified": "^10.1.2",
|
||||
"unist-util-visit": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.x"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,mjs,ts,tsx}": [
|
||||
"eslint --fix",
|
||||
@@ -81,5 +95,14 @@
|
||||
"*.{md,mdx}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@types/react": "npm:types-react@19.0.0-rc.1",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@react-three/postprocessing": "patches/@react-three__postprocessing.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
diff --git a/dist/EffectComposer.js b/dist/EffectComposer.js
|
||||
index 335539d470b862efff4d34818a28d1ed78255005..c4e47fad5ea95dd28ef1570919bbcba0b1883ad8 100644
|
||||
--- a/dist/EffectComposer.js
|
||||
+++ b/dist/EffectComposer.js
|
||||
@@ -77,9 +77,9 @@ const EffectComposer = React.memo(
|
||||
useLayoutEffect(() => {
|
||||
const passes = [];
|
||||
if (group.current && instance.current && composer) {
|
||||
- const children2 = instance.current.objects;
|
||||
+ const children2 = instance.current.children;
|
||||
for (let i = 0; i < children2.length; i++) {
|
||||
- const child = children2[i];
|
||||
+ const child = children2[i].object;
|
||||
if (child instanceof Effect) {
|
||||
const effects = [child];
|
||||
if (!isConvolution(child)) {
|
||||
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 550 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 859 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 369 KiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 360 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 903 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 347 KiB |
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 282 KiB |
|
After Width: | Height: | Size: 232 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 338 KiB |
|
After Width: | Height: | Size: 313 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 173 KiB |
@@ -1,29 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { GetNotifiedDialog } from '~components/get-notified-dialog'
|
||||
import { useState } from 'react'
|
||||
import * as Dialog from '../../_components/dialog'
|
||||
|
||||
export const ThankYouDialog = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
// TODO - Check on load for some parameter in the URL from shopify in order to open the dialog
|
||||
return (
|
||||
<Dialog.Root open={open} onOpenChange={setOpen}>
|
||||
<Dialog.Content>
|
||||
<Dialog.Success
|
||||
title="Thanks for choosing Keycard!"
|
||||
description="You have successfully purchased a Keycard set. It will arrive shortly!"
|
||||
image="/assets/sign-up-success.png"
|
||||
onClose={() => setOpen(false)}
|
||||
footer={{
|
||||
title: 'Want to up your game?',
|
||||
description: 'Keycard Shell is coming 2025',
|
||||
Dialog: GetNotifiedDialog,
|
||||
buttonText: 'Get notified',
|
||||
}}
|
||||
/>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { BuyCards } from '~/app/(homepage)/_components/buy-cards'
|
||||
import { ComparisonTable } from '~/app/(homepage)/_components/comparison-table'
|
||||
import { Faqs } from '~/app/(homepage)/_components/faqs'
|
||||
import { Hero } from '~/app/(homepage)/_components/hero'
|
||||
import { KeycardFeatures } from '~/app/(homepage)/_components/keycard-features'
|
||||
import { KeycardShell } from '~/app/(homepage)/_components/keycard-shell'
|
||||
import { ThankYouDialog } from './_components/thank-you-dialog'
|
||||
|
||||
export default async function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<ThankYouDialog />
|
||||
<Hero />
|
||||
<KeycardShell />
|
||||
<KeycardFeatures />
|
||||
<ComparisonTable />
|
||||
<Faqs />
|
||||
<BuyCards />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
'use client'
|
||||
|
||||
import { BuyKeycardDialog } from '~components/buy-keycard-dialog'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export const AutoOpenBuyKeycardDialog = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const buyKeycard = new URLSearchParams(window.location.search).get(
|
||||
'buyKeycard',
|
||||
)
|
||||
|
||||
if (!buyKeycard || buyKeycard !== 'true') {
|
||||
return
|
||||
}
|
||||
|
||||
setOpen(true)
|
||||
}, [])
|
||||
|
||||
return <BuyKeycardDialog open={open} onOpenChange={setOpen} />
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
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'
|
||||
import Image from 'next/image'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
image: string
|
||||
@@ -42,14 +43,14 @@ const Section = (props: Props) => {
|
||||
imageClassName,
|
||||
])}
|
||||
/>
|
||||
<div className="flex size-full flex-col items-center justify-end rounded-28 border border-white-8 bg-white-3 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-4">
|
||||
<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>
|
||||
<h1 className="flex pb-4 text-center font-lora text-32 font-400 lg:text-48">
|
||||
<h2 className="flex pb-4 text-center font-lora text-32 font-400 lg:text-48">
|
||||
{title}
|
||||
</h1>
|
||||
</h2>
|
||||
<p className="pb-8 text-center text-20 font-300 text-white-80">
|
||||
{description}
|
||||
</p>
|
||||
@@ -72,7 +73,7 @@ const BuyCards = () => {
|
||||
A card shaped <br /> hardware wallet
|
||||
</>
|
||||
}
|
||||
description="Something will say here about this product. Certainly, you don't want to miss it."
|
||||
description="Store and trade your crypto in a simple, secure and slim hardware wallet."
|
||||
buttons={
|
||||
<>
|
||||
<BuyKeycardDialog>
|
||||
@@ -83,7 +84,9 @@ const BuyCards = () => {
|
||||
</ButtonLink>
|
||||
</>
|
||||
}
|
||||
info="Starts from $25"
|
||||
info={`From ${formatPrice({
|
||||
amount: KEYCARD_PRODUCTS.ONE_CARD_SET.price,
|
||||
})}`}
|
||||
/>
|
||||
<Section
|
||||
image="/assets/bottom-keycard-shell.png"
|
||||
@@ -92,10 +95,10 @@ const BuyCards = () => {
|
||||
secondName="shell"
|
||||
title={
|
||||
<>
|
||||
One device for all <br /> your keycards
|
||||
One device for all <br /> your Keycards
|
||||
</>
|
||||
}
|
||||
description="Something will say here about this product. Certainly you don't want to miss it"
|
||||
description="Unrivalled security with an infinite number of removable Keycards, each with their own key."
|
||||
buttons={
|
||||
<>
|
||||
<GetNotifiedDialog>
|
||||
@@ -106,7 +109,7 @@ const BuyCards = () => {
|
||||
</ButtonLink>
|
||||
</>
|
||||
}
|
||||
info="Coming 2025"
|
||||
info="Coming 2025 · Bundled with Keycard"
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
@@ -1,23 +1,31 @@
|
||||
import {
|
||||
CheckIcon,
|
||||
FirmwareIcon,
|
||||
InfoIcon,
|
||||
RemoveIcon,
|
||||
} from '@status-im/icons/20'
|
||||
import { KEYCARD_PRODUCTS } from '~/app/_constants/shopify/products'
|
||||
import { formatPrice } from '~/app/_utils/format-price'
|
||||
import { Tag } from '~components/tag'
|
||||
import { Tooltip } from '~components/tooltip'
|
||||
import { Check, Info, Minus, Shield } from '~icons'
|
||||
import { createElement } from 'react'
|
||||
|
||||
const features: Array<Feature> = [
|
||||
{
|
||||
name: 'Competitive pricing',
|
||||
tooltip: 'Pricing information',
|
||||
keycardShell: {
|
||||
label: 'TBD',
|
||||
},
|
||||
keycard: {
|
||||
label: '$25',
|
||||
label: formatPrice({
|
||||
amount: KEYCARD_PRODUCTS.ONE_CARD_SET.price,
|
||||
}),
|
||||
},
|
||||
tangem: {
|
||||
label: '$55',
|
||||
label: '€50',
|
||||
},
|
||||
ledger: {
|
||||
label: '$80',
|
||||
label: '€80',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -42,7 +50,6 @@ const features: Array<Feature> = [
|
||||
},
|
||||
keycard: {
|
||||
featured: true,
|
||||
tooltip: 'Bitcoin only',
|
||||
},
|
||||
tangem: {
|
||||
featured: true,
|
||||
@@ -53,16 +60,15 @@ const features: Array<Feature> = [
|
||||
},
|
||||
{
|
||||
name: 'Hardware level protection',
|
||||
tooltip: 'Security certification level',
|
||||
keycardShell: {
|
||||
badge: {
|
||||
text: 'EAL 6+',
|
||||
gradient: true,
|
||||
icon: Shield,
|
||||
icon: FirmwareIcon,
|
||||
},
|
||||
},
|
||||
keycard: { badge: { text: 'EAL 6+', gradient: true, icon: Shield } },
|
||||
tangem: { badge: { text: 'EAL 6+', gradient: true, icon: Shield } },
|
||||
keycard: { badge: { text: 'EAL 6+', gradient: true, icon: FirmwareIcon } },
|
||||
tangem: { badge: { text: 'EAL 6+', gradient: true, icon: FirmwareIcon } },
|
||||
ledger: { badge: { text: 'EAL 5+' } },
|
||||
},
|
||||
{
|
||||
@@ -70,7 +76,6 @@ const features: Array<Feature> = [
|
||||
tooltip: 'Compatible with mobile devices',
|
||||
keycardShell: {
|
||||
featured: true,
|
||||
tooltip: 'iOS and Android',
|
||||
},
|
||||
keycard: {
|
||||
featured: true,
|
||||
@@ -89,16 +94,13 @@ const features: Array<Feature> = [
|
||||
},
|
||||
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',
|
||||
@@ -128,14 +130,13 @@ const features: Array<Feature> = [
|
||||
featured: false,
|
||||
},
|
||||
ledger: {
|
||||
featured: true,
|
||||
featured: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Open source secure element',
|
||||
keycardShell: {
|
||||
featured: true,
|
||||
tooltip: 'Secure element is open source',
|
||||
},
|
||||
keycard: {
|
||||
featured: false,
|
||||
@@ -157,11 +158,9 @@ const features: Array<Feature> = [
|
||||
},
|
||||
tangem: {
|
||||
featured: false,
|
||||
tooltip: 'Not applicable',
|
||||
},
|
||||
ledger: {
|
||||
featured: false,
|
||||
tooltip: 'Not applicable',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -171,8 +170,7 @@ const features: Array<Feature> = [
|
||||
featured: true,
|
||||
},
|
||||
keycard: {
|
||||
featured: true,
|
||||
tooltip: 'Only with Keycard wallet',
|
||||
featured: false,
|
||||
},
|
||||
tangem: {
|
||||
featured: false,
|
||||
@@ -216,7 +214,6 @@ const features: Array<Feature> = [
|
||||
name: 'Secure element has its own usages',
|
||||
keycardShell: {
|
||||
featured: true,
|
||||
tooltip: 'Multiple use cases for secure element',
|
||||
},
|
||||
keycard: {
|
||||
featured: 'n/a',
|
||||
@@ -252,11 +249,9 @@ const features: Array<Feature> = [
|
||||
},
|
||||
keycard: {
|
||||
featured: 'n/a',
|
||||
tooltip: 'Not applicable',
|
||||
},
|
||||
tangem: {
|
||||
featured: 'n/a',
|
||||
tooltip: 'Not applicable',
|
||||
},
|
||||
ledger: {
|
||||
featured: false,
|
||||
@@ -277,21 +272,6 @@ const features: Array<Feature> = [
|
||||
featured: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Oshwa label',
|
||||
keycardShell: {
|
||||
featured: true,
|
||||
},
|
||||
keycard: {
|
||||
featured: false,
|
||||
},
|
||||
tangem: {
|
||||
featured: false,
|
||||
},
|
||||
ledger: {
|
||||
featured: false,
|
||||
},
|
||||
},
|
||||
] as const
|
||||
|
||||
type FeatureVariant = {
|
||||
@@ -321,9 +301,9 @@ const FeatureInfo = ({
|
||||
}) => {
|
||||
if (!variant || typeof variant === 'string') return null
|
||||
|
||||
if (variant.featured === true) return <Check className="text-orange" />
|
||||
if (variant.featured === true) return <CheckIcon className="text-orange" />
|
||||
if (variant.featured === false)
|
||||
return <Minus className="size-4 text-white-60" />
|
||||
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="text-white-95">{variant.label}</p>
|
||||
if (variant.badge) {
|
||||
@@ -339,24 +319,6 @@ 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">
|
||||
<Info className="ml-2 flex-shrink-0 text-white-40 transition-colors hover:text-white-60" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const ComparisonTable = () => {
|
||||
const products: Array<keyof Feature> = [
|
||||
'keycardShell',
|
||||
@@ -366,10 +328,10 @@ const ComparisonTable = () => {
|
||||
]
|
||||
|
||||
return (
|
||||
<section className="mx-auto max-w-[1352px] overflow-x-hidden pt-[120px] text-white-95 lg:pt-[200px]">
|
||||
<h1 className="px-3 font-lora text-32 text-white-95 min-[1512px]:px-0">
|
||||
<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.
|
||||
</h1>
|
||||
</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,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">
|
||||
@@ -380,7 +342,7 @@ const ComparisonTable = () => {
|
||||
<div className="p-4 pb-5">Ledger</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-28 border border-white-12 bg-white-3 px-6">
|
||||
<div className="rounded-28 border border-white-8 bg-white-4 px-6">
|
||||
{features.map((feature, index) => {
|
||||
return (
|
||||
<div
|
||||
@@ -394,7 +356,7 @@ const ComparisonTable = () => {
|
||||
{feature.name.split(' ').slice(-1)[0]}
|
||||
{feature.tooltip && (
|
||||
<Tooltip label={feature.tooltip}>
|
||||
<Info className="ml-2 flex-shrink-0 text-white-40 transition-colors hover:text-white-60" />
|
||||
<InfoIcon className="ml-2 flex-shrink-0 text-white-40 transition-colors hover:text-white-60" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
@@ -406,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>
|
||||
@@ -1,8 +1,10 @@
|
||||
import * as Accordion from '@radix-ui/react-accordion'
|
||||
import { ChevronDownIcon } from '@status-im/icons/20'
|
||||
import { TwitterIcon } from '@status-im/icons/social'
|
||||
import { ButtonLink } from '~components/button-link'
|
||||
import { ChevronDown, Discord, X } from '~icons'
|
||||
import { Image } from '~components/image'
|
||||
import { Link } from '~components/link'
|
||||
import { cx } from 'cva'
|
||||
import Image from 'next/image'
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
@@ -12,8 +14,19 @@ const faqs = [
|
||||
},
|
||||
{
|
||||
question: 'Which assets does Keycard support?',
|
||||
answer:
|
||||
'Keycard supports a wide range of cryptocurrencies including Bitcoin, Ethereum, and many ERC-20 tokens.',
|
||||
answer: (
|
||||
<>
|
||||
Keycard works seamlessly with Ethereum and EVM based assets. It supports
|
||||
all assets on{' '}
|
||||
<Link
|
||||
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
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: 'Are my funds safe if I lose my Keycard?',
|
||||
@@ -26,17 +39,18 @@ const faqs = [
|
||||
'Keycard is compatible with various software wallets. Please check our documentation for a full list.',
|
||||
},
|
||||
{
|
||||
question: 'What happens if I lose access to my Keycard?',
|
||||
answer: 'You can recover your assets using your backup seed phrase.',
|
||||
question: 'Why should I get a Keycard?',
|
||||
answer:
|
||||
'Keycard is a secure, pocket-sized hardware wallet with a provably genuine, immutable chip to protect your digital assets.',
|
||||
},
|
||||
]
|
||||
|
||||
const Faqs = () => {
|
||||
return (
|
||||
<section className="mx-auto max-w-[1352px]">
|
||||
<section className="mx-auto max-w-[1352px] pt-[144px]">
|
||||
<div className="flex flex-col items-start justify-between px-3 lg:flex-row lg:items-end min-[1512px]:px-0">
|
||||
<div className="flex w-full flex-col lg:pt-[200px]">
|
||||
<div className="block pt-[120px] lg:hidden">
|
||||
<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-mobile.png"
|
||||
alt="FAQ Background"
|
||||
@@ -45,9 +59,9 @@ const Faqs = () => {
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<h1 className="font-lora text-32 text-white-95">
|
||||
<h2 className="mt-14 font-lora text-32 text-white-95 lg:mt-0">
|
||||
Frequently asked questions
|
||||
</h1>
|
||||
</h2>
|
||||
<Accordion.Root
|
||||
className="flex-1 pt-8 lg:max-w-[549px] lg:pt-14"
|
||||
type="single"
|
||||
@@ -69,34 +83,35 @@ const Faqs = () => {
|
||||
)}
|
||||
>
|
||||
{faq.question}
|
||||
<div className="rounded-[10px] border border-white-12 bg-white-8 p-[6px]">
|
||||
<ChevronDown className="text-white-95 transition-transform duration-300 ease-[cubic-bezier(0.87,_0,_0.13,_1)] group-data-[state=open]:rotate-180" />
|
||||
<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>
|
||||
</Accordion.Header>
|
||||
<Accordion.Content
|
||||
className={cx(
|
||||
'overflow-hidden text-16 font-300 text-white-80 data-[state=closed]:animate-slideUp data-[state=open]:animate-slideDown',
|
||||
'overflow-hidden pr-14 text-16 font-300 text-white-80 data-[state=closed]:animate-slideUp data-[state=open]:animate-slideDown',
|
||||
)}
|
||||
>
|
||||
<span className="flex pt-4">{faq.answer}</span>
|
||||
<div className="pt-4">{faq.answer}</div>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
))}
|
||||
</Accordion.Root>
|
||||
</div>
|
||||
<div className="flex flex-col items-end">
|
||||
<div className="relative -right-1/4 -mt-48 hidden translate-x-[156px] translate-y-10 lg:block">
|
||||
<div className="absolute top-32 z-10 h-1/2 w-full bg-gradient-to-t from-[transparent] to-dark-100" />
|
||||
<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.png"
|
||||
alt="FAQ Background"
|
||||
width={736}
|
||||
height={736}
|
||||
height={790}
|
||||
width={1010}
|
||||
className="relative right-[-245px] top-[-40px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-14 flex w-full flex-col gap-6 rounded-28 border border-white-8 bg-white-3 p-6 pt-5 lg:mt-0 lg:max-w-[320px]">
|
||||
<div className="mt-14 flex flex-1 flex-col gap-6 rounded-28 border border-white-8 bg-white-4 p-6 pt-5 lg:mt-10 lg:max-w-[320px]">
|
||||
<div className="flex flex-col gap-[6px]">
|
||||
<p className="font-lora text-24 font-400 text-white-95">
|
||||
Still have questions?
|
||||
@@ -107,16 +122,11 @@ const Faqs = () => {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<ButtonLink href="/" variant="secondary">
|
||||
Get in touch
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
href="https://discord.com" // TODO: fix this link
|
||||
href="mailto:support@keycard.tech"
|
||||
variant="secondary"
|
||||
className="px-[9px]"
|
||||
aria-label="Keycard on Discord"
|
||||
>
|
||||
<Discord />
|
||||
Get in touch
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
href="https://x.com/keycard_"
|
||||
@@ -124,7 +134,7 @@ const Faqs = () => {
|
||||
className="px-[9px]"
|
||||
aria-label="Keycard on X"
|
||||
>
|
||||
<X />
|
||||
<TwitterIcon />
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,11 +1,13 @@
|
||||
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 Image from 'next/image'
|
||||
import { Image } from '~components/image'
|
||||
|
||||
const Hero = () => {
|
||||
return (
|
||||
<section className="relative flex h-[calc(100svh-80px)] flex-col justify-end overflow-clip rounded-t-28 border border-white-8 bg-white-3 px-0 pt-0 full-view-port lg:h-auto lg:px-[72px] lg:py-20 lg:remove-full-view-port">
|
||||
<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"
|
||||
@@ -23,14 +25,16 @@ const Hero = () => {
|
||||
priority
|
||||
/>
|
||||
|
||||
<div className="relative z-10 flex max-w-[434px] flex-col px-5 lg:pt-0">
|
||||
<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">
|
||||
A card shaped <br /> hardware wallet
|
||||
Lightweight design
|
||||
<br />
|
||||
heavyweight security
|
||||
</h1>
|
||||
<p className="pb-8 text-20 font-300 text-white-80">
|
||||
Store and trade your crypto with a simple, secure and slim hardware
|
||||
wallet.
|
||||
Trade swiftly and safely back up your assets with Keycard hardware
|
||||
wallets.
|
||||
</p>
|
||||
<div className="flex space-x-4">
|
||||
<BuyKeycardDialog>
|
||||
@@ -41,7 +45,10 @@ const Hero = () => {
|
||||
</ButtonLink>
|
||||
</div>
|
||||
<p className="pb-8 pt-6 text-16 font-300 text-white-60 lg:pb-0">
|
||||
From $25
|
||||
From{' '}
|
||||
{formatPrice({
|
||||
amount: KEYCARD_PRODUCTS.ONE_CARD_SET.price,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,31 +1,33 @@
|
||||
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 { Github, Infinity, Shield } from '~icons'
|
||||
import { InfinityIcon } from '~icons/infinity'
|
||||
import { cx } from 'cva'
|
||||
import Image from 'next/image'
|
||||
import { createElement } from 'react'
|
||||
|
||||
const features = [
|
||||
const FEATURES = [
|
||||
{
|
||||
title: 'Best in class security',
|
||||
title: 'Best-in-class security',
|
||||
description:
|
||||
'Our secure element has the highest level of security EAL6+ certified by Common Criteria.',
|
||||
'Keycard secure element has the highest level of certification provided by Common Criteria.',
|
||||
image: '/assets/feature-keycard.png',
|
||||
imageClassName: '!justify-start',
|
||||
badge: { icon: Shield, text: 'EAL 6+', gradient: true },
|
||||
badge: { icon: FirmwareIcon, text: 'EAL 6+', gradient: true },
|
||||
className: 'row-span-2 col-span-1',
|
||||
},
|
||||
{
|
||||
title: '100% open source',
|
||||
title: 'Fully open source',
|
||||
description:
|
||||
'We have nothing to hide! Our software, hardware and construction is fully open source.',
|
||||
button: { icon: Github, text: 'View on GitHub' },
|
||||
'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: 'Fully airgapped',
|
||||
title: 'Airgapped',
|
||||
description:
|
||||
"Through Keycard Shell's camera or Keycard's contactless nature, our products are truly airgapped.",
|
||||
'Through Keycard Shell’s camera or Keycard’s 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',
|
||||
@@ -40,8 +42,8 @@ const features = [
|
||||
{
|
||||
title: 'Easy to backup',
|
||||
description:
|
||||
'Create cards to back up your master key and store them in a safe place instead of the typical piece of paper.',
|
||||
badge: { icon: Infinity, text: 'backups' },
|
||||
'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',
|
||||
},
|
||||
{
|
||||
@@ -65,18 +67,18 @@ const UnderlinedWord = ({ children }: { children: React.ReactNode }) => (
|
||||
const KeycardFeatures = () => {
|
||||
return (
|
||||
<section className="max-w-[1352px] px-3 pt-[120px] lg:mx-auto lg:pt-[200px] min-[1512px]:px-0">
|
||||
<h1 className="max-w-[665px] font-lora text-32 text-white-95">
|
||||
<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.
|
||||
</h1>
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 gap-5 pt-20 lg:grid-cols-4 lg:gap-6">
|
||||
{features.map((feature, index) => (
|
||||
{FEATURES.map((feature, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={cx([
|
||||
'relative flex flex-col justify-between overflow-clip rounded-28 bg-white-3 outline outline-1 outline-white-8',
|
||||
'relative flex flex-col justify-between overflow-clip rounded-28 bg-white-4 outline outline-1 outline-white-8',
|
||||
feature.className,
|
||||
])}
|
||||
>
|
||||
@@ -96,7 +98,7 @@ const KeycardFeatures = () => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-6 pt-5 lg:pt-6">
|
||||
<div className="p-6 pt-5">
|
||||
<div>
|
||||
<h3 className="mb-[6px] font-lora text-24 font-400">
|
||||
{feature.title}
|
||||
@@ -118,8 +120,8 @@ const KeycardFeatures = () => {
|
||||
)}
|
||||
{feature.button && (
|
||||
<ButtonLink
|
||||
href="https://github.com/status-im/status-keycard"
|
||||
className="mt-4 font-500 [&_path]:hover:fill-white-dark"
|
||||
href="https://github.com/keycard-tech/keycard-pro"
|
||||
className="font-500 [&_path]:hover:fill-white-dark"
|
||||
variant="white"
|
||||
icon={<feature.button.icon />}
|
||||
>
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Button } from '~components/button'
|
||||
import { ButtonLink } from '~components/button-link'
|
||||
import { GetNotifiedDialog } from '~components/get-notified-dialog'
|
||||
import Image from 'next/image'
|
||||
import { Image } from '~components/image'
|
||||
|
||||
const KeycardShell = () => {
|
||||
return (
|
||||
<section className="relative flex flex-col justify-end overflow-hidden rounded-b-28 border border-t-0 border-white-8 bg-white-3 backdrop-blur-[20px] full-view-port lg:flex-row lg:items-center lg:justify-normal lg:remove-full-view-port">
|
||||
<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="900"
|
||||
height="600"
|
||||
className="object-contain"
|
||||
width="758"
|
||||
height="758"
|
||||
className="relative left-0 top-[-86px] object-cover object-center xl:top-[-128px] xl:object-contain"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
@@ -26,18 +26,18 @@ const KeycardShell = () => {
|
||||
priority
|
||||
/>
|
||||
|
||||
<div className="relative z-10 -mt-16 flex max-w-[434px] flex-col px-5 pb-8 pt-0 lg:mt-0 lg:py-20 lg:pl-0">
|
||||
<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>
|
||||
<h1 className="flex pb-8 font-lora text-32 font-400 lg:pb-4 lg:text-48">
|
||||
One device for all
|
||||
<p className="flex pb-8 font-lora text-32 font-400 lg:pb-4 lg:text-48">
|
||||
One secure device,
|
||||
<br />
|
||||
your Keycards
|
||||
</h1>
|
||||
infinite backups
|
||||
</p>
|
||||
<p className="pb-8 text-20 font-300 text-white-80">
|
||||
A modular and stateless hardware wallet that
|
||||
<br /> relies on Keycard as the secure element.
|
||||
Unrivalled security with an infinite number of removable Keycards,
|
||||
each with their own key.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<GetNotifiedDialog>
|
||||
@@ -47,7 +47,10 @@ const KeycardShell = () => {
|
||||
Learn more
|
||||
</ButtonLink>
|
||||
</div>
|
||||
<p className="pt-6 text-16 font-300 text-white-60">Coming 2025</p>
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
After Width: | Height: | Size: 372 KiB |
@@ -0,0 +1,28 @@
|
||||
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 { Faqs } from './_components/faqs'
|
||||
import { Hero } from './_components/hero'
|
||||
import { KeycardFeatures } from './_components/keycard-features'
|
||||
import { KeycardShell } from './_components/keycard-shell'
|
||||
|
||||
export const metadata = Metadata({
|
||||
title: {
|
||||
absolute: 'Keycard: Secure Crypto & Hardware Wallet',
|
||||
},
|
||||
})
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<AutoOpenBuyKeycardDialog />
|
||||
<Hero />
|
||||
<KeycardShell />
|
||||
<KeycardFeatures />
|
||||
<ComparisonTable />
|
||||
<Faqs />
|
||||
<BuyCards />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { baseComponents } from '~/app/_components/content'
|
||||
import { getPostBySlug, getPostSlugs } from '~/app/_lib/ghost'
|
||||
import { Metadata } from '~/app/_metadata'
|
||||
import { Breadcrumbs } from '~/app/docs/_components/breadcrumbs'
|
||||
import { formatDate } from '~/app/_utils/format-date'
|
||||
import { Breadcrumbs } from '~/app/(with-navigation)/docs/_components/breadcrumbs'
|
||||
import { Image } from '~components/image'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { createElement, Fragment } from 'react'
|
||||
import rehypeParse from 'rehype-parse'
|
||||
@@ -8,8 +11,6 @@ import rehypeReact from 'rehype-react'
|
||||
import { unified } from 'unified'
|
||||
import { PostAuthor } from '../_components/post-author'
|
||||
import { PostTag } from '../_components/post-tag'
|
||||
import { getPostBySlug, getPostSlugs } from '../../_lib/ghost'
|
||||
import { formatDate } from '../../_utils/format-date'
|
||||
|
||||
export const revalidate = 3600 // 1 hour
|
||||
export const dynamicParams = true
|
||||
@@ -98,10 +99,12 @@ export default async function BlogDetailPage(props: Props) {
|
||||
</div>
|
||||
|
||||
<div className="mx-auto w-full max-w-[1504px] px-0 py-6 xl:py-10">
|
||||
<img
|
||||
<Image
|
||||
src={post.feature_image!}
|
||||
className="aspect-[374/182] size-full rounded-28 object-cover xl:aspect-[1456/470]"
|
||||
alt={post.feature_image_alt!}
|
||||
className="aspect-[83/38] size-full rounded-28 object-cover"
|
||||
width={664}
|
||||
height={304}
|
||||
alt={post.feature_image_alt! ?? post.title}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Image } from '~components/image'
|
||||
import { cva } from 'cva'
|
||||
import Image from 'next/image'
|
||||
|
||||
type Props = {
|
||||
size: '56' | '32' | '20'
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { PostOrPage } from '@tryghost/content-api'
|
||||
import { formatDate } from '~/app/_utils/format-date'
|
||||
import { Image } from '~components/image'
|
||||
import { Link } from '~components/link'
|
||||
import { formatDate } from '../../_utils/format-date'
|
||||
import { PostAuthor } from './post-author'
|
||||
import { PostTag } from './post-tag'
|
||||
|
||||
@@ -19,11 +20,12 @@ export const HighlightedPostCard = (props: Props) => {
|
||||
className="grid grid-cols-1 gap-5 2xl:grid-cols-3 2xl:gap-7"
|
||||
>
|
||||
<div className="col-span-2 w-full flex-[2] shrink-0">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
className="aspect-[366/206] size-full rounded-16 object-cover"
|
||||
<Image
|
||||
className="aspect-[286/161] size-full rounded-16 object-cover"
|
||||
width={1144}
|
||||
height={644}
|
||||
src={post.feature_image!}
|
||||
alt={post.feature_image_alt!}
|
||||
alt={post.feature_image_alt! ?? post.title}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import type { PostOrPage, PostsOrPages } from '@tryghost/content-api'
|
||||
import { useMemo } from 'react'
|
||||
import { match } from 'ts-pattern'
|
||||
import { useInfiniteLoading } from '../_hooks/use-infinite-loading'
|
||||
import {
|
||||
getPosts,
|
||||
getPostsByAuthorSlug,
|
||||
getPostsByTagSlug,
|
||||
} from '../../_lib/ghost'
|
||||
} from '~/app/_lib/ghost'
|
||||
import { useMemo } from 'react'
|
||||
import { match } from 'ts-pattern'
|
||||
import { useInfiniteLoading } from '../_hooks/use-infinite-loading'
|
||||
import { PostGrid } from './post-grid'
|
||||
|
||||
type Props = {
|
||||
@@ -33,7 +33,7 @@ export const PostAuthor = (props: {
|
||||
tabIndex={0}
|
||||
onClick={handleNavigation}
|
||||
onKeyDown={handleNavigation}
|
||||
className="flex cursor-pointer gap-2 text-white-95 hover:text-orange"
|
||||
className="flex cursor-pointer gap-2 leading-6 text-white-95 hover:text-orange"
|
||||
>
|
||||
<Avatar
|
||||
size="20"
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { PostOrPage } from '@tryghost/content-api'
|
||||
import { Link } from '../../_components/link'
|
||||
import { formatDate } from '../../_utils/format-date'
|
||||
import { formatDate } from '~/app/_utils/format-date'
|
||||
import { Image } from '~components/image'
|
||||
import { Link } from '~components/link'
|
||||
import { PostAuthor } from './post-author'
|
||||
import { PostTag } from './post-tag'
|
||||
|
||||
@@ -18,7 +19,7 @@ export const PostCard = (props: PostCardProps) => {
|
||||
return (
|
||||
<Link
|
||||
href={`/blog/${post.slug}`}
|
||||
className="flex flex-col rounded-16 border border-white-8 bg-white-3 transition-all hover:scale-[101%]"
|
||||
className="flex flex-col rounded-16 border border-white-8 bg-white-4 transition-all hover:scale-[101%]"
|
||||
>
|
||||
<div className="flex grow flex-col gap-4 p-4">
|
||||
{showTag && (
|
||||
@@ -49,10 +50,12 @@ export const PostCard = (props: PostCardProps) => {
|
||||
|
||||
<div className="w-full px-2 pb-2">
|
||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
||||
<img
|
||||
className="aspect-[334/188] size-full rounded-12 object-cover"
|
||||
<Image
|
||||
className="aspect-[350/190] size-full rounded-12 object-cover"
|
||||
width={350}
|
||||
height={190}
|
||||
src={post.feature_image!}
|
||||
alt={post.feature_image_alt!}
|
||||
alt={post.feature_image_alt! ?? post.title}
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
@@ -60,7 +63,7 @@ export const PostCard = (props: PostCardProps) => {
|
||||
}
|
||||
|
||||
export const PostCardSkeleton = () => (
|
||||
<div className="flex flex-col rounded-16 border border-white-8 bg-white-3 transition-all hover:scale-[101%]">
|
||||
<div className="flex flex-col rounded-16 border border-white-8 bg-white-4 transition-all hover:scale-[101%]">
|
||||
<div className="flex grow flex-col gap-[13px] p-4">Loading...</div>
|
||||
</div>
|
||||
)
|
||||
@@ -41,7 +41,7 @@ function PostTag(props: Props, ref: Ref<HTMLButtonElement | HTMLDivElement>) {
|
||||
|
||||
const styles = cva({
|
||||
base: [
|
||||
'inline-flex shrink-0 items-center justify-center gap-1 border border-white-8 bg-white-3 font-500 transition-all hover:border-white-12 hover:bg-white-8 text-white-95',
|
||||
'inline-flex shrink-0 items-center justify-center gap-1 border border-white-8 bg-white-4 font-500 transition-all hover:border-white-12 hover:bg-white-8 text-white-95',
|
||||
'outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-orange focus-visible:ring-offset-2',
|
||||
'disabled:cursor-default disabled:opacity-[.3]',
|
||||
],
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useIntersectionObserver } from '~/app/_hooks/use-intersection-observer'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useIntersectionObserver } from './use-intersection-observer'
|
||||
|
||||
export function useInfiniteLoading({
|
||||
rootMargin,
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RefObject, useEffect, useState } from 'react'
|
||||
|
||||
export function useResizeObserver(elementRef: RefObject<Element>) {
|
||||
export function useResizeObserver(elementRef: RefObject<Element | null>) {
|
||||
const [columnCount, setColumnCount] = useState(1)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Breadcrumbs } from '~/app/docs/_components/breadcrumbs'
|
||||
import { getAuthorSlugs, getPostsByAuthorSlug } from '~/app/_lib/ghost'
|
||||
import { Metadata } from '~/app/_metadata'
|
||||
import { Breadcrumbs } from '~/app/(with-navigation)/docs/_components/breadcrumbs'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { Avatar } from '../../_components/avatar'
|
||||
import { InfinitePostGrid } from '../../_components/infinite-post-grid'
|
||||
import { getAuthorSlugs, getPostsByAuthorSlug } from '../../../_lib/ghost'
|
||||
|
||||
export const revalidate = 3600 // 1 hour
|
||||
export const dynamicParams = true
|
||||
@@ -13,6 +14,17 @@ export async function generateStaticParams() {
|
||||
return slugs.map(slug => ({ slug })) satisfies Array<Awaited<Props['params']>>
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: Props) {
|
||||
const response = await getPostsByAuthorSlug((await params).slug)
|
||||
if (!response) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
return Metadata({
|
||||
title: response.author.name ?? response.author.slug,
|
||||
})
|
||||
}
|
||||
|
||||
type Props = {
|
||||
params: Promise<{ slug: string }>
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Metadata } from '../_metadata'
|
||||
import { Metadata } from '~/app/_metadata'
|
||||
import { Providers } from './_providers'
|
||||
|
||||
type Props = {
|
||||
@@ -7,8 +7,8 @@ type Props = {
|
||||
|
||||
export const metadata = Metadata({
|
||||
title: {
|
||||
template: '%s — Keycard',
|
||||
default: 'News',
|
||||
template: '%s - Blog - Keycard',
|
||||
default: 'Blog',
|
||||
},
|
||||
description: 'Thoughts, stories and ideas.',
|
||||
})
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Breadcrumbs } from '~/app/docs/_components/breadcrumbs'
|
||||
import { getPostsByTagSlug, getTagSlugs } from '~/app/_lib/ghost'
|
||||
import { Metadata } from '~/app/_metadata'
|
||||
import { Breadcrumbs } from '~/app/(with-navigation)/docs/_components/breadcrumbs'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { InfinitePostGrid } from '../../_components/infinite-post-grid'
|
||||
import { getPostsByTagSlug, getTagSlugs } from '../../../_lib/ghost'
|
||||
|
||||
export const revalidate = 3600 // 1 hour
|
||||
export const dynamicParams = true
|
||||
@@ -12,6 +13,17 @@ export async function generateStaticParams() {
|
||||
return slugs.map(slug => ({ slug })) satisfies Array<Awaited<Props['params']>>
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: Props) {
|
||||
const response = await getPostsByTagSlug((await params).slug)
|
||||
if (!response) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
return Metadata({
|
||||
title: response.tag.name ?? response.tag.slug,
|
||||
})
|
||||
}
|
||||
|
||||
type Props = {
|
||||
params: Promise<{ slug: string }>
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import { formatDate } from '~/app/_utils/format-date'
|
||||
import config from '~/config/docs.json'
|
||||
import { Link } from '~components/link'
|
||||
import { notFound } from 'next/navigation'
|
||||
import React from 'react'
|
||||
import { Breadcrumbs } from '../_components/breadcrumbs'
|
||||
import { generateBreadcrumbs } from '../_utils/generate-breadcrumbs'
|
||||
import { getDocumentationArticle } from '../_utils/get-documentation-article'
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChevronRightIcon } from '@status-im/icons/20'
|
||||
import { Link } from '~/app/_components/link'
|
||||
import { ChevronRight } from '~/app/_icons/chevron-right'
|
||||
import { Fragment } from 'react'
|
||||
|
||||
type Props = {
|
||||
@@ -13,7 +13,7 @@ const Breadcrumbs = (props: Props) => {
|
||||
const { items } = props
|
||||
|
||||
return (
|
||||
<div className="mx-[-8px] border-y border-white-12 px-10 py-3">
|
||||
<div className="mx-[-8px] border-y border-white-12 px-5 py-3 lg:px-8">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 overflow-x-scroll whitespace-nowrap scrollbar-none">
|
||||
{items.map((item, index) => {
|
||||
@@ -37,7 +37,7 @@ const Breadcrumbs = (props: Props) => {
|
||||
{item.label}
|
||||
</Link>
|
||||
<span className="size-5">
|
||||
<ChevronRight />
|
||||
<ChevronRightIcon />
|
||||
</span>
|
||||
</Fragment>
|
||||
)
|
||||
@@ -1,11 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import { CheckIcon, CopyIcon } from '@status-im/icons/20'
|
||||
import { cx } from 'cva'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { onlyText } from 'react-children-utilities'
|
||||
import { useCopyToClipboard } from '../_hooks/use-copy-to-clipboard'
|
||||
import { CheckIcon } from '../_icons/check-icon'
|
||||
import { CopyIcon } from '../_icons/copy-icon'
|
||||
|
||||
export function CodeBlock(props: React.ComponentProps<'figure'>) {
|
||||
const [, copy] = useCopyToClipboard()
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BulletIcon } from '@status-im/icons/20'
|
||||
import { Link } from '~components/link'
|
||||
import { cx } from 'cva'
|
||||
import { Children, cloneElement, ComponentProps } from 'react'
|
||||
import { match } from 'ts-pattern'
|
||||
import { BulletIcon } from '../_icons/bullet-icon'
|
||||
import { renderText } from '../_utils/render-text'
|
||||
import { AnchorLink } from './anchor-link'
|
||||
import { CodeBlock } from './code-block'
|
||||
@@ -150,7 +150,7 @@ export const baseComponents = {
|
||||
{...rest}
|
||||
className={cx(
|
||||
blockquoteParagraphTextSize[size],
|
||||
'mt-5 border-l border-dashed border-white-3 !pt-0 pl-6',
|
||||
'mt-5 border-l border-dashed border-white-4 !pt-0 pl-6',
|
||||
)}
|
||||
>
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import * as Accordion from '@radix-ui/react-accordion'
|
||||
import { ChevronRightIcon } from '@status-im/icons/20'
|
||||
import { Link } from '~components/link'
|
||||
import { ChevronRight } from '~icons'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { decodeUriComponent } from '../_utils/decode-uri-component'
|
||||
@@ -39,7 +39,7 @@ const Sidenav: React.FC<SidenavProps> = ({ items }) => {
|
||||
}, [pathname, items])
|
||||
|
||||
return (
|
||||
<nav className="flex w-[255px] flex-col items-start justify-start border-r border-white-12 px-8 py-6">
|
||||
<nav className="flex w-[255px] flex-col items-start justify-start border-r border-white-12 p-6">
|
||||
<Accordion.Root
|
||||
type="single"
|
||||
collapsible
|
||||
@@ -84,9 +84,12 @@ const SidenavItem = (props: SidenavItemProps) => {
|
||||
<Accordion.Item value={title}>
|
||||
<div>
|
||||
<div className="flex gap-0.5">
|
||||
<Accordion.Trigger className="group flex items-center gap-0.5">
|
||||
<Accordion.Trigger
|
||||
className="group flex items-center gap-0.5"
|
||||
aria-label={`Toggle ${title} section`}
|
||||
>
|
||||
<div className="transition-transform group-aria-expanded:rotate-90">
|
||||
<ChevronRight />
|
||||
<ChevronRightIcon />
|
||||
</div>
|
||||
</Accordion.Trigger>
|
||||
<Link
|
||||
@@ -33,7 +33,9 @@ export function renderText(params: {
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
return cloneElement(child as React.ReactElement, { parent })
|
||||
return cloneElement(child as React.ReactElement<{ parent?: string }>, {
|
||||
parent,
|
||||
})
|
||||
}
|
||||
|
||||
return child
|
||||
@@ -7,7 +7,7 @@ type Props = {
|
||||
|
||||
export default async function DocsLayout({ children }: Props) {
|
||||
return (
|
||||
<div className="relative mt-[84px] min-h-screen w-full">
|
||||
<div className="relative mt-[84px] w-full">
|
||||
<div className="absolute bottom-0 left-0 top-12 border-white-12 lg:border-r">
|
||||
<div className="sticky top-[100px] hidden lg:block">
|
||||
<Sidenav items={config} />
|
||||
@@ -1,49 +1,49 @@
|
||||
import Image from 'next/image'
|
||||
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 = () => {
|
||||
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',
|
||||
},
|
||||
]
|
||||
|
||||
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">
|
||||
@@ -62,7 +62,7 @@ const AirGapped = () => {
|
||||
|
||||
<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">Something about airgapped</h2>
|
||||
<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>
|
||||
@@ -73,7 +73,7 @@ const AirGapped = () => {
|
||||
TRANSACTION SIGNING WITH
|
||||
</div>
|
||||
<div className="grid grid-cols-2">
|
||||
{wallets.map((wallet, index) => (
|
||||
{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"
|
||||
@@ -1,4 +1,4 @@
|
||||
import Image from 'next/image'
|
||||
import { Image } from '~components/image'
|
||||
|
||||
const Comparision = () => {
|
||||
return (
|
||||
@@ -1,16 +1,18 @@
|
||||
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'
|
||||
import { Github, Shield } from '~icons'
|
||||
|
||||
const features = [
|
||||
const FEATURES = [
|
||||
{
|
||||
name: 'Unparalleled security',
|
||||
description: `Keycard's secure element has passed Common Criteria's EAL6+ certification.`,
|
||||
description: `Keycard's secure element has passed Common Criteria EAL6+ certification.`,
|
||||
badge: 'EAL 6+',
|
||||
},
|
||||
{
|
||||
name: 'Stateless',
|
||||
description: 'Your info and private keys are never stored on the device.',
|
||||
description:
|
||||
'Your information and private keys are never stored on the device.',
|
||||
tags: ['Keys stored on Keycard'],
|
||||
},
|
||||
{
|
||||
@@ -22,9 +24,9 @@ const features = [
|
||||
|
||||
const FeaturesCard = () => {
|
||||
return (
|
||||
<section className="px-3 pt-[120px] lg:pt-0">
|
||||
<div className="grid grid-cols-1 gap-8 rounded-28 border border-white-8 bg-white-3 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) => (
|
||||
<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}
|
||||
@@ -35,7 +37,7 @@ const FeaturesCard = () => {
|
||||
</p>
|
||||
|
||||
{Boolean(feature.badge) && (
|
||||
<Tag gradient icon={<Shield />}>
|
||||
<Tag gradient icon={<FirmwareIcon />}>
|
||||
{feature.badge}
|
||||
</Tag>
|
||||
)}
|
||||
@@ -51,7 +53,7 @@ const FeaturesCard = () => {
|
||||
<div className="flex flex-col gap-6">
|
||||
<div>
|
||||
<h2 className="pb-[6px] font-lora text-24 font-400 text-white-95">
|
||||
Open source security
|
||||
Fully open source
|
||||
</h2>
|
||||
<p className="text-16 font-300 text-white-60">
|
||||
Our software, hardware and construction are fully open source.
|
||||
@@ -59,10 +61,10 @@ const FeaturesCard = () => {
|
||||
</div>
|
||||
|
||||
<ButtonLink
|
||||
href="https://github.com/status-im/status-keycard"
|
||||
href="https://github.com/keycard-tech/keycard-pro"
|
||||
className="font-500 [&_path]:hover:fill-white-dark"
|
||||
variant="white"
|
||||
icon={<Github />}
|
||||
icon={<GithubIcon />}
|
||||
>
|
||||
View on Github
|
||||
</ButtonLink>
|
||||
@@ -1,14 +1,11 @@
|
||||
import { ButtonLink } from '~components/button-link'
|
||||
import { Image } from '~components/image'
|
||||
import { Tag } from '~components/tag'
|
||||
import { Tooltip } from '~components/tooltip'
|
||||
import { Github, Shield } from '~icons'
|
||||
import { cx } from 'cva'
|
||||
import Image from 'next/image'
|
||||
import { createElement } from 'react'
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: 'Runs forever',
|
||||
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',
|
||||
@@ -25,7 +22,7 @@ const features = [
|
||||
image: '/assets/keycard-shell/wallets/metamask.png',
|
||||
},
|
||||
{
|
||||
name: 'Rabbit',
|
||||
name: 'Rabby',
|
||||
image: '/assets/keycard-shell/wallets/rabbit.png',
|
||||
},
|
||||
{
|
||||
@@ -41,30 +38,29 @@ const features = [
|
||||
{
|
||||
title: 'Large display',
|
||||
description:
|
||||
'Enjoy full visibility of sensitive information like your seed phrase.',
|
||||
'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]:scale-75 lg:scale-100',
|
||||
className: 'row-span-2 col-span-1 flex-col-reverse gap-0 [&_img]:w-fit',
|
||||
},
|
||||
{
|
||||
title: 'Open source security',
|
||||
title: 'Air-gapped',
|
||||
description:
|
||||
'Our software, hardware and construction is fully open source.',
|
||||
button: { icon: Github, text: 'View on GitHub' },
|
||||
'Send transactions through the air via QR code standard ERC-4527.',
|
||||
badge: { text: 'ERC-4527' },
|
||||
className: 'col-span-1',
|
||||
},
|
||||
{
|
||||
title: 'Unparalleled security',
|
||||
title: 'Easy to backup',
|
||||
description:
|
||||
'Keycard’s secure element has passed Common Criteria EAL6+ certification.',
|
||||
badge: { icon: Shield, text: 'EAL 6+', gradient: true },
|
||||
'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:
|
||||
'Something here about how it’s really super cool and a great feature.',
|
||||
'Works out of the box with your favourite Bitcoin wallets.',
|
||||
className: 'col-span-1',
|
||||
icons: [
|
||||
{
|
||||
@@ -91,14 +87,14 @@ 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, robust and made to last
|
||||
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-3 outline outline-1 outline-white-8',
|
||||
'relative flex flex-col justify-between overflow-clip rounded-28 bg-white-4 outline outline-1 outline-white-8',
|
||||
feature.className,
|
||||
])}
|
||||
>
|
||||
@@ -121,16 +117,7 @@ const FeaturesGrid = () => {
|
||||
{feature.description}
|
||||
</p>
|
||||
|
||||
{feature.badge && (
|
||||
<Tag
|
||||
gradient={feature.badge.gradient}
|
||||
icon={
|
||||
feature.badge.icon && createElement(feature.badge.icon)
|
||||
}
|
||||
>
|
||||
{feature.badge.text}
|
||||
</Tag>
|
||||
)}
|
||||
{feature.badge && <Tag>{feature.badge.text}</Tag>}
|
||||
{feature.icons && (
|
||||
<div className="flex items-center gap-3">
|
||||
{feature.icons?.map((icon, index) => (
|
||||
@@ -140,21 +127,12 @@ const FeaturesGrid = () => {
|
||||
alt={icon.name}
|
||||
width={40}
|
||||
height={40}
|
||||
className="transition-opacity duration-200 hover:opacity-[80%]"
|
||||
/>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{feature.button && (
|
||||
<ButtonLink
|
||||
href="https://github.com/status-im/status-keycard"
|
||||
className="font-500 [&_path]:hover:fill-white-dark"
|
||||
variant="white"
|
||||
icon={<feature.button.icon />}
|
||||
>
|
||||
{feature.button.text}
|
||||
</ButtonLink>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,17 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import { Arrow } from '~icons'
|
||||
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 Image from 'next/image'
|
||||
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.',
|
||||
'All the words clearly numbered and visible ready for you to write them down.',
|
||||
image: '/assets/keycard-shell/keycard-shell-feature-card-1.png',
|
||||
},
|
||||
{
|
||||
@@ -34,7 +34,7 @@ const features = [
|
||||
{
|
||||
name: 'Block USB data transfer',
|
||||
description:
|
||||
'Add an extra layer of security by preventing unwanted data transfers.',
|
||||
'Add an extra layer of safety every time you connect your cable to charge.',
|
||||
image: '/assets/keycard-shell/keycard-shell-feature-card-5.png',
|
||||
},
|
||||
{
|
||||
@@ -92,18 +92,20 @@ const FeaturesSlider = () => {
|
||||
</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:text-white-100 disabled:opacity-[40%]"
|
||||
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"
|
||||
>
|
||||
<Arrow className="-scale-x-100 transform" />
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
<button
|
||||
className="rounded-12 border border-white-12 bg-white-8 p-[10px] text-white-95 opacity-[100%] transition-all hover:text-white-100 disabled:opacity-[40%]"
|
||||
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"
|
||||
>
|
||||
<Arrow />
|
||||
<ArrowRightIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,7 +116,7 @@ const FeaturesSlider = () => {
|
||||
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-3 p-10">
|
||||
<div className="rounded-28 border border-white-8 bg-white-4 p-10">
|
||||
<Image
|
||||
src={feature.image}
|
||||
alt={feature.name}
|
||||
@@ -124,7 +126,7 @@ const FeaturesSlider = () => {
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-8">
|
||||
<p className="pb-[6px] text-left text-24 font-500 text-white-95">
|
||||
<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">
|
||||