mirror of
https://github.com/ruisaraiva19/favycon.git
synced 2026-09-12 19:51:07 +05:00
Merge pull request #486 from ruisaraiva19/develop
Some checks failed
Release / Semantic Release (push) Has been cancelled
Some checks failed
Release / Semantic Release (push) Has been cancelled
chore: release
This commit is contained in:
commit
d4c2ef6e2a
@ -290,7 +290,11 @@
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
&.personGithub {
|
||||
// Both modifiers do the same job: drop the base class's Twitter blue for the
|
||||
// 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;
|
||||
|
||||
p {
|
||||
@ -316,7 +320,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
:global(.dark) .personTwitter.personGithub p {
|
||||
:global(.dark) .personTwitter.personGithub p,
|
||||
:global(.dark) .personTwitter.personWebsite p {
|
||||
color: $white;
|
||||
|
||||
&:hover {
|
||||
@ -328,6 +333,13 @@
|
||||
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 {
|
||||
padding-bottom: 48px;
|
||||
margin-top: 12px;
|
||||
|
||||
@ -7,6 +7,7 @@ import { Typography } from 'components/typography'
|
||||
import { ToolTitle } from 'components/tool-title'
|
||||
import { SvgTwitter } from 'components/svgs/svg-twitter'
|
||||
import { SvgGithub } from 'components/svgs/svg-github'
|
||||
import { SvgWebsite } from 'components/svgs/svg-website'
|
||||
|
||||
import styles from './index.module.scss'
|
||||
import { SvgFavycon } from 'components/svgs/svg-favycon'
|
||||
@ -19,26 +20,47 @@ import miguelTeixeira from '../../public/images/people/miguel-teixeira.png'
|
||||
|
||||
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 = [
|
||||
{
|
||||
screenName: 'aboutaugusto',
|
||||
handle: 'aboutaugusto',
|
||||
name: 'Augusto Lopes',
|
||||
role: 'Product Designer',
|
||||
social: 'twitter',
|
||||
social: 'twitter' as const,
|
||||
photo: augustoLopes,
|
||||
},
|
||||
{
|
||||
screenName: 'ruisaraiva19',
|
||||
handle: 'ruisaraiva.dev',
|
||||
name: 'Rui Saraiva',
|
||||
role: 'Full-Stack Developer',
|
||||
social: 'twitter',
|
||||
role: 'Principal Engineer',
|
||||
social: 'website' as const,
|
||||
photo: ruiSaraiva,
|
||||
},
|
||||
{
|
||||
screenName: 'miguellteixeira',
|
||||
handle: 'miguellteixeira',
|
||||
name: 'Miguel Teixeira',
|
||||
role: 'Full-Stack Developer',
|
||||
social: 'github',
|
||||
social: 'github' as const,
|
||||
photo: miguelTeixeira,
|
||||
},
|
||||
]
|
||||
@ -138,56 +160,47 @@ const FavyconInfo = ({
|
||||
Behind the curtains
|
||||
</Typography>
|
||||
<Typography variant="largeBody" weight="medium" className={styles.subTitle}>
|
||||
For new updates be sure to follow these guys on Twitter.
|
||||
For new updates be sure to follow these guys.
|
||||
</Typography>
|
||||
</div>
|
||||
<hr className={styles.hr} />
|
||||
<div className={styles.people}>
|
||||
{people.map((person) => (
|
||||
<div key={person.screenName} className={styles.person}>
|
||||
<div className={styles.personAvatar}>
|
||||
<Image
|
||||
src={person.photo}
|
||||
alt={person.name}
|
||||
width={56}
|
||||
height={56}
|
||||
placeholder="blur"
|
||||
layout="intrinsic"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="largeBody" weight="semiBold">
|
||||
{person.name}
|
||||
</Typography>
|
||||
<Typography variant="regularBody" weight="medium" className={styles.personRole}>
|
||||
{person.role}
|
||||
</Typography>
|
||||
{person.social === 'twitter' ? (
|
||||
{people.map((person) => {
|
||||
const { Icon, url, modifier } = socials[person.social]
|
||||
|
||||
return (
|
||||
<div key={person.handle} className={styles.person}>
|
||||
<div className={styles.personAvatar}>
|
||||
<Image
|
||||
src={person.photo}
|
||||
alt={person.name}
|
||||
width={56}
|
||||
height={56}
|
||||
placeholder="blur"
|
||||
layout="intrinsic"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="largeBody" weight="semiBold">
|
||||
{person.name}
|
||||
</Typography>
|
||||
<Typography variant="regularBody" weight="medium" className={styles.personRole}>
|
||||
{person.role}
|
||||
</Typography>
|
||||
<a
|
||||
href={`https://twitter.com/${person.screenName}`}
|
||||
className={styles.personTwitter}
|
||||
href={url(person.handle)}
|
||||
className={classnames(styles.personTwitter, modifier)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Typography variant="footer" weight="semiBold">
|
||||
<SvgTwitter /> {person.screenName}
|
||||
<Icon /> {person.handle}
|
||||
</Typography>
|
||||
</a>
|
||||
) : (
|
||||
<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>
|
||||
<hr className={styles.hr} />
|
||||
<div className={classnames(styles.footer, styles.modalFooter)}>
|
||||
@ -202,8 +215,8 @@ const FavyconInfo = ({
|
||||
GitHub
|
||||
</a>
|
||||
. Have feedback? Send it to{' '}
|
||||
<a className={styles.contact} href="mailto:hello@favycon.app">
|
||||
hello@favycon.app
|
||||
<a className={styles.contact} href="mailto:hello@ruisaraiva.dev">
|
||||
hello@ruisaraiva.dev
|
||||
</a>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@ -1,43 +1,80 @@
|
||||
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 = {
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const SEO = ({ title, description }: SEOProps) => (
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/favicon-57x57.png" />
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/favicon-60x60.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/favicon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/favicon-76x76.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/favicon-114x114.png" />
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/favicon-120x120.png" />
|
||||
<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="https://favycon.vercel.app" />
|
||||
<meta property="og:image" content="https://favycon.vercel.app/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" />
|
||||
</Head>
|
||||
)
|
||||
const SEO = ({ title, description }: SEOProps) => {
|
||||
const structuredData = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'Favycon',
|
||||
description,
|
||||
url: SITE_URL,
|
||||
applicationCategory: 'DeveloperApplication',
|
||||
operatingSystem: 'Any',
|
||||
browserRequirements: 'Requires JavaScript',
|
||||
image: `${SITE_URL}/share.png`,
|
||||
license: 'https://opensource.org/licenses/MIT',
|
||||
// It is genuinely free, and Google wants the price stated to believe it.
|
||||
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
||||
author: { '@type': 'Person', ...AUTHOR },
|
||||
contributor: CONTRIBUTORS.map((person) => ({ '@type': 'Person', ...person })),
|
||||
codeRepository: 'https://github.com/ruisaraiva19/favycon',
|
||||
}
|
||||
|
||||
return (
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
<meta name="author" content={AUTHOR.name} />
|
||||
<link rel="author" href={AUTHOR.url} />
|
||||
{/* favycon.app has no DNS, so vercel.app is the only hostname this answers on. */}
|
||||
<link rel="canonical" href={SITE_URL} />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/favicon-57x57.png" />
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/favicon-60x60.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/favicon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/favicon-76x76.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/favicon-114x114.png" />
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/favicon-120x120.png" />
|
||||
<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 }
|
||||
|
||||
22
components/svgs/svg-website.tsx
Normal file
22
components/svgs/svg-website.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
// 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 }
|
||||
@ -18,7 +18,7 @@
|
||||
],
|
||||
"author": {
|
||||
"name": "Rui Saraiva",
|
||||
"email": "ruisaraiva19@gmail.com",
|
||||
"email": "hello@ruisaraiva.dev",
|
||||
"url": "https://ruisaraiva.dev/"
|
||||
},
|
||||
"license": "MIT",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 8.3 KiB |
48
public/llms.txt
Normal file
48
public/llms.txt
Normal file
@ -0,0 +1,48 @@
|
||||
# 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.
|
||||
@ -1,2 +1,8 @@
|
||||
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
|
||||
|
||||
6
public/sitemap.xml
Normal file
6
public/sitemap.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?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>
|
||||
Loading…
Reference in New Issue
Block a user