Add cms-drupal example (#25198)

Adds cms-drupal example.

Demo: https://cms-drupal.vercel.app
This commit is contained in:
arshad 2021-06-17 00:59:31 +04:00 committed by GitHub
parent 22676abb31
commit 3231c35354
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 960 additions and 0 deletions

View file

@ -0,0 +1,6 @@
NEXT_PUBLIC_DRUPAL_BASE_URL=http://localhost:8080
NEXT_IMAGE_DOMAIN=localhost
DRUPAL_SITE_ID=
DRUPAL_CLIENT_ID=
DRUPAL_CLIENT_SECRET=
DRUPAL_PREVIEW_SECRET=

34
examples/cms-drupal/.gitignore vendored Normal file
View file

@ -0,0 +1,34 @@
# 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*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

View file

@ -0,0 +1,51 @@
# A statically generated blog example using Next.js and Drupal
This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [Drupal](https://drupal.org/project/next) as the data source.
## Demo
### [https://cms-drupal.vercel.app](https://cms-drupal.vercel.app)
## Deploy your own
Once you have [configured the Next.js module for Drupal](https://next-drupal.org/guides/decoupled-drupal-nextjs) and have access to the environment variables you'll need, deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?c=1&s=https://github.com/vercel/next.js/tree/canary/examples/cms-drupal&env=NEXT_PUBLIC_DRUPAL_BASE_URL,NEXT_IMAGE_DOMAIN,DRUPAL_SITE_ID,DRUPAL_FRONT_PAGE,DRUPAL_PREVIEW_SECRET,DRUPAL_NEXT_CLIENT_ID,DRUPAL_NEXT_CLIENT_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20ghost&envLink=https://vercel.link/cms-drupal-env)
### Related examples
- [WordPress](/examples/cms-wordpress)
- [DatoCMS](/examples/cms-datocms)
- [Sanity](/examples/cms-sanity)
- [TakeShape](/examples/cms-takeshape)
- [Prismic](/examples/cms-prismic)
- [Contentful](/examples/cms-contentful)
- [Strapi](/examples/cms-strapi)
- [Agility CMS](/examples/cms-agilitycms)
- [Cosmic](/examples/cms-cosmic)
- [ButterCMS](/examples/cms-buttercms)
- [Storyblok](/examples/cms-storyblok)
- [Kontent](/examples/cms-kontent)
- [Ghost](/examples/cms-ghost)
- [GraphCMS](/examples/cms-graphcms)
- [Blog Starter](/examples/blog-starter)
## How to use
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
```bash
npx create-next-app --example cms-drupal cms-drupal-app
# or
yarn create next-app --example cms-drupal cms-drupal-app
```
## Setup Drupal
See the Get Started guide [here](https://next-drupal.org/guides/decoupled-drupal-nextjs).
#### Deploy from Our Template
Alternatively, you can deploy using our template by clicking on the Deploy button below.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?c=1&s=https://github.com/vercel/next.js/tree/canary/examples/cms-drupal&env=NEXT_PUBLIC_DRUPAL_BASE_URL,NEXT_IMAGE_DOMAIN,DRUPAL_SITE_ID,DRUPAL_FRONT_PAGE,DRUPAL_PREVIEW_SECRET,DRUPAL_NEXT_CLIENT_ID,DRUPAL_NEXT_CLIENT_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20ghost&envLink=https://vercel.link/cms-drupal-env)

View file

@ -0,0 +1,42 @@
import Container from './container'
import cn from 'classnames'
import { EXAMPLE_PATH } from '../lib/constants'
export default function Alert({ preview }) {
return (
<div
className={cn('border-b', {
'bg-accent-7 border-accent-7 text-white': preview,
'bg-accent-1 border-accent-2': !preview,
})}
>
<Container>
<div className="py-2 text-center text-sm">
{preview ? (
<>
This page is a preview.{' '}
<a
href="/api/exit-preview"
className="underline hover:text-cyan duration-200 transition-colors"
>
Click here
</a>{' '}
to exit preview mode.
</>
) : (
<>
The source code for this blog is{' '}
<a
href={`https://github.com/vercel/next.js/tree/canary/examples/${EXAMPLE_PATH}`}
className="underline hover:text-success duration-200 transition-colors"
>
available on GitHub
</a>
.
</>
)}
</div>
</Container>
</div>
)
}

View file

@ -0,0 +1,27 @@
import Image from 'next/image'
export default function Avatar({ author }) {
const name = author
? author.firstName && author.lastName
? `${author.firstName} ${author.lastName}`
: author.name
: null
return (
<>
{author && (
<div className="flex items-center">
<div className="w-12 h-12 relative mr-4">
<Image
src={author.avatar.url}
layout="fill"
className="w-12 h-12 rounded-full mr-4"
alt={name}
/>
</div>
<div className="text-xl font-bold">{name}</div>
</div>
)}
</>
)
}

View file

@ -0,0 +1,16 @@
export default function Categories({ categories }) {
return (
<span className="ml-1">
under
{categories.edges.length > 0 ? (
categories.edges.map((category, index) => (
<span key={index} className="ml-1">
{category.node.name}
</span>
))
) : (
<span className="ml-1">{categories.edges.node.name}</span>
)}
</span>
)
}

View file

@ -0,0 +1,3 @@
export default function Container({ children }) {
return <div className="container mx-auto px-5">{children}</div>
}

View file

@ -0,0 +1,28 @@
import cn from 'classnames'
import Image from 'next/image'
import Link from 'next/link'
export default function CoverImage({ title, coverImage, slug }) {
const image = (
<Image
width={2000}
height={1000}
alt={`Cover Image for ${title}`}
src={coverImage?.sourceUrl}
className={cn('shadow-small', {
'hover:shadow-medium transition-shadow duration-200': slug,
})}
/>
)
return (
<div className="sm:mx-0">
{slug ? (
<Link href={slug}>
<a aria-label={title}>{image}</a>
</Link>
) : (
image
)}
</div>
)
}

View file

@ -0,0 +1,6 @@
import { parseISO, format } from 'date-fns'
export default function Date({ dateString }) {
const date = parseISO(dateString)
return <time dateTime={dateString}>{format(date, 'LLLL d, yyyy')}</time>
}

View file

@ -0,0 +1,30 @@
import Container from './container'
import { EXAMPLE_PATH } from '../lib/constants'
export default function Footer() {
return (
<footer className="bg-accent-1 border-t border-accent-2">
<Container>
<div className="py-28 flex flex-col lg:flex-row items-center">
<h3 className="text-4xl lg:text-5xl font-bold tracking-tighter leading-tight text-center lg:text-left mb-10 lg:mb-0 lg:pr-4 lg:w-1/2">
Statically Generated with Next.js.
</h3>
<div className="flex flex-col lg:flex-row justify-center items-center lg:pl-4 lg:w-1/2">
<a
href="https://nextjs.org/docs/basic-features/pages"
className="mx-3 bg-black hover:bg-white hover:text-black border border-black text-white font-bold py-3 px-12 lg:px-8 duration-200 transition-colors mb-6 lg:mb-0"
>
Read Documentation
</a>
<a
href={`https://github.com/vercel/next.js/tree/canary/examples/${EXAMPLE_PATH}`}
className="mx-3 font-bold hover:underline"
>
View on GitHub
</a>
</div>
</div>
</Container>
</footer>
)
}

View file

@ -0,0 +1,12 @@
import Link from 'next/link'
export default function Header() {
return (
<h2 className="text-2xl md:text-4xl font-bold tracking-tight md:tracking-tighter leading-tight mb-20 mt-8">
<Link href="/">
<a className="hover:underline">Blog</a>
</Link>
.
</h2>
)
}

View file

@ -0,0 +1,45 @@
import Avatar from '../components/avatar'
import Date from '../components/date'
import CoverImage from '../components/cover-image'
import Link from 'next/link'
export default function HeroPost({
title,
coverImage,
date,
excerpt,
author,
slug,
}) {
return (
<section>
<div className="mb-8 md:mb-16">
{coverImage && (
<CoverImage title={title} coverImage={coverImage} slug={slug} />
)}
</div>
<div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28">
<div>
<h3 className="mb-4 text-4xl lg:text-6xl leading-tight">
<Link href={slug}>
<a
className="hover:underline"
dangerouslySetInnerHTML={{ __html: title }}
/>
</Link>
</h3>
<div className="mb-4 md:mb-0 text-lg">
<Date dateString={date} />
</div>
</div>
<div>
<div
className="text-lg leading-relaxed mb-4"
dangerouslySetInnerHTML={{ __html: excerpt }}
/>
<Avatar author={author} />
</div>
</div>
</section>
)
}

View file

@ -0,0 +1,28 @@
import { CMS_NAME, CMS_URL } from '../lib/constants'
export default function Intro() {
return (
<section className="flex-col md:flex-row flex items-center md:justify-between mt-16 mb-16 md:mb-12">
<h1 className="text-6xl md:text-8xl font-bold tracking-tighter leading-tight md:pr-8">
Blog.
</h1>
<h4 className="text-center md:text-left text-lg mt-5 md:pl-8">
A statically generated blog example using{' '}
<a
href="https://nextjs.org/"
className="underline hover:text-success duration-200 transition-colors"
>
Next.js
</a>{' '}
and{' '}
<a
href={CMS_URL}
className="underline hover:text-success duration-200 transition-colors"
>
{CMS_NAME}
</a>
.
</h4>
</section>
)
}

View file

@ -0,0 +1,16 @@
import Alert from '../components/alert'
import Footer from '../components/footer'
import Meta from '../components/meta'
export default function Layout({ preview, children }) {
return (
<>
<Meta />
<div className="min-h-screen">
<Alert preview={preview} />
<main>{children}</main>
</div>
<Footer />
</>
)
}

View file

@ -0,0 +1,42 @@
import Head from 'next/head'
import { CMS_NAME, HOME_OG_IMAGE_URL } from '../lib/constants'
export default function Meta() {
return (
<Head>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/favicon/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon/favicon-16x16.png"
/>
<link rel="manifest" href="/favicon/site.webmanifest" />
<link
rel="mask-icon"
href="/favicon/safari-pinned-tab.svg"
color="#000000"
/>
<link rel="shortcut icon" href="/favicon/favicon.ico" />
<meta name="msapplication-TileColor" content="#000000" />
<meta name="msapplication-config" content="/favicon/browserconfig.xml" />
<meta name="theme-color" content="#000" />
<link rel="alternate" type="application/rss+xml" href="/feed.xml" />
<meta
name="description"
content={`A statically generated blog example using Next.js and ${CMS_NAME}.`}
/>
<meta property="og:image" content={HOME_OG_IMAGE_URL} />
</Head>
)
}

View file

@ -0,0 +1,32 @@
import { absoluteURL } from '../lib/api'
import PostPreview from './post-preview'
export default function MoreStories({ posts }) {
return (
<section>
<h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight">
More Stories
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-16 lg:gap-32 gap-20 md:gap-32 mb-32">
{posts.map((node) => (
<PostPreview
key={node.id}
title={node.title}
coverImage={{
sourceUrl: absoluteURL(node.field_image.uri.url),
}}
date={node.created}
author={{
name: node.uid.field_name,
avatar: {
url: absoluteURL(node.uid.user_picture.uri.url),
},
}}
slug={node.path.alias}
excerpt={node.body.summary}
/>
))}
</div>
</section>
)
}

View file

@ -0,0 +1,12 @@
import styles from './post-body.module.css'
export default function PostBody({ content }) {
return (
<div className="max-w-2xl mx-auto">
<div
className={styles.content}
dangerouslySetInnerHTML={{ __html: content }}
/>
</div>
)
}

View file

@ -0,0 +1,76 @@
.content {
@apply text-lg leading-relaxed;
}
.content p,
.content ul,
.content ol,
.content blockquote {
@apply my-6;
}
.content a {
@apply underline;
}
.content ul,
.content ol {
@apply pl-4;
}
.content ul {
@apply list-disc;
}
.content ol {
@apply list-decimal;
}
.content ul > li > ul,
.content ol > li > ol {
@apply my-0 ml-4;
}
.content ul > li > ul {
list-style: circle;
}
.content h2 {
@apply text-3xl mt-12 mb-4 leading-snug;
}
.content h3 {
@apply text-2xl mt-8 mb-4 leading-snug;
}
.content h4 {
@apply text-xl mt-6 mb-4 leading-snug;
}
.content pre {
@apply whitespace-pre overflow-x-auto p-4 text-sm leading-tight border border-gray-400 bg-gray-100;
}
.content code {
@apply text-sm;
}
.content figcaption {
@apply text-center text-sm;
}
.content blockquote {
@apply border-l-4 border-gray-500 bg-gray-200 italic ml-0 py-4 px-6;
}
.content blockquote p {
@apply mt-0;
}
.content blockquote cite {
@apply not-italic;
}
.content audio {
@apply w-full;
}

View file

@ -0,0 +1,34 @@
import Avatar from '../components/avatar'
import Date from '../components/date'
import CoverImage from '../components/cover-image'
import PostTitle from '../components/post-title'
import Categories from '../components/categories'
export default function PostHeader({
title,
coverImage,
date,
author,
categories,
}) {
return (
<>
<PostTitle>{title}</PostTitle>
<div className="hidden md:block md:mb-12">
<Avatar author={author} />
</div>
<div className="mb-8 md:mb-16 sm:mx-0">
<CoverImage title={title} coverImage={coverImage} />
</div>
<div className="max-w-2xl mx-auto">
<div className="block md:hidden mb-6">
<Avatar author={author} />
</div>
<div className="mb-6 text-lg">
Posted <Date dateString={date} />
{categories?.length ? <Categories categories={categories} /> : null}
</div>
</div>
</>
)
}

View file

@ -0,0 +1,37 @@
import Avatar from '../components/avatar'
import Date from '../components/date'
import CoverImage from './cover-image'
import Link from 'next/link'
export default function PostPreview({
title,
coverImage,
date,
excerpt,
author,
slug,
}) {
return (
<div>
<div className="mb-5">
<CoverImage title={title} coverImage={coverImage} slug={slug} />
</div>
<h3 className="text-3xl mb-3 leading-snug">
<Link href={slug}>
<a
className="hover:underline"
dangerouslySetInnerHTML={{ __html: title }}
></a>
</Link>
</h3>
<div className="text-lg mb-4">
<Date dateString={date} />
</div>
<div
className="text-lg leading-relaxed mb-4"
dangerouslySetInnerHTML={{ __html: excerpt }}
/>
<Avatar author={author} />
</div>
)
}

View file

@ -0,0 +1,8 @@
export default function PostTitle({ children }) {
return (
<h1
className="text-6xl md:text-7xl lg:text-8xl font-bold tracking-tighter leading-tight md:leading-none mb-12 text-center md:text-left"
dangerouslySetInnerHTML={{ __html: children }}
/>
)
}

View file

@ -0,0 +1,3 @@
export default function SectionSeparator() {
return <hr className="border-accent-2 mt-28 mb-24" />
}

View file

@ -0,0 +1,14 @@
export default function Tags({ tags }) {
return (
<div className="max-w-2xl mx-auto">
<p className="mt-8 text-lg font-bold">
Tagged
{tags.edges.map((tag, index) => (
<span key={index} className="ml-4 font-normal">
{tag.node.name}
</span>
))}
</p>
</div>
)
}

View file

@ -0,0 +1,3 @@
export function absoluteURL(url) {
return `${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}${url}`
}

View file

@ -0,0 +1,5 @@
export const EXAMPLE_PATH = 'cms-drupal'
export const CMS_NAME = 'Drupal'
export const CMS_URL = 'https://drupal.org/project/next'
export const HOME_OG_IMAGE_URL =
'https://og-image.vercel.app/Next.js%20Example%20Blog%20with%20**Drupal**.png?theme=light&md=1&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=https%3A%2F%2Fwww.drupal.org%2Ffiles%2FEL_blue_RGB%25281%2529.png'

View file

@ -0,0 +1,5 @@
module.exports = {
images: {
domains: [process.env.NEXT_IMAGE_DOMAIN],
},
}

View file

@ -0,0 +1,25 @@
{
"name": "cms-drupal",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"classnames": "2.3.1",
"date-fns": "2.21.3",
"next": "latest",
"next-drupal": "latest",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"autoprefixer": "10.2.5",
"postcss": "8.2.15",
"postcss-flexbugs-fixes": "5.0.2",
"postcss-preset-env": "6.7.0",
"tailwindcss": "2.1.2"
},
"license": "MIT"
}

View file

@ -0,0 +1,107 @@
import { useRouter } from 'next/router'
import ErrorPage from 'next/error'
import Head from 'next/head'
import {
getPathsFromContext,
getResourceCollectionFromContext,
getResourceFromContext,
} from 'next-drupal'
import Container from '../components/container'
import PostBody from '../components/post-body'
import MoreStories from '../components/more-stories'
import Header from '../components/header'
import PostHeader from '../components/post-header'
import SectionSeparator from '../components/section-separator'
import Layout from '../components/layout'
import PostTitle from '../components/post-title'
import { CMS_NAME } from '../lib/constants'
import { absoluteURL } from '../lib/api'
export default function Post({ post, morePosts, preview }) {
const router = useRouter()
if (!router.isFallback && !post?.id) {
return <ErrorPage statusCode={404} />
}
return (
<Layout preview={preview}>
<Container>
<Header />
{router.isFallback ? (
<PostTitle>Loading</PostTitle>
) : (
<>
<article>
<Head>
<title>
{post.title} | Next.js Blog Example with {CMS_NAME}
</title>
<meta
property="og:image"
content={absoluteURL(post.field_image.uri.url)}
/>
</Head>
<PostHeader
title={post.title}
coverImage={{
sourceUrl: absoluteURL(post.field_image.uri.url),
}}
date={post.created}
author={{
name: post.uid.field_name,
avatar: {
url: absoluteURL(post.uid.user_picture.uri.url),
},
}}
/>
<PostBody content={post.body.processed} />
</article>
<SectionSeparator />
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
</>
)}
</Container>
</Layout>
)
}
export async function getStaticProps(context) {
const post = await getResourceFromContext('node--article', context, {
params: {
include: 'field_image,uid,uid.user_picture',
},
})
let morePosts = []
if (post) {
morePosts = await getResourceCollectionFromContext(
'node--article',
context,
{
params: {
include: 'field_image,uid,uid.user_picture',
sort: '-created',
'filter[id][condition][path]': 'id',
'filter[id][condition][operator]': '<>',
'filter[id][condition][value]': post.id,
},
}
)
}
return {
props: {
preview: context.preview || false,
post,
morePosts,
},
}
}
export async function getStaticPaths(context) {
return {
paths: await getPathsFromContext('node--article', context),
fallback: true,
}
}

View file

@ -0,0 +1,7 @@
import '../styles/index.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp

View file

@ -0,0 +1,15 @@
import Document, { Html, Head, Main, NextScript } from 'next/document'
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}

View file

@ -0,0 +1,8 @@
export default async function exit(_, res) {
// Exit the current user from "Preview Mode". This function accepts no args.
res.clearPreviewData()
// Redirect the user back to the index page.
res.writeHead(307, { Location: '/' })
res.end()
}

View file

@ -0,0 +1,3 @@
import { DrupalPreview } from 'next-drupal'
export default DrupalPreview()

View file

@ -0,0 +1,60 @@
import Head from 'next/head'
import { getResourceCollectionFromContext } from 'next-drupal'
import Container from '../components/container'
import MoreStories from '../components/more-stories'
import HeroPost from '../components/hero-post'
import Intro from '../components/intro'
import Layout from '../components/layout'
import { CMS_NAME } from '../lib/constants'
import { absoluteURL } from '../lib/api'
export default function Index({ posts }) {
const heroPost = posts[0]
const morePosts = posts.slice(1)
return (
<Layout>
<Head>
<title>Next.js Blog Example with {CMS_NAME}</title>
</Head>
<Container>
<Intro />
{heroPost && (
<HeroPost
title={heroPost.title}
coverImage={{
sourceUrl: absoluteURL(heroPost.field_image.uri.url),
}}
date={heroPost.created}
author={{
name: heroPost.uid.field_name,
avatar: {
url: absoluteURL(heroPost.uid.user_picture.uri.url),
},
}}
slug={heroPost.path.alias}
excerpt={heroPost.body.summary}
/>
)}
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
</Container>
</Layout>
)
}
export async function getStaticProps(context) {
const posts = await getResourceCollectionFromContext(
'node--article',
context,
{
params: {
include: 'field_image,uid,uid.user_picture',
sort: '-created',
},
}
)
return {
props: { posts },
}
}

View file

@ -0,0 +1,18 @@
module.exports = {
plugins: [
'tailwindcss',
'postcss-flexbugs-fixes',
[
'postcss-preset-env',
{
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
features: {
'custom-properties': false,
},
},
],
],
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/favicons/mstile-150x150.png"/>
<TileColor>#000000</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -0,0 +1,33 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M4785 10234 c-22 -2 -92 -9 -155 -14 -1453 -131 -2814 -915 -3676
-2120 -480 -670 -787 -1430 -903 -2235 -41 -281 -46 -364 -46 -745 0 -381 5
-464 46 -745 278 -1921 1645 -3535 3499 -4133 332 -107 682 -180 1080 -224
155 -17 825 -17 980 0 687 76 1269 246 1843 539 88 45 105 57 93 67 -8 6 -383
509 -833 1117 l-818 1105 -1025 1517 c-564 834 -1028 1516 -1032 1516 -4 1 -8
-673 -10 -1496 -3 -1441 -4 -1499 -22 -1533 -26 -49 -46 -69 -88 -91 -32 -16
-60 -19 -211 -19 l-173 0 -46 29 c-30 19 -52 44 -67 73 l-21 45 2 2005 3 2006
31 39 c16 21 50 48 74 61 41 20 57 22 230 22 204 0 238 -8 291 -66 15 -16 570
-852 1234 -1859 664 -1007 1572 -2382 2018 -3057 l810 -1227 41 27 c363 236
747 572 1051 922 647 743 1064 1649 1204 2615 41 281 46 364 46 745 0 381 -5
464 -46 745 -278 1921 -1645 3535 -3499 4133 -327 106 -675 179 -1065 223 -96
10 -757 21 -840 13z m2094 -3094 c48 -24 87 -70 101 -118 8 -26 10 -582 8
-1835 l-3 -1798 -317 486 -318 486 0 1307 c0 845 4 1320 10 1343 16 56 51 100
99 126 41 21 56 23 213 23 148 0 174 -2 207 -20z"/>
<path d="M7843 789 c-35 -22 -46 -37 -15 -20 22 13 58 40 52 41 -3 0 -20 -10
-37 -21z"/>
<path d="M7774 744 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
<path d="M7724 714 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
<path d="M7674 684 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
<path d="M7598 644 c-38 -20 -36 -28 2 -9 17 9 30 18 30 20 0 7 -1 6 -32 -11z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,19 @@
{
"name": "Next.js",
"short_name": "Next.js",
"icons": [
{
"src": "/favicons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/favicons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#000000",
"background_color": "#000000",
"display": "standalone"
}

View file

@ -0,0 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.container {
max-width: 1280px;
}

View file

@ -0,0 +1,34 @@
module.exports = {
mode: 'jit',
purge: ['./components/**/*.js', './pages/**/*.js'],
theme: {
extend: {
colors: {
'accent-1': '#FAFAFA',
'accent-2': '#EAEAEA',
'accent-7': '#333',
success: '#0070f3',
cyan: '#79FFE1',
},
spacing: {
28: '7rem',
},
letterSpacing: {
tighter: '-.04em',
},
lineHeight: {
tight: 1.2,
},
fontSize: {
'5xl': '2.5rem',
'6xl': '2.75rem',
'7xl': '4.5rem',
'8xl': '6.25rem',
},
boxShadow: {
small: '0 5px 10px rgba(0, 0, 0, 0.12)',
medium: '0 8px 30px rgba(0, 0, 0, 0.12)',
},
},
},
}