Compare commits

..

No commits in common. "develop" and "v1.14.2" have entirely different histories.

31 changed files with 3290 additions and 3796 deletions

View File

@ -8,8 +8,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Lint commit messages - name: Lint commit messages
uses: wagoid/commitlint-github-action@v6 uses: wagoid/commitlint-github-action@v2

View File

@ -8,9 +8,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v2
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' cache: 'yarn'

View File

@ -11,22 +11,22 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v2
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' cache: 'yarn'
- name: Install dependencies - name: Install dependencies
run: yarn install --frozen-lockfile run: yarn install --frozen-lockfile
- name: Cache Next.js build cache - name: Cache Next.js build cache
uses: actions/cache@v4 uses: actions/cache@v2
with: with:
path: ${{ github.workspace }}/.next/cache path: ${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}
- name: Build static assets - name: Build static assets
run: yarn build run: yarn build
- name: Semantic Release - name: Semantic Release
uses: cycjimmy/semantic-release-action@v4 uses: cycjimmy/semantic-release-action@v2
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -7,10 +7,10 @@ jobs:
name: Run Cypress name: Run Cypress
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v2
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' cache: 'yarn'
@ -19,7 +19,7 @@ jobs:
run: yarn install --frozen-lockfile run: yarn install --frozen-lockfile
- name: Cache Next.js build cache - name: Cache Next.js build cache
uses: actions/cache@v4 uses: actions/cache@v2
with: with:
path: ${{ github.workspace }}/.next/cache path: ${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-nextjs-${{ hashFiles('**/yarn.lock') }}
@ -28,7 +28,7 @@ jobs:
run: yarn build run: yarn build
- name: Run Cypress - name: Run Cypress
uses: cypress-io/github-action@v6 uses: cypress-io/github-action@v2
with: with:
start: npm start start: npm start
wait-on: http://localhost:3000 wait-on: http://localhost:3000
@ -36,13 +36,13 @@ jobs:
# store videos and any screenshots # store videos and any screenshots
# NOTE: screenshots will be generated only if E2E test failed # NOTE: screenshots will be generated only if E2E test failed
# thus we store screenshots only on failures # thus we store screenshots only on failures
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v2
if: failure() if: failure()
with: with:
name: cypress-screenshots name: cypress-screenshots
path: cypress/screenshots path: cypress/screenshots
# Test run video was always captured, so this action uses "always()" condition # Test run video was always captured, so this action uses "always()" condition
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v2
if: always() if: always()
with: with:
name: cypress-videos name: cypress-videos

2
.nvmrc
View File

@ -1 +1 @@
22.11 14.18

View File

@ -1,7 +1,8 @@
{ {
"useTabs": true, "useTabs": true,
"printWidth": 120, "printWidth": 120,
"singleQuote": true, "singleQuote": true,
"trailingComma": "es5", "trailingComma": "es5",
"jsxBracketSameLine": true,
"semi": false "semi": false
} }

View File

@ -3,11 +3,11 @@
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"eslint.run": "onType", "eslint.run": "onType",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit", "source.fixAll.eslint": true,
"source.fixAll.stylelint": "explicit" "source.fixAll.stylelint": true
}, },
"css.validate": false, "css.validate": false,
"less.validate": false, "less.validate": false,
"scss.validate": false, "scss.validate": false,
"typescript.tsdk": "node_modules/typescript/lib" "cssModules.camelCase": true
} }

View File

