diff --git a/.eleventy.js b/.eleventy.js index 41f980b1b..7dac6a2a5 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -1,5 +1,6 @@ const lodashGet = require("lodash/get"); const yaml = require("js-yaml"); +const { URL } = require("url"); module.exports = function (eleventyConfig) { // Support yaml data files @@ -38,6 +39,10 @@ module.exports = function (eleventyConfig) { return md.render(content); }); + eleventyConfig.addFilter('absoluteUrl', (url, base) => { + return (new URL(url, base)).toString() || url + }) + eleventyConfig.addFilter('convertFromEpoc', (time) => { let date = new Date(0); @@ -188,6 +193,7 @@ module.exports = function (eleventyConfig) { eleventyConfig.addPassthroughCopy("src/site/browserconfig.xml"); eleventyConfig.addPassthroughCopy("src/site/site.webmanifest"); eleventyConfig.addPassthroughCopy("src/site/survey/2021/community-survey-2021-methodology.pdf"); + eleventyConfig.addPassthroughCopy("src/site/survey/2022/community-survey-2022-methodology.pdf"); return { dir: { diff --git a/README.md b/README.md index 7d018928b..7be0cd5b1 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ An entry-point for learning about this architectural model. A place to learn wha ## Contributing resources -We've collected a set of videos, presentation, articles and other learning resources about Jamstack. You can contribute content to that pool of resources! +We've collected a set of videos, presentations, articles and other learning resources about Jamstack. You can contribute content to that pool of resources! We accept contributions submitted as [pull requests](https://github.com/jamstack/jamstack.org/pulls). @@ -15,18 +15,20 @@ We accept contributions submitted as [pull requests](https://github.com/jamstack To contribute a link to a resource: -1. Create a new yaml file in the [`src/site/_data/resources`](src/site/_data/resources) folder with a unique and descriptive name. Populate that file according to the structure shown below. -1. For presentations and video, add an optional thumbnail image to the [`src/site/_data/resources`](src/site/img/cms/resources) folder. (Image should be a jpeg 600px wide and 400px tall) +1. Create a new md file in the [`src/site/resources`](src/site/resources) folder with a unique and descriptive name. Populate that file according to the structure shown below. +1. For presentations and video, add an optional thumbnail image to the [`src/site/img/cms`](src/site/img/cms) folder. (Image should be a jpeg 600px wide and 400px tall) 1. Submit a pull request -_resource yaml reference:_ +_resource md reference:_ ```yaml +--- title: Resource title date: Publish date (YYYY-MM-DD) link: the URL of this resource thumbnailurl: /img/cms/resources/resource-thumbnail.jpg type: - article (Help us group and sort the resources by type article|video|presentation) +--- ``` Before submitting a pull request, or if you are suggesting/contributing code or content changes, it is wise to preview your change in a local build. We've tried to make the process of running a local build as low as possible. @@ -58,7 +60,7 @@ npm start ## Styling with TailwindCSS -This site uses [TailwindCSS](https://tailwindcss.com) to offer utility CSS classes and provide a rapid means to styling the site. This means that most styling can be done without writing any additional CSS. Instead, utility classes can be added directly to the HTML. This can provide some very rapid development and also offer surprising levels of familiarity for these used to working in this way (since the conventions and classes are not _per site_.) +This site uses [TailwindCSS](https://tailwindcss.com) to offer utility CSS classes and provide a rapid means to styling the site. This means that most styling can be done without writing any additional CSS. Instead, utility classes can be added directly to the HTML. This can provide some very rapid development and also offer surprising levels of familiarity for those used to working in this way (since the conventions and classes are not _per site_.) While running/developing locally, the `npm run start` command will generate the site including the CSS pipeline from Tailwind. @@ -84,3 +86,4 @@ npm run start You can clone this repository and bootstrap it as a test site of your own, complete with the CI/CD build pipeline on [Netlify](https://netlify.com?utm_source=github&utm_medium=jamstackorg-pnh&utm_campaign=devex) by clicking the button below. (Requires free GitHub and Netlify accounts) [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/jamstack/jamstack.org) + diff --git a/algolia.config.js b/algolia.config.js deleted file mode 100644 index 7f17e4d3f..000000000 --- a/algolia.config.js +++ /dev/null @@ -1,8 +0,0 @@ -require('dotenv').config(); - -module.exports = { - appId: 'VGOL4P01VV', - indexName: 'jamstack', - // It’s okay to have this in the repo, it’s used in the clientside JS - searchOnlyApiKey: "a457b566acbf454a61eaaae2e4fee0bf", -}; diff --git a/netlify.toml b/netlify.toml index 8f670ab5b..15cd02e51 100644 --- a/netlify.toml +++ b/netlify.toml @@ -3,7 +3,7 @@ publish = "dist" [build.environment] - NODE_VERSION = "12.16.2" + NODE_VERSION = "22.17.1" NODE_ENV = "production" [dev] @@ -20,26 +20,12 @@ to = "https://jamstackconf.com/conf/" status = 200 -[[redirects]] - from = "/conf/cfp" - to = "https://docs.google.com/forms/d/e/1FAIpQLSc-z50GyD7zXzr_JCn2M1NBFZ-h65kSdu7zn43V1u2qAKO3ew/viewform" - status = 302 [[redirects]] from = "/conf/*" to = "https://jamstackconf.com/conf/:splat" status = 200 -[[redirects]] - from = "/discord" - to = "https://discord.gg/jamstack" - status = 302 - -[[redirects]] - from = "/women-of-jamstack/" - to = "https://womenofjamstack.com" - status = 302 - [[redirects]] from = "/best-practices" to = "/resources" @@ -70,9 +56,14 @@ to = "/headless-cms/keystone/" status = 301 +[[redirects]] + from = "/headless-cms/graphcms/" + to = "/headless-cms/hygraph/" + status = 301 + [[redirects]] from = "/survey/" - to = "/survey/2021/" + to = "/survey/2022/" status = 302 [[redirects]] @@ -80,5 +71,16 @@ to = "https://www.netlify.com/blog/2020/05/27/state-of-the-jamstack-survey-2020-first-results/" status = 301 +[[redirects]] + from = "/headless-cms/netlify-cms/" + to = "/headless-cms/decap-cms/" + status = 301 + +[[redirects]] + from = "/discord" + to = "https://answers.netlify.com/" + status = 302 + [[plugins]] package = "./plugins/keep-dot-cache-folder" + diff --git a/package.json b/package.json index 4a7af4d59..657cbc4e8 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "url": "https://github.com/jamstack/jamstack.org" }, "engines": { - "node": ">=12" + "node": ">=22" }, "scripts": { "build": "npm-run-all build:html build:css", @@ -27,6 +27,7 @@ "autoprefixer": "^10.2.5", "cssnano": "^4.1.10", "d3": "^7.1.1", + "d3-textwrap": "^3.0.0", "dotenv": "^8.2.0", "fast-glob": "^3.2.5", "gray-matter": "^4.0.2", @@ -43,6 +44,6 @@ "postcss-cli": "^8.3.1", "postcss-import": "^13.0.0", "spdx-correct": "^3.1.1", - "tailwindcss": "^2.0.3" + "tailwindcss": "^3.1.8" } -} \ No newline at end of file +} diff --git a/src/css/d3chart.css b/src/css/d3chart.css index 8c15760b8..7be0aa43d 100644 --- a/src/css/d3chart.css +++ b/src/css/d3chart.css @@ -1,332 +1,528 @@ .d3chart-placeholder { - width: 100%; - height: 450px; + width: 100%; + height: 450px; } .d3chart-placeholder-large { - height: 660px; + height: 660px; } .d3chart-placeholder-xl { - height: 1000px; + height: 1000px; } .d3chart-legend-placeholder { - min-height: 1.71875em; /* 27.5px /16 */ + min-height: 1.71875em; /* 27.5px /16 */ } .d3chart { - position: relative; + position: relative; +} +.d3chart svg { + max-width: 100%; } .d3chart > .d3chart-legend { - position: absolute; - top: 0; - right: 0; + position: absolute; + top: 0; + right: 0; } .d3chart-legend { - display: flex; - justify-content: flex-end; - gap: .5em; - font-size: 0.8125em; /* 13px /16 */ - font-weight: 600; + display: flex; + justify-content: flex-end; + gap: 0.5em; + font-size: 0.8125em; /* 13px /16 */ + font-weight: 600; } .d3chart-legend button { - font-weight: inherit; + font-weight: inherit; } .d3chart-legend-wrap .d3chart-legend { - flex-wrap: wrap; - justify-content: center; - margin-left: auto; - margin-right: auto; + flex-wrap: wrap; + justify-content: center; + margin-left: auto; + margin-right: auto; } .d3chart + .d3chart-legend-placeholder .d3chart-legend { - justify-content: center; + justify-content: center; } .d3chart-legend > * { - border-radius: .25em; - padding: .25em .5em; + border-radius: 0.25em; + padding: 0.25em 0.5em; } .d3chart .tick text { - font-size: 1.3em; /* 13px /10 */ + font-size: 1.3em; /* 13px /10 */ +} + +.d3chart .tick line { + shape-rendering: crispEdges; } + .d3chart .tick line { - shape-rendering: crispEdges; - stroke: rgba(255,255,255,.15); + stroke: rgba(22, 26, 42, 0.15); } .d3chart-bubble .tick:nth-child(2n) line { - stroke: rgba(255,255,255,.22); + stroke: rgba(22, 26, 42, 0.22); } -.d3chart-bubble .tick:nth-child(2n+1) line { - stroke: rgba(255,255,255,.1); +.d3chart-bubble .tick:nth-child(2n + 1) line { + stroke: rgba(22, 26, 42, 0.1); } + .d3chart-bubble .d3chart-xaxis :first-child line, .d3chart-bubble .d3chart-yaxis .tick:last-child line { - stroke: #737680; - stroke-width: 2px; + stroke: #737680; + stroke-width: 2px; } .d3chart-hbar .d3chart-xaxis .tick:first-child line, .d3chart-vbar .d3chart-yaxis .tick:first-child line { - stroke: #c5c5c9; - stroke-width: 2px; + stroke: #c5c5c9; + stroke-width: 2px; +} +.d3chart-highlight-zero-axis .d3chart-xaxis :first-child line, +.d3chart-highlight-zero-axis .d3chart-yaxis .tick:last-child line { + stroke-width: 0; } .d3chart-hbar .d3chart-xaxis .tick:first-child line { - transform: translateX(-1px); + transform: translateX(-1px); } .d3chart-vbar .d3chart-yaxis .tick:first-child line { - transform: translateY(1px); + transform: translateY(1px); } .d3chart-vbar .d3chart-xaxis text, .d3chart-hbar .d3chart-yaxis text { - --d3chart-label-clamp: 2vw; - font-size: 12px; - font-size: clamp(12px, var(--d3chart-label-clamp), 14px); - font-weight: 600; + --d3chart-label-clamp: 2vw; + font-size: 12px; + font-size: clamp(12px, var(--d3chart-label-clamp), 14px); + font-weight: 600; } +.d3chart-bubble.d3chart-highlight-zero-axis [data-chart-value="0"] line { + stroke-width: 2px; + stroke: #737680; +} .d3chart-inlinebarvalue { - --d3chart-label-clamp: 2vw; - font-size: 12px; - font-size: clamp(11px, var(--d3chart-label-clamp), 16px); - font-weight: 600; - text-anchor: middle; + --d3chart-label-clamp: 2vw; + font-size: 12px; + font-size: clamp(11px, var(--d3chart-label-clamp), 16px); + font-weight: 600; + text-anchor: middle; } .d3chart-inlinebarvalue-h { - font-size: 16px; - font-weight: 600; - text-anchor: start; - dominant-baseline: central; - alignment-baseline: middle; + font-size: 16px; + font-weight: 600; + text-anchor: start; + dominant-baseline: central; + alignment-baseline: middle; } .d3chart-inlinebarvalue-h.inside { - text-anchor: end; + text-anchor: end; } .d3chart-inlinebarvalue-h.inside-offset { - font-size: 14px; - text-anchor: end; + font-size: 14px; + text-anchor: end; } /* Wrapped labels */ .d3chart-yaxis text.d3chart-label-wrapped { - font-size: 13px; + font-size: 13px; } /* Axis labels */ .d3chart-axislabel { - fill: #fff; - text-anchor: end; - font-weight: 700; + text-anchor: end; + font-weight: 700; } .d3chart-axislabel-center { - text-anchor: middle; + text-anchor: middle; } /* Bubble charts */ .d3chart-bubblelabel { - text-anchor: middle; - dominant-baseline: central; - font-size: 12px; - font-weight: 600; + text-anchor: middle; + dominant-baseline: central; + font-size: 12px; + font-weight: 600; } .d3chart-bubblelabel.offset-l, .d3chart-bubblelabel.offset-r { - font-weight: 700; - text-shadow: none; + font-weight: 700; + text-shadow: none; } .d3chart-bubblelabel.offset-l { - text-anchor: end; + text-anchor: end; } .d3chart-bubblelabel.offset-r { - text-anchor: start; + text-anchor: start; } .d3chart-bubblelabelbg.offset { - background-color: rgba(255,255,255,.4); + background-color: rgba(255, 255, 255, 0.4); } .d3chart-bubble circle { - fill-opacity: .85; -} -.d3chart-bubble-active .d3chart-bubblelabel, -.d3chart-bubble-active .d3chart-bubblecircle { - fill-opacity: .15; -} -.d3chart-bubble-active .d3chart-bubblelabel.active, -.d3chart-bubble-active .d3chart-bubblecircle.active { - fill-opacity: 1; + fill-opacity: 0.85; } .d3chart-bubble .d3chart-yaxis .tick:last-child text { - display: none; + display: none; } .d3chart-bubble .d3chart-xaxis .tick text { - transform: translateY(2px); + transform: translateY(2px); } /* Color gradients */ .d3chart-color-0 { - fill: url(#gradient-sunrise-v); + fill: url(#gradient-sunrise-v); } .d3chart-hbar .d3chart-color-0 { - fill: url(#gradient-sunrise-h); + fill: url(#gradient-sunrise-h); } .d3chart-color-1 { - fill: url(#gradient-blue-v); + fill: url(#gradient-blue-v); } .d3chart-hbar .d3chart-color-1 { - fill: url(#gradient-blue-h); + fill: url(#gradient-blue-h); } .d3chart-color-2 { - fill: url(#gradient-sun-v); + fill: url(#gradient-sun-v); } .d3chart-hbar .d3chart-color-2 { - fill: url(#gradient-sun-h); + fill: url(#gradient-sun-h); } .d3chart-color-3 { - fill: url(#gradient-seamist-v); + fill: url(#gradient-seamist-v); } .d3chart-hbar .d3chart-color-3 { - fill: url(#gradient-seamist-h); + fill: url(#gradient-seamist-h); } .d3chart-color-4 { - fill: url(#gradient-hallows-v); + fill: url(#gradient-hallows-v); } .d3chart-hbar .d3chart-color-4 { - fill: url(#gradient-hallows-h); + fill: url(#gradient-hallows-h); } .d3chart-color-5 { - fill: url(#gradient-bubblegum-v); + fill: url(#gradient-purple-v); } .d3chart-hbar .d3chart-color-5 { - fill: url(#gradient-bubblegum-h); + fill: url(#gradient-purple-h); } .d3chart-color-6 { - fill: url(#gradient-purple-v); + fill: url(#gradient-bubblegum-v); } .d3chart-hbar .d3chart-color-6 { - fill: url(#gradient-purple-h); + fill: url(#gradient-bubblegum-h); } .d3chart-color-7 { - fill: url(#gradient-air-v); + fill: url(#gradient-air-v); } .d3chart-hbar .d3chart-color-7 { - fill: url(#gradient-air-h); + fill: url(#gradient-air-h); } .d3chart-color-8 { - fill: url(#gradient-pink-v); + fill: url(#gradient-pink-v); } .d3chart-hbar .d3chart-color-8 { - fill: url(#gradient-pink-h); + fill: url(#gradient-pink-h); } .d3chart-color-9 { - fill: url(#gradient-leaves-v); + fill: url(#gradient-leaves-v); } .d3chart-hbar .d3chart-color-9 { - fill: url(#gradient-leaves-h); + fill: url(#gradient-leaves-h); } .d3chart-color-10 { - fill: url(#gradient-haze-v); + fill: url(#gradient-haze-v); } .d3chart-hbar .d3chart-color-10 { - fill: url(#gradient-haze-h); + fill: url(#gradient-haze-h); } .d3chart-color-11 { - fill: url(#gradient-gnat-v); + fill: url(#gradient-gnat-v); } .d3chart-hbar .d3chart-color-11 { - fill: url(#gradient-gnat-h); + fill: url(#gradient-gnat-h); } .d3chart-color-12 { - fill: url(#gradient-fire-v); + fill: url(#gradient-fire-v); } .d3chart-hbar .d3chart-color-12 { - fill: url(#gradient-fire-h); + fill: url(#gradient-fire-h); } .d3chart-color-13 { - fill: url(#gradient-ocean-v); + fill: url(#gradient-ocean-v); } .d3chart-hbar .d3chart-color-13 { - fill: url(#gradient-ocean-h); + fill: url(#gradient-ocean-h); } .d3chart-color-14 { - fill: url(#gradient-night-v); + fill: url(#gradient-night-v); } .d3chart-hbar .d3chart-color-14 { - fill: url(#gradient-night-h); + fill: url(#gradient-night-h); } .d3chart-color-15 { - fill: url(#gradient-dusk-v); + fill: url(#gradient-dusk-v); } .d3chart-hbar .d3chart-color-15 { - fill: url(#gradient-dusk-h); + fill: url(#gradient-dusk-h); +} + +.d3chart-color-stroke-0 { + stroke: url(#gradient-sunrise-h); +} + +.d3chart-color-stroke-1 { + stroke: url(#gradient-blue-h); +} + +.d3chart-color-stroke-2 { + stroke: url(#gradient-sun-h); +} + +.d3chart-color-stroke-3 { + stroke: url(#gradient-seamist-h); +} + +.d3chart-color-stroke-4 { + stroke: url(#gradient-hallows-h); +} + +.d3chart-color-stroke-5 { + stroke: url(#gradient-bubblegum-h); +} + +.d3chart-color-stroke-6 { + stroke: url(#gradient-purple-h); +} + +.d3chart-color-stroke-7 { + stroke: url(#gradient-air-h); +} + +.d3chart-color-stroke-8 { + stroke: url(#gradient-pink-h); +} + +.d3chart-color-stroke-9 { + stroke: url(#gradient-leaves-h); +} + +.d3chart-color-stroke-10 { + stroke: url(#gradient-haze-h); +} + +.d3chart-color-stroke-11 { + stroke: url(#gradient-gnat-h); +} + +.d3chart-color-stroke-12 { + stroke: url(#gradient-fire-h); +} + +.d3chart-color-stroke-13 { + stroke: url(#gradient-ocean-h); +} + +.d3chart-color-stroke-14 { + stroke: url(#gradient-night-h); +} + +.d3chart-color-stroke-15 { + stroke: url(#gradient-dusk-h); +} + +.d3chart-colors-extended .d3chart-legend-16 { + color: #000; + background-image: linear-gradient(108deg, #f0185d, #ff668f); +} +.d3chart-colors-extended .d3chart-color-16 { + fill: url(#gradient-extended-16-v); +} + +.d3chart-colors-extended .d3chart-legend-17 { + color: #000; + background-image: linear-gradient(108deg, #448bd0, #80c0ff); +} +.d3chart-colors-extended .d3chart-color-17 { + fill: url(#gradient-extended-17-v); +} + +.d3chart-colors-extended .d3chart-legend-18 { + color: #000; + background-image: linear-gradient(108deg, #dbd600, #ffff54); +} +.d3chart-colors-extended .d3chart-color-18 { + fill: url(#gradient-extended-18-v); +} + +.d3chart-colors-extended .d3chart-legend-19 { + color: #000; + background-image: linear-gradient(108deg, #63edd7, #a1ffff); +} +.d3chart-colors-extended .d3chart-color-19 { + fill: url(#gradient-extended-19-v); +} + +.d3chart-colors-extended .d3chart-legend-20 { + color: #000; + background-image: linear-gradient(108deg, #cb5f00, #ff932f); +} +.d3chart-colors-extended .d3chart-color-20 { + fill: url(#gradient-extended-20-v); +} + +.d3chart-colors-extended .d3chart-legend-21 { + color: #000; + background-image: linear-gradient(108deg, #ff98a8, #ffd0df); +} +.d3chart-colors-extended .d3chart-color-21 { + fill: url(#gradient-extended-21-v); +} + +.d3chart-colors-extended .d3chart-legend-22 { + color: #fff; + background-image: linear-gradient(108deg, #a800dc, #e449ff); +} +.d3chart-colors-extended .d3chart-color-22 { + fill: url(#gradient-extended-22-v); +} + +.d3chart-colors-extended .d3chart-legend-23 { + color: #000; + background-image: linear-gradient(108deg, #00cfe4, #6affff); +} +.d3chart-colors-extended .d3chart-color-23 { + fill: url(#gradient-extended-23-v); +} + +.d3chart-colors-extended .d3chart-legend-24 { + color: #fff; + background-image: linear-gradient(108deg, #c5114c, #ff5a7c); +} +.d3chart-colors-extended .d3chart-color-24 { + fill: url(#gradient-extended-24-v); +} + +.d3chart-colors-extended .d3chart-legend-25 { + color: #000; + background-image: linear-gradient(108deg, #4af4b5, #8effed); +} +.d3chart-colors-extended .d3chart-color-25 { + fill: url(#gradient-extended-25-v); +} + +.d3chart-colors-extended .d3chart-legend-26 { + color: #000; + background-image: linear-gradient(108deg, #aa9ee9, #e2d5ff); +} +.d3chart-colors-extended .d3chart-color-26 { + fill: url(#gradient-extended-26-v); +} + +.d3chart-colors-extended .d3chart-legend-27 { + color: #000; + background-image: linear-gradient(108deg, #00c6c9, #57ffff); +} +.d3chart-colors-extended .d3chart-color-27 { + fill: url(#gradient-extended-27-v); +} + +.d3chart-colors-extended .d3chart-legend-28 { + color: #000; + background-image: linear-gradient(108deg, #e64b00, #ff8300); +} +.d3chart-colors-extended .d3chart-color-28 { + fill: url(#gradient-extended-28-v); } /* Legend gradients */ .d3chart-legend-0 { - color: #fff; - background: linear-gradient(352.65deg, #F0047F 1.39%, #FC814A 82.63%); + color: #fff; + background: linear-gradient(352.65deg, #f0047f 1.39%, #fc814a 82.63%); } .d3chart-legend-1 { - color: #000; - background: linear-gradient(47.9deg, #0090CA 6.17%, #00BFAD 79.63%); + color: #000; + background: linear-gradient(47.9deg, #0090ca 6.17%, #00bfad 79.63%); } .d3chart-legend-2 { - color: #000; - background: linear-gradient(180deg, #FFC803 0%, #FC814A 100%); + color: #000; + background: linear-gradient(180deg, #fc814a 0%, #ffc803 100%); } .d3chart-legend-3 { - color: #000; - background: linear-gradient(180deg, #78ECC2 0%, #00FFB2 100%); + color: #000; + background: linear-gradient(180deg, #78ecc2 0%, #00ffb2 100%); } .d3chart-legend-4 { - color: #000; - background: linear-gradient(108.82deg, #DF4A1F 0%, #FFA278 90.74%); + color: #000; + background: linear-gradient(108.82deg, #df4a1f 0%, #ffa278 90.74%); } .d3chart-legend-5 { - color: #000; - background: linear-gradient(108.82deg, #FD98BC 32.87%, #FFCCDE 90.74%); + color: #000; + background: linear-gradient(108.82deg, #6b38fb 0%, #ccb4ff 90.74%); } .d3chart-legend-6 { - color: #000; - background: linear-gradient(108.82deg, #6B38FB 0%, #CCB4FF 90.74%); + color: #000; + background: linear-gradient(108.82deg, #fd98bc 32.87%, #ffccde 90.74%); } .d3chart-legend-7 { - color: #000; - background: linear-gradient(108.82deg, #03D0D0 0%, #B5FFF8 90.74%); + color: #000; + background: linear-gradient(108.82deg, #03d0d0 0%, #b5fff8 90.74%); } .d3chart-legend-8 { - color: #fff; - background: linear-gradient(108.82deg, #C40468 0%, #FC2796 90.74%); + color: #fff; + background: linear-gradient(108.82deg, #c40468 0%, #fc2796 90.74%); } .d3chart-legend-9 { - color: #000; - background: linear-gradient(180deg, #78F19A 0%, #13B110 100%); + color: #000; + background: linear-gradient(180deg, #78f19a 0%, #13b110 100%); } .d3chart-legend-10 { - color: #000; - background: linear-gradient(108.82deg, #91A5EE 37.71%, #D6DEFF 90.74%); + color: #000; + background: linear-gradient(108.82deg, #91a5ee 37.71%, #d6deff 90.74%); } .d3chart-legend-11 { - color: #000; - background: linear-gradient(108.82deg, #02C6B3 40.13%, #59F7E7 90.74%); + color: #000; + background: linear-gradient(108.82deg, #02c6b3 40.13%, #59f7e7 90.74%); } .d3chart-legend-12 { - color: #fff; - background: linear-gradient(108.82deg, #FF0F00 0%, #FF928A 90.74%); + color: #fff; + background: linear-gradient(108.82deg, #ff0f00 0%, #ff928a 90.74%); } .d3chart-legend-13 { - color: #000; - background: linear-gradient(180deg, #003EDD 0%, #6CDCFF 100%); + color: #000; + background: linear-gradient(180deg, #003edd 0%, #6cdcff 100%); } .d3chart-legend-14 { - color: #000; - background: linear-gradient(108.82deg, #02465F 3.38%, #6AD7FF 90.74%); + color: #000; + background: linear-gradient(108.82deg, #02465f 3.38%, #6ad7ff 90.74%); } .d3chart-legend-15 { - color: #fff; - background: linear-gradient(108.82deg, #960000 0%, #E94242 92.82%); + color: #fff; + background: linear-gradient(108.82deg, #960000 0%, #e94242 92.82%); } .d3chart-legend-16 { - color: #fff; - background: linear-gradient(108.82deg, #FF72CF 0%, #C92ECC 90.74%); + color: #fff; + background: linear-gradient(108.82deg, #ff72cf 0%, #c92ecc 90.74%); } +/* Overrides */ +/* Dark mode */ +.dark .tick line { + stroke: rgba(255, 255, 255, 0.15); +} +.dark .d3chart-bubble .tick:nth-child(2n) line { + stroke: rgba(255, 255, 255, 0.22); +} +.dark .d3chart-bubble .tick:nth-child(2n + 1) line { + stroke: rgba(255, 255, 255, 0.1); +} -/* Overrides */ \ No newline at end of file +.dark .d3chart-bubble.d3chart-highlight-zero-axis [data-chart-value="0"] line { + stroke: rgba(255, 255, 255, 0.75); +} + +.dark .d3chart-axislabel { + fill: #fff; +} + +.dark .d3chart-bubblelabel.offset-l { + filter: url(#offset-label-bg); +} diff --git a/src/css/tailwind.css b/src/css/tailwind.css index 44f5ae7ba..58aacb659 100644 --- a/src/css/tailwind.css +++ b/src/css/tailwind.css @@ -3,12 +3,16 @@ @import "../site/css/hubspot-form.css"; /* purgecss start ignore */ -@tailwind base; -@tailwind components; +@tailwind base; +@tailwind components; /* purgecss end ignore */ - @layer base { + h1, + h2, + h3 { + @apply text-blue-900 dark:text-white; + } h1 { @apply font-bold text-4xl leading-tight my-8; } @@ -18,7 +22,7 @@ } } h2 { - @apply text-white text-3xl font-bold leading-none mb-2; + @apply text-3xl font-bold leading-none mb-2; } @screen md { h2 { @@ -27,9 +31,6 @@ } } - - - /* Content sections conventions */ @@ -39,12 +40,11 @@ section { @apply mx-auto; } - -section a, -dd a { - @apply text-white; +:where(section a), +:where(dd a) { + @apply dark:text-white; @apply border-b; - @apply border-blue-100; + @apply border-blue-900 dark:border-blue-100; } section:not(.cards):not(.no-underline) a:hover, section:not(.cards):not(.no-underline) a:focus, @@ -52,11 +52,10 @@ dd a:hover, dd a:focus { @apply border-pink-500; } -p+p { +p + p { @apply mt-4; } - /* CTA links */ @@ -80,7 +79,6 @@ a.cta:focus { box-shadow: 0px 3px 15px rgba(241, 86, 77, 0.6); } - /* Footer links */ @@ -94,7 +92,6 @@ footer p a:focus { @apply border-red-500; } - /* left/right flip-flopping lists */ @@ -105,6 +102,40 @@ footer p a:focus { } } +/* Color theme selector */ +.color-theme-selector-wrapper { + --padding-inline: 0.5rem; + --icon-size: 1rem; + + @apply invisible; + @apply relative w-32 h-8 flex items-center; + @apply rounded-default border border-gray-200 dark:border-transparent; + @apply bg-white; + @apply text-black; +} + +.color-theme-selector-wrapper-icon { + width: var(--icon-size); + max-height: 100%; + margin-left: var(--padding-inline); +} + +.color-theme-selector-wrapper::after { + position: absolute; + top: 50%; + right: var(--padding-inline); + transform: translateY(-50%); + content: ""; + width: var(--icon-size); + height: var(--icon-size); + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 9L12 15L18 9' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"); +} + +.color-theme-selector-wrapper select { + @apply absolute w-full flex items-center bg-transparent; + appearance: none; + padding-left: calc(var(--icon-size) + var(--padding-inline) + 0.5rem); +} .videowrapper { position: relative; @@ -121,31 +152,32 @@ footer p a:focus { /* purgecss start ignore */ .bg-gradient-jams { - background: #D1036F linear-gradient(91.78deg, #D1036F 2.57%, #B6005F 96.33%); + background: #d1036f linear-gradient(91.78deg, #d1036f 2.57%, #b6005f 96.33%); } .bg-gradient-pink-orange, .hover\:bg-gradient-pink-orange:hover, .hover\:bg-gradient-pink-orange:focus { - background: #E7017A linear-gradient(91.78deg, #E7017A 2.57%, #DF4A1F 96.33%); + background: #e7017a linear-gradient(91.78deg, #e7017a 2.57%, #df4a1f 96.33%); } .bg-gradient-blue-green { - background: #0090CA linear-gradient(101.87deg, #0090CA 0%, #00BFAD 105.55%); + background: #0090ca linear-gradient(101.87deg, #0090ca 0%, #00bfad 105.55%); } .bg-gradient-card-sunrise { - --tw-gradient-stops: #F0047F 0%, #FC814A 100%; + --tw-gradient-stops: #f0047f 0%, #fc814a 100%; } .bg-gradient-card-blue { - --tw-gradient-stops: #04A2DD 0%, #4FF3EA 100%; + --tw-gradient-stops: #04a2dd 0%, #4ff3ea 100%; } .bg-gradient-card-seafoam { - --tw-gradient-stops: #88F9ED 0%, #00FFB2 100%; + --tw-gradient-stops: #88f9ed 0%, #00ffb2 100%; } .bg-gradient-card-gold { - --tw-gradient-stops: #FFC803 0%, #FC814A 100%; + --tw-gradient-stops: #ffc803 0%, #fc814a 100%; } .bg-gradient-card-blue-seafoam.bg-gradient-card-blue-seafoam { - background: linear-gradient(101.87deg, #0090CA 0%, #00BFAD 105.55%), linear-gradient(180deg, #009DDC 0%, #58FCEC 100%); + background: linear-gradient(101.87deg, #0090ca 0%, #00bfad 105.55%), + linear-gradient(180deg, #009ddc 0%, #58fcec 100%); } .card-shadow { @@ -255,10 +287,11 @@ details[open] .summary-swap-open { } /* Desktop sticky nav */ -@media (min-width: 768px) and (min-height: 53.75em) { /* 860px */ +@media (min-width: 768px) { /* 860px */ .page-header { position: sticky; top: 0; + overflow-y: auto; } .page-nav { justify-content: space-between; @@ -272,12 +305,14 @@ details[open] .summary-swap-open { .hero-text { font-size: 2.5em; } -@media (min-width: 40em) { /* 640px */ +@media (min-width: 40em) { + /* 640px */ .hero-text { font-size: 3em; /* 48px /16 */ } } -@media (min-width: 64em) { /* 1024px */ +@media (min-width: 64em) { + /* 1024px */ .hero-text { font-size: 4em; /* 64px /16 */ } @@ -291,7 +326,7 @@ details[open] .summary-swap-open { .tool-content h1, .tool-content h2, .tool-content h3 { - margin: .5em 0; + margin: 0.5em 0; } .tool-content h1, .tool-content h2, @@ -319,7 +354,7 @@ details[open] .summary-swap-open { /* Filters */ .filter-form { - opacity: .4; + opacity: 0.4; pointer-events: none; } .filter-container--js .filter-form { @@ -337,14 +372,14 @@ details[open] .summary-swap-open { /* Jamstack TV */ .ais-SearchBox-input { - @apply bg-blue-900; - @apply text-white; + @apply bg-white dark:bg-blue-900; + @apply dark:text-white; @apply font-bold; @apply p-3; @apply pl-14; @apply mx-auto; @apply border; - border-color: #5A5F75; + @apply border-gray-200 dark:border-gray-400; @apply rounded-full; @apply block; @apply w-full; @@ -365,7 +400,7 @@ details[open] .summary-swap-open { @supports (box-shadow: none) { .ais-SearchBox-input:focus { outline: none; - box-shadow: 0 0 1px 4px #E7017A; + box-shadow: 0 0 1px 4px #e7017a; } } @@ -393,9 +428,10 @@ details[open] .summary-swap-open { @apply bg-black; @apply text-sm; @apply font-bold; + @apply text-blue-100; position: absolute; - right: .4em; - top: .4em; + right: 0.4em; + top: 0.4em; } .jamstacktv-time { display: inline-block; @@ -442,10 +478,10 @@ details[open] .summary-swap-open { @apply italic; } .jamstacktv-caption-quote:before { - content: "“" + content: "“"; } .jamstacktv-caption-quote:after { - content: "”" + content: "”"; } .jamstacktv-no-skip .jamstacktv-title { @apply text-2xl; @@ -471,6 +507,205 @@ details[open] .summary-swap-open { height: 4px; background-color: red; } + +/* Survey */ +.chart-data-table-head { + @apply bg-gray-200 dark:bg-gray-700 border-b-2; +} + +.permalink-heading { + @apply flex items-center gap-x-2; +} + +.permalink-heading-anchor { + @apply border-0; +} + +.permalink-heading-icon { + @apply w-4; + @apply opacity-50; +} + +.permalink-heading-icon:hover { + @apply opacity-100; +} + +.survey-grid { + --gap: 2rem; + --full: minmax(var(--gap), 1fr); + --content: min(52rem, 100% - var(--gap) * 2); + --popout: minmax(0, 2rem); + --feature: minmax(0, 5rem); + + display: grid; + grid-template-columns: + [full-start] var(--full) + [feature-start] var(--feature) + [popout-start] var(--popout) + [content-start] var(--content) [content-end] + var(--popout) [popout-end] + var(--feature) [feature-end] + var(--full) [full-end]; +} + +.survey-grid > * { + grid-column: content; +} + +.stack > * + * { + margin-block-end: 0; + margin-block-start: var(--stack-space); +} + +:where(.survey) section { + padding: 0; + margin: 0; + max-width: none; +} + +:where(.survey) h1, +h2, +h3, +h4, +h5 { + margin: 0; +} + +.survey-intro-headings { + grid-column: popout; +} + +.survey-intro-heading { + max-width: 20ch; + @apply font-extrabold; +} + +.survey-intro-subheading { + margin-top: 1.5rem; + @apply leading-tight; +} + +.survey h3 { + @apply text-2xl font-semibold; +} + +.survey h4 { + @apply text-xl font-semibold; +} + +.survey h5 { + @apply text-lg font-semibold; +} + +.survey { + margin-block-start: 4.5rem; +} + +.survey > * + * { + margin-block-start: 6rem; +} + +.survey-toc, +.survey-toc + * { + margin-block-start: 3rem; +} + +.survey-section > * + * { + margin-block-start: 1.5rem; +} + +.survey-section:not(:first-of-type) { + padding-bottom: 6rem; +} + +.survey-section h3, +.survey-section h4, +.survey-section h5 { + margin-block-start: 3rem; +} + +.survey-chart h4, +.survey-chart h5 { + margin-block-start: 0; +} + +.survey-section .survey-chart, +.survey-section .survey-chart + * { + margin-block-start: 3rem; +} + +.survey-section .survey-chart-split, +.survey-section .survey-chart-split + * { + margin-block-start: 4.5rem; +} + +.survey-section ul { + list-style-position: inside; + list-style-type: disc; +} + +.survey-section ul > * + * { + margin-block-start: 0.375rem; +} + +.survey-chart > * + * { + margin-block-start: 1.5rem; +} + +.survey-chart > div:first-of-type { + column-gap: 1.5rem; +} + +.survey-section:first-of-type p:first-of-type { + margin-block-start: 4.5rem; +} + +.survey-chart-split { + display: flex; +} + +.survey-chart-split { + --min: 24rem; + --gap: 1.5rem; + + grid-column: feature; + display: grid; + grid-column-gap: var(--gap); + grid-row-gap: 4.5rem; + grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--min)), 1fr)); +} + +.survey-chart-split .survey-chart { + margin-block-start: 0; +} + +.survey-chart-respondents { + @apply text-sm text-right text-gray-600 dark:text-gray-300; +} + +.survey-chart-subheading { + @apply text-gray-600 dark:text-gray-300 text-sm; + margin-top: 0.375rem; +} + +.survey .final-heading { + @apply font-extrabold bg-gradient-pink-orange text-white; + grid-column: full; + text-align: center; + margin: 2em 0 1em 0; + font-size: clamp(1.5rem, 0.7857rem + 3.5714vw, 4rem); + padding: 0.5em 1em; + line-height: 1.125; + margin-inline: auto; +} + +.survey table { + display: block; + max-width: fit-content; + overflow-x: auto; + white-space: nowrap; +} + /* purgecss end ignore */ @tailwind utilities; diff --git a/src/js/color-theme.js b/src/js/color-theme.js new file mode 100644 index 000000000..376ff85b8 --- /dev/null +++ b/src/js/color-theme.js @@ -0,0 +1,97 @@ +const htmlElement = document.documentElement; +const colorThemeSelector = document.querySelector( + "[data-color-theme-selector]" +); +const colorThemeSelectorInput = colorThemeSelector.querySelector("select"); +const colorThemeSelectorInputIcon = colorThemeSelector.querySelector('.color-theme-selector-wrapper-icon'); +const colorThemeSelectorInputIconSrc = colorThemeSelectorInputIcon.querySelector('use'); +const storedTheme = localStorage.theme; + +function setInputIcon(type) { + if(type === 'dark') { + colorThemeSelectorInputIconSrc.setAttribute('xlink:href', '#icon-color-theme-dark'); + } else if(type === 'light') { + colorThemeSelectorInputIconSrc.setAttribute('xlink:href', '#icon-color-theme-light'); + } else { + colorThemeSelectorInputIconSrc.setAttribute('xlink:href', '#icon-color-theme-system'); + } +} + +function setInputState(storedTheme) { + if (!!storedTheme) { + colorThemeSelectorInput.querySelector( + `[value="${storedTheme}"]` + ).selected = true; + + setInputIcon(storedTheme); + + htmlElement.classList.add(storedTheme); + } +} + +function displayThemeSelector() { + colorThemeSelector.style.visibility = 'visible'; +} + +function toggleThemeClass(type) { + if (type === "dark") { + if (htmlElement.classList.contains("light")) { + htmlElement.classList.remove("light"); + } + + htmlElement.classList.add("dark"); + } else if (type == "light") { + if (htmlElement.classList.contains("dark")) { + htmlElement.classList.remove("dark"); + } + + htmlElement.classList.add("light"); + } else { + // "System" mode - blanket remove all theme classes from the root element + if (htmlElement.classList.contains("dark")) { + htmlElement.classList.remove("dark"); + } + + if (htmlElement.classList.contains("light")) { + htmlElement.classList.remove("light"); + } + + // If the current system color scheme preference is dark, apply the .dark class + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + htmlElement.classList.add("dark"); + } + } +} + +function updateTheme(type) { + switch (type) { + case "dark": + toggleThemeClass("dark"); + + setInputIcon('dark'); + + localStorage.theme = "dark"; + break; + case "light": + toggleThemeClass("light"); + + setInputIcon('light'); + + localStorage.theme = "light"; + break; + default: + toggleThemeClass("system"); + + setInputIcon('system'); + + localStorage.removeItem("theme"); + break; + } +} + +setInputState(storedTheme); +displayThemeSelector(); + +colorThemeSelectorInput.addEventListener("change", (e) => { + updateTheme(e.target.value); +}); diff --git a/src/site/_data/algolia.js b/src/site/_data/algolia.js deleted file mode 100644 index 952da68df..000000000 --- a/src/site/_data/algolia.js +++ /dev/null @@ -1,8 +0,0 @@ -const algoliaConfig = require("../../../algolia.config.js"); - -// don’t hardcode anything here, make the config variables opt-in -module.exports = { - appId: algoliaConfig.appId, - indexName: algoliaConfig.indexName, - searchOnlyApiKey: algoliaConfig.searchOnlyApiKey, -}; \ No newline at end of file diff --git a/src/site/_data/announcementbar.json b/src/site/_data/announcementbar.json index d769098a2..f399b0b95 100644 --- a/src/site/_data/announcementbar.json +++ b/src/site/_data/announcementbar.json @@ -1,5 +1,5 @@ { - "text": "Register for Jamstack Conf!", - "cta": "Join us in San Francisco or online, November 7-8.", - "url": "https://ntl.fyi/3K1iprI" + "text": "Let’s talk about the Future of Jamstack —", + "cta": "Join us", + "url": "https://thefutureofjamstack.org/" } diff --git a/src/site/_data/community.yaml b/src/site/_data/community.yaml index ad3e29563..b46c22fd9 100644 --- a/src/site/_data/community.yaml +++ b/src/site/_data/community.yaml @@ -1,3 +1,8 @@ +- name: Kolachi + link: https://www.meetup.com/jamstack-kolachi/ + feed: https://api.meetup.com/jamstack-kolachi/events + country: Pakistan + - name: San Francisco link: https://www.meetup.com/Jamstack-sf/ feed: https://api.meetup.com/Jamstack-sf/events diff --git a/src/site/_data/glossary.yaml b/src/site/_data/glossary.yaml index 0873efeee..d747f4dd4 100644 --- a/src/site/_data/glossary.yaml +++ b/src/site/_data/glossary.yaml @@ -32,7 +32,7 @@ definition: 'An approach to sharing the work of rendering page views and persisting them as part of the latest deploy. Some pages are rendered as part of a build, others are rendered on demand when first requested via their URL. In this way, build times can be kept manageable for even very large sites as nominated pages can have their rendering deferred until first requested. - Since the pages rendered on demand become part of the latest deployment, key Jamstack principals and advantages such as [Immutable deploys](/glossary/immutable) and [atomic deploys](/glossary/atomic) are preserved. + Since the pages rendered on demand become part of the latest deployment, key Jamstack principles and advantages such as [Immutable deploys](/glossary/immutable) and [atomic deploys](/glossary/atomic) are preserved. This approach, [discussed in this RFC](https://github.com/jamstack/jamstack.org/discussions/549), can also populate pages with content not available at the time of the site build. Such as content contributed by users. diff --git a/src/site/_data/meta.js b/src/site/_data/meta.js new file mode 100644 index 000000000..92780ca8b --- /dev/null +++ b/src/site/_data/meta.js @@ -0,0 +1,4 @@ +module.exports = { + lang: "en", + url: process.env.URL || "http://localhost:8080", +}; diff --git a/src/site/_includes/banner.njk b/src/site/_includes/banner.njk index 928e12e63..9ed3b47e3 100644 --- a/src/site/_includes/banner.njk +++ b/src/site/_includes/banner.njk @@ -1,3 +1,5 @@ +{% if announcementbar.text %} - {{ announcementbar.text }} {% if announcementbar.cta %}{{ announcementbar.cta }}{% endif %} + {{ announcementbar.text }} {% if announcementbar.cta %}{{ announcementbar.cta }}{% endif %} +{% endif %} diff --git a/src/site/_includes/components/cards-meetup.njk b/src/site/_includes/components/cards-meetup.njk index 927fda9c9..73f1ba51c 100644 --- a/src/site/_includes/components/cards-meetup.njk +++ b/src/site/_includes/components/cards-meetup.njk @@ -1,7 +1,7 @@ {% macro card(name, url, theme ) %} - -
+ +
{{ name }} →
diff --git a/src/site/_includes/components/cards.njk b/src/site/_includes/components/cards.njk index 82588d2c6..c7dc2f0f2 100644 --- a/src/site/_includes/components/cards.njk +++ b/src/site/_includes/components/cards.njk @@ -8,7 +8,7 @@ {%- if item.data.language %} data-filter-language="{{ item.data.language | join(",") | lower }}"{% endif %} {%- if item.data.license %} data-filter-license="{{ item.data.license | join(",") | lower }}"{% endif %}>
-
{% if item.data.startertemplaterepo %} - + Deploy to Netlify diff --git a/src/site/_includes/components/color-theme-selector.njk b/src/site/_includes/components/color-theme-selector.njk new file mode 100644 index 000000000..c55dc4f7e --- /dev/null +++ b/src/site/_includes/components/color-theme-selector.njk @@ -0,0 +1,13 @@ +
+ +
\ No newline at end of file diff --git a/src/site/_includes/components/meetup-link.njk b/src/site/_includes/components/meetup-link.njk index 80ef2b252..5eb123664 100644 --- a/src/site/_includes/components/meetup-link.njk +++ b/src/site/_includes/components/meetup-link.njk @@ -1,4 +1,4 @@ - + Group: {{ item.group.name }} Name: {{ item.name }} Venue: {{ item.venue.name }} diff --git a/src/site/_includes/components/permalink-heading.njk b/src/site/_includes/components/permalink-heading.njk new file mode 100644 index 000000000..07d39ab55 --- /dev/null +++ b/src/site/_includes/components/permalink-heading.njk @@ -0,0 +1,22 @@ +{% macro render(level, text, headingClasses, id) %} + <{{level}} id="{% if id %}{{ id }}{% else %}{{ text | slugify }}{% endif %}" class="permalink-heading {{ headingClasses }}"> + {{ text }} + + Permalink + + + +{% endmacro %} diff --git a/src/site/_includes/components/section-heading.njk b/src/site/_includes/components/section-heading.njk index ada668649..25fa3a2ee 100644 --- a/src/site/_includes/components/section-heading.njk +++ b/src/site/_includes/components/section-heading.njk @@ -1,9 +1,9 @@ {% macro heading(title, icon, description ) %} -

+

{{ title }}

-

+

{{ description | safe }}

{% endmacro %} @@ -20,10 +20,10 @@ {% endif %}
-

+

{{ title }}

-
+
{{ description | safe }}
diff --git a/src/site/_includes/components/ticker.njk b/src/site/_includes/components/ticker.njk index 5290334b1..a3897b692 100644 --- a/src/site/_includes/components/ticker.njk +++ b/src/site/_includes/components/ticker.njk @@ -1,69 +1,129 @@ {% macro main() %} -
- -
- {{ iconsMarkup() }} +
+
+ {{ iconsMarkup() }} +
+
-
- {{ iconsMarkup() }} -
-
{% endmacro %} {% macro iconsMarkup() %} - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + {% endmacro %} {% macro icons() %} - {% endmacro %} \ No newline at end of file diff --git a/src/site/_includes/components/zinger-cta.njk b/src/site/_includes/components/zinger-cta.njk index 427dbc821..6b0fac2a4 100644 --- a/src/site/_includes/components/zinger-cta.njk +++ b/src/site/_includes/components/zinger-cta.njk @@ -1,6 +1,6 @@ {% macro cta(text, url, theme, icon ) %} - + {% if icon %}{% endif %}

{{ text }} diff --git a/src/site/_includes/footer.njk b/src/site/_includes/footer.njk index fa33354e0..ca474f434 100644 --- a/src/site/_includes/footer.njk +++ b/src/site/_includes/footer.njk @@ -1,11 +1,16 @@

-
diff --git a/src/site/_includes/header.njk b/src/site/_includes/header.njk index f2e5f15d0..e8202b172 100644 --- a/src/site/_includes/header.njk +++ b/src/site/_includes/header.njk @@ -1,25 +1,21 @@ {% set linkList = [ { "url": "/", "text": "Home" }, - { "url": "/conf/", "text": "Jamstack Conf 2022" }, { "url": "/glossary/", "text": "Glossary" }, - { "url": "/women-of-jamstack/", "text": "Women of Jamstack"}, { "url": "/tv/", "text": "Jamstack TV" }, { "url": "/generators/", "text": "Site Generators" }, { "url": "/headless-cms/", "text": "Headless CMS" }, - { "url": "/community/", "text": "Community" }, - { "url": "/survey/2021/", "text": "Community Survey", "children": [ - { "url": "/survey/2021/#demographics", "text": "Demographics" }, - { "url": "/survey/2021/#adoption", "text": "Jamstack adoption" }, - { "url": "/survey/2021/#workflows", "text": "Workflows" }, - { "url": "/survey/2021/#choices", "text": "Technology choices" }, - { "url": "/survey/2021/#conclusion", "text": "Conclusion" } + { "url": "/survey/2022/", "text": "Community Survey", "children": [ + { "url": "/survey/2022/#whos-doing-the-building", "text": "Who’s doing the building?" }, + { "url": "/survey/2022/#what-is-the-jamstack-community-building", "text": "What is the Jamstack Community building?" }, + { "url": "/survey/2022/#how-are-we-building", "text": "How are we building?" }, + { "url": "/survey/2022/#emerging-trends-in-the-jamstack-community", "text": "Emerging Trends in the Jamstack Community" } ] } ] %} \ No newline at end of file + diff --git a/src/site/_includes/layouts/base.njk b/src/site/_includes/layouts/base.njk index d760d1313..f270239b1 100644 --- a/src/site/_includes/layouts/base.njk +++ b/src/site/_includes/layouts/base.njk @@ -5,7 +5,7 @@ ogimage: "/img/og/default-og-image.png" --- - + @@ -15,9 +15,18 @@ ogimage: "/img/og/default-og-image.png" {%- endfor %} {{ title }} | Jamstack + + + + {%- for url in preconnect %} {%- endfor %} @@ -32,42 +41,62 @@ ogimage: "/img/og/default-og-image.png" - - + {%- for url in javascripts %} {%- endfor %} - + -
-
+
+
{% include "header.njk" %}
{% include "banner.njk" %} + {% include "components/color-theme-selector.njk" %} {{ content | safe }} -
+
{% include "footer.njk" %}
+ diff --git a/src/site/_includes/navigation-links.njk b/src/site/_includes/navigation-links.njk index baf56a661..05e53ab67 100644 --- a/src/site/_includes/navigation-links.njk +++ b/src/site/_includes/navigation-links.njk @@ -2,7 +2,7 @@ {% for link in linkList %}
  • + class="block font-bold text-sm dark:text-white hover:underline py-2 px-10 {% if page.url.split("/")[1] == link.url.split("/")[1] %} bg-gray-200 dark:bg-gray-900{% endif %}"> {{ link.text }}
  • @@ -12,7 +12,7 @@ {%- for childlink in link.children %}
  • + class="block text-sm dark:text-white hover:underline py-2 pl-16 pr-8{% if page.url.split("/")[1] == childlink.url.split("/")[1] %} bg-gray-200 dark:bg-gray-900 font-bold{% endif %}"> {{ childlink.text }}
  • diff --git a/src/site/_includes/search-js.njk b/src/site/_includes/search-js.njk deleted file mode 100644 index 49dfa62c2..000000000 --- a/src/site/_includes/search-js.njk +++ /dev/null @@ -1,102 +0,0 @@ - - - \ No newline at end of file diff --git a/src/site/_includes/survey/adoption.njk b/src/site/_includes/survey/2021/adoption.njk similarity index 98% rename from src/site/_includes/survey/adoption.njk rename to src/site/_includes/survey/2021/adoption.njk index 9df8cc039..f37d36edb 100644 --- a/src/site/_includes/survey/adoption.njk +++ b/src/site/_includes/survey/2021/adoption.njk @@ -16,7 +16,7 @@ Show Chart Data - + @@ -65,7 +65,7 @@ Show Chart Data
    Years of Experience Remote
    - + @@ -101,7 +101,7 @@ Show Chart Data
    Number of users Most sites Some sites
    - + @@ -134,7 +134,7 @@ Show Chart Data
    Frequency of large sites Content producer Back-end
    - + @@ -171,7 +171,7 @@ Show Chart Data
    How frequently do you build sites for audiences of millions Somewhat Very
    - + @@ -228,7 +228,7 @@ Show Chart Data
    Industry Percentage
    - + diff --git a/src/site/_includes/survey/choices.njk b/src/site/_includes/survey/2021/choices.njk similarity index 98% rename from src/site/_includes/survey/choices.njk rename to src/site/_includes/survey/2021/choices.njk index 9490c69ee..2b5a7d6df 100644 --- a/src/site/_includes/survey/choices.njk +++ b/src/site/_includes/survey/2021/choices.njk @@ -18,7 +18,7 @@ Show Chart Data
    Technique Percentage of respondents who use this Satisfaction score
    - + @@ -156,7 +156,7 @@ Show Chart Data
    CMS Usage Satisfaction
    - + @@ -281,7 +281,7 @@ Show Chart Data
    CMS Usage change (%) Satisfaction change
    - + @@ -419,7 +419,7 @@ Show Chart Data
    Language Usage Satisfaction
    - + @@ -560,7 +560,7 @@ Show Chart Data
    CMS Usage change (%) Satisfaction change
    - + @@ -682,7 +682,7 @@ Show Chart Data
    Framework Percentage of respondents who use this Satisfaction score
    - + @@ -814,7 +814,7 @@ Show Chart Data
    Framework Percentage of respondents who use this Satisfaction score
    - + @@ -952,7 +952,7 @@ Show Chart Data
    CMS Usage change (%) Satisfaction change
    - + diff --git a/src/site/_includes/survey/conclusion.njk b/src/site/_includes/survey/2021/conclusion.njk similarity index 100% rename from src/site/_includes/survey/conclusion.njk rename to src/site/_includes/survey/2021/conclusion.njk diff --git a/src/site/_includes/survey/demographics.njk b/src/site/_includes/survey/2021/demographics.njk similarity index 97% rename from src/site/_includes/survey/demographics.njk rename to src/site/_includes/survey/2021/demographics.njk index d23f078d2..04f925d48 100644 --- a/src/site/_includes/survey/demographics.njk +++ b/src/site/_includes/survey/2021/demographics.njk @@ -17,7 +17,7 @@ Show Chart Data
    API Most used
    - + @@ -53,7 +53,7 @@ Show Chart Data
    People 2020 2021
    - + @@ -110,7 +110,7 @@ Show Chart Data
    Job Title Percentage of Survey Participants
    - + diff --git a/src/site/_includes/survey/experience.njk b/src/site/_includes/survey/2021/experience.njk similarity index 98% rename from src/site/_includes/survey/experience.njk rename to src/site/_includes/survey/2021/experience.njk index 584b56537..608c9ff47 100644 --- a/src/site/_includes/survey/experience.njk +++ b/src/site/_includes/survey/2021/experience.njk @@ -18,7 +18,7 @@ Show Chart Data
    Employment Status 2020 2021
    - + @@ -62,7 +62,7 @@ Show Chart Data
    Years of Experience 2020 2021
    - + @@ -216,7 +216,7 @@ Show Chart Data
    Years of Experience Africa Asia Pacific
    - + @@ -256,7 +256,7 @@ Show Chart Data
    Years of Experience Full-time Students
    - + @@ -298,7 +298,7 @@ Show Chart Data
    Pandemic effect
    - + @@ -338,7 +338,7 @@ Show Chart Data
    Years of Experience Remote
    - + diff --git a/src/site/_includes/survey/workflows.njk b/src/site/_includes/survey/2021/workflows.njk similarity index 98% rename from src/site/_includes/survey/workflows.njk rename to src/site/_includes/survey/2021/workflows.njk index f4850b3d2..fab1d1c79 100644 --- a/src/site/_includes/survey/workflows.njk +++ b/src/site/_includes/survey/2021/workflows.njk @@ -17,7 +17,7 @@ Show Chart Data
    Years of Experience Remote
    - + @@ -55,7 +55,7 @@ Show Chart Data
    Target Very Somewhat
    - + @@ -101,7 +101,7 @@ Show Chart Data
    Priority Score
    - + @@ -183,7 +183,7 @@ Show Chart Data
    Design tool Percentage of respondents who use this Satisfaction score
    - + diff --git a/src/site/_includes/survey/2022/how-are-we-building/cms-usage-vs-satisfaction.njk b/src/site/_includes/survey/2022/how-are-we-building/cms-usage-vs-satisfaction.njk new file mode 100644 index 000000000..794a81414 --- /dev/null +++ b/src/site/_includes/survey/2022/how-are-we-building/cms-usage-vs-satisfaction.njk @@ -0,0 +1,125 @@ +
    +
    + + {{ permalinkHeading.render('h4', "Content Management Systems") }} +
    + +
    + +
    +
    +
    + +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data +
    Editor Percentage of respondents who use this Satisfaction score
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CMSUsed on "some" or "many" projectsSatisfaction score
    1. WordPress37%0.5
    2. Notion26%2.3
    3. WordPress (Headless)22%1.0
    4. Contentful19%1.4
    5. Strapi18%2.0
    6. Sanity16%3.0
    7. Drupal14%0.6
    8. Wix13%0.6
    9. Webflow12%1.0
    10. Prismic11%1.8
    11. Squarespace11%0.6
    12. Ghost10%1.5
    13. Storyblok9%2.0
    14. Builder8%1.0
    15. Forestry8%1.0
    16. Agility CMS7%0.8
    17. Weebly7%0.8
    18. ButterCMS6%1.0
    19. Contentstack6%1.0
    + +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/how-are-we-building/frameworks-usage-vs-satisfaction-changes.njk b/src/site/_includes/survey/2022/how-are-we-building/frameworks-usage-vs-satisfaction-changes.njk new file mode 100644 index 000000000..fb14f6a99 --- /dev/null +++ b/src/site/_includes/survey/2022/how-are-we-building/frameworks-usage-vs-satisfaction-changes.njk @@ -0,0 +1,191 @@ +
    +
    + {{ permalinkHeading.render('h4', 'Frameworks by 1-year change in usage and satisfaction') }} +
    + +
    + +
    +
    +
    + +
    Source: Jamstack Community Survey 2021—2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FrameworkUsage change (%)Satisfaction changeUsage
    1. React2.9%-1.471%
    2. Express-2.3%-0.249%
    3. Next.js3.8%-2.847%
    4. jQuery-6.8%0.144%
    5. Vue-6.4%-2.133%
    6. Vite17.8%0.132%
    7. Gatsby-8.9%-1.028%
    8. Nuxt.js-2.8%-2.922%
    9. Angular 2+0.1%-0.220%
    10. 11ty1.6%-2.219%
    11. Svelte4.6%-0.219%
    12. SvelteKit6.9%-2.015%
    13. Jekyll-2.5%-0.114%
    14. Angular 1.x-1.3%0.114%
    15. Hugo-1.8%-0.113%
    16. Preact1.5%-0.712%
    17. Remix7.7%0.910%
    18. Nest0.2%-0.69%
    19. VuePress-0.8%-0.78%
    20. Gridsome-1.5%-0.97%
    21. Docusaurus0.8%0.67%
    22. Hapi0.4%-0.36%
    23. Sapper-1.1%-0.55%
    24. Stencil0.7%-0.35%
    25. RedwoodJS-0.3%1.24%
    26. Blitz.js0.7%1.04%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/how-are-we-building/frameworks-usage-vs-satisfaction.njk b/src/site/_includes/survey/2022/how-are-we-building/frameworks-usage-vs-satisfaction.njk new file mode 100644 index 000000000..392823a9b --- /dev/null +++ b/src/site/_includes/survey/2022/how-are-we-building/frameworks-usage-vs-satisfaction.njk @@ -0,0 +1,179 @@ +
    +
    + {{ permalinkHeading.render('h4', 'Frameworks by usage and satisfaction') }} +
    + +
    + +
    +
    +
    + +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    LanguageUsed on "some" or "many" projectsSatisfaction score
    1. React71%2.9
    2. Express49%1.7
    3. Next.js47%4.2
    4. jQuery44%0.3
    5. Vue33%3.1
    6. Vite32%9.7
    7. Gatsby28%0.9
    8. Nuxt.js22%2.7
    9. Angular 2+20%0.7
    10. 11ty19%3.8
    11. Svelte19%5.3
    12. SvelteKit15%4.0
    13. Jekyll14%0.4
    14. Angular 1.x14%0.3
    15. Hugo13%1.2
    16. Preact12%2.0
    17. Astro11%4.5
    18. Remix10%2.3
    19. Nest9%2.0
    20. VuePress8%1.7
    21. Gridsome7%0.8
    22. Docusaurus7%2.5
    23. Hapi6%1.0
    24. SolidJS6%2.0
    25. Sapper5%0.7
    26. Stencil5%1.5
    27. Quasar4%1.0
    28. RedwoodJS4%3.0
    29. Blitz.js4%3.0
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/how-are-we-building/index.njk b/src/site/_includes/survey/2022/how-are-we-building/index.njk new file mode 100644 index 000000000..290136c48 --- /dev/null +++ b/src/site/_includes/survey/2022/how-are-we-building/index.njk @@ -0,0 +1,268 @@ +
    + {{ + permalinkHeading.render( + "h2", + "How are we building?", + "", + "how-are-we-building" + ) + }} + +

    + Our largest set of questions revolve around technical choices. It’s easy for + this kind of data to turn into a popularity contest, so we should be clear: + the most popular choice is not always the best choice for you. As we’ll see + shortly, your use case matters much more than total adoption of a + technology. However, within the bounds of a use case, popularity can help. + Open source technology benefits from more contributors: bugs are fixed + faster, documentation is better, rough edges are smoothed away more quickly, + and there will be more plugins and third-party integrations. +

    + + {{ + permalinkHeading.render( + "h3", + "A note on how to read Usage + Satisfaction graphs" + ) + }} + +

    + This section contains a number of graphs like the one below. On the + horizontal axis, we measure the usage of a technology, as measured by the + number of people who say they have used that technology in the last year on + “some projects” or “many projects”. We do not count people who say they use + a technology “rarely”, so we believe our “some+many” number represents real, + regular usage. +

    + +

    + At the same time as we ask people how often they use a technology, we ask + them whether they would like to use it more or less in the coming year. We + take the ratio of the “want to use it more” and the “want to use it less” + numbers to create our vertical axis, which we call the “Satisfaction score”. + A score of 1.0 or more means the technology’s users are on balance + enthusiastic about it, while under 1.0 it means they are not. In the three + years of our survey, a satisfaction score under 1.0 has been strongly (but + not perfectly) predictive of a loss in usage the following year, which high + satisfaction scores correlate well to growth in share. +

    + + {{ permalinkHeading.render("h3", "Content Management Systems (CMS)") }} + +

    + The decoupled nature of frontend and backend code in the Jamstack ecosystem + means that CMS are a big component of many of the websites we build. As + anyone who’s built a site with one knows, once a CMS has become embedded + into your company’s culture and workflows it can be hard to get it out + again, so this is a critical choice for many people. +

    + +
      +
    • + The overall leader in the CMS space remains WordPress, as it has been for + many years. However, with a satisfaction score of just 0.5, unenthusiastic + users of WordPress outnumber enthusiastic ones 2-to-1, and WordPress has + lost usage share over the course of our surveys. +
    • +
    • + WordPress used as an API (“headless” mode) has more enthusiastic users + than WordPress in traditional mode, and a substantial 22% share, but this + share has been growing only slowly. +
    • +
    • + Notion is something of an outlier in this data: certainly some people are + using it via its API to power websites, but we believe many people who + answered yes to this option are using it for internal content. We intend + to run a small follow-up survey to confirm this. +
    • +
    • + Given high satisfaction scores, Sanity and Strapi were our choices in last + year’s survey to be breakout contenders in this year's, and they both grew + share, though not as much as we had expected. Contentful lost usage share + in this year’s survey compared to last year’s. +
    • +
    • + Of the smaller CMS systems, Storyblok is notable for high satisfaction. + This is the first year we’ve tracked it and it came in at 8% share, so + we’ll be looking for it to grow. +
    • +
    + + {% include './cms-usage-vs-satisfaction.njk' %} + + {{ permalinkHeading.render("h3", "Programming languages") }} + +

    + There are not a lot of surprises in this year’s programming language data if + you have seen our previous surveys. One note: when we show programming + languages, we should be clear that this data is about their popularity + within the Jamstack community; in more general computing surveys Java is a + much more popular choice. +

    + +
      +
    • + JavaScript remains the near-universal choice, with 96% of respondents + saying they have used it in some or many projects in the last year. +
    • +
    • + TypeScript continues rapid growth, hitting 67% usage this year, overtaking + SQL as the second-most used language. +
    • +
    • + When asked about their primary programming language, 53% + of people still say JavaScript, a number that has declined in all 3 years + of our survey, while 21% say TypeScript is their primary language, more + than doubling its usage as a primary language. The continuing migration + from JavaScript to TypeScript is a trend we are following closely. +
    • +
    + + {% include './programming-language-usage-vs-satisfaction.njk' %} + + {{ permalinkHeading.render("h3", "Web frameworks") }} + +

    + Always our largest section, we tracked 29 frameworks this year, with a few + that we have tracked in previous years falling out of the survey (our + cut-off for frameworks that are not growing quickly is 4% share). +

    + + {{ permalinkHeading.render("h4", "React and Next.js") }} + +

    + The most obvious story in our framework data is the continued growth of + React. With high satisfaction scores last year, we predicted it would + continue to grow and that was borne out this year, hitting a new record of + 71% share, the highest of any framework we’ve tracked in all 3 years. While + there are many options for building a reactive web app, the enormous + ecosystem around React continues to make it an easy choice for many. +

    + +

    + Riding the tails of React’s popularity is Next.js, a full featured “kitchen + sink” framework based on React. This year 47%, or nearly 1 in 2 developers + say they used Next.js in some or many projects, and with a satisfaction + score over 4.0 we expect to see it continue to grow. +

    + + {{ permalinkHeading.render("h4", "Vite") }} + +

    + Although we have been tracking it in our frameworks data, Vite is more of a + bundler, competing with choices such as Webpack and Babel. It has been + adopted as the default bundler for several other frameworks including Nuxt + and SvelteKit, contributing to its high share, but its stellar satisfaction + score is all its own. +

    + + {% include './frameworks-usage-vs-satisfaction.njk' %} + + {{ permalinkHeading.render("h4", "Zooming in on smaller frameworks") }} + +

    + Looking at the crowded bottom-left corner of the overall frameworks graph + can hide some detail, so we take a closer look at frameworks at 10% share or + less. In here are some older frameworks such as Hapi and Gridsome, but also + some new entrants. +

    + +
      +
    • + Remix jumped from 2% share in last year’s survey to 10% this year, and is + an exciting new contender in the space. At the end of October Remix + announced they have been + acquired by Shopify + so it will be interesting to see what effect that has on their trajectory. +
    • +
    • + Docusaurus does one thing very well and has been rewarded with + consistently high satisfaction scores and modest growth. +
    • +
    • SolidJS, a new entry to our survey, clocks in at 6% share.
    • +
    + + {% include './smaller-frameworks-usage-vs-satisfaction.njk' %} + + {{ permalinkHeading.render("h4", "Tracking usage and satisfaction changes") }} + +

    + We have found it instructive to look at how usage and satisfaction scores in + our survey have changed from year to year. Keep in mind that these are + changes; Next.js and Nuxt.js for example both have high + satisfaction scores overall, just lower than last year. We split this graph + into four quadrants. +

    + + {{ permalinkHeading.render("h5", "Bottom-right: regular growth") }} + +

    + A pattern we have seen every year is that frameworks that grow share usually + lose satisfaction score while doing so. This makes sense: as more people + adopt a technology, there are fewer enthusiastic early adopters, and more + people using the framework for use cases that are outside of its sweet spot. +

    + +
      +
    • + React and Next.js both show growth in share and loss in satisfaction, as + expected. +
    • +
    • + Svelte and SvelteKit, another component-framework pair, did the same. +
    • +
    • + 11ty was the only purely static site generator (SSG) in our survey to show + growth in usage share. For this reason we think 11ty is now the clear + choice if a static site is your use case. +
    • +
    + + {{ permalinkHeading.render("h5", "Top right: early adoption") }} + +

    + Technologies in the early phases of adoption tend to see rapid growth and + users who get happier year on year. +

    + +
      +
    • + As mentioned, Vite is seeing huge growth – it more than doubled its usage + share from last year, while maintaining its high satisfaction score. +
    • +
    • + Remix, already mentioned, jumped from 2% to 10% share and increased + satisfaction. +
    • +
    + + {{ permalinkHeading.render("h5", "Top left: core users") }} + +

    + Occupying a quadrant almost by itself is jQuery. Anyone still using jQuery + in 2022 is heavily invested in doing so and it shows. +

    + + {{ permalinkHeading.render("h5", "Bottom left: danger zone") }} + +

    + Losing usage share and satisfaction score at the same time is bad news for + project maintainers. +

    + +
      +
    • + Gatsby has lost share in all 3 years of our survey, and its 0.9 + satisfaction score indicates this trend is likely to continue. +
    • +
    • + Vue and Nuxt.js are new to this quadrant; in last year’s survey they were + still growing. The continued growth of React and Next.js makes it + difficult for similar alternatives to compete. +
    • +
    + + {% include './frameworks-usage-vs-satisfaction-changes.njk' %} +
    diff --git a/src/site/_includes/survey/2022/how-are-we-building/programming-language-usage-vs-satisfaction.njk b/src/site/_includes/survey/2022/how-are-we-building/programming-language-usage-vs-satisfaction.njk new file mode 100644 index 000000000..dfc28aeba --- /dev/null +++ b/src/site/_includes/survey/2022/how-are-we-building/programming-language-usage-vs-satisfaction.njk @@ -0,0 +1,113 @@ +
    +
    + {{ permalinkHeading.render('h4', 'Programming languages by usage and satisfaction') }} +
    + +
    + +
    +
    +
    + +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    LanguageUsed on "some" or "many" projectsSatisfaction score
    1. JavaScript96%3.0
    2. TypeScript67%7.4
    3. SQL64%1.8
    4. Shell (Bash)53%1.5
    5. Python42%2.2
    6. PHP42%0.6
    7. Java26%0.6
    8. C#21%1.1
    9. Ruby18%1.0
    10. C/C++17%1.1
    11. Go16%2.2
    12. Rust12%3.0
    13. Visual Basic10%0.7
    14. Swift9%2.0
    15. Objective-C6%0.5
    16. Perl6%0.5
    17. Elixir6%1.5
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/how-are-we-building/smaller-frameworks-usage-vs-satisfaction.njk b/src/site/_includes/survey/2022/how-are-we-building/smaller-frameworks-usage-vs-satisfaction.njk new file mode 100644 index 000000000..8407a6390 --- /dev/null +++ b/src/site/_includes/survey/2022/how-are-we-building/smaller-frameworks-usage-vs-satisfaction.njk @@ -0,0 +1,94 @@ +
    +
    + {{ permalinkHeading.render('h4', 'Smaller frameworks by usage and satisfaction') }} +
    + +
    + +
    +
    +
    + +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FrameworkUsed on "some" or "many" projectsSatisfaction score
    1. Remix10%2.3
    2. Nest9%2.0
    3. VuePress8%1.7
    4. Gridsome7%0.8
    5. Docusaurus7%2.5
    6. Hapi6%1.0
    7. SolidJS6%2.0
    8. Sapper5%0.7
    9. Stencil5%1.5
    10. Quasar4%1.0
    11. RedwoodJS4%3.0
    12. Blitz.js4%3.0
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/what-are-we-building/audience-sizes.njk b/src/site/_includes/survey/2022/what-are-we-building/audience-sizes.njk new file mode 100644 index 000000000..0321629d1 --- /dev/null +++ b/src/site/_includes/survey/2022/what-are-we-building/audience-sizes.njk @@ -0,0 +1,62 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "How many users are the websites you're building meant to serve?") }} +

    Percentage of respondents

    +
    +
    + +
    + +
    +
    +
    + +
    Source: Jamstack Community Survey 2020—2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    202020212022
    10s of users63%65%64%
    100s of users78%77%74%
    1000s of users83%79%75%
    100-000s of users58%55%55%
    1-000-000s of users32%32%36%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/what-are-we-building/index.njk b/src/site/_includes/survey/2022/what-are-we-building/index.njk new file mode 100644 index 000000000..ea36e74f3 --- /dev/null +++ b/src/site/_includes/survey/2022/what-are-we-building/index.njk @@ -0,0 +1,84 @@ +
    + {{ + permalinkHeading.render( + "h2", + "What is the Jamstack Community building?", + "", + "what-is-the-jamstack-community-building" + ) + }} + +

    Moving on from demographics, let’s look at what we’re building in 2022.

    + + {{ permalinkHeading.render("h3", "Purposes of websites built") }} + +

    + Most people build lots of sites in a year, so we allowed people to give + multiple answers to our question about what the sites they built were for. + The results were similar to last year: the single most common answer was + personal websites (such as blogs or resumes). Consumer software, B2B + software and e-commerce remained major areas of focus. +

    + + {% include './what-is-the-purpose-of-the-sites-you-built-in-2022.njk' %} + + {{ permalinkHeading.render("h3", "Application types") }} + +

    + Another question we repeated from last year was asking people what kinds of + websites they built. As was the case in 2021, Single Page Apps (SPAs) were + popular, but a majority were various levels of static websites – either + fully or mostly static. This is unsurprising, since the core of Jamstack has + always been progressive enhancement of static websites. +

    + +

    + Fully dynamic websites remain popular for some applications, and this time + we asked about a new category: edge-dynamic sites, which we’re defining here + as sites that are fully dynamic, and render all their content at the edge + (i.e. using serverless functions or edge functions). This is a pretty new + category and so it was also the smallest, but nearly half (47%) said they’d + built at least one website of this kind this year. This tracks the + growth in serverless + we saw in later questions. +

    + + {% include './types-of-sites-built-last-12-months.njk' %} + + {{ permalinkHeading.render("h3", "Target devices") }} + +

    + Another standard question we ask every year is about what devices your work + targets. We’ve used this previously to point out that while “mobile-first” + has been the mantra of the industry for a long time, desktop devices still + have a small edge in terms of being the most important target for our work, + with tablets third. +

    + +

    + However, over the last 3 years our “everything else” category, called + “device-specific browsers” (we suggested things like Internet of Things + devices, or smart watches) has been steadily growing and now fully one-third + of people say this somewhat poorly defined fourth category is at least + somewhat important. This was a surprise! We’ll be conducting follow-up + surveys to discover what exactly the folks who call these devices important + were talking about. +

    + + {% include './target-devices-by-type.njk' %} + + {{ permalinkHeading.render("h3", "Audience sizes") }} + +

    + Our final question about the goals of our websites in 2022 was about + audience sizes: how big is the audience your website serves? This is another + question where we have data from all 3 years of the survey and are able to + see a trend, although not much has changed. The most common type of website + remains one built for a relatively small audience – hundreds, or a few + thousand users. But more than a third of people say they’ve built websites + this year intended for audiences of millions, and this category grew in + 2022. +

    + + {% include './audience-sizes.njk' %} +
    diff --git a/src/site/_includes/survey/2022/what-are-we-building/target-devices-by-type.njk b/src/site/_includes/survey/2022/what-are-we-building/target-devices-by-type.njk new file mode 100644 index 000000000..a2f09aa19 --- /dev/null +++ b/src/site/_includes/survey/2022/what-are-we-building/target-devices-by-type.njk @@ -0,0 +1,62 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "Target devices by type, 2020-2022", 'text-xl font-semibold') }} +

    Percentage of respondents saying these targets were somewhat or very important

    +
    +
    + +
    + +
    +
    +
    + +
    Source: Jamstack Community Survey 2020—2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Type202020212022
    Desktops99%98%97%
    Phones95%94%94%
    Tablets92%91%90%
    Device-specific browsers18%25%34%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/what-are-we-building/types-of-sites-built-last-12-months.njk b/src/site/_includes/survey/2022/what-are-we-building/types-of-sites-built-last-12-months.njk new file mode 100644 index 000000000..88dcca2bb --- /dev/null +++ b/src/site/_includes/survey/2022/what-are-we-building/types-of-sites-built-last-12-months.njk @@ -0,0 +1,73 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "Types of websites built in the last 12 months") }} +

    Percentage of respondents

    +
    +
    +
    + +
    +
    +
    + +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NoneA few projectsMany projectsMost projectsAll
    SPA20%41%15%16%8%
    Fully dynamic28%36%15%15%6%
    Edge-dynamic53%30%9%6%3%
    Mostly static26%43%17%11%3%
    Fully static30%40%15%11%4%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/what-are-we-building/what-is-the-purpose-of-the-sites-you-built-in-2022.njk b/src/site/_includes/survey/2022/what-are-we-building/what-is-the-purpose-of-the-sites-you-built-in-2022.njk new file mode 100644 index 000000000..08341a4b6 --- /dev/null +++ b/src/site/_includes/survey/2022/what-are-we-building/what-is-the-purpose-of-the-sites-you-built-in-2022.njk @@ -0,0 +1,84 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "What is the purpose of the websites you built in 2022?") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PurposePercentage of Survey Participants
    Personal websites45%
    Consumer software40%
    B2B software39%
    E-commerce38%
    Informational38%
    Internal tools37%
    Documentation29%
    Lead capture29%
    Enterprise software26%
    News/Entertainment14%
    Social media14%
    Retail13%
    Games11%
    Streaming media9%
    Politics/Activism5%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/where-are-we-going/index.njk b/src/site/_includes/survey/2022/where-are-we-going/index.njk new file mode 100644 index 000000000..541dd8064 --- /dev/null +++ b/src/site/_includes/survey/2022/where-are-we-going/index.njk @@ -0,0 +1,158 @@ +
    + {{ + permalinkHeading.render( + "h2", + "Emerging Trends in the Jamstack Community", + "", + "emerging-trends-in-the-jamstack-community" + ) + }} + +

    + In addition to the current state of the Jamstack community, we also gathered + some data about emerging trends, and tried to use our data to make some + predictions about where we expect things will go in 2023. +

    + + {{ permalinkHeading.render("h3", "Trends in web frameworks") }} + +

    + The continued dominance of React in the web framework landscape seems set to + continue, and we expect further growth from React and its allied Next.js in + 2023. But React is only one of many possible ways to build a useful website. +

    + +

    + If you’re looking for interactivity with high performance and a low resource + footprint, such as if your user base is primarily mobile, you might want to + look at Astro or Sveltekit. +

    + +

    + As we mentioned already, if you’re building a static or nearly-static site, + we continue to think 11ty is an excellent choice given its growth relative + to other SSGs in the space. +

    + + {{ permalinkHeading.render("h3", "Is Web3 the future?") }} + +

    + We heard a great deal on social media in 2022 about Web3, so we included a + couple of specific questions about Web3 technologies in this year’s survey + (after running a small pre-survey, we did not include the Metaverse in our + definition of Web3, as a majority of respondents did not think of it as part + of Web3). +

    + +

    + Overall, only about 10% of respondents said they had tried out any of the + Web3 technologies we asked about. Applying the same “some or many projects” + standard that we do when counting web frameworks, Web3 technologies did not + cross 3% usage. +

    + + {% include './web3-usage.njk' %} + +

    + Low usage is to be expected in an early technology, so we also asked + sentiment questions. 13% of respondents did not know what Web3 was, while + another third were neutral towards it. Of those who expressed feelings about + Web3, those who were negative about it (31%) slightly outnumbered those who + were positive about it (28%). If we translate this into the satisfaction + score we use elsewhere in the survey, it would be 0.9, and we would expect + Web3 to lose usage share in the coming year. +

    + + {% include './web3-feelings.njk' %} + + {{ permalinkHeading.render("h3", "Web Components have arrived") }} + +

    + Browser-native Web Components were introduced 11 years ago but lacked + support from all major browsers until roughly + 2018. Since then, their adoption has accelerated notably, and while they are + still not in use by the majority of our respondents we believe we can call + them a solid choice in 2022. +

    + +

    + Using the same standards we apply to web frameworks, native Web Components + have usage of 32%. Even more positively, their Satisfaction Score is 4.3, so + we expect rapid growth in the adoption of web components in 2023. +

    + + {% include './web-components.njk' %} + + {{ permalinkHeading.render("h3", "Jamstack is Increasingly Serverless") }} + +

    + The final trend we covered was the growth in serverless technology, + sometimes also called edge computing. Last year we were taken somewhat by + surprise to learn that serverless adoption had hit 46%, so this year we made + sure to ask a more detailed question. +

    + +

    + Using the standard we used last year of any adoption at all, serverless + usage jumped from 46% to 71%. We expected growth, but that was much faster + than we predicted. Applying our usual standard of “some+many” projects we + use for web frameworks, serverless technology is at 35% adoption, which + relative to frameworks would make it bigger than Vue but smaller than + Next.js. +

    + +

    + We mentioned above that there was a big shift in the last year of people + describing themselves as “full stack” developers from “front end” + developers. We think the big jump in serverless adoption may be the + explanation: serverless lets front-end developers build full-stack + applications with a minimum of fuss, and the adoption has been so fast it’s + changing how we describe ourselves. +

    + +

    + Given the rapid growth since last year, we expect to see further growth in + adoption and especially users moving from the “few projects” category into + more serious usage. +

    + + {% include './serverless.njk' %} + +

    + Jamstack remains the standard architecture of the web +

    + +

    + The evolution of the web as a platform continues to be rapid and exciting, + with new technologies pushing the boundaries of what the web can do and how + quickly developers can ship. We’ve also learned more about our community as + human beings: where they are, who they are, and what motivates them. +

    + +

    + We hope giving you a sense of the community you’re part of and the + technologies that your peers use gives you a sense of place and some ideas + about where you should put your time and energy in the next year. +

    + +

    + Once again, we’d like to thank everybody who participated in the community + survey. +

    + + +
    diff --git a/src/site/_includes/survey/2022/where-are-we-going/serverless.njk b/src/site/_includes/survey/2022/where-are-we-going/serverless.njk new file mode 100644 index 000000000..b0c42ca28 --- /dev/null +++ b/src/site/_includes/survey/2022/where-are-we-going/serverless.njk @@ -0,0 +1,45 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "How many websites you've built this year have used serverless functions?") }} +

    Percentage of respondents

    +
    +
    +
    + +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Count
    None30%
    A few projects36%
    Some projects18%
    Many projects12%
    All5%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/where-are-we-going/web-components.njk b/src/site/_includes/survey/2022/where-are-we-going/web-components.njk new file mode 100644 index 000000000..09fbf9448 --- /dev/null +++ b/src/site/_includes/survey/2022/where-are-we-going/web-components.njk @@ -0,0 +1,52 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "How much have you used Web Components in the last 12 months?") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Count
    Not aware of them23%
    Rarely and don't want to16%
    Rarely but want more29%
    Some and want fewer5%
    Some and want more19%
    Many and want fewer1%
    Many and want more7%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/where-are-we-going/web3-feelings.njk b/src/site/_includes/survey/2022/where-are-we-going/web3-feelings.njk new file mode 100644 index 000000000..c3afe692b --- /dev/null +++ b/src/site/_includes/survey/2022/where-are-we-going/web3-feelings.njk @@ -0,0 +1,48 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "In general, how do you feel about Web3?") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    In general, how do you feel about Web3?Count
    I don't know what it is13%
    Strongly negative18%
    Negative13%
    Neutral29%
    Positive20%
    Strongly positive8%
    +
    +
    diff --git a/src/site/_includes/survey/2022/where-are-we-going/web3-usage.njk b/src/site/_includes/survey/2022/where-are-we-going/web3-usage.njk new file mode 100644 index 000000000..6e579c002 --- /dev/null +++ b/src/site/_includes/survey/2022/where-are-we-going/web3-usage.njk @@ -0,0 +1,89 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "Which Web3 technologies did you use in the last 12 months?") }} +

    Percentage of respondents

    +
    +
    +
    + +
    +
    +
    + +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NoneA few projectsMany projectsMost projectsAll
    Bitcoin89%7%1%1%1%
    Ethereum87%9%1%1%1%
    Solana93%4%1%1%1%
    Other blockchain89%7%1%1%1%
    DAOs93%4%1%1%1%
    Other dApps90%6%2%1%1%
    NFTs86%10%2%1%1%
    +
    +
    diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/employment-status.njk b/src/site/_includes/survey/2022/whos-doing-the-building/employment-status.njk new file mode 100644 index 000000000..18263c682 --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/employment-status.njk @@ -0,0 +1,52 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "What's your employment status?") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Employment StatusPercentage of Survey Participants
    Full-time50%
    Student21%
    Self-employed13%
    Contractor6%
    Part-time5%
    Between jobs5%
    Retired1%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/experience-by-region.njk b/src/site/_includes/survey/2022/whos-doing-the-building/experience-by-region.njk new file mode 100644 index 000000000..88238164d --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/experience-by-region.njk @@ -0,0 +1,155 @@ +
    +
    +
    + {{ permalinkHeading.render('h5', "Experience by region") }} +

    Percentage of respondents

    +
    +
    +
    + +
    +
    +
    + +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Years of experienceAfricaAsia PacificCentral AmericaEastern AsiaEuropeMiddle EastNorth AmericaSouth AmericaSouthern AsiaCaribbean
    < 19.3%21.1%0.5%3.6%21.7%2.1%21.7%7.2%12.9%0.0%
    1-212.4%16.4%1.2%0.7%27.9%0.9%21.6%5.9%12.0%0.9%
    3-48.4%13.1%1.3%2.2%37.4%2.2%24.5%4.5%5.4%1.1%
    5-65.7%12.9%2.5%2.0%34.5%2.2%28.3%6.2%3.7%2.0%
    7-83.7%6.7%0.7%1.9%39.6%0.7%37.0%3.0%5.6%1.1%
    9-102.5%5.8%1.1%0.4%42.4%0.7%40.6%4.7%1.1%0.7%
    11-123.8%5.0%0.6%1.3%51.9%1.3%32.5%3.1%0.6%0.0%
    13-143.5%8.1%0.0%0.0%39.1%5.8%35.6%2.3%5.8%0.0%
    15+0.7%8.0%0.5%1.1%40.3%1.5%44.1%2.0%1.3%0.5%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/experience-increasing-over-time.njk b/src/site/_includes/survey/2022/whos-doing-the-building/experience-increasing-over-time.njk new file mode 100644 index 000000000..34acd9653 --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/experience-increasing-over-time.njk @@ -0,0 +1,94 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "Experience increasing over time") }} +

    Years of experience relevant to current job, 2020-2022

    +
    +

    Percentage of respondents

    +
    + +
    + +
    +
    +
    + + +
    Source: Jamstack Community Survey 2020—2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Years of experience202020212022
    < 14%13%8%
    1-213%19%16%
    3-420%18%16%
    5-615%12%14%
    7-89%7%9%
    9-1012%8%9%
    11-128%5%5%
    13-145%3%3%
    15+14%14%19%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/have-you-changed-jobs-in-the-last-12-months.njk b/src/site/_includes/survey/2022/whos-doing-the-building/have-you-changed-jobs-in-the-last-12-months.njk new file mode 100644 index 000000000..48fd52739 --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/have-you-changed-jobs-in-the-last-12-months.njk @@ -0,0 +1,32 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "Have you changed jobs in the last 12 months?") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + +
    Have you changed jobs in the last 12 months?Count
    No67%
    Yes33%
    +
    +
    diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/i-changed-jobs-to-work-remotely-more-often.njk b/src/site/_includes/survey/2022/whos-doing-the-building/i-changed-jobs-to-work-remotely-more-often.njk new file mode 100644 index 000000000..c1e4dceb6 --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/i-changed-jobs-to-work-remotely-more-often.njk @@ -0,0 +1,44 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "I changed jobs to work remotely more often") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Percentage of Survey Participants
    Strongly disagree23%
    Somewhat disagree8%
    Neither agree nor disagree34%
    Somewhat agree12%
    Strongly agree23%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/i-enjoy-remote-work.njk b/src/site/_includes/survey/2022/whos-doing-the-building/i-enjoy-remote-work.njk new file mode 100644 index 000000000..cabec9216 --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/i-enjoy-remote-work.njk @@ -0,0 +1,44 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "I enjoy remote work") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Percentage of Survey Participants
    Strongly disagree3%
    Somewhat disagree4%
    Neither agree nor disagree7%
    Somewhat agree26%
    Strongly agree61%
    +
    +
    diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/i-would-like-to-work-remote-more-often.njk b/src/site/_includes/survey/2022/whos-doing-the-building/i-would-like-to-work-remote-more-often.njk new file mode 100644 index 000000000..a41891b56 --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/i-would-like-to-work-remote-more-often.njk @@ -0,0 +1,44 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "I would like to work remotely more often") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Percentage of Survey Participants
    Strongly disagree5%
    Somewhat disagree8%
    Neither agree nor disagree28%
    Somewhat agree16%
    Strongly agree43%
    +
    +
    diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/i-would-quit-my-job-if-in-person-was-more-often.njk b/src/site/_includes/survey/2022/whos-doing-the-building/i-would-quit-my-job-if-in-person-was-more-often.njk new file mode 100644 index 000000000..dafbc7cec --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/i-would-quit-my-job-if-in-person-was-more-often.njk @@ -0,0 +1,44 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "I would quit my job if they made me work in person more often") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Percentage of Survey Participants
    Strongly disagree12%
    Somewhat disagree12%
    Neither agree nor disagree20%
    Somewhat agree27%
    Strongly agree28%
    +
    +
    diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/i-would-quit-my-job-if-remote-was-more-often.njk b/src/site/_includes/survey/2022/whos-doing-the-building/i-would-quit-my-job-if-remote-was-more-often.njk new file mode 100644 index 000000000..8132c1a68 --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/i-would-quit-my-job-if-remote-was-more-often.njk @@ -0,0 +1,44 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "I would quit my job if they made me work remotely more often") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Percentage of Survey Participants
    Strongly disagree65%
    Somewhat disagree11%
    Neither agree nor disagree13%
    Somewhat agree5%
    Strongly agree6%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/index.njk b/src/site/_includes/survey/2022/whos-doing-the-building/index.njk new file mode 100644 index 000000000..0305bd1a9 --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/index.njk @@ -0,0 +1,235 @@ +
    + + {{ permalinkHeading.render("h2", "Who’s doing the building?", "", "whos-doing-the-building") }} + +

    + As usual, we kick off by looking at the demographics of our community. Who + are we, exactly? +

    + + + {{ permalinkHeading.render("h3", "Job titles") }} + +

    + There was not much change in the breakdown of reported job titles in our + survey this year: as usual, nearly everyone (84%) who responded considers + themselves to be an engineer of some kind. There was one curious change, + however: the number of people calling themselves “full stack” versus “front + end” has almost exactly flipped, from 32% full stack and 45% front end last + year to 44% full stack and 33% front end in the 2022 survey. None of the + other demographic markers we tracked changed very much, so we believe this + is a real shift in how the community thinks of itself. We have two theories + about why this might be the case, and we’ll discuss them in the sections on + job changes and serverless. +

    + + + {% include "./job-title-2021-vs-2022.njk" %} + + + {{ permalinkHeading.render("h3", "Employment status") }} + +

    + This year when asking about employment status we added a new category, + “self-employed”, which meant that the results are not totally comparable to + last year. A bunch of people who last year described themselves as + “full-time” switched to the “self-employed” category, which probably doesn’t + describe an actual change in status but more accurately describes what they + already were. Students continue to be the second-biggest group in the + community, at 21% of all respondents. As we said last year, this is a + solidly positive sign for a community: the Jamstack remains a popular way to + on-board students at bootcamps into deploying websites for the first time, + and becoming the “default” way to build a website means the Jamstack can + expect to enjoy growth for years to come. +

    + + + {% include "./employment-status.njk" %} + + {{ permalinkHeading.render("h3", "Working experience") }} + +

    + When asking about our community’s level of working experience, we saw a + continuing trend from 2020 and 2021: the community is slowly increasing in + experience. 2021 was our biggest year for new community members, and you can + see that cohort moving up by 1 year of experience in this chart. In 2022, + nearly 1 in 5 developers say they have been working in their current career + for 15 or more years. +

    + + + {% include "./experience-increasing-over-time.njk" %} + + + {{ permalinkHeading.render("h4", "Increasing geographical diversity") }} + +

    + Repeating a phenomenon we first noticed last year, the geographical + diversity of our respondents has a strong correlation to their level of + career experience. In the most experienced group, 84% of respondents come + from either North America or Europe. In our newest group, those with less + than a year of experience, that falls to just 43%. That means in 2022 for + the first time, more than half of people who joined the Jamstack community + came from outside of the two big regions! +

    + +

    + An explanation for this correlation that we find persuasive is that access + to technology is continuing to improve worldwide, leading to increased + geographical diversity. We think this is an encouraging trend, and hope that + it will lead to greater diversity in other dimensions as well. +

    + + + {% include "./experience-by-region.njk" %} + +

    + Every region outside of Europe and North America grew in share. The + fastest-growing region was Africa, which jumped from 4% of respondents to 8% + from 2021 to 2022. This author is also delighted to note that his home + region, the Caribbean, went from 0.5% to 1% in the same period. +

    + + + {% include "./respondents-by-region.njk" %} + + + {{ permalinkHeading.render("h3", "The Great Resignation") }} + +

    + A phenomenon that gained a great deal of attention in 2021 was a spike in + the number of people changing jobs, which has become known as The Great + Resignation. We were interested to get hard numbers on the reality of this + change, and we were not disappointed: fully one-third of our respondents + reported that they changed jobs in the last year, a huge shift. In our job + titles data we saw a big change in job titles, with 11% switching from + front-end to full-stack roles, a change that seems totally plausible in the + context of a community where 33% of people changed jobs. +

    + + + {% include "./have-you-changed-jobs-in-the-last-12-months.njk" %} + + + {{ permalinkHeading.render("h4", "Why people stay") }} + +

    + We had a second question about the great resignation asking people what + motivated their behavior – either why they stayed, or why they left. The + biggest reason people kept their jobs will be no surprise: people stay if + they like their team. Humans are social animals, and a team you love makes + work more bearable. +

    + +

    + A more surprising finding was that the number two reason, as measured by + those who called it “extremely important”, was remote work. People really, + really like working remotely. Money was important, but it was only the + fifth-biggest reason people stayed where they were. Career growth was also a + very important reason to stay. +

    + + + {% include "./what-influenced-staying.njk" %} + + + {{ permalinkHeading.render("h4", "Why people leave") }} + +

    + Why people left jobs was even heavier on remote work: being able to work + remotely at the new job was the number one reason people left their jobs in + our community, as measured by the number of people saying it was an + “extremely important” reason. Growing in your career came in second when + measured in this way, though if you include people who called things “very” + important in addition to “extremely” important it came first. Company + culture, bad teams, and not enough money came next. +

    + + + {% include "./what-influenced-leaving.njk" %} + + + {{ permalinkHeading.render("h3", "Remote work") }} + +

    + Given that one-third of respondents changed jobs in the last year and many + indicated that remote work was their primary reason for either staying or + leaving a company, our next finding makes sense: a startling 83% of our + respondents say they work remotely at least half of the time. Three in five + (62%) work remotely at least 90% of the time, which we’re going to call + “full time remote”. In last year’s survey about a third said their job had + gone full-time remote, and we know from earlier surveys (such as + GitHub’s Octoverse report) that about a third of people were already working remotely before the + pandemic, so this is roughly double the pre-pandemic numbers. +

    + + + {% include "./remote-frequency.njk" %} + + + {{ permalinkHeading.render("h4", "Changes in remote work") }} + +

    + Since a lot of remote work was driven by the pandemic and offices around the + world are still in the process of reopening, we thought it was fair to ask + whether or not this new state was going to be permanent, or whether people + were returning to offices, but slowly. +

    + +

    + The clear response was that remote work is here to stay. A solid majority + (76%) of respondents said their frequency of remote work had either stayed + the same or increased in the last year. Indeed the strongest signal is that + this is the new normal: 52% of people said nothing changed about their + remote working situation, and the ratio of those working remotely more often + versus less often was just 1.04, meaning only a small net change. +

    + + + {% include "./remote-changes.njk" %} + + + {{ permalinkHeading.render("h4", "Attitudes to remote work") }} + +

    + We also asked our community about their attitudes to various aspects of + remote work. 87% of respondents say they enjoy remote work, but only 71% say + their company has remote work “figured out”, which implies there’s 16% of + people enjoying remote work even though they believe their company doesn’t + do it very well. +

    + +
    + + {% include "./i-enjoy-remote-work.njk" %} + {% include "./my-company-has-remote-work-figured-out.njk" %} +
    + +

    + As we suspected from the job change data, the number of people who would + like to work remotely even more often than they currently do is high: 59%. + And the number saying they changed jobs specifically to be able to work + remotely more often is 35%. That is a huge amount of change, and a strong + motivator. +

    + +
    + + {% include "./i-would-like-to-work-remote-more-often.njk" %} + {% include "./i-changed-jobs-to-work-remotely-more-often.njk" %} +
    + +

    + Our final pair of questions about remote work determined two things: first, + we confirmed that it’s not just that people hate when their working + conditions change: asked if they would quit their jobs if asked to work + remotely more often, only 11% said they would, while 55% of respondents said + they would quit their jobs rather than work remotely less often. +

    + +
    + + {% include "./i-would-quit-my-job-if-remote-was-more-often.njk" %} + {% include "./i-would-quit-my-job-if-in-person-was-more-often.njk" %} +
    +
    diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/job-title-2021-vs-2022.njk b/src/site/_includes/survey/2022/whos-doing-the-building/job-title-2021-vs-2022.njk new file mode 100644 index 000000000..d1bd78961 --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/job-title-2021-vs-2022.njk @@ -0,0 +1,68 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', 'Job titles, 2021 vs. 2022') }} +

    Percentage of respondents

    +
    +
    +
    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Job Title20212022
    Developer (full-stack)32%44%
    Developer (front-end)45%33%
    Developer (back-end)5%5%
    Designer4%4%
    Manager6%4%
    Executive/Business owner4%
    Content producer2%3%
    DevOps2%2%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/my-company-has-remote-work-figured-out.njk b/src/site/_includes/survey/2022/whos-doing-the-building/my-company-has-remote-work-figured-out.njk new file mode 100644 index 000000000..2a412e928 --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/my-company-has-remote-work-figured-out.njk @@ -0,0 +1,44 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "My company has remote work figured out") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Percentage of Survey Participants
    Strongly disagree6%
    Somewhat disagree9%
    Neither agree nor disagree14%
    Somewhat agree32%
    Strongly agree39%
    +
    +
    diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/remote-changes.njk b/src/site/_includes/survey/2022/whos-doing-the-building/remote-changes.njk new file mode 100644 index 000000000..4d812daa1 --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/remote-changes.njk @@ -0,0 +1,44 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "Has your frequency of remote work changed in the last 12 months?") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FrequencyPercentage of Survey Participants
    Lots more in office7%
    Slightly more in office16%
    No changes52%
    Slighty more remote9%
    Lots more remote15%
    +
    +
    diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/remote-frequency.njk b/src/site/_includes/survey/2022/whos-doing-the-building/remote-frequency.njk new file mode 100644 index 000000000..e3ad238ee --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/remote-frequency.njk @@ -0,0 +1,56 @@ +
    +
    +
    + {{ permalinkHeading.render('h4', "What percentage of your time do you work remotely?") }} +

    Percentage of respondents

    +
    +
    +
    +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FrequencyPercentage of Survey Participants
    0%3%
    1-9%4%
    10-24%5%
    25-49%5%
    50-74%9%
    75-89%12%
    90-99%23%
    100%39%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/respondents-by-region.njk b/src/site/_includes/survey/2022/whos-doing-the-building/respondents-by-region.njk new file mode 100644 index 000000000..74ca67f4e --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/respondents-by-region.njk @@ -0,0 +1,82 @@ +
    +
    +
    + {{ permalinkHeading.render("h5", "Respondents by region") }} +

    Percentage of respondents

    +
    + +
    +
    +
    +
    Source: Jamstack Community Survey 2021—2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Employment Status20212022
    Europe39%33%
    North America31%28%
    All Asia18%19%
    Asia Pacific11%12%
    Africa4%8%
    Southern Asia6%8%
    South America5%5%
    Eastern Asia1%2%
    Middle East1%2%
    Central America1%1%
    Caribbean1%1%
    +
    +
    diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/what-influenced-leaving.njk b/src/site/_includes/survey/2022/whos-doing-the-building/what-influenced-leaving.njk new file mode 100644 index 000000000..94e15ccdd --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/what-influenced-leaving.njk @@ -0,0 +1,114 @@ +
    +
    +
    + {{ permalinkHeading.render('h5', "Why did you leave your job?") }} +

    Percentage of respondents

    +
    +
    + +
    + +
    +
    +
    + +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Not at all importantSlightly importantModerately importantVery importantExtremely important
    Remote work6%6%18%30%41%
    Career growth3%5%18%35%39%
    Company culture4%6%21%38%31%
    Team4%6%21%38%31%
    Money4%5%20%40%30%
    My manager6%9%24%34%26%
    Corporate ethics6%9%25%36%25%
    Technology choices4%7%25%42%22%
    Environmental impact15%16%30%25%14%
    Involuntary36%10%28%15%11%
    +
    +
    \ No newline at end of file diff --git a/src/site/_includes/survey/2022/whos-doing-the-building/what-influenced-staying.njk b/src/site/_includes/survey/2022/whos-doing-the-building/what-influenced-staying.njk new file mode 100644 index 000000000..2e1377e18 --- /dev/null +++ b/src/site/_includes/survey/2022/whos-doing-the-building/what-influenced-staying.njk @@ -0,0 +1,114 @@ +
    +
    +
    + {{ permalinkHeading.render('h5', "Why did you stay in your job?") }} +

    Percentage of respondents

    +
    +
    + +
    + +
    +
    +
    + +
    Source: Jamstack Community Survey 2022
    + +
    + Show Chart Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Not at all importantSlightly importantModerately importantVery importantExtremely important
    Team3%5%19%40%34%
    Remote work5%9%22%32%32%
    Career growth3%6%21%39%31%
    Company culture4%8%21%38%29%
    Money3%6%25%39%28%
    Corporate ethics6%9%24%37%25%
    My manager6%7%24%38%24%
    Technology choices2%7%24%44%23%
    Environmental impact14%16%30%26%14%
    Involuntary31%10%34%15%10%
    +
    +
    \ No newline at end of file diff --git a/src/site/code-of-conduct.njk b/src/site/code-of-conduct.njk index 7353d28d2..05a0825d9 100644 --- a/src/site/code-of-conduct.njk +++ b/src/site/code-of-conduct.njk @@ -20,20 +20,20 @@ layout: layouts/base.njk We are a diverse, global community. We have members from every gender, sexuality, gender expression, race, ethnicity, tribal background, religious affiliation, citizenship and nationality, age, physical ability, educational background, experience level that you can think of - and want everyone to feel welcome!

    - Whether it is contributing to our website, connecting in our Discord, or attending conferences and independently organized meetings, it's not only our hope, but our expectation, that you'll abide by these three basic ground rules: + Whether it is contributing to our website or attending conferences and independently organized meetings, it's not only our hope, but our expectation, that you'll abide by these three basic ground rules:

    • -

      We approach interactions with thoughtfulness and care.

      +

      We approach interactions with thoughtfulness and care.

      We are patient & kind to others. We don't dismiss someone because they have a different level of experience, are of a different background, or have a difference of opinion than us.
    • -

      We are respectful when we disagree with someone.

      +

      We are respectful when we disagree with someone.

      There is a human in front of us. We don’t allow frustration to turn into a personal attack. A community where people feel uncomfortable or threatened is not a productive one. We are collectively responsible for each other.
    • -

      We work to correct mistakes when they occur.

      +

      We work to correct mistakes when they occur.

      No one is expected to always be perfect or to know everything - sometimes even good intentions have unwanted outcomes. But how we respond to criticism is important. If someone criticizes our conduct, or points out ways we have harmed someone, we listen without taking it personally, and work towards a resolution - together.
    @@ -52,7 +52,7 @@ layout: layouts/base.njk
  • Advocating for or encouraging any of the above behaviors.
  • - If soeone has said or done something that violates this code of conduct, please refer to the Resolving Code of Conduct Violations section. + If someone has said or done something that violates this code of conduct, please refer to the Resolving Code of Conduct Violations section.

    @@ -68,7 +68,7 @@ layout: layouts/base.njk -
    +{#

    Discord Code of Conduct

    • Written communication is easy to misinterpret. Ask for clarification before jumping to conclusions.
    • @@ -79,7 +79,7 @@ layout: layouts/base.njk
    • Limit your use of `@channel` and `@here` as this notifies everyone across global timezones.
    • Select the most appropriate channel for your conversation and avoid reposting the same messages across many channels in ways which may feel spammy.
    -
    +
    #}

    Group Meeting Code of Conduct

    @@ -106,15 +106,21 @@ layout: layouts/base.njk

      -
    • In the moment:

      Are you safe? If not, get to a safe place. Can you talk to someone you trust about what happened?
    • -
    • As soon as possible:

      evaluate the situation - is it safe to talk to the person directly about this incident? +
    • +

      In the moment:

      Are you safe? If not, get to a safe place. Can you talk to someone you trust about what happened?
    • +
    • +

      As soon as possible:

      evaluate the situation - is it safe to talk to the person directly about this incident?
        -
      • If it seems safe

        , try and speak with the person privately, in person if you can. Try and work to a resolution together. If it goes well, great! If it does not go well, please speak to an organizer as soon as you can. -
      • If it does not seem safe

        , or the conversation did not go well, speak to an organizer about what happened as soon as you can.
      • +
      • +

        If it seems safe

        , try and speak with the person privately, in person if you can. Try and work to a resolution together. If it goes well, great! If it does not go well, please speak to an organizer as soon as you can. + + +
      • +

        If it does not seem safe

        , or the conversation did not go well, speak to an organizer about what happened as soon as you can.
    • -

      After the fact:

      Organizers are familiar with this Code of Conduct and will take reports of CoC violations seriously. They will ask you details such as: +

      After the fact:

      Organizers are familiar with this Code of Conduct and will take reports of CoC violations seriously. They will ask you details such as:
      • time / place / manner of the alleged violation.
      • whether there were additional witnesses or other people involved.
      • @@ -137,12 +143,6 @@ layout: layouts/base.njk

        If you'd like to report a CoC violation that happens...

          -
        • - in the Jamstack Discord, please speak to Domitrius Clark, or Phil Hawksworth by contacting us in the Jamstack Discord . Please do note we may be out of your timezone, but we will respond. -
        • -
        • - on Jamstack.org, for example in a PR, please speak to Phil Hawksworth or Perry Eising by contacting us in the Jamstack Discord or via email. Our emails are our first names @ netlify.com -
        • at an in person meeting that is organized by your local city, please speak with the organizers, who will reach out to us via Discord or email.
        • @@ -157,4 +157,4 @@ layout: layouts/base.njk

          Depending on the severity of the issue, the person may receive a last-chance warning, may be asked to leave the activity/group, or may receive a future ban. In very severe cases, we reserve the right to involve law enforcement should we feel it necessary. Tickets or other participation fees won't be refunded for people who are removed for CoC violations.

          -
    + \ No newline at end of file diff --git a/src/site/community.njk b/src/site/community.njk index 44632f5bf..ca9e577e5 100644 --- a/src/site/community.njk +++ b/src/site/community.njk @@ -10,29 +10,11 @@ layout: layouts/base.njk

    Join the global community

    Our community members gather in many channels, online and in-person around the world, to learn about modern web development techniques and emerging technologies. Join the community to exchange ideas, find new opportunities and help build a better web.

    - -
    - {{ zinger.cta( - "Join the Discord", - "https://jamstack.org/discord", - "bg-gradient-card-sunrise card-shadow hover:card-shadow-sunrise", - "#logo-discord" - ) - }} - {{ zinger.cta( - "JamstackConf Twitter", - "https://twitter.com/jamstackconf", - "bg-gradient-card-blue-seafoam card-shadow hover:card-shadow-blue-seafoam", - "#logo-twitter" - ) - }} -
    -

    +

    To participate in our community channels, it’s important to read and follow our Code of Conduct

    -

    Community Mission

    @@ -46,28 +28,6 @@ layout: layouts/base.njk

    -{% if meetups.length > 0 %} -
    -

    Upcoming Events

    - - - - - - - - - - - {% for meetup, data in meetups %} - {% set item = meetup %} - {% include "components/meetup-link.njk" %} - {% endfor %} - -
    -
    -{% endif %} -

    Find a Local User Group

    @@ -78,10 +38,10 @@ layout: layouts/base.njk
    @@ -95,12 +55,12 @@ layout: layouts/base.njk %} {%- for item in community | sort(false, false, 'name') %} - {# chose a pseudorandom theme based on the city name #} - {% set theme = item.name.length % 4 %} -
  • - {{ meetup.card( item.name, item.link, themes[theme] ) }} -
  • + {{ meetup.card( item.name, item.link, themes[theme] ) }} + {%- endfor %} @@ -112,5 +72,4 @@ layout: layouts/base.njk Don’t see a group near you? We love to support new user group leaders and help grow the community. We can connect you with other user group leaders to share content ideas, provide stickers and swag, and get you listed on the user group page.

    Learn how to get started - - + \ No newline at end of file diff --git a/src/site/css/hubspot-form.css b/src/site/css/hubspot-form.css index a79bdf72d..b9ab33639 100644 --- a/src/site/css/hubspot-form.css +++ b/src/site/css/hubspot-form.css @@ -1,10 +1,10 @@ .hs-form .hs-input { @apply mb-3; @apply p-2; - @apply bg-gray-400; + @apply dark:bg-gray-400; @apply w-full; - color: white; + @apply dark:text-white; } .hs-form .hs-form-required { @@ -18,7 +18,7 @@ } .hs-form .hs-form-field label.hs-error-msg { - @apply text-red-300; + @apply text-red-500 dark:text-red-300; @apply text-sm; @apply font-normal; } diff --git a/src/site/css/ticker.css b/src/site/css/ticker.css index 57dafda98..150cbf566 100644 --- a/src/site/css/ticker.css +++ b/src/site/css/ticker.css @@ -1,39 +1,46 @@ .logos-ticker { --speed: 90s; + --gap: 3em; display: flex; - flex-wrap: nowrap; overflow: hidden; - position: relative; -} - -.logos-ticker-fade { - background: linear-gradient(to right, rgba(22, 26, 43, 1) 0%, rgba(22, 26, 43, .01) 20%, rgba(22, 26, 43, .01) 80%, rgba(22, 26, 43, 1) 100%); - height: 100%; - left: 0; - position: absolute; - top: 0; - width: 100%; - z-index: 2; + user-select: none; + gap: var(--gap); + mask-image: linear-gradient( + to right, + hsl(0 0% 0% / 0), + hsl(0 0% 0% / 1) 20%, + hsl(0 0% 0% / 1) 80%, + hsl(0 0% 0% / 0) + ); } .logos-ticker-container { + flex-shrink: 0; display: flex; align-items: center; - justify-content: center; + justify-content: space-around; + gap: var(--gap); + min-width: 100%; animation: slide var(--speed) linear infinite; } + .logos-ticker-container svg { - fill: #646E73; - margin: 0 1.5em; + flex: 0 0 auto; + fill: #646e73; } -@keyframes slide { - 0% { - transform: translate3d(0, 0, 0); +@media (prefers-reduced-motion: reduce) { + .logos-ticker-container { + animation-play-state: paused; } +} - 100% { - transform: translate3d(-100%, 0, 0); +@keyframes slide { + from { + transform: translateX(0); + } + to { + transform: translateX(calc(-100% - var(--gap, 0px))); } } diff --git a/src/site/generators.njk b/src/site/generators.njk index f1534bfcd..966b189ab 100644 --- a/src/site/generators.njk +++ b/src/site/generators.njk @@ -24,7 +24,7 @@ layout: layouts/base.njk
    + \ No newline at end of file diff --git a/src/site/logos.njk b/src/site/logos.njk index 9051b2a24..108642d67 100644 --- a/src/site/logos.njk +++ b/src/site/logos.njk @@ -6,7 +6,7 @@ layout: layouts/base.njk

    Logos

    - Offical Jamstack logos and uages guidelines. + Offical Jamstack logos and usage guidelines.

    diff --git a/src/site/resources/index.njk b/src/site/resources/index.njk index bd498fa69..5532a95c7 100644 --- a/src/site/resources/index.njk +++ b/src/site/resources/index.njk @@ -38,7 +38,7 @@ layout: layouts/base.njk More videos and presentations -
    +

    Articles and resources

    Selected articles and case studies of Jamstack implementations. @@ -51,5 +51,3 @@ layout: layouts/base.njk More articles and resources

    - -{% include "components/join-the-movement.njk" %} diff --git a/src/site/survey-closed.njk b/src/site/survey-closed.njk index 7efb933f8..b0a4f4aa2 100644 --- a/src/site/survey-closed.njk +++ b/src/site/survey-closed.njk @@ -18,5 +18,4 @@ excludeFromSitemap: true

    Jamstack Sticker Giveaway

    Oh no! We’ve maxed out on our sticker giveaway.

    Thank you for taking the time to complete the survey. Please continue to share with your network, communities, and teams.

    -

    Ready to connect with others in the Jamstack Community? Join the Jamstack Discord!

    -
    \ No newline at end of file + diff --git a/src/site/survey-confirmation.njk b/src/site/survey-confirmation.njk index 0eeaf6e82..a6e3a5547 100644 --- a/src/site/survey-confirmation.njk +++ b/src/site/survey-confirmation.njk @@ -33,7 +33,7 @@ layout: layouts/base.njk

    Stickers will be sent after campaign has ended, or maximum has been reached. Stickers will be shipped with U.S. domestic or international postage stamps. Delivery is not guaranteed or trackable.

    -
    - Register for Jamstack Conf! +
    + Register for Jamstack Conf!
    diff --git a/src/site/survey-thanks.njk b/src/site/survey-thanks.njk index 78c4693f2..f4ccbfea2 100644 --- a/src/site/survey-thanks.njk +++ b/src/site/survey-thanks.njk @@ -8,5 +8,4 @@ excludeFromSitemap: true

    We’ve received your sticker request!

    Stickers will be sent after campaign has ended, or maximum has been reached. Stickers will be shipped with U.S. domestic or international postage stamps. Delivery is not guarenteed or trackable.

    -

    Ready to connect with others in the Jamstack Community? Join the Jamstack Discord!

    -
    \ No newline at end of file + diff --git a/src/site/survey/2021.njk b/src/site/survey/2021.njk index 03441a31d..f40a4367d 100644 --- a/src/site/survey/2021.njk +++ b/src/site/survey/2021.njk @@ -61,21 +61,21 @@ gradientColors:

    To the developers who took the time to respond to our survey to help us learn more about the Jamstack and our community: Thank you. We hope you walk away from this report learning something about yourselves and the expanding Jamstack community.

    @@ -84,27 +84,27 @@ gradientColors:

    Demographics

    - {% include "survey/demographics.njk" %} + {% include "survey/2021/demographics.njk" %} - {% include "survey/experience.njk" %} + {% include "survey/2021/experience.njk" %}

    Jamstack adoption

    - {% include "survey/adoption.njk" %} + {% include "survey/2021/adoption.njk" %}

    Workflows

    - {% include "survey/workflows.njk" %} + {% include "survey/2021/workflows.njk" %}

    Technology choices

    - {% include "survey/choices.njk" %} + {% include "survey/2021/choices.njk" %}

    Jamstack has become the standard architecture for the web.

    - {% include "survey/conclusion.njk" %} + {% include "survey/2021/conclusion.njk" %}
    \ No newline at end of file diff --git a/src/site/survey/2021/d3chart-survey.js b/src/site/survey/2021/d3chart-survey-2021.js similarity index 100% rename from src/site/survey/2021/d3chart-survey.js rename to src/site/survey/2021/d3chart-survey-2021.js diff --git a/src/site/survey/2021/d3chart.js b/src/site/survey/2021/d3chart.js deleted file mode 100644 index 9f104699b..000000000 --- a/src/site/survey/2021/d3chart.js +++ /dev/null @@ -1,934 +0,0 @@ -class D3Chart { - constructor(targetId, options, className) { - this.targetId = targetId; - this.className = className; - - this.options = Object.assign({ - showInlineBarValues: "inside", // inside, inside-offset, and outside supported - showLegend: true, - showAxisLabels: false, - margin: {}, - colors: [ - "#F0047F", - "#00BFAD", - "#FFC803", - "#78ECC2", - "#DF4A1F", - "#FD98BC", - "#6B38FB", - "#03D0D0", - "#C40468", - "#78F19A", - "#91A5EE", - "#02C6B3", - "#FF0F00", - "#003EDD", - "#02465F", - "#960000", - "#FF72CF", - ], - // only applies when `showInlineBarValues: "inside"` - labelColors: [ - "#fff", - "#000", - "#000", - "#000", - "#000", - "#000", - "#000", - "#000", - "#fff", - "#000", - "#000", - "#000", - "#000", - "#000", - "#000", - "#fff", - "#fff", - ], - colorMod: 0, - inlineLabelPad: 5, - labelPrecision: 0, - // TODO make this automatic by parsing `%` signs - valueType: ["percentage"], - sortLegend: false, - highlightElementsFromLegend: false - }, options); - - this.options.colors = this.normalizeColors(this.options.colors, this.options.colorMod); - this.options.labelColors = this.normalizeColors(this.options.labelColors, this.options.colorMod); - } - - onResize(callback) { - if (!("ResizeObserver" in window)) { - window.addEventListener("resize", () => { - callback.call(this); - }); - return; - } - - let resizeObserver = new ResizeObserver(entries => { - for (let entry of entries) { - // console.log( "resizing", this.target ); - callback.call(this); - } - }); - - resizeObserver.observe(this.target); - } - - onDeferInit(callback) { - if (!('IntersectionObserver' in window)) { - callback.call(this); - return; - } - - let observer = new IntersectionObserver((entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - // console.log( "initing", this.target ); - callback.call(this); - observer.unobserve(entry.target); - } - }); - }, { - threshold: .1 - }); - - observer.observe(this.target); - } - - normalizeColors(colors = [], mod = 0) { - if(mod) { - let c = []; - let len = colors.length; - let k = len + mod; - for(let j = mod || 0; j < k; j++) { - c.push(colors[j % len]); - } - return c; - } - - return colors; - } - - get margin() { - let m = Object.assign({ - top: 30, - right: 10, - bottom: 25, - left: 40, - }, this.options.margin); - - return m; - } - - get dimensions() { - let target = this.target; - return { - container: { - width: target.clientWidth, - height: target.clientHeight, - }, - min: { - width: 300, - height: 450 - }, - max: { - height: 1000 - }, - }; - } - - get width() { - return Math.max(this.dimensions.container.width, this.dimensions.min.width); - } - - get height() { - return Math.max(Math.min(this.dimensions.container.height, this.dimensions.max.height) - this.margin.bottom, this.dimensions.min.height); - } - - get svg() { - return d3.create("svg") - .attr("height", this.height) - .attr("viewBox", [0, 0, this.width, this.height]); - } - - get colors() { - return d3.scaleOrdinal().range(this.options.colors); - } - - get labelColors() { - return d3.scaleOrdinal().range(this.options.labelColors); - } - - get target() { - return document.getElementById(this.targetId); - } - - reset(svg) { - let target = this.target; - target.classList.add("d3chart"); - if(this.className) { - target.classList.add(this.className); - } - - for(let child of target.children) { - if(child.tagName.toLowerCase() === "svg") { - child.remove(); - } - } - - let node = svg.node(); - target.appendChild(node); - } - - // Thanks https://bl.ocks.org/mbostock/7555321 - static wrapText(text, width) { - text.each(function() { - var text = d3.select(this), - words = text.text().split(/\s+/).reverse(), - word, - line = [], - lineNumber = 0, - lineHeight = 1.01, // ems - y = text.attr("y"), - dy = parseFloat(text.attr("dy")), - tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y), - firstTspan = tspan; - - let wrapCount = 0; - while (word = words.pop()) { - line.push(word); - tspan.text(line.join(" ")); - if (tspan.node().getComputedTextLength() > width) { - wrapCount++; - line.pop(); - tspan.text(line.join(" ")); - line = [word]; - tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", lineHeight + dy + "em").text(word); - } - } - - if(wrapCount) { - text.attr("dy", 0).attr("class", "d3chart-label-wrapped"); - firstTspan.attr("dy", (-0.3 * wrapCount * lineHeight) + "em") - } - }); - } - - parseDataToCsv(tableId, reverse) { - let table = document.getElementById(tableId); - let headerCells = table.querySelectorAll(":scope thead th"); - let bodyRows = table.querySelectorAll(":scope tbody tr"); - - let headerOutput = []; - for(let th of headerCells) { - headerOutput.push(th.textContent); - } - - let output = []; - for(let tr of bodyRows) { - let row = []; - for(let child of tr.children) { - let value = child.textContent; - if(value.endsWith("%")) { - value = parseFloat(value) / 100; - } - row.push(value); - } - output.push(row.join(",")); - } - if(reverse) { - return [headerOutput.join(","), ...output.reverse()].join("\n"); - } - return [headerOutput.join(","), ...output].join("\n"); - - } - - retrieveLabelId(label) { - let match = label.match(/^(\d*)\./); - if(match && match[1]) { - return parseInt(match[1], 10); - } - } - - slugify(slug, prefix) { - return `${prefix}${slug.toLowerCase().replace(/[\s\.]/g, "")}`; - } - - generateLegend(labels = []) { - let container = document.createElement("div"); - container.classList.add("d3chart-legend"); - - let entries = []; - for(let j = 0; j < labels.length; j++) { - let tag = "div"; - let attrs = ""; - if(this.options.highlightElementsFromLegend) { - tag = "button"; - attrs = " type='button'" - } - - entries.push({ - label: labels[j], - html: `<${tag}${attrs} class="d3chart-legend-entry d3chart-legend-${j + this.options.colorMod}">${labels[j] || ""}` - }); - } - - if(this.options.sortLegend) { - entries = entries.sort((a, b) => { - let idA = this.retrieveLabelId(a.label); - let idB = this.retrieveLabelId(b.label); - if(idA && idB) { - return idA - idB; - } - if(a.label < b.label) { - return -1; - } else if(b.label < a.label) { - return 1; - } - return 0; - }); - } - - let html = []; - for(let entry of entries) { - html.push(entry.html); - } - container.innerHTML = html.join(""); - return container; - } - - getKeys(data) { - return data.columns.slice(1); - } - - highlightElements(target, method) { - // TODO this is specific to Bubble chart - if(target.classList.contains("d3chart-legend-entry")) { - let circleSlug = this.slugify(target.innerHTML, `${this.targetId}-bubblecircle-`); - let labelSlug = this.slugify(target.innerHTML, `${this.targetId}-bubblelabel-`); - - let circle = document.getElementById(circleSlug); - let label = document.getElementById(labelSlug); - - circle.classList[method]("active"); - label.classList[method]("active"); - - circle.closest("svg").classList[method]("d3chart-bubble-active"); - } - } - - renderLegend(data) { - if(!this.options.showLegend) { - return; - } - - let keys = this.getKeys(data); - let legend = this.generateLegend(keys, this.options.colors); - - if(this.options.highlightElementsFromLegend) { - legend.addEventListener("mouseover", e => { - this.highlightElements(e.target, "add"); - }); - legend.addEventListener("mouseout", e => { - this.highlightElements(e.target, "remove"); - }); - legend.addEventListener("focusin", e => { - this.highlightElements(e.target, "add"); - }); - legend.addEventListener("focusout", e => { - this.highlightElements(e.target, "remove"); - }); - } - - let selector = ":scope .d3chart-legend-placeholder"; - - let previousEl = this.target.previousElementSibling; - let legendAnchorBefore = previousEl ? previousEl.querySelector(selector) : null; - - let nextEl = this.target.nextElementSibling; - let legendAnchorAfter = nextEl ? nextEl.querySelector(selector) : null; - - if(legendAnchorBefore || legendAnchorAfter) { - (legendAnchorBefore || legendAnchorAfter).appendChild(legend) - } else { - // inside - this.target.appendChild(legend); - } - } - - roundValue(num, valueType = "percentage") { - if(valueType !== "percentage") { - return num; - } - - let d0 = (num * 100).toFixed(0); - if(this.options.labelPrecision === 0) { - return d0; - } - - let d1 = (num * 100).toFixed(1); - if(d1.endsWith(".0")) { - return d0; - } - return d1; - } -} - -class D3VerticalBarChart extends D3Chart { - constructor(target, tableId, optionOverrides = {}) { - let chart = super(target, optionOverrides, "d3chart-vbar"); - - let csvData = chart.parseDataToCsv(tableId); - let dataSplit = csvData.split("\n"); - this.axisLabels = [dataSplit[0].split(",")[0]]; - - let data = Object.assign(d3.csvParse(csvData, d3.autoType)); - - - this.onDeferInit(function() { - this.render(chart, data); - this.renderLegend(data); - - this.onResize(function() { - this.render(chart, data); - }) - }); - } - - render(chart, data) { - let { - options, - margin, - width, - height, - dimensions, - svg, - colors, - labelColors, - } = chart; - - let keys = this.getKeys(data); - let groupKey = data.columns[0]; - let groups = data.map(d => d[groupKey]); - - let y = d3.scaleLinear() - .domain([ - 0, - d3.max(data, d => { - if(options.mode === "stacked") { - let sum = 0; - for(let key of keys) { - sum += d[key]; - } - return sum; - } - - return d3.max(keys, key => d[key]) - }) - ]).nice() - .rangeRound([height - margin.bottom, margin.top]); - - let x0 = d3.scaleBand() - .domain(groups) - .rangeRound([margin.left, width - margin.right]) - .paddingInner(.2); - - let x1 = d3.scaleBand() - .domain(keys) - .rangeRound([0, x0.bandwidth()]) - .padding(0.05); - - let yAxis = g => g - .attr("transform", `translate(${margin.left},0)`) - .attr("class", "d3chart-yaxis") - .call(d3 - .axisLeft(y) - .ticks(null, options.valueType[0] === "percentage" ? "%" : "") - .tickSize(-width + margin.left + margin.right)) - .call(g => g.select(".domain").remove()); - - let xAxis = g => g - .attr("transform", `translate(0,${height - margin.bottom})`) - .attr("class", "d3chart-xaxis") - .call(d3 - .axisBottom(x0) - .tickSizeOuter(0)) - .call(g => g.select(".domain").remove()); - - let dataMod = d => { - let incrementer = 0; - - return keys.map(key => { - let data = { - key, - value: d[key], - width: x1.bandwidth(), - height: y(0) - y(d[key]), - left: x1(key), - top: y(d[key]) - }; - - if(options.mode === "stacked") { - data.width = x0.bandwidth(); - data.left = 0; - data.top = y(d[key]) - incrementer; - incrementer += data.height; - } - - return data; - }) - }; - - svg.append("g").call(xAxis); - svg.append("g").call(yAxis); - - svg.append("g") - .selectAll("g") - .data(data) - .join("g") - .attr("transform", d => `translate(${x0(d[groupKey])},0)`) - .selectAll("rect") - .data(dataMod) - .join("rect") - .attr("x", d => d.left) - .attr("y", d => d.top) - .attr("width", d => d.width) - .attr("height", d => d.height) - .attr("fill", d => colors(d.key)) - .attr("class", (d, j) => `d3chart-color-${j + options.colorMod}`); - - if(options.showInlineBarValues) { - svg.append("g") - .selectAll("g") - .data(data) - .join("g") - .attr("transform", d => `translate(${x0(d[groupKey])},0)`) - .selectAll("text") - .data(dataMod) - .join("text") - .attr("x", d => d.left + d.width / 2) - .attr("y", d => d.top - (options.showInlineBarValues === "outside" ? options.inlineLabelPad : (-15 - options.inlineLabelPad))) - .attr("fill", d => options.showInlineBarValues === "inside" ? labelColors(d.key) : "currentColor") - .attr("class", "d3chart-inlinebarvalue") - .text(d => this.roundValue(d.value, options.valueType[0]) + (options.valueType[0] === "percentage" ? "%" : "")); - } - - // TODO for horizontal bar chart - if(options.showAxisLabels) { - svg.append("text") - .attr("x", Math.round(width/2)) - .attr("y", height - 6) - .attr("class", "d3chart-axislabel d3chart-axislabel-center") - .text(this.axisLabels[0]); - } - - chart.reset(svg); - } -} - -class D3HorizontalBarChart extends D3Chart { - constructor(target, tableId, optionOverrides = {}) { - optionOverrides.margin = Object.assign({ - top: 20, - right: 50, - bottom: 20, - left: 120 - }, optionOverrides.margin); - let chart = super(target, optionOverrides, "d3chart-hbar"); - let csvData = chart.parseDataToCsv(tableId, true); - let data = Object.assign(d3.csvParse(csvData, d3.autoType)); - - this.onDeferInit(function() { - this.render(chart, data); - this.renderLegend(data); - - this.onResize(function() { - this.render(chart, data); - }); - }); - } - - render(chart, data) { - let { - options, - margin, - width, - height, - dimensions, - svg, - colors, - labelColors, - } = chart; - - let keys = this.getKeys(data); - let groupKey = data.columns[0]; - let groups = data.map(d => d[groupKey]); - - let x = d3.scaleLinear() - .domain([0, d3.max(data, d => { - if(options.scale === "proportional") { - return 1; - } - - if(options.mode === "stacked") { - let sum = 0; - for(let key of keys) { - sum += d[key]; - } - return sum; - } - - return d3.max(keys, key => d[key]); - })]).nice() - .rangeRound([margin.left, width - margin.right]); - - let y0 = d3.scaleBand() - .domain(groups) - .rangeRound([height - margin.bottom - margin.top, margin.top]) - .paddingInner(options.showInlineBarValues === "inside-offset" ? 0.25 : 0.15); - - let y1 = d3.scaleBand() - .domain(keys) - .rangeRound([0, y0.bandwidth()]) - .padding(0.05); - - let xAxis = g => g - .attr("transform", `translate(0, ${(margin.top + margin.bottom)/4})`) - .attr("class", "d3chart-xaxis") - .call(d3 - .axisBottom(x) - .ticks(5, options.valueType[0] === "percentage" ? "%" : "") - .tickSize(height - margin.bottom - margin.top)) - .call(g => g.select(".domain").remove()); - - let yAxis = g => g - .attr("transform", `translate(${margin.left - 6},0)`) - .attr("class", "d3chart-yaxis") - .call(d3.axisLeft(y0).tickSize(0)) - .call(g => g.select(".domain").remove()); - - let dataMod = d => { - let incrementer = 0; - let sum = 0; - for(let key of keys) { - sum += d[key]; - } - - return keys.map(key => { - let data = { - key, - value: d[key], - sum, - width: x(options.scale === "proportional" ? (d[key] / sum) : d[key]) - x(0), - height: y1.bandwidth(), - left: margin.left, - top: y1(key) - }; - - if(options.mode === "stacked") { - data.top = 0; - data.height = y0.bandwidth(); - data.left = margin.left + incrementer; - - incrementer += data.width; - } - - return data; - }) - }; - - svg.append("g").call(xAxis); - svg.append("g").call(yAxis); - - svg.append("g") - .selectAll("g") - .data(data) - .join("g") - .attr("transform", d => `translate(0,${y0(d[groupKey])})`) - .selectAll("rect") - .data(dataMod) - .join("rect") - .attr("x", d => d.left) - .attr("y", d => d.top) - .attr("width", d => d.width) - .attr("height", d => d.height) - .attr("fill", d => colors(d.key)) - .attr("class", (d, j) => `d3chart-color-${j + options.colorMod}`); - - if(options.showInlineBarValues) { - svg.append("g") - .selectAll("g") - .data(data) - .join("g") - .attr("transform", d => `translate(0,${y0(d[groupKey])})`) - .selectAll("text") - .data(dataMod) - .join("text") - .attr("x", d => { - let offset = options.inlineLabelPad; - if(options.showInlineBarValues.startsWith("inside")) { - offset = -1 * offset; - } - if(options.showInlineBarValues === "inside-offset") { - offset += 16; - } - return d.left + d.width + offset; - }) - .attr("y", d => { - if(options.showInlineBarValues === "inside-offset") { - return -10; - } - return d.top + Math.floor(d.height / 2) - 1; - }) - .attr("class", d => "d3chart-inlinebarvalue-h" + (options.showInlineBarValues.length ? ` ${options.showInlineBarValues}` : "")) - .attr("fill", d => options.showInlineBarValues === "inside" ? labelColors(d.key) : "currentColor") - .text(d => this.roundValue(d.value, options.valueType[0]) + (options.valueType[0] === "percentage" ? "%" : "")); - } - - chart.reset(svg); - - if(options.wrapAxisLabel && options.wrapAxisLabel.left) { - D3Chart.wrapText(svg.selectAll(".d3chart-yaxis .tick text"), margin.left - 6); - } - } -} - -class D3BubbleChart extends D3Chart { - constructor(target, tableId, optionOverrides = {}) { - optionOverrides.margin = { - top: 20, - right: 20, - bottom: 50, - left: 65 - }; - - optionOverrides.sortLegend = true; - optionOverrides.highlightElementsFromLegend = true; - optionOverrides.showAxisLabels = true; - - if(!optionOverrides.valueType) { - optionOverrides.valueType = ["percentage", "percentage"]; - } - - let chart = super(target, optionOverrides, "d3chart-bubble"); - let csvData = chart.parseDataToCsv(tableId); - let dataSplit = csvData.split("\n"); - this.axisLabels = dataSplit[0].split(",").slice(1); - - let data = dataSplit.slice(1).map((entry, id) => { - let [name, x, y, r] = entry.split(","); - return { - name, - id, - x, - y, - r, - }; - }); - - // sort from smallest to largest circles to insert in order (to render in the right z-index) - data = data.slice().sort((a, b) => { - return b.r - a.r; - }); - - this.onDeferInit(function() { - this.render(chart, data); - this.renderLegend(data); - - this.onResize(function() { - this.render(chart, data); - }); - }); - } - - getKeys(data) { - let keys = []; - for(let entry of data) { - keys.push(entry.name); - } - return keys; - } - - resolveLimit(data, key, valueType, mode) { - let limit = d3[mode](data, d => parseFloat(d[key])); - if(valueType !== "percentage") { - if(mode === "max") { - limit = Math.ceil(limit); - } else if(mode === "min") { - limit = Math.min(Math.floor(limit), 0); - } - } else { - if(mode === "max") { - if(limit > 1) { - limit += .1; - } else { - // round up to at most 1 if percentage < 100% - if(limit > .5) { - limit = Math.min(limit + .1, 1); - } else { - limit = limit + .05, 1; - } - } - } - if(mode === "min") { - if(limit <= 0) { - limit -= .1; - } else { - // round up to at most 1 if percentage < 100% - limit = Math.min(limit, 0); - } - } - } - - return limit; - } - - render(chart, data) { - let { - options, - margin, - width, - height, - dimensions, - svg, - colors, - labelColors, - } = chart; - - let targetId = this.targetId; - - let xAxisMin = this.resolveLimit(data, "x", options.valueType[0], "min"); - let xAxisMax = this.resolveLimit(data, "x", options.valueType[0], "max"); - let yAxisMin = this.resolveLimit(data, "y", options.valueType[1], "min"); - let yAxisMax = this.resolveLimit(data, "y", options.valueType[1], "max"); - - let xScale = d3.scaleLinear() - .domain([ - xAxisMin, - xAxisMax - ]) - .range([ - margin.left, - width - margin.right - ]); - - let yScale = d3.scaleLinear() - .domain([ - yAxisMax, - yAxisMin, - ]) - .range([ - margin.top, - height - margin.top - margin.bottom - ]); - - let rScale = d3.scaleLinear() - .range([7, 25]) - .domain([ - Math.min(d3.min(data, d => parseFloat(d.r)), 0), - d3.max(data, d => parseFloat(d.r)) - ]); - - let xAxis = d3.axisBottom() - .scale(xScale) - .ticks(null) - .tickSize(-height + margin.bottom + margin.top) - .tickFormat(d => options.valueType[0] === "percentage" ? `${(d*100).toFixed(0)}%` : d); - - svg.append("g") - .attr("class", "d3chart-xaxis") - .attr("transform", function(){ - return "translate(0," + (height - margin.bottom) + ")"; - }) - .call(xAxis) - .call(g => g.select(".domain").remove()); - - let yAxis = d3.axisLeft() - .scale(yScale) - .ticks(null) - .tickSize(-width + margin.right + margin.left) - .tickFormat(d => options.valueType[1] === "percentage" ? `${(d*100).toFixed(0)}%` : d); - - svg.append("g") - .attr("class", "d3chart-yaxis") - .attr("transform", function(){ - return "translate(" + margin.left + "," + margin.top + ")"; - }) - .call(yAxis) - .call(g => g.select(".domain").remove()); - - if(options.showAxisLabels) { - // Axis labels - svg.append("text") - .attr("x", width - margin.right) - .attr("y", height - 6) - .attr("class", "d3chart-axislabel") - .text(this.axisLabels[0]); - - svg.append("text") - .attr("x", -1 * margin.top) - .attr("y", 6) - .attr("dy", ".75em") - .attr("transform", "rotate(-90)") - .attr("class", "d3chart-axislabel") - .text(this.axisLabels[1]); - } - - let group = svg.append("g"); - - let circles = group.selectAll("circle").data(data); - - // Text Labels - function isOffsetLabel(d) { - let range = rScale(d.r); - return range <= 10; - } - - circles - .enter() - .insert("circle") - .attr("cx", function (d) { - return xScale(d.x); - }) - .attr("cy", function (d) { - return yScale(d.y); - }) - .attr("r", function (d) { - return rScale(d.r); - }) - .attr("id", d => this.slugify(d.name, `${targetId}-bubblecircle-`)) - .attr("fill", d => colors(d)) - .attr("class", (d, j) => `d3chart-bubblecircle d3chart-color-${j + options.colorMod}`); - - circles - .enter() - .append("text") - .attr("id", d => this.slugify(d.name, `${targetId}-bubblelabel-`)) - .attr("filter", d => { - return isOffsetLabel(d) ? "url(#offset-label-bg)" : "" - }) - .attr("x", d => { - return xScale(d.x) - (isOffsetLabel(d) ? rScale(d.r) + 4 : 0); - }) - .attr("y", d => yScale(d.y)) - .attr("class", d => { - return "d3chart-bubblelabel" + (isOffsetLabel(d) ? " offset-l" : ""); - }) - .attr("fill", d => isOffsetLabel(d) ? "currentColor" : labelColors(d)) - .text(d => { - let labelId = this.retrieveLabelId(d.name); - if(labelId) { - return labelId; - } - return d.name; - }) - .filter(d => isOffsetLabel(d)) - .lower(); - - chart.reset(svg); - } -} \ No newline at end of file diff --git a/src/site/survey/2021/js.njk b/src/site/survey/2021/js.njk index 27c8bf986..19ffb114c 100644 --- a/src/site/survey/2021/js.njk +++ b/src/site/survey/2021/js.njk @@ -1,6 +1,7 @@ --- permalink: /survey/2021/bundle.js --- + {% include "../../../../node_modules/d3/dist/d3.min.js" %} -{% include "./d3chart.js" %} -{% include "./d3chart-survey.js" %} \ No newline at end of file +{% include "../shared/d3chart.js" %} +{% include "./d3chart-survey-2021.js" %} diff --git a/src/site/survey/2022.njk b/src/site/survey/2022.njk new file mode 100644 index 000000000..24f00fe85 --- /dev/null +++ b/src/site/survey/2022.njk @@ -0,0 +1,201 @@ +--- +title: "Jamstack Community Survey Results 2022" +description: "The third annual Jamstack Survey conducted by Netlify reveals developer attitudes towards trends like remote work, Web3, serverless, edge and more." +layout: layouts/base.njk +ogimage: "/img/og/jamstack-community-survey-2022-og.png" +stylesheets: + - /css/d3chart.css +javascripts: + - /survey/2022/bundle.js +gradientColors: + sunrise: ["#F0047F", "#FC814A"] + blue: ["#0090c9", "#00c0ad"] + sun: ["#FC814A", "#FFC803"] + seamist: ["#78ECC2", "#00FFB2"] + hallows: ["#DF4A1F", "#FFA278"] + bubblegum: ["#FF98BC", "#FFCCDE"] + purple: ["#6B38FB", "#CCB4FF"] + air: ["#03d0d0", "#B5FFF8"] + pink: ["#c40468", "#fc2796"] + leaves: ["#78f19a", "#13b110"] + haze: ["#91A5EE", "#d6deff"] + gnat: ["#02C6B3", "#59F7E7"] + fire: ["#FF0F00", "#FF928A"] + ocean: ["#003EDD", "#6CDCFF"] + night: ["#02465F", "#6AD7FF"] + dusk: ["#960000", "#E94242"] + rain: ["#FF72CF", "#C92ECC"] +gradientColorsExtended: + "16": ["#f0185d", "#ff668f"] + "17": ["#448bd0", "#80c0ff"] + "18": ["#dbd600", "#ffff54"] + "19": ["#63edd7", "#a1ffff"] + "20": ["#cb5f00", "#ff932f"] + "21": ["#ff98a8", "#ffd0df"] + "22": ["#a800dc", "#e449ff"] + "23": ["#00cfe4", "#6affff"] + "24": ["#c5114c", "#ff5a7c"] + "25": ["#4af4b5", "#8effed"] + "26": ["#aa9ee9", "#e2d5ff"] + "27": ["#00c6c9", "#57ffff"] + "28": ["#e64b00", "#ff8300"] +--- + +{% import "components/permalink-heading.njk" as permalinkHeading %} + + + + + + + + + + + + {%- for key, entry in gradientColors %} + + + + + + + + + {%- endfor %} {%- for key, entry in gradientColorsExtended %} + + + + + + + + + {%- endfor %} + + + +
    +
    +
    +

    Jamstack gives developers full-stack powers

    +

    Findings from the Jamstack Community Survey 2022

    +
    +

    + The third year of the Jamstack Community Survey found a mix of things we + expected – indeed, things we predicted last year – as well as some big + surprises about the many diverse members of our community. Some key + takeaways include: +

    +
      +
    • + Four out of five developers are now working remotely most of the time, + and more than half say they would quit their jobs rather than go back to + an office. +
    • +
    • + The number of people who have used serverless technology jumped to 70%, + taking it fully into the mainstream. +
    • +
    • + React continued to grow to an almost unprecedented 71% share of + developers, and Next.js rode that wave and is now used by 1 in every 2 + developers. +
    • +
    +

    + Netlify sits at the + center of the Jamstack community, and we conduct our annual survey so we + can understand our community of developers. This helps us tailor our + products and services to our community. In sharing our survey results, we + also want to help developers better understand themselves and one another. + Working as a developer often means working in a vacuum, without a sense of + what’s happening in the broader community. Our survey data can help + provide a sense of best practices as well as an idea of what else is + happening in the community. +

    +

    + In addition to our usual framework census and our questions about content + management systems, this year we asked about some emerging technologies + that have received a lot of attention. The fuzzy group of technologies + called “Web3” garnered mixed feelings despite a great deal of press in + 2021 and 2022. Browser-native web components, on the other hand, seem to + have finally reached mainstream adoption. +

    +

    + As usual, our survey covers everyone we can reach: every kind of developer + responded to our survey from every region of the world, whether or not + they were Netlify users, and whether or not they considered themselves + Jamstack developers. Our survey this year received a little under 7,000 + responses. If you’re interested in the specifics of our methodology, we + have a + detailed writeup + of the demographics and margins of error in our survey. +

    +

    + As usual, we want to thank the developers who took the time to contribute + to the survey. We have done our best to take the data you’ve given us and + turn it into useful, actionable insights for everyone in our community, + and we hope it helps you. +

    +

    This year, our results are split into four sections:

    + +
    + + + + {% include "survey/2022/whos-doing-the-building/index.njk" %} + + {% include "survey/2022/what-are-we-building/index.njk" %} + + {% include "survey/2022/how-are-we-building/index.njk" %} + + {% include "survey/2022/where-are-we-going/index.njk" %} +
    diff --git a/src/site/survey/2022/community-survey-2022-methodology.pdf b/src/site/survey/2022/community-survey-2022-methodology.pdf new file mode 100644 index 000000000..33e636b24 Binary files /dev/null and b/src/site/survey/2022/community-survey-2022-methodology.pdf differ diff --git a/src/site/survey/2022/d3chart-survey-2022.js b/src/site/survey/2022/d3chart-survey-2022.js new file mode 100644 index 000000000..a6ebe7b29 --- /dev/null +++ b/src/site/survey/2022/d3chart-survey-2022.js @@ -0,0 +1,392 @@ +new D3HorizontalBarChart( + "job-titles-2021-2022-comparison-chart", + "job-titles-2021-2022-comparison-table", + { + showInlineBarValues: "outside", + margin: { + left: 188, + }, + scaleTicks: { + x: true, + }, + colorMod: 1, + interactive: true + } +); + +new D3HorizontalBarChart("employment-status-chart", "employment-status-table", { + showInlineBarValues: "outside", + showLegend: false, + margin: { + left: 128, + }, + colorMod: 0, + scaleTicks: { + x: true, + }, +}); + +new D3VerticalBarChart( + "experience-increasing-over-time-chart", + "experience-increasing-over-time-table", + { + showInlineBarValues: false, + interactive: true + } +); + +new D3HorizontalBarChart( + "experience-by-region-chart", + "experience-by-region-table", + { + mode: "stacked", + showInlineBarValues: false, + margin: { + left: 48, + right: 0, + }, + interactive: true + } +); + +new D3VerticalBarChart( + "respondents-by-region-chart", + "respondents-by-region-table", + { + showInlineBarValues: false, + margin: { + left: 32, + bottom: 88, + right: 32, + }, + colorMod: 1, + rotateXAxisLabels: true, + interactive: true + } +); + +new D3HorizontalBarChart( + "have-you-changed-jobs-in-the-last-12-months-chart", + "have-you-changed-jobs-in-the-last-12-months-table", + { + showInlineBarValues: "outside", + showLegend: false, + margin: { + left: 40, + }, + colorMod: 3, + } +); + +new D3HorizontalBarChart( + "what-influenced-staying-chart", + "what-influenced-staying-table", + { + mode: "stacked", + showInlineBarValues: false, + margin: { + left: 164, + right: 0, + }, + scaleTicks: { + x: true, + }, + interactive: true + } +); + +new D3HorizontalBarChart( + "what-influenced-leaving-chart", + "what-influenced-leaving-table", + { + mode: "stacked", + showInlineBarValues: false, + margin: { + left: 164, + right: 0, + }, + scaleTicks: { + x: true, + }, + interactive: true + } +); + +new D3HorizontalBarChart("remote-frequency-chart", "remote-frequency-table", { + showLegend: false, + showInlineBarValues: "outside", + margin: { + left: 64, + }, + colorMod: 0, + scaleTicks: { + x: true, + }, +}); + +new D3HorizontalBarChart("remote-changes-chart", "remote-changes-table", { + showLegend: false, + showInlineBarValues: "outside", + margin: { + left: 164, + }, + colorMod: 1, + scaleTicks: { + x: true, + }, +}); + +new D3VerticalBarChart( + "i-enjoy-remote-work-chart", + "i-enjoy-remote-work-table", + { + showLegend: false, + showInlineBarValues: "outside", + colorMod: 2, + wrapTicks: { + x: true, + }, + } +); + +new D3VerticalBarChart( + "my-company-has-remote-work-figured-out-chart", + "my-company-has-remote-work-figured-out-table", + { + showLegend: false, + showInlineBarValues: "outside", + colorMod: 2, + wrapTicks: { + x: true, + }, + } +); + +new D3VerticalBarChart( + "i-would-like-to-work-remote-more-often-chart", + "i-would-like-to-work-remote-more-often-table", + { + showLegend: false, + showInlineBarValues: "outside", + colorMod: 3, + wrapTicks: { + x: true, + }, + } +); + +new D3VerticalBarChart( + "i-would-like-to-work-remote-more-often-chart", + "i-would-like-to-work-remote-more-often-table", + { + showLegend: false, + showInlineBarValues: "outside", + colorMod: 3, + wrapTicks: { + x: true, + }, + } +); + +new D3VerticalBarChart( + "i-changed-jobs-to-work-remotely-more-often-chart", + "i-changed-jobs-to-work-remotely-more-often-table", + { + showLegend: false, + showInlineBarValues: "outside", + colorMod: 3, + wrapTicks: { + x: true, + }, + } +); + +new D3VerticalBarChart( + "i-would-quit-if-in-person-was-more-often-chart", + "i-would-quit-if-in-person-was-more-often-table", + { + showLegend: false, + showInlineBarValues: "outside", + colorMod: 0, + wrapTicks: { + x: true, + }, + } +); + +new D3VerticalBarChart( + "i-would-quit-my-job-if-remote-was-more-often-chart", + "i-would-quit-my-job-if-remote-was-more-often-table", + { + showLegend: false, + showInlineBarValues: "outside", + colorMod: 0, + wrapTicks: { + x: true, + }, + } +); + +new D3HorizontalBarChart( + "what-is-the-purpose-of-the-sites-you-built-in-2022-chart", + "what-is-the-purpose-of-the-sites-you-built-in-2022-table", + { + showInlineBarValues: "outside", + showLegend: false, + margin: { + left: 148, + }, + colorMod: 2, + scaleTicks: { + x: true, + }, + } +); + +new D3HorizontalBarChart( + "types-of-sites-built-last-12-months-chart", + "types-of-sites-built-last-12-months-table", + { + mode: "stacked", + showInlineBarValues: false, + margin: { + left: 128, + right: 0, + }, + scaleTicks: { + x: true, + }, + interactive: true + } +); + +new D3VerticalBarChart( + "target-devices-by-type-chart", + "target-devices-by-type-table", + { + showInlineBarValues: "outside", + wrapTicks: { + x: true, + }, + interactive: true + } +); + +new D3VerticalBarChart("audience-sizes-chart", "audience-sizes-table", { + showInlineBarValues: "outside", + wrapTicks: { + x: true, + }, + interactive: true +}); + +new D3BubbleChart( + "cms-usage-vs-satisfaction-chart", + "cms-usage-vs-satisfaction-table", + { + radiusColumn: 1, + valueType: ["percentage", "float"], + extendedColors: true, + scaleTicks: { + x: true, + }, + } +); + +new D3BubbleChart( + "programming-language-usage-vs-satisfaction-chart", + "programming-language-usage-vs-satisfaction-table", + { + radiusColumn: 1, + valueType: ["percentage", "float"], + scaleTicks: { + x: true, + }, + } +); + +new D3BubbleChart( + "frameworks-usage-vs-satisfaction-chart", + "frameworks-usage-vs-satisfaction-table", + { + radiusColumn: 1, + valueType: ["percentage", "float"], + extendedColors: true, + scaleTicks: { + x: true, + }, + } +); + +new D3BubbleChart( + "smaller-frameworks-usage-vs-satisfaction-chart", + "smaller-frameworks-usage-vs-satisfaction-table", + { + radiusColumn: 1, + valueType: ["percentage", "float"], + scaleTicks: { + x: true, + }, + } +); + +new D3BubbleChart( + "frameworks-usage-vs-satisfaction-changes-chart", + "frameworks-usage-vs-satisfaction-changes-table", + { + valueType: ["percentage", "float"], + extendedColors: true, + scaleTicks: { + x: true, + }, + } +); + +new D3HorizontalBarChart("web3-feelings-chart", "web3-feelings-table", { + showLegend: false, + showInlineBarValues: "outside", + margin: { + left: 164, + }, + colorMod: 1, + scaleTicks: { + x: true, + }, +}); + +new D3HorizontalBarChart("web3-usage-chart", "web3-usage-table", { + mode: "stacked", + colorMod: 2, + showInlineBarValues: false, + margin: { + left: 128, + scaleTicks: { + x: true, + }, + }, + interactive: true +}); + +new D3HorizontalBarChart("web-components-chart", "web-components-table", { + showLegend: false, + showInlineBarValues: "outside", + margin: { + left: 180, + }, + colorMod: 3, + scaleTicks: { + x: true, + }, +}); + +new D3VerticalBarChart("serverless-usage-chart", "serverless-usage-table", { + showLegend: false, + showInlineBarValues: "outside", + colorMod: 2, + wrapTicks: { + x: true, + }, + scaleTicks: { + x: true, + }, +}); diff --git a/src/site/survey/2022/js.njk b/src/site/survey/2022/js.njk new file mode 100644 index 000000000..d437aadfc --- /dev/null +++ b/src/site/survey/2022/js.njk @@ -0,0 +1,8 @@ +--- +permalink: /survey/2022/bundle.js +--- + +{% include "../../../../node_modules/d3/dist/d3.min.js" %} +{% include "../../../../node_modules/d3-textwrap/build/d3-textwrap.min.js" %} +{% include "../shared/d3chart.js" %} +{% include "./d3chart-survey-2022.js" %} diff --git a/src/site/survey/shared/d3chart.js b/src/site/survey/shared/d3chart.js new file mode 100644 index 000000000..4385125f6 --- /dev/null +++ b/src/site/survey/shared/d3chart.js @@ -0,0 +1,1559 @@ +const debounce = (callback, wait) => { + let timeoutId = null; + return (...args) => { + window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(() => { + callback.apply(null, args); + }, wait); + }; +}; + +class D3Chart { + constructor(targetId, options, className) { + this.targetId = targetId; + this.className = className; + + this.options = Object.assign( + { + showInlineBarValues: "inside", // inside, inside-offset, and outside supported + showLegend: true, + showAxisLabels: false, + margin: {}, + colors: [ + "#F0047F", + "#00BFAD", + "#FFC803", + "#78ECC2", + "#DF4A1F", + "#FD98BC", + "#6B38FB", + "#03D0D0", + "#C40468", + "#78F19A", + "#91A5EE", + "#02C6B3", + "#FF0F00", + "#003EDD", + "#02465F", + "#960000", + "#FF72CF", + ], + // only applies when `showInlineBarValues: "inside"` + labelColors: [ + "#fff", + "#000", + "#000", + "#000", + "#000", + "#000", + "#000", + "#000", + "#fff", + "#000", + "#000", + "#000", + "#000", + "#000", + "#000", + "#fff", + "#fff", + "#000", + "#000", + "#000", + "#000", + "#000", + "#000", + "#fff", + "#000", + "#fff", + "#000", + "#000", + "#000", + "#000", + ], + colorMod: 0, + inlineLabelPad: 5, + labelPrecision: 0, + // TODO make this automatic by parsing `%` signs + valueType: ["percentage"], + sortLegend: false, + highlightElementsFromLegend: false, + extendedColors: false, + }, + options + ); + + this.options.colors = this.normalizeColors( + this.options.colors, + this.options.colorMod + ); + this.options.labelColors = this.normalizeColors( + this.options.labelColors, + this.options.colorMod + ); + } + + scaleTicksX(svg) { + const getTranslateX = (node) => + node.transform.baseVal.consolidate().matrix["e"]; + + const tickDistancesX = []; + const tickWidths = []; + + svg.selectAll(".d3chart-xaxis .tick").each(function () { + tickDistancesX.push(getTranslateX(d3.select(this).node())); + tickWidths.push(d3.select(this).node().getBBox().width); + }); + + const tickSize = (tickDistancesX.at(-1) - tickDistancesX.at(-2)) * 0.75; + const largestTickWidth = Math.max(...tickWidths); + const baseFontSize = 1.3; + + if (largestTickWidth >= tickSize) { + const scale = tickSize / largestTickWidth; + + svg + .selectAll(".d3chart-xaxis .tick text") + .style("font-size", `${baseFontSize * scale}em`) + } + } + + onResize(callback) { + if (!("ResizeObserver" in window)) { + window.addEventListener("resize", () => { + callback.call(this); + }); + return; + } + + let resizeObserver = new ResizeObserver((entries) => { + for (let entry of entries) { + // console.log( "resizing", this.target ); + callback.call(this); + } + }); + + resizeObserver.observe(this.target); + } + + onDeferInit(callback) { + if (!("IntersectionObserver" in window)) { + callback.call(this); + return; + } + + let observer = new IntersectionObserver( + (entries, observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + // console.log( "initing", this.target ); + callback.call(this); + observer.unobserve(entry.target); + } + }); + }, + { + threshold: 0.1, + } + ); + + observer.observe(this.target); + } + + normalizeColors(colors = [], mod = 0) { + if (mod) { + let c = []; + let len = colors.length; + let k = len + mod; + for (let j = mod || 0; j < k; j++) { + c.push(colors[j % len]); + } + return c; + } + + return colors; + } + + get margin() { + let m = Object.assign( + { + top: 30, + right: 10, + bottom: 25, + left: 40, + }, + this.options.margin + ); + + return m; + } + + get dimensions() { + let target = this.target; + return { + container: { + width: target.clientWidth, + height: target.clientHeight, + }, + min: { + width: 300, + height: 450, + }, + max: { + height: 1000, + }, + }; + } + + get width() { + return Math.max(this.dimensions.container.width, this.dimensions.min.width); + } + + get height() { + return Math.max( + Math.min(this.dimensions.container.height, this.dimensions.max.height) - + this.margin.bottom, + this.dimensions.min.height + ); + } + + get svg() { + return d3 + .create("svg") + .attr("height", this.height) + .attr("viewBox", [0, 0, this.width, this.height]); + } + + get colors() { + return d3.scaleOrdinal().range(this.options.colors); + } + + get labelColors() { + return d3.scaleOrdinal().range(this.options.labelColors); + } + + get target() { + return document.getElementById(this.targetId); + } + + reset(svg) { + let target = this.target; + target.classList.add("d3chart"); + if (this.className) { + target.classList.add(this.className); + } + + for (let child of target.children) { + if (child.tagName.toLowerCase() === "svg") { + child.remove(); + } + } + + let node = svg.node(); + target.appendChild(node); + } + + // Thanks https://bl.ocks.org/mbostock/7555321 + static wrapText(text, width) { + text.each(function () { + var text = d3.select(this), + words = text.text().split(/\s+/).reverse(), + word, + line = [], + lineNumber = 0, + lineHeight = 1.01, // ems + y = text.attr("y"), + dy = parseFloat(text.attr("dy")), + tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y), + firstTspan = tspan; + + let wrapCount = 0; + while ((word = words.pop())) { + line.push(word); + tspan.text(line.join(" ")); + if (tspan.node().getComputedTextLength() > width) { + wrapCount++; + line.pop(); + tspan.text(line.join(" ")); + line = [word]; + tspan = text + .append("tspan") + .attr("x", 0) + .attr("y", y) + .attr("dy", lineHeight + dy + "em") + .text(word); + } + } + + if (wrapCount) { + text.attr("dy", 0).attr("class", "d3chart-label-wrapped"); + firstTspan.attr("dy", -0.3 * wrapCount * lineHeight + "em"); + } + }); + } + + parseDataToCsv(tableId, reverse) { + let table = document.getElementById(tableId); + let headerCells = table.querySelectorAll(":scope thead th"); + let bodyRows = table.querySelectorAll(":scope tbody tr"); + + let headerOutput = []; + for (let th of headerCells) { + headerOutput.push(th.textContent.replace(/,/g, ",")); + } + + let output = []; + for (let tr of bodyRows) { + let row = []; + for (let child of tr.children) { + let value = child.textContent; + if ( + child.getAttribute("data-avoid-parse") === null && + value.endsWith("%") + ) { + value = parseFloat(value) / 100; + } + row.push(value); + } + output.push(row.join(",")); + } + + if (reverse) { + return [headerOutput.join(","), ...output.reverse()].join("\n"); + } + return [headerOutput.join(","), ...output].join("\n"); + } + + retrieveLabelId(label) { + let match = label.match(/^(\d*)\./); + if (match && match[1]) { + return parseInt(match[1], 10); + } + } + + slugify(slug, prefix) { + return `${prefix}${slug.toLowerCase().replace(/[\s\.]/g, "")}`; + } + + generateLegend(labels = []) { + let container = document.createElement("div"); + container.classList.add("d3chart-legend"); + + let entries = []; + for (let j = 0; j < labels.length; j++) { + let tag = "div"; + let attrs = ""; + if ( + this.options.highlightElementsFromLegend || + this.options.interactive + ) { + tag = "button"; + attrs = " type='button'"; + } + + attrs += ` data-item=${this.slugify(labels[j], "")} `; + + entries.push({ + label: labels[j], + html: `<${tag}${attrs} class="d3chart-legend-entry d3chart-legend-${ + j + this.options.colorMod + }">${labels[j] || ""}`, + }); + } + + if (this.options.sortLegend) { + entries = entries.sort((a, b) => { + let idA = this.retrieveLabelId(a.label); + let idB = this.retrieveLabelId(b.label); + if (idA && idB) { + return idA - idB; + } + if (a.label < b.label) { + return -1; + } else if (b.label < a.label) { + return 1; + } + return 0; + }); + } + + let html = []; + for (let entry of entries) { + html.push(entry.html); + } + container.innerHTML = html.join(""); + return container; + } + + getKeys(data) { + return data.columns.slice(1); + } + + highlightElements(target, method) { + // TODO this is specific to Bubble chart + if (target.classList.contains("d3chart-legend-entry")) { + let circleSlug = this.slugify( + target.innerHTML, + `${this.targetId}-bubblecircle-` + ); + let labelSlug = this.slugify( + target.innerHTML, + `${this.targetId}-bubblelabel-` + ); + + let circle = document.getElementById(circleSlug); + let label = document.getElementById(labelSlug); + + circle.classList[method]("active"); + label.classList[method]("active"); + + circle.closest("svg").classList[method]("d3chart-bubble-active"); + } + } + + renderLegend(data) { + if (!this.options.showLegend) { + return; + } + + let keys = this.getKeys(data); + let legend = this.generateLegend(keys, this.options.colors); + + legend.classList.add(`${this.targetId}-legend`); + + let selector = ":scope .d3chart-legend-placeholder"; + + let previousEl = this.target.previousElementSibling; + let legendAnchorBefore = previousEl + ? previousEl.querySelector(selector) + : null; + + let nextEl = this.target.nextElementSibling; + let legendAnchorAfter = nextEl ? nextEl.querySelector(selector) : null; + + if (legendAnchorBefore || legendAnchorAfter) { + (legendAnchorBefore || legendAnchorAfter).appendChild(legend); + } else { + // inside + this.target.appendChild(legend); + } + } + + roundValue(num, valueType = "percentage") { + if (valueType !== "percentage") { + return num; + } + + let d0 = (num * 100).toFixed(0); + if (this.options.labelPrecision === 0) { + return d0; + } + + let d1 = (num * 100).toFixed(1); + if (d1.endsWith(".0")) { + return d0; + } + return d1; + } +} + +class D3VerticalBarChart extends D3Chart { + constructor(target, tableId, optionOverrides = {}) { + if (!optionOverrides.rotateXAxisLabels) { + optionOverrides.rotateXAxisLabels = { + maxWidth: 0, + }; + } + + let chart = super(target, optionOverrides, "d3chart-vbar"); + + let csvData = chart.parseDataToCsv(tableId); + let dataSplit = csvData.split("\n"); + this.axisLabels = [dataSplit[0].split(",")[0]]; + + let data = Object.assign(d3.csvParse(csvData, d3.autoType)); + + this.onDeferInit(function () { + this.render(chart, data); + this.renderLegend(data); + + this.onResize(function () { + this.render(chart, data); + }); + }); + } + + render(chart, data) { + let { + options, + margin, + width, + height, + dimensions, + svg, + colors, + labelColors, + } = chart; + + let keys = this.getKeys(data); + let groupKey = data.columns[0]; + let groups = data.map((d) => d[groupKey]); + + let y = d3 + .scaleLinear() + .domain([ + 0, + d3.max(data, (d) => { + if (options.mode === "stacked") { + let sum = 0; + for (let key of keys) { + sum += d[key]; + } + return sum; + } + + return d3.max(keys, (key) => d[key]); + }), + ]) + .nice() + .rangeRound([height - margin.bottom, margin.top]); + + let x0 = d3 + .scaleBand() + .domain(groups) + .rangeRound([margin.left, width - margin.right]) + .paddingInner(0.2); + + let x1 = d3 + .scaleBand() + .domain(keys) + .rangeRound([0, x0.bandwidth()]) + .padding(0.1); + + let yAxis = (g) => + g + .attr("transform", `translate(${margin.left},0)`) + .attr("class", "d3chart-yaxis") + .call( + d3 + .axisLeft(y) + .ticks(null, options.valueType[0] === "percentage" ? "%" : "") + .tickSize(-width + margin.left + margin.right) + ) + .call((g) => g.select(".domain").remove()); + + let xAxis = (g) => + g + .attr("transform", `translate(0,${height - margin.bottom})`) + .attr("class", "d3chart-xaxis") + .call(d3.axisBottom(x0).tickSizeOuter(0)) + .call((g) => g.select(".domain").remove()); + + let dataMod = (d) => { + let incrementer = 0; + + return keys.map((key) => { + let data = { + key, + value: d[key], + width: x1.bandwidth(), + height: y(0) - y(d[key]), + left: x1(key), + top: y(d[key]), + slug: this.slugify(key, ""), + }; + + if (options.mode === "stacked") { + data.width = x0.bandwidth(); + data.left = 0; + data.top = y(d[key]) - incrementer; + incrementer += data.height; + } + + return data; + }); + }; + + svg.append("g").call(xAxis); + svg.append("g").call(yAxis); + + svg + .append("g") + .selectAll("g") + .data(data) + .join("g") + .attr("transform", (d) => `translate(${x0(d[groupKey])},0)`) + .selectAll("rect") + .data(dataMod) + .join("rect") + .attr("x", (d) => d.left) + .attr("y", (d) => d.top) + .attr("width", (d) => d.width) + .attr("height", (d) => (isNaN(d.height) ? 0 : d.height)) + .attr("fill", (d) => colors(d.key)) + .attr("data-item", (d) => d.key) + .attr("class", (d, j) => `d3chart-color-${j + options.colorMod}`) + .classed("d3chart-rect", true); + + if (options.showInlineBarValues) { + svg + .append("g") + .selectAll("g") + .data(data) + .join("g") + .attr("transform", (d) => `translate(${x0(d[groupKey])},0)`) + .selectAll("text") + .data(dataMod) + .join("text") + .attr("x", (d) => d.left + d.width / 2) + .attr("y", (d) => { + if (isNaN(d.height)) { + return 0; + } + + return ( + d.top - + (options.showInlineBarValues === "outside" + ? options.inlineLabelPad + : -15 - options.inlineLabelPad) + ); + }) + .attr("fill", (d) => + options.showInlineBarValues === "inside" + ? labelColors(d.key) + : "currentColor" + ) + .attr("class", "d3chart-inlinebarvalue") + .text((d) => { + if (d.value === null) { + return ""; + } + + return ( + this.roundValue(d.value, options.valueType[0]) + + (options.valueType[0] === "percentage" ? "%" : "") + ); + }); + } + + // TODO for horizontal bar chart + if (options.showAxisLabels) { + svg + .append("text") + .attr("x", Math.round(width / 2)) + .attr("y", height - 6) + .attr("class", "d3chart-axislabel d3chart-axislabel-center") + .text(this.axisLabels[0]); + } + + chart.reset(svg); + + if (options.wrapTicks && options.wrapTicks.x) { + const heights = []; + const wrap = d3.textwrap().bounds({ + height: margin.bottom, + width: x0.bandwidth() * 1 + keys.length + 2, + }); + + svg.selectAll(".d3chart-xaxis text").call(wrap); + svg + .selectAll("foreignObject") + .attr("x", function () { + return (-1 * +d3.select(this).attr("width")) / 2; + }) + .style("text-align", "center") + .style("font-weight", 600) + .attr("height", function () { + const height = d3 + .select(this) + .select("div") + .node() + .getBoundingClientRect().height; + heights.push(height); + return height; + }); + + svg.attr("overflow", "visible"); + svg.node().parentNode.style.marginBottom = `${Math.max(...heights)}px`; + } + + if (options.rotateXAxisLabels === true) { + svg + .select(".d3chart-xaxis") + .selectAll("text") + .attr("transform", "rotate(45)") + .style("text-anchor", "start"); + } + + if (options.interactive) { + this.setupInteractivity(svg); + } + } + + setupInteractivity(svg) { + const rectElements = svg.selectAll(".d3chart-rect"); + const labelElements = svg.selectAll(".d3chart-bubblelabel"); + + let resetTimeout; + + const legendItems = d3.selectAll( + `.${this.targetId}-legend .d3chart-legend-entry` + ); + + function knockBackOpacity() { + rectElements.style("fill-opacity", 0.15); + labelElements.style("fill-opacity", 0.15); + legendItems.style("opacity", 0.15); + } + + function resetOpacity() { + resetTimeout = setTimeout(() => { + labelElements.style("fill-opacity", 1); + rectElements.style("fill-opacity", 1); + + legendItems.style("opacity", 1); + }, 512); + } + + function handleLegendIteraction() { + clearTimeout(resetTimeout); + + knockBackOpacity(); + + const item = d3.select(this).attr("data-item"); + + const rect = rectElements.filter(function () { + return d3.select(this).attr("data-item") === item; + }); + + const label = labelElements.filter(function () { + return d3.select(this).attr("data-item") === item; + }); + + rect.style("fill-opacity", 1); + label.style("fill-opacity", 1); + + d3.select(this).style("opacity", 1); + } + + legendItems.on("mouseover", handleLegendIteraction); + legendItems.on("focus", handleLegendIteraction); + + legendItems.on("mouseout", function () { + resetOpacity(); + }); + + legendItems.on("focusout", function () { + resetOpacity(); + }); + + rectElements.on("mouseover", function (e, data) { + clearTimeout(resetTimeout); + + knockBackOpacity(); + + const label = svg.select( + `.d3chart-bubblelabel[data-item="${data.slug}"]` + ); + const legendItem = legendItems.filter(function () { + return d3.select(this).attr("data-item") === data.slug; + }); + + d3.select(this).style("fill-opacity", 1); + label.style("fill-opacity", 1); + legendItem.style("opacity", 1); + }); + + rectElements.on("mouseout", function () { + resetOpacity(); + }); + } +} + +class D3HorizontalBarChart extends D3Chart { + constructor(target, tableId, optionOverrides = {}) { + optionOverrides.margin = Object.assign( + { + top: 20, + right: 50, + bottom: 20, + left: 120, + }, + optionOverrides.margin + ); + let chart = super(target, optionOverrides, "d3chart-hbar"); + let csvData = chart.parseDataToCsv(tableId, true); + let data = Object.assign(d3.csvParse(csvData, d3.autoType)); + + this.onDeferInit(function () { + this.render(chart, data); + this.renderLegend(data); + + this.onResize(function () { + this.render(chart, data); + }); + }); + } + + render(chart, data) { + let { + options, + margin, + width, + height, + dimensions, + svg, + colors, + labelColors, + } = chart; + + let keys = this.getKeys(data); + let groupKey = data.columns[0]; + let groups = data.map((d) => d[groupKey]); + + let x = d3 + .scaleLinear() + .domain([ + 0, + d3.max(data, (d) => { + if (options.scale === "proportional") { + return 1; + } + + if (options.mode === "stacked") { + let sum = 0; + for (let key of keys) { + sum += d[key]; + } + return sum; + } + + return d3.max(keys, (key) => d[key]); + }), + ]) + .nice() + .rangeRound([margin.left, width - margin.right]); + + let y0 = d3 + .scaleBand() + .domain(groups) + .rangeRound([height - margin.bottom - margin.top, margin.top]) + .paddingInner( + options.showInlineBarValues === "inside-offset" ? 0.25 : 0.15 + ); + + let y1 = d3 + .scaleBand() + .domain(keys) + .rangeRound([0, y0.bandwidth()]) + .padding(0.05); + + let xAxis = (g) => + g + .attr("transform", `translate(0, ${(margin.top + margin.bottom) / 4})`) + .attr("class", "d3chart-xaxis") + .call( + d3 + .axisBottom(x) + .ticks(5, options.valueType[0] === "percentage" ? "%" : "") + .tickSize(height - margin.bottom - margin.top) + ) + .call((g) => g.select(".domain").remove()); + + let yAxis = (g) => + g + .attr("transform", `translate(${margin.left - 6},0)`) + .attr("class", "d3chart-yaxis") + .call(d3.axisLeft(y0).tickSize(0)) + .call((g) => g.select(".domain").remove()); + + let dataMod = (d) => { + let incrementer = 0; + let sum = 0; + for (let key of keys) { + sum += d[key]; + } + + return keys.map((key) => { + let data = { + key, + value: d[key], + sum, + width: + x(options.scale === "proportional" ? d[key] / sum : d[key]) - x(0), + height: y1.bandwidth(), + left: margin.left, + top: y1(key), + slug: this.slugify(key, ""), + }; + + if (options.mode === "stacked") { + data.top = 0; + data.height = y0.bandwidth(); + data.left = margin.left + incrementer; + + incrementer += data.width; + } + + return data; + }); + }; + + svg.append("g").call(xAxis); + svg.append("g").call(yAxis); + + svg + .append("g") + .selectAll("g") + .data(data) + .join("g") + .attr("transform", (d) => `translate(0,${y0(d[groupKey])})`) + .selectAll("rect") + .data(dataMod) + .join("rect") + .attr("x", (d) => d.left) + .attr("y", (d) => d.top) + .attr("width", (d) => (isNaN(d.width) ? 0 : d.width)) + .attr("height", (d) => d.height) + .attr("fill", (d) => colors(d.key)) + .attr("class", (d, j) => `d3chart-color-${j + options.colorMod}`) + .classed("d3chart-rect", true) + .attr("data-item", (d) => d.slug); + + if (options.showInlineBarValues) { + svg + .append("g") + .selectAll("g") + .data(data) + .join("g") + .attr("transform", (d) => `translate(0,${y0(d[groupKey])})`) + .selectAll("text") + .data(dataMod) + .join("text") + .attr("x", (d) => { + let offset = options.inlineLabelPad; + + if (isNaN(d.width)) { + return d.left + offset; + } + + if (options.showInlineBarValues.startsWith("inside")) { + offset = -1 * offset; + } + + if (options.showInlineBarValues === "inside-offset") { + offset += 16; + } + + return d.left + d.width + offset; + }) + .attr("y", (d) => { + if (options.showInlineBarValues === "inside-offset") { + return -10; + } + return d.top + Math.floor(d.height / 2) - 1; + }) + .attr( + "class", + (d) => + "d3chart-inlinebarvalue-h" + + (options.showInlineBarValues.length + ? ` ${options.showInlineBarValues}` + : "") + ) + .attr("fill", (d) => + options.showInlineBarValues === "inside" + ? labelColors(d.key) + : "currentColor" + ) + .text((d) => { + if (d.value === null) { + return ""; + } + + return ( + this.roundValue(d.value, options.valueType[0]) + + (options.valueType[0] === "percentage" ? "%" : "") + ); + }); + } + + chart.reset(svg); + + if (options.scaleTicks && options.scaleTicks.x) { + this.scaleTicksX(svg); + } + + if (options.wrapAxisLabel && options.wrapAxisLabel.left) { + D3Chart.wrapText( + svg.selectAll(".d3chart-yaxis .tick text"), + margin.left - 6 + ); + } + + if (options.interactive) { + this.setupInteractivity(svg); + } + } + + setupInteractivity(svg) { + const rectElements = svg.selectAll(".d3chart-rect"); + const labelElements = svg.selectAll(".d3chart-bubblelabel"); + + let resetTimeout; + + const legendItems = d3.selectAll( + `.${this.targetId}-legend .d3chart-legend-entry` + ); + + function knockBackOpacity() { + rectElements.style("fill-opacity", 0.15); + labelElements.style("fill-opacity", 0.15); + legendItems.style("opacity", 0.15); + } + + function resetOpacity() { + resetTimeout = setTimeout(() => { + labelElements.style("fill-opacity", 1); + rectElements.style("fill-opacity", 1); + + legendItems.style("opacity", 1); + }, 512); + } + + function handleLegendIteraction() { + clearTimeout(resetTimeout); + + knockBackOpacity(); + + const item = d3.select(this).attr("data-item"); + + const rect = rectElements.filter(function () { + return d3.select(this).attr("data-item") === item; + }); + + const label = labelElements.filter(function () { + return d3.select(this).attr("data-item") === item; + }); + + rect.style("fill-opacity", 1); + label.style("fill-opacity", 1); + + d3.select(this).style("opacity", 1); + } + + legendItems.on("mouseover", handleLegendIteraction); + legendItems.on("focus", handleLegendIteraction); + + legendItems.on("mouseout", function () { + resetOpacity(); + }); + + legendItems.on("focusout", function () { + resetOpacity(); + }); + + rectElements.on("mouseover", function (e, data) { + clearTimeout(resetTimeout); + + knockBackOpacity(); + + const label = svg.select( + `.d3chart-bubblelabel[data-item="${data.slug}"]` + ); + const legendItem = legendItems.filter(function () { + return d3.select(this).attr("data-item") === data.slug; + }); + + d3.select(this).style("fill-opacity", 1); + label.style("fill-opacity", 1); + legendItem.style("opacity", 1); + }); + + rectElements.on("mouseout", function () { + resetOpacity(); + }); + } +} + +class D3BubbleChart extends D3Chart { + constructor(target, tableId, optionOverrides = {}) { + optionOverrides.margin = { + top: 20, + right: 20, + bottom: 50, + left: 65, + }; + + optionOverrides.sortLegend = true; + optionOverrides.highlightElementsFromLegend = true; + optionOverrides.showAxisLabels = true; + + if (!optionOverrides.valueType) { + optionOverrides.valueType = ["percentage", "percentage"]; + } + + let chart = super(target, optionOverrides, "d3chart-bubble"); + let csvData = chart.parseDataToCsv(tableId); + let dataSplit = csvData.split("\n"); + this.axisLabels = dataSplit[0].split(",").slice(1); + + let data = dataSplit.slice(1).map((entry, id) => { + const columns = entry.split(","); + let [name, x, y, r] = columns; + return { + name, + id, + x, + y, + r: r ?? columns[optionOverrides.radiusColumn], + slug: this.slugify(name, ""), + }; + }); + + // sort from smallest to largest circles to insert in order (to render in the right z-index) + data = data.slice().sort((a, b) => { + return b.r - a.r; + }); + + this.onDeferInit(function () { + this.render(chart, data); + this.renderLegend(data); + + this.onResize(function () { + this.render(chart, data); + }); + }); + } + + getKeys(data) { + let keys = []; + for (let entry of data) { + keys.push(entry.name); + } + return keys; + } + + resolveLimit(data, key, valueType, mode) { + let limit = d3[mode](data, (d) => parseFloat(d[key])); + if (valueType !== "percentage") { + if (mode === "max") { + limit = Math.ceil(limit); + } else if (mode === "min") { + limit = Math.min(Math.floor(limit), 0); + } + } else { + if (mode === "max") { + if (limit > 1) { + limit += 0.1; + } else { + // round up to at most 1 if percentage < 100% + if (limit > 0.5) { + limit = Math.min(limit + 0.1, 1); + } else { + (limit = limit + 0.05), 1; + } + } + } + if (mode === "min") { + if (limit <= 0) { + limit -= 0.1; + } else { + // round up to at most 1 if percentage < 100% + limit = Math.min(limit, 0); + } + } + } + + return limit; + } + + render(chart, data) { + let { + options, + margin, + width, + height, + dimensions, + svg, + colors, + labelColors, + } = chart; + let xScale = d3.scaleLinear().range([margin.left, width - margin.right]); + let yScale = d3.scaleLinear().range([margin.top, height - margin.bottom]); + + let xAxisMin = this.resolveLimit(data, "x", options.valueType[0], "min"); + let xAxisMax = this.resolveLimit(data, "x", options.valueType[0], "max"); + let yAxisMin = this.resolveLimit(data, "y", options.valueType[1], "min"); + let yAxisMax = this.resolveLimit(data, "y", options.valueType[1], "max"); + + const yExtent = d3.extent([yAxisMin, yAxisMax]); + const yRange = yExtent[1] - yExtent[0]; + + xScale.domain([xAxisMin, xAxisMax]).nice(); + yScale + .domain([yExtent[1] + yRange * 0.05, yExtent[0] - yRange * 0.05]) + .nice(); + + let rScale = d3 + .scaleLinear() + .range([7, 25]) + .domain([ + Math.min( + d3.min(data, (d) => parseFloat(d.r)), + 0 + ), + d3.max(data, (d) => parseFloat(d.r)), + ]); + + let xAxis = d3 + .axisBottom() + .scale(xScale) + .ticks(null) + .tickSize(-height + margin.bottom + margin.top) + .tickFormat((d) => + options.valueType[0] === "percentage" ? `${(d * 100).toFixed(0)}%` : d + ); + + svg + .append("g") + .attr("class", "d3chart-xaxis") + .attr("transform", function () { + return "translate(0," + (height - margin.bottom) + ")"; + }) + .call(xAxis) + .call((g) => g.select(".domain").remove()); + + let yAxis = d3 + .axisLeft() + .scale(yScale) + .ticks(null) + .tickSize(-width + margin.right + margin.left) + .tickFormat((d) => + options.valueType[1] === "percentage" ? `${(d * 100).toFixed(0)}%` : d + ); + + svg + .append("g") + .attr("class", "d3chart-yaxis") + .attr("transform", function () { + return "translate(" + margin.left + "," + 0 + ")"; + }) + .call(yAxis) + .call((g) => g.select(".domain").remove()); + + svg.selectAll(".d3chart-xaxis .tick").attr("data-chart-value", (d) => d); + svg.selectAll(".d3chart-yaxis .tick").attr("data-chart-value", (d) => d); + + if (options.showAxisLabels) { + // Axis labels + svg + .append("text") + .attr("x", width - margin.right) + .attr("y", height - 6) + .attr("class", "d3chart-axislabel") + .text(this.axisLabels[0]); + + svg + .append("text") + .attr("x", -1 * margin.top) + .attr("y", 6) + .attr("dy", ".75em") + .attr("transform", "rotate(-90)") + .attr("class", "d3chart-axislabel") + .text(this.axisLabels[1]); + } + + let group = svg.append("g"); + + let circles = group.selectAll("circle").data(data); + + // Text Labels + function isOffsetLabel(d) { + let range = rScale(d.r); + return range <= 10; + } + + circles + .enter() + .insert("circle") + .attr("data-item", (d) => d.slug) + .attr("cx", function (d) { + return xScale(d.x); + }) + .attr("cy", function (d) { + return yScale(d.y); + }) + .attr("r", function (d) { + return rScale(d.r); + }) + .attr( + "class", + (d, j) => `d3chart-bubblecircle d3chart-color-${j + options.colorMod}` + ); + + circles + .enter() + .append("text") + .attr("data-item", (d) => d.slug) + .attr("x", (d) => { + return xScale(d.x) - (isOffsetLabel(d) ? rScale(d.r) + 4 : 0); + }) + .attr("y", (d) => yScale(d.y)) + .attr("class", (d) => { + return "d3chart-bubblelabel" + (isOffsetLabel(d) ? " offset-l" : ""); + }) + .attr("fill", (d) => (isOffsetLabel(d) ? "currentColor" : labelColors(d))) + .attr("pointer-events", "none") + .text((d) => { + let labelId = this.retrieveLabelId(d.name); + if (labelId) { + return labelId; + } + return d.name; + }) + .filter((d) => isOffsetLabel(d)) + .lower(); + + chart.reset(svg); + + if (options.scaleTicks && options.scaleTicks.x) { + this.scaleTicksX(svg); + } + + this.setupInteractivity(svg); + } + + setupInteractivity(svg) { + const circleElements = svg.selectAll(".d3chart-bubblecircle"); + const labelElements = svg.selectAll(".d3chart-bubblelabel"); + + let resetTimeout; + + const legendItems = d3.selectAll( + `.${this.targetId}-legend .d3chart-legend-entry` + ); + + function knockBackOpacity() { + circleElements.style("fill-opacity", 0.15); + labelElements.style("fill-opacity", 0.15); + legendItems.style("opacity", 0.15); + } + + function resetOpacity() { + resetTimeout = setTimeout(() => { + labelElements.style("fill-opacity", 1); + circleElements.style("fill-opacity", 0.85); + + legendItems.style("opacity", 1); + }, 512); + } + + function handleLegendIteraction() { + clearTimeout(resetTimeout); + + knockBackOpacity(); + + const item = d3.select(this).attr("data-item"); + + const circle = circleElements.filter(function () { + return d3.select(this).attr("data-item") === item; + }); + + const label = labelElements.filter(function () { + return d3.select(this).attr("data-item") === item; + }); + + circle.style("fill-opacity", 1); + label.style("fill-opacity", 1); + + d3.select(this).style("opacity", 1); + } + + legendItems.on("mouseover", handleLegendIteraction); + legendItems.on("focus", handleLegendIteraction); + + legendItems.on("mouseout", function () { + resetOpacity(); + }); + + legendItems.on("focusout", resetOpacity); + + circleElements.on("mouseover", function (e, data) { + clearTimeout(resetTimeout); + + knockBackOpacity(); + + const label = svg.select( + `.d3chart-bubblelabel[data-item="${data.slug}"]` + ); + const legendItem = legendItems.filter(function () { + return d3.select(this).attr("data-item") === data.slug; + }); + + d3.select(this).style("fill-opacity", 1); + label.style("fill-opacity", 1); + legendItem.style("opacity", 1); + }); + + circleElements.on("mouseout", function () { + resetOpacity(); + }); + } +} + +class D3LineChart extends D3Chart { + constructor(target, tableId, optionOverrides = {}) { + let chart = super(target, optionOverrides, "d3chart-hline"); + let csvData = chart.parseDataToCsv(tableId, true); + let data = Object.assign(d3.csvParse(csvData, d3.autoType)); + + this.onDeferInit(function () { + this.render(chart, data); + this.renderLegend(data); + + this.onResize(function () { + this.render(chart, data); + }); + }); + } + + render(chart, data) { + let { options, margin, width, height, dimensions, svg } = chart; + + const paddingX = dimensions.container.width / 16; + const paddingY = 0; + + const timeConv = d3.timeParse("%Y"); + + const slices = data.columns.slice(1).map((id) => { + return { + id, + values: data.map((d) => { + return { + date: timeConv(d.Date), + measurement: +d[id], + }; + }), + slug: this.slugify(id, ""), + }; + }); + + const xScale = d3 + .scaleTime() + .range([margin.left + paddingX, width - margin.right - paddingX]) + .domain(d3.extent(data, (d) => timeConv(d.Date))); + + const yScale = d3 + .scaleLinear() + .rangeRound([height - margin.bottom - paddingY, margin.top + paddingY]) + .domain([ + 0, + d3.max(slices, (c) => d3.max(c.values, (d) => d.measurement)), + ]) + .nice(); + + const yaxis = d3 + .axisLeft() + .tickFormat(d3.format(".0%")) + .tickSize(-width + margin.left + margin.right) + .scale(yScale); + const xaxis = d3.axisBottom().ticks(d3.timeYear.every(1)).scale(xScale); + + svg + .append("g") + .attr("transform", `translate(0, ${height - margin.bottom})`) + .call(xaxis) + .call((g) => g.select(".domain").remove()); + + svg + .append("g") + .attr("transform", `translate(${margin.left}, 0)`) + .call(yaxis) + .call((g) => g.select(".domain").remove()); + + const line = d3 + .line() + .x(function (d) { + return xScale(d.date); + }) + .y(function (d) { + return yScale(d.measurement); + }); + + const lines = svg.selectAll("lines").data(slices).enter().append("g"); + + lines + .append("path") + .attr("d", (d) => line(d.values)) + .attr("fill", "none") + .attr("stroke-width", 5) + .attr( + "class", + (d, j) => `d3chart-line d3chart-color-stroke-${j + options.colorMod}` + ) + .attr("data-item", (d) => d.slug); + + chart.reset(svg); + + this.setupInteractivity(svg); + } + + setupInteractivity(svg) { + const lineElements = svg.selectAll(".d3chart-line"); + const legendItems = d3.selectAll( + `.${this.targetId}-legend .d3chart-legend-entry` + ); + + let resetTimeout; + + function knockBackOpacity() { + lineElements.style("opacity", 0.15); + legendItems.style("opacity", 0.15); + } + + function resetOpacity() { + resetTimeout = setTimeout(() => { + lineElements.style("opacity", 1); + legendItems.style("opacity", 1); + }, 512); + } + + lineElements.on("mouseover", function (e, data) { + clearTimeout(resetTimeout); + + knockBackOpacity(); + + d3.select(this).style("opacity", 1); + + const legendItem = legendItems.filter(function () { + return d3.select(this).attr("data-item") === data.slug; + }); + + legendItem.style("opacity", 1); + }); + + lineElements.on("mouseout", function () { + resetOpacity(); + }); + + legendItems.on("mouseover", function (e, data) { + clearTimeout(resetTimeout); + knockBackOpacity(); + + const slug = d3.select(this).attr("data-item"); + + const line = lineElements.filter(function (d) { + return d.slug === slug; + }); + + line.style("opacity", 1); + + d3.select(this).style("opacity", 1); + }); + + legendItems.on("mouseout", function () { + resetOpacity(); + }); + } +} diff --git a/src/site/tv.njk b/src/site/tv.njk index be0250a5a..c75f98b08 100644 --- a/src/site/tv.njk +++ b/src/site/tv.njk @@ -1,30 +1,385 @@ --- title: Jamstack TV -subtitle: Jamstack TV is a searchable collection of video resources. Interested in a topic? Looking for a specific talk? Search here and we’ll find that talk or topic timestamp within the videos for you. +subtitle: A curated collection of Jamstack video resources featuring talks, tutorials, and conference presentations from the community. layout: layouts/base.njk -preconnect: - - https://cdn.jsdelivr.net -javascriptIncludes: - - search-js.njk --- -{# This search index data is populated by the code at https://github.com/netlify/jamstack-tv-search-data #}

    {{ title }}

    -

    {{ subtitle }}

    +

    {{ subtitle }}

    -
    -
    - -
    Search by Algolia
    -
    -
    -
    Sort by
    -
    -
    -
    -
    +
    + +
      +
    1. + +
      + Jamstack Meetup SF: Realizing the Potential of the A in Jamstack + 42:53 +
      +
      +
      + Jamstack Meetups + + Jamstack Meetup SF: Realizing the Potential of the A in Jamstack + +
      + + hello and + +
      +
      +
    2. + +
    3. + +
      + [Livestream] JAMstack Meetup #16 + 59:56 +
      +
      +
      + Jamstack Meetups + [Livestream] JAMstack Meetup #16 +
      + + + (captions sponsored by Prisma) - [Enxhi] And we're live. + + +
      +
      +
    4. + +
    5. + +
      + Oh The Scripts We'll Load - A Performance Talk by Tim Kadlec + 31:31 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + + Oh The Scripts We'll Load - A Performance Talk by Tim Kadlec + +
      + + thanks phil + +
      +
      +
    6. + +
    7. + +
      + Jamstack Conf Virtual - The Jammies Awards + 18:39 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + Jamstack Conf Virtual - The Jammies Awards +
      + + so let's move on + +
      +
      +
    8. + +
    9. + +
      + JamSnack - What's New in Hugo + 2:26 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + JamSnack - What's New in Hugo +
      + + hello + +
      +
      +
    10. + +
    11. + +
      + JamSnack - What's New in Next.js + 3:07 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + JamSnack - What's New in Next.js +
      + + hello jams.com my name is cassidy and + +
      +
      +
    12. + +
    13. + +
      + JamSnack - What's New in Redwood.js + 2:20 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + JamSnack - What's New in Redwood.js +
      + + does the javascript ecosystem leave you + +
      +
      +
    14. + +
    15. + +
      + JamSnack - What's New in Scully + 2:48 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + JamSnack - What's New in Scully +
      + + hey jammers + +
      +
      +
    16. + +
    17. + +
      + JamSnack - What's New in Vue.js + 2:54 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + JamSnack - What's New in Vue.js +
      + + hello james.com this is evan yu + +
      +
      +
    18. + +
    19. + +
      + JamSnack - What's New in Gatsby.js + 1:44 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + JamSnack - What's New in Gatsby.js +
      + + hi + +
      +
      +
    20. + +
    21. + +
      + JamSnack - What's New in Angular + 2:01 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + JamSnack - What's New in Angular +
      + + hi + +
      +
      +
    22. + +
    23. + +
      + JamSnack - What's New in 11ty + 2:32 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + JamSnack - What's New in 11ty +
      + + what's up y'all + +
      +
      +
    24. + +
    25. + +
      + Teespring's Journey to the Jamstack, Rick Takes + 17:32 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + Teespring's Journey to the Jamstack, Rick Takes +
      + + hi everyone + +
      +
      +
    26. + +
    27. + +
      + State of the Jamstack Oct 2020 Keynote, Matt Biilmann of Netlify + 20:49 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + + State of the Jamstack Oct 2020 Keynote, Matt Biilmann of Netlify + +
      + + hi everybody + +
      +
      +
    28. + +
    29. + +
      + Bringing Remote Sensing and Disaster Relief to the Jamstack + 9:38 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + + Bringing Remote Sensing and Disaster Relief to the Jamstack + +
      + + hey everyone + +
      +
      +
    30. + +
    31. + +
      + Migrating to Netlify, One Page at a Time + 9:56 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + Migrating to Netlify, One Page at a Time +
      + + hello + +
      +
      +
    32. + +
    33. + +
      + Jamstack Lightning Launch: Algolia Search Crawlers + 7:47 +
      +
      +
      + Jamstack Conf Virtual Oct 2020 + Jamstack Lightning Launch: Algolia Search Crawlers +
      + + + [Musique] i want my name is fair and engineering + + +
      +
      +
    34. +
    +
    \ No newline at end of file diff --git a/src/site/what-is-jamstack.njk b/src/site/what-is-jamstack.njk index 60bb17d48..3acb5b409 100644 --- a/src/site/what-is-jamstack.njk +++ b/src/site/what-is-jamstack.njk @@ -31,7 +31,7 @@ layout: layouts/base.njk With the markup and other user interface assets of Jamstack sites served directly from a CDN, they can be delivered very quickly and securely. On this foundation, Jamstack sites can use JavaScript and APIs to talk to backend services, allowing experiences to be enhanced and personalized.

    -
    +

    Supercharging with services

    The thriving API economy has become a significant enabler for Jamstack sites. The ability to leverage domain experts who offer their products and service via APIs has allowed teams to build far more complex applications than if they were to take on the risk and burden of such capabilities themselves. Now we can outsource things like authentication and identity, payments, content management, data services, search, and much more. @@ -40,7 +40,7 @@ layout: layouts/base.njk Jamstack sites might utilise such services at build time, and also at run time directly from the browser via JavaScript. And the clean decoupling of these services allows for greater portability and flexibility, as well as significantly reduced risk.

    -
    +

    Named to help the conversation

    The name "Jamstack" came about because as Matt Biilmann and Chris Bach were creating modern web development workflows and capabilities at Netlify, they found there was no easy way to refer to the architectural approach in conversation. Jamstack embraces many existing fundamentals of web architectures, and so they created the term Jamstack to help us talk about it more succinctly. @@ -51,4 +51,3 @@ layout: layouts/base.njk

    -{% include "components/join-the-movement.njk" %} diff --git a/src/site/why-jamstack.njk b/src/site/why-jamstack.njk index 8ab0c0062..e92619c2b 100644 --- a/src/site/why-jamstack.njk +++ b/src/site/why-jamstack.njk @@ -47,7 +47,7 @@ layout: layouts/base.njk When hosting complexity is reduced, so are maintenance tasks. A pre-generated site, being served directly from a simple host or directly from a CDN does not need a team of experts to "keep the lights on".

    - The work was done during the build, so now the generated site is stable and can be hosted without servers which might require patching, updating and maintain. + The work was done during the build, so now the generated site is stable and can be hosted without servers which might require patching, updating and maintenance.

    @@ -61,11 +61,9 @@ layout: layouts/base.njk

    -
    +

    Developer Experience

    Jamstack sites can be built with a wide variety of tools. They do not depend on the proprietary technologies or exotic and little known frameworks. Instead, they build on widely available tools and conventions. As a result, it's not hard to find enthusiastic and talented developers who have the right skills to build with the Jamstack. Efficiency and effectiveness can prosper.

    -
    - -{% include "components/join-the-movement.njk" %} +
    \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index fb220f679..75495f63d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,8 +1,9 @@ module.exports = { - purge: [ + content: [ './src/site/**/*.njk', './src/site/**/*.md' ], + darkMode: ['class'], theme: { borderRadius: { 'none': '0', @@ -33,8 +34,8 @@ module.exports = { 700: "#2D3247", 400: "#5A5F75", 300: "#9AA0B6", - 200: "#DEDEDE", - 100: "#10121E" + 200: "#D8DEEC", + 100: "#F8FAFF" }, pink : { 900 : '#D1036F', // old