diff --git a/.eslintrc.yml b/.eslintrc.yml
index 63cdb25b0b33a28d5dfff4851224b23ca3a73534..ad85c6aaf16b6c8ca8cd7ddb049cc7a0e90eab8c 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -1,7 +1,8 @@
 extends:
-  - standard-jsx
+  - standard
   - standard-react
   - plugin:@typescript-eslint/recommended
+  - prettier
   - prettier/@typescript-eslint
   - prettier/standard
   - prettier/react
@@ -39,7 +40,6 @@ overrides:
       es6: true
     rules:
       no-console: 'off'
-      '@typescript-eslint/no-var-requires': off
 
   - files:
       - '**/*.@(ts|tsx)'
@@ -48,8 +48,13 @@ overrides:
     parser: '@typescript-eslint/parser'
     rules:
       # Argh.
-      '@typescript-eslint/explicit-function-return-type': 'off'
+      '@typescript-eslint/explicit-function-return-type':
+        ['error', { 'allowExpressions': true }]
+      '@typescript-eslint/no-empty-function': 'error'
+      '@typescript-eslint/no-var-requires': 'error'
       '@typescript-eslint/no-object-literal-type-assertion': 'off'
+      '@typescript-eslint/no-explicit-any': 'error'
+      '@typescript-eslint/ban-ts-ignore': 'off'
 
   - files:
       - core/**/*.ts
@@ -113,9 +118,9 @@ rules:
   # Allow unused parameters. In callbacks, removing them seems to obscure
   # what the functions are doing.
   '@typescript-eslint/no-unused-vars': ['error', { 'args': 'none' }]
-  no-unused-vars: off
+  no-unused-vars: 'off'
 
-  '@typescript-eslint/no-var-requires': error
+  '@typescript-eslint/no-var-requires': 'off'
 
   # These should be disabled by eslint-config-prettier, but are not.
   no-extra-semi: 'off'
@@ -163,6 +168,8 @@ rules:
 
   # Disable some from TypeScript.
   '@typescript-eslint/camelcase': off
+  '@typescript-eslint/explicit-function-return-type': 'off'
+  '@typescript-eslint/no-empty-function': 'off'
 
   react/jsx-sort-props: 'error'
   react-hooks/rules-of-hooks: 'error'
diff --git a/core/base-service/coalesce.spec.js b/core/base-service/coalesce.spec.js
index ed596d4276f0b4d7f3e4270244405a933f101dda..180f9e1e6aa1dc9749a75d135a9ae9249ff0a9b9 100644
--- a/core/base-service/coalesce.spec.js
+++ b/core/base-service/coalesce.spec.js
@@ -14,9 +14,9 @@ describe('coalesce', function() {
     given(null, [], {}).expect([])
     given(null, undefined, 0, {}).expect(0)
 
-    const a = null,
-      c = 0,
-      d = 1
+    const a = null
+    const c = 0
+    const d = 1
     let b
     given(a, b, c, d).expect(0)
   })
