[
  {
    "path": ".gitignore",
    "content": ".cache\npublic\n"
  },
  {
    "path": "README.md",
    "content": "\n[![][hero]][hero]\n\n[hero]: https://contrast.now.sh/cff/40f?size=256\n\n# Contrast Swatch\n\nImage microservice for color contrast information\n\n[![][a]][a]\n[![][b]][b]\n[![][c]][c]\n\n[a]: https://contrast.now.sh/bcf/409\n[b]: https://contrast.now.sh/98f/206\n[c]: https://contrast.now.sh/fff/40f\n\n## Usage\n\nContrast swatch images can be used in any place an image is rendered.\nThe URL accepts a foreground and background color.\n\n[`https://contrast.now.sh/cff/40f`][example]\n\n[example]: https://contrast.now.sh/cff/40f\n\n**HTML**\n\n```html\n<img src=\"https://contrast.now.sh/cff/07c\" alt=\"color contrast indicator\" />\n```\n\n**Markdown**\n\n```md\n![color contrast indicator](https://contrast.now.sh/cff/07c)\n```\n\n## React\n\nYou can wrap the image in a React component (or any templating engine) for generating documentation.\n\n```js\nimport React from 'react'\n\nexport default ({\n  foreground,\n  background,\n  ...props\n}) =>\n  <img\n    {...props}\n    src={`https://contrast.now.sh/${foreground}/${background}`}\n    alt='color contrast indicator'\n  />\n```\n\n## RGB\n\nCompare two `rgb` values, or an `rgb` and a hex value:\n\n```\nhttps://contrast.now.sh/rgb(204,255,255)/40f\n```\n\n[![][rgb]][rgb]\n\n[rgb]: https://contrast.now.sh/rgb(204,255,255)/40f\n\n## Customization\n\nUse URL queries to customize the styles.\n\n```\nhttps://contrast.now.sh/cff/40f?width=256&height=96&fontSize=1.25\n```\n\n**Pass/Fail Label**\n\n[![][pass]][pass]\n[![][fail]][fail]\n\n[pass]: https://contrast.now.sh/cff/40f?width=256&height=128&label=1\n[fail]: https://contrast.now.sh/a6f/40f?width=256&height=128&label=1\n\n**Font Size**\n\n[![][smallfont]][smallfont]\n[![][largefont]][largefont]\n\n[smallfont]: https://contrast.now.sh/cff/40f?width=256&height=128&fontSize=0.5\n[largefont]: https://contrast.now.sh/cff/40f?width=256&height=128&fontSize=2\n\n**Size**\n\n[![][large]][large]\n[![][small]][small]\n\n[large]: https://contrast.now.sh/cff/40f?size=320\n[small]: https://contrast.now.sh/cff/40f?size=48\n\n**Width & Height**\n\n[![][wide]][wide]\n[![][tall]][tall]\n\n[wide]: https://contrast.now.sh/cff/40f?width=256&height=48\n[tall]: https://contrast.now.sh/cff/40f?width=32&height=48\n\n**Custom Text**\n\n[![][text]][text]\n\n[text]: https://contrast.now.sh/cff/40f?width=256&text=Aa\n\n**Font Weight**\n\n[![][weight]][weight]\n\n[weight]: https://contrast.now.sh/cff/40f?fontWeight=900&width=256\n\n**Radius**\n\n[![][rounded]][rounded]\n[![][circle]][circle]\n\n[rounded]: https://contrast.now.sh/cff/40f?radius=8\n[circle]: https://contrast.now.sh/cff/40f?radius=48\n\n## Options\n\n\nOption | Description\n---|---\n`size`      | Width & height in pixels\n`width`     | Width of image in pixels\n`height`    | Height of image in pixels (font size will scale based on height)\n`fontSize`  | Relative font size (default 1)\n`fontWeight`| Font weight (default 1)\n`label`     | Show a pass/fail label based on the [WCAG Criteria][wcag]\n`radius`    | Border radius\n`baseline`   | Shift text baseline down\n`text`      | Render any custom text\n\n## Metadata\n\nA JSON response with color contrast information can be fetched by adding the `type=json` query to the URL.\n\n```\nhttps://contrast.now.sh/cff/40f?type=json\n```\n\n**Note:** the returned JSON schema might change in a future version\n\n[wcag]: https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html\n\n## Related\n\n- [Colorable](https://colorable.jxnblk.com)\n- [Use Contrast](https://usecontrast.com/)\n\nMIT License\n"
  },
  {
    "path": "index.js",
    "content": "const Color = require('color')\nconst { createElement: h } = require('react')\nconst { renderToStaticMarkup } = require('react-dom/server')\n\nconst homepage = 'https://github.com/jxnblk/contrast-swatch'\n\nconst parseURL = (req) => {\n  const { foreground, background, ...query } = req.query\n\n  if (!foreground || !background) return null\n\n  return {\n    foreground,\n    background,\n    query,\n  }\n}\n\nconst HEX = /^[A-Fa-f0-9]{3,6}$/\n\nconst getColor = (raw) => {\n  if (HEX.test(raw)) raw = '#' + raw\n  try {\n    return Color(raw)\n  } catch (e) {\n    return null\n  }\n}\n\nconst getLabel = contrast => {\n  if (contrast >= 7) return 'AAA'\n  if (contrast >= 4.5) return 'AA'\n  if (contrast >= 3) return 'lg'\n  return 'Fail'\n}\n\nconst parseColors = (data) => {\n  const foreground = getColor(data.foreground)\n  const background = getColor(data.background)\n  const contrast = foreground.contrast(background)\n  const label = getLabel(contrast)\n\n  return {\n    foreground,\n    background,\n    raw: data,\n    hex: {\n      foreground: foreground.hex(),\n      background: background.hex(),\n    },\n    rgb: {\n      foreground: foreground.rgb().array(),\n      background: background.rgb().array(),\n    },\n    contrast,\n    label,\n  }\n}\n\nconst round = n => Math.floor(100 * n) / 100\n\nconst svg = req => {\n  const data = parseURL(req)\n  if (!data) return null\n  const colors = parseColors(data)\n\n  const opts = Object.assign({\n    width: 128,\n    height: 128,\n    font: 'system-ui,sans-serif',\n    fontSize: 1,\n    fontWeight: 700,\n    baseline: 0,\n    label: false,\n    radius: 0,\n  }, data.query)\n\n  const width = Number(opts.size || opts.width)\n  const height = Number(opts.size || opts.height)\n  const xwidth = 32 * (width / height)\n  const fontSize = Number(opts.fontSize) * 8\n\n  let text = [ round(colors.contrast) ]\n  if (Boolean(opts.label)) {\n    text.push(colors.label)\n  }\n  if (opts.text) text = [opts.text]\n\n  const el = h('svg', {\n    xmlns: 'http://www.w3.org/2000/svg',\n    width,\n    height,\n    viewBox: `0 0 ${xwidth} 32`,\n    fill: colors.hex.foreground,\n    style: {\n      fontFamily: opts.font,\n      fontWeight: opts.fontWeight,\n      fontSize,\n    },\n  },\n    h('rect', {\n      width: xwidth,\n      height: 32,\n      fill: colors.hex.background,\n      rx: opts.radius,\n    }),\n    h('text', {\n      textAnchor: 'middle',\n      x: xwidth / 2,\n      y: 16 + Number(opts.baseline),\n      dominantBaseline: 'middle',\n    },\n      text.join(' ')\n    )\n  )\n\n  const svg = renderToStaticMarkup(el)\n\n  return {\n    ...data,\n    colors,\n    svg,\n  }\n}\n\nmodule.exports = async (req, res) => {\n  const data = svg(req)\n\n  if (!data) return\n\n  switch (data.query.type) {\n    case 'json':\n      return res.send(data)\n  }\n\n  res.setHeader('Content-Type', 'image/svg+xml;charset=utf-8')\n  res.send(data.svg)\n}\n"
  },
  {
    "path": "now.json",
    "content": "{\n  \"version\": 2,\n  \"alias\": \"contrast.now.sh\",\n  \"public\": true,\n  \"builds\": [\n    {\n      \"src\": \"index.js\",\n      \"use\": \"@now/node\"\n    }\n  ],\n  \"routes\": [\n    {\n      \"src\": \"/\",\n      \"status\": 302,\n      \"headers\": {\n        \"Location\": \"https://github.com/jxnblk/contrast-swatch\"\n      },\n      \"continue\": true\n    },\n    {\n      \"src\": \"/(?<fg>[^/]+)/(?<bg>[^/]+)\",\n      \"dest\": \"/index.js?foreground=$fg&background=$bg\",\n      \"headers\": {\n        \"Cache-Control\": \"max-age=604800\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"private\": true,\n  \"name\": \"server\",\n  \"version\": \"0.0.1\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"now dev\"\n  },\n  \"dependencies\": {\n    \"color\": \"^3.1.2\",\n    \"react\": \"^16.8.6\",\n    \"react-dom\": \"^16.8.6\"\n  }\n}\n"
  }
]