├── .browserslistrc
├── .eleventy.js
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .stylelintrc
├── README.md
├── babel.config.js
├── netlify.toml
├── package.json
├── rollup.config.js
├── src
├── _data
│ └── pages.js
├── _includes
│ └── layout.njk
├── app.js
├── components
│ ├── App.js
│ ├── App.scss
│ ├── BaseWrapper.js
│ ├── BaseWrapper.scss
│ ├── LayoutDefault.js
│ ├── LayoutDefault.scss
│ ├── LikeForm.js
│ ├── LikeForm.scss
│ ├── SectionContent.js
│ ├── SectionHero.js
│ ├── SectionHero.scss
│ ├── SectionMasonry.js
│ ├── SectionMasonry.scss
│ ├── SectionTeaser.js
│ ├── SectionTeaser.scss
│ └── with-hydration.js
├── page.11ty.js
├── styles
│ ├── generic
│ │ ├── base.scss
│ │ └── box-sizing-reset.scss
│ ├── index.scss
│ ├── scopes
│ │ └── content.scss
│ └── utilities
│ │ └── text-align.scss
└── utils
│ ├── is-server.js
│ └── when-visible.js
└── yarn.lock
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 0.5%
2 | not ie <= 10
3 | not op_mini all
4 | not dead
5 |
--------------------------------------------------------------------------------
/.eleventy.js:
--------------------------------------------------------------------------------
1 | module.exports = function eleventy() {
2 | return {
3 | dir: {
4 | input: `src`,
5 | output: `dist`,
6 | },
7 | };
8 | };
9 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 |
4 | !.eleventy.js
5 | !.eslintrc.js
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: [
4 | `preact`,
5 | `@avalanche/eslint-config`,
6 | ],
7 | rules: {
8 | 'no-console': process.env.NODE_ENV === `production` ? `error` : `warn`,
9 | 'no-debugger': process.env.NODE_ENV === `production` ? `error` : `warn`,
10 | 'no-underscore-dangle': `off`,
11 | },
12 | parserOptions: {
13 | ecmaVersion: 2020,
14 | parser: `babel-eslint`,
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Numerous always-ignore extensions
2 | *.diff
3 | *.err
4 | *.log
5 | *.orig
6 | *.rej
7 | *.swo
8 | *.swp
9 | *.tgz
10 | *.vi
11 | *.zip
12 | *~
13 |
14 | # OS or Editor folders
15 | ._*
16 | .cache
17 | .DS_Store
18 | .idea
19 | .project
20 | .settings
21 | .tmproj
22 | *.esproj
23 | *.sublime-project
24 | *.sublime-workspace
25 | nbproject
26 | Thumbs.db
27 |
28 | # Folders to ignore
29 | dist
30 | node_modules
31 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@avalanche/stylelint-config",
3 | "rules": {
4 | "max-nesting-depth": 4
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # eleventy-preact
2 |
3 | [](https://www.patreon.com/maoberlehner)
4 | [](https://paypal.me/maoberlehner)
5 |
6 | ## Build Setup
7 |
8 | ```bash
9 | # Serve with hot reload.
10 | npm run dev
11 |
12 | # Build for production with minification.
13 | npm run build
14 |
15 | # Lint all files.
16 | npm run lint
17 | ```
18 |
19 | ## About
20 |
21 | ### Author
22 |
23 | Markus Oberlehner
24 | Website: https://markus.oberlehner.net
25 | Twitter: https://twitter.com/MaOberlehner
26 | PayPal.me: https://paypal.me/maoberlehner
27 | Patreon: https://www.patreon.com/maoberlehner
28 |
29 | ### License
30 |
31 | MIT
32 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | `@babel/preset-env`,
5 | {
6 | loose: true,
7 | modules: false,
8 | },
9 | ],
10 | ],
11 | plugins: [
12 | // See https://github.com/developit/htm/tree/master/packages/babel-plugin-htm
13 | // for configuration options.
14 | [`babel-plugin-htm`, {
15 | import: `preact`,
16 | }],
17 | ],
18 | };
19 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [[redirects]]
2 | from = "/like/*"
3 | to = "/"
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eleventy-preact",
3 | "version": "0.1.0",
4 | "author": "Markus Oberlehner",
5 | "homepage": "https://github.com/maoberlehner/eleventy-preact",
6 | "license": "MIT",
7 | "private": true,
8 | "scripts": {
9 | "build": "NODE_ENV=production concurrently 'eleventy' 'npm run scripts:app' 'npm run styles'",
10 | "dev": "NODE_ENV=dev concurrently 'eleventy --serve' 'npm run watch'",
11 | "scripts:app": "rollup --config --file dist/app.js --format iife --name App src/app.js",
12 | "scripts:app:watch": "npm run scripts:app -- --watch",
13 | "styles": "node-sass --importer node_modules/node-sass-magic-importer/dist/cli.js -o dist src/styles/index.scss",
14 | "styles:watch": "chokidar 'src/**/*.scss' -c 'npm run styles'",
15 | "watch": "concurrently 'npm run scripts:app:watch' 'npm run styles:watch'",
16 | "lint:scripts": "eslint --ext .js .",
17 | "lint:styles": "stylelint 'src/**/*.scss'",
18 | "lint": "concurrently 'npm run lint:scripts' 'npm run lint:styles'"
19 | },
20 | "dependencies": {
21 | "htm": "^3.0.3",
22 | "preact": "^10.3.4",
23 | "reset-css": "^5.0.1"
24 | },
25 | "devDependencies": {
26 | "@11ty/eleventy": "^0.10.0",
27 | "@avalanche/eslint-config": "^4.0.0",
28 | "@avalanche/stylelint-config": "^1.0.1",
29 | "@babel/preset-env": "^7.9.0",
30 | "@rollup/plugin-commonjs": "^11.0.2",
31 | "@rollup/plugin-node-resolve": "^7.1.1",
32 | "babel-eslint": "^10.1.0",
33 | "babel-plugin-htm": "^3.0.0",
34 | "chokidar-cli": "^2.1.0",
35 | "concurrently": "^5.1.0",
36 | "eslint": "^6.8.0",
37 | "eslint-config-preact": "^1.1.1",
38 | "eslint-plugin-import": "^2.20.1",
39 | "node-sass": "^4.13.1",
40 | "node-sass-magic-importer": "^5.3.2",
41 | "preact-render-to-string": "^5.1.4",
42 | "rollup": "^2.2.0",
43 | "rollup-plugin-babel": "^4.4.0",
44 | "rollup-plugin-filesize": "^6.2.1",
45 | "rollup-plugin-terser": "^5.3.0",
46 | "stylelint": "^13.2.1"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { terser } from 'rollup-plugin-terser';
2 | import babel from 'rollup-plugin-babel';
3 | import commonjs from '@rollup/plugin-commonjs';
4 | import filesize from 'rollup-plugin-filesize';
5 | import resolve from '@rollup/plugin-node-resolve';
6 |
7 | export default {
8 | plugins: [
9 | resolve(),
10 | commonjs(),
11 | babel(),
12 | terser(),
13 | filesize(),
14 | ],
15 | };
16 |
--------------------------------------------------------------------------------
/src/_data/pages.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | date: new Date(),
4 | name: `Hello World`,
5 | sections: [
6 | {
7 | name: `hero`,
8 | data: {
9 | image: `https://images.unsplash.com/photo-1518699705938-d9be21ec6ff6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1920&h=450&q=50`,
10 | title: `Welcome!`,
11 | text: `This is a site built with Eleventy + Preact.`,
12 | },
13 | },
14 | {
15 | name: `content`,
16 | data: {
17 | html: `
Lorem Ipsum Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Make sure to also visit Page 2!
`,
18 | },
19 | },
20 | {
21 | name: `masonry`,
22 | data: {
23 | images: [
24 | {
25 | alt: `Snowy mountains.`,
26 | id: 1,
27 | src: `https://images.unsplash.com/reserve/4JUAYmLTuS1mBvBBwe3D_St_Anton.png?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80`,
28 | },
29 | {
30 | alt: `Snowy mountains.`,
31 | id: 2,
32 | src: `https://images.unsplash.com/photo-1464277621354-a197b03f19cb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80`,
33 | },
34 | {
35 | alt: `Snowy mountains.`,
36 | id: 3,
37 | src: `https://images.unsplash.com/photo-1467049878711-ebcf8c4b23da?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80`,
38 | },
39 | {
40 | alt: `Snowy mountains.`,
41 | id: 4,
42 | src: `https://images.unsplash.com/photo-1464852045489-bccb7d17fe39?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80`,
43 | },
44 | {
45 | alt: `Snowy mountains.`,
46 | id: 5,
47 | src: `https://images.unsplash.com/photo-1462351850891-7aedcb9e2450?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80`,
48 | },
49 | {
50 | alt: `Snowy mountains.`,
51 | id: 6,
52 | src: `https://images.unsplash.com/photo-1465726208258-198e6f4402ae?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80`,
53 | },
54 | ],
55 | },
56 | },
57 | {
58 | name: `teaser`,
59 | data: [
60 | {
61 | image: `https://images.unsplash.com/photo-1569938298692-3547607c467a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjF9&auto=format&fit=crop&w=600&h=600&q=30`,
62 | title: `Lorem Ipsum`,
63 | text: `Tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.`,
64 | link: `https://markus.oberlehner.net`,
65 | },
66 | {
67 | image: `https://images.unsplash.com/photo-1562650740-ce0f04d0d73e?ixlib=rb-1.2.1&auto=format&fit=crop&w=600&h=600&q=30`,
68 | title: `Lorem Ipsum`,
69 | text: `Tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.`,
70 | link: `https://markus.oberlehner.net`,
71 | },
72 | {
73 | image: `https://images.unsplash.com/photo-1580565387371-f73cd4b83859?ixlib=rb-1.2.1&auto=format&fit=crop&w=600&h=600&q=30`,
74 | title: `Lorem Ipsum`,
75 | text: `Tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.`,
76 | link: `https://markus.oberlehner.net`,
77 | },
78 | ],
79 | },
80 | ],
81 | slug: `/`,
82 | },
83 | {
84 | date: new Date(),
85 | name: `Page 2`,
86 | sections: [
87 | {
88 | name: `hero`,
89 | data: {
90 | image: `https://images.unsplash.com/photo-1517690623533-ca77a9a4b402?ixlib=rb-1.2.1&auto=format&fit=crop&w=1920&h=450&q=50`,
91 | title: `Welcome to page 2!`,
92 | text: `This is a site built with Eleventy + Preact.`,
93 | },
94 | },
95 | {
96 | name: `content`,
97 | data: {
98 | html: `Lorem Ipsum Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
`,
99 | },
100 | },
101 | ],
102 | slug: `/page-2`,
103 | },
104 | ];
105 |
--------------------------------------------------------------------------------
/src/_includes/layout.njk:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ title }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{ content | safe }}
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | const { html, render } = require(`htm/preact`);
2 |
3 | const whenVisible = require(`./utils/when-visible`);
4 |
5 | const LikeForm = require(`./components/LikeForm`);
6 |
7 | const componentMap = {
8 | LikeForm,
9 | };
10 |
11 | const $componentMarkers = document.querySelectorAll(`[data-cmp-id]`);
12 |
13 | Array.from($componentMarkers).forEach(($marker) => {
14 | const $component = $marker.nextElementSibling;
15 |
16 | whenVisible($component, () => {
17 | const { name, props } = window.__STATE__.components[$marker.dataset.cmpId];
18 | const Component = componentMap[name];
19 |
20 | render(html`<${Component} ...${props}/>`, $component.parentNode, $component);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/src/components/App.js:
--------------------------------------------------------------------------------
1 | const { html } = require(`htm/preact`);
2 |
3 | const LayoutDefault = require(`./LayoutDefault`);
4 | const SectionContent = require(`./SectionContent`);
5 | const SectionHero = require(`./SectionHero`);
6 | const SectionMasonry = require(`./SectionMasonry`);
7 | const SectionTeaser = require(`./SectionTeaser`);
8 |
9 | const sections = {
10 | content: SectionContent,
11 | hero: SectionHero,
12 | masonry: SectionMasonry,
13 | teaser: SectionTeaser,
14 | };
15 |
16 | module.exports = ({ page }) => html`
17 | <${LayoutDefault}>
18 |
19 | ${page.sections.map(({ data, name }) => html`
20 | <${sections[name]} data=${data}/>
21 | `)}
22 |
23 | />
24 | `;
25 |
--------------------------------------------------------------------------------
/src/components/App.scss:
--------------------------------------------------------------------------------
1 | .App {
2 | > :not(:first-child) {
3 | margin-top: 3em;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/BaseWrapper.js:
--------------------------------------------------------------------------------
1 | const { html } = require(`htm/preact`);
2 |
3 | module.exports = ({ children }) => html`
4 |
5 | ${children}
6 |
7 | `;
8 |
--------------------------------------------------------------------------------
/src/components/BaseWrapper.scss:
--------------------------------------------------------------------------------
1 | .BaseWrapper {
2 | margin-right: auto;
3 | margin-left: auto;
4 | max-width: 48em;
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/LayoutDefault.js:
--------------------------------------------------------------------------------
1 | const { html } = require(`htm/preact`);
2 |
3 | const BaseWrapper = require(`./BaseWrapper`);
4 |
5 | module.exports = ({ children }) => html`
6 |
7 |
8 | ${children}
9 |
10 |
15 |
16 | `;
17 |
--------------------------------------------------------------------------------
/src/components/LayoutDefault.scss:
--------------------------------------------------------------------------------
1 | .LayoutDefault__footer {
2 | margin-top: 5em;
3 | padding-top: 1em;
4 | padding-bottom: 1em;
5 | border-top: 1px solid #efefef;
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/LikeForm.js:
--------------------------------------------------------------------------------
1 | const { html } = require(`htm/preact`);
2 | const { useState } = require(`preact/hooks`);
3 |
4 | const withHydration = require(`./with-hydration`);
5 |
6 | function LikeForm({ id }) {
7 | const [likes, setLikes] = useState(0);
8 | const handleClick = (e) => {
9 | e.preventDefault();
10 | setLikes(likes + 1);
11 | };
12 |
13 | return html`
14 |
28 | `;
29 | }
30 |
31 | module.exports = withHydration(LikeForm);
32 |
--------------------------------------------------------------------------------
/src/components/LikeForm.scss:
--------------------------------------------------------------------------------
1 | .LikeForm__button {
2 | margin-right: 0.5em;
3 | padding: 0;
4 | border: none;
5 | background-color: transparent;
6 | cursor: pointer;
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/SectionContent.js:
--------------------------------------------------------------------------------
1 | const { html } = require(`htm/preact`);
2 |
3 | const BaseWrapper = require(`./BaseWrapper`);
4 |
5 | module.exports = ({ data }) => html`
6 | <${BaseWrapper}>
7 |
11 | />
12 | `;
13 |
--------------------------------------------------------------------------------
/src/components/SectionHero.js:
--------------------------------------------------------------------------------
1 | const { html } = require(`htm/preact`);
2 |
3 | module.exports = ({ data }) => html`
4 |
5 |
10 |
11 |
12 | ${data.title}
13 |
14 | ${data.text}
15 |
16 |
17 | `;
18 |
--------------------------------------------------------------------------------
/src/components/SectionHero.scss:
--------------------------------------------------------------------------------
1 | .SectionHero {
2 | position: relative;
3 | }
4 |
5 | .SectionHero__image {
6 | max-width: 100%;
7 | height: auto;
8 | }
9 |
10 | .SectionHero__info {
11 | display: flex;
12 | position: absolute;
13 | top: 0;
14 | right: 0;
15 | bottom: 0;
16 | left: 0;
17 | flex-direction: column;
18 | justify-content: center;
19 | align-items: center;
20 | color: #fff;
21 | text-shadow: 0 0 0.75em rgba(0, 0, 0, 0.4);
22 | font-size: 2em;
23 | }
24 |
25 | .SectionHero__title {
26 | margin-bottom: 0.25em;
27 | font-size: 2em;
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/SectionMasonry.js:
--------------------------------------------------------------------------------
1 | const { html } = require(`htm/preact`);
2 |
3 | const BaseWrapper = require(`./BaseWrapper`);
4 | const LikeForm = require(`./LikeForm`);
5 |
6 | module.exports = ({ data }) => html`
7 | <${BaseWrapper}>
8 |
9 | ${data.images.map(({ alt, id, src }) => html`
10 |
11 |
17 | <${LikeForm} id=${id}/>
18 |
19 | `)}
20 |
21 | />
22 | `;
23 |
--------------------------------------------------------------------------------
/src/components/SectionMasonry.scss:
--------------------------------------------------------------------------------
1 | .SectionMasonry {
2 | margin-top: -1em;
3 | columns: 3 242px;
4 | column-gap: 1em;
5 | text-align: right;
6 | }
7 |
8 | .SectionMasonry__item {
9 | padding-top: 1em;
10 | page-break-inside: avoid;
11 | }
12 |
13 | .SectionMasonry__image {
14 | max-width: 100%;
15 | height: auto;
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/SectionTeaser.js:
--------------------------------------------------------------------------------
1 | const { html } = require(`htm/preact`);
2 |
3 | const BaseWrapper = require(`./BaseWrapper`);
4 |
5 | module.exports = ({ data }) => html`
6 | <${BaseWrapper}>
7 |
8 | ${data.map(teaser => html`
9 |
10 |
16 |
17 |
18 | ${teaser.title}
19 |
20 |
21 | ${teaser.text}
22 |
23 |
24 | read more »
25 |
26 |
27 |
28 | `)}
29 |
30 | />
31 | `;
32 |
--------------------------------------------------------------------------------
/src/components/SectionTeaser.scss:
--------------------------------------------------------------------------------
1 | .SectionTeaser {
2 | display: flex;
3 | margin-top: -1em;
4 | margin-left: -1em;
5 | flex-wrap: wrap;
6 | }
7 |
8 | .SectionTeaser__item {
9 | padding-top: 1em;
10 | padding-left: 1em;
11 | min-width: 14em;
12 | flex-basis: 0%;
13 | flex-grow: 1;
14 | flex-shrink: 1;
15 | }
16 |
17 | .SectionTeaser__image {
18 | max-width: 100%;
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/with-hydration.js:
--------------------------------------------------------------------------------
1 | const { html } = require(`htm/preact`);
2 |
3 | const isServer = require(`../utils/is-server`);
4 |
5 | let id = 0;
6 |
7 | module.exports = Component => (props) => {
8 | id += 1;
9 |
10 | const scriptSrc = `
11 | window.__STATE__.components[${id}]={name:${JSON.stringify(Component.name)},props:${JSON.stringify(props)}}
12 | `;
13 |
14 | return html`
15 | ${isServer && html``}
16 | <${Component} ...${props}/>
17 | `;
18 | };
19 |
--------------------------------------------------------------------------------
/src/page.11ty.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable class-methods-use-this */
2 | const { html } = require(`htm/preact`);
3 | const render = require(`preact-render-to-string`);
4 |
5 | const App = require(`./components/App`);
6 |
7 | module.exports = class Page {
8 | data() {
9 | return {
10 | title: `Setting up Eleventy with Preact and htm`,
11 | layout: `layout.njk`,
12 | pagination: {
13 | data: `pages`,
14 | size: 1,
15 | alias: `page`,
16 | addAllPagesToCollections: true,
17 | },
18 | permalink: ({ page }) => `/${page.slug}/index.html`,
19 | };
20 | }
21 |
22 | render(data) {
23 | return render(html`<${App} page=${data.page}/>`);
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/src/styles/generic/base.scss:
--------------------------------------------------------------------------------
1 | html {
2 | text-size-adjust: 100%;
3 | }
4 |
5 | body {
6 | font-weight: 300;
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
8 | }
9 |
--------------------------------------------------------------------------------
/src/styles/generic/box-sizing-reset.scss:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | }
4 |
5 | * {
6 | &,
7 | &::before,
8 | &::after {
9 | box-sizing: inherit;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | // Generic
2 | @import '~reset-css/sass/reset';
3 | @import './generic/**/*.scss';
4 |
5 | // Components
6 | @import '../components/**/*.scss';
7 |
8 | // Scopes
9 | @import './scopes/**/*.scss';
10 |
11 | // Utilities
12 | @import './utilities/**/*.scss';
13 |
--------------------------------------------------------------------------------
/src/styles/scopes/content.scss:
--------------------------------------------------------------------------------
1 | .s-content {
2 | line-height: 1.5;
3 |
4 | > :not(:first-child) {
5 | margin-top: 1em;
6 | }
7 |
8 | h1 {
9 | font-size: 2.027em;
10 | }
11 |
12 | h2 {
13 | font-size: 1.802em;
14 | }
15 |
16 | h3 {
17 | font-size: 1.602em;
18 | }
19 |
20 | h4 {
21 | font-size: 1.424em;
22 | }
23 |
24 | h5 {
25 | font-size: 1.266em;
26 | }
27 |
28 | strong {
29 | font-weight: 600;
30 | }
31 |
32 | li {
33 | list-style-type: none;
34 |
35 | &::before {
36 | margin-right: 0.5em;
37 | content: '●';
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/styles/utilities/text-align.scss:
--------------------------------------------------------------------------------
1 | .u-text-align-center {
2 | text-align: center;
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/is-server.js:
--------------------------------------------------------------------------------
1 | module.exports = typeof window === `undefined`;
2 |
--------------------------------------------------------------------------------
/src/utils/when-visible.js:
--------------------------------------------------------------------------------
1 | module.exports = function whenVisible($element, callback, options) {
2 | if (typeof IntersectionObserver === `undefined`) {
3 | callback();
4 | return;
5 | }
6 |
7 | const observer = new IntersectionObserver((entries) => {
8 | entries.forEach((entry) => {
9 | if (!entry.isIntersecting) return;
10 | observer.unobserve($element);
11 | callback();
12 | });
13 | }, options);
14 | observer.observe($element);
15 | };
16 |
--------------------------------------------------------------------------------