diff --git a/components/favycon-info/index.module.scss b/components/favycon-info/index.module.scss
index 280df65..5e8ed9b 100644
--- a/components/favycon-info/index.module.scss
+++ b/components/favycon-info/index.module.scss
@@ -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;
diff --git a/components/favycon-info/index.tsx b/components/favycon-info/index.tsx
index ddbb7c2..51855af 100644
--- a/components/favycon-info/index.tsx
+++ b/components/favycon-info/index.tsx
@@ -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
- For new updates be sure to follow these guys on Twitter.
+ For new updates be sure to follow these guys.
diff --git a/components/seo/index.tsx b/components/seo/index.tsx
index 8b33f8b..c0d0141 100644
--- a/components/seo/index.tsx
+++ b/components/seo/index.tsx
@@ -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) => (
-
- {title}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-)
+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 (
+
+ {title}
+
+
+
+ {/* favycon.app has no DNS, so vercel.app is the only hostname this answers on. */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
export { SEO }
diff --git a/components/svgs/svg-website.tsx b/components/svgs/svg-website.tsx
new file mode 100644
index 0000000..b0b8301
--- /dev/null
+++ b/components/svgs/svg-website.tsx
@@ -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> = (props) => (
+
+)
+
+export { SvgWebsite }
diff --git a/package.json b/package.json
index d687444..1478992 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
],
"author": {
"name": "Rui Saraiva",
- "email": "ruisaraiva19@gmail.com",
+ "email": "hello@ruisaraiva.dev",
"url": "https://ruisaraiva.dev/"
},
"license": "MIT",
diff --git a/public/images/people/rui-saraiva.png b/public/images/people/rui-saraiva.png
index bd9c82d..a44175e 100644
Binary files a/public/images/people/rui-saraiva.png and b/public/images/people/rui-saraiva.png differ
diff --git a/public/llms.txt b/public/llms.txt
new file mode 100644
index 0000000..c5421a4
--- /dev/null
+++ b/public/llms.txt
@@ -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 ``. 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-.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.
diff --git a/public/robots.txt b/public/robots.txt
index 26b4d70..c6f7a71 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,2 +1,8 @@
User-Agent: *
-Allow: /
\ No newline at end of file
+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
diff --git a/public/sitemap.xml b/public/sitemap.xml
new file mode 100644
index 0000000..71de8ed
--- /dev/null
+++ b/public/sitemap.xml
@@ -0,0 +1,6 @@
+
+
+
+ https://favycon.vercel.app
+
+