diff --git a/core/base-service/errors.js b/core/base-service/errors.js
index 98c0043ebe2db30daa2889083216b5404fc03006..5bbd709089df19bb1de900c1821a2121e05e498e 100644
--- a/core/base-service/errors.js
+++ b/core/base-service/errors.js
@@ -56,6 +56,7 @@ class NotFound extends ShieldsRuntimeError {
   get name() {
     return 'NotFound'
   }
+
   get defaultPrettyMessage() {
     return defaultNotFoundError
   }
@@ -82,6 +83,7 @@ class InvalidResponse extends ShieldsRuntimeError {
   get name() {
     return 'InvalidResponse'
   }
+
   get defaultPrettyMessage() {
     return 'invalid'
   }
@@ -107,6 +109,7 @@ class Inaccessible extends ShieldsRuntimeError {
   get name() {
     return 'Inaccessible'
   }
+
   get defaultPrettyMessage() {
     return 'inaccessible'
   }
@@ -131,6 +134,7 @@ class ImproperlyConfigured extends ShieldsRuntimeError {
   get name() {
     return 'ImproperlyConfigured'
   }
+
   get defaultPrettyMessage() {
     return 'improperly configured'
   }
@@ -156,6 +160,7 @@ class InvalidParameter extends ShieldsRuntimeError {
   get name() {
     return 'InvalidParameter'
   }
+
   get defaultPrettyMessage() {
     return 'invalid parameter'
   }
@@ -180,6 +185,7 @@ class Deprecated extends ShieldsRuntimeError {
   get name() {
     return 'Deprecated'
   }
+
   get defaultPrettyMessage() {
     return 'no longer available'
   }
diff --git a/core/base-service/validate.js b/core/base-service/validate.js
index c4db4104f469fca3c3e9414685487c792586e49d..0bfc4a8773269b0a08629469c2b9dedbf49fad14 100644
--- a/core/base-service/validate.js
+++ b/core/base-service/validate.js
@@ -21,8 +21,8 @@ function validate(
   }
   const options = { abortEarly: false }
   if (allowAndStripUnknownKeys) {
-    options['allowUnknown'] = true
-    options['stripUnknown'] = true
+    options.allowUnknown = true
+    options.stripUnknown = true
   }
   const { error, value } = schema.validate(data, options)
   if (error) {
diff --git a/core/server/monitor.js b/core/server/monitor.js
index d62e2df573723e853b49701b7d6a8ad03a885799..96ef183af760d3533d7ad76ef9b9aca2354bc795 100644
--- a/core/server/monitor.js
+++ b/core/server/monitor.js
@@ -43,7 +43,7 @@ function setRoutes({ rateLimit }, { server, metricInstance }) {
         .split(/[/-]/)
         .slice(0, 3)
         .join('')
-      const referer = req.headers['referer']
+      const referer = req.headers.referer
 
       if (ipRateLimit.isBanned(ip, req, res)) {
         metricInstance.noteRateLimitExceeded('ip')
diff --git a/core/server/server.spec.js b/core/server/server.spec.js
index bad8d9d6b129bd0a6d81b025e861a9ef6c23bbf9..7150b7e36d8e50fa2f6900312eed3d3e678fcb29 100644
--- a/core/server/server.spec.js
+++ b/core/server/server.spec.js
@@ -24,7 +24,7 @@ describe('The server', function() {
   })
 
   it('should allow strings for port', async function() {
-    //fixes #4391 - This allows the app to be run using iisnode, which uses a named pipe for the port.
+    // fixes #4391 - This allows the app to be run using iisnode, which uses a named pipe for the port.
     const pipeServer = createTestServer({
       port: '\\\\.\\pipe\\9c137306-7c4d-461e-b7cf-5213a3939ad6',
     })
diff --git a/core/token-pooling/token-pool.js b/core/token-pooling/token-pool.js
index 83e76771db450e6f7143eb001c4df2229d24a536..5061c8dbf6c168ebcb009870cfdcbec5cff619f3 100644
--- a/core/token-pooling/token-pool.js
+++ b/core/token-pooling/token-pool.js
@@ -55,18 +55,23 @@ class Token {
   get id() {
     return this._id
   }
+
   get data() {
     return this._data
   }
+
   get usesRemaining() {
     return this._usesRemaining
   }
+
   get nextReset() {
     return this._nextReset
   }
+
   get isValid() {
     return this._isValid
   }
+
   get isFrozen() {
     return this._isFrozen
   }
diff --git a/frontend/components/badge-examples.tsx b/frontend/components/badge-examples.tsx
index c10424b173dce734ff125c3eb5f055d07146bc30..0a3229d5b8e4fc0ca35a3e22152fa6723c4a14b7 100644
--- a/frontend/components/badge-examples.tsx
+++ b/frontend/components/badge-examples.tsx
@@ -42,8 +42,8 @@ function Example({
   onClick: (example: RenderableExample, isSuggestion: boolean) => void
   exampleData: RenderableExample
   isBadgeSuggestion: boolean
-}) {
-  function handleClick() {
+}): JSX.Element {
+  function handleClick(): void {
     onClick(exampleData, isBadgeSuggestion)
   }
 
@@ -106,7 +106,7 @@ export function BadgeExamples({
   areBadgeSuggestions: boolean
   baseUrl?: string
   onClick: (exampleData: RenderableExample, isSuggestion: boolean) => void
-}) {
+}): JSX.Element {
   return (
     <ExampleTable>
       <tbody>
diff --git a/frontend/components/category-headings.tsx b/frontend/components/category-headings.tsx
index 9cbcbb6a43a52fdc06bcfb7bf804d62c7dc1fd6a..2f1d03c9a512ac3f788035e46d2088e33d809dbd 100644
--- a/frontend/components/category-headings.tsx
+++ b/frontend/components/category-headings.tsx
@@ -12,7 +12,7 @@ export function CategoryHeading({
   category: { id, name },
 }: {
   category: Category
-}) {
+}): JSX.Element {
   return (
     <Link to={`/category/${id}`}>
       <H3 id={id}>{name}</H3>
@@ -20,7 +20,11 @@ export function CategoryHeading({
   )
 }
 
-export function CategoryHeadings({ categories }: { categories: Category[] }) {
+export function CategoryHeadings({
+  categories,
+}: {
+  categories: Category[]
+}): JSX.Element {
   return (
     <div>
       {categories.map(category => (
@@ -61,7 +65,11 @@ const StyledNav = styled.nav`
   }
 `
 
-export function CategoryNav({ categories }: { categories: Category[] }) {
+export function CategoryNav({
+  categories,
+}: {
+  categories: Category[]
+}): JSX.Element {
   return (
     <StyledNav>
       <ul>
diff --git a/frontend/components/common.tsx b/frontend/components/common.tsx
index bd9d47b3b1fe594c2b7eb30d86ac950b605b14de..60e2a78db7cbd464317a34e690a848eddcba0030 100644
--- a/frontend/components/common.tsx
+++ b/frontend/components/common.tsx
@@ -74,7 +74,7 @@ export function Badge({
   height = '20px',
   clickable = false,
   ...rest
-}: BadgeProps) {
+}: BadgeProps): JSX.Element {
   return (
     <BadgeWrapper clickable={clickable} display={display} height={height}>
       {src ? <img alt={alt} src={src} {...rest} /> : nonBreakingSpace}
diff --git a/frontend/components/customizer/builder-common.tsx b/frontend/components/customizer/builder-common.tsx
index 907d78a160532613f68f55c0e65c41263c4fe894..939184028728f3dbbc29e216ae6d340ae02aacc5 100644
--- a/frontend/components/customizer/builder-common.tsx
+++ b/frontend/components/customizer/builder-common.tsx
@@ -20,7 +20,7 @@ export function BuilderContainer({
   children,
 }: {
   children: JSX.Element[] | JSX.Element
-}) {
+}): JSX.Element {
   return (
     <BuilderOuterContainer>
       <BuilderInnerContainer>{children}</BuilderInnerContainer>
diff --git a/frontend/components/customizer/copied-content-indicator.tsx b/frontend/components/customizer/copied-content-indicator.tsx
index 42751944cef1cb49e7590e171d4332f7bea41d36..560fe33872a6d03d0a3a1fd9dbdbdf06c26959ef 100644
--- a/frontend/components/customizer/copied-content-indicator.tsx
+++ b/frontend/components/customizer/copied-content-indicator.tsx
@@ -41,7 +41,7 @@ function _CopiedContentIndicator(
     children: JSX.Element | JSX.Element[]
   },
   ref: React.Ref<CopiedContentIndicatorHandle>
-) {
+): JSX.Element {
   const [pose, setPose] = useState('hidden')
 
   useImperativeHandle(ref, () => ({
@@ -50,7 +50,7 @@ function _CopiedContentIndicator(
     },
   }))
 
-  function handlePoseComplete() {
+  function handlePoseComplete(): void {
     if (pose === 'effectStart') {
       setPose('effectEnd')
     } else {
diff --git a/frontend/components/customizer/customizer.tsx b/frontend/components/customizer/customizer.tsx
index 5a6b1a19aecfa208ec5a0a74da5c11a74a3a8247..fe925ec73b32667c895221b6d339cb6244da4748 100644
--- a/frontend/components/customizer/customizer.tsx
+++ b/frontend/components/customizer/customizer.tsx
@@ -36,7 +36,7 @@ export default function Customizer({
   initialStyle?: string
   isPrefilled: boolean
   link?: string
-}) {
+}): JSX.Element {
   // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/35572
   // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/28884#issuecomment-471341041
   const indicatorRef = useRef<
@@ -48,12 +48,12 @@ export default function Customizer({
   const [markup, setMarkup] = useState()
   const [message, setMessage] = useState()
 
-  function generateBuiltBadgeUrl() {
+  function generateBuiltBadgeUrl(): string {
     const suffix = queryString ? `?${queryString}` : ''
     return `${baseUrl || getBaseUrlFromWindowLocation()}${path}${suffix}`
   }
 
-  function renderLivePreview() {
+  function renderLivePreview(): JSX.Element {
     // There are some usability issues here. It would be better if the message
     // changed from a validation error to a loading message once the
     // parameters were filled in, and also switched back to loading when the
@@ -75,7 +75,7 @@ export default function Customizer({
     )
   }
 
-  async function copyMarkup(markupFormat: MarkupFormat) {
+  async function copyMarkup(markupFormat: MarkupFormat): Promise<void> {
     const builtBadgeUrl = generateBuiltBadgeUrl()
     const markup = generateMarkup({
       badgeUrl: builtBadgeUrl,
@@ -98,7 +98,7 @@ export default function Customizer({
     }
   }
 
-  function renderMarkupAndLivePreview() {
+  function renderMarkupAndLivePreview(): JSX.Element {
     return (
       <div>
         {renderLivePreview()}
@@ -124,7 +124,7 @@ export default function Customizer({
   }: {
     path: string
     isComplete: boolean
-  }) {
+  }): void {
     setPath(path)
     setPathIsComplete(isComplete)
   }
@@ -135,7 +135,7 @@ export default function Customizer({
   }: {
     queryString: string
     isComplete: boolean
-  }) {
+  }): void {
     setQueryString(queryString)
   }
 
diff --git a/frontend/components/customizer/path-builder.tsx b/frontend/components/customizer/path-builder.tsx
index 019a0d230572b98636485baebdcf985351cded6b..35b02f6be9aca06f5e0e2209891782c2ca1a2d18 100644
--- a/frontend/components/customizer/path-builder.tsx
+++ b/frontend/components/customizer/path-builder.tsx
@@ -84,7 +84,7 @@ export function constructPath({
 }: {
   tokens: Token[]
   namedParams: { [k: string]: string }
-}) {
+}): { path: string; isComplete: boolean } {
   let isComplete = true
   const path = tokens
     .map(token => {
@@ -123,7 +123,7 @@ export default function PathBuilder({
     isComplete: boolean
   }) => void
   isPrefilled: boolean
-}) {
+}): JSX.Element {
   const [tokens] = useState(() => parse(pattern))
   const [namedParams, setNamedParams] = useState(() =>
     isPrefilled
@@ -150,7 +150,7 @@ export default function PathBuilder({
 
   function handleTokenChange({
     target: { name, value },
-  }: ChangeEvent<HTMLInputElement | HTMLSelectElement>) {
+  }: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
     setNamedParams({
       ...namedParams,
       [name]: value,
@@ -161,7 +161,7 @@ export default function PathBuilder({
     literal: string,
     tokenIndex: number,
     pathContainsOnlyLiterals: boolean
-  ) {
+  ): JSX.Element {
     return (
       <PathBuilderColumn
         key={`${tokenIndex}-${literal}`}
@@ -177,7 +177,7 @@ export default function PathBuilder({
     )
   }
 
-  function renderNamedParamInput(token: Key) {
+  function renderNamedParamInput(token: Key): JSX.Element {
     const { pattern } = token
     const name = `${token.name}`
     const options = patternToOptions(pattern)
@@ -219,7 +219,7 @@ export default function PathBuilder({
     token: Key,
     tokenIndex: number,
     namedParamIndex: number
-  ) {
+  ): JSX.Element {
     const { delimiter, optional } = token
     const name = `${token.name}`
 
diff --git a/frontend/components/customizer/query-string-builder.tsx b/frontend/components/customizer/query-string-builder.tsx
index 0e2966a5dc6992b9b82278fbff00fe07b8868f5b..956abe050bbb77104a4a965b30a2832d4d04f539 100644
--- a/frontend/components/customizer/query-string-builder.tsx
+++ b/frontend/components/customizer/query-string-builder.tsx
@@ -113,7 +113,7 @@ function ServiceQueryParam({
   isStringParam: boolean
   stringParamCount?: number
   handleServiceQueryParamChange: ChangeEventHandler<HTMLInputElement>
-}) {
+}): JSX.Element {
   return (
     <tr>
       <td>
@@ -160,7 +160,7 @@ function BadgeOptionInput({
   handleBadgeOptionChange: ChangeEventHandler<
     HTMLSelectElement | HTMLInputElement
   >
-}) {
+}): JSX.Element {
   if (name === 'style') {
     return (
       <select name="style" onChange={handleBadgeOptionChange} value={value}>
@@ -192,7 +192,7 @@ function BadgeOption({
   name: BadgeOptionName
   value: string
   handleBadgeOptionChange: ChangeEventHandler<HTMLInputElement>
-}) {
+}): JSX.Element {
   const {
     label = humanizeString(name),
     shieldsDefaultValue: hasShieldsDefaultValue,
@@ -237,7 +237,7 @@ export default function QueryStringBuilder({
     queryString: string
     isComplete: boolean
   }) => void
-}) {
+}): JSX.Element {
   const [queryParams, setQueryParams] = useState(() =>
     // For each of the custom query params defined in `exampleParams`,
     // create empty values in `queryParams`.
@@ -266,14 +266,14 @@ export default function QueryStringBuilder({
 
   function handleServiceQueryParamChange({
     target: { name, type: targetType, checked, value },
-  }: ChangeEvent<HTMLInputElement>) {
+  }: ChangeEvent<HTMLInputElement>): void {
     const outValue = targetType === 'checkbox' ? checked : value
     setQueryParams({ ...queryParams, [name]: outValue })
   }
 
   function handleBadgeOptionChange({
     target: { name, value },
-  }: ChangeEvent<HTMLInputElement>) {
+  }: ChangeEvent<HTMLInputElement>): void {
     setBadgeOptions({ ...badgeOptions, [name]: value })
   }
 
diff --git a/frontend/components/customizer/request-markup-button.tsx b/frontend/components/customizer/request-markup-button.tsx
index 8d9bbae482a19b9cd3d6d18c347e9823e209b2f0..5223062e970c644647d90d3d95529aad4782c16f 100644
--- a/frontend/components/customizer/request-markup-button.tsx
+++ b/frontend/components/customizer/request-markup-button.tsx
@@ -3,14 +3,17 @@ import styled from 'styled-components'
 import Select, { components } from 'react-select'
 import { MarkupFormat } from '../../lib/generate-image-markup'
 
-const ClickableControl = (props: any) => (
-  <components.Control
-    {...props}
-    innerProps={{
-      onMouseDown: props.selectProps.onControlMouseDown,
-    }}
-  />
-)
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function ClickableControl(props: any): JSX.Element {
+  return (
+    <components.Control
+      {...props}
+      innerProps={{
+        onMouseDown: props.selectProps.onControlMouseDown,
+      }}
+    />
+  )
+}
 
 interface Option {
   value: MarkupFormat
@@ -76,14 +79,14 @@ export default function GetMarkupButton({
 }: {
   onMarkupRequested: (markupFormat: MarkupFormat) => Promise<void>
   isDisabled: boolean
-}) {
+}): JSX.Element {
   // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/35572
   // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/28884#issuecomment-471341041
   const selectRef = useRef<Select<Option>>() as React.MutableRefObject<
     Select<Option>
   >
 
-  async function onControlMouseDown(event: MouseEvent) {
+  async function onControlMouseDown(event: MouseEvent): Promise<void> {
     if (onMarkupRequested) {
       await onMarkupRequested('link')
     }
diff --git a/frontend/components/development/logo-page.tsx b/frontend/components/development/logo-page.tsx
index 3a308f55572db84c37d55ccec49dc2336a8f5807..74ae904ed319e0bdb241d0a24b1a04a3cde21ca7 100644
--- a/frontend/components/development/logo-page.tsx
+++ b/frontend/components/development/logo-page.tsx
@@ -18,7 +18,7 @@ const StyledTable = styled.table`
   }
 `
 
-function NamedLogoTable({ logoNames }: { logoNames: string[] }) {
+function NamedLogoTable({ logoNames }: { logoNames: string[] }): JSX.Element {
   return (
     <StyledTable>
       <thead>
@@ -62,7 +62,7 @@ function NamedLogoTable({ logoNames }: { logoNames: string[] }) {
   )
 }
 
-export default function LogoPage() {
+export default function LogoPage(): JSX.Element {
   return (
     <div>
       <Meta />
diff --git a/frontend/components/donate.tsx b/frontend/components/donate.tsx
index 0434f801f31ad6c2cedf7ed7fba7c71cd493e244..e40122bcd6b2cd98b667a33ec68e96f7771dadf9 100644
--- a/frontend/components/donate.tsx
+++ b/frontend/components/donate.tsx
@@ -5,7 +5,7 @@ const Donate = styled.div`
   padding: 25px 50px;
 `
 
-export default function DonateBox() {
+export default function DonateBox(): JSX.Element {
   return (
     <Donate>
       Love Shields? Please consider{' '}
diff --git a/frontend/components/dynamic-badge-maker.tsx b/frontend/components/dynamic-badge-maker.tsx
index d252404271601fb56dc1c90990e6b98289076098..1082b334865d9f78228e8e4460054ea5b5724ffe 100644
--- a/frontend/components/dynamic-badge-maker.tsx
+++ b/frontend/components/dynamic-badge-maker.tsx
@@ -28,7 +28,7 @@ const inputs = [
 
 export default function DynamicBadgeMaker({
   baseUrl = document.location.href,
-}) {
+}): JSX.Element {
   const [values, setValues] = useState<State>({
     datatype: '',
     label: '',
@@ -44,14 +44,14 @@ export default function DynamicBadgeMaker({
 
   function onChange({
     target: { name, value },
-  }: ChangeEvent<HTMLInputElement | HTMLSelectElement>) {
+  }: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
     setValues({
       ...values,
       [name]: value,
     })
   }
 
-  function onSubmit(e: React.FormEvent) {
+  function onSubmit(e: React.FormEvent): void {
     e.preventDefault()
 
     const { datatype, label, dataUrl, query, color, prefix, suffix } = values
diff --git a/frontend/components/footer.tsx b/frontend/components/footer.tsx
index 3dca375f3a0fb89c6adf049f7fadce57e113e9f3..ba6555ed1bca84e247135a57ef95ec2d1ebdaa73 100644
--- a/frontend/components/footer.tsx
+++ b/frontend/components/footer.tsx
@@ -8,7 +8,7 @@ const SpacedA = styled.a`
   margin-right: 10px;
 `
 
-export default function Footer({ baseUrl }: { baseUrl: string }) {
+export default function Footer({ baseUrl }: { baseUrl: string }): JSX.Element {
   return (
     <section>
       <H2 id="like-this">Like This?</H2>
diff --git a/frontend/components/header.tsx b/frontend/components/header.tsx
index e1c3d5a2b72b256d745946f3d38681fae03fd05b..7862e282c28e8f70781dc428575d50e5fe860bb2 100644
--- a/frontend/components/header.tsx
+++ b/frontend/components/header.tsx
@@ -8,7 +8,7 @@ const Highlights = styled.p`
   font-style: italic;
 `
 
-export default function Header() {
+export default function Header(): JSX.Element {
   return (
     <section>
       <Link to="/">
diff --git a/frontend/components/main.tsx b/frontend/components/main.tsx
index 851b32d3e9147d0ce7d431f3a60013fce986c20a..b680154be33ae8f5f541fe1809de1bf562c1f3c3 100644
--- a/frontend/components/main.tsx
+++ b/frontend/components/main.tsx
@@ -38,7 +38,11 @@ interface PageContext {
   category?: Category
 }
 
-export default function Main({ pageContext }: { pageContext: PageContext }) {
+export default function Main({
+  pageContext,
+}: {
+  pageContext: PageContext
+}): JSX.Element {
   const [searchIsInProgress, setSearchIsInProgress] = useState(false)
   const [queryIsTooShort, setQueryIsTooShort] = useState(false)
   const [searchResults, setSearchResults] = useState<{
@@ -51,7 +55,7 @@ export default function Main({ pageContext }: { pageContext: PageContext }) {
   ] = useState(false)
   const searchTimeout = useRef(0)
 
-  function performSearch(query: string) {
+  function performSearch(query: string): void {
     setSearchIsInProgress(false)
 
     setQueryIsTooShort(query.length === 1)
@@ -67,7 +71,7 @@ export default function Main({ pageContext }: { pageContext: PageContext }) {
     }
   }
 
-  function searchQueryChanged(query: string) {
+  function searchQueryChanged(query: string): void {
     /*
     Add a small delay before showing search results
     so that we wait until the user has stopped typing
@@ -83,12 +87,15 @@ export default function Main({ pageContext }: { pageContext: PageContext }) {
     searchTimeout.current = window.setTimeout(() => performSearch(query), 500)
   }
 
-  function exampleClicked(example: RenderableExample, isSuggestion: boolean) {
+  function exampleClicked(
+    example: RenderableExample,
+    isSuggestion: boolean
+  ): void {
     setSelectedExample(example)
     setSelectedExampleIsSuggestion(isSuggestion)
   }
 
-  function dismissMarkupModal() {
+  function dismissMarkupModal(): void {
     setSelectedExample(undefined)
   }
 
@@ -98,7 +105,7 @@ export default function Main({ pageContext }: { pageContext: PageContext }) {
   }: {
     category: Category
     definitions: ServiceDefinition[]
-  }) {
+  }): JSX.Element {
     const flattened = definitions.reduce((accum, current) => {
       const { examples } = current
       return accum.concat(examples)
@@ -117,7 +124,7 @@ export default function Main({ pageContext }: { pageContext: PageContext }) {
     )
   }
 
-  function renderMain() {
+  function renderMain(): JSX.Element | JSX.Element[] {
     const { category } = pageContext
 
     if (searchIsInProgress) {
diff --git a/frontend/components/markup-modal/index.tsx b/frontend/components/markup-modal/index.tsx
index aee750274d83197188efa60f1a0b7eaf51a19b0f..238a8e644c79c9c370ce1de8e8e88ac2feb9d4b2 100644
--- a/frontend/components/markup-modal/index.tsx
+++ b/frontend/components/markup-modal/index.tsx
@@ -19,7 +19,7 @@ export function MarkupModal({
   isBadgeSuggestion: boolean
   baseUrl: string
   onRequestClose: () => void
-}) {
+}): JSX.Element {
   return (
     <Modal
       ariaHideApp={false}
diff --git a/frontend/components/markup-modal/markup-modal-content.tsx b/frontend/components/markup-modal/markup-modal-content.tsx
index 65e83a4399b611859df695e502dfecdb8ecf28e9..6247e088a28bb6b905a6d983a031144af960a767 100644
--- a/frontend/components/markup-modal/markup-modal-content.tsx
+++ b/frontend/components/markup-modal/markup-modal-content.tsx
@@ -22,7 +22,7 @@ export function MarkupModalContent({
   example: RenderableExample
   isBadgeSuggestion: boolean
   baseUrl: string
-}) {
+}): JSX.Element {
   let documentation: { __html: string } | undefined
   let link: string | undefined
   if (isBadgeSuggestion) {
diff --git a/frontend/components/meta.tsx b/frontend/components/meta.tsx
index d515be0fcf78c0b470da7ce5f9a6833017b23436..f8228730fb8cfd7a6527c89613c5022f3a485522 100644
--- a/frontend/components/meta.tsx
+++ b/frontend/components/meta.tsx
@@ -7,7 +7,7 @@ const description = `We serve fast and scalable informational images as badges
 for GitHub, Travis CI, Jenkins, WordPress and many more services. Use them to
 track the state of your projects, or for promotional purposes.`
 
-export default function Meta() {
+export default function Meta(): JSX.Element {
   return (
     <Helmet>
       <title>
diff --git a/frontend/components/snippet.tsx b/frontend/components/snippet.tsx
index f3884bef97bf572c01557a596857a49a8c5aaa81..95b4cee01faeac7fe9409ade485883adcdc9d021 100644
--- a/frontend/components/snippet.tsx
+++ b/frontend/components/snippet.tsx
@@ -50,7 +50,7 @@ export function Snippet({
   snippet: string
   truncate?: boolean
   fontSize?: string
-}) {
+}): JSX.Element {
   return (
     <CodeContainer truncate={truncate}>
       <ClickToSelect>
diff --git a/frontend/components/static-badge-maker.tsx b/frontend/components/static-badge-maker.tsx
index 4801b7bc8df58ad4a5019ee5cad18a56a533d84c..05388dc2a0666c74159f689327efda25b1e839c4 100644
--- a/frontend/components/static-badge-maker.tsx
+++ b/frontend/components/static-badge-maker.tsx
@@ -5,7 +5,9 @@ import { InlineInput } from './common'
 type StateKey = 'label' | 'message' | 'color'
 type State = Record<StateKey, string>
 
-export default function StaticBadgeMaker({ baseUrl = document.location.href }) {
+export default function StaticBadgeMaker({
+  baseUrl = document.location.href,
+}): JSX.Element {
   const [values, setValues] = useState<State>({
     label: '',
     message: '',
@@ -16,14 +18,14 @@ export default function StaticBadgeMaker({ baseUrl = document.location.href }) {
 
   function onChange({
     target: { name, value },
-  }: ChangeEvent<HTMLInputElement | HTMLSelectElement>) {
+  }: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void {
     setValues({
       ...values,
       [name]: value,
     })
   }
 
-  function onSubmit(e: React.FormEvent) {
+  function onSubmit(e: React.FormEvent): void {
     e.preventDefault()
 
     const { label, message, color } = values
diff --git a/frontend/components/suggestion-and-search.tsx b/frontend/components/suggestion-and-search.tsx
index dec115a8fab09f290421ad4e574e3f573c62486f..9328ba7ab533f3ca5d6d97e17b605241c2aa8922 100644
--- a/frontend/components/suggestion-and-search.tsx
+++ b/frontend/components/suggestion-and-search.tsx
@@ -32,7 +32,7 @@ export default function SuggestionAndSearch({
   queryChanged: (query: string) => void
   onBadgeClick: (example: RenderableExample, isSuggestion: boolean) => void
   baseUrl: string
-}) {
+}): JSX.Element {
   const queryChangedDebounced = useRef(
     debounce(queryChanged, 50, { leading: true })
   )
@@ -43,7 +43,7 @@ export default function SuggestionAndSearch({
 
   function onQueryChanged({
     target: { value: query },
-  }: ChangeEvent<HTMLInputElement>) {
+  }: ChangeEvent<HTMLInputElement>): void {
     const isUrl = query.startsWith('https://') || query.startsWith('http://')
     setIsUrl(isUrl)
     setProjectUrl(isUrl ? query : undefined)
@@ -51,7 +51,7 @@ export default function SuggestionAndSearch({
     queryChangedDebounced.current(query)
   }
 
-  async function getSuggestions() {
+  async function getSuggestions(): Promise<void> {
     if (!projectUrl) {
       setSuggestions([])
       return
@@ -77,7 +77,7 @@ export default function SuggestionAndSearch({
     setSuggestions(suggestions)
   }
 
-  function renderSuggestions() {
+  function renderSuggestions(): JSX.Element | null {
     if (suggestions.length === 0) {
       return null
     }
diff --git a/frontend/components/usage.tsx b/frontend/components/usage.tsx
index cde5bb723346086c796285c545ed800a321e1f43..00669451af3d23e02cb69dc9ba80d4d6712a2198 100644
--- a/frontend/components/usage.tsx
+++ b/frontend/components/usage.tsx
@@ -44,7 +44,7 @@ function QueryParam({
 }: {
   snippet: string
   documentation: JSX.Element | JSX.Element[]
-}) {
+}): JSX.Element {
   return (
     <tr>
       <QueryParamSyntax>
@@ -61,7 +61,7 @@ function EscapingConversion({
 }: {
   lhs: JSX.Element
   rhs: JSX.Element
-}) {
+}): JSX.Element {
   return (
     <tr>
       <Lhs>{lhs}</Lhs>
@@ -77,7 +77,7 @@ function ColorExamples({
 }: {
   baseUrl: string
   colors: string[]
-}) {
+}): JSX.Element {
   return (
     <span>
       {colors.map((color, i) => (
@@ -91,7 +91,7 @@ function ColorExamples({
   )
 }
 
-function StyleExamples({ baseUrl }: { baseUrl: string }) {
+function StyleExamples({ baseUrl }: { baseUrl: string }): JSX.Element {
   return (
     <QueryParamTable>
       <tbody>
@@ -118,7 +118,7 @@ function StyleExamples({ baseUrl }: { baseUrl: string }) {
   )
 }
 
-function NamedLogos() {
+function NamedLogos(): JSX.Element {
   const renderLogo = (logo: string): JSX.Element => (
     <LogoName key={logo}>{logo}</LogoName>
   )
@@ -132,7 +132,7 @@ function NamedLogos() {
   return <>{result}</>
 }
 
-function StaticBadgeEscapingRules() {
+function StaticBadgeEscapingRules(): JSX.Element {
   return (
     <EscapingRuleTable>
       <tbody>
@@ -180,7 +180,7 @@ function StaticBadgeEscapingRules() {
   )
 }
 
-export default function Usage({ baseUrl }: { baseUrl: string }) {
+export default function Usage({ baseUrl }: { baseUrl: string }): JSX.Element {
   return (
     <section>
       <H2 id="your-badge">Your Badge</H2>
diff --git a/frontend/lib/generate-image-markup.ts b/frontend/lib/generate-image-markup.ts
index 4190f67adadff7051e3d10ac2ae98d5a02ba9598..2f7fcd1deb05d85efb64d7a455aabb028918a5fe 100644
--- a/frontend/lib/generate-image-markup.ts
+++ b/frontend/lib/generate-image-markup.ts
@@ -1,8 +1,8 @@
-export function bareLink(badgeUrl: string, link?: string, title = '') {
+export function bareLink(badgeUrl: string, link?: string, title = ''): string {
   return badgeUrl
 }
 
-export function html(badgeUrl: string, link?: string, title?: string) {
+export function html(badgeUrl: string, link?: string, title?: string): string {
   // To be more robust, this should escape the title.
   const alt = title ? ` alt="${title}"` : ''
   const img = `<img${alt} src="${badgeUrl}">`
@@ -13,7 +13,11 @@ export function html(badgeUrl: string, link?: string, title?: string) {
   }
 }
 
-export function markdown(badgeUrl: string, link?: string, title?: string) {
+export function markdown(
+  badgeUrl: string,
+  link?: string,
+  title?: string
+): string {
   const withoutLink = `![${title || ''}](${badgeUrl})`
   if (link) {
     return `[${withoutLink}](${link})`
@@ -26,7 +30,7 @@ export function reStructuredText(
   badgeUrl: string,
   link?: string,
   title?: string
-) {
+): string {
   let result = `.. image:: ${badgeUrl}`
   if (title) {
     result += `   :alt: ${title}`
@@ -37,7 +41,7 @@ export function reStructuredText(
   return result
 }
 
-function quoteAsciiDocAttribute(attr: string | null) {
+function quoteAsciiDocAttribute(attr: string | null): string {
   if (attr == null) {
     return 'None'
   } else {
@@ -61,7 +65,7 @@ function mapValues(
 export function renderAsciiDocAttributes(
   positional: string[],
   named: { [k: string]: string | null }
-) {
+): string {
   // http://asciidoc.org/userguide.html#X21
   const needsQuoting =
     positional.some(attr => attr && attr.includes(',')) ||
@@ -83,7 +87,11 @@ export function renderAsciiDocAttributes(
   }
 }
 
-export function asciiDoc(badgeUrl: string, link?: string, title?: string) {
+export function asciiDoc(
+  badgeUrl: string,
+  link?: string,
+  title?: string
+): string {
   const positional = title ? [title] : []
   const named = link ? { link } : ({} as { [k: string]: string })
   const attrs = renderAsciiDocAttributes(positional, named)
@@ -102,7 +110,7 @@ export function generateMarkup({
   link?: string
   title?: string
   markupFormat: MarkupFormat
-}) {
+}): string {
   const generatorFn = {
     markdown,
     rst: reStructuredText,
diff --git a/frontend/lib/redirect-legacy-routes.ts b/frontend/lib/redirect-legacy-routes.ts
index aebf7269c9d013d32c38c32a506d705717fa30e6..57fb534b86e32c3b29ece7c58256a55091f7a20c 100644
--- a/frontend/lib/redirect-legacy-routes.ts
+++ b/frontend/lib/redirect-legacy-routes.ts
@@ -1,6 +1,6 @@
 import { navigate } from 'gatsby'
 
-export default function redirectLegacyRoutes() {
+export default function redirectLegacyRoutes(): void {
   const { hash } = window.location
   if (hash && hash.startsWith('#/examples/')) {
     const category = hash.replace('#/examples/', '')
diff --git a/frontend/lib/service-definitions/index.ts b/frontend/lib/service-definitions/index.ts
index a962c74910e8004f9e86ee062a368ddfa78ddf51..e358139614a91e6ca61d497cf8bfc7c0a1e9ecc8 100644
--- a/frontend/lib/service-definitions/index.ts
+++ b/frontend/lib/service-definitions/index.ts
@@ -53,13 +53,15 @@ export interface ServiceDefinition {
 export const services = definitions.services as ServiceDefinition[]
 export const categories = definitions.categories as Category[]
 
-export function findCategory(category: string) {
+export function findCategory(category: string): Category | undefined {
   return categories.find(({ id }) => id === category)
 }
 
 const byCategory = groupBy(services, 'category')
-export function getDefinitionsForCategory(category: string) {
-  return byCategory[category]
+export function getDefinitionsForCategory(
+  category: string
+): ServiceDefinition[] {
+  return byCategory[category] || []
 }
 
 export interface Suggestion {
diff --git a/frontend/lib/service-definitions/service-definition-set-helper.spec.ts b/frontend/lib/service-definitions/service-definition-set-helper.spec.ts
index 4a053a855b1170cc042f4e494da3442a0c0b5fb2..d6ade05506feec4bedd06c5f95147b656930f513 100644
--- a/frontend/lib/service-definitions/service-definition-set-helper.spec.ts
+++ b/frontend/lib/service-definitions/service-definition-set-helper.spec.ts
@@ -3,10 +3,12 @@ import { predicateFromQuery } from './service-definition-set-helper'
 import { Example } from '.'
 
 describe('Badge example functions', function() {
-  const exampleMatchesQuery = (
+  function exampleMatchesQuery(
     { examples }: { examples: Example[] },
     query: string
-  ) => predicateFromQuery(query)({ examples })
+  ): boolean {
+    return predicateFromQuery(query)({ examples })
+  }
 
   test(exampleMatchesQuery, () => {
     forCases([given({ examples: [{ title: 'node version' }] }, 'npm')]).expect(
diff --git a/frontend/lib/service-definitions/service-definition-set-helper.ts b/frontend/lib/service-definitions/service-definition-set-helper.ts
index 2e713d4178dd0f039d6e64984e810f634ed0b01c..e4fcd27b076b1ac579ca31dd992d8fca9b18b529 100644
--- a/frontend/lib/service-definitions/service-definition-set-helper.ts
+++ b/frontend/lib/service-definitions/service-definition-set-helper.ts
@@ -1,13 +1,15 @@
 import escapeStringRegexp from 'escape-string-regexp'
 import { Example, ServiceDefinition } from '.'
 
-export function exampleMatchesRegex(example: Example, regex: RegExp) {
+export function exampleMatchesRegex(example: Example, regex: RegExp): boolean {
   const { title, keywords } = example
   const haystack = [title].concat(keywords).join(' ')
   return regex.test(haystack)
 }
 
-export function predicateFromQuery(query: string) {
+export function predicateFromQuery(
+  query: string
+): ({ examples }: { examples: Example[] }) => boolean {
   const escaped = escapeStringRegexp(query)
   const regex = new RegExp(escaped, 'i') // Case-insensitive.
   return ({ examples }: { examples: Example[] }) =>
@@ -21,30 +23,32 @@ export default class ServiceDefinitionSetHelper {
     this.definitionData = definitionData
   }
 
-  public static create(definitionData: ServiceDefinition[]) {
+  public static create(
+    definitionData: ServiceDefinition[]
+  ): ServiceDefinitionSetHelper {
     return new ServiceDefinitionSetHelper(definitionData)
   }
 
-  public getCategory(wantedCategory: string) {
+  public getCategory(wantedCategory: string): ServiceDefinitionSetHelper {
     return ServiceDefinitionSetHelper.create(
       this.definitionData.filter(({ category }) => category === wantedCategory)
     )
   }
 
-  public search(query: string) {
+  public search(query: string): ServiceDefinitionSetHelper {
     const predicate = predicateFromQuery(query)
     return ServiceDefinitionSetHelper.create(
       this.definitionData.filter(predicate)
     )
   }
 
-  public notDeprecated() {
+  public notDeprecated(): ServiceDefinitionSetHelper {
     return ServiceDefinitionSetHelper.create(
       this.definitionData.filter(({ isDeprecated }) => !isDeprecated)
     )
   }
 
-  public toArray() {
+  public toArray(): ServiceDefinition[] {
     return this.definitionData
   }
 }
diff --git a/frontend/pages/endpoint.tsx b/frontend/pages/endpoint.tsx
index 98813bcc85744f3fd46c398e65413761505776f8..6c5194137a0322ff499d12c51c5449f41e475be6 100644
--- a/frontend/pages/endpoint.tsx
+++ b/frontend/pages/endpoint.tsx
@@ -1,5 +1,4 @@
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled, { css } from 'styled-components'
 import { staticBadgeUrl } from '../../core/badge-urls/make-badge-url'
 import { baseUrl } from '../constants'
@@ -44,11 +43,11 @@ const JsonExampleBlock = styled.code<JsonExampleBlockProps>`
   white-space: pre;
 `
 
-const JsonExample = ({ data }: { [k: string]: any }) => (
-  <JsonExampleBlock>{JSON.stringify(data, undefined, 2)}</JsonExampleBlock>
-)
-JsonExample.propTypes = {
-  data: PropTypes.object.isRequired,
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function JsonExample({ data }: { [k: string]: any }): JSX.Element {
+  return (
+    <JsonExampleBlock>{JSON.stringify(data, undefined, 2)}</JsonExampleBlock>
+  )
 }
 
 const Schema = styled.dl`
@@ -89,161 +88,166 @@ const Schema = styled.dl`
   }
 `
 
-const EndpointPage = () => (
-  <MainContainer>
-    <GlobalStyle />
-    <Meta />
-    <Header />
-    <H3>Endpoint</H3>
-    <Snippet snippet={`${baseUrl}/endpoint?url=...&style=...`} />
-    <p>Endpoint response:</p>
-    <JsonExample
-      data={{
-        schemaVersion: 1,
-        label: 'hello',
-        message: 'sweet world',
-        color: 'orange',
-      }}
-    />
-    <p>Shields response:</p>
-    <Badge
-      alt="hello | sweet world"
-      src={staticBadgeUrl({
-        baseUrl,
-        label: 'hello',
-        message: 'sweet world',
-        color: 'orange',
-      })}
-    />
-    <Explanation>
-      <p>
-        Developers rely on Shields for visual consistency and powerful
-        customization options. As a service provider or data provider, you can
-        use the endpoint badge to provide content while giving users the full
-        power of Shields' badge customization.
-      </p>
-      <p>
-        Using the endpoint badge, you can provide content for a badge through a
-        JSON endpoint. The content can be prerendered, or generated on the fly.
-        To strike a balance between responsiveness and bandwidth utilization on
-        one hand, and freshness on the other, cache behavior is configurable,
-        subject to the Shields minimum. The endpoint URL is provided to Shields
-        through the query string. Shields fetches it and formats the badge.
-      </p>
-      <p>
-        The endpoint badge is a better alternative than redirecting to the
-        static badge enpoint or generating SVG on your server:
-      </p>
-      <ol>
-        <li>
-          <a href="https://en.wikipedia.org/wiki/Separation_of_content_and_presentation">
-            Content and presentation are separate.
-          </a>{' '}
-          The service provider authors the badge, and Shields takes input from
-          the user to format it. As a service provider you author the badge but
-          don't have to concern yourself with styling. You don't even have to
-          pass the formatting options through to Shields.
-        </li>
-        <li>
-          Badge formatting is always 100% up to date. There's no need to track
-          updates to the npm package, badge templates, or options.
-        </li>
-        <li>
-          A JSON response is easy to implement; easier than an HTTP redirect. It
-          is trivial in almost any framework, and is more compatible with
-          hosting environments such as{' '}
-          <a href="https://runkit.com/docs/endpoint">RunKit endpoints</a>.
-        </li>
-        <li>
-          As a service provider you can rely on the Shields CDN. There's no need
-          to study the HTTP headers. Adjusting cache behavior is as simple as
-          setting a property in the JSON response.
-        </li>
-      </ol>
-    </Explanation>
-    <h4>Schema</h4>
-    <Explanation>
-      <p>
-        Breaking changes to the schema will trigger an increment to the
-        `schemaVersion`.
-      </p>
-    </Explanation>
-    <Schema>
-      <dt>schemaVersion</dt>
-      <dd>
-        Required. Always the number <code>1</code>.
-      </dd>
-      <dt>label</dt>
-      <dd>
-        Required. The left text, or the empty string to omit the left side of
-        the badge. This can be overridden by the query string.
-      </dd>
-      <dt>message</dt>
-      <dd>Required. Can't be empty. The right text.</dd>
-      <dt>color</dt>
-      <dd>
-        Default: <code>lightgrey</code>. The right color. Supports the eight
-        named colors above, as well as hex, rgb, rgba, hsl, hsla and css named
-        colors. This can be overridden by the query string.
-      </dd>
-      <dt>labelColor</dt>
-      <dd>
-        Default: <code>grey</code>. The left color. This can be overridden by
-        the query string.
-      </dd>
-      <dt>isError</dt>
-      <dd>
-        Default: <code>false</code>. <code>true</code> to treat this as an error
-        badge. This prevents the user from overriding the color. In the future
-        it may affect cache behavior.
-      </dd>
-      <dt>namedLogo</dt>
-      <dd>
-        Default: none. One of the named logos supported by Shields or {}
-        <a href="https://simpleicons.org/">simple-icons</a>. Can be overridden
-        by the query string.
-      </dd>
-      <dt>logoSvg</dt>
-      <dd>Default: none. An SVG string containing a custom logo.</dd>
-      <dt>logoColor</dt>
-      <dd>
-        Default: none. Same meaning as the query string. Can be overridden by
-        the query string.
-      </dd>
-      <dt>logoWidth</dt>
-      <dd>
-        Default: none. Same meaning as the query string. Can be overridden by
-        the query string.
-      </dd>
-      <dt>logoPosition</dt>
-      <dd>
-        Default: none. Same meaning as the query string. Can be overridden by
-        the query string.
-      </dd>
-      <dt>style</dt>
-      <dd>
-        Default: <code>flat</code>. The default template to use. Can be
-        overridden by the query string.
-      </dd>
-      <dt>cacheSeconds</dt>
-      <dd>
-        Default: <code>300</code>, min <code>300</code>. Set the HTTP cache
-        lifetime in seconds, which should be respected by the Shields' CDN and
-        downstream users. Values below 300 will be ignored. This lets you tune
-        performance and traffic vs. responsiveness. The value you specify can be
-        overridden by the user via the query string, but only to a longer value.
-      </dd>
-    </Schema>
-    <h4>Customize and test</h4>
-    <Customizer
-      baseUrl={baseUrl}
-      exampleNamedParams={{}}
-      exampleQueryParams={{ url: 'https://shields.redsparr0w.com/2473/monday' }}
-      isPrefilled={false}
-      pattern="/endpoint"
-      title="Custom badge"
-    />
-    <Footer baseUrl={baseUrl} />
-  </MainContainer>
-)
-export default EndpointPage
+export default function EndpointPage(): JSX.Element {
+  return (
+    <MainContainer>
+      <GlobalStyle />
+      <Meta />
+      <Header />
+      <H3>Endpoint</H3>
+      <Snippet snippet={`${baseUrl}/endpoint?url=...&style=...`} />
+      <p>Endpoint response:</p>
+      <JsonExample
+        data={{
+          schemaVersion: 1,
+          label: 'hello',
+          message: 'sweet world',
+          color: 'orange',
+        }}
+      />
+      <p>Shields response:</p>
+      <Badge
+        alt="hello | sweet world"
+        src={staticBadgeUrl({
+          baseUrl,
+          label: 'hello',
+          message: 'sweet world',
+          color: 'orange',
+        })}
+      />
+      <Explanation>
+        <p>
+          Developers rely on Shields for visual consistency and powerful
+          customization options. As a service provider or data provider, you can
+          use the endpoint badge to provide content while giving users the full
+          power of Shields' badge customization.
+        </p>
+        <p>
+          Using the endpoint badge, you can provide content for a badge through
+          a JSON endpoint. The content can be prerendered, or generated on the
+          fly. To strike a balance between responsiveness and bandwidth
+          utilization on one hand, and freshness on the other, cache behavior is
+          configurable, subject to the Shields minimum. The endpoint URL is
+          provided to Shields through the query string. Shields fetches it and
+          formats the badge.
+        </p>
+        <p>
+          The endpoint badge is a better alternative than redirecting to the
+          static badge enpoint or generating SVG on your server:
+        </p>
+        <ol>
+          <li>
+            <a href="https://en.wikipedia.org/wiki/Separation_of_content_and_presentation">
+              Content and presentation are separate.
+            </a>{' '}
+            The service provider authors the badge, and Shields takes input from
+            the user to format it. As a service provider you author the badge
+            but don't have to concern yourself with styling. You don't even have
+            to pass the formatting options through to Shields.
+          </li>
+          <li>
+            Badge formatting is always 100% up to date. There's no need to track
+            updates to the npm package, badge templates, or options.
+          </li>
+          <li>
+            A JSON response is easy to implement; easier than an HTTP redirect.
+            It is trivial in almost any framework, and is more compatible with
+            hosting environments such as{' '}
+            <a href="https://runkit.com/docs/endpoint">RunKit endpoints</a>.
+          </li>
+          <li>
+            As a service provider you can rely on the Shields CDN. There's no
+            need to study the HTTP headers. Adjusting cache behavior is as
+            simple as setting a property in the JSON response.
+          </li>
+        </ol>
+      </Explanation>
+      <h4>Schema</h4>
+      <Explanation>
+        <p>
+          Breaking changes to the schema will trigger an increment to the
+          `schemaVersion`.
+        </p>
+      </Explanation>
+      <Schema>
+        <dt>schemaVersion</dt>
+        <dd>
+          Required. Always the number <code>1</code>.
+        </dd>
+        <dt>label</dt>
+        <dd>
+          Required. The left text, or the empty string to omit the left side of
+          the badge. This can be overridden by the query string.
+        </dd>
+        <dt>message</dt>
+        <dd>Required. Can't be empty. The right text.</dd>
+        <dt>color</dt>
+        <dd>
+          Default: <code>lightgrey</code>. The right color. Supports the eight
+          named colors above, as well as hex, rgb, rgba, hsl, hsla and css named
+          colors. This can be overridden by the query string.
+        </dd>
+        <dt>labelColor</dt>
+        <dd>
+          Default: <code>grey</code>. The left color. This can be overridden by
+          the query string.
+        </dd>
+        <dt>isError</dt>
+        <dd>
+          Default: <code>false</code>. <code>true</code> to treat this as an
+          error badge. This prevents the user from overriding the color. In the
+          future it may affect cache behavior.
+        </dd>
+        <dt>namedLogo</dt>
+        <dd>
+          Default: none. One of the named logos supported by Shields or {}
+          <a href="https://simpleicons.org/">simple-icons</a>. Can be overridden
+          by the query string.
+        </dd>
+        <dt>logoSvg</dt>
+        <dd>Default: none. An SVG string containing a custom logo.</dd>
+        <dt>logoColor</dt>
+        <dd>
+          Default: none. Same meaning as the query string. Can be overridden by
+          the query string.
+        </dd>
+        <dt>logoWidth</dt>
+        <dd>
+          Default: none. Same meaning as the query string. Can be overridden by
+          the query string.
+        </dd>
+        <dt>logoPosition</dt>
+        <dd>
+          Default: none. Same meaning as the query string. Can be overridden by
+          the query string.
+        </dd>
+        <dt>style</dt>
+        <dd>
+          Default: <code>flat</code>. The default template to use. Can be
+          overridden by the query string.
+        </dd>
+        <dt>cacheSeconds</dt>
+        <dd>
+          Default: <code>300</code>, min <code>300</code>. Set the HTTP cache
+          lifetime in seconds, which should be respected by the Shields' CDN and
+          downstream users. Values below 300 will be ignored. This lets you tune
+          performance and traffic vs. responsiveness. The value you specify can
+          be overridden by the user via the query string, but only to a longer
+          value.
+        </dd>
+      </Schema>
+      <h4>Customize and test</h4>
+      <Customizer
+        baseUrl={baseUrl}
+        exampleNamedParams={{}}
+        exampleQueryParams={{
+          url: 'https://shields.redsparr0w.com/2473/monday',
+        }}
+        isPrefilled={false}
+        pattern="/endpoint"
+        title="Custom badge"
+      />
+      <Footer baseUrl={baseUrl} />
+    </MainContainer>
+  )
+}
diff --git a/lib/load-simple-icons.spec.js b/lib/load-simple-icons.spec.js
index 3b420449991046f1f98e7b59ef876f9a27115bda..985d198eb5a321a1abd10a3d6cc8fa653e26c66c 100644
--- a/lib/load-simple-icons.spec.js
+++ b/lib/load-simple-icons.spec.js
@@ -10,7 +10,7 @@ describe('loadSimpleIcons', function() {
   })
 
   it('prepares three color themes', function() {
-    expect(simpleIcons['sentry'].base64).to.have.all.keys(
+    expect(simpleIcons.sentry.base64).to.have.all.keys(
       'default',
       'light',
       'dark'
diff --git a/lib/logos.spec.js b/lib/logos.spec.js
index 800d22fd667b3893bd0b3b6f64c41af697525401..f0c7bac535859094d77b2e0d2a01e64744380c98 100644
--- a/lib/logos.spec.js
+++ b/lib/logos.spec.js
@@ -19,7 +19,7 @@ describe('Logo helpers', function() {
   })
 
   test(isDataUrl, () => {
-    //valid input
+    // valid input
     given('data:image/svg+xml;base64,PHN2ZyB4bWxu').expect(true)
 
     // invalid inputs
diff --git a/package-lock.json b/package-lock.json
index 3f021eeb5f764d19dffd64750692274c3ffb2ba0..b456f4d34a2646c68f470bea7f130239a63ea4c9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3760,106 +3760,97 @@
       "dev": true
     },
     "@typescript-eslint/eslint-plugin": {
-      "version": "1.13.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.13.0.tgz",
-      "integrity": "sha512-WQHCozMnuNADiqMtsNzp96FNox5sOVpU8Xt4meaT4em8lOG1SrOv92/mUbEHQVh90sldKSfcOc/I0FOb/14G1g==",
+      "version": "2.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.21.0.tgz",
+      "integrity": "sha512-b5jjjDMxzcjh/Sbjuo7WyhrQmVJg0WipTHQgXh5Xwx10uYm6nPWqN1WGOsaNq4HR3Zh4wUx4IRQdDkCHwyewyw==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/experimental-utils": "1.13.0",
-        "eslint-utils": "^1.3.1",
+        "@typescript-eslint/experimental-utils": "2.21.0",
+        "eslint-utils": "^1.4.3",
         "functional-red-black-tree": "^1.0.1",
-        "regexpp": "^2.0.1",
-        "tsutils": "^3.7.0"
+        "regexpp": "^3.0.0",
+        "tsutils": "^3.17.1"
+      },
+      "dependencies": {
+        "regexpp": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz",
+          "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==",
+          "dev": true
+        }
       }
     },
     "@typescript-eslint/experimental-utils": {
-      "version": "1.13.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz",
-      "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==",
+      "version": "2.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.21.0.tgz",
+      "integrity": "sha512-olKw9JP/XUkav4lq0I7S1mhGgONJF9rHNhKFn9wJlpfRVjNo3PPjSvybxEldvCXnvD+WAshSzqH5cEjPp9CsBA==",
       "dev": true,
       "requires": {
         "@types/json-schema": "^7.0.3",
-        "@typescript-eslint/typescript-estree": "1.13.0",
-        "eslint-scope": "^4.0.0"
-      },
-      "dependencies": {
-        "eslint-scope": {
-          "version": "4.0.3",
-          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
-          "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
-          "dev": true,
-          "requires": {
-            "esrecurse": "^4.1.0",
-            "estraverse": "^4.1.1"
-          }
-        }
+        "@typescript-eslint/typescript-estree": "2.21.0",
+        "eslint-scope": "^5.0.0"
       }
     },
     "@typescript-eslint/parser": {
-      "version": "1.13.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.13.0.tgz",
-      "integrity": "sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==",
+      "version": "2.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.21.0.tgz",
+      "integrity": "sha512-VrmbdrrrvvI6cPPOG7uOgGUFXNYTiSbnRq8ZMyuGa4+qmXJXVLEEz78hKuqupvkpwJQNk1Ucz1TenrRP90gmBg==",
       "dev": true,
       "requires": {
         "@types/eslint-visitor-keys": "^1.0.0",
-        "@typescript-eslint/experimental-utils": "1.13.0",
-        "@typescript-eslint/typescript-estree": "1.13.0",
-        "eslint-visitor-keys": "^1.0.0"
+        "@typescript-eslint/experimental-utils": "2.21.0",
+        "@typescript-eslint/typescript-estree": "2.21.0",
+        "eslint-visitor-keys": "^1.1.0"
       },
       "dependencies": {
-        "@typescript-eslint/experimental-utils": {
-          "version": "1.13.0",
-          "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz",
-          "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==",
-          "dev": true,
-          "requires": {
-            "@types/json-schema": "^7.0.3",
-            "@typescript-eslint/typescript-estree": "1.13.0",
-            "eslint-scope": "^4.0.0"
-          }
-        },
-        "@typescript-eslint/typescript-estree": {
-          "version": "1.13.0",
-          "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz",
-          "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==",
-          "dev": true,
-          "requires": {
-            "lodash.unescape": "4.0.1",
-            "semver": "5.5.0"
-          }
-        },
-        "eslint-scope": {
-          "version": "4.0.3",
-          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
-          "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
-          "dev": true,
-          "requires": {
-            "esrecurse": "^4.1.0",
-            "estraverse": "^4.1.1"
-          }
-        },
-        "semver": {
-          "version": "5.5.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
-          "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
+        "eslint-visitor-keys": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
+          "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
           "dev": true
         }
       }
     },
     "@typescript-eslint/typescript-estree": {
-      "version": "1.13.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz",
-      "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==",
+      "version": "2.21.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.21.0.tgz",
+      "integrity": "sha512-NC/nogZNb9IK2MEFQqyDBAciOT8Lp8O3KgAfvHx2Skx6WBo+KmDqlU3R9KxHONaijfTIKtojRe3SZQyMjr3wBw==",
       "dev": true,
       "requires": {
-        "lodash.unescape": "4.0.1",
-        "semver": "5.5.0"
+        "debug": "^4.1.1",
+        "eslint-visitor-keys": "^1.1.0",
+        "glob": "^7.1.6",
+        "is-glob": "^4.0.1",
+        "lodash": "^4.17.15",
+        "semver": "^6.3.0",
+        "tsutils": "^3.17.1"
       },
       "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "eslint-visitor-keys": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
+          "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
+          "dev": true
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        },
         "semver": {
-          "version": "5.5.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
-          "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
           "dev": true
         }
       }
@@ -4098,9 +4089,9 @@
       "dev": true
     },
     "acorn-jsx": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz",
-      "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==",
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
+      "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
       "dev": true
     },
     "address": {
@@ -10324,53 +10315,54 @@
       }
     },
     "eslint": {
-      "version": "5.16.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz",
-      "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==",
+      "version": "6.8.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz",
+      "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==",
       "dev": true,
       "requires": {
         "@babel/code-frame": "^7.0.0",
-        "ajv": "^6.9.1",
+        "ajv": "^6.10.0",
         "chalk": "^2.1.0",
         "cross-spawn": "^6.0.5",
         "debug": "^4.0.1",
         "doctrine": "^3.0.0",
-        "eslint-scope": "^4.0.3",
-        "eslint-utils": "^1.3.1",
-        "eslint-visitor-keys": "^1.0.0",
-        "espree": "^5.0.1",
+        "eslint-scope": "^5.0.0",
+        "eslint-utils": "^1.4.3",
+        "eslint-visitor-keys": "^1.1.0",
+        "espree": "^6.1.2",
         "esquery": "^1.0.1",
         "esutils": "^2.0.2",
         "file-entry-cache": "^5.0.1",
         "functional-red-black-tree": "^1.0.1",
-        "glob": "^7.1.2",
-        "globals": "^11.7.0",
+        "glob-parent": "^5.0.0",
+        "globals": "^12.1.0",
         "ignore": "^4.0.6",
         "import-fresh": "^3.0.0",
         "imurmurhash": "^0.1.4",
-        "inquirer": "^6.2.2",
-        "js-yaml": "^3.13.0",
+        "inquirer": "^7.0.0",
+        "is-glob": "^4.0.0",
+        "js-yaml": "^3.13.1",
         "json-stable-stringify-without-jsonify": "^1.0.1",
         "levn": "^0.3.0",
-        "lodash": "^4.17.11",
+        "lodash": "^4.17.14",
         "minimatch": "^3.0.4",
         "mkdirp": "^0.5.1",
         "natural-compare": "^1.4.0",
-        "optionator": "^0.8.2",
-        "path-is-inside": "^1.0.2",
+        "optionator": "^0.8.3",
         "progress": "^2.0.0",
         "regexpp": "^2.0.1",
-        "semver": "^5.5.1",
-        "strip-ansi": "^4.0.0",
-        "strip-json-comments": "^2.0.1",
+        "semver": "^6.1.2",
+        "strip-ansi": "^5.2.0",
+        "strip-json-comments": "^3.0.1",
         "table": "^5.2.3",
-        "text-table": "^0.2.0"
+        "text-table": "^0.2.0",
+        "v8-compile-cache": "^2.0.3"
       },
       "dependencies": {
         "ansi-regex": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
           "dev": true
         },
         "ansi-styles": {
@@ -10404,6 +10396,14 @@
             "semver": "^5.5.0",
             "shebang-command": "^1.2.0",
             "which": "^1.2.9"
+          },
+          "dependencies": {
+            "semver": {
+              "version": "5.7.1",
+              "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+              "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+              "dev": true
+            }
           }
         },
         "debug": {
@@ -10430,40 +10430,38 @@
           "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
           "dev": true
         },
-        "eslint-scope": {
-          "version": "4.0.3",
-          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
-          "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+        "eslint-visitor-keys": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
+          "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
+          "dev": true
+        },
+        "glob-parent": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
+          "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
           "dev": true,
           "requires": {
-            "esrecurse": "^4.1.0",
-            "estraverse": "^4.1.1"
+            "is-glob": "^4.0.1"
           }
         },
-        "esprima": {
-          "version": "4.0.1",
-          "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
-          "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
-          "dev": true
-        },
-        "import-fresh": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz",
-          "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==",
+        "globals": {
+          "version": "12.3.0",
+          "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz",
+          "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==",
           "dev": true,
           "requires": {
-            "parent-module": "^1.0.0",
-            "resolve-from": "^4.0.0"
+            "type-fest": "^0.8.1"
           }
         },
-        "js-yaml": {
-          "version": "3.13.1",
-          "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
-          "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+        "import-fresh": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
+          "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
           "dev": true,
           "requires": {
-            "argparse": "^1.0.7",
-            "esprima": "^4.0.0"
+            "parent-module": "^1.0.0",
+            "resolve-from": "^4.0.0"
           }
         },
         "minimist": {
@@ -10482,25 +10480,57 @@
           }
         },
         "ms": {
-          "version": "2.1.1",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
-          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
           "dev": true
         },
+        "optionator": {
+          "version": "0.8.3",
+          "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+          "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+          "dev": true,
+          "requires": {
+            "deep-is": "~0.1.3",
+            "fast-levenshtein": "~2.0.6",
+            "levn": "~0.3.0",
+            "prelude-ls": "~1.1.2",
+            "type-check": "~0.3.2",
+            "word-wrap": "~1.2.3"
+          }
+        },
         "semver": {
-          "version": "5.7.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
-          "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
           "dev": true
         },
         "strip-ansi": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
-          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
           "dev": true,
           "requires": {
-            "ansi-regex": "^3.0.0"
+            "ansi-regex": "^4.1.0"
           }
+        },
+        "strip-json-comments": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
+          "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
+          "dev": true
+        },
+        "type-fest": {
+          "version": "0.8.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+          "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+          "dev": true
+        },
+        "v8-compile-cache": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
+          "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
+          "dev": true
         }
       }
     },
@@ -10523,15 +10553,9 @@
       }
     },
     "eslint-config-standard": {
-      "version": "12.0.0",
-      "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz",
-      "integrity": "sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ==",
-      "dev": true
-    },
-    "eslint-config-standard-jsx": {
-      "version": "8.1.0",
-      "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-8.1.0.tgz",
-      "integrity": "sha512-ULVC8qH8qCqbU792ZOO6DaiaZyHNS/5CZt3hKqHkEhVlhPEPN3nfBqqxJCyp59XrjIBZPu1chMYe9T2DXZ7TMw==",
+      "version": "14.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.0.tgz",
+      "integrity": "sha512-EF6XkrrGVbvv8hL/kYa/m6vnvmUT+K82pJJc4JJVMM6+Qgqh0pnwprSxdduDLB9p/7bIxD+YV5O0wfb8lmcPbA==",
       "dev": true
     },
     "eslint-config-standard-react": {
@@ -11004,9 +11028,9 @@
           "dev": true
         },
         "resolve": {
-          "version": "1.14.1",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.1.tgz",
-          "integrity": "sha512-fn5Wobh4cxbLzuHaE+nphztHy43/b++4M6SsGFC2gB8uYwf0C8LcarfCz1un7UTW8OFQg9iNjZ4xpcFVGebDPg==",
+          "version": "1.15.1",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",
+          "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==",
           "dev": true,
           "requires": {
             "path-parse": "^1.0.6"
@@ -11210,9 +11234,9 @@
       }
     },
     "eslint-plugin-react-hooks": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.0.tgz",
-      "integrity": "sha512-bzvdX47Jx847bgAYf0FPX3u1oxU+mKU8tqrpj4UX9A96SbAmj/HVEefEy6rJUog5u8QIlOPTKZcBpGn5kkKfAQ==",
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz",
+      "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==",
       "dev": true
     },
     "eslint-plugin-sort-class-members": {
@@ -11238,12 +11262,20 @@
       }
     },
     "eslint-utils": {
-      "version": "1.4.2",
-      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
-      "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
+      "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
       "dev": true,
       "requires": {
-        "eslint-visitor-keys": "^1.0.0"
+        "eslint-visitor-keys": "^1.1.0"
+      },
+      "dependencies": {
+        "eslint-visitor-keys": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
+          "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
+          "dev": true
+        }
       }
     },
     "eslint-visitor-keys": {
@@ -11253,20 +11285,20 @@
       "dev": true
     },
     "espree": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz",
-      "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==",
+      "version": "6.1.2",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz",
+      "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==",
       "dev": true,
       "requires": {
-        "acorn": "^6.0.7",
-        "acorn-jsx": "^5.0.0",
-        "eslint-visitor-keys": "^1.0.0"
+        "acorn": "^7.1.0",
+        "acorn-jsx": "^5.1.0",
+        "eslint-visitor-keys": "^1.1.0"
       },
       "dependencies": {
-        "acorn": {
-          "version": "6.1.1",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz",
-          "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==",
+        "eslint-visitor-keys": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
+          "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
           "dev": true
         }
       }
@@ -16975,36 +17007,39 @@
       }
     },
     "inquirer": {
-      "version": "6.2.2",
-      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz",
-      "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==",
+      "version": "7.0.4",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz",
+      "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==",
       "dev": true,
       "requires": {
-        "ansi-escapes": "^3.2.0",
+        "ansi-escapes": "^4.2.1",
         "chalk": "^2.4.2",
-        "cli-cursor": "^2.1.0",
+        "cli-cursor": "^3.1.0",
         "cli-width": "^2.0.0",
         "external-editor": "^3.0.3",
-        "figures": "^2.0.0",
-        "lodash": "^4.17.11",
-        "mute-stream": "0.0.7",
+        "figures": "^3.0.0",
+        "lodash": "^4.17.15",
+        "mute-stream": "0.0.8",
         "run-async": "^2.2.0",
-        "rxjs": "^6.4.0",
-        "string-width": "^2.1.0",
-        "strip-ansi": "^5.0.0",
+        "rxjs": "^6.5.3",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^5.1.0",
         "through": "^2.3.6"
       },
       "dependencies": {
         "ansi-escapes": {
-          "version": "3.2.0",
-          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
-          "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
-          "dev": true
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz",
+          "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==",
+          "dev": true,
+          "requires": {
+            "type-fest": "^0.8.1"
+          }
         },
         "ansi-regex": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz",
-          "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==",
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+          "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
           "dev": true
         },
         "ansi-styles": {
@@ -17027,29 +17062,117 @@
             "supports-color": "^5.3.0"
           }
         },
+        "cli-cursor": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+          "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+          "dev": true,
+          "requires": {
+            "restore-cursor": "^3.1.0"
+          }
+        },
+        "emoji-regex": {
+          "version": "8.0.0",
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+          "dev": true
+        },
         "escape-string-regexp": {
           "version": "1.0.5",
           "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
           "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
           "dev": true
         },
-        "rxjs": {
-          "version": "6.4.0",
-          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz",
-          "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==",
+        "figures": {
+          "version": "3.2.0",
+          "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+          "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
           "dev": true,
           "requires": {
-            "tslib": "^1.9.0"
+            "escape-string-regexp": "^1.0.5"
+          }
+        },
+        "is-fullwidth-code-point": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+          "dev": true
+        },
+        "mimic-fn": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+          "dev": true
+        },
+        "mute-stream": {
+          "version": "0.0.8",
+          "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
+          "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
+          "dev": true
+        },
+        "onetime": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
+          "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
+          "dev": true,
+          "requires": {
+            "mimic-fn": "^2.1.0"
+          }
+        },
+        "restore-cursor": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+          "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+          "dev": true,
+          "requires": {
+            "onetime": "^5.1.0",
+            "signal-exit": "^3.0.2"
+          }
+        },
+        "string-width": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+          "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^8.0.0",
+            "is-fullwidth-code-point": "^3.0.0",
+            "strip-ansi": "^6.0.0"
+          },
+          "dependencies": {
+            "strip-ansi": {
+              "version": "6.0.0",
+              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+              "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+              "dev": true,
+              "requires": {
+                "ansi-regex": "^5.0.0"
+              }
+            }
           }
         },
         "strip-ansi": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz",
-          "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==",
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
           "dev": true,
           "requires": {
-            "ansi-regex": "^4.0.0"
+            "ansi-regex": "^4.1.0"
+          },
+          "dependencies": {
+            "ansi-regex": {
+              "version": "4.1.0",
+              "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+              "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+              "dev": true
+            }
           }
+        },
+        "type-fest": {
+          "version": "0.8.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+          "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+          "dev": true
         }
       }
     },
@@ -19338,12 +19461,6 @@
       "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=",
       "dev": true
     },
-    "lodash.unescape": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
-      "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=",
-      "dev": true
-    },
     "lodash.uniq": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@@ -28412,9 +28529,9 @@
       "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
     },
     "tsutils": {
-      "version": "3.14.0",
-      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.14.0.tgz",
-      "integrity": "sha512-SmzGbB0l+8I0QwsPgjooFRaRvHLBLNYM8SeQ0k6rtNDru5sCGeLJcZdwilNndN+GysuFjF5EIYgN8GfFG6UeUw==",
+      "version": "3.17.1",
+      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
+      "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
       "dev": true,
       "requires": {
         "tslib": "^1.8.1"
diff --git a/package.json b/package.json
index f93944ce88e7a5a1685b9150e61b0503dc457cfd..a173dd3791cfccbd3ec8f685233bb665444fae59 100644
--- a/package.json
+++ b/package.json
@@ -162,9 +162,8 @@
     "@types/react-modal": "^3.10.5",
     "@types/react-select": "^3.0.10",
     "@types/styled-components": "4.1.8",
-    "@typescript-eslint/eslint-plugin": "^1.13.0",
-    "@typescript-eslint/parser": "^1.13.0",
-    "acorn": "^7.1.0",
+    "@typescript-eslint/eslint-plugin": "^2.14.0",
+    "@typescript-eslint/parser": "^2.14.0",
     "babel-plugin-inline-react-svg": "^1.1.1",
     "babel-plugin-istanbul": "^6.0.0",
     "babel-preset-gatsby": "^0.2.29",
@@ -182,10 +181,9 @@
     "danger-plugin-no-test-shortcuts": "^2.0.0",
     "enzyme": "^3.11.0",
     "enzyme-adapter-react-16": "^1.15.2",
-    "eslint": "^5.16.0",
-    "eslint-config-prettier": "^6.10.0",
-    "eslint-config-standard": "^12.0.0",
-    "eslint-config-standard-jsx": "^8.1.0",
+    "eslint": "^6.8.0",
+    "eslint-config-prettier": "^6.9.0",
+    "eslint-config-standard": "^14.1.0",
     "eslint-config-standard-react": "^9.0.0",
     "eslint-plugin-chai-friendly": "^0.5.0",
     "eslint-plugin-cypress": "^2.10.3",
@@ -196,7 +194,7 @@
     "eslint-plugin-node": "^11.0.0",
     "eslint-plugin-promise": "^4.2.1",
     "eslint-plugin-react": "^7.18.3",
-    "eslint-plugin-react-hooks": "^2.5.0",
+    "eslint-plugin-react-hooks": "^1.7.0",
     "eslint-plugin-sort-class-members": "^1.6.0",
     "eslint-plugin-standard": "^4.0.1",
     "fetch-ponyfill": "^6.1.0",
diff --git a/server.js b/server.js
index 1ee3e72b48b5859d3b8e870cb40586ced204c215..cb7055612a0c40ae084beeccfac85733edc3cc42 100644
--- a/server.js
+++ b/server.js
@@ -16,7 +16,7 @@ Sentry.init({
     const filtered = integrations.filter(
       integration => !disabledIntegrations.includes(integration.name)
     )
-    if (filtered.length != integrations.length - disabledIntegrations.length) {
+    if (filtered.length !== integrations.length - disabledIntegrations.length) {
       throw Error(
         `An error occurred while filtering integrations. The following inetgrations were found: ${integrations.map(
           ({ name }) => name
diff --git a/services/appveyor/appveyor-build.service.js b/services/appveyor/appveyor-build.service.js
index 443ef7544ed48cf3357cf275613b43acc7efb442..e8b7118eda38ba4579240a7a4d76f401f7308aef 100644
--- a/services/appveyor/appveyor-build.service.js
+++ b/services/appveyor/appveyor-build.service.js
@@ -31,7 +31,7 @@ module.exports = class AppVeyorBuild extends AppVeyorBase {
 
   async handle({ user, repo, branch }) {
     const data = await this.fetch({ user, repo, branch })
-    if (!data.hasOwnProperty('build')) {
+    if (!('build' in data)) {
       // this project exists but no builds have been run on it yet
       return this.constructor.render({ status: 'no builds found' })
     }
diff --git a/services/appveyor/appveyor-job-build.service.js b/services/appveyor/appveyor-job-build.service.js
index 8fe6e454efc64d19728439d35f230a8d47517b28..9ea2b34bfe8809abd7875453e23ec546e4ff463e 100644
--- a/services/appveyor/appveyor-job-build.service.js
+++ b/services/appveyor/appveyor-job-build.service.js
@@ -39,7 +39,7 @@ module.exports = class AppVeyorJobBuild extends AppVeyorBase {
   }
 
   transform({ data, jobName }) {
-    if (!data.hasOwnProperty('build')) {
+    if (!('build' in data)) {
       // this project exists but no builds have been run on it yet
       return { status: 'no builds found' }
     }
diff --git a/services/appveyor/appveyor-tests.service.js b/services/appveyor/appveyor-tests.service.js
index f886bf7d0a67f5aa9df25933b591a34b0fb51521..803dac6c6270dbd923b0d0ff6544b3bd7997c365 100644
--- a/services/appveyor/appveyor-tests.service.js
+++ b/services/appveyor/appveyor-tests.service.js
@@ -144,13 +144,13 @@ module.exports = class AppVeyorTests extends AppVeyorBase {
     const isCompact = compactMessage !== undefined
     const data = await this.fetch({ user, repo, branch })
 
-    if (!data.hasOwnProperty('build')) {
+    if (!('build' in data)) {
       return { message: 'no builds found' }
     }
 
-    let total = 0,
-      passed = 0,
-      failed = 0
+    let total = 0
+    let passed = 0
+    let failed = 0
     data.build.jobs.forEach(job => {
       total += job.testsCount
       passed += job.passedTestsCount
diff --git a/services/aur/aur.service.js b/services/aur/aur.service.js
index 604db37bcbe7a84e0d276a418ffdedd66f0176f0..54f04f580fdf32a14329eac28e32b961b84e3c4e 100644
--- a/services/aur/aur.service.js
+++ b/services/aur/aur.service.js
@@ -97,6 +97,7 @@ class AurVotes extends BaseAurService {
   static get category() {
     return 'rating'
   }
+
   static get route() {
     return {
       base: 'aur/votes',
diff --git a/services/cran/cran.service.js b/services/cran/cran.service.js
index 27bc7a470fb292bb0a6f664c46df5e0205973ffd..73095370bfbee4ef7c2a71f4960717a1df401bb1 100644
--- a/services/cran/cran.service.js
+++ b/services/cran/cran.service.js
@@ -52,7 +52,7 @@ class CranLicense extends BaseCranService {
 
   async handle({ packageName }) {
     const data = await this.fetch({ packageName })
-    return this.constructor.render({ license: data['License'] })
+    return this.constructor.render({ license: data.License })
   }
 }
 
@@ -84,7 +84,7 @@ class CranVersion extends BaseCranService {
 
   async handle({ packageName }) {
     const data = await this.fetch({ packageName })
-    return this.constructor.render({ version: data['Version'] })
+    return this.constructor.render({ version: data.Version })
   }
 }
 
diff --git a/services/f-droid/f-droid.service.js b/services/f-droid/f-droid.service.js
index 4c72c83de5960fbeaedf66c7e9b445b9d423982f..ae5f16f2592de317cddd79e55f9356967f40d56c 100644
--- a/services/f-droid/f-droid.service.js
+++ b/services/f-droid/f-droid.service.js
@@ -45,6 +45,7 @@ module.exports = class FDroid extends BaseYamlService {
       },
     ]
   }
+
   static get defaultBadgeData() {
     return { label: 'f-droid' }
   }
@@ -62,7 +63,7 @@ module.exports = class FDroid extends BaseYamlService {
       url: `${url}.yml`,
       ...options,
     })
-    return { version: yaml['CurrentVersion'] }
+    return { version: yaml.CurrentVersion }
   }
 
   async fetchText(url, options) {
diff --git a/services/github/github-contributors.service.js b/services/github/github-contributors.service.js
index c6345eef6dedb67bf15242c51832ca5ee261aa59..502b1d22268e6e739b8cb18b16a62f3db3c6f933 100644
--- a/services/github/github-contributors.service.js
+++ b/services/github/github-contributors.service.js
@@ -53,7 +53,7 @@ module.exports = class GithubContributors extends GithubAuthV3Service {
       errorMessages: errorMessagesFor('repo not found'),
     })
 
-    const parsed = parseLinkHeader(res.headers['link'])
+    const parsed = parseLinkHeader(res.headers.link)
     let contributorCount
     if (parsed === null) {
       const json = this._parseJson(buffer)
diff --git a/services/github/github-deployments.service.js b/services/github/github-deployments.service.js
index b65d41b1b527702c58c44011017779447ba8c5a8..40c1ac5fd27086d0f0c8bc69c42b56a9642efb40 100644
--- a/services/github/github-deployments.service.js
+++ b/services/github/github-deployments.service.js
@@ -83,7 +83,7 @@ module.exports = class GithubDeployments extends GithubAuthV4Service {
     }
 
     let message
-    if (state == 'IN_PROGRESS') {
+    if (state === 'IN_PROGRESS') {
       message = 'in progress'
     } else {
       message = state.toLowerCase()
@@ -96,7 +96,7 @@ module.exports = class GithubDeployments extends GithubAuthV4Service {
   }
 
   async fetch({ user, repo, environment }) {
-    return await this._requestGraphql({
+    return this._requestGraphql({
       query: gql`
         query($user: String!, $repo: String!, $environment: String!) {
           repository(owner: $user, name: $repo) {
@@ -117,7 +117,7 @@ module.exports = class GithubDeployments extends GithubAuthV4Service {
   }
 
   transform({ data }) {
-    if (data.repository.deployments.nodes.length == 0) {
+    if (data.repository.deployments.nodes.length === 0) {
       throw new NotFound({ prettyMessage: 'environment not found' })
     }
     // This happens for the brief moment a deployment is created, but no
diff --git a/services/github/github-issue-detail.service.js b/services/github/github-issue-detail.service.js
index 011c2f9af4cf7d204ffffffe41f99ded9be03811..46041d79c7f021027ab6f8ffbfac27cbdbc9d821 100644
--- a/services/github/github-issue-detail.service.js
+++ b/services/github/github-issue-detail.service.js
@@ -29,7 +29,7 @@ const stateMap = {
   transform: ({ json }) => ({
     state: json.state,
     // Because eslint will not be happy with this snake_case name :(
-    merged: json['merged_at'] !== null,
+    merged: json.merged_at !== null,
   }),
   render: ({ value, isPR, number }) => {
     const state = value.state
@@ -219,7 +219,7 @@ module.exports = class GithubIssueDetail extends GithubAuthV3Service {
 
   transform({ json, property, issueKind }) {
     const value = propertyMap[property].transform({ json, property })
-    const isPR = json.hasOwnProperty('pull_request') || issueKind === 'pulls'
+    const isPR = 'pull_request' in json || issueKind === 'pulls'
     return { value, isPR }
   }
 
diff --git a/services/github/github-release.tester.js b/services/github/github-release.tester.js
index 0fe4b12d1e8e11fc8675774f9d80456489d01c59..7042c1f7f835e77957411e22d18070c9af19ecc2 100644
--- a/services/github/github-release.tester.js
+++ b/services/github/github-release.tester.js
@@ -36,7 +36,7 @@ t.create('Release (repo not found)')
   .get('/v/release/badges/helmets.json')
   .expectBadge({ label: 'release', message: 'no releases or repo not found' })
 
-//redirects
+// redirects
 t.create('Release (legacy route: release)')
   .get('/release/photonstorm/phaser.svg')
   .expectRedirect('/github/v/release/photonstorm/phaser.svg')
diff --git a/services/github/github-tag.service.js b/services/github/github-tag.service.js
index 5c44a78ba7d903e6b44f4bf9398a67f316f053f7..51083f7edd1b65fa8bfb24242e28b333600741b9 100644
--- a/services/github/github-tag.service.js
+++ b/services/github/github-tag.service.js
@@ -83,7 +83,7 @@ class GithubTag extends GithubAuthV4Service {
 
   async fetch({ user, repo, sort }) {
     const limit = sort === 'semver' ? 100 : 1
-    return await this._requestGraphql({
+    return this._requestGraphql({
       query: gql`
         query($user: String!, $repo: String!, $limit: Int!) {
           repository(owner: $user, name: $repo) {
@@ -120,8 +120,9 @@ class GithubTag extends GithubAuthV4Service {
 
     const json = await this.fetch({ user, repo, sort })
     const tags = json.data.repository.refs.edges.map(edge => edge.node.name)
-    if (tags.length === 0)
+    if (tags.length === 0) {
       throw new NotFound({ prettyMessage: 'no tags found' })
+    }
     return this.constructor.render({
       version: this.constructor.getLatestTag({
         tags,
diff --git a/services/jetbrains/jetbrains-version.service.js b/services/jetbrains/jetbrains-version.service.js
index 22d1cd90959f23cf222a1261947651c8c1b03d66..6c845a936c413e83d8f38c1ab2a05ac6b1e3d303 100644
--- a/services/jetbrains/jetbrains-version.service.js
+++ b/services/jetbrains/jetbrains-version.service.js
@@ -55,7 +55,7 @@ module.exports = class JetbrainsVersion extends JetbrainsBase {
   async handle({ pluginId }) {
     const pluginData = await this.fetchPackageData({ pluginId, schema })
     const version =
-      pluginData['plugin-repository'].category['idea-plugin'][0]['version']
+      pluginData['plugin-repository'].category['idea-plugin'][0].version
     return this.constructor.render({ version })
   }
 }
diff --git a/services/packagist/packagist-php-version.service.js b/services/packagist/packagist-php-version.service.js
index ac1e9aae1a3c19e769900046a1904882ee319341..4caa0c32c090a002ee2b6b5ceda85614cdaef450 100644
--- a/services/packagist/packagist-php-version.service.js
+++ b/services/packagist/packagist-php-version.service.js
@@ -84,9 +84,7 @@ module.exports = class PackagistPhpVersion extends BasePackagistService {
       server,
     })
 
-    if (
-      !allData.packages[this.getPackageName(user, repo)].hasOwnProperty(version)
-    ) {
+    if (!(version in allData.packages[this.getPackageName(user, repo)])) {
       throw new NotFound({ prettyMessage: 'invalid version' })
     }
 
diff --git a/services/spack/spack.service.js b/services/spack/spack.service.js
index 0977c56a1a75f6b77a9ec0d43fafb5cbba5a7e8c..ed5ad36f1cfe63376c2ed4e98b7d057217300eee 100644
--- a/services/spack/spack.service.js
+++ b/services/spack/spack.service.js
@@ -51,6 +51,6 @@ module.exports = class SpackVersion extends BaseJsonService {
 
   async handle({ packageName }) {
     const pkg = await this.fetch({ packageName })
-    return this.constructor.render({ version: pkg['latest_version'] })
+    return this.constructor.render({ version: pkg.latest_version })
   }
 }
diff --git a/services/text-formatters.js b/services/text-formatters.js
index e93263300995875645183d38a30f7e1ae90dd618..95cb600d4e3d088e3bddc49c0b8f1f85e64b9abd 100644
--- a/services/text-formatters.js
+++ b/services/text-formatters.js
@@ -43,8 +43,8 @@ function currencyFromCode(code) {
 }
 
 function ordinalNumber(n) {
-  const s = ['ᵗʰ', 'ˢᵗ', 'ⁿᵈ', 'ʳᵈ'],
-    v = n % 100
+  const s = ['ᵗʰ', 'ˢᵗ', 'ⁿᵈ', 'ʳᵈ']
+  const v = n % 100
   return n + (s[(v - 20) % 10] || s[v] || s[0])
 }
 
diff --git a/services/version.js b/services/version.js
index e13623f3fb26d081ff12028005f2a3ca75834167..6a60307404fccf398e85a5819397fd097d85083d 100644
--- a/services/version.js
+++ b/services/version.js
@@ -12,8 +12,8 @@ const { addv } = require('./text-formatters')
 const { version: versionColor } = require('./color-formatters')
 
 function listCompare(a, b) {
-  const alen = a.length,
-    blen = b.length
+  const alen = a.length
+  const blen = b.length
   for (let i = 0; i < alen; i++) {
     if (a[i] < b[i]) {
       return -1
diff --git a/services/visual-studio-app-center/visual-studio-app-center-builds.service.js b/services/visual-studio-app-center/visual-studio-app-center-builds.service.js
index a317eabf7cce3fbecdbc4cf26f67c9cdf89b503e..7592ef676f06aa758b6b02e92b3097b1477086bb 100644
--- a/services/visual-studio-app-center/visual-studio-app-center-builds.service.js
+++ b/services/visual-studio-app-center/visual-studio-app-center-builds.service.js
@@ -54,7 +54,7 @@ module.exports = class VisualStudioAppCenterBuilds extends BaseVisualStudioAppCe
       schema,
       url: `https://api.appcenter.ms/v0.1/apps/${owner}/${app}/branches/${branch}/builds`,
     })
-    if (json[0] == undefined)
+    if (json[0] === undefined)
       // Fetch will return a 200 with no data if no builds were found.
       throw new NotFound({ prettyMessage: 'no builds found' })
     return renderBuildStatusBadge({ status: json[0].result })