Initial commit
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
NEXT_PUBLIC_DOCSEARCH_APP_ID=R2IYF7ETH7
|
||||
NEXT_PUBLIC_DOCSEARCH_API_KEY=599cec31baffa4868cae4e79f180729b
|
||||
NEXT_PUBLIC_DOCSEARCH_INDEX_NAME=docsearch
|
||||
3
.eslintrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
32
.gitignore
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
129
LICENSE.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Tailwind UI License
|
||||
|
||||
## Personal License
|
||||
|
||||
Tailwind Labs Inc. grants you an on-going, non-exclusive license to use the Components and Templates.
|
||||
|
||||
The license grants permission to **one individual** (the Licensee) to access and use the Components and Templates.
|
||||
|
||||
You **can**:
|
||||
|
||||
- Use the Components and Templates to create unlimited End Products.
|
||||
- Modify the Components and Templates to create derivative components and templates. Those components and templates are subject to this license.
|
||||
- Use the Components and Templates to create unlimited End Products for unlimited Clients.
|
||||
- Use the Components and Templates to create End Products where the End Product is sold to End Users.
|
||||
- Use the Components and Templates to create End Products that are open source and freely available to End Users.
|
||||
|
||||
You **cannot**:
|
||||
|
||||
- Use the Components and Templates to create End Products that are designed to allow an End User to build their own End Products using the Components and Templates or derivatives of the Components and Templates.
|
||||
- Re-distribute the Components and Templates or derivatives of the Components and Templates separately from an End Product, neither in code or as design assets.
|
||||
- Share your access to the Components and Templates with any other individuals.
|
||||
- Use the Components and Templates to produce anything that may be deemed by Tailwind Labs Inc, in their sole and absolute discretion, to be competitive or in conflict with the business of Tailwind Labs Inc.
|
||||
|
||||
### Example usage
|
||||
|
||||
Examples of usage **allowed** by the license:
|
||||
|
||||
- Creating a personal website by yourself.
|
||||
- Creating a website or web application for a client that will be owned by that client.
|
||||
- Creating a commercial SaaS application (like an invoicing app for example) where end users have to pay a fee to use the application.
|
||||
- Creating a commercial self-hosted web application that is sold to end users for a one-time fee.
|
||||
- Creating a web application where the primary purpose is clearly not to simply re-distribute the components (like a conference organization app that uses the components for its UI for example) that is free and open source, where the source code is publicly available.
|
||||
|
||||
Examples of usage **not allowed** by the license:
|
||||
|
||||
- Creating a repository of your favorite Tailwind UI components or templates (or derivatives based on Tailwind UI components or templates) and publishing it publicly.
|
||||
- Creating a React or Vue version of Tailwind UI and making it available either for sale or for free.
|
||||
- Create a Figma or Sketch UI kit based on the Tailwind UI component designs.
|
||||
- Creating a "website builder" project where end users can build their own websites using components or templates included with or derived from Tailwind UI.
|
||||
- Creating a theme, template, or project starter kit using the components or templates and making it available either for sale or for free.
|
||||
- Creating an admin panel tool (like [Laravel Nova](https://nova.laravel.com/) or [ActiveAdmin](https://activeadmin.info/)) that is made available either for sale or for free.
|
||||
|
||||
In simple terms, use Tailwind UI for anything you like as long as it doesn't compete with Tailwind UI.
|
||||
|
||||
### Personal License Definitions
|
||||
|
||||
Licensee is the individual who has purchased a Personal License.
|
||||
|
||||
Components and Templates are the source code and design assets made available to the Licensee after purchasing a Tailwind UI license.
|
||||
|
||||
End Product is any artifact produced that incorporates the Components or Templates or derivatives of the Components or Templates.
|
||||
|
||||
End User is a user of an End Product.
|
||||
|
||||
Client is an individual or entity receiving custom professional services directly from the Licensee, produced specifically for that individual or entity. Customers of software-as-a-service products are not considered clients for the purpose of this document.
|
||||
|
||||
## Team License
|
||||
|
||||
Tailwind Labs Inc. grants you an on-going, non-exclusive license to use the Components and Templates.
|
||||
|
||||
The license grants permission for **up to 25 Employees and Contractors of the Licensee** to access and use the Components and Templates.
|
||||
|
||||
You **can**:
|
||||
|
||||
- Use the Components and Templates to create unlimited End Products.
|
||||
- Modify the Components and Templates to create derivative components and templates. Those components and templates are subject to this license.
|
||||
- Use the Components and Templates to create unlimited End Products for unlimited Clients.
|
||||
- Use the Components and Templates to create End Products where the End Product is sold to End Users.
|
||||
- Use the Components and Templates to create End Products that are open source and freely available to End Users.
|
||||
|
||||
You **cannot**:
|
||||
|
||||
- Use the Components or Templates to create End Products that are designed to allow an End User to build their own End Products using the Components or Templates or derivatives of the Components or Templates.
|
||||
- Re-distribute the Components or Templates or derivatives of the Components or Templates separately from an End Product.
|
||||
- Use the Components or Templates to create End Products that are the property of any individual or entity other than the Licensee or Clients of the Licensee.
|
||||
- Use the Components or Templates to produce anything that may be deemed by Tailwind Labs Inc, in their sole and absolute discretion, to be competitive or in conflict with the business of Tailwind Labs Inc.
|
||||
|
||||
### Example usage
|
||||
|
||||
Examples of usage **allowed** by the license:
|
||||
|
||||
- Creating a website for your company.
|
||||
- Creating a website or web application for a client that will be owned by that client.
|
||||
- Creating a commercial SaaS application (like an invoicing app for example) where end users have to pay a fee to use the application.
|
||||
- Creating a commercial self-hosted web application that is sold to end users for a one-time fee.
|
||||
- Creating a web application where the primary purpose is clearly not to simply re-distribute the components or templates (like a conference organization app that uses the components or a template for its UI for example) that is free and open source, where the source code is publicly available.
|
||||
|
||||
Examples of use **not allowed** by the license:
|
||||
|
||||
- Creating a repository of your favorite Tailwind UI components or template (or derivatives based on Tailwind UI components or templates) and publishing it publicly.
|
||||
- Creating a React or Vue version of Tailwind UI and making it available either for sale or for free.
|
||||
- Creating a "website builder" project where end users can build their own websites using components or templates included with or derived from Tailwind UI.
|
||||
- Creating a theme or template using the components or templates and making it available either for sale or for free.
|
||||
- Creating an admin panel tool (like [Laravel Nova](https://nova.laravel.com/) or [ActiveAdmin](https://activeadmin.info/)) that is made available either for sale or for free.
|
||||
- Creating any End Product that is not the sole property of either your company or a client of your company. For example your employees/contractors can't use your company Tailwind UI license to build their own websites or side projects.
|
||||
|
||||
### Team License Definitions
|
||||
|
||||
Licensee is the business entity who has purchased a Team License.
|
||||
|
||||
Components and Templates are the source code and design assets made available to the Licensee after purchasing a Tailwind UI license.
|
||||
|
||||
End Product is any artifact produced that incorporates the Components or Templates or derivatives of the Components or Templates.
|
||||
|
||||
End User is a user of an End Product.
|
||||
|
||||
Employee is a full-time or part-time employee of the Licensee.
|
||||
|
||||
Contractor is an individual or business entity contracted to perform services for the Licensee.
|
||||
|
||||
Client is an individual or entity receiving custom professional services directly from the Licensee, produced specifically for that individual or entity. Customers of software-as-a-service products are not considered clients for the purpose of this document.
|
||||
|
||||
## Enforcement
|
||||
|
||||
If you are found to be in violation of the license, access to your Tailwind UI account will be terminated, and a refund may be issued at our discretion. When license violation is blatant and malicious (such as intentionally redistributing the Components or Templates through private warez channels), no refund will be issued.
|
||||
|
||||
The copyright of the Components and Templates is owned by Tailwind Labs Inc. You are granted only the permissions described in this license; all other rights are reserved. Tailwind Labs Inc. reserves the right to pursue legal remedies for any unauthorized use of the Components or Templates outside the scope of this license.
|
||||
|
||||
## Liability
|
||||
|
||||
Tailwind Labs Inc.’s liability to you for costs, damages, or other losses arising from your use of the Components or Templates — including third-party claims against you — is limited to a refund of your license fee. Tailwind Labs Inc. may not be held liable for any consequential damages related to your use of the Components or Templates.
|
||||
|
||||
This Agreement is governed by the laws of the Province of Ontario and the applicable laws of Canada. Legal proceedings related to this Agreement may only be brought in the courts of Ontario. You agree to service of process at the e-mail address on your original order.
|
||||
|
||||
## Questions?
|
||||
|
||||
Unsure which license you need, or unsure if your use case is covered by our licenses?
|
||||
|
||||
Email us at [support@tailwindui.com](mailto:support@tailwindui.com) with your questions.
|
||||
48
README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Syntax
|
||||
|
||||
Syntax is a [Tailwind UI](https://tailwindui.com) site template built using [Tailwind CSS](https://tailwindcss.com) and [Next.js](https://nextjs.org).
|
||||
|
||||
## Getting started
|
||||
|
||||
To get started with this template, first install the npm dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
cp .env.example .env.local
|
||||
```
|
||||
|
||||
Next, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Finally, open [http://localhost:3000](http://localhost:3000) in your browser to view the website.
|
||||
|
||||
## Customizing
|
||||
|
||||
You can start editing this template by modifying the files in the `/src` folder. The site will auto-update as you edit these files.
|
||||
|
||||
## Global search
|
||||
|
||||
By default this template uses [Algolia DocSearch](https://docsearch.algolia.com) for the global search. DocSearch is free for open-source projects, and you can sign up for an account on their website. Once your DocSearch account is ready, update the following [environment variables](https://nextjs.org/docs/basic-features/environment-variables) in your project with the values provided by Algolia:
|
||||
|
||||
```
|
||||
NEXT_PUBLIC_DOCSEARCH_APP_ID=
|
||||
NEXT_PUBLIC_DOCSEARCH_API_KEY=
|
||||
NEXT_PUBLIC_DOCSEARCH_INDEX_NAME=
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This site template is a commercial product and is licensed under the [Tailwind UI license](https://tailwindui.com/license).
|
||||
|
||||
## Learn more
|
||||
|
||||
To learn more about the technologies used in this site template, see the following resources:
|
||||
|
||||
- [Tailwind CSS](https://tailwindcss.com/docs) - the official Tailwind CSS documentation
|
||||
- [Next.js](https://nextjs.org/docs) - the official Next.js documentation
|
||||
- [Headless UI](https://headlessui.dev) - the official Headless UI documentation
|
||||
- [Markdoc](https://markdoc.io) - the official Markdoc documentation
|
||||
- [DocSearch](https://docsearch.algolia.com) - the official DocSearch documentation
|
||||
8
jsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
28
markdoc/nodes.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Fence } from '@/components/Fence'
|
||||
import { nodes as defaultNodes } from '@markdoc/markdoc'
|
||||
|
||||
const nodes = {
|
||||
document: {
|
||||
render: undefined,
|
||||
},
|
||||
th: {
|
||||
...defaultNodes.th,
|
||||
attributes: {
|
||||
...defaultNodes.th.attributes,
|
||||
scope: {
|
||||
type: String,
|
||||
default: 'col',
|
||||
},
|
||||
},
|
||||
},
|
||||
fence: {
|
||||
render: Fence,
|
||||
attributes: {
|
||||
language: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default nodes
|
||||
47
markdoc/tags.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Callout } from '@/components/Callout'
|
||||
import { QuickLink, QuickLinks } from '@/components/QuickLinks'
|
||||
|
||||
const tags = {
|
||||
callout: {
|
||||
attributes: {
|
||||
title: { type: String },
|
||||
type: {
|
||||
type: String,
|
||||
default: 'note',
|
||||
matches: ['note', 'warning'],
|
||||
errorLevel: 'critical',
|
||||
},
|
||||
},
|
||||
render: Callout,
|
||||
},
|
||||
figure: {
|
||||
selfClosing: true,
|
||||
attributes: {
|
||||
src: { type: String },
|
||||
alt: { type: String },
|
||||
caption: { type: String },
|
||||
},
|
||||
render: ({ src, alt = '', caption }) => (
|
||||
<figure>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={src} alt={alt} />
|
||||
<figcaption>{caption}</figcaption>
|
||||
</figure>
|
||||
),
|
||||
},
|
||||
'quick-links': {
|
||||
render: QuickLinks,
|
||||
},
|
||||
'quick-link': {
|
||||
selfClosing: true,
|
||||
render: QuickLink,
|
||||
attributes: {
|
||||
title: { type: String },
|
||||
description: { type: String },
|
||||
icon: { type: String },
|
||||
href: { type: String },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default tags
|
||||
12
next.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const withMarkdoc = require('@markdoc/next.js')
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
pageExtensions: ['js', 'jsx', 'md'],
|
||||
experimental: {
|
||||
scrollRestoration: true,
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = withMarkdoc()(nextConfig)
|
||||
5728
package-lock.json
generated
Normal file
38
package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "tailwindui-syntax",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"browserslist": "defaults, not ie <= 11",
|
||||
"dependencies": {
|
||||
"@docsearch/react": "^3.2.1",
|
||||
"@headlessui/react": "^1.7.2",
|
||||
"@markdoc/markdoc": "0.1.7",
|
||||
"@markdoc/next.js": "0.1.6",
|
||||
"@sindresorhus/slugify": "^2.1.0",
|
||||
"@tailwindcss/typography": "^0.5.7",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"clsx": "^1.2.1",
|
||||
"focus-visible": "^5.2.0",
|
||||
"next": "13.0.2",
|
||||
"postcss-focus-visible": "^6.0.4",
|
||||
"postcss-import": "^14.1.0",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"tailwindcss": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/cli": "^0.1.59",
|
||||
"@swc/core": "^1.3.25",
|
||||
"eslint": "8.26.0",
|
||||
"eslint-config-next": "13.0.2",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-tailwindcss": "^0.1.13"
|
||||
}
|
||||
}
|
||||
10
postcss.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
tailwindcss: {},
|
||||
'postcss-focus-visible': {
|
||||
replaceWith: '[data-focus-visible-added]',
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
5
prettier.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
singleQuote: true,
|
||||
semi: false,
|
||||
plugins: [require('prettier-plugin-tailwindcss')],
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/fonts/Inter-italic.var.woff2
Normal file
BIN
public/fonts/Inter-roman.var.woff2
Normal file
93
public/fonts/lexend.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
Copyright 2018 The Lexend Project Authors (https://github.com/googlefonts/lexend), with Reserved Font Name “RevReading Lexend”.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
public/fonts/lexend.woff2
Normal file
BIN
public/images/alby-settings.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/images/astral-login.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
public/images/astral-login2.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
public/images/astral-login3.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
public/images/astral-login4.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
public/images/astral-login5.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/images/astral-verified.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
19
src/components/Button.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import Link from 'next/link'
|
||||
import clsx from 'clsx'
|
||||
|
||||
const styles = {
|
||||
primary:
|
||||
'rounded-full bg-sky-300 py-2 px-4 text-sm font-semibold text-slate-900 hover:bg-sky-200 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-300/50 active:bg-sky-500',
|
||||
secondary:
|
||||
'rounded-full bg-slate-800 py-2 px-4 text-sm font-medium text-white hover:bg-slate-700 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-slate-400',
|
||||
}
|
||||
|
||||
export function Button({ variant = 'primary', className, href, ...props }) {
|
||||
className = clsx(styles[variant], className)
|
||||
|
||||
return href ? (
|
||||
<Link href={href} className={className} {...props} />
|
||||
) : (
|
||||
<button className={className} {...props} />
|
||||
)
|
||||
}
|
||||
41
src/components/Callout.jsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { Icon } from '@/components/Icon'
|
||||
|
||||
const styles = {
|
||||
note: {
|
||||
container:
|
||||
'bg-sky-50 dark:bg-slate-800/60 dark:ring-1 dark:ring-slate-300/10',
|
||||
title: 'text-sky-900 dark:text-sky-400',
|
||||
body: 'text-sky-800 [--tw-prose-background:theme(colors.sky.50)] prose-a:text-sky-900 prose-code:text-sky-900 dark:text-slate-300 dark:prose-code:text-slate-300',
|
||||
},
|
||||
warning: {
|
||||
container:
|
||||
'bg-amber-50 dark:bg-slate-800/60 dark:ring-1 dark:ring-slate-300/10',
|
||||
title: 'text-amber-900 dark:text-amber-500',
|
||||
body: 'text-amber-800 [--tw-prose-underline:theme(colors.amber.400)] [--tw-prose-background:theme(colors.amber.50)] prose-a:text-amber-900 prose-code:text-amber-900 dark:text-slate-300 dark:[--tw-prose-underline:theme(colors.sky.700)] dark:prose-code:text-slate-300',
|
||||
},
|
||||
}
|
||||
|
||||
const icons = {
|
||||
note: (props) => <Icon icon="lightbulb" {...props} />,
|
||||
warning: (props) => <Icon icon="warning" color="amber" {...props} />,
|
||||
}
|
||||
|
||||
export function Callout({ type = 'note', title, children }) {
|
||||
let IconComponent = icons[type]
|
||||
|
||||
return (
|
||||
<div className={clsx('my-8 flex rounded-3xl p-6', styles[type].container)}>
|
||||
<IconComponent className="h-8 w-8 flex-none" />
|
||||
<div className="ml-4 flex-auto">
|
||||
<p className={clsx('m-0 font-display text-xl', styles[type].title)}>
|
||||
{title}
|
||||
</p>
|
||||
<div className={clsx('prose mt-2.5', styles[type].body)}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
30
src/components/Fence.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Fragment } from 'react'
|
||||
import Highlight, { defaultProps } from 'prism-react-renderer'
|
||||
|
||||
export function Fence({ children, language }) {
|
||||
return (
|
||||
<Highlight
|
||||
{...defaultProps}
|
||||
code={children.trimEnd()}
|
||||
language={language}
|
||||
theme={undefined}
|
||||
>
|
||||
{({ className, style, tokens, getTokenProps }) => (
|
||||
<pre className={className} style={style}>
|
||||
<code>
|
||||
{tokens.map((line, lineIndex) => (
|
||||
<Fragment key={lineIndex}>
|
||||
{line
|
||||
.filter((token) => !token.empty)
|
||||
.map((token, tokenIndex) => (
|
||||
<span key={tokenIndex} {...getTokenProps({ token })} />
|
||||
))}
|
||||
{'\n'}
|
||||
</Fragment>
|
||||
))}
|
||||
</code>
|
||||
</pre>
|
||||
)}
|
||||
</Highlight>
|
||||
)
|
||||
}
|
||||
179
src/components/Hero.jsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import { Fragment } from 'react'
|
||||
import Image from 'next/image'
|
||||
import clsx from 'clsx'
|
||||
import Highlight, { defaultProps } from 'prism-react-renderer'
|
||||
|
||||
import { Button } from '@/components/Button'
|
||||
import { HeroBackground } from '@/components/HeroBackground'
|
||||
import blurCyanImage from '@/images/blur-cyan.png'
|
||||
import blurIndigoImage from '@/images/blur-indigo.png'
|
||||
|
||||
const codeLanguage = 'json'
|
||||
const code = `{
|
||||
"id": "4376c65d2f232afbe9b882a35baa4f6fe8667c4e684749af565f981833ed6a65",
|
||||
"pubkey": "6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93",
|
||||
"created_at": 1673347337,
|
||||
"kind": 1,
|
||||
"tags": [
|
||||
["e","3da979448d9ba263864c4d6f14984c423a3838364ec255f03c7904b1ae77f206"],
|
||||
["p","bf2376e17ba4ec269d10fcc996a4746b451152be9031fa48e74553dde5526bce"]
|
||||
],
|
||||
"content": "Walled gardens became prisons, and nostr is the first step towards tearing down the prison walls.",
|
||||
"sig": "908a15e46fb4d8675bab026fc230a0e3542bfade63da02d542fb78b2a8513fcd0092619a2c8c1221e581946e0191f2af505dfdf8657a414dbca329186f009262"
|
||||
}`
|
||||
|
||||
const tabs = [
|
||||
{ name: 'event.json', isActive: true },
|
||||
]
|
||||
|
||||
function TrafficLightsIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 42 10" fill="none" {...props}>
|
||||
<circle cx="5" cy="5" r="4.5" />
|
||||
<circle cx="21" cy="5" r="4.5" />
|
||||
<circle cx="37" cy="5" r="4.5" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<div className="overflow-hidden bg-slate-900 dark:-mb-32 dark:mt-[-4.5rem] dark:pb-32 dark:pt-[4.5rem] dark:lg:mt-[-4.75rem] dark:lg:pt-[4.75rem]">
|
||||
<div className="py-16 sm:px-2 lg:relative lg:py-20 lg:px-0">
|
||||
<div className="mx-auto grid max-w-2xl grid-cols-1 items-center gap-y-16 gap-x-8 px-4 lg:max-w-8xl lg:grid-cols-2 lg:px-8 xl:gap-x-16 xl:px-12">
|
||||
<div className="relative z-10 md:text-center lg:text-left">
|
||||
<Image
|
||||
className="absolute bottom-full right-full -mr-72 -mb-56 opacity-50"
|
||||
src={blurCyanImage}
|
||||
alt=""
|
||||
width={530}
|
||||
height={530}
|
||||
unoptimized
|
||||
priority
|
||||
/>
|
||||
<div className="relative">
|
||||
<p className="inline bg-gradient-to-r from-indigo-200 via-sky-400 to-indigo-200 bg-clip-text font-display text-5xl tracking-tight text-transparent">
|
||||
A social network for the decentralized era
|
||||
</p>
|
||||
<p className="mt-3 text-2xl tracking-tight text-slate-400">
|
||||
Learn about Nostr: A simple, open protocol that enables a truly censorship-resistant & global social network.
|
||||
</p>
|
||||
<div className="mt-8 flex gap-4 md:justify-center lg:justify-start">
|
||||
<Button href="/">Get started</Button>
|
||||
<Button href="https://github.com/nostr-protocol/nostr" variant="secondary">
|
||||
View on GitHub
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative lg:static xl:pl-10">
|
||||
<div className="absolute inset-x-[-50vw] -top-32 -bottom-48 [mask-image:linear-gradient(transparent,white,white)] dark:[mask-image:linear-gradient(transparent,white,transparent)] lg:left-[calc(50%+14rem)] lg:right-0 lg:-top-32 lg:-bottom-32 lg:[mask-image:none] lg:dark:[mask-image:linear-gradient(white,white,transparent)]">
|
||||
<HeroBackground className="absolute top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2 lg:left-0 lg:translate-x-0 lg:translate-y-[-60%]" />
|
||||
</div>
|
||||
<div className="relative">
|
||||
<Image
|
||||
className="absolute -top-64 -right-64"
|
||||
src={blurCyanImage}
|
||||
alt=""
|
||||
width={530}
|
||||
height={530}
|
||||
unoptimized
|
||||
priority
|
||||
/>
|
||||
<Image
|
||||
className="absolute -bottom-40 -right-44"
|
||||
src={blurIndigoImage}
|
||||
alt=""
|
||||
width={567}
|
||||
height={567}
|
||||
unoptimized
|
||||
priority
|
||||
/>
|
||||
<div className="absolute inset-0 rounded-2xl bg-gradient-to-tr from-sky-300 via-sky-300/70 to-blue-300 opacity-10 blur-lg" />
|
||||
<div className="absolute inset-0 rounded-2xl bg-gradient-to-tr from-sky-300 via-sky-300/70 to-blue-300 opacity-10" />
|
||||
<div className="relative rounded-2xl bg-[#0A101F]/80 ring-1 ring-white/10 backdrop-blur">
|
||||
<div className="absolute -top-px left-20 right-11 h-px bg-gradient-to-r from-sky-300/0 via-sky-300/70 to-sky-300/0" />
|
||||
<div className="absolute -bottom-px left-11 right-20 h-px bg-gradient-to-r from-blue-400/0 via-blue-400 to-blue-400/0" />
|
||||
<div className="pl-4 pt-4">
|
||||
<TrafficLightsIcon className="h-2.5 w-auto stroke-slate-500/30" />
|
||||
<div className="mt-4 flex space-x-2 text-xs">
|
||||
{tabs.map((tab) => (
|
||||
<div
|
||||
key={tab.name}
|
||||
className={clsx(
|
||||
'flex h-6 rounded-full',
|
||||
tab.isActive
|
||||
? 'bg-gradient-to-r from-sky-400/30 via-sky-400 to-sky-400/30 p-px font-medium text-sky-300'
|
||||
: 'text-slate-500'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex items-center rounded-full px-2.5',
|
||||
tab.isActive && 'bg-slate-800'
|
||||
)}
|
||||
>
|
||||
{tab.name}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 flex items-start px-1 text-sm">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="select-none border-r border-slate-300/5 pr-4 font-mono text-slate-600"
|
||||
>
|
||||
{Array.from({
|
||||
length: code.split('\n').length,
|
||||
}).map((_, index) => (
|
||||
<Fragment key={index}>
|
||||
{(index + 1).toString().padStart(2, '0')}
|
||||
<br />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
<Highlight
|
||||
{...defaultProps}
|
||||
code={code}
|
||||
language={codeLanguage}
|
||||
theme={undefined}
|
||||
>
|
||||
{({
|
||||
className,
|
||||
style,
|
||||
tokens,
|
||||
getLineProps,
|
||||
getTokenProps,
|
||||
}) => (
|
||||
<pre
|
||||
className={clsx(
|
||||
className,
|
||||
'flex overflow-x-auto pb-6'
|
||||
)}
|
||||
style={style}
|
||||
>
|
||||
<code className="px-4">
|
||||
{tokens.map((line, lineIndex) => (
|
||||
<div key={lineIndex} {...getLineProps({ line })}>
|
||||
{line.map((token, tokenIndex) => (
|
||||
<span
|
||||
key={tokenIndex}
|
||||
{...getTokenProps({ token })}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</code>
|
||||
</pre>
|
||||
)}
|
||||
</Highlight>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
188
src/components/HeroBackground.jsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import { useId } from 'react'
|
||||
|
||||
export function HeroBackground(props) {
|
||||
let id = useId()
|
||||
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 668 1069"
|
||||
width={668}
|
||||
height={1069}
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<clipPath id={`${id}-clip-path`}>
|
||||
<path
|
||||
fill="#fff"
|
||||
transform="rotate(-180 334 534.4)"
|
||||
d="M0 0h668v1068.8H0z"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g opacity=".4" clipPath={`url(#${id}-clip-path)`} strokeWidth={4}>
|
||||
<path
|
||||
opacity=".3"
|
||||
d="M584.5 770.4v-474M484.5 770.4v-474M384.5 770.4v-474M283.5 769.4v-474M183.5 768.4v-474M83.5 767.4v-474"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<path
|
||||
d="M83.5 221.275v6.587a50.1 50.1 0 0 0 22.309 41.686l55.581 37.054a50.102 50.102 0 0 1 22.309 41.686v6.587M83.5 716.012v6.588a50.099 50.099 0 0 0 22.309 41.685l55.581 37.054a50.102 50.102 0 0 1 22.309 41.686v6.587M183.7 584.5v6.587a50.1 50.1 0 0 0 22.31 41.686l55.581 37.054a50.097 50.097 0 0 1 22.309 41.685v6.588M384.101 277.637v6.588a50.1 50.1 0 0 0 22.309 41.685l55.581 37.054a50.1 50.1 0 0 1 22.31 41.686v6.587M384.1 770.288v6.587a50.1 50.1 0 0 1-22.309 41.686l-55.581 37.054A50.099 50.099 0 0 0 283.9 897.3v6.588"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<path
|
||||
d="M384.1 770.288v6.587a50.1 50.1 0 0 1-22.309 41.686l-55.581 37.054A50.099 50.099 0 0 0 283.9 897.3v6.588M484.3 594.937v6.587a50.1 50.1 0 0 1-22.31 41.686l-55.581 37.054A50.1 50.1 0 0 0 384.1 721.95v6.587M484.3 872.575v6.587a50.1 50.1 0 0 1-22.31 41.686l-55.581 37.054a50.098 50.098 0 0 0-22.309 41.686v6.582M584.501 663.824v39.988a50.099 50.099 0 0 1-22.31 41.685l-55.581 37.054a50.102 50.102 0 0 0-22.309 41.686v6.587M283.899 945.637v6.588a50.1 50.1 0 0 1-22.309 41.685l-55.581 37.05a50.12 50.12 0 0 0-22.31 41.69v6.59M384.1 277.637c0 19.946 12.763 37.655 31.686 43.962l137.028 45.676c18.923 6.308 31.686 24.016 31.686 43.962M183.7 463.425v30.69c0 21.564 13.799 40.709 34.257 47.529l134.457 44.819c18.922 6.307 31.686 24.016 31.686 43.962M83.5 102.288c0 19.515 13.554 36.412 32.604 40.645l235.391 52.309c19.05 4.234 32.605 21.13 32.605 40.646M83.5 463.425v-58.45M183.699 542.75V396.625M283.9 1068.8V945.637M83.5 363.225v-141.95M83.5 179.524v-77.237M83.5 60.537V0M384.1 630.425V277.637M484.301 830.824V594.937M584.5 1068.8V663.825M484.301 555.275V452.988M584.5 622.075V452.988M384.1 728.537v-56.362M384.1 1068.8v-20.88M384.1 1006.17V770.287M283.9 903.888V759.85M183.699 1066.71V891.362M83.5 1068.8V716.012M83.5 674.263V505.175"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="83.5"
|
||||
cy="384.1"
|
||||
r="10.438"
|
||||
transform="rotate(-180 83.5 384.1)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="83.5"
|
||||
cy="200.399"
|
||||
r="10.438"
|
||||
transform="rotate(-180 83.5 200.399)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="83.5"
|
||||
cy="81.412"
|
||||
r="10.438"
|
||||
transform="rotate(-180 83.5 81.412)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="183.699"
|
||||
cy="375.75"
|
||||
r="10.438"
|
||||
transform="rotate(-180 183.699 375.75)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="183.699"
|
||||
cy="563.625"
|
||||
r="10.438"
|
||||
transform="rotate(-180 183.699 563.625)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="384.1"
|
||||
cy="651.3"
|
||||
r="10.438"
|
||||
transform="rotate(-180 384.1 651.3)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="484.301"
|
||||
cy="574.062"
|
||||
r="10.438"
|
||||
transform="rotate(-180 484.301 574.062)"
|
||||
fill="#0EA5E9"
|
||||
fillOpacity=".42"
|
||||
stroke="#0EA5E9"
|
||||
/>
|
||||
<circle
|
||||
cx="384.1"
|
||||
cy="749.412"
|
||||
r="10.438"
|
||||
transform="rotate(-180 384.1 749.412)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="384.1"
|
||||
cy="1027.05"
|
||||
r="10.438"
|
||||
transform="rotate(-180 384.1 1027.05)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="283.9"
|
||||
cy="924.763"
|
||||
r="10.438"
|
||||
transform="rotate(-180 283.9 924.763)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="183.699"
|
||||
cy="870.487"
|
||||
r="10.438"
|
||||
transform="rotate(-180 183.699 870.487)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="283.9"
|
||||
cy="738.975"
|
||||
r="10.438"
|
||||
transform="rotate(-180 283.9 738.975)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="83.5"
|
||||
cy="695.138"
|
||||
r="10.438"
|
||||
transform="rotate(-180 83.5 695.138)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="83.5"
|
||||
cy="484.3"
|
||||
r="10.438"
|
||||
transform="rotate(-180 83.5 484.3)"
|
||||
fill="#0EA5E9"
|
||||
fillOpacity=".42"
|
||||
stroke="#0EA5E9"
|
||||
/>
|
||||
<circle
|
||||
cx="484.301"
|
||||
cy="432.112"
|
||||
r="10.438"
|
||||
transform="rotate(-180 484.301 432.112)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="584.5"
|
||||
cy="432.112"
|
||||
r="10.438"
|
||||
transform="rotate(-180 584.5 432.112)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="584.5"
|
||||
cy="642.95"
|
||||
r="10.438"
|
||||
transform="rotate(-180 584.5 642.95)"
|
||||
fill="#1E293B"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="484.301"
|
||||
cy="851.699"
|
||||
r="10.438"
|
||||
transform="rotate(-180 484.301 851.699)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
<circle
|
||||
cx="384.1"
|
||||
cy="256.763"
|
||||
r="10.438"
|
||||
transform="rotate(-180 384.1 256.763)"
|
||||
stroke="#334155"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
77
src/components/Icon.jsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { useId } from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { InstallationIcon } from '@/components/icons/InstallationIcon'
|
||||
import { LightbulbIcon } from '@/components/icons/LightbulbIcon'
|
||||
import { PluginsIcon } from '@/components/icons/PluginsIcon'
|
||||
import { PresetsIcon } from '@/components/icons/PresetsIcon'
|
||||
import { ThemingIcon } from '@/components/icons/ThemingIcon'
|
||||
import { WarningIcon } from '@/components/icons/WarningIcon'
|
||||
|
||||
const icons = {
|
||||
installation: InstallationIcon,
|
||||
presets: PresetsIcon,
|
||||
plugins: PluginsIcon,
|
||||
theming: ThemingIcon,
|
||||
lightbulb: LightbulbIcon,
|
||||
warning: WarningIcon,
|
||||
}
|
||||
|
||||
const iconStyles = {
|
||||
blue: '[--icon-foreground:theme(colors.slate.900)] [--icon-background:theme(colors.white)]',
|
||||
amber:
|
||||
'[--icon-foreground:theme(colors.amber.900)] [--icon-background:theme(colors.amber.100)]',
|
||||
}
|
||||
|
||||
export function Icon({ color = 'blue', icon, className, ...props }) {
|
||||
let id = useId()
|
||||
let IconComponent = icons[icon]
|
||||
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
className={clsx(className, iconStyles[color])}
|
||||
{...props}
|
||||
>
|
||||
<IconComponent id={id} color={color} />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const gradients = {
|
||||
blue: [
|
||||
{ stopColor: '#0EA5E9' },
|
||||
{ stopColor: '#22D3EE', offset: '.527' },
|
||||
{ stopColor: '#818CF8', offset: 1 },
|
||||
],
|
||||
amber: [
|
||||
{ stopColor: '#FDE68A', offset: '.08' },
|
||||
{ stopColor: '#F59E0B', offset: '.837' },
|
||||
],
|
||||
}
|
||||
|
||||
export function Gradient({ color = 'blue', ...props }) {
|
||||
return (
|
||||
<radialGradient
|
||||
cx={0}
|
||||
cy={0}
|
||||
r={1}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
{...props}
|
||||
>
|
||||
{gradients[color].map((stop, stopIndex) => (
|
||||
<stop key={stopIndex} {...stop} />
|
||||
))}
|
||||
</radialGradient>
|
||||
)
|
||||
}
|
||||
|
||||
export function LightMode({ className, ...props }) {
|
||||
return <g className={clsx('dark:hidden', className)} {...props} />
|
||||
}
|
||||
|
||||
export function DarkMode({ className, ...props }) {
|
||||
return <g className={clsx('hidden dark:inline', className)} {...props} />
|
||||
}
|
||||
297
src/components/Layout.jsx
Normal file
@@ -0,0 +1,297 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { Hero } from '@/components/Hero'
|
||||
import { Logo, Logomark } from '@/components/Logo'
|
||||
import { MobileNavigation } from '@/components/MobileNavigation'
|
||||
import { Navigation } from '@/components/Navigation'
|
||||
import { Prose } from '@/components/Prose'
|
||||
import { Search } from '@/components/Search'
|
||||
import { ThemeSelector } from '@/components/ThemeSelector'
|
||||
|
||||
const navigation = [
|
||||
{
|
||||
title: 'The Basics',
|
||||
links: [
|
||||
{ title: 'What is Nostr?', href: '/' },
|
||||
{ title: 'Get started', href: '/get-started' },
|
||||
{ title: 'Verify your identity', href: '/verify-your-identity' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'The Protocol',
|
||||
links: [
|
||||
{ title: 'The Nostr Protocol', href: '/the-protocol' },
|
||||
{ title: 'Events', href: '/the-protocol/events' },
|
||||
{ title: 'NIPs', href: '/the-protocol/nips' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'The Clients',
|
||||
links: [
|
||||
{ title: 'How do clients work?', href: '/clients' },
|
||||
{ title: 'Client comparison list', href: '/clients/comparison' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Resources',
|
||||
links: [
|
||||
{ title: 'Other resources', href: '/resources' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Contributing',
|
||||
links: [
|
||||
{ title: 'How to contribute', href: '/contribute' },
|
||||
{ title: 'Help us translate', href: '/contribute/translate' },
|
||||
{ title: 'Write guides', href: '/contribute/write' },
|
||||
{ title: 'Improve the site', href: '/contribute/code' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function GitHubIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function Header({ navigation }) {
|
||||
let [isScrolled, setIsScrolled] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
function onScroll() {
|
||||
setIsScrolled(window.scrollY > 0)
|
||||
}
|
||||
onScroll()
|
||||
window.addEventListener('scroll', onScroll, { passive: true })
|
||||
return () => {
|
||||
window.removeEventListener('scroll', onScroll, { passive: true })
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<header
|
||||
className={clsx(
|
||||
'sticky top-0 z-50 flex flex-wrap items-center justify-between bg-white px-4 py-5 shadow-md shadow-slate-900/5 transition duration-500 dark:shadow-none sm:px-6 lg:px-8',
|
||||
isScrolled
|
||||
? 'dark:bg-slate-900/95 dark:backdrop-blur dark:[@supports(backdrop-filter:blur(0))]:bg-slate-900/75'
|
||||
: 'dark:bg-transparent'
|
||||
)}
|
||||
>
|
||||
<div className="mr-6 flex lg:hidden">
|
||||
<MobileNavigation navigation={navigation} />
|
||||
</div>
|
||||
<div className="relative flex flex-grow basis-0 items-center">
|
||||
<Link href="/" aria-label="Home page">
|
||||
<Logo className="h-9 w-auto fill-slate-700 dark:fill-sky-100 lg:block" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="-my-5 mr-6 sm:mr-8 md:mr-0">
|
||||
<Search />
|
||||
</div>
|
||||
<div className="relative flex basis-0 justify-end gap-6 sm:gap-8 md:flex-grow">
|
||||
<ThemeSelector className="relative z-10" />
|
||||
<Link href="https://github.com/nostr-protocol/nostr" className="group" aria-label="GitHub">
|
||||
<GitHubIcon className="h-6 w-6 fill-slate-400 group-hover:fill-slate-500 dark:group-hover:fill-slate-300" />
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
function useTableOfContents(tableOfContents) {
|
||||
let [currentSection, setCurrentSection] = useState(tableOfContents[0]?.id)
|
||||
|
||||
let getHeadings = useCallback((tableOfContents) => {
|
||||
return tableOfContents
|
||||
.flatMap((node) => [node.id, ...node.children.map((child) => child.id)])
|
||||
.map((id) => {
|
||||
let el = document.getElementById(id)
|
||||
if (!el) return
|
||||
|
||||
let style = window.getComputedStyle(el)
|
||||
let scrollMt = parseFloat(style.scrollMarginTop)
|
||||
|
||||
let top = window.scrollY + el.getBoundingClientRect().top - scrollMt
|
||||
return { id, top }
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (tableOfContents.length === 0) return
|
||||
let headings = getHeadings(tableOfContents)
|
||||
function onScroll() {
|
||||
let top = window.scrollY
|
||||
let current = headings[0].id
|
||||
for (let heading of headings) {
|
||||
if (top >= heading.top) {
|
||||
current = heading.id
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
setCurrentSection(current)
|
||||
}
|
||||
window.addEventListener('scroll', onScroll, { passive: true })
|
||||
onScroll()
|
||||
return () => {
|
||||
window.removeEventListener('scroll', onScroll, { passive: true })
|
||||
}
|
||||
}, [getHeadings, tableOfContents])
|
||||
|
||||
return currentSection
|
||||
}
|
||||
|
||||
export function Layout({ children, title, tableOfContents }) {
|
||||
let router = useRouter()
|
||||
let isHomePage = router.pathname === '/'
|
||||
let allLinks = navigation.flatMap((section) => section.links)
|
||||
let linkIndex = allLinks.findIndex((link) => link.href === router.pathname)
|
||||
let previousPage = allLinks[linkIndex - 1]
|
||||
let nextPage = allLinks[linkIndex + 1]
|
||||
let section = navigation.find((section) =>
|
||||
section.links.find((link) => link.href === router.pathname)
|
||||
)
|
||||
let currentSection = useTableOfContents(tableOfContents)
|
||||
|
||||
function isActive(section) {
|
||||
if (section.id === currentSection) {
|
||||
return true
|
||||
}
|
||||
if (!section.children) {
|
||||
return false
|
||||
}
|
||||
return section.children.findIndex(isActive) > -1
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header navigation={navigation} />
|
||||
|
||||
{isHomePage && <Hero />}
|
||||
|
||||
<div className="relative mx-auto flex max-w-8xl justify-center sm:px-2 lg:px-8 xl:px-12">
|
||||
<div className="hidden lg:relative lg:block lg:flex-none">
|
||||
<div className="absolute inset-y-0 right-0 w-[50vw] bg-slate-50 dark:hidden" />
|
||||
<div className="absolute top-16 bottom-0 right-0 hidden h-12 w-px bg-gradient-to-t from-slate-800 dark:block" />
|
||||
<div className="absolute top-28 bottom-0 right-0 hidden w-px bg-slate-800 dark:block" />
|
||||
<div className="sticky top-[4.5rem] -ml-0.5 h-[calc(100vh-4.5rem)] overflow-y-auto overflow-x-hidden py-16 pl-0.5">
|
||||
<Navigation
|
||||
navigation={navigation}
|
||||
className="w-64 pr-8 xl:w-72 xl:pr-16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 max-w-2xl flex-auto px-4 py-16 lg:max-w-none lg:pr-0 lg:pl-8 xl:px-16">
|
||||
<article>
|
||||
{(title || section) && (
|
||||
<header className="mb-9 space-y-1">
|
||||
{section && (
|
||||
<p className="font-display text-sm font-medium text-sky-500">
|
||||
{section.title}
|
||||
</p>
|
||||
)}
|
||||
{title && (
|
||||
<h1 className="font-display text-3xl tracking-tight text-slate-900 dark:text-white">
|
||||
{title}
|
||||
</h1>
|
||||
)}
|
||||
</header>
|
||||
)}
|
||||
<Prose>{children}</Prose>
|
||||
</article>
|
||||
<dl className="mt-12 flex border-t border-slate-200 pt-6 dark:border-slate-800">
|
||||
{previousPage && (
|
||||
<div>
|
||||
<dt className="font-display text-sm font-medium text-slate-900 dark:text-white">
|
||||
Previous
|
||||
</dt>
|
||||
<dd className="mt-1">
|
||||
<Link
|
||||
href={previousPage.href}
|
||||
className="text-base font-semibold text-slate-500 hover:text-slate-600 dark:text-slate-400 dark:hover:text-slate-300"
|
||||
>
|
||||
<span aria-hidden="true">←</span> {previousPage.title}
|
||||
</Link>
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
{nextPage && (
|
||||
<div className="ml-auto text-right">
|
||||
<dt className="font-display text-sm font-medium text-slate-900 dark:text-white">
|
||||
Next
|
||||
</dt>
|
||||
<dd className="mt-1">
|
||||
<Link
|
||||
href={nextPage.href}
|
||||
className="text-base font-semibold text-slate-500 hover:text-slate-600 dark:text-slate-400 dark:hover:text-slate-300"
|
||||
>
|
||||
{nextPage.title} <span aria-hidden="true">→</span>
|
||||
</Link>
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
<div className="hidden xl:sticky xl:top-[4.5rem] xl:-mr-6 xl:block xl:h-[calc(100vh-4.5rem)] xl:flex-none xl:overflow-y-auto xl:py-16 xl:pr-6">
|
||||
<nav aria-labelledby="on-this-page-title" className="w-56">
|
||||
{tableOfContents.length > 0 && (
|
||||
<>
|
||||
<h2
|
||||
id="on-this-page-title"
|
||||
className="font-display text-sm font-medium text-slate-900 dark:text-white"
|
||||
>
|
||||
On this page
|
||||
</h2>
|
||||
<ol role="list" className="mt-4 space-y-3 text-sm">
|
||||
{tableOfContents.map((section) => (
|
||||
<li key={section.id}>
|
||||
<h3>
|
||||
<Link
|
||||
href={`#${section.id}`}
|
||||
className={clsx(
|
||||
isActive(section)
|
||||
? 'text-sky-500'
|
||||
: 'font-normal text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-300'
|
||||
)}
|
||||
>
|
||||
{section.title}
|
||||
</Link>
|
||||
</h3>
|
||||
{section.children.length > 0 && (
|
||||
<ol
|
||||
role="list"
|
||||
className="mt-2 space-y-3 pl-5 text-slate-500 dark:text-slate-400"
|
||||
>
|
||||
{section.children.map((subSection) => (
|
||||
<li key={subSection.id}>
|
||||
<Link
|
||||
href={`#${subSection.id}`}
|
||||
className={
|
||||
isActive(subSection)
|
||||
? 'text-sky-500'
|
||||
: 'hover:text-slate-600 dark:hover:text-slate-300'
|
||||
}
|
||||
>
|
||||
{subSection.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
22
src/components/Logo.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
function LogomarkPaths() {
|
||||
return (
|
||||
<g fill="none" stroke="#38BDF8" strokeLinejoin="round" strokeWidth={3}>
|
||||
<path d="M10.308 5L18 17.5 10.308 30 2.615 17.5 10.308 5z" />
|
||||
<path d="M18 17.5L10.308 5h15.144l7.933 12.5M18 17.5h15.385L25.452 30H10.308L18 17.5z" />
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
export function Logomark(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 36 36" fill="none" {...props}>
|
||||
<LogomarkPaths />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function Logo(props) {
|
||||
return (
|
||||
<span class="flex font-display text-2xl md:text-3xl font-bold dark:text-sky-100 text-slate-900">Nostr.how</span>
|
||||
)
|
||||
}
|
||||
93
src/components/MobileNavigation.jsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Dialog } from '@headlessui/react'
|
||||
|
||||
import { Logomark } from '@/components/Logo'
|
||||
import { Navigation } from '@/components/Navigation'
|
||||
|
||||
function MenuIcon(props) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M4 7h16M4 12h16M4 17h16" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function CloseIcon(props) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
{...props}
|
||||
>
|
||||
<path d="M5 5l14 14M19 5l-14 14" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function MobileNavigation({ navigation }) {
|
||||
let router = useRouter()
|
||||
let [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return
|
||||
|
||||
function onRouteChange() {
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
router.events.on('routeChangeComplete', onRouteChange)
|
||||
router.events.on('routeChangeError', onRouteChange)
|
||||
|
||||
return () => {
|
||||
router.events.off('routeChangeComplete', onRouteChange)
|
||||
router.events.off('routeChangeError', onRouteChange)
|
||||
}
|
||||
}, [router, isOpen])
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="relative"
|
||||
aria-label="Open navigation"
|
||||
>
|
||||
<MenuIcon className="h-6 w-6 stroke-slate-500" />
|
||||
</button>
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={setIsOpen}
|
||||
className="fixed inset-0 z-50 flex items-start overflow-y-auto bg-slate-900/50 pr-10 backdrop-blur lg:hidden"
|
||||
aria-label="Navigation"
|
||||
>
|
||||
<Dialog.Panel className="min-h-full w-full max-w-xs bg-white px-4 pt-5 pb-12 dark:bg-slate-900 sm:px-6">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsOpen(false)}
|
||||
aria-label="Close navigation"
|
||||
>
|
||||
<CloseIcon className="h-6 w-6 stroke-slate-500" />
|
||||
</button>
|
||||
<Link href="/" className="ml-6" aria-label="Home page">
|
||||
<Logomark className="h-9 w-9" />
|
||||
</Link>
|
||||
</div>
|
||||
<Navigation navigation={navigation} className="mt-5 px-1" />
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
41
src/components/Navigation.jsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export function Navigation({ navigation, className }) {
|
||||
let router = useRouter()
|
||||
|
||||
return (
|
||||
<nav className={clsx('text-base lg:text-sm', className)}>
|
||||
<ul role="list" className="space-y-9">
|
||||
{navigation.map((section) => (
|
||||
<li key={section.title}>
|
||||
<h2 className="font-display font-medium text-slate-900 dark:text-white">
|
||||
{section.title}
|
||||
</h2>
|
||||
<ul
|
||||
role="list"
|
||||
className="mt-2 space-y-2 border-l-2 border-slate-100 dark:border-slate-800 lg:mt-4 lg:space-y-4 lg:border-slate-200"
|
||||
>
|
||||
{section.links.map((link) => (
|
||||
<li key={link.href} className="relative">
|
||||
<Link
|
||||
href={link.href}
|
||||
className={clsx(
|
||||
'block w-full pl-3.5 before:pointer-events-none before:absolute before:-left-1 before:top-1/2 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full',
|
||||
link.href === router.pathname
|
||||
? 'font-semibold text-sky-500 before:bg-sky-500'
|
||||
: 'text-slate-500 before:hidden before:bg-slate-300 hover:text-slate-600 hover:before:block dark:text-slate-400 dark:before:bg-slate-700 dark:hover:text-slate-300'
|
||||
)}
|
||||
>
|
||||
{link.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
25
src/components/Prose.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import clsx from 'clsx'
|
||||
|
||||
export function Prose({ as: Component = 'div', className, ...props }) {
|
||||
return (
|
||||
<Component
|
||||
className={clsx(
|
||||
className,
|
||||
'prose prose-slate max-w-none dark:prose-invert dark:text-slate-400',
|
||||
// headings
|
||||
'prose-headings:scroll-mt-28 prose-headings:font-display prose-headings:font-normal lg:prose-headings:scroll-mt-[8.5rem]',
|
||||
// lead
|
||||
'prose-lead:text-slate-500 dark:prose-lead:text-slate-400',
|
||||
// links
|
||||
'prose-a:font-semibold dark:prose-a:text-sky-400',
|
||||
// link underline
|
||||
'prose-a:no-underline prose-a:shadow-[inset_0_-2px_0_0_var(--tw-prose-background,#fff),inset_0_calc(-1*(var(--tw-prose-underline-size,4px)+2px))_0_0_var(--tw-prose-underline,theme(colors.sky.300))] hover:prose-a:[--tw-prose-underline-size:6px] dark:[--tw-prose-background:theme(colors.slate.900)] dark:prose-a:shadow-[inset_0_calc(-1*var(--tw-prose-underline-size,2px))_0_0_var(--tw-prose-underline,theme(colors.sky.800))] dark:hover:prose-a:[--tw-prose-underline-size:6px]',
|
||||
// pre
|
||||
'prose-pre:rounded-xl prose-pre:bg-slate-900 prose-pre:shadow-lg dark:prose-pre:bg-slate-800/60 dark:prose-pre:shadow-none dark:prose-pre:ring-1 dark:prose-pre:ring-slate-300/10',
|
||||
// hr
|
||||
'dark:prose-hr:border-slate-800'
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
31
src/components/QuickLinks.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
import { Icon } from '@/components/Icon'
|
||||
|
||||
export function QuickLinks({ children }) {
|
||||
return (
|
||||
<div className="not-prose my-12 grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function QuickLink({ title, description, href, icon }) {
|
||||
return (
|
||||
<div className="group relative rounded-xl border border-slate-200 dark:border-slate-800">
|
||||
<div className="absolute -inset-px rounded-xl border-2 border-transparent opacity-0 [background:linear-gradient(var(--quick-links-hover-bg,theme(colors.sky.50)),var(--quick-links-hover-bg,theme(colors.sky.50)))_padding-box,linear-gradient(to_top,theme(colors.indigo.400),theme(colors.cyan.400),theme(colors.sky.500))_border-box] group-hover:opacity-100 dark:[--quick-links-hover-bg:theme(colors.slate.800)]" />
|
||||
<div className="relative overflow-hidden rounded-xl p-6">
|
||||
<Icon icon={icon} className="h-8 w-8" />
|
||||
<h2 className="mt-4 font-display text-base text-slate-900 dark:text-white">
|
||||
<Link href={href}>
|
||||
<span className="absolute -inset-px rounded-xl" />
|
||||
{title}
|
||||
</Link>
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-slate-700 dark:text-slate-400">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
80
src/components/Search.jsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import Link from 'next/link'
|
||||
import Router from 'next/router'
|
||||
import { DocSearchModal, useDocSearchKeyboardEvents } from '@docsearch/react'
|
||||
|
||||
const docSearchConfig = {
|
||||
appId: process.env.NEXT_PUBLIC_DOCSEARCH_APP_ID,
|
||||
apiKey: process.env.NEXT_PUBLIC_DOCSEARCH_API_KEY,
|
||||
indexName: process.env.NEXT_PUBLIC_DOCSEARCH_INDEX_NAME,
|
||||
}
|
||||
|
||||
function Hit({ hit, children }) {
|
||||
return <Link href={hit.url}>{children}</Link>
|
||||
}
|
||||
|
||||
function SearchIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 20 20" {...props}>
|
||||
<path d="M16.293 17.707a1 1 0 0 0 1.414-1.414l-1.414 1.414ZM9 14a5 5 0 0 1-5-5H2a7 7 0 0 0 7 7v-2ZM4 9a5 5 0 0 1 5-5V2a7 7 0 0 0-7 7h2Zm5-5a5 5 0 0 1 5 5h2a7 7 0 0 0-7-7v2Zm8.707 12.293-3.757-3.757-1.414 1.414 3.757 3.757 1.414-1.414ZM14 9a4.98 4.98 0 0 1-1.464 3.536l1.414 1.414A6.98 6.98 0 0 0 16 9h-2Zm-1.464 3.536A4.98 4.98 0 0 1 9 14v2a6.98 6.98 0 0 0 4.95-2.05l-1.414-1.414Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function Search() {
|
||||
let [isOpen, setIsOpen] = useState(false)
|
||||
let [modifierKey, setModifierKey] = useState()
|
||||
|
||||
const onOpen = useCallback(() => {
|
||||
setIsOpen(true)
|
||||
}, [setIsOpen])
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
setIsOpen(false)
|
||||
}, [setIsOpen])
|
||||
|
||||
useDocSearchKeyboardEvents({ isOpen, onOpen, onClose })
|
||||
|
||||
useEffect(() => {
|
||||
setModifierKey(
|
||||
/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘' : 'Ctrl '
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="group flex h-6 w-6 items-center justify-center sm:justify-start md:h-auto md:w-80 md:flex-none md:rounded-lg md:py-2.5 md:pl-4 md:pr-3.5 md:text-sm md:ring-1 md:ring-slate-200 md:hover:ring-slate-300 dark:md:bg-slate-800/75 dark:md:ring-inset dark:md:ring-white/5 dark:md:hover:bg-slate-700/40 dark:md:hover:ring-slate-500 lg:w-96"
|
||||
onClick={onOpen}
|
||||
>
|
||||
<SearchIcon className="h-5 w-5 flex-none fill-slate-400 group-hover:fill-slate-500 dark:fill-slate-500 md:group-hover:fill-slate-400" />
|
||||
<span className="sr-only md:not-sr-only md:ml-2 md:text-slate-500 md:dark:text-slate-400">
|
||||
Search docs
|
||||
</span>
|
||||
{modifierKey && (
|
||||
<kbd className="ml-auto hidden font-medium text-slate-400 dark:text-slate-500 md:block">
|
||||
<kbd className="font-sans">{modifierKey}</kbd>
|
||||
<kbd className="font-sans">K</kbd>
|
||||
</kbd>
|
||||
)}
|
||||
</button>
|
||||
{isOpen &&
|
||||
createPortal(
|
||||
<DocSearchModal
|
||||
{...docSearchConfig}
|
||||
initialScrollY={window.scrollY}
|
||||
onClose={onClose}
|
||||
hitComponent={Hit}
|
||||
navigator={{
|
||||
navigate({ itemUrl }) {
|
||||
Router.push(itemUrl)
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
document.body
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
130
src/components/ThemeSelector.jsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Listbox } from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
const themes = [
|
||||
{ name: 'Light', value: 'light', icon: LightIcon },
|
||||
{ name: 'Dark', value: 'dark', icon: DarkIcon },
|
||||
{ name: 'System', value: 'system', icon: SystemIcon },
|
||||
]
|
||||
|
||||
function LightIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7 1a1 1 0 0 1 2 0v1a1 1 0 1 1-2 0V1Zm4 7a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm2.657-5.657a1 1 0 0 0-1.414 0l-.707.707a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0 0-1.414Zm-1.415 11.313-.707-.707a1 1 0 0 1 1.415-1.415l.707.708a1 1 0 0 1-1.415 1.414ZM16 7.999a1 1 0 0 0-1-1h-1a1 1 0 1 0 0 2h1a1 1 0 0 0 1-1ZM7 14a1 1 0 1 1 2 0v1a1 1 0 1 1-2 0v-1Zm-2.536-2.464a1 1 0 0 0-1.414 0l-.707.707a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0 0-1.414Zm0-8.486A1 1 0 0 1 3.05 4.464l-.707-.707a1 1 0 0 1 1.414-1.414l.707.707ZM3 8a1 1 0 0 0-1-1H1a1 1 0 0 0 0 2h1a1 1 0 0 0 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function DarkIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.23 3.333C7.757 2.905 7.68 2 7 2a6 6 0 1 0 0 12c.68 0 .758-.905.23-1.332A5.989 5.989 0 0 1 5 8c0-1.885.87-3.568 2.23-4.668ZM12 5a1 1 0 0 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 0 2 1 1 0 0 0-1 1 1 1 0 1 1-2 0 1 1 0 0 0-1-1 1 1 0 1 1 0-2 1 1 0 0 0 1-1 1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function SystemIcon(props) {
|
||||
return (
|
||||
<svg aria-hidden="true" viewBox="0 0 16 16" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M1 4a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v4a3 3 0 0 1-3 3h-1.5l.31 1.242c.084.333.36.573.63.808.091.08.182.158.264.24A1 1 0 0 1 11 15H5a1 1 0 0 1-.704-1.71c.082-.082.173-.16.264-.24.27-.235.546-.475.63-.808L5.5 11H4a3 3 0 0 1-3-3V4Zm3-1a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function ThemeSelector(props) {
|
||||
let [selectedTheme, setSelectedTheme] = useState()
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTheme) {
|
||||
document.documentElement.setAttribute('data-theme', selectedTheme.value)
|
||||
} else {
|
||||
setSelectedTheme(
|
||||
themes.find(
|
||||
(theme) =>
|
||||
theme.value === document.documentElement.getAttribute('data-theme')
|
||||
)
|
||||
)
|
||||
}
|
||||
}, [selectedTheme])
|
||||
|
||||
useEffect(() => {
|
||||
let handler = () =>
|
||||
setSelectedTheme(
|
||||
themes.find(
|
||||
(theme) => theme.value === (window.localStorage.theme ?? 'system')
|
||||
)
|
||||
)
|
||||
|
||||
window.addEventListener('storage', handler)
|
||||
|
||||
return () => window.removeEventListener('storage', handler)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Listbox
|
||||
as="div"
|
||||
value={selectedTheme}
|
||||
onChange={setSelectedTheme}
|
||||
{...props}
|
||||
>
|
||||
<Listbox.Label className="sr-only">Theme</Listbox.Label>
|
||||
<Listbox.Button
|
||||
className="flex h-6 w-6 items-center justify-center rounded-lg shadow-md shadow-black/5 ring-1 ring-black/5 dark:bg-slate-700 dark:ring-inset dark:ring-white/5"
|
||||
aria-label={selectedTheme?.name}
|
||||
>
|
||||
<LightIcon className="hidden h-4 w-4 fill-sky-400 [[data-theme=light]_&]:block" />
|
||||
<DarkIcon className="hidden h-4 w-4 fill-sky-400 [[data-theme=dark]_&]:block" />
|
||||
<LightIcon className="hidden h-4 w-4 fill-slate-400 [:not(.dark)[data-theme=system]_&]:block" />
|
||||
<DarkIcon className="hidden h-4 w-4 fill-slate-400 [.dark[data-theme=system]_&]:block" />
|
||||
</Listbox.Button>
|
||||
<Listbox.Options className="absolute top-full left-1/2 mt-3 w-36 -translate-x-1/2 space-y-1 rounded-xl bg-white p-3 text-sm font-medium shadow-md shadow-black/5 ring-1 ring-black/5 dark:bg-slate-800 dark:ring-white/5">
|
||||
{themes.map((theme) => (
|
||||
<Listbox.Option
|
||||
key={theme.value}
|
||||
value={theme}
|
||||
className={({ active, selected }) =>
|
||||
clsx(
|
||||
'flex cursor-pointer select-none items-center rounded-[0.625rem] p-1',
|
||||
{
|
||||
'text-sky-500': selected,
|
||||
'text-slate-900 dark:text-white': active && !selected,
|
||||
'text-slate-700 dark:text-slate-400': !active && !selected,
|
||||
'bg-slate-100 dark:bg-slate-900/40': active,
|
||||
}
|
||||
)
|
||||
}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<div className="rounded-md bg-white p-1 shadow ring-1 ring-slate-900/5 dark:bg-slate-700 dark:ring-inset dark:ring-white/5">
|
||||
<theme.icon
|
||||
className={clsx(
|
||||
'h-4 w-4',
|
||||
selected
|
||||
? 'fill-sky-400 dark:fill-sky-400'
|
||||
: 'fill-slate-400'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">{theme.name}</div>
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Listbox>
|
||||
)
|
||||
}
|
||||
41
src/components/icons/InstallationIcon.jsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { DarkMode, Gradient, LightMode } from '@/components/Icon'
|
||||
|
||||
export function InstallationIcon({ id, color }) {
|
||||
return (
|
||||
<>
|
||||
<defs>
|
||||
<Gradient
|
||||
id={`${id}-gradient`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 21 -21 0 12 3)"
|
||||
/>
|
||||
<Gradient
|
||||
id={`${id}-gradient-dark`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 21 -21 0 16 7)"
|
||||
/>
|
||||
</defs>
|
||||
<LightMode>
|
||||
<circle cx={12} cy={12} r={12} fill={`url(#${id}-gradient)`} />
|
||||
<path
|
||||
d="m8 8 9 21 2-10 10-2L8 8Z"
|
||||
fillOpacity={0.5}
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</LightMode>
|
||||
<DarkMode>
|
||||
<path
|
||||
d="m4 4 10.286 24 2.285-11.429L28 14.286 4 4Z"
|
||||
fill={`url(#${id}-gradient-dark)`}
|
||||
stroke={`url(#${id}-gradient-dark)`}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</DarkMode>
|
||||
</>
|
||||
)
|
||||
}
|
||||
46
src/components/icons/LightbulbIcon.jsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { DarkMode, Gradient, LightMode } from '@/components/Icon'
|
||||
|
||||
export function LightbulbIcon({ id, color }) {
|
||||
return (
|
||||
<>
|
||||
<defs>
|
||||
<Gradient
|
||||
id={`${id}-gradient`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 21 -21 0 20 11)"
|
||||
/>
|
||||
<Gradient
|
||||
id={`${id}-gradient-dark`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 24.5001 -19.2498 0 16 5.5)"
|
||||
/>
|
||||
</defs>
|
||||
<LightMode>
|
||||
<circle cx={20} cy={20} r={12} fill={`url(#${id}-gradient)`} />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M20 24.995c0-1.855 1.094-3.501 2.427-4.792C24.61 18.087 26 15.07 26 12.231 26 7.133 21.523 3 16 3S6 7.133 6 12.23c0 2.84 1.389 5.857 3.573 7.973C10.906 21.494 12 23.14 12 24.995V27a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-2.005Z"
|
||||
className="fill-[var(--icon-background)]"
|
||||
fillOpacity={0.5}
|
||||
/>
|
||||
<path
|
||||
d="M25 12.23c0 2.536-1.254 5.303-3.269 7.255l1.391 1.436c2.354-2.28 3.878-5.547 3.878-8.69h-2ZM16 4c5.047 0 9 3.759 9 8.23h2C27 6.508 21.998 2 16 2v2Zm-9 8.23C7 7.76 10.953 4 16 4V2C10.002 2 5 6.507 5 12.23h2Zm3.269 7.255C8.254 17.533 7 14.766 7 12.23H5c0 3.143 1.523 6.41 3.877 8.69l1.392-1.436ZM13 27v-2.005h-2V27h2Zm1 1a1 1 0 0 1-1-1h-2a3 3 0 0 0 3 3v-2Zm4 0h-4v2h4v-2Zm1-1a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2Zm0-2.005V27h2v-2.005h-2ZM8.877 20.921C10.132 22.136 11 23.538 11 24.995h2c0-2.253-1.32-4.143-2.731-5.51L8.877 20.92Zm12.854-1.436C20.32 20.852 19 22.742 19 24.995h2c0-1.457.869-2.859 2.122-4.074l-1.391-1.436Z"
|
||||
className="fill-[var(--icon-foreground)]"
|
||||
/>
|
||||
<path
|
||||
d="M20 26a1 1 0 1 0 0-2v2Zm-8-2a1 1 0 1 0 0 2v-2Zm2 0h-2v2h2v-2Zm1 1V13.5h-2V25h2Zm-5-11.5v1h2v-1h-2Zm3.5 4.5h5v-2h-5v2Zm8.5-3.5v-1h-2v1h2ZM20 24h-2v2h2v-2Zm-2 0h-4v2h4v-2Zm-1-10.5V25h2V13.5h-2Zm2.5-2.5a2.5 2.5 0 0 0-2.5 2.5h2a.5.5 0 0 1 .5-.5v-2Zm2.5 2.5a2.5 2.5 0 0 0-2.5-2.5v2a.5.5 0 0 1 .5.5h2ZM18.5 18a3.5 3.5 0 0 0 3.5-3.5h-2a1.5 1.5 0 0 1-1.5 1.5v2ZM10 14.5a3.5 3.5 0 0 0 3.5 3.5v-2a1.5 1.5 0 0 1-1.5-1.5h-2Zm2.5-3.5a2.5 2.5 0 0 0-2.5 2.5h2a.5.5 0 0 1 .5-.5v-2Zm2.5 2.5a2.5 2.5 0 0 0-2.5-2.5v2a.5.5 0 0 1 .5.5h2Z"
|
||||
className="fill-[var(--icon-foreground)]"
|
||||
/>
|
||||
</LightMode>
|
||||
<DarkMode>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16 2C10.002 2 5 6.507 5 12.23c0 3.144 1.523 6.411 3.877 8.691.75.727 1.363 1.52 1.734 2.353.185.415.574.726 1.028.726H12a1 1 0 0 0 1-1v-4.5a.5.5 0 0 0-.5-.5A3.5 3.5 0 0 1 9 14.5V14a3 3 0 1 1 6 0v9a1 1 0 1 0 2 0v-9a3 3 0 1 1 6 0v.5a3.5 3.5 0 0 1-3.5 3.5.5.5 0 0 0-.5.5V23a1 1 0 0 0 1 1h.36c.455 0 .844-.311 1.03-.726.37-.833.982-1.626 1.732-2.353 2.354-2.28 3.878-5.547 3.878-8.69C27 6.507 21.998 2 16 2Zm5 25a1 1 0 0 0-1-1h-8a1 1 0 0 0-1 1 3 3 0 0 0 3 3h4a3 3 0 0 0 3-3Zm-8-13v1.5a.5.5 0 0 1-.5.5 1.5 1.5 0 0 1-1.5-1.5V14a1 1 0 1 1 2 0Zm6.5 2a.5.5 0 0 1-.5-.5V14a1 1 0 1 1 2 0v.5a1.5 1.5 0 0 1-1.5 1.5Z"
|
||||
fill={`url(#${id}-gradient-dark)`}
|
||||
/>
|
||||
</DarkMode>
|
||||
</>
|
||||
)
|
||||
}
|
||||
63
src/components/icons/PluginsIcon.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { DarkMode, Gradient, LightMode } from '@/components/Icon'
|
||||
|
||||
export function PluginsIcon({ id, color }) {
|
||||
return (
|
||||
<>
|
||||
<defs>
|
||||
<Gradient
|
||||
id={`${id}-gradient`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 21 -21 0 20 11)"
|
||||
/>
|
||||
<Gradient
|
||||
id={`${id}-gradient-dark-1`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 22.75 -22.75 0 16 6.25)"
|
||||
/>
|
||||
<Gradient
|
||||
id={`${id}-gradient-dark-2`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 14 -14 0 16 10)"
|
||||
/>
|
||||
</defs>
|
||||
<LightMode>
|
||||
<circle cx={20} cy={20} r={12} fill={`url(#${id}-gradient)`} />
|
||||
<g
|
||||
fillOpacity={0.5}
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M3 9v14l12 6V15L3 9Z" />
|
||||
<path d="M27 9v14l-12 6V15l12-6Z" />
|
||||
</g>
|
||||
<path
|
||||
d="M11 4h8v2l6 3-10 6L5 9l6-3V4Z"
|
||||
fillOpacity={0.5}
|
||||
className="fill-[var(--icon-background)]"
|
||||
/>
|
||||
<g
|
||||
className="stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M20 5.5 27 9l-12 6L3 9l7-3.5" />
|
||||
<path d="M20 5c0 1.105-2.239 2-5 2s-5-.895-5-2m10 0c0-1.105-2.239-2-5-2s-5 .895-5 2m10 0v3c0 1.105-2.239 2-5 2s-5-.895-5-2V5" />
|
||||
</g>
|
||||
</LightMode>
|
||||
<DarkMode strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
|
||||
<path
|
||||
d="M17.676 3.38a3.887 3.887 0 0 0-3.352 0l-9 4.288C3.907 8.342 3 9.806 3 11.416v9.168c0 1.61.907 3.073 2.324 3.748l9 4.288a3.887 3.887 0 0 0 3.352 0l9-4.288C28.093 23.657 29 22.194 29 20.584v-9.168c0-1.61-.907-3.074-2.324-3.748l-9-4.288Z"
|
||||
stroke={`url(#${id}-gradient-dark-1)`}
|
||||
/>
|
||||
<path
|
||||
d="M16.406 8.087a.989.989 0 0 0-.812 0l-7 3.598A1.012 1.012 0 0 0 8 12.61v6.78c0 .4.233.762.594.925l7 3.598a.989.989 0 0 0 .812 0l7-3.598c.361-.163.594-.525.594-.925v-6.78c0-.4-.233-.762-.594-.925l-7-3.598Z"
|
||||
fill={`url(#${id}-gradient-dark-2)`}
|
||||
stroke={`url(#${id}-gradient-dark-2)`}
|
||||
/>
|
||||
</DarkMode>
|
||||
</>
|
||||
)
|
||||
}
|
||||
43
src/components/icons/PresetsIcon.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { DarkMode, Gradient, LightMode } from '@/components/Icon'
|
||||
|
||||
export function PresetsIcon({ id, color }) {
|
||||
return (
|
||||
<>
|
||||
<defs>
|
||||
<Gradient
|
||||
id={`${id}-gradient`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 21 -21 0 20 3)"
|
||||
/>
|
||||
<Gradient
|
||||
id={`${id}-gradient-dark`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 22.75 -22.75 0 16 6.25)"
|
||||
/>
|
||||
</defs>
|
||||
<LightMode>
|
||||
<circle cx={20} cy={12} r={12} fill={`url(#${id}-gradient)`} />
|
||||
<g
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
fillOpacity={0.5}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M3 5v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2Z" />
|
||||
<path d="M18 17v10a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2V17a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2Z" />
|
||||
<path d="M18 5v4a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2Z" />
|
||||
<path d="M3 25v2a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2Z" />
|
||||
</g>
|
||||
</LightMode>
|
||||
<DarkMode fill={`url(#${id}-gradient-dark)`}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3 17V4a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1Zm16 10v-9a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2Zm0-23v5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-8a1 1 0 0 0-1 1ZM3 28v-3a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1Z"
|
||||
/>
|
||||
<path d="M2 4v13h2V4H2Zm2-2a2 2 0 0 0-2 2h2V2Zm8 0H4v2h8V2Zm2 2a2 2 0 0 0-2-2v2h2Zm0 13V4h-2v13h2Zm-2 2a2 2 0 0 0 2-2h-2v2Zm-8 0h8v-2H4v2Zm-2-2a2 2 0 0 0 2 2v-2H2Zm16 1v9h2v-9h-2Zm3-3a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1v-2Zm6 0h-6v2h6v-2Zm3 3a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2Zm0 9v-9h-2v9h2Zm-3 3a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2Zm-6 0h6v-2h-6v2Zm-3-3a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1h-2Zm2-18V4h-2v5h2Zm0 0h-2a2 2 0 0 0 2 2V9Zm8 0h-8v2h8V9Zm0 0v2a2 2 0 0 0 2-2h-2Zm0-5v5h2V4h-2Zm0 0h2a2 2 0 0 0-2-2v2Zm-8 0h8V2h-8v2Zm0 0V2a2 2 0 0 0-2 2h2ZM2 25v3h2v-3H2Zm2-2a2 2 0 0 0-2 2h2v-2Zm9 0H4v2h9v-2Zm2 2a2 2 0 0 0-2-2v2h2Zm0 3v-3h-2v3h2Zm-2 2a2 2 0 0 0 2-2h-2v2Zm-9 0h9v-2H4v2Zm-2-2a2 2 0 0 0 2 2v-2H2Z" />
|
||||
</DarkMode>
|
||||
</>
|
||||
)
|
||||
}
|
||||
59
src/components/icons/ThemingIcon.jsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { DarkMode, Gradient, LightMode } from '@/components/Icon'
|
||||
|
||||
export function ThemingIcon({ id, color }) {
|
||||
return (
|
||||
<>
|
||||
<defs>
|
||||
<Gradient
|
||||
id={`${id}-gradient`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 21 -21 0 12 11)"
|
||||
/>
|
||||
<Gradient
|
||||
id={`${id}-gradient-dark`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 24.5 -24.5 0 16 5.5)"
|
||||
/>
|
||||
</defs>
|
||||
<LightMode>
|
||||
<circle cx={12} cy={20} r={12} fill={`url(#${id}-gradient)`} />
|
||||
<path
|
||||
d="M27 12.13 19.87 5 13 11.87v14.26l14-14Z"
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
fillOpacity={0.5}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3 3h10v22a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V3Z"
|
||||
className="fill-[var(--icon-background)]"
|
||||
fillOpacity={0.5}
|
||||
/>
|
||||
<path
|
||||
d="M3 9v16a4 4 0 0 0 4 4h2a4 4 0 0 0 4-4V9M3 9V3h10v6M3 9h10M3 15h10M3 21h10"
|
||||
className="stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M29 29V19h-8.5L13 26c0 1.5-2.5 3-5 3h21Z"
|
||||
fillOpacity={0.5}
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</LightMode>
|
||||
<DarkMode>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3 2a1 1 0 0 0-1 1v21a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H3Zm16.752 3.293a1 1 0 0 0-1.593.244l-1.045 2A1 1 0 0 0 17 8v13a1 1 0 0 0 1.71.705l7.999-8.045a1 1 0 0 0-.002-1.412l-6.955-6.955ZM26 18a1 1 0 0 0-.707.293l-10 10A1 1 0 0 0 16 30h13a1 1 0 0 0 1-1V19a1 1 0 0 0-1-1h-3ZM5 18a1 1 0 1 0 0 2h6a1 1 0 1 0 0-2H5Zm-1-5a1 1 0 0 1 1-1h6a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1Zm1-7a1 1 0 0 0 0 2h6a1 1 0 1 0 0-2H5Z"
|
||||
fill={`url(#${id}-gradient-dark)`}
|
||||
/>
|
||||
</DarkMode>
|
||||
</>
|
||||
)
|
||||
}
|
||||
55
src/components/icons/WarningIcon.jsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { DarkMode, Gradient, LightMode } from '@/components/Icon'
|
||||
|
||||
export function WarningIcon({ id, color }) {
|
||||
return (
|
||||
<>
|
||||
<defs>
|
||||
<Gradient
|
||||
id={`${id}-gradient`}
|
||||
color={color}
|
||||
gradientTransform="rotate(65.924 1.519 20.92) scale(25.7391)"
|
||||
/>
|
||||
<Gradient
|
||||
id={`${id}-gradient-dark`}
|
||||
color={color}
|
||||
gradientTransform="matrix(0 24.5 -24.5 0 16 5.5)"
|
||||
/>
|
||||
</defs>
|
||||
<LightMode>
|
||||
<circle cx={20} cy={20} r={12} fill={`url(#${id}-gradient)`} />
|
||||
<path
|
||||
d="M3 16c0 7.18 5.82 13 13 13s13-5.82 13-13S23.18 3 16 3 3 8.82 3 16Z"
|
||||
fillOpacity={0.5}
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="m15.408 16.509-1.04-5.543a1.66 1.66 0 1 1 3.263 0l-1.039 5.543a.602.602 0 0 1-1.184 0Z"
|
||||
className="fill-[var(--icon-foreground)] stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M16 23a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
|
||||
fillOpacity={0.5}
|
||||
stroke="currentColor"
|
||||
className="fill-[var(--icon-background)] stroke-[color:var(--icon-foreground)]"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</LightMode>
|
||||
<DarkMode>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M2 16C2 8.268 8.268 2 16 2s14 6.268 14 14-6.268 14-14 14S2 23.732 2 16Zm11.386-4.85a2.66 2.66 0 1 1 5.228 0l-1.039 5.543a1.602 1.602 0 0 1-3.15 0l-1.04-5.543ZM16 20a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z"
|
||||
fill={`url(#${id}-gradient-dark)`}
|
||||
/>
|
||||
</DarkMode>
|
||||
</>
|
||||
)
|
||||
}
|
||||
BIN
src/images/blur-cyan.png
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
src/images/blur-indigo.png
Normal file
|
After Width: | Height: | Size: 219 KiB |
75
src/pages/_app.jsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import Head from 'next/head'
|
||||
import { slugifyWithCounter } from '@sindresorhus/slugify'
|
||||
|
||||
import { Layout } from '@/components/Layout'
|
||||
|
||||
import 'focus-visible'
|
||||
import '@/styles/tailwind.css'
|
||||
|
||||
function getNodeText(node) {
|
||||
let text = ''
|
||||
for (let child of node.children ?? []) {
|
||||
if (typeof child === 'string') {
|
||||
text += child
|
||||
}
|
||||
text += getNodeText(child)
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
function collectHeadings(nodes, slugify = slugifyWithCounter()) {
|
||||
let sections = []
|
||||
|
||||
for (let node of nodes) {
|
||||
if (node.name === 'h2' || node.name === 'h3') {
|
||||
let title = getNodeText(node)
|
||||
if (title) {
|
||||
let id = slugify(title)
|
||||
node.attributes.id = id
|
||||
if (node.name === 'h3') {
|
||||
if (!sections[sections.length - 1]) {
|
||||
throw new Error(
|
||||
'Cannot add `h3` to table of contents without a preceding `h2`'
|
||||
)
|
||||
}
|
||||
sections[sections.length - 1].children.push({
|
||||
...node.attributes,
|
||||
title,
|
||||
})
|
||||
} else {
|
||||
sections.push({ ...node.attributes, title, children: [] })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sections.push(...collectHeadings(node.children ?? [], slugify))
|
||||
}
|
||||
|
||||
return sections
|
||||
}
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
let title = pageProps.markdoc?.frontmatter.title
|
||||
|
||||
let pageTitle =
|
||||
pageProps.markdoc?.frontmatter.pageTitle ||
|
||||
`${pageProps.markdoc?.frontmatter.title} - Docs`
|
||||
|
||||
let description = pageProps.markdoc?.frontmatter.description
|
||||
|
||||
let tableOfContents = pageProps.markdoc?.content
|
||||
? collectHeadings(pageProps.markdoc.content)
|
||||
: []
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{pageTitle}</title>
|
||||
{description && <meta name="description" content={description} />}
|
||||
</Head>
|
||||
<Layout title={title} tableOfContents={tableOfContents}>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
53
src/pages/_document.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Head, Html, Main, NextScript } from 'next/document'
|
||||
|
||||
const themeScript = `
|
||||
let isDarkMode = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
|
||||
function updateTheme(theme) {
|
||||
theme = theme ?? window.localStorage.theme ?? 'system'
|
||||
|
||||
if (theme === 'dark' || (theme === 'system' && isDarkMode.matches)) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else if (theme === 'light' || (theme === 'system' && !isDarkMode.matches)) {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
|
||||
return theme
|
||||
}
|
||||
|
||||
function updateThemeWithoutTransitions(theme) {
|
||||
updateTheme(theme)
|
||||
document.documentElement.classList.add('[&_*]:!transition-none')
|
||||
window.setTimeout(() => {
|
||||
document.documentElement.classList.remove('[&_*]:!transition-none')
|
||||
}, 0)
|
||||
}
|
||||
|
||||
document.documentElement.setAttribute('data-theme', updateTheme())
|
||||
|
||||
new MutationObserver(([{ oldValue }]) => {
|
||||
let newValue = document.documentElement.getAttribute('data-theme')
|
||||
if (newValue !== oldValue) {
|
||||
try {
|
||||
window.localStorage.setItem('theme', newValue)
|
||||
} catch {}
|
||||
updateThemeWithoutTransitions(newValue)
|
||||
}
|
||||
}).observe(document.documentElement, { attributeFilter: ['data-theme'], attributeOldValue: true })
|
||||
|
||||
isDarkMode.addEventListener('change', () => updateThemeWithoutTransitions())
|
||||
`
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html className="antialiased [font-feature-settings:'ss01']" lang="en">
|
||||
<Head>
|
||||
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
||||
</Head>
|
||||
<body className="bg-white dark:bg-slate-900">
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
71
src/pages/clients.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Clients
|
||||
description: An overview of how Nostr clients work and a comparison of the available clients
|
||||
---
|
||||
|
||||
Quasi sapiente voluptates aut minima non doloribus similique quisquam. In quo expedita ipsum nostrum corrupti incidunt. Et aut eligendi ea perferendis.
|
||||
|
||||
---
|
||||
|
||||
## Quis vel iste dicta
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Et pariatur ab quas
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
```js
|
||||
/** @type {import('@tailwindlabs/lorem').ipsum} */
|
||||
export default {
|
||||
lorem: 'ipsum',
|
||||
dolor: ['sit', 'amet', 'consectetur'],
|
||||
adipiscing: {
|
||||
elit: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Natus aspernatur iste
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Quos porro ut molestiae
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Voluptatem quas possimus
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Id vitae minima
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Vitae laborum maiores
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Corporis exercitationem
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Reprehenderit magni
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
71
src/pages/clients/comparison.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Client Comparison
|
||||
description: A comparison of the available Nostr clients on all platforms
|
||||
---
|
||||
|
||||
Quasi sapiente voluptates aut minima non doloribus similique quisquam. In quo expedita ipsum nostrum corrupti incidunt. Et aut eligendi ea perferendis.
|
||||
|
||||
---
|
||||
|
||||
## Quis vel iste dicta
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Et pariatur ab quas
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
```js
|
||||
/** @type {import('@tailwindlabs/lorem').ipsum} */
|
||||
export default {
|
||||
lorem: 'ipsum',
|
||||
dolor: ['sit', 'amet', 'consectetur'],
|
||||
adipiscing: {
|
||||
elit: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Natus aspernatur iste
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Quos porro ut molestiae
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Voluptatem quas possimus
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Id vitae minima
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Vitae laborum maiores
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Corporis exercitationem
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Reprehenderit magni
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
71
src/pages/contribute.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: How to contribute
|
||||
description: Quidem magni aut exercitationem maxime rerum eos.
|
||||
---
|
||||
|
||||
Quasi sapiente voluptates aut minima non doloribus similique quisquam. In quo expedita ipsum nostrum corrupti incidunt. Et aut eligendi ea perferendis.
|
||||
|
||||
---
|
||||
|
||||
## Quis vel iste dicta
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Et pariatur ab quas
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
```js
|
||||
/** @type {import('@tailwindlabs/lorem').ipsum} */
|
||||
export default {
|
||||
lorem: 'ipsum',
|
||||
dolor: ['sit', 'amet', 'consectetur'],
|
||||
adipiscing: {
|
||||
elit: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Natus aspernatur iste
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Quos porro ut molestiae
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Voluptatem quas possimus
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Id vitae minima
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Vitae laborum maiores
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Corporis exercitationem
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Reprehenderit magni
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
71
src/pages/contribute/code.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Help us maintain Nostr.how
|
||||
description: We need your help to maintain the Nostr.how codebase
|
||||
---
|
||||
|
||||
Quasi sapiente voluptates aut minima non doloribus similique quisquam. In quo expedita ipsum nostrum corrupti incidunt. Et aut eligendi ea perferendis.
|
||||
|
||||
---
|
||||
|
||||
## Quis vel iste dicta
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Et pariatur ab quas
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
```js
|
||||
/** @type {import('@tailwindlabs/lorem').ipsum} */
|
||||
export default {
|
||||
lorem: 'ipsum',
|
||||
dolor: ['sit', 'amet', 'consectetur'],
|
||||
adipiscing: {
|
||||
elit: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Natus aspernatur iste
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Quos porro ut molestiae
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Voluptatem quas possimus
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Id vitae minima
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Vitae laborum maiores
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Corporis exercitationem
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Reprehenderit magni
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
71
src/pages/contribute/translate.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Help us translate Nostr.how
|
||||
description: We need your help to translate Nostr.how into more languages
|
||||
---
|
||||
|
||||
Quasi sapiente voluptates aut minima non doloribus similique quisquam. In quo expedita ipsum nostrum corrupti incidunt. Et aut eligendi ea perferendis.
|
||||
|
||||
---
|
||||
|
||||
## Quis vel iste dicta
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Et pariatur ab quas
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
```js
|
||||
/** @type {import('@tailwindlabs/lorem').ipsum} */
|
||||
export default {
|
||||
lorem: 'ipsum',
|
||||
dolor: ['sit', 'amet', 'consectetur'],
|
||||
adipiscing: {
|
||||
elit: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Natus aspernatur iste
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Quos porro ut molestiae
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Voluptatem quas possimus
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Id vitae minima
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Vitae laborum maiores
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Corporis exercitationem
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Reprehenderit magni
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
71
src/pages/contribute/write.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Help us write new guides for Nostr.how
|
||||
description: We need your help to create more content
|
||||
---
|
||||
|
||||
Quasi sapiente voluptates aut minima non doloribus similique quisquam. In quo expedita ipsum nostrum corrupti incidunt. Et aut eligendi ea perferendis.
|
||||
|
||||
---
|
||||
|
||||
## Quis vel iste dicta
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Et pariatur ab quas
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
```js
|
||||
/** @type {import('@tailwindlabs/lorem').ipsum} */
|
||||
export default {
|
||||
lorem: 'ipsum',
|
||||
dolor: ['sit', 'amet', 'consectetur'],
|
||||
adipiscing: {
|
||||
elit: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Natus aspernatur iste
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Quos porro ut molestiae
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Voluptatem quas possimus
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Id vitae minima
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Vitae laborum maiores
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Corporis exercitationem
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Reprehenderit magni
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
71
src/pages/get-started.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Get started
|
||||
description: How to get started using Nostr
|
||||
---
|
||||
|
||||
A step-by-step guide to getting started with Nostr.
|
||||
|
||||
---
|
||||
|
||||
## Understanding keys
|
||||
|
||||
Each Nostr account is based on a public/private key pair. A simple way to think about this is that your public key is your username and your private key is your password, with one major caveat. Unlike a password, your private cannot be reset if lost.
|
||||
|
||||
Let me say that again so it's clear: **If you lose your private key your Nostr account is lost. If somene else gains access to your private key, they can take control of your account.**
|
||||
|
||||
Make sure you store you private key somewhere safe, like a password manager (we like [1Password](https://1password.com/)).
|
||||
|
||||
## Protocol vs Client
|
||||
Nostr itself is just a protocol; an agreed upon procedure for passing messages around on the internet.
|
||||
|
||||
You will access Nostr (the protocol) via a client. Clients can be web, desktop, or mobile apps. Some Nostr clients allow you to sign in by pasting in your private key. This is generally not recommended - it's tedious and insecure.
|
||||
|
||||
Instead, we will use a crypto wallet, which is a piece of software specifically designed to manage private keys.
|
||||
|
||||
[Alby](https://chrome.google.com/webstore/detail/alby-bitcoin-lightning-wa/iokeahhehimjnekafflcihljlcjccdbe) is a bitcoin lightning wallet that has built-in support for Nostr. It's a great option for new users.
|
||||
|
||||
## Let's do this!
|
||||
|
||||
### Step 1 – Install Alby
|
||||
Get [Alby](https://chrome.google.com/webstore/detail/alby-bitcoin-lightning-wa/iokeahhehimjnekafflcihljlcjccdbe) from the Chrome Webstore or directly from the [Alby website](https://getalby.com/).
|
||||
|
||||
Once you have the extension installed create a new account. Unless you're a pro already, we recommend just creating an account on Alby and setting up a new Lightning wallet.
|
||||
|
||||
### Step 2 – Generate your Private Key
|
||||
|
||||
1. Once Alby is installed and you've created or connected to a lightning wallet, navigate to the Alby settings page. 
|
||||
1. Scroll to the Nostr section and click the "generate" button in the private key field. This will generate a private key and save it to your alby wallet. You can now use this private key to log into Nostr.
|
||||
1. Once you've generated your private key, click the small eyeball icon to show your key. Copy the private key (which will start with `nsec`) and save it to a password manager or somewhere **very** safe. Remember, if you lose this key, it's gone forever, along with all your account data.
|
||||
|
||||
### Step 3 – Log into Nostr via a client
|
||||
|
||||
Now that you have a private key, you can log into Nostr. But remember, Nostr is only a protocol, not an application, so you will need to use a Nostr client to access the network. For this guide we'll use [Astral](https://astral.ninja), a simple twitter-like web client.
|
||||
|
||||
1. Navigate to [Astral](https://astral.ninja).
|
||||
1. In the login popup that appears, click the "USE PUBLIC KEY FROM EXTENSION" button. 
|
||||
1. Authorize key usage in the alby extension popup. 
|
||||
1. Once you confirm you'll be presented with another popup in Astral. We can see here that Alby has passed our public key to Astral. Since we don't want to store our private key on Astral should just press "Proceed" here. 
|
||||
1. Astral will then show you yet another popup telling you that your private key isn't present. Again, this is what we want so you can just hit "Close". 
|
||||
1. You are logged in! It may take a few moments for content to start loading.
|
||||
1. At this point, you can copy your public key from the top left of the page. You should store this alongside your private key so you have it. 
|
||||
|
||||
### Step 4 – Find friends to follow
|
||||
* If you know someone is on Nostr, find their profile by searching for their public key.
|
||||
* Many Twitter users are tweeting their Nostr pubkeys with the hashtag #nostr so searching this hashtag can give you a good start.
|
||||
* [nostr.directory](https://nostr.directory) is a database mapping twitter users to their Nostr pubkeys.
|
||||
|
||||
## What does "Signing" mean?
|
||||
In order to interact with the Nostr protocol you must create a cryptographic signature each time you perform an action. Think of this signature as an authentication step where you confirm that you are, indeed, who you say you are.
|
||||
|
||||
Most Nostr clients try to make this easy and quick (or allow you to save your private key in the client so that they can sign on your behalf anytime you do anything).
|
||||
|
||||
By only providing our public key above, we'll be prompted to sign anytime we want to interact with any posts or perform any updates to our profile. When that happens, Alby will automatically pop up (like it did during the sign up step) and you can confirm that you actually want to sign.
|
||||
|
||||
## Can I use other clients?
|
||||
Yes! Now that you have created your public/private key pair, you can use this pair on any Nostr client to access your account. Remember, the client is just an interface to see messages broadcast on the Nostr protocol.
|
||||
|
||||
Since it's so early in Nostr's development, not all clients support all protocol features in the same way. It's worth checking out our [client comparison](/clients/comparison) to find the best client for you.
|
||||
|
||||
## Safely store your keys
|
||||
|
||||
You saved your private and public keys somewhere **very** safe, didn't you?
|
||||
57
src/pages/index.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: What is Nostr?
|
||||
pageTitle: Nostr.how - A complete guide to Nostr
|
||||
description: An easy to understand guide to the simplest, most decentralized social media protocol on the internet.
|
||||
---
|
||||
|
||||
Nostr is a simple, open protocol that enables global, decentralized, and censorship-resistant social media. {% .lead %}
|
||||
|
||||
{% quick-links %}
|
||||
|
||||
{% quick-link title="Get started" icon="installation" href="/get-started" description="Create an account and join thousands of others on Nostr." /%}
|
||||
|
||||
{% quick-link title="The protocol" icon="presets" href="/the-protocol" description="Learn more about how the Nostr protocol works and what makes it special." /%}
|
||||
|
||||
{% quick-link title="Find a client" icon="plugins" href="/clients" description="Find a client (app) for the web, iOS, Andriod, or Desktop." /%}
|
||||
|
||||
{% quick-link title="Contribute" icon="theming" href="/contribute" description="Find out how you can help the Nostr protocol or Nostr.how" /%}
|
||||
|
||||
{% /quick-links %}
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste.
|
||||
|
||||
---
|
||||
|
||||
## What is Nostr?
|
||||
|
||||
Nostr is a protocol, designed for simplicity, to create a censorship-resistant global social network. Let's unpack that a little:
|
||||
|
||||
### Simple
|
||||
The protocol is based on very simple & flexible `Event` objects (which are passed around as plain JSON) and uses standard public-key cryptography for keys & signing. This makes it easy to run relays and build clients and ensures that the protocol can be extended over time.
|
||||
|
||||
### Resilient
|
||||
Because Nostr doesn't rely on a small number of trusted servers for moving or storing data, it's very resilient. The protocol assumes that relays will disappear and allows users to connect & publish to an arbitrary number of relays that they can change over time.
|
||||
|
||||
### Verifiable
|
||||
Because Nostr accounts are based on [public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography) it's easy to verify messages were really sent by the user in question.
|
||||
|
||||
Like HTTP or TCP-IP, Nostr is a protocol; an open standard upon which anyone can build. Nostr is not an app or service that you sign up for.
|
||||
|
||||
## Why we need Nostr
|
||||
|
||||
Social media has developed into a key way information flows around the world. Unfortunately, our current social media systems are broken:
|
||||
|
||||
### Big tech social media
|
||||
1. Uses your attention to sell ads
|
||||
1. Uses bizarre techniques to keep you addicted (refer to point 1)
|
||||
1. Decides what content to show you based on a secret algorithm that you can't inspect or change
|
||||
1. Has complete control over who can participate and who is censored
|
||||
1. Is overrun with spam and bots
|
||||
|
||||
### Mastadon (and similar)
|
||||
1. User identities are attached to domain names which are controlled by third-parties.
|
||||
1. These third-parties can ban you, just like centralized social media platforms. Server owners can also block other servers.
|
||||
1. Migration between servers is difficult and can only be accomplished if servers cooperate.
|
||||
1. There are no clear incentives to run servers, therefore they tend to be run by enthusiasts and people who want to have their name attached to a cool domain. Because of this, users are subject to the despotism of a single person, which is often worse than that of a big company like Twitter, and they can't migrate out.
|
||||
1. Since servers tend to be run by amateurs, they are often abandoned. This effectively bans everybody that signed up via that server.
|
||||
1. There are huge issues with data duplication across servers.
|
||||
71
src/pages/resources.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Resources
|
||||
description: Other Nostr resources to explore
|
||||
---
|
||||
|
||||
Quasi sapiente voluptates aut minima non doloribus similique quisquam. In quo expedita ipsum nostrum corrupti incidunt. Et aut eligendi ea perferendis.
|
||||
|
||||
---
|
||||
|
||||
## Quis vel iste dicta
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Et pariatur ab quas
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
```js
|
||||
/** @type {import('@tailwindlabs/lorem').ipsum} */
|
||||
export default {
|
||||
lorem: 'ipsum',
|
||||
dolor: ['sit', 'amet', 'consectetur'],
|
||||
adipiscing: {
|
||||
elit: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Natus aspernatur iste
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Quos porro ut molestiae
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Voluptatem quas possimus
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Id vitae minima
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
|
||||
---
|
||||
|
||||
## Vitae laborum maiores
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur.
|
||||
|
||||
### Corporis exercitationem
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Possimus saepe veritatis sint nobis et quam eos. Architecto consequatur odit perferendis fuga eveniet possimus rerum cumque. Ea deleniti voluptatum deserunt voluptatibus ut non iste. Provident nam asperiores vel laboriosam omnis ducimus enim nesciunt quaerat. Minus tempora cupiditate est quod.
|
||||
|
||||
### Reprehenderit magni
|
||||
|
||||
Sit commodi iste iure molestias qui amet voluptatem sed quaerat. Nostrum aut pariatur. Sint ipsa praesentium dolor error cumque velit tenetur quaerat exercitationem. Consequatur et cum atque mollitia qui quia necessitatibus.
|
||||
|
||||
Voluptas beatae omnis omnis voluptas. Cum architecto ab sit ad eaque quas quia distinctio. Molestiae aperiam qui quis deleniti soluta quia qui. Dolores nostrum blanditiis libero optio id. Mollitia ad et asperiores quas saepe alias.
|
||||
17
src/pages/the-protocol.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: The Protocol
|
||||
description: Information on how the Nostr protocol works
|
||||
---
|
||||
|
||||
Quasi sapiente voluptates aut minima non doloribus similique quisquam. In quo expedita ipsum nostrum corrupti incidunt. Et aut eligendi ea perferendis.
|
||||
|
||||
---
|
||||
|
||||
## How does Nostr work?
|
||||
|
||||
* There are two components: clients and relays. Each user runs a client. Anyone can run a relay.
|
||||
* Every user is identified by a public key. Every post is signed. Every client validates these signatures.
|
||||
* Clients fetch data from relays of their choice and publish data to other relays of their choice. A relay doesn't talk to another relay, only directly to users.
|
||||
* For example, to "follow" someone a user just instructs their client to query the relays it knows for posts from that public key.
|
||||
* On startup, a client queries data from all relays it knows for all users it follows (for example, all updates from the last day), then displays that data to the user chronologically.
|
||||
* A "post" can contain any kind of structured data, but the most used ones are going to find their way into the standard so all clients and relays can handle them seamlessly.
|
||||
17
src/pages/the-protocol/events.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Events
|
||||
description: The basic atomic unit on the Nostr protocol
|
||||
---
|
||||
|
||||
Quasi sapiente voluptates aut minima non doloribus similique quisquam. In quo expedita ipsum nostrum corrupti incidunt. Et aut eligendi ea perferendis.
|
||||
|
||||
---
|
||||
|
||||
## How does Nostr work?
|
||||
|
||||
* There are two components: clients and relays. Each user runs a client. Anyone can run a relay.
|
||||
* Every user is identified by a public key. Every post is signed. Every client validates these signatures.
|
||||
* Clients fetch data from relays of their choice and publish data to other relays of their choice. A relay doesn't talk to another relay, only directly to users.
|
||||
* For example, to "follow" someone a user just instructs their client to query the relays it knows for posts from that public key.
|
||||
* On startup, a client queries data from all relays it knows for all users it follows (for example, all updates from the last day), then displays that data to the user chronologically.
|
||||
* A "post" can contain any kind of structured data, but the most used ones are going to find their way into the standard so all clients and relays can handle them seamlessly.
|
||||
17
src/pages/the-protocol/nips.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Nostr Implementation Possibilities (NIPs)
|
||||
description: Documentation on what must, should, and may be implemented by Nostr.
|
||||
---
|
||||
|
||||
Quasi sapiente voluptates aut minima non doloribus similique quisquam. In quo expedita ipsum nostrum corrupti incidunt. Et aut eligendi ea perferendis.
|
||||
|
||||
---
|
||||
|
||||
## How does Nostr work?
|
||||
|
||||
* There are two components: clients and relays. Each user runs a client. Anyone can run a relay.
|
||||
* Every user is identified by a public key. Every post is signed. Every client validates these signatures.
|
||||
* Clients fetch data from relays of their choice and publish data to other relays of their choice. A relay doesn't talk to another relay, only directly to users.
|
||||
* For example, to "follow" someone a user just instructs their client to query the relays it knows for posts from that public key.
|
||||
* On startup, a client queries data from all relays it knows for all users it follows (for example, all updates from the last day), then displays that data to the user chronologically.
|
||||
* A "post" can contain any kind of structured data, but the most used ones are going to find their way into the standard so all clients and relays can handle them seamlessly.
|
||||
50
src/pages/verify-your-identity.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Verify your Identity
|
||||
description: How to verify your identity on Nostr
|
||||
---
|
||||
|
||||
How to verify your identity on Nostr and get a verification checkmark and an easier way to share your account
|
||||
|
||||
---
|
||||
|
||||
## NIP-05 Verification
|
||||
You might have noticed on Astral (and other clients) that some users have checks, just like on Twitter.
|
||||
|
||||

|
||||
|
||||
The verification process on Nostr is docuemented in a Nostr Implementation Possibilities (NIP) called [NIP-05](https://github.com/nostr-protocol/nips/blob/master/05.md).
|
||||
|
||||
NIP-05 enables a Nostr user to map their public key to a DNS-based internet identifier. The verification mechanism is similar to how Google requires you to verify your ownership of a domain using a DNS record.
|
||||
|
||||
The major benefit of verification is that it allows a Nostr user to be identified by a human-readable name, instead of a long, hard-to-remember public key. This enables verified Nostr users to easily share their identity with others.
|
||||
|
||||
To utilize NIP-05, Nostr users add a nip05 url to their profile (most clients have support for this). NIP-05 urls look like emails – bob@example.com. Let's break down the parts:
|
||||
|
||||
1. Everything before the `@` symbol ("bob", in our example). This must match the value of the name field in your Nostr profile.
|
||||
1. Everything after the `@` symbol ("example.com", in our example). This is the domain where the client can look to find a `/.well-known/nostr.json` file that contains the user's name & public key.
|
||||
|
||||
When clients see a nip05 url, they will look for a /.well-known/nostr.json file at the specified domain. This file must contain the nostr public key for the specified user. Read more specifics in the NIP-05 spec.
|
||||
|
||||
While it sounds technical, it's suprisingly easy to get verified. Let's see how to do it.
|
||||
|
||||
### Option 1: Pay a provider for verification
|
||||
|
||||
If you don't have your own domain or don't want to set it up yourself, you can take advantage of a NIP-05 service, such as [nostrplebs.com](https://nostrplebs.com). In exchange for a handful of [sats](https://coinmarketcap.com/alexandria/glossary/satoshi-sats), you can add your public key to thier `nostr.json`.
|
||||
|
||||
|
||||
### Option 2: Self hosted verification
|
||||
|
||||
If you already own a domain, this is a free option. You just need to add a `.well-known/nostr.json` file to your domain. The contents of the file should be the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"names": {
|
||||
"YOUR_NOSTR_NAME": "YOUR_NOSTR_PUBLIC_KEY"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Make sure this file is served with the `Access-Control-Allow-Origin` header set to `*` as it needs to be accessible by clients.
|
||||
|
||||
|
||||
|
||||
503
src/styles/docsearch.css
Normal file
@@ -0,0 +1,503 @@
|
||||
/*! @docsearch/css 3.1.0 | MIT License | © Algolia, Inc. and contributors | https://docsearch.algolia.com */
|
||||
:root {
|
||||
--docsearch-primary-color: red;
|
||||
--docsearch-highlight-color: var(--docsearch-primary-color);
|
||||
--docsearch-muted-color: theme('colors.slate.500');
|
||||
--docsearch-emphasis-color: theme('colors.slate.900');
|
||||
--docsearch-logo-color: #5468ff;
|
||||
--docsearch-modal-width: 35rem;
|
||||
--docsearch-modal-height: 37.5rem;
|
||||
--docsearch-modal-background: theme('colors.white');
|
||||
--docsearch-modal-shadow: theme('boxShadow.xl');
|
||||
--docsearch-searchbox-height: 3rem;
|
||||
--docsearch-hit-color: theme('colors.slate.700');
|
||||
--docsearch-hit-active-color: theme('colors.sky.600');
|
||||
--docsearch-hit-active-background: theme('colors.slate.100');
|
||||
--docsearch-footer-height: 3rem;
|
||||
--docsearch-border-color: theme('colors.slate.200');
|
||||
--docsearch-input-color: theme('colors.slate.900');
|
||||
--docsearch-heading-color: theme('colors.slate.900');
|
||||
--docsearch-key-background: theme('colors.slate.100');
|
||||
--docsearch-key-hover-background: theme('colors.slate.200');
|
||||
--docsearch-key-color: theme('colors.slate.500');
|
||||
--docsearch-action-color: theme('colors.slate.400');
|
||||
--docsearch-action-active-background: theme('colors.slate.200');
|
||||
--docsearch-loading-background: theme('colors.slate.400');
|
||||
--docsearch-loading-foreground: theme('colors.slate.900');
|
||||
}
|
||||
|
||||
.dark {
|
||||
--docsearch-highlight-color: var(--docsearch-primary-color);
|
||||
--docsearch-muted-color: theme('colors.slate.400');
|
||||
--docsearch-emphasis-color: theme('colors.white');
|
||||
--docsearch-logo-color: theme('colors.slate.300');
|
||||
--docsearch-modal-background: theme('colors.slate.800');
|
||||
--docsearch-modal-shadow: 0 0 0 1px theme('colors.slate.700'),
|
||||
theme('boxShadow.xl');
|
||||
--docsearch-hit-color: theme('colors.slate.300');
|
||||
--docsearch-hit-active-color: theme('colors.sky.400');
|
||||
--docsearch-hit-active-background: rgb(51 65 85 / 0.3);
|
||||
--docsearch-border-color: rgb(148 163 184 / 0.1);
|
||||
--docsearch-heading-color: theme('colors.white');
|
||||
--docsearch-key-background: rgb(51 65 85 / 0.4);
|
||||
--docsearch-key-hover-background: rgb(51 65 85 / 0.8);
|
||||
--docsearch-key-color: theme('colors.slate.400');
|
||||
--docsearch-input-color: theme('colors.white');
|
||||
--docsearch-action-color: theme('colors.slate.500');
|
||||
--docsearch-action-active-background: theme('colors.slate.700');
|
||||
--docsearch-loading-background: theme('colors.slate.500');
|
||||
--docsearch-loading-foreground: theme('colors.white');
|
||||
}
|
||||
|
||||
.DocSearch--active {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.DocSearch-Container {
|
||||
position: fixed;
|
||||
z-index: 200;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
height: calc(var(--docsearch-vh, 1vh) * 100);
|
||||
background-color: rgb(15 23 42 / 0.5);
|
||||
backdrop-filter: blur(theme('backdropBlur.DEFAULT'));
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.DocSearch-Link {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: 0;
|
||||
color: var(--docsearch-highlight-color);
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.DocSearch-Modal {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
height: calc(var(--docsearch-vh, 1vh) * 100);
|
||||
background: var(--docsearch-modal-background);
|
||||
}
|
||||
|
||||
.DocSearch-SearchBar {
|
||||
display: flex;
|
||||
height: var(--docsearch-searchbox-height);
|
||||
border-bottom: 1px solid var(--docsearch-border-color);
|
||||
}
|
||||
|
||||
.DocSearch-Form {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.DocSearch-Input {
|
||||
appearance: none;
|
||||
color: var(--docsearch-input-color);
|
||||
flex: 1;
|
||||
font-size: 1rem;
|
||||
background: transparent;
|
||||
padding: 0 1rem 0 3rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.DocSearch-Input::placeholder {
|
||||
color: theme('colors.slate.400');
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.DocSearch-Input::-webkit-search-cancel-button,
|
||||
.DocSearch-Input::-webkit-search-decoration,
|
||||
.DocSearch-Input::-webkit-search-results-button,
|
||||
.DocSearch-Input::-webkit-search-results-decoration {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.DocSearch-Reset {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.DocSearch-Container--Stalled .DocSearch-MagnifierLabel,
|
||||
.DocSearch-LoadingIndicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.DocSearch-Container--Stalled .DocSearch-LoadingIndicator {
|
||||
position: absolute;
|
||||
top: 0.875rem;
|
||||
left: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.DocSearch-LoadingIndicator svg {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.DocSearch-LoadingIndicator path,
|
||||
.DocSearch-LoadingIndicator circle {
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.DocSearch-LoadingIndicator circle {
|
||||
stroke: var(--docsearch-loading-background);
|
||||
stroke-opacity: 1;
|
||||
}
|
||||
|
||||
.DocSearch-LoadingIndicator path {
|
||||
stroke: var(--docsearch-loading-foreground);
|
||||
stroke-opacity: 1;
|
||||
}
|
||||
|
||||
.DocSearch-MagnifierLabel {
|
||||
position: absolute;
|
||||
top: 0.875rem;
|
||||
left: 1rem;
|
||||
pointer-events: none;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
background: url("data:image/svg+xml,%3Csvg fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.293 17.707a1 1 0 0 0 1.414-1.414l-1.414 1.414ZM9 14a5 5 0 0 1-5-5H2a7 7 0 0 0 7 7v-2ZM4 9a5 5 0 0 1 5-5V2a7 7 0 0 0-7 7h2Zm5-5a5 5 0 0 1 5 5h2a7 7 0 0 0-7-7v2Zm8.707 12.293-3.757-3.757-1.414 1.414 3.757 3.757 1.414-1.414ZM14 9a4.98 4.98 0 0 1-1.464 3.536l1.414 1.414A6.98 6.98 0 0 0 16 9h-2Zm-1.464 3.536A4.98 4.98 0 0 1 9 14v2a6.98 6.98 0 0 0 4.95-2.05l-1.414-1.414Z' fill='%2394A3B8'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.dark .DocSearch-MagnifierLabel {
|
||||
background: url("data:image/svg+xml,%3Csvg fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.293 17.707a1 1 0 0 0 1.414-1.414l-1.414 1.414ZM9 14a5 5 0 0 1-5-5H2a7 7 0 0 0 7 7v-2ZM4 9a5 5 0 0 1 5-5V2a7 7 0 0 0-7 7h2Zm5-5a5 5 0 0 1 5 5h2a7 7 0 0 0-7-7v2Zm8.707 12.293-3.757-3.757-1.414 1.414 3.757 3.757 1.414-1.414ZM14 9a4.98 4.98 0 0 1-1.464 3.536l1.414 1.414A6.98 6.98 0 0 0 16 9h-2Zm-1.464 3.536A4.98 4.98 0 0 1 9 14v2a6.98 6.98 0 0 0 4.95-2.05l-1.414-1.414Z' fill='%2364748b'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.DocSearch-MagnifierLabel svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.DocSearch-Dropdown {
|
||||
height: 100%;
|
||||
max-height: calc(
|
||||
var(--docsearch-vh, 1vh) * 100 - var(--docsearch-searchbox-height) -
|
||||
var(--docsearch-footer-height)
|
||||
);
|
||||
overflow-y: auto;
|
||||
overflow-y: overlay;
|
||||
padding: 1rem 0.5rem;
|
||||
scrollbar-color: var(--docsearch-muted-color)
|
||||
var(--docsearch-modal-background);
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.DocSearch-Dropdown::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.DocSearch-Dropdown::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.DocSearch-Dropdown::-webkit-scrollbar-thumb {
|
||||
background-color: var(--docsearch-muted-color);
|
||||
border: 3px solid var(--docsearch-modal-background);
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.DocSearch-StartScreen {
|
||||
padding: 2rem 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.DocSearch-Label {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.DocSearch-Help,
|
||||
.DocSearch-Label {
|
||||
color: var(--docsearch-muted-color);
|
||||
}
|
||||
|
||||
.DocSearch-Help {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.DocSearch-Title {
|
||||
font-size: 0.875rem;
|
||||
color: var(--docsearch-muted-color);
|
||||
}
|
||||
|
||||
.DocSearch-Title strong {
|
||||
color: var(--docsearch-emphasis-color);
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
.DocSearch-Logo a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.DocSearch-Logo svg {
|
||||
color: var(--docsearch-logo-color);
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.DocSearch-Hits + .DocSearch-Hits {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.DocSearch-Hits mark {
|
||||
background: none;
|
||||
color: var(--docsearch-hit-active-color);
|
||||
}
|
||||
|
||||
.DocSearch-HitsFooter {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.DocSearch-Hit {
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.DocSearch-Hit--deleting,
|
||||
.DocSearch-Hit--favoriting {
|
||||
transform: scale(1);
|
||||
transition: all 0.0001s linear;
|
||||
}
|
||||
|
||||
.DocSearch-Hit a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-radius: theme('borderRadius.lg');
|
||||
}
|
||||
|
||||
.DocSearch-Hit-source,
|
||||
.DocSearch-NoResults .DocSearch-Help {
|
||||
margin-left: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-family: theme('fontFamily.display');
|
||||
color: var(--docsearch-heading-color);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-Tree {
|
||||
width: 0.5rem;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-Tree * {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.DocSearch-Hit[aria-selected='true'] a,
|
||||
.DocSearch-Prefill:hover,
|
||||
.DocSearch-Prefill:focus {
|
||||
background-color: var(--docsearch-hit-active-background);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.DocSearch-Hit[aria-selected='true'] mark {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-Container,
|
||||
.DocSearch-Prefill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.75rem;
|
||||
color: var(--docsearch-hit-color);
|
||||
}
|
||||
|
||||
.DocSearch-Hit-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-action {
|
||||
color: var(--docsearch-action-color);
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-action + .DocSearch-Hit-action {
|
||||
margin-left: 0.375rem;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-action-button {
|
||||
border-radius: 50%;
|
||||
color: inherit;
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-action svg {
|
||||
height: 1.125rem;
|
||||
width: 1.125rem;
|
||||
}
|
||||
|
||||
svg.DocSearch-Hit-Select-Icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-Select-Icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-action-button:focus,
|
||||
.DocSearch-Hit-action-button:hover {
|
||||
background: var(--docsearch-action-active-background);
|
||||
}
|
||||
|
||||
.DocSearch-Hit-content-wrapper {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-title,
|
||||
.DocSearch-Prefill {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-path {
|
||||
color: var(--docsearch-muted-color);
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-path,
|
||||
.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-text,
|
||||
.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-title,
|
||||
.DocSearch-Hit[aria-selected='true'] mark,
|
||||
.DocSearch-Prefill:hover,
|
||||
.DocSearch-Prefill:focus {
|
||||
color: var(--docsearch-hit-active-color);
|
||||
}
|
||||
|
||||
.DocSearch-NoResults .DocSearch-Screen-Icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.DocSearch-NoResults .DocSearch-Title {
|
||||
text-align: center;
|
||||
padding: 2rem 1rem 3rem;
|
||||
}
|
||||
|
||||
.DocSearch-NoResults-Prefill-List {
|
||||
margin: 0 -0.5rem;
|
||||
padding: 1rem 0.5rem 0;
|
||||
border-top: 1px solid var(--docsearch-border-color);
|
||||
}
|
||||
|
||||
.DocSearch-Prefill {
|
||||
width: 100%;
|
||||
border-radius: theme('borderRadius.lg');
|
||||
}
|
||||
|
||||
.DocSearch-Footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
height: var(--docsearch-footer-height);
|
||||
z-index: 300;
|
||||
border-top: 1px solid var(--docsearch-border-color);
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.DocSearch-Commands {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.DocSearch-Cancel {
|
||||
background: var(--docsearch-key-background);
|
||||
color: var(--docsearch-key-color);
|
||||
align-self: center;
|
||||
flex: none;
|
||||
font-size: 0.75rem;
|
||||
user-select: none;
|
||||
border-radius: theme('borderRadius.md');
|
||||
padding: 0 0.375rem;
|
||||
height: 1.5rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.DocSearch-Cancel:hover {
|
||||
background: var(--docsearch-key-hover-background);
|
||||
}
|
||||
|
||||
@screen sm {
|
||||
.DocSearch-Container {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.DocSearch-Modal {
|
||||
height: auto;
|
||||
border-radius: theme('borderRadius.xl');
|
||||
box-shadow: var(--docsearch-modal-shadow);
|
||||
margin: 4rem auto auto;
|
||||
width: auto;
|
||||
max-width: var(--docsearch-modal-width);
|
||||
}
|
||||
|
||||
.DocSearch-Input {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.DocSearch-Footer {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.DocSearch-Commands {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.DocSearch-Commands li {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.DocSearch-Commands li:not(:last-of-type) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.DocSearch-Commands-Key {
|
||||
background: var(--docsearch-key-background);
|
||||
color: var(--docsearch-key-color);
|
||||
width: 1.5rem;
|
||||
height: 1.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: theme('borderRadius.md');
|
||||
margin-right: 0.375rem;
|
||||
}
|
||||
|
||||
.DocSearch-Dropdown {
|
||||
height: auto;
|
||||
max-height: calc(
|
||||
var(--docsearch-modal-height) - var(--docsearch-searchbox-height) -
|
||||
var(--docsearch-footer-height)
|
||||
);
|
||||
}
|
||||
}
|
||||
23
src/styles/fonts.css
Normal file
@@ -0,0 +1,23 @@
|
||||
@font-face {
|
||||
font-family: 'Lexend';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url(/fonts/lexend.woff2) format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-weight: 100 900;
|
||||
font-display: block;
|
||||
font-style: normal;
|
||||
font-named-instance: 'Regular';
|
||||
src: url('/fonts/Inter-roman.var.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-weight: 100 900;
|
||||
font-display: block;
|
||||
font-style: italic;
|
||||
font-named-instance: 'Italic';
|
||||
src: url('/fonts/Inter-italic.var.woff2') format('woff2');
|
||||
}
|
||||
47
src/styles/prism.css
Normal file
@@ -0,0 +1,47 @@
|
||||
pre[class*='language-'] {
|
||||
color: theme('colors.slate.50');
|
||||
}
|
||||
|
||||
.token.tag,
|
||||
.token.class-name,
|
||||
.token.selector,
|
||||
.token.selector .class,
|
||||
.token.selector.class,
|
||||
.token.function {
|
||||
color: theme('colors.pink.400');
|
||||
}
|
||||
|
||||
.token.attr-name,
|
||||
.token.keyword,
|
||||
.token.rule,
|
||||
.token.pseudo-class,
|
||||
.token.important {
|
||||
color: theme('colors.slate.300');
|
||||
}
|
||||
|
||||
.token.module {
|
||||
color: theme('colors.pink.400');
|
||||
}
|
||||
|
||||
.token.attr-value,
|
||||
.token.class,
|
||||
.token.string,
|
||||
.token.property {
|
||||
color: theme('colors.sky.300');
|
||||
}
|
||||
|
||||
.token.punctuation,
|
||||
.token.attr-equals {
|
||||
color: theme('colors.slate.500');
|
||||
}
|
||||
|
||||
.token.unit,
|
||||
.language-css .token.function {
|
||||
color: theme('colors.teal.200');
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.operator,
|
||||
.token.combinator {
|
||||
color: theme('colors.slate.400');
|
||||
}
|
||||
6
src/styles/tailwind.css
Normal file
@@ -0,0 +1,6 @@
|
||||
@import 'tailwindcss/base';
|
||||
@import './fonts.css';
|
||||
@import './docsearch.css';
|
||||
@import './prism.css';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
49
tailwind.config.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const { NodeNextRequest } = require('next/dist/server/base-http/node')
|
||||
const defaultTheme = require('tailwindcss/defaultTheme')
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{js,jsx}'],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
fontSize: {
|
||||
xs: ['0.75rem', { lineHeight: '1rem' }],
|
||||
sm: ['0.875rem', { lineHeight: '1.5rem' }],
|
||||
base: ['1rem', { lineHeight: '2rem' }],
|
||||
lg: ['1.125rem', { lineHeight: '1.75rem' }],
|
||||
xl: ['1.25rem', { lineHeight: '2rem' }],
|
||||
'2xl': ['1.5rem', { lineHeight: '2.5rem' }],
|
||||
'3xl': ['2rem', { lineHeight: '2.5rem' }],
|
||||
'4xl': ['2.5rem', { lineHeight: '3rem' }],
|
||||
'5xl': ['3rem', { lineHeight: '3.5rem' }],
|
||||
'6xl': ['3.75rem', { lineHeight: '1' }],
|
||||
'7xl': ['4.5rem', { lineHeight: '1' }],
|
||||
'8xl': ['6rem', { lineHeight: '1' }],
|
||||
'9xl': ['8rem', { lineHeight: '1' }],
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', ...defaultTheme.fontFamily.sans],
|
||||
display: ['Lexend', ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
maxWidth: {
|
||||
'8xl': '88rem',
|
||||
},
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
css: {
|
||||
code: {
|
||||
'&::before': {
|
||||
display: 'none',
|
||||
},
|
||||
'&::after': {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
}
|
||||