@ -2,7 +2,7 @@
.base { .base {
padding: 5px 12px; padding: 5px 12px;
font-family: 'Manrope', Arial, '"Helvetica Neue"', Helvetica, sans-serif; font-family: Manrope, Arial, '"Helvetica Neue"', Helvetica, sans-serif;
font-size: $font-size-title; font-size: $font-size-title;
line-height: $line-height-title; line-height: $line-height-title;
color: $black; color: $black;

View File

@ -208,7 +208,6 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
} }
const { getRootProps, getInputProps, isDragActive } = useDropzone({ const { getRootProps, getInputProps, isDragActive } = useDropzone({
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onDrop, onDrop,
accept: Object.keys(ACCEPT_MIME_TYPES), accept: Object.keys(ACCEPT_MIME_TYPES),
maxSize: ONE_MB, maxSize: ONE_MB,
@ -231,15 +230,13 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
className={classnames(styles.container, { className={classnames(styles.container, {
[styles.loading]: isLoading, [styles.loading]: isLoading,
[styles.transparent]: !image, [styles.transparent]: !image,
})} })}>
>
{!image && !zipData && ( {!image && !zipData && (
<> <>
<div <div
className={styles.dropZoneWrapper} className={styles.dropZoneWrapper}
onMouseEnter={() => setIsHover(true)} onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)} onMouseLeave={() => setIsHover(false)}>
>
<SvgDropZone className={classnames(styles.dashed, { [styles.dragActive]: isDragActive })} /> <SvgDropZone className={classnames(styles.dashed, { [styles.dragActive]: isDragActive })} />
<div {...getRootProps()} className={styles.dropZone} data-cy="drag-and-drop"> <div {...getRootProps()} className={styles.dropZone} data-cy="drag-and-drop">
<input {...getInputProps()} /> <input {...getInputProps()} />
@ -252,8 +249,7 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
className={classnames(styles.imageUploadText, styles.mobileUploadText, { className={classnames(styles.imageUploadText, styles.mobileUploadText, {
[styles.dragActive]: isDragActive, [styles.dragActive]: isDragActive,
})} })}
data-cy="drag-and-drop-text" data-cy="drag-and-drop-text">
>
<span> <span>
Generate all the sizes for your Generate all the sizes for your
<br /> <br />
@ -266,8 +262,7 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
className={classnames(styles.imageUploadText, styles.desktopUploadText, { className={classnames(styles.imageUploadText, styles.desktopUploadText, {
[styles.dragActive]: isDragActive, [styles.dragActive]: isDragActive,
})} })}
data-cy="drag-and-drop-text" data-cy="drag-and-drop-text">
>
<CSSTransition in={!isDragActive} timeout={200} classNames="collapse" unmountOnExit> <CSSTransition in={!isDragActive} timeout={200} classNames="collapse" unmountOnExit>
<span>Drag &amp;&nbsp;</span> <span>Drag &amp;&nbsp;</span>
</CSSTransition> </CSSTransition>
@ -318,8 +313,7 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
weight="semiBold" weight="semiBold"
color="gray" color="gray"
tag="div" tag="div"
className={styles.filenameWrapper} className={styles.filenameWrapper}>
>
<span className={styles.filename} data-cy="preview-filename"> <span className={styles.filename} data-cy="preview-filename">
{imageData.name} {imageData.name}
{imageData.extension} {imageData.extension}
@ -363,8 +357,7 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
id="pwa" id="pwa"
disabled={PWADisabled} disabled={PWADisabled}
onChange={togglePwa} onChange={togglePwa}
data-cy="preview-pwa-compatible" data-cy="preview-pwa-compatible">
>
<Typography variant="regularBody" weight="semiBold" muted={PWADisabled}> <Typography variant="regularBody" weight="semiBold" muted={PWADisabled}>
PWA compatible PWA compatible
</Typography> </Typography>
@ -380,8 +373,7 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
id="dark" id="dark"
disabled disabled
onChange={toggleDarkMode} onChange={toggleDarkMode}
data-cy="preview-dark-mode-version" data-cy="preview-dark-mode-version">
>
<Typography variant="regularBody" weight="semiBold" muted> <Typography variant="regularBody" weight="semiBold" muted>
Dark Mode version Dark Mode version
</Typography> </Typography>
@ -400,18 +392,14 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
onClick={() => { onClick={() => {
splitbee.track('Re-upload') splitbee.track('Re-upload')
resetImage() resetImage()
}} }}>
>
Re-upload Re-upload
</Button> </Button>
<Button <Button
color="white" color="white"
background="bgLink" background="bgLink"
className={styles.imageGenerate} className={styles.imageGenerate}
onClick={() => { onClick={() => generateFavicon(image)}>
generateFavicon(image)
}}
>
Generate Favicon Generate Favicon
</Button> </Button>
</div> </div>
@ -454,8 +442,7 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
className={styles.showCode} className={styles.showCode}
variant="regularTransparent" variant="regularTransparent"
color="gray" color="gray"
onClick={() => setIsModalOpen(true)} onClick={() => setIsModalOpen(true)}>
>
Show Show
</Button> </Button>
</div> </div>
@ -468,16 +455,14 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
onClick={() => { onClick={() => {
splitbee.track('Make a new one') splitbee.track('Make a new one')
resetImage() resetImage()
}} }}>
>
Make a new one Make a new one
</Button> </Button>
<Button <Button
color="white" color="white"
background="bgGreen" background="bgGreen"
className={styles.imageGenerate} className={styles.imageGenerate}
onClick={() => onDownload(zipData)} onClick={() => onDownload(zipData)}>
>
Download Favicon Download Favicon
</Button> </Button>
</div> </div>
@ -490,8 +475,7 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
className={styles.content} className={styles.content}
overlayClassName={styles.overlay} overlayClassName={styles.overlay}
closeTimeoutMS={200} closeTimeoutMS={200}
contentLabel="Code generated" contentLabel="Code generated">
>
<div className={styles.modalContainer} {...bind()}> <div className={styles.modalContainer} {...bind()}>
<div className={classnames(styles.modalHeader, styles.modalHeaderMobile)}> <div className={classnames(styles.modalHeader, styles.modalHeaderMobile)}>
<Typography variant="extraLargeTitle" weight="extraBold" color="white" colorImmutable> <Typography variant="extraLargeTitle" weight="extraBold" color="white" colorImmutable>

View File

@ -38,8 +38,7 @@ const FavyconError = ({ error, clearError }: FavyconErrorProps) => {
variant="regularBody" variant="regularBody"
color="white" color="white"
colorImmutable={true} colorImmutable={true}
weight="semiBold" weight="semiBold">
>
{error} {error}
</Typography> </Typography>
<svg height={20} width={20}> <svg height={20} width={20}>

View File

@ -290,11 +290,7 @@
margin-right: 2px; margin-right: 2px;
} }
// Both modifiers do the same job: drop the base class's Twitter blue for the &.personGithub {
// neutral ink. Kept as two names rather than one shared class so the markup
// still says which kind of link it is.
&.personGithub,
&.personWebsite {
color: $black; color: $black;
p { p {
@ -320,8 +316,7 @@
} }
} }
:global(.dark) .personTwitter.personGithub p, :global(.dark) .personTwitter.personGithub p {
:global(.dark) .personTwitter.personWebsite p {
color: $white; color: $white;
&:hover { &:hover {
@ -333,13 +328,6 @@
fill: $white; fill: $white;
} }
// The GitHub mark is a filled path; the globe is stroked, so it needs the other
// property. Both are set explicitly rather than relying on currentColor, to
// match how the rule above already works.
:global(.dark) .personTwitter.personWebsite p svg {
stroke: $white;
}
.modalFooter { .modalFooter {
padding-bottom: 48px; padding-bottom: 48px;
margin-top: 12px; margin-top: 12px;

View File

@ -7,7 +7,6 @@ import { Typography } from 'components/typography'
import { ToolTitle } from 'components/tool-title' import { ToolTitle } from 'components/tool-title'
import { SvgTwitter } from 'components/svgs/svg-twitter' import { SvgTwitter } from 'components/svgs/svg-twitter'
import { SvgGithub } from 'components/svgs/svg-github' import { SvgGithub } from 'components/svgs/svg-github'
import { SvgWebsite } from 'components/svgs/svg-website'
import styles from './index.module.scss' import styles from './index.module.scss'
import { SvgFavycon } from 'components/svgs/svg-favycon' import { SvgFavycon } from 'components/svgs/svg-favycon'
@ -20,47 +19,26 @@ import miguelTeixeira from '../../public/images/people/miguel-teixeira.png'
const Modal = dynamic(() => import('react-modal')) const Modal = dynamic(() => import('react-modal'))
// A lookup rather than a chain of ternaries in the JSX, because there are three
// kinds of link now. `personTwitter` is the shared base class — it predates the
// other two and carries the layout, so the modifiers only restate the colour.
const socials = {
twitter: {
Icon: SvgTwitter,
url: (handle: string) => `https://twitter.com/${handle}`,
modifier: undefined,
},
github: {
Icon: SvgGithub,
url: (handle: string) => `https://github.com/${handle}`,
modifier: styles.personGithub,
},
website: {
Icon: SvgWebsite,
url: (handle: string) => `https://${handle}`,
modifier: styles.personWebsite,
},
}
const people = [ const people = [
{ {
handle: 'aboutaugusto', screenName: 'aboutaugusto',
name: 'Augusto Lopes', name: 'Augusto Lopes',
role: 'Product Designer', role: 'Product Designer',
social: 'twitter' as const, social: 'twitter',
photo: augustoLopes, photo: augustoLopes,
}, },
{ {
handle: 'ruisaraiva.dev', screenName: 'ruisaraiva19',
name: 'Rui Saraiva', name: 'Rui Saraiva',
role: 'Principal Engineer', role: 'Full-Stack Developer',
social: 'website' as const, social: 'twitter',
photo: ruiSaraiva, photo: ruiSaraiva,
}, },
{ {
handle: 'miguellteixeira', screenName: 'miguellteixeira',
name: 'Miguel Teixeira', name: 'Miguel Teixeira',
role: 'Full-Stack Developer', role: 'Full-Stack Developer',
social: 'github' as const, social: 'github',
photo: miguelTeixeira, photo: miguelTeixeira,
}, },
] ]
@ -124,8 +102,7 @@ const FavyconInfo = ({
className={styles.content} className={styles.content}
overlayClassName={styles.overlay} overlayClassName={styles.overlay}
closeTimeoutMS={200} closeTimeoutMS={200}
contentLabel="About Us" contentLabel="About Us">
>
<div className={styles.modalContainer} {...bind()}> <div className={styles.modalContainer} {...bind()}>
<div className={styles.modalHeaderMobile}> <div className={styles.modalHeaderMobile}>
<div className={styles.modalHeaderTop}> <div className={styles.modalHeaderTop}>
@ -160,47 +137,54 @@ const FavyconInfo = ({
Behind the curtains Behind the curtains
</Typography> </Typography>
<Typography variant="largeBody" weight="medium" className={styles.subTitle}> <Typography variant="largeBody" weight="medium" className={styles.subTitle}>
For new updates be sure to follow these guys. For new updates be sure to follow these guys on Twitter.
</Typography> </Typography>
</div> </div>
<hr className={styles.hr} /> <hr className={styles.hr} />
<div className={styles.people}> <div className={styles.people}>
{people.map((person) => { {people.map((person) => (
const { Icon, url, modifier } = socials[person.social] <div key={person.screenName} className={styles.person}>
<div className={styles.personAvatar}>
return ( <Image
<div key={person.handle} className={styles.person}> src={person.photo}
<div className={styles.personAvatar}> alt={person.name}
<Image width={56}
src={person.photo} height={56}
alt={person.name} placeholder="blur"
width={56} layout="intrinsic"
height={56} />
placeholder="blur" </div>
layout="intrinsic" <div>
/> <Typography variant="largeBody" weight="semiBold">
</div> {person.name}
<div> </Typography>
<Typography variant="largeBody" weight="semiBold"> <Typography variant="regularBody" weight="medium" className={styles.personRole}>
{person.name} {person.role}
</Typography> </Typography>
<Typography variant="regularBody" weight="medium" className={styles.personRole}> {person.social === 'twitter' ? (
{person.role}
</Typography>
<a <a
href={url(person.handle)} href={`https://twitter.com/${person.screenName}`}
className={classnames(styles.personTwitter, modifier)} className={styles.personTwitter}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer">
>
<Typography variant="footer" weight="semiBold"> <Typography variant="footer" weight="semiBold">
<Icon /> {person.handle} <SvgTwitter /> {person.screenName}
</Typography> </Typography>
</a> </a>
</div> ) : (
<a
href={`https://github.com/${person.screenName}`}
className={classnames(styles.personTwitter, styles.personGithub)}
target="_blank"
rel="noopener noreferrer">
<Typography variant="footer" weight="semiBold">
<SvgGithub /> {person.screenName}
</Typography>
</a>
)}
</div> </div>
) </div>
})} ))}
</div> </div>
<hr className={styles.hr} /> <hr className={styles.hr} />
<div className={classnames(styles.footer, styles.modalFooter)}> <div className={classnames(styles.footer, styles.modalFooter)}>
@ -210,13 +194,12 @@ const FavyconInfo = ({
className={styles.repo} className={styles.repo}
href="https://github.com/ruisaraiva19/favycon" href="https://github.com/ruisaraiva19/favycon"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer">
>
GitHub GitHub
</a> </a>
. Have feedback? Send it to{' '} . Have feedback? Send it to{' '}
<a className={styles.contact} href="mailto:hello@ruisaraiva.dev"> <a className={styles.contact} href="mailto:hello@favycon.app">
hello@ruisaraiva.dev hello@favycon.app
</a> </a>
</Typography> </Typography>
</div> </div>

View File

@ -1,80 +1,43 @@
import Head from 'next/head' import Head from 'next/head'
const SITE_URL = 'https://favycon.vercel.app'
const AUTHOR = { name: 'Rui Saraiva', url: 'https://ruisaraiva.dev' }
// The tool credits three people in its About modal, so the structured data says
// the same thing rather than a tidier untruth: Rui as author, the other two as
// contributors, linked where the modal links them.
const CONTRIBUTORS = [
{ name: 'Augusto Lopes', url: 'https://twitter.com/aboutaugusto' },
{ name: 'Miguel Teixeira', url: 'https://github.com/miguellteixeira' },
]
type SEOProps = { type SEOProps = {
title: string title: string
description: string description: string
} }
const SEO = ({ title, description }: SEOProps) => { const SEO = ({ title, description }: SEOProps) => (
const structuredData = { <Head>
'@context': 'https://schema.org', <title>{title}</title>
'@type': 'SoftwareApplication', <meta name="description" content={description} />
name: 'Favycon', <link rel="apple-touch-icon" sizes="57x57" href="/favicon-57x57.png" />
description, <link rel="apple-touch-icon" sizes="60x60" href="/favicon-60x60.png" />
url: SITE_URL, <link rel="apple-touch-icon" sizes="72x72" href="/favicon-72x72.png" />
applicationCategory: 'DeveloperApplication', <link rel="apple-touch-icon" sizes="76x76" href="/favicon-76x76.png" />
operatingSystem: 'Any', <link rel="apple-touch-icon" sizes="114x114" href="/favicon-114x114.png" />
browserRequirements: 'Requires JavaScript', <link rel="apple-touch-icon" sizes="120x120" href="/favicon-120x120.png" />
image: `${SITE_URL}/share.png`, <link rel="apple-touch-icon" sizes="144x144" href="/favicon-144x144.png" />
license: 'https://opensource.org/licenses/MIT', <link rel="apple-touch-icon" sizes="152x152" href="/favicon-152x152.png" />
// It is genuinely free, and Google wants the price stated to believe it. <link rel="apple-touch-icon" sizes="180x180" href="/favicon-180x180.png" />
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' }, <meta name="apple-mobile-web-app-title" content="Favycon" />
author: { '@type': 'Person', ...AUTHOR }, <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
contributor: CONTRIBUTORS.map((person) => ({ '@type': 'Person', ...person })), <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
codeRepository: 'https://github.com/ruisaraiva19/favycon', <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
} <link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png" />
<link rel="icon" type="image/png" sizes="192x192" href="/favicon-192x192.png" />
return ( <meta name="msapplication-TileColor" content="#ffffff" />
<Head> <meta name="msapplication-TileImage" content="/favicon-144x144.png" />
<title>{title}</title> <meta name="msapplication-config" content="/browserconfig.xml" />
<meta name="description" content={description} /> <meta property="og:title" content={title} />
<meta name="author" content={AUTHOR.name} /> <meta property="og:description" content={description} />
<link rel="author" href={AUTHOR.url} /> <meta property="og:type" content="website" />
{/* favycon.app has no DNS, so vercel.app is the only hostname this answers on. */} <meta property="og:site_name" content={title} />
<link rel="canonical" href={SITE_URL} /> <meta property="og:url" content="https://favycon.vercel.app" />
<link rel="apple-touch-icon" sizes="57x57" href="/favicon-57x57.png" /> <meta property="og:image" content="https://favycon.vercel.app/share.png?v5" />
<link rel="apple-touch-icon" sizes="60x60" href="/favicon-60x60.png" /> <meta property="og:image:type" content="image/png" />
<link rel="apple-touch-icon" sizes="72x72" href="/favicon-72x72.png" /> <meta property="og:image:width" content="1200" />
<link rel="apple-touch-icon" sizes="76x76" href="/favicon-76x76.png" /> <meta property="og:image:height" content="630" />
<link rel="apple-touch-icon" sizes="114x114" href="/favicon-114x114.png" /> <meta name="twitter:card" content="summary_large_image" />
<link rel="apple-touch-icon" sizes="120x120" href="/favicon-120x120.png" /> </Head>
<link rel="apple-touch-icon" sizes="144x144" href="/favicon-144x144.png" /> )
<link rel="apple-touch-icon" sizes="152x152" href="/favicon-152x152.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/favicon-180x180.png" />
<meta name="apple-mobile-web-app-title" content="Favycon" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png" />
<link rel="icon" type="image/png" sizes="192x192" href="/favicon-192x192.png" />
<meta name="msapplication-TileColor" content="#ffffff" />
<meta name="msapplication-TileImage" content="/favicon-144x144.png" />
<meta name="msapplication-config" content="/browserconfig.xml" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:site_name" content={title} />
<meta property="og:url" content={SITE_URL} />
<meta property="og:image" content={`${SITE_URL}/share.png?v5`} />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} />
</Head>
)
}
export { SEO } export { SEO }

View File

@ -1,22 +0,0 @@
// Drawn from primitives rather than path data so the geometry is exact: a
// circle, the equator and one meridian. Stroked rather than filled, which is
// why the dark-mode rule for .personWebsite sets `stroke` where .personGithub
// sets `fill`.
const SvgWebsite: React.FC<JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>> = (props) => (
<svg
width={18}
height={18}
viewBox="0 0 18 18"
fill="none"
stroke="currentColor"
strokeWidth={1.4}
strokeLinecap="round"
{...props}
>
<circle cx={9} cy={9} r={7} />
<line x1={2} y1={9} x2={16} y2={9} />
<ellipse cx={9} cy={9} rx={3.2} ry={7} />
</svg>
)
export { SvgWebsite }

View File

@ -1,7 +1,7 @@
@import '../../styles/variables.scss'; @import '../../styles/variables.scss';
.root { .root {
font-family: 'Manrope', Arial, Helvetica Neue, Helvetica, sans-serif; font-family: Manrope, Arial, Helvetica Neue, Helvetica, sans-serif;
} }
.h1 { .h1 {

View File

@ -1,15 +0,0 @@
import { defineConfig } from 'cypress'
export default defineConfig({
viewportWidth: 1280,
viewportHeight: 720,
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require('./cypress/plugins/index.js')(on, config)
},
baseUrl: 'http://localhost:3000',
},
})

5
cypress.json Normal file
View File

@ -0,0 +1,5 @@
{
"baseUrl": "http://localhost:3000",
"viewportWidth": 1280,
"viewportHeight": 720
}

View File

@ -1,4 +1,3 @@
/** @type {import('next').NextConfig} */
module.exports = { module.exports = {
poweredByHeader: false, poweredByHeader: false,
images: { images: {
@ -6,33 +5,28 @@ module.exports = {
imageSizes: [56, 112, 185, 370, 370, 375, 540, 740, 750, 1080], imageSizes: [56, 112, 185, 370, 370, 375, 540, 740, 750, 1080],
}, },
headers() { headers() {
return process.env.NODE_ENV === 'production' return [
? [ {
source: '/(.*)',
headers: [
{ {
source: '/(.*)', key: 'X-XSS-Protection',
headers: [ value: '1; mode=block',
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Content-Security-Policy',
value: `default-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://hive.splitbee.io https://vitals.vercel-insights.com; script-src 'self' https://cdn.splitbee.io; font-src 'self'; frame-ancestors 'self'`,
},
],
}, },
] {
: [] key: 'X-Frame-Options',
}, value: 'SAMEORIGIN',
eslint: { },
ignoreDuringBuilds: true, {
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Content-Security-Policy',
value: `default-src 'self' 'unsafe-inline'; connect-src 'self' https://hive.splitbee.io https://vitals.vercel-insights.com; script-src 'self' https://cdn.splitbee.io; font-src 'self' https://fonts.gstatic.com; frame-ancestors 'self'`,
},
],
},
]
}, },
} }

View File

@ -18,7 +18,7 @@
], ],
"author": { "author": {
"name": "Rui Saraiva", "name": "Rui Saraiva",
"email": "hello@ruisaraiva.dev", "email": "ruisaraiva19@gmail.com",
"url": "https://ruisaraiva.dev/" "url": "https://ruisaraiva.dev/"
}, },
"license": "MIT", "license": "MIT",
@ -44,101 +44,97 @@
"dependencies": { "dependencies": {
"@fiahfy/ico-convert": "0.0.12", "@fiahfy/ico-convert": "0.0.12",
"@splitbee/web": "0.3.0", "@splitbee/web": "0.3.0",
"@use-gesture/react": "10.2.16", "@use-gesture/react": "10.2.0",
"adm-zip": "0.5.9", "adm-zip": "0.5.9",
"blurhash": "1.1.5", "blurhash": "1.1.4",
"classnames": "2.3.1", "classnames": "2.3.1",
"multer": "1.4.3", "multer": "1.4.3",
"next": "12.2.0", "next": "12.0.6",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"prismjs": "1.28.0", "prismjs": "1.25.0",
"prop-types": "15.8.1", "prop-types": "15.7.2",
"react": "17.0.2", "react": "17.0.2",
"react-clipboard.js": "2.0.16", "react-clipboard.js": "2.0.16",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-dropzone": "11.4.2", "react-dropzone": "11.4.2",
"react-modal": "3.15.1", "react-modal": "3.14.4",
"react-transition-group": "4.4.2", "react-transition-group": "4.4.2",
"react-use": "17.4.0" "react-use": "17.3.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.18.6", "@babel/core": "7.16.0",
"@commitlint/cli": "17.0.3", "@commitlint/cli": "15.0.0",
"@commitlint/config-conventional": "17.0.3", "@commitlint/config-conventional": "15.0.0",
"@storybook/addon-a11y": "6.5.9", "@storybook/addon-a11y": "6.4.7",
"@storybook/addon-actions": "6.5.9", "@storybook/addon-actions": "6.4.7",
"@storybook/addon-console": "1.2.3", "@storybook/addon-console": "1.2.3",
"@storybook/addon-docs": "6.5.9", "@storybook/addon-docs": "6.4.7",
"@storybook/addon-essentials": "6.5.9", "@storybook/addon-essentials": "6.4.7",
"@storybook/addon-links": "6.5.9", "@storybook/addon-links": "6.4.7",
"@storybook/addon-postcss": "2.0.0", "@storybook/addon-postcss": "2.0.0",
"@storybook/addons": "6.5.9", "@storybook/addons": "6.4.7",
"@storybook/api": "6.5.9", "@storybook/api": "6.4.7",
"@storybook/builder-webpack5": "6.5.9", "@storybook/builder-webpack5": "6.4.7",
"@storybook/client-api": "6.5.9", "@storybook/client-api": "6.4.7",
"@storybook/components": "6.5.9", "@storybook/components": "6.4.7",
"@storybook/core-events": "6.5.9", "@storybook/core-events": "6.4.7",
"@storybook/manager-webpack5": "6.5.9", "@storybook/manager-webpack5": "6.4.7",
"@storybook/node-logger": "6.5.9", "@storybook/node-logger": "6.4.7",
"@storybook/react": "6.5.9", "@storybook/react": "6.4.7",
"@storybook/theming": "6.5.9", "@storybook/theming": "6.4.7",
"@types/adm-zip": "0.4.34", "@types/adm-zip": "0.4.34",
"@types/multer": "1.4.7", "@types/multer": "1.4.7",
"@types/node": "16.11.11", "@types/node": "16.11.11",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
"@types/prismjs": "1.26.0", "@types/prismjs": "1.16.6",
"@types/react": "17.0.37", "@types/react": "17.0.37",
"@types/react-modal": "3.13.1", "@types/react-modal": "3.13.1",
"@types/react-transition-group": "4.4.5", "@types/react-transition-group": "4.4.4",
"@types/sharp": "0.29.4", "@types/sharp": "0.29.4",
"@typescript-eslint/eslint-plugin": "5.30.5", "@typescript-eslint/eslint-plugin": "5.5.0",
"@typescript-eslint/parser": "5.30.5", "@typescript-eslint/parser": "5.5.0",
"all-contributors-cli": "6.20.0", "all-contributors-cli": "6.20.0",
"babel-loader": "8.2.5", "babel-loader": "8.2.3",
"commitizen": "4.2.4", "commitizen": "4.2.4",
"cypress": "10.3.0", "cypress": "9.1.1",
"cypress-file-upload": "5.0.8", "cypress-file-upload": "5.0.8",
"cz-conventional-changelog": "3.3.0", "cz-conventional-changelog": "3.3.0",
"dotenv": "16.0.1", "dotenv": "10.0.0",
"eslint": "8.19.0", "eslint": "8.4.0",
"eslint-config-next": "12.2.0", "eslint-config-next": "12.0.6",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.3.0",
"eslint-plugin-cypress": "2.12.1", "eslint-plugin-cypress": "2.12.1",
"eslint-plugin-prettier": "4.2.1", "eslint-plugin-prettier": "4.0.0",
"eslint-plugin-storybook": "0.5.13", "eslint-plugin-storybook": "0.5.3",
"husky": "8.0.1", "husky": "7.0.4",
"lint-staged": "13.0.3", "lint-staged": "12.1.2",
"npm-run-all": "4.1.5", "npm-run-all": "4.1.5",
"postcss": "8.4.14", "postcss": "8.4.4",
"postcss-100vh-fix": "1.0.2", "postcss-100vh-fix": "1.0.2",
"postcss-flexbugs-fixes": "5.0.2", "postcss-flexbugs-fixes": "5.0.2",
"postcss-preset-env": "7.7.2", "postcss-preset-env": "7.0.1",
"postcss-scss": "4.0.4", "postcss-scss": "4.0.2",
"prettier": "2.7.1", "prettier": "2.3.2",
"react-refresh": "0.14.0", "react-refresh": "0.11.0",
"require-from-string": "2.0.2", "sass": "1.44.0",
"sass": "1.53.0", "sass-loader": "12.3.0",
"sass-loader": "13.0.2", "semantic-release": "18.0.1",
"semantic-release": "19.0.3",
"start-server-and-test": "1.14.0", "start-server-and-test": "1.14.0",
"storybook-addon-next-router": "4.0.0", "storybook-addon-next-router": "3.1.0",
"storybook-dark-mode": "1.1.0", "storybook-dark-mode": "1.0.8",
"stylelint": "14.9.1", "stylelint": "14.1.0",
"stylelint-config-css-modules": "4.1.0", "stylelint-config-css-modules": "2.3.0",
"stylelint-config-prettier": "9.0.3", "stylelint-config-prettier": "9.0.3",
"stylelint-config-recess-order": "3.0.0", "stylelint-config-recess-order": "3.0.0",
"stylelint-config-standard": "26.0.0", "stylelint-config-standard": "24.0.0",
"stylelint-prettier": "2.0.0", "stylelint-prettier": "2.0.0",
"stylelint-scss": "4.2.0", "stylelint-scss": "4.0.0",
"typescript": "4.7.4", "typescript": "4.4.4",
"webpack": "5.73.0" "webpack": "5.64.4"
}, },
"config": { "config": {
"commitizen": { "commitizen": {
"path": "cz-conventional-changelog" "path": "cz-conventional-changelog"
} }
},
"resolutions": {
"sharp": "^0.28.0"
} }
} }

View File

@ -4,13 +4,16 @@ import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document { class MyDocument extends Document {
render() { render() {
return ( return (
<Html lang="en" className="preload"> <Html lang="en">
<Head /> <Head>
<body> <link rel="preconnect" href="https://fonts.googleapis.com" />
<script src="/js/noflash.js?v1" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
</Head>
<body className="preload">
<script src="/js/noflash.js" />
<Main /> <Main />
<NextScript /> <NextScript />
<script src="/js/onload.js?v1" /> <script src="/js/onload.js" />
</body> </body>
</Html> </Html>
) )

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,5 +1,5 @@
;(function () { ;(function () {
window.addEventListener('load', function () { window.addEventListener('load', function () {
document.documentElement.classList.remove('preload') document.body.classList.remove('preload')
}) })
})() })()

View File

@ -1,48 +0,0 @@
# Favycon
> A free online tool that turns one image into a complete set of favicons, in
> every size and format a site needs, as a downloadable zip.
Drag and drop an image and Favycon returns `favicons.zip` plus the markup to
paste into your `<head>`. Nothing is stored: the image is converted in the
request and the zip is streamed straight back.
Site: https://favycon.vercel.app
## Input
- Accepted formats: JPEG, PNG, SVG.
- Minimum dimensions: 310x310. SVG input is exempt, since it is rescaled from
vector.
- With the PWA option enabled the minimum is 512x512, again except for SVG.
## What the zip contains
- `icons/favicon-<size>.png` at 16x16, 32x32, 57x57, 60x60, 70x70, 72x72,
76x76, 96x96, 114x114, 120x120, 144x144, 150x150, 152x152, 180x180, 192x192
and 310x310.
- `icons/favicon.ico`, rendered from a 256x256 master.
- `icons/favicon.svg`, only when the uploaded image was itself an SVG.
- `icons/browserconfig.xml` for IE10+ tiles.
- `icons/manifest.json`, only when the PWA option is on. That option also adds
128x128, 384x384 and 512x512 PNGs.
- `readme.txt` with the HTML to add, written to match the options chosen.
## Facts
- Author: Rui Saraiva (https://ruisaraiva.dev)
- Contributors: Augusto Lopes (product design), Miguel Teixeira
- Built in 2020
- Licence: MIT
- Source: https://github.com/ruisaraiva19/favycon
- Price: free, no account, no upload limit published
- Contact: hello@ruisaraiva.dev
## Notes
- This is a single-page tool. There is no blog, docs site or API documentation
to crawl beyond this file and the repository.
- There is a public HTTP endpoint at `POST /api/favycon`. It takes a multipart
body with an `icon` file field and an optional `pwa` field set to `1`, and
responds with `application/zip`. It is what the page itself calls; it is not
a documented or versioned public API.

View File

@ -1,8 +1,2 @@
User-Agent: * User-Agent: *
Allow: / Allow: /
# Nothing is disallowed. /api/favycon is POST-only and answers a GET with 405,
# which is a clearer signal to a crawler than a rule it has to fetch robots.txt
# to learn. A plain-text description of the tool lives at /llms.txt
Sitemap: https://favycon.vercel.app/sitemap.xml

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://favycon.vercel.app</loc>
</url>
</urlset>

View File

@ -1,48 +1,48 @@
@font-face { @font-face {
font-family: 'Manrope'; font-family: Manrope;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url('/fonts/manrope-v12.woff2') format('woff2'); src: url('https://fonts.gstatic.com/s/manrope/v4/xn7gYHE41ni1AdIRggexSvfedN4.woff2') format('woff2');
font-display: swap; font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
} }
@font-face { @font-face {
font-family: 'Manrope'; font-family: Manrope;
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
src: url('/fonts/manrope-v12.woff2') format('woff2'); src: url('https://fonts.gstatic.com/s/manrope/v4/xn7gYHE41ni1AdIRggexSvfedN4.woff2') format('woff2');
font-display: swap; font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
} }
@font-face { @font-face {
font-family: 'Manrope'; font-family: Manrope;
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
src: url('/fonts/manrope-v12.woff2') format('woff2'); src: url('https://fonts.gstatic.com/s/manrope/v4/xn7gYHE41ni1AdIRggexSvfedN4.woff2') format('woff2');
font-display: swap; font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
} }
@font-face { @font-face {
font-family: 'Manrope'; font-family: Manrope;
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
src: url('/fonts/manrope-v12.woff2') format('woff2'); src: url('https://fonts.gstatic.com/s/manrope/v4/xn7gYHE41ni1AdIRggexSvfedN4.woff2') format('woff2');
font-display: swap; font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
} }
@font-face { @font-face {
font-family: 'Manrope'; font-family: Manrope;
font-style: normal; font-style: normal;
font-weight: 800; font-weight: 800;
src: url('/fonts/manrope-v12.woff2') format('woff2'); src: url('https://fonts.gstatic.com/s/manrope/v4/xn7gYHE41ni1AdIRggexSvfedN4.woff2') format('woff2');
font-display: swap; font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;

View File

@ -11,14 +11,14 @@ body {
/* Disables pull-to-refresh but allows overscroll glow effects. */ /* Disables pull-to-refresh but allows overscroll glow effects. */
overscroll-behavior-y: contain; overscroll-behavior-y: contain;
font-family: 'Manrope', Arial, 'Helvetica Neue', Helvetica, sans-serif; font-family: Manrope, Arial, 'Helvetica Neue', Helvetica, sans-serif;
/* https://gist.github.com/hsleonis/55712b0eafc9b25f1944 */ /* https://gist.github.com/hsleonis/55712b0eafc9b25f1944 */
font-size: 100%; font-size: 100%;
font-variant-ligatures: none; font-variant-ligatures: none;
text-shadow: rgba($black, 0.01) 0 0 1px; text-shadow: rgba($black, 0.01) 0 0 1px;
transition: background 0.2s ease; transition: background 0.2s ease;
text-rendering: optimizelegibility; text-rendering: optimizeLegibility;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;

6455
yarn.lock

File diff suppressed because it is too large Load Diff