├── .eslintrc.json
├── .gitignore
├── Licence
├── README.md
├── components
├── ReusableStyles.jsx
├── footer.jsx
└── landing.jsx
├── next-seo.config.js
├── next.config.js
├── package.json
├── pages
├── _app.jsx
├── _document.js
├── api
│ └── hello.ts
└── index.jsx
├── public
├── button-dots.svg
├── dots.svg
├── favicon.ico
├── figma-bg.png
├── figma-dark.svg
├── figma.svg
├── figma_icon.svg
├── overlayz.svg
├── plugin.png
├── preview.jpg
├── realvjy.svg
├── stippling-dark.svg
├── stippling.svg
├── twitter.svg
└── vercel.svg
├── styles
├── Home.module.scss
└── globals.scss
├── tsconfig.json
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/Licence:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2022-present realvjy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a
6 | copy of this software and associated documentation files (the "Software"),
7 | to deal in the Software without restriction, including without limitation
8 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 | and/or sell copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 | DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | I coded this retro-looking, cool header for the landing page of my new stippling Figma plugin. It uses mouse control to create a parallax effect with a stack of images. You can use it on your project freely.
2 |
3 | ---
4 | ### Here [Stippling plugin](https://www.figma.com/community/plugin/1409794712197371392/stippling)
5 | ### original website [stippling.app](https://stippling.app)
6 |
--------------------------------------------------------------------------------
/components/ReusableStyles.jsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import styled from "styled-components";
3 |
4 | export const Container = styled.div`
5 | position: relative;
6 | align-items: center;
7 | justify-content: center;
8 | width: 900px;
9 | padding: 0px;
10 | margin: 0 auto;
11 | @media screen and (max-width: 600px) {
12 | width: 100%;
13 | overflow-x: hidden;
14 | }
15 | `;
16 |
--------------------------------------------------------------------------------
/components/footer.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @next/next/no-img-element */
2 |
3 | import styled from "styled-components";
4 | import Link from "next/link";
5 |
6 | export default function Footer() {
7 | return (
8 |
9 |
10 |
11 | made by
12 |
13 |
14 |
15 |
16 |
17 | at
18 |
19 |
20 | @overlayz
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 |
34 | const Wrapper = styled.div`
35 | display: flex;
36 | justify-content: center;
37 | margin-top: 30px;
38 | padding: 40px 0;
39 | font-size: 16px;
40 | letter-spacing: -0.2px;
41 | font-weight: 500;
42 | width: 100%;
43 | color: rgba(255, 255, 255, 0.8);
44 | gap: 4px;
45 | align-items: center;
46 | span {
47 | padding-inline: 5px;
48 | font-weight: 500;
49 | .img-r {
50 | transform: scale(1.2);
51 | }
52 | a {
53 | text-decoration: none;
54 | color: rgba(255, 255, 255, 1);
55 | opacity: 0.9;
56 | &:hover {
57 | opacity: 1;
58 | }
59 | }
60 | &.img {
61 | padding: 0;
62 | }
63 | }
64 | `;
65 |
--------------------------------------------------------------------------------
/components/landing.jsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | import styled from "styled-components";
4 | import React, { useState, useEffect, useRef } from "react";
5 | import { Container } from "./ReusableStyles";
6 | import Footer from "./footer";
7 |
8 | export default function Landing() {
9 | const [position, setPosition] = useState({ x: 1, y: 1 });
10 | const [isLoaded, setIsLoaded] = useState(false);
11 | const containerRef = useRef(null);
12 | const intensity = 4;
13 | useEffect(() => {
14 | const handleMouseMove = (e) => {
15 | if (!containerRef.current) return;
16 |
17 | const { left, top, width, height } =
18 | containerRef.current.getBoundingClientRect();
19 | const x = (e.clientX - left) / width - 0.5;
20 | const y = (e.clientY - top) / height - 0.5;
21 |
22 | setPosition({ x, y });
23 | };
24 |
25 | window.addEventListener("mousemove", handleMouseMove);
26 |
27 | const timer = setTimeout(() => setIsLoaded(true), 100);
28 |
29 | return () => {
30 | window.removeEventListener("mousemove", handleMouseMove);
31 | clearTimeout(timer);
32 | };
33 | }, []);
34 |
35 | const getImageStyle = (multiplier) => ({
36 | transform: `translate(${position.x * multiplier * intensity}px, ${
37 | position.y * multiplier * intensity
38 | }px)`,
39 | transition: isLoaded ? "transform 0.1s ease-out" : "none",
40 | opacity: isLoaded ? 1 : 0,
41 | });
42 |
43 | const mainImageStyle = getImageStyle(15);
44 | const shadowImageStyle = getImageStyle(25);
45 | const dotsImageStyle = getImageStyle(5);
46 | return (
47 |
48 |
49 |
50 |
51 |
57 |
63 |
69 |
75 |
80 |
81 |
82 |
83 |
84 | Install Now
85 |
86 |
87 |
88 |
89 |
90 |
91 | );
92 | }
93 |
94 | const HeadSection = styled.div`
95 | position: relative;
96 | width: 100%;
97 | display: flex;
98 | align-items: center;
99 | flex-direction: column;
100 | `;
101 |
102 | const HeadWrapper = styled.div`
103 | display: flex;
104 | flex-direction: column;
105 | justify-content: center;
106 | align-items: center;
107 | min-height: 100vh;
108 | @media screen and (max-width: 600px) {
109 | padding-top: 80px;
110 | padding-bottom: 40px;
111 | }
112 | `;
113 |
114 | const Logo = styled.div`
115 | position: relative;
116 | width: 100%;
117 | height: 400px;
118 | display: flex;
119 | flex-direction: column;
120 | align-items: center;
121 | margin-top: 40px;
122 | @media screen and (max-width: 600px) {
123 | height: 150px;
124 | }
125 | img {
126 | @media screen and (max-width: 600px) {
127 | /* margin: 20px; */
128 | }
129 | &.main {
130 | z-index: 2;
131 | width: 1000px;
132 | @media screen and (max-width: 600px) {
133 | width: 90%;
134 | }
135 | }
136 | &.shadow {
137 | z-index: 1;
138 | width: 1000px;
139 | @media screen and (max-width: 600px) {
140 | width: 90%;
141 | }
142 | }
143 | &.dots {
144 | z-index: 0;
145 | width: 1200px;
146 | left: -120px;
147 | top: -80px;
148 | mix-blend-mode: overlay;
149 | @media screen and (max-width: 600px) {
150 | width: 110%;
151 | left: -20px;
152 | top: -40px;
153 | }
154 | }
155 | &.figma {
156 | width: 350px;
157 | z-index: 4;
158 | bottom: 70px;
159 | left: calc(50% - 110px);
160 | @media screen and (max-width: 600px) {
161 | width: 35%;
162 | bottom: 20px;
163 | left: calc(50% - 50px);
164 | }
165 | }
166 | &.figma-shadow {
167 | width: 350px;
168 | z-index: 3;
169 | bottom: 70px;
170 | left: calc(50% - 120px);
171 | @media screen and (max-width: 600px) {
172 | width: 35%;
173 | bottom: 20px;
174 | left: calc(50% - 50px);
175 | }
176 | }
177 | }
178 | `;
179 |
180 | const Content = styled.div`
181 | color: var(--primary-text);
182 | display: flex;
183 | flex-direction: column;
184 | align-items: center;
185 | padding: 24px;
186 | h1 {
187 | text-align: center;
188 | font-weight: 300;
189 | max-width: 500px;
190 | font-size: 30px;
191 | @media screen and (max-width: 600px) {
192 | font-size: 20px;
193 | }
194 | }
195 | `;
196 |
197 | const DotButton = styled.a`
198 | padding: 20px 62px;
199 | /* background-image: url("button-dots.svg"),
200 | linear-gradient(180deg, #fffb00 0%, #ff2f93 100%); */
201 | background-color: #000;
202 | color: #fff;
203 | font-weight: 500;
204 | background-position: 0px -2px;
205 | background-repeat: no-repeat;
206 | overflow: hidden;
207 | font-size: 22px;
208 | line-height: 32px;
209 | border-radius: 42px;
210 | letter-spacing: -0.5px;
211 | text-decoration: none;
212 | cursor: pointer;
213 | display: flex;
214 | gap: 8px;
215 | img {
216 | height: 32px;
217 | }
218 | text-shadow: rgba(0, 0, 0, 1) 2px 2px 0px, rgba(0, 0, 0, 1) -2px -2px 0px;
219 | box-shadow: inset rgba(224, 2, 190, 0.1) 0px 0px 2px 1px;
220 | transition: all ease 300ms;
221 | &:hover {
222 | box-shadow: inset rgba(224, 2, 190, 0.5) 0px 0px 12px 2px;
223 | }
224 | @media screen and (max-width: 600px) {
225 | font-size: 18px;
226 | line-height: 24px;
227 | border-radius: 42px;
228 | }
229 | `;
230 |
--------------------------------------------------------------------------------
/next-seo.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | title: "Stippling - Figma plugin",
3 | description:
4 | "Generate dots like stippling or halftone effect in Figma",
5 | openGraph: {
6 | type: "website",
7 | locale: "en_IE",
8 | title: "Stippling - Figma plugin",
9 | url: "https://stippling.app",
10 | description:
11 | "Generate dots like stippling or halftone effect in Figma",
12 | locale: "en_EN",
13 | keywords:
14 | "figma plugin, stippling, halftone, figma halftone, image effect, image tracer ",
15 | images: [
16 | {
17 | width: 1200,
18 | height: 630,
19 | url: `https://stippling.app/preview.jpg`,
20 | },
21 | ],
22 | site_name: "stippling.app",
23 | },
24 | twitter: {
25 | handle: "@realvjy",
26 | site: "stippling.app",
27 | cardType: "summary_large_image",
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | compiler: {
6 | styledComponents: true,
7 | },
8 | }
9 |
10 | module.exports = nextConfig
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stippling-web",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "next": "13.1.6",
13 | "next-seo": "^6.5.0",
14 | "nextjs-google-analytics": "^2.3.3",
15 | "react": "18.2.0",
16 | "react-dom": "18.2.0",
17 | "sass": "^1.58.3",
18 | "scss-reset": "^1.2.2",
19 | "styled-components": "^6.1.12"
20 | },
21 | "devDependencies": {
22 | "@types/node": "18.14.0",
23 | "@types/react": "18.0.28",
24 | "@types/react-dom": "18.0.11",
25 | "eslint": "8.34.0",
26 | "eslint-config-next": "13.1.6",
27 | "typescript": "4.9.5"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/pages/_app.jsx:
--------------------------------------------------------------------------------
1 | import "../styles/globals.scss";
2 | import SEO from "../next-seo.config";
3 | import { DefaultSeo } from "next-seo";
4 | import { GoogleAnalytics } from "nextjs-google-analytics";
5 | function MyApp({ Component, pageProps }) {
6 | return (
7 | <>
8 |
9 |
35 |
36 | >
37 | );
38 | }
39 |
40 | export default MyApp;
41 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from "next/document";
2 | import { ServerStyleSheet } from "styled-components";
3 |
4 | export default class MyDocument extends Document {
5 | static async getInitialProps(ctx) {
6 | const sheet = new ServerStyleSheet();
7 | const originalRenderPage = ctx.renderPage;
8 |
9 | try {
10 | ctx.renderPage = () =>
11 | originalRenderPage({
12 | enhanceApp: (App) => (props) =>
13 | sheet.collectStyles( ),
14 | });
15 |
16 | const initialProps = await Document.getInitialProps(ctx);
17 | return {
18 | ...initialProps,
19 | styles: (
20 | <>
21 | {initialProps.styles}
22 | {sheet.getStyleElement()}
23 | >
24 | ),
25 | };
26 | } finally {
27 | sheet.seal();
28 | }
29 | }
30 |
31 | render() {
32 | return (
33 |
34 |
35 |
36 |
41 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | );
53 | }
54 | }
--------------------------------------------------------------------------------
/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | import type { NextApiRequest, NextApiResponse } from 'next'
3 |
4 | type Data = {
5 | name: string
6 | }
7 |
8 | export default function handler(
9 | req: NextApiRequest,
10 | res: NextApiResponse
11 | ) {
12 | res.status(200).json({ name: 'John Doe' })
13 | }
14 |
--------------------------------------------------------------------------------
/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import Footer from "../components/footer";
2 | import Landing from "../components/landing";
3 | import styled from "styled-components";
4 | const Home = () => {
5 | return (
6 |
7 |
8 |
9 | );
10 | };
11 |
12 | export default Home;
13 |
14 | const Main = styled.div`
15 | position: relative;
16 | width: 100%;
17 | margin: 0 auto;
18 | display: flex;
19 | flex-direction: column;
20 | align-items: center;
21 | `;
22 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realvjy/stippling-header/f5304945e19ba98d67fdf5bdba7dfe7343cebd52/public/favicon.ico
--------------------------------------------------------------------------------
/public/figma-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realvjy/stippling-header/f5304945e19ba98d67fdf5bdba7dfe7343cebd52/public/figma-bg.png
--------------------------------------------------------------------------------
/public/figma-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/figma.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/figma_icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/public/overlayz.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/public/plugin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realvjy/stippling-header/f5304945e19ba98d67fdf5bdba7dfe7343cebd52/public/plugin.png
--------------------------------------------------------------------------------
/public/preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realvjy/stippling-header/f5304945e19ba98d67fdf5bdba7dfe7343cebd52/public/preview.jpg
--------------------------------------------------------------------------------
/public/realvjy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/public/stippling-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/public/twitter.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/styles/Home.module.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realvjy/stippling-header/f5304945e19ba98d67fdf5bdba7dfe7343cebd52/styles/Home.module.scss
--------------------------------------------------------------------------------
/styles/globals.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;0,800;1,700;1,800&display=swap');
2 |
3 | :root{
4 | --main_theme: #ffffff;
5 | --bg: #2B00C2;
6 | --primary-text: #fff;
7 | --selection-bg: #f81ce5;
8 | // --main-bg: linear-gradient(180deg, rgba(0, 0, 0, 0.0) 0%, rgba(100, 100, 100, 0.2) 100%);
9 | }
10 |
11 | body {
12 | background: var(--bg);
13 | font-family: 'Inter', sans-serif;
14 | color: var(--primary-text);
15 | padding: 0;
16 | margin: 0;
17 | display: block;
18 | }
19 |
20 |
21 |
22 | .absolute{
23 | position: absolute;
24 | }
25 |
26 | ::selection {
27 | background: var(--selection-bg);
28 | color: var(--text-primary);
29 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "components/footer.jsx", "pages/_app.jsx", "pages/index.jsx", "components/header.jsx", "components/landing.jsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------