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.
- {people.map((person) => ( -
-
- {person.name} -
-
- - {person.name} - - - {person.role} - - {person.social === 'twitter' ? ( + {people.map((person) => { + const { Icon, url, modifier } = socials[person.social] + + return ( +
+
+ {person.name} +
+
+ + {person.name} + + + {person.role} + - {person.screenName} + {person.handle} - ) : ( - - - {person.screenName} - - - )} +
-
- ))} + ) + })}

@@ -202,8 +215,8 @@ const FavyconInfo = ({ GitHub . Have feedback? Send it to{' '} - - hello@favycon.app + + hello@ruisaraiva.dev
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. */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +