project first commit

This commit is contained in:
Yves Gatesoupe 2020-06-19 23:47:44 +02:00
commit 3cd8fef0f0
94 changed files with 12647 additions and 0 deletions

90
.eleventy.js Normal file
View File

@ -0,0 +1,90 @@
const rssPlugin = require('@11ty/eleventy-plugin-rss');
const syntaxHighlight = require('@11ty/eleventy-plugin-syntaxhighlight');
const fs = require('fs');
// Import filters
const dateFilter = require('./src/filters/date-filter.js');
const markdownFilter = require('./src/filters/markdown-filter.js');
const w3DateFilter = require('./src/filters/w3-date-filter.js');
// Import transforms
const htmlMinTransform = require('./src/transforms/html-min-transform.js');
const parseTransform = require('./src/transforms/parse-transform.js');
// Import data files
const site = require('./src/_data/site.json');
module.exports = function(config) {
// Filters
config.addFilter('dateFilter', dateFilter);
config.addFilter('markdownFilter', markdownFilter);
config.addFilter('w3DateFilter', w3DateFilter);
// Layout aliases
config.addLayoutAlias('home', 'layouts/home.njk');
// Transforms
config.addTransform('htmlmin', htmlMinTransform);
config.addTransform('parse', parseTransform);
// Passthrough copy
config.addPassthroughCopy('src/fonts');
config.addPassthroughCopy('src/images');
config.addPassthroughCopy('src/js');
config.addPassthroughCopy('src/admin/config.yml');
config.addPassthroughCopy('src/admin/previews.js');
config.addPassthroughCopy('node_modules/nunjucks/browser/nunjucks-slim.js');
config.addPassthroughCopy('src/robots.txt');
const now = new Date();
// Custom collections
const livePosts = post => post.date <= now && !post.data.draft;
config.addCollection('posts', collection => {
return [
...collection.getFilteredByGlob('./src/posts/*.md').filter(livePosts)
].reverse();
});
config.addCollection('postFeed', collection => {
return [...collection.getFilteredByGlob('./src/posts/*.md').filter(livePosts)]
.reverse()
.slice(0, site.maxPostsPerPage);
});
config.addCollection('newsFeed', collection => {
return [...collection.getFilteredByGlob('./src/posts/*.md').filter(livePosts)]
.reverse()
.slice(0, site.maxNewsPerPage);
});
// Plugins
config.addPlugin(rssPlugin);
config.addPlugin(syntaxHighlight);
// 404
config.setBrowserSyncConfig({
callbacks: {
ready: function(err, browserSync) {
const content_404 = fs.readFileSync('dist/404.html');
browserSync.addMiddleware('*', (req, res) => {
// Provides the 404 content without redirect.
res.write(content_404);
res.end();
});
}
}
});
// Watch sass
// config.addWatchTarget("./src/scss/");
return {
dir: {
input: 'src',
output: 'dist'
},
passthroughFileCopy: true,
pathPrefix: '/preprod' //TODO remove when prod
};
};

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
*.log
npm-debug.*
*.scssc
*.log
*.swp
.DS_Store
.sass-cache
node_modules
dist/*
deploy-prod.js
deploy-preprod.js
# Specifics
# Hide design tokens
src/scss/_tokens.scss
# Hide compiled CSS
src/_includes/assets/*

6
.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"printWidth": 90,
"tabWidth": 2,
"singleQuote": true,
"bracketSpacing": false
}

18
.stylelintrc.json Normal file
View File

@ -0,0 +1,18 @@
{
"extends": "stylelint-config-sass-guidelines",
"rules": {
"order/properties-alphabetical-order": null,
"property-no-vendor-prefix": null,
"selector-class-pattern": [
"^[a-z0-9\\-_]+$",
{
"message":
"Selector should be written in lowercase (selector-class-pattern)"
}
],
"max-nesting-depth": 4,
"number-leading-zero": "never",
"selector-no-qualifying-type": null,
"selector-max-compound-selectors": 4
}
}

21
LICENSE.txt Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 andy-bell.design and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

30
deploy.js Normal file
View File

@ -0,0 +1,30 @@
var FtpDeploy = require("ftp-deploy");
var ftpDeploy = new FtpDeploy();
var config = {
user: "user",
// Password optional, prompted if none given
password: "password",
host: "ftp.someserver.com",
port: 21,
localRoot: __dirname + "/local-folder",
remoteRoot: "/public_html/remote-folder/",
// include: ["*", "**/*"], // this would upload everything except dot files
include: ["*.php", "dist/*", ".*"],
// e.g. exclude sourcemaps, and ALL files in node_modules (including dot files)
exclude: ["dist/**/*.map", "node_modules/**", "node_modules/**/.*", ".git/**"],
// delete ALL existing files at destination before uploading, if true
deleteRemote: false,
// Passive mode is forced (EPSV command is not sent)
forcePasv: true
};
// use with promises
ftpDeploy
.deploy(config)
.then(res => console.log("finished:", res))
.catch(err => console.log(err));
ftpDeploy.on("uploading", function(data) {
console.log(data.filename); // partial path with filename being uploaded
});

9665
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

56
package.json Executable file
View File

@ -0,0 +1,56 @@
{
"name": "astrolabe-website",
"version": "0.1.0",
"description": "Site web de la coopérative Astrolabe CAE",
"main": "index.js",
"dependencies": {
"@11ty/eleventy": "^0.10.0",
"@11ty/eleventy-plugin-rss": "^1.0.7",
"@11ty/eleventy-plugin-syntaxhighlight": "^2.0.3",
"@tbranyen/jsdom": "^13.0.0",
"concurrently": "^4.1.2",
"html-minifier": "^4.0.0",
"image-size": "^0.8.3",
"json-to-scss": "^1.5.0",
"sass": "^1.26.3",
"semver": "^6.3.0",
"slugify": "^1.4.0",
"stalfos": "github:hankchizljaw/stalfos#c8971d22726326cfc04089b2da4d51eeb1ebb0eb"
},
"devDependencies": {
"@erquhart/rollup-plugin-node-builtins": "^2.1.5",
"bl": "^3.0.0",
"chokidar-cli": "^2.1.0",
"cross-env": "^5.2.1",
"ftp-deploy": "^2.3.7",
"make-dir-cli": "^2.0.0",
"prettier": "^1.19.1",
"rollup": "^1.32.1",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^5.2.0",
"stylelint": "^13.6.1",
"stylelint-config-sass-guidelines": "^7.0.0"
},
"scripts": {
"sass:tokens": "npx json-to-scss src/_data/tokens.json src/scss/_tokens.scss",
"sass:lint": "npx stylelint src/scss/**/*.scss",
"sass:process": "npm run sass:tokens && npm run sass:lint && sass src/scss/global.scss src/_includes/assets/css/global.css --style=compressed",
"start": "concurrently \"npm run sass:process -- --watch\" \"npm run serve\"",
"serve": "cross-env ELEVENTY_ENV=development npx eleventy --serve",
"preprod": "cross-env ELEVENTY_ENV=preprod npm run sass:process && npx eleventy",
"deploy-preprod": "npm run preprod && node deploy-preprod",
"prod": "cross-env ELEVENTY_ENV=prod npm run sass:process && npx eleventy"
},
"repository": {
"type": "git",
"url": "git+https://git.ouvaton.coop/astrolabe/SiteWebAstrolabe.git"
},
"keywords": [],
"author": "",
"license": "MIT",
"bugs": {
"url": "https://git.ouvaton.coop/astrolabe/SiteWebAstrolabe/issues"
},
"homepage": "https://git.ouvaton.coop/astrolabe/SiteWebAstrolabe"
}

17
src/404.md Normal file
View File

@ -0,0 +1,17 @@
---
title: '404 - not found'
layout: layouts/page.njk
permalink: 404.html
eleventyExcludeFromCollections: true
---
Were sorry, but that content cant be found. Please go [back to home](/).
{% comment %}
Read more: https://www.11ty.io/docs/quicktips/not-found/
This will work for both GitHub pages and Netlify:
- https://help.github.com/articles/creating-a-custom-404-page-for-your-github-pages-site/
- https://www.netlify.com/docs/redirects/#custom-404
{% endcomment %}

9
src/_data/global.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
random() {
const segment = () => {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return `${segment()}-${segment()}-${segment()}`;
},
now: Date.now()
};

10
src/_data/helpers.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
getNextHeadingLevel(currentLevel) {
return parseInt(currentLevel, 10) + 1;
},
getReadingTime(text) {
const wordsPerMinute = 200;
const numberOfWords = text.split(/\s/g).length;
return Math.ceil(numberOfWords / wordsPerMinute);
}
};

29
src/_data/navigation.json Normal file
View File

@ -0,0 +1,29 @@
{
"items": [
{
"text": "Comprendre la CAE",
"url": "/comprendre",
"external": false
},
{
"text": "Nous rejoindre",
"url": "/rejoindre",
"external": false
},
{
"text": "L'équipe",
"url": "/equipe",
"external": false
},
{
"text": "Actualité",
"url": "/actualite",
"external": false
},
{
"text": "Nous contacter",
"url": "/contact",
"external": false
}
]
}

23
src/_data/site.json Normal file
View File

@ -0,0 +1,23 @@
{
"showThemeCredit": true,
"name": "Astrolabe CAE",
"shortDesc": "Site web de la coopérative Astrolabe CAE",
"url": "https://astrolabe.coop/preprod",
"authorEmail": "contacte@astrolabe.coop",
"authorHandle": "@AstrolabeCae",
"authorName": "Astrolabe CAE",
"authorAddress": "34 La Ville Allée",
"authorCity": "35630 HEDE BAZOUGES",
"authorSocial": {
"mastodon": "https://framapiaf.org/@AstrolabeCAE",
"twitter": "https://twitter.com/AstrolabeCae",
"linkedin": "https://www.linkedin.com/company/astrolabe-cae/"
},
"designerName": "Yves Gatesoupe",
"designerHandle": "https://twitter.com/YGdsgn",
"illustrators": "Igé Maulana, Leopold Merleau",
"enableThirdPartyComments": false,
"maxPostsPerPage": 5,
"maxNewsPerPage": 4,
"faviconPath": "/images/favicon.png"
}

28
src/_data/styleguide.js Normal file
View File

@ -0,0 +1,28 @@
const tokens = require('./tokens.json');
module.exports = {
colors() {
let response = [];
Object.keys(tokens.colors).forEach(key => {
response.push({
value: tokens.colors[key],
key
});
});
return response;
},
sizes() {
let response = [];
Object.keys(tokens['size-scale']).forEach(key => {
response.push({
value: tokens['size-scale'][key],
key
});
});
return response;
}
};

28
src/_data/tokens.json Normal file
View File

@ -0,0 +1,28 @@
{
"size-scale": {
"base": "1rem",
"300": "0.8rem",
"500": "1.25rem",
"600": "1.56rem",
"700": "1.95rem",
"800": "2.44rem",
"900": "3.05rem",
"max": "4rem"
},
"colors": {
"primary": "#d6f253",
"primary-shade": "#102538",
"primary-glare": "#22547c",
"secondary": "#282156",
"highlight": "#d6f253",
"light-gray": "#f1f0f7",
"light": "#fff",
"mid": "#ccc",
"dark": "#111",
"slate": "#404040"
},
"fonts": {
"base": "\"'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'\"",
"brand": "\"'Varela', sans-serif\""
}
}

View File

@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
focusable="false"
aria-hidden="true"
fill="currentColor"
>
<path
d="M9.707 18.707l6-6a.999.999 0 0 0 0-1.414l-6-6a.999.999 0 1 0-1.414 1.414L13.586 12l-5.293 5.293a.999.999 0 1 0 1.414 1.414z"
/>
</svg>

After

Width:  |  Height:  |  Size: 310 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -0,0 +1,16 @@
{% extends 'layouts/base.njk' %}
{# Intro content #}
{% set introHeading = title %}
{% set introSummary %}{{ content | safe }}{% endset %}
{# Post list content #}
{% set postListHeading = 'All posts' %}
{% set postListItems = collections.posts %}
{% block content %}
<main id="main-content" tabindex="-1">
{% include "partials/components/intro.njk" %}
{% include "partials/components/post-list.njk" %}
</main>
{% endblock %}

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ site.url }}/feed.xml" />
<link rel="icon" href="{{ site.faviconPath }}" type="image/png" />
{% include "partials/global/meta-info.njk" %}
<script>document.documentElement.classList.remove('no-js');</script>
<style>{% include "assets/css/global.css" %}</style>
{% block head %}
{% endblock %}
</head>
<body>
{% include "partials/global/site-head.njk" %}
{% block content %}
{% endblock content %}
{% include "partials/global/site-foot.njk" %}
{% block foot %}
{% endblock %}
{# <script type="module" src="/js/components/theme-toggle.js" async defer></script> #}
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js');
});
}
</script>
<script src="https://identity.netlify.com/v1/netlify-identity-widget.js" defer></script>
</body>
</html>

View File

@ -0,0 +1,18 @@
{% extends 'layouts/base.njk' %}
{# Intro content #}
{% set introHeading = title %}
{% block content %}
<main id="main-content" tabindex="-1">
<article class="[ post ] [ h-entry ]">
{% include "partials/components/intro.njk" %}
<div class="[ post__body ] [ inner-wrapper ] [ leading-loose pad-top-900 pad-bottom-900 text-500 ] [ sf-flow ] [ e-content ]">
{{ content | safe }}
{% include "partials/components/contact-form.njk" %}
</div>
</article>
</main>
{% endblock %}
{{ content | safe }}

View File

@ -0,0 +1,19 @@
{% extends 'layouts/base.njk' %}
{# Post list content #}
{% set newsListHeading = newsHeading %}
{% set newsListItems = collections.newsFeed %}
{# Presentation content #}
{% set presentation %}
{{ content | safe }}
{% endset %}
{% block content %}
<main id="main-content" tabindex="-1">
{% include "partials/components/intro.njk" %}
{% include "partials/components/news-list.njk" %}
{% include "partials/components/post-list.njk" %}
{% include "partials/components/presentation.njk" %}
</main>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends 'layouts/base.njk' %}
{# Intro content #}
{% set introHeading = title %}
{% block content %}
<main id="main-content" tabindex="-1">
<article class="[ post ] [ h-entry ]">
{% include "partials/components/intro.njk" %}
<div class="[ post__body ] [ inner-wrapper ] [ leading-loose pad-top-900 pad-bottom-900 text-500 ] [ sf-flow ] [ e-content ]">
{{ content | safe }}
</div>
</article>
</main>
{% endblock %}
{{ content | safe }}

View File

@ -0,0 +1,47 @@
{% extends 'layouts/base.njk' %}
{# Intro content #}
{% set introHeading = title %}
{% set introSummary %}
<p class="[ intro__meta ] [ text-500 leading-tight ]">
{% if date %}
<time datetime="{{ date | w3DateFilter }}" class="dt-published">{{ date | dateFilter }}</time>
{% endif %}
<span>— {{ helpers.getReadingTime(content) }} minute read</span>
</p>
{% endset %}
{% block content %}
<main id="main-content" tabindex="-1">
<article class="[ post ] [ h-entry ]">
{% include "partials/components/intro.njk" %}
<div class="[ post__body ] [ inner-wrapper ] [ leading-loose pad-top-900 {{ 'pad-bottom-900' if not site.enableThirdPartyComments }} text-500 ] [ sf-flow ] [ e-content ]">
{{ content | safe }}
</div>
{% if site.enableThirdPartyComments %}
<hr />
<aside class="[ post__body ] [ inner-wrapper ] [ pad-bottom-900 text-500 ]">
{% include "partials/global/third-party-comments.njk" %}
</aside>
{% endif %}
{% if tags %}
<footer class="[ post__footer ] [ pad-top-500 pad-bottom-500 ]">
<div class="inner-wrapper">
<div class="[ nav ] [ box-flex align-center ]">
<h2 class="font-base text-600 weight-mid">Filed under</h2>
<ul class="[ nav__list ] [ box-flex align-center pad-left-400 ] [ p-category ]">
{% for item in tags %}
<li class="nav__item">
<a href="/tags/{{ item }}">{{ item }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</footer>
{% endif %}
</article>
</main>
{% endblock %}
{{ content | safe }}

View File

@ -0,0 +1,181 @@
{# ===================
Forms
=================== #}
{% macro label( text, name ) %}
<label class="question__label" for="field-{{ name }}">{{ text }}</label>
{% endmacro %}
{% macro field( type, name, data ) %}
<br>
<input class="question__field"
type="{{ type }}"
name="{{ name }}"
id="field-{{ name }}"
{% if data.required %}required aria-required="true"{% endif %}
{% if data.placeholder %}placeholder="{{ data.placeholder }}"{% endif %}
{% if data.pattern %}pattern="{{ data.pattern }}"{% endif %}
{% if data.description %}aria-describedby="description-{{ name }}"{% endif %}
{% if data.autocomplete %}autocomplete="{{ data.autocomplete }}"{% endif %}
{% if data.autocorrect %}autocorrect="{{ data.autocorrect }}"{% endif %}
{% if data.spellcheck %}spellcheck="{{ data.spellcheck }}"{% endif %}
{% if data.autocapitalize %}autocapitalize="{{ data.autocapitalize }}"{% endif %}
>
{% if data.description %}
<br>
{{ description( name, data.description ) }}
{% endif %}
{% endmacro %}
{% macro confirm( text, name, data ) %}
<label for="field-{{ name }}" class="question--confirm">
<input class="question__field question__field--confirm"
type="checkbox"
name="{{ name }}"
id="field-{{ name }}"
value="1"
{% if data.required %}required aria-required="true"{% endif %}
{% if data.description %}aria-describedby="description-{{ name }}"{% endif %}
>
{{ text }}
</label>
{% if data.description %}
<br>
{{ description( name, data.description ) }}
{% endif %}
{% endmacro %}
{% macro select( name, options, data ) %}
<br>
<select id="field-{{ name }}"
name="{{ name }}"
{% if data.required %}required aria-required="true"{% endif %}
{% if data.multiple %}multiple{% endif %}
{% if data.description %}aria-describedby="description-{{ name }}"{% endif %}
>
{% for opt in data.options_before %}
{{ option( opt ) }}
{% endfor %}
{% for opt in options %}
{{ option( opt ) }}
{% endfor %}
{% for opt in data.options_after %}
{{ option( opt ) }}
{% endfor %}
</select>
{% if data.description %}
<br>
{{ description( name, data.description ) }}
{% endif %}
{% endmacro %}
{% macro option( data ) %}
{% if data.value %}
<option value="{{ data.value }}">{{ data.label }}</option>
{% else %}
<option>{{ data }}</option>
{% endif %}
{% endmacro %}
{% macro textarea( name, data ) %}
<br>
<textarea id="field-{{ name }}"
name="{{ name }}"
{% if data.rows %}rows="{{ data.rows }}"{% else %}rows="5"{% endif %}
cols="100"
{% if data.required %}required aria-required="true"{% endif %}
{% if data.autocorrect %}autocorrect="{{ data.autocorrect }}"{% endif %}
{% if data.spellcheck %}spellcheck="{{ data.spellcheck }}"{% endif %}
{% if data.autocapitalize %}autocapitalize="{{ data.autocapitalize }}"{% endif %}
{% if data.description %}aria-describedby="description-{{ name }}"{% endif %}
></textarea>
{% if data.description %}
{{ description( name, data.description ) }}
{% endif %}
{% endmacro %}
{% macro radios( label, name, options, data ) %}
<fieldset>
<legend
{% if data.description %}aria-describedby="description-{{ name }}"{% endif %}
>{{ label }}</legend>
<ul class="field-list__field-group__list">
{% for option in options %}
<li>
{% if option.value %}
<label for="field-{{ name }}-{{ option.value }}">
<input type="radio"
name="{{ name }}"
id="field-{{ name }}-{{ option.value }}"
value="{{ option.value }}"
{% if option.note %}aria-describedby="description-{{ name }}-{{ option.value }}"{% endif %}
>{{ option.label }}</label>
{% else %}
<label for="field-{{ name }}-{{ option }}">
<input type="radio"
name="{{ name }}"
id="field-{{ name }}-{{ option }}"
value="{{ option }}"
>{{ option }}</label>
{% endif %}
{% if option.note %}
<br>
{{ description( ( name + '-' + option.value ), option.note ) }}
{% endif %}
</li>
{% endfor %}
</ul>
{% if data.description %}
{{ description( name, data.description ) }}
{% endif %}
</fieldset>
{% endmacro %}
{% macro checkboxes( label, name, options, data ) %}
<fieldset>
<legend
{% if data.description %}aria-describedby="description-{{ name }}"{% endif %}
>{{ label }}</legend>
<ul class="field-list__field-group__list">
{% for option in options %}
<li>
{% if option.value %}
<label for="field-{{ name }}-{{ option.value }}">
<input type="checkbox"
name="{{ name }}[]"
id="field-{{ name }}-{{ option.value }}"
value="{{ option.value }}"
{% if option.note %}aria-describedby="description-{{ name }}-{{ option.value }}"{% endif %}
>{{ option.label }}</label>
{% else %}
<label for="field-{{ name }}-{{ option }}">
<input type="checkbox"
name="{{ name }}[]"
id="field-{{ name }}-{{ option }}"
value="{{ option }}"
>{{ option }}</label>
{% endif %}
{% if option.note %}
<br>
{{ description( ( name + '-' + option.value ), option.note ) }}
{% endif %}
</li>
{% endfor %}
</ul>
{% if data.description %}
{{ description( name, data.description ) }}
{% endif %}
</fieldset>
{% endmacro %}
{% macro description( id, html ) %}
<em class="[ field-list__field-group__description ]" id="description-{{ id }}">{{ html | safe }}</em>
{% endmacro %}
{% macro hidden_field( name, value ) %}
<input type="hidden" name="{{ name }}" id="field-{{ name }}" value="{{ value }}">
{% endmacro %}
{% macro button( text ) %}
<button type="submit" class="[ button ] [ font-base text-base weight-bold ]">{{ text }}</button>
{% endmacro %}

View File

@ -0,0 +1,4 @@
{# ========================
Site-specific Macros
======================== #}

View File

@ -0,0 +1,22 @@
{% from "macros/form.njk" import label, field, textarea, button %}
<form name="contact" method="POST" data-netlify="true" action="/thank-you" netlify-honeypot="bot-field">
<ol class="[ field-list ]">
<li class="[ field-list__field-group ]">
{{ label("Whats your name?", "name") }}
{{ field( "text", "name", { required: true, placeholder: "Katherine Johnson", autocomplete: "name", autocorrect: "off", autocapitalize: "off" } ) }}
</li>
<li class="[ field-list__field-group ]">
{{ label("Whats your email address?", "email") }}
{{ field( "email", "email", { required: true, placeholder: "katherine@johnson.tld", autocomplete: "email" } ) }}
</li>
<li class="[ field-list__field-group ]">
{{ label("Whats on your mind?", "message") }}
{{ textarea( "message", { required: true, autocapitalize: "sentences", spellcheck: "true" } ) }}
</li>
<li hidden>
<label>Dont fill this out if you're human: <input name="bot-field" /></label>
</li>
</ol>
{{ button("Send message") }}
</form>

View File

@ -0,0 +1,10 @@
<header class="[ intro ]">
<div class="[ inner-wrapper ]">
<h1 class="[ intro__heading ]">{{ brandHeading }}</h1>
{% if introSummary %}
<div class="[ intro__summary ] [ sf-flow ] [ leading-mid measure-short ]">{{ introSummary | safe }}</div>
{% endif %}
<a role="button" href="/README" class="btn btn-secondary">Une CAE c'est quoi ?</button>
<a role="button" href="/README" class="btn btn-primary">Nous rejoindre</button>
</div>
</header>

View File

@ -0,0 +1,22 @@
{% if navigation.items %}
<nav class="nav" aria-label="{{ariaLabel}}">
<ul class="[ nav__list ] [ box-flex align-center md:space-before ]">
{% for item in navigation.items %}
{% set relAttribute = '' %}
{% set currentAttribute = '' %}
{% if item.rel %}
{% set relAttribute = ' rel="' + item.rel + '"' %}
{% endif %}
{% if page.url == item.url %}
{% set currentAttribute = ' aria-current="page"' %}
{% endif %}
<li class="nav__item">
<a href="{{ item.url | url }}"{{ relAttribute | safe }}{{ currentAttribute | safe }}>{{ item.text }}</a>
</li>
{% endfor %}
</ul>
</nav>
{% endif %}

View File

@ -0,0 +1,25 @@
{% if newsListItems.length %}
<section class="[ news-list ]">
<svg aria-hidden="true" viewBox="0 0 1440 349" width="auto" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="#282156" d="M1440 154H0v195h1440z"/><path d="M1440 54.078l-48 12.41c-48 12.41-144 37.23-240 45.53-96 7.989-192 .232-288-29.01C768 54.079 672 4.438 576 .327c-96-4.421-192 37.463-288 45.452-96 8.3-192-16.52-240-28.931L0 4.437V203h1440V54.078z" fill="#282156"/></svg>
<div class="[ wrapper ]">
<div class="news-list__inner">
<h2 class="[ news-list__heading ]">{{ newsListHeading }}</h2>
<ol class="[ news-list__items ]" reversed>
{% for item in newsListItems %}
{% if item.date.getTime() <= global.now %}
<li class="news-list__item">
<h3 class="news-list__item-heading">
<a href="{{ item.url | url }}" class="news-list__link" rel="bookmark">{{ item.data.title }}</a>
</h3>
<p class="news-list__item-date">
<time datetime="{{ item.date | w3DateFilter }}">{{ item.date | dateFilter }}</time>
</p>
</li>
{% endif %}
{% endfor %}
</ol>
<a href="/" class="news-list__see-all">Voir tout</a>
</div>
</div>
</section>
{% endif %}

View File

@ -0,0 +1,23 @@
{% set paginationLinkTokens = 'leading-tight text-500 weight-mid box-inline-flex align-center pad-bottom-300' %}
{% if paginationNextUrl or paginationPrevUrl %}
<hr />
<div class="inner-wrapper">
<footer class="[ pagination ] [ pad-bottom-900 ]">
<nav class="[ pagination__nav ] [ box-flex space-between align-center ]">
{% if paginationPrevUrl %}
<a href="{{ paginationPrevUrl }}" class="{{ paginationLinkTokens }}" data-direction="backwards">
<span>{{ paginationPrevText if paginationPrevText else 'Previous' }}</span>
{% include "icons/arrow.svg" %}
</a>
{% endif %}
{% if paginationNextUrl %}
<a href="{{ paginationNextUrl }}" class="{{ paginationLinkTokens }}" data-direction="forwards">
<span>{{ paginationNextText if paginationNextText else 'Next' }}</span>
{% include "icons/arrow.svg" %}
</a>
{% endif %}
</nav>
</footer>
</div>
{% endif %}

View File

@ -0,0 +1,21 @@
{% if postListItems.length %}
<section class="[ post-list ] [ pad-top-700 gap-bottom-900 ]">
<div class="[ inner-wrapper ] [ sf-flow ]">
<h2 class="[ post-list__heading ]">{{ postListHeading }}</h2>
<ol class="[ post-list__items ] [ sf-flow ] [ pad-top-300 ]" reversed>
{% for item in postListItems %}
{% if item.date.getTime() <= global.now %}
<li class="post-list__item">
<h3 class="font-base leading-tight text-600 weight-mid">
<a href="{{ item.url }}" class="post-list__link" rel="bookmark">{{ item.data.title }}</a>
</h3>
<p class="text-500 gap-top-300 weight-mid">
<time datetime="{{ item.date | w3DateFilter }}">{{ item.date | dateFilter }}</time>
</p>
</li>
{% endif %}
{% endfor %}
</ol>
</div>
</section>
{% endif %}

View File

@ -0,0 +1,5 @@
<section class="[ presentation ]">
<div class="[ wrapper ]">
{{ presentation | safe }}
</div>
</section>

View File

@ -0,0 +1,41 @@
{% set pageTitle = site.name + ' - ' + title %}
{% set pageDesc = '' %}
{% set siteTitle = site.name %}
{% set currentUrl = site.url + page.url %}
{% if metaTitle %}
{% set pageTitle = metaTitle %}
{% endif %}
{% if metaDesc %}
{% set pageDesc = metaDesc %}
{% endif %}
<title>{{ pageTitle }}</title>
<link rel="canonical" href="{{ currentUrl }}" />
<meta property="og:site_name" content="{{ siteTitle }}" />
<meta property="og:title" content="{{ pageTitle }}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{ currentUrl }}" />
{% if site.authorHandle %}
<meta name="twitter:creator" content="@{{ site.authorHandle | replace('@', '') }}" />
{% endif %}
{% if metaDesc %}
<meta name="description" content="{{ metaDesc }}" />
<meta name="twitter:description" content="{{ metaDesc }}" />
<meta property="og:description" content="{{ metaDesc }}" />
{% endif %}
{% if socialImage %}
<meta property="og:image" content="{{ socialImage }}" />
<meta name="twitter:image" content="{{ socialImage }}" />
<meta property="og:image:alt" content="Page image for {{ site.name }}" />
<meta name="twitter:image:alt" content="Page image for {{ site.name }}" />
{% endif %}
{% if site.paymentPointer %}
<meta name="monetization" content="{{ site.paymentPointer }}" />
{% endif %}

View File

@ -0,0 +1,92 @@
const CACHE_KEYS = {
PRE_CACHE: `precache-${VERSION}`,
RUNTIME: `runtime-${VERSION}`
};
// URLS that we dont want to end up in the cache
const EXCLUDED_URLS = [
'admin',
'.netlify',
'https://identity.netlify.com/v1/netlify-identity-widget.js',
'https://unpkg.com/netlify-cms@^2.9.3/dist/netlify-cms.js',
'/contact',
'/thank-you'
];
// URLS that we want to be cached when the worker is installed
const PRE_CACHE_URLS = ['/', '/fonts/VarelaRound-Regular.ttf', '/fonts/OpenSans-Bold.ttf', '/fonts/OpenSans-SemiBold.ttf', '/fonts/OpenSans-Regular.ttf'];
// You might want to bypass a certain host
const IGNORED_HOSTS = ['localhost', 'unpkg.com', ];
/**
* Takes an array of strings and puts them in a named cache store
*
* @param {String} cacheName
* @param {Array} items=[]
*/
const addItemsToCache = function(cacheName, items = []) {
caches.open(cacheName).then(cache => cache.addAll(items));
};
self.addEventListener('install', evt => {
self.skipWaiting();
addItemsToCache(CACHE_KEYS.PRE_CACHE, PRE_CACHE_URLS);
});
self.addEventListener('activate', evt => {
// Look for any old caches that don't match our set and clear them out
evt.waitUntil(
caches
.keys()
.then(cacheNames => {
return cacheNames.filter(item => !Object.values(CACHE_KEYS).includes(item));
})
.then(itemsToDelete => {
return Promise.all(
itemsToDelete.map(item => {
return caches.delete(item);
})
);
})
.then(() => self.clients.claim())
);
});
self.addEventListener('fetch', evt => {
const {hostname} = new URL(evt.request.url);
// Check we don't want to ignore this host
if (IGNORED_HOSTS.indexOf(hostname) >= 0) {
return;
}
// Check we don't want to ignore this URL
if (EXCLUDED_URLS.some(page => evt.request.url.indexOf(page) > -1)) {
return;
}
evt.respondWith(
caches.match(evt.request).then(cachedResponse => {
// Item found in cache so return
if (cachedResponse) {
return cachedResponse;
}
// Nothing found so load up the request from the network
return caches.open(CACHE_KEYS.RUNTIME).then(cache => {
return fetch(evt.request)
.then(response => {
// Put the new response in cache and return it
return cache.put(evt.request, response.clone()).then(() => {
return response;
});
})
.catch(ex => {
return;
});
});
})
);
});

View File

@ -0,0 +1,24 @@
<footer role="contentinfo" class="[ site-foot ]">
<div class="wrapper">
<div class="[ site-foot__inner ]">
<div class="">
<h3>Mentions légales</h3>
<p>Protection des données</p>
</div>
<div class="">
<h3>Crédits</h3>
<p>Design et intégration : <a href="{{site.designerHandle}}" target="_blank">{{site.designerName}}</a></p>
<p>Illustrations : {{site.illustrators}}
</div>
<div class="">
<h3>Contact</h3>
<p>{{site.authorName}}</p>
<p>{{site.authorAddress}}<br>{{site.authorCity}}</p>
<ul>
<a href="{{site.authorSocial.mastodon}}" class="social">Mastodon</a>
<a href="{{site.authorSocial.twitter}}" class="social">Twitter</a>
<a href="{{site.authorSocial.linkedin}}" class="social">Linkedin</a>
</ul>
</div>
</div>
</footer>

View File

@ -0,0 +1,13 @@
<a class="skip-link" href="#main-content">Skip to content</a>
<header role="banner" class="[ site-head ]">
<div class="wrapper">
<div class="[ site-head__inner ] [ md:box-flex space-between align-center ]">
<a href="{{ url }}" class="[ site-head__site-name ] [ leading-tight ]">
<span class="visually-hidden">{{ site.name }} - Home</span>
{% include "icons/astrolabe_logo.svg" %}
</a>
{% set ariaLabel = 'navigation' %}
{% include "partials/components/nav.njk" %}
</div>
</div>
</header>

View File

@ -0,0 +1,7 @@
<!-- ADD YOUR THIRD PARTY COMMENTS CODE HERE -->
<!-- COMMENTO EXAMPLE
<div id="commento"></div>
<script defer
src="https://cdn.commento.io/js/commento.js">
</script>
-->

4
src/archive.md Normal file
View File

@ -0,0 +1,4 @@
---
title: 'Posts Archive'
layout: 'layouts/archive.njk'
---

30
src/feed.njk Normal file
View File

@ -0,0 +1,30 @@
---
permalink: '/feed.xml'
---
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{{ site.name }}</title>
<subtitle></subtitle>
<link href="{{ site.url }}{{ permalink }}" rel="self"/>
<link href="{{ site.url }}/"/>
{% if collections.posts|length %}
<updated>{{ collections.posts | rssLastUpdatedDate }}</updated>
{% endif %}
<id>{{ site.url }}</id>
<author>
<name>{{ site.authorName }}</name>
<email>{{ site.authorEmail }}</email>
</author>
{% for post in collections.posts %}
{% set absolutePostUrl %}{{ site.url }}{{ post.url | url }}{% endset %}
<entry>
<title>{{ post.data.title }}</title>
<link href="{{ absolutePostUrl }}"/>
<updated>{{ post.date | rssDate }}</updated>
<id>{{ absolutePostUrl }}</id>
<content type="html"><![CDATA[
{{ post.templateContent | safe }}
]]></content>
</entry>
{% endfor %}
</feed>

View File

@ -0,0 +1,17 @@
// Stolen from https://stackoverflow.com/a/31615643
const appendSuffix = n => {
var s = ['th', 'st', 'nd', 'rd'],
v = n % 100;
return n + (s[(v - 20) % 10] || s[v] || s[0]);
};
module.exports = function dateFilter(value) {
const dateObject = new Date(value);
// const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const months = ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juill.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'];
const dayWithSuffix = appendSuffix(dateObject.getDate());
// return `${dayWithSuffix} ${months[dateObject.getMonth()]} ${dateObject.getFullYear()}`;
return `${dateObject.getDate()} ${months[dateObject.getMonth()]} ${dateObject.getFullYear()}`;
};

View File

@ -0,0 +1,9 @@
const markdownIt = require('markdown-it')({
html: true,
breaks: true,
linkify: true
});
module.exports = function markdown(value) {
return markdownIt.render(value);
};

View File

@ -0,0 +1,5 @@
module.exports = function w3cDate(value) {
const dateObject = new Date(value);
return dateObject.toISOString();
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/images/demo-image-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
src/images/demo-image-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

BIN
src/images/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

1
src/images/gitlab.svg Normal file
View File

@ -0,0 +1 @@
<svg width="110" height="101" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M109.052 56.536l-5.704-17.493a2.415 2.415 0 00-.138-.47L91.766 3.412a4.54 4.54 0 00-1.669-2.251 4.555 4.555 0 00-2.674-.846 4.478 4.478 0 00-2.661.856 4.464 4.464 0 00-1.632 2.266L72.222 36.942H37.795L26.871 3.438a4.465 4.465 0 00-1.624-2.26 4.478 4.478 0 00-2.65-.862h-.025a4.555 4.555 0 00-4.331 3.125L6.788 38.643c0 .032-.026.057-.035.09L.946 56.538a6.485 6.485 0 002.364 7.272l50.17 36.386a2.57 2.57 0 003.032-.016l50.179-36.37a6.493 6.493 0 002.361-7.275zM34.061 42.085l13.984 42.96-33.57-42.96H34.06zm27.911 42.97l13.41-41.187.578-1.783h19.602L65.19 80.92l-3.218 4.133zM87.467 6.735l9.827 30.206h-19.67l9.844-30.206zm-16.91 35.33l-9.743 29.927L55 89.816l-15.534-47.75h31.09zM22.55 6.736l9.846 30.206H12.739l9.81-30.206zM6.33 59.668a1.38 1.38 0 01-.501-1.547l4.311-13.225 31.624 40.466L6.329 59.668zm97.345 0L68.238 85.352l.118-.154 31.505-40.302 4.312 13.219a1.383 1.383 0 01-.498 1.55" fill="#111"/></svg>

After

Width:  |  Height:  |  Size: 998 B

BIN
src/images/social-share.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

40
src/index.md Normal file
View File

@ -0,0 +1,40 @@
---
layout: home
title: Accueil
brandHeading: La Coopérative d'Activité et d'Emploi spécialisée en informatique !
newsHeading: Actualité & Évenements
metaDesc: 'Hylia is a lightweight Eleventy starter kit to help you to create your own blog or personal website.'
---
<article>
<div class="content">
## Qui sommes-nous ?
Astrolabe CAE est une scop spécialisée dans la prestation de **services** autour des métiers du **numérique**.
Notre objectif est de favoriser l**autonomie** et l**émancipation** de nos membres sur un modèle déconomie sociale et **solidaire**.
</div>
![logo Gitlab](/images/gitlab.svg)
</article>
<article>
<div class="content">
## Communs numérique
Chez Astrolabe nous aimons et faisons la promotion du **logiciel libre**. Nos sommes membres d[Alliance Libre](http://www.alliance-libre.org/) et nous mettons nos documents et projets internes à disposition sur notre [gitlab]().
</div>
![logo Gitlab](/images/gitlab.svg "test")
</article>
<article>
<div class="content">
## Des profils variés
Nos coopérateurs possèdent des compétences propres allant de développement linux embarqué au web design et créent ainsi la **pluralité** de nos prestations.
<br><br>
Nous sommes également **distributeurs** de la solution logicielle [Naega](https://www.crealead.com/naega#bootstrap-fieldgroup-nav-item--prsentation).
</div>
![logo Gitlab](/images/gitlab.svg)
</article>

View File

@ -0,0 +1,98 @@
// For syntax highlighting only
const html = String.raw;
class ThemeToggle extends HTMLElement {
constructor() {
super();
this.STORAGE_KEY = 'user-color-scheme';
this.COLOR_MODE_KEY = '--color-mode';
}
connectedCallback() {
this.render();
}
getCSSCustomProp(propKey) {
let response = getComputedStyle(document.documentElement).getPropertyValue(propKey);
// Tidy up the string if theres something to work with
if (response.length) {
response = response.replace(/\'|"/g, '').trim();
}
// Return the string response by default
return response;
}
applySetting(passedSetting) {
let currentSetting = passedSetting || localStorage.getItem(this.STORAGE_KEY);
if (currentSetting) {
document.documentElement.setAttribute('data-user-color-scheme', currentSetting);
this.setButtonLabelAndStatus(currentSetting);
} else {
this.setButtonLabelAndStatus(this.getCSSCustomProp(this.COLOR_MODE_KEY));
}
}
toggleSetting() {
let currentSetting = localStorage.getItem(this.STORAGE_KEY);
switch (currentSetting) {
case null:
currentSetting =
this.getCSSCustomProp(this.COLOR_MODE_KEY) === 'dark' ? 'light' : 'dark';
break;
case 'light':
currentSetting = 'dark';
break;
case 'dark':
currentSetting = 'light';
break;
}
localStorage.setItem(this.STORAGE_KEY, currentSetting);
return currentSetting;
}
setButtonLabelAndStatus(currentSetting) {
this.modeToggleButton.innerText = `${
currentSetting === 'dark' ? 'Light' : 'Dark'
} theme`;
this.modeStatusElement.innerText = `Color mode is now "${currentSetting}"`;
}
render() {
this.innerHTML = html`
<div class="[ theme-toggle ] [ md:ta-right gap-top-500 ]">
<div role="status" class="[ visually-hidden ][ js-mode-status ]"></div>
<button class="[ button ] [ font-base text-base weight-bold ] [ js-mode-toggle ]">
Dark theme
</button>
</div>
`;
this.afterRender();
}
afterRender() {
this.modeToggleButton = document.querySelector('.js-mode-toggle');
this.modeStatusElement = document.querySelector('.js-mode-status');
this.modeToggleButton.addEventListener('click', evt => {
evt.preventDefault();
this.applySetting(this.toggleSetting());
});
this.applySetting();
}
}
if ('customElements' in window) {
customElements.define('theme-toggle', ThemeToggle);
}
export default ThemeToggle;

9
src/pages/contact.md Normal file
View File

@ -0,0 +1,9 @@
---
title: 'Contact'
permalink: '/contact/index.html'
layout: 'layouts/contact.njk'
---
You can have a contact page which uses a basic form. The [code with the form fields lives here](https://github.com/hankchizljaw/hylia/blob/master/src/_includes/layouts/contact.njk).
To delete the contact form for this site, delete this page in the CMS or at `src/pages/contact.md`. You probably will also want to delete `src/pages/thank-you.md`.

3
src/pages/pages.json Normal file
View File

@ -0,0 +1,3 @@
{
"layout": "layouts/page.njk"
}

6
src/pages/thank-you.md Normal file
View File

@ -0,0 +1,6 @@
---
title: 'Thank you'
permalink: '/thank-you/index.html'
---
This is your thank you page where if someone fills in your contact form, they will be directed to. Make sure you add a nice message 🙂

View File

@ -0,0 +1,64 @@
---
title: A post with code samples
date: '2019-12-18'
tags:
- demo-content
- code
- blog
---
The best way to demo a code post is to display a real life post, so check out this one from [andy-bell.design](https://andy-bell.design/wrote/creating-a-full-bleed-css-utility/) about a full bleed CSS utility.
- - -
Sometimes you want to break your components out of the constraints that they find themselves in. A common situation where this occurs is when you dont have much control of the container that it exists in, such as a CMS main content area.
This is even more the case with editing tools such as the [WordPress Gutenberg editor](https://wordpress.org/gutenberg/), where in theory, you could pull in a component from a design system and utilise it in the main content of your web page. In these situations, it can be pretty darn handy to have a little utility that makes the element 100% of the viewports width _and_ still maintain its flow within its parent container.
This is when I normally pull the `.full-bleed` utility class out of my back pocket.
## The `.full-bleed` utility
Its small, but hella mighty:
```css
.full-bleed {
width: 100vw;
margin-left: 50%;
transform: translateX(-50%);
}
```
Here it is in a context where it makes a fancy `<aside>` and a `<figure>` element bleed out of their parent container.
<iframe height="300" style="width: 100%;" scrolling="no" title="Piccalilli CSS Utility — Issue #2 — Full bleed utility" src="//codepen.io/andybelldesign/embed/Nmxrwv/?height=300&theme-id=dark&default-tab=css,result" frameborder="no" allowtransparency="true" allowfullscreen="true">
See the Pen <a href='https://codepen.io/andybelldesign/pen/Nmxrwv/'>Piccalilli CSS Utility — Issue #2 — Full bleed utility</a> by Andy Bell
(<a href='https://codepen.io/andybelldesign'>@andybelldesign</a>) on <a href='https://codepen.io'>CodePen</a>.
</iframe>
The `.full-bleed` utility gives those elements prominence and _importantly_ keeps their semantic place in the page. Just how I like it.
- - -
🔥 **Pro tip**: When working with a utility like `.full-bleed`, its a good idea to add an inner container that has a max-width and auto horizontal margin. For this, I normal create a shared `.wrapper` component like this:
```css
.wrapper {
max-width: 50rem;
margin-left: auto;
margin-right: auto;
}
```
Having a container like `.wrapper` helps to create consistent, centred content.
- - -
### How the `.full-bleed` utility works
We set the container to be `width: 100vw`, which equates to the full viewport width. We couldnt set it to `width: 100%` because it would only fill the space of its parent element. The parent elements width _is_ useful though, because by setting `margin-left: 50%`, we are telling the component to align its **left edge** to the center of its parent element, because `50%` is half of the **parent elements** width.
Finally, we use CSS transforms to `translateX(-50%)`. Because the transform works off the elements dimensions and not the parents dimensions, itll pull the element back `50vw`, because its `100vw` wide, thus making it sit perfectly flush with the viewports edges.
## Wrapping up
Hopefully this short and sweet trick will help you out on your projects. If it does, [drop me a tweet](https://twitter.com/andybelldesign), because Id love to see it!

View File

@ -0,0 +1,25 @@
---
title: A post with figures and video
date: '2019-04-18'
tags:
- demo-content
- blog
- media
---
A post to demonstrate how a blog post looks on Hylia. Content is all set in the “Body” field as markdown and Eleventy transforms it into a proper HTML post. You can also edit the markdown file directly if you prefer not to use the CMS.
If you want to make an image bleed-out, add a title attribute to it and the front-end will automatically wrap it in a `<figure>` tag for you.
![The top of a grey concrete building with a blue sky in the background](/images/demo-image-1.jpg "Brutalism at its finest. Photo by Artificial Photography on Unsplash.")
You can also add videos to posts from YouTube or Vimeo (or wherever, really) and the front-end will also make those bleed-out for you too.
<iframe width="560" height="315" src="https://www.youtube.com/embed/_38JDGnr0vA" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Finally, how about a `<blockquote>`?
> Quotes will take a slightly different style to normal body text and look fancy.
![Person holds up a photograph of a riverside and buildings with the same river as a backdrop](/images/demo-image-2.jpg "Remember, if you want a figure and caption, add a 'title' attribute to image in the body field — Photo by Kharytonova Antonina on Unsplash.")
Hopefully, this has demonstrated how simple it is to make a nice looking blog with Hylia.

View File

@ -0,0 +1,33 @@
---
title: A scheduled post
date: '2022-06-18'
tags:
- demo-content
- simple-post
- blog
---
This post is scheduled for the future, specifically mid-2022. Hopefully you're still blogging by then too. Once that date ticks by, this post will automatically become published and visible.
Otherwise, below is some styled content for you to play with.
A simple post to demonstrate how a normal blog post looks on Hylia. Content is all set in the “Body” field as markdown and Eleventy transforms it into a proper HTML post. You can also edit the markdown file directly if you prefer not to use the CMS.
How about a `<blockquote>`?
> Maecenas faucibus mollis interdum. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae elit libero, a pharetra augue.
A list of stuff:
- Sed posuere consectetur est at lobortis
- Aenean lacinia bibendum nulla sed consectetur
- Sed posuere consectetur est at lobortis
How about an ordered list of stuff:
1. Sed posuere consectetur est at lobortis
2. Aenean lacinia bibendum nulla sed consectetur
3. Sed posuere consectetur est at lobortis
Hopefully, this has demonstrated how simple it is to make a nice looking blog with Hylia.

View File

@ -0,0 +1,28 @@
---
title: A simple post
date: '2019-05-18'
tags:
- demo-content
- simple-post
- blog
---
A simple post to demonstrate how a normal blog post looks on Hylia. Content is all set in the “Body” field as markdown and Eleventy transforms it into a proper HTML post. You can also edit the markdown file directly if you prefer not to use the CMS.
How about a `<blockquote>`?
> Maecenas faucibus mollis interdum. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae elit libero, a pharetra augue.
A list of stuff:
- Sed posuere consectetur est at lobortis
- Aenean lacinia bibendum nulla sed consectetur
- Sed posuere consectetur est at lobortis
How about an ordered list of stuff:
1. Sed posuere consectetur est at lobortis
2. Aenean lacinia bibendum nulla sed consectetur
3. Sed posuere consectetur est at lobortis
Hopefully, this has demonstrated how simple it is to make a nice looking blog with Hylia.

View File

@ -0,0 +1,28 @@
---
title: Réunion d'information Astrolabe
date: '2020-06-12'
time: '19:00'
tags:
- évenement
- réunion
---
A simple post to demonstrate how a normal blog post looks on Hylia. Content is all set in the “Body” field as markdown and Eleventy transforms it into a proper HTML post. You can also edit the markdown file directly if you prefer not to use the CMS.
How about a `<blockquote>`?
> Maecenas faucibus mollis interdum. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae elit libero, a pharetra augue.
A list of stuff:
- Sed posuere consectetur est at lobortis
- Aenean lacinia bibendum nulla sed consectetur
- Sed posuere consectetur est at lobortis
How about an ordered list of stuff:
1. Sed posuere consectetur est at lobortis
2. Aenean lacinia bibendum nulla sed consectetur
3. Sed posuere consectetur est at lobortis
Hopefully, this has demonstrated how simple it is to make a nice looking blog with Hylia.

3
src/posts/posts.json Normal file
View File

@ -0,0 +1,3 @@
{
"layout": "layouts/post.njk"
}

3
src/robots.txt Normal file
View File

@ -0,0 +1,3 @@
User-agent: *
Disallow: /preprod/
Noindex: /preprod/

182
src/scss/_config.scss Normal file
View File

@ -0,0 +1,182 @@
@import 'tokens';
/**
* SIZE SCALE
* This is a Major Third scale that powers all the utilities that
* it is relevant for (font-size, margin, padding). All items are
* calcuated off these tokens.
*/
$stalfos-size-scale: map-get($tokens, 'size-scale');
/**
* COLORS
* Colors are shared between backgrounds and text by default.
* You can also use them to power borders, fills or shadows, for example.
*/
$stalfos-colors: map-get($tokens, 'colors');
/**
* UTIL PREFIX
* All pre-built, framework utilities will have this prefix.
* Example: the wrapper utility is '.sf-wrapper' because the default
* prefix is 'sf-'.
*/
$stalfos-util-prefix: 'sf-';
/**
* METRICS
* Various misc metrics to use around the site
*/
$metrics: (
'wrap-max-width': 71.25rem,
'wrap-inner-max-width': 62.25rem,
'md-breakpoint': 48rem
);
/**
* CORE CONFIG
* This powers everything from utility class generation to breakpoints
* to enabling/disabling pre-built components/utilities.
*/
$stalfos-config: (
'align': (
'items': (
'start': 'flex-start',
'center': 'center',
'end': 'flex-end'
),
'output': 'responsive',
'property': 'align-items'
),
'bg': (
'items': $stalfos-colors,
'output': 'standard',
'property': 'background'
),
'color': (
'items': $stalfos-colors,
'output': 'standard',
'property': 'color'
),
'box': (
'items': (
'block': 'block',
'flex': 'flex',
'inline-flex': 'inline-flex',
'hide': 'none'
),
'output': 'responsive',
'property': 'display'
),
'font': (
'items': map-get($tokens, 'fonts'),
'output': 'standard',
'property': 'font-family'
),
'gap-top': (
'items': $stalfos-size-scale,
'output': 'standard',
'property': 'margin-top'
),
'gap-bottom': (
'items': $stalfos-size-scale,
'output': 'standard',
'property': 'margin-bottom'
),
'leading': (
'items': (
'tight': '1.2',
'mid': '1.5',
'loose': '1.7'
),
'output': 'standard',
'property': 'line-height'
),
'measure': (
'items': (
'long': '75ch',
'short': '60ch',
'compact': '40ch'
),
'output': 'standard',
'property': 'max-width'
),
'pad-top': (
'items': $stalfos-size-scale,
'output': 'standard',
'property': 'padding-top'
),
'pad-bottom': (
'items': $stalfos-size-scale,
'output': 'standard',
'property': 'padding-bottom'
),
'pad-left': (
'items': $stalfos-size-scale,
'output': 'standard',
'property': 'padding-left'
),
'space': (
'items': (
'between': 'space-between',
'around': 'space-around',
'before': 'flex-end'
),
'output': 'responsive',
'property': 'justify-content'
),
'stack': (
'items': (
'300': 0,
'400': 10,
'500': 20,
'600': 30,
'700': 40
),
'output': 'standard',
'property': 'z-index'
),
'ta': (
'items': (
'right': 'right',
'left': 'left',
'center': 'center'
),
'output': 'responsive',
'property': 'text-align'
),
'text': (
'items': $stalfos-size-scale,
'output': 'responsive',
'property': 'font-size'
),
'weight': (
'items': (
'light': '300',
'regular': '400',
'mid': '600',
'bold': '700'
),
'output': 'standard',
'property': 'font-weight'
),
'width': (
'items': (
'full': '100%',
'half': percentage(1/2),
'quarter': percentage(1/4),
'third': percentage(1/3)
),
'output': 'responsive',
'property': 'width'
),
'breakpoints': (
'md': #{'(min-width: ' + map-get($metrics, 'md-breakpoint') + ')'}
),
'utilities': (
'reset': 'on',
'icon': 'off',
'flow': 'on',
'wrapper': 'off'
)
);

55
src/scss/_theme.scss Normal file
View File

@ -0,0 +1,55 @@
:root {
// Pull the tokens and generate custom props
@each $color in $stalfos-colors {
#{'--color-' + nth($color, 1)}: #{nth($color, 2)};
}
// Set theme aliases
--color-mode: 'light';
--color-bg: #{get-color('light')};
--color-bg-glare: #{get-color('light')};
--color-text: #{get-color('dark')};
--color-text-glare: #{get-color('dark')};
--color-selection-text: #{get-color('light')};
--color-selection-bg: #{get-color('dark')};
--color-stroke: #{get-color('mid')};
--color-action-bg: #{get-color('primary')};
--color-action-text: #{get-color('light')};
--color-theme-primary: #{get-color('primary')};
--color-theme-primary-glare: #{get-color('primary-glare')};
--color-theme-highlight: #{get-color('highlight')};
--color-theme-highlight-block: #{get-color('highlight')};
--color-theme-secondary: #{get-color('secondary')};
--color-light-gray: #{get-color('light-gray')};
--color-white: #{get-color('light')};
}
@include dark-mode() {
--color-bg: #{get-color('dark')};
--color-bg-glare: #{get-color('slate')};
--color-text: #{get-color('light')};
--color-selection-text: #{get-color('dark')};
--color-selection-bg: #{get-color('light')};
--color-stroke: #{get-color('slate')};
--color-theme-primary: #{lighten(get-color('primary'), 50%)};
--color-theme-primary-glare: #{lighten(get-color('primary-glare'), 50%)};
--color-action-bg: var(--color-theme-primary-glare);
--color-action-text: #{get-color('dark')};
--color-theme-highlight: #{get-color('highlight')};
--color-theme-highlight-block: #{get-color('slate')};
--color-theme-feature-text: #{get-color('highlight')};
}
body {
color: var(--color-text);
background-color: var(--color-bg);
}
main {
overflow: hidden;
}
::selection {
color: var(--color-selection-text);
background-color: var(--color-selection-bg);
}

94
src/scss/_typography.scss Normal file
View File

@ -0,0 +1,94 @@
/* varela-round-regular - latin */
@font-face {
font-family: 'Varela Round';
font-style: normal;
font-weight: 400;
font-display: swap;
src: local('Varela Round Regular'), local('VarelaRound-Regular'),
url('/preprod/fonts/varela-round-v12-latin-regular.woff') format('woff');
}
/* varela-regular - latin */
@font-face {
font-family: 'Varela';
font-style: normal;
font-weight: 400;
src: local('Varela'),
url('/preprod/fonts/varela-v10-latin-regular.woff') format('woff'), /* Modern Browsers */
}
/* open-sans-300 - latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'),
url('/preprod/fonts/open-sans-v17-latin-300.woff') format('woff'), /* Modern Browsers */
}
/* open-sans-regular - latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'),
url('/preprod/fonts/open-sans-v17-latin-regular.woff') format('woff'), /* Modern Browsers */
}
/* open-sans-600 - latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'),
url('/preprod/fonts/open-sans-v17-latin-600.woff') format('woff'), /* Modern Browsers */
}
/* open-sans-700 - latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'),
url('/preprod/fonts/open-sans-v17-latin-700.woff') format('woff'), /* Modern Browsers */
}
body {
$line-height: get-size(600);
@include apply-utility('font', 'base');
// Strip the unit off the size ratio to generate a line height
line-height: $line-height / ($line-height * 0 + 1);
}
h1,
h2 {
@include apply-utility('font', 'brand');
}
h1 {
font-size: 2.125rem;
line-height: 1.4;
font-weight: 400;
}
h2 {
font-size: 1.5rem;
font-weight: 400;
}
h3 {
font-size: get-size(500);
}
@include media-query('md') {
// h1 {
// font-size: get-size(900);
// }
// h2 {
// font-size: get-size(800);
// }
// h3 {
// font-size: get-size(700);
// }
}

View File

@ -0,0 +1,57 @@
.button {
display: inline-block;
border: 0;
background-color: var(--color-action-bg);
color: var(--color-action-text);
padding: get-size(300) get-size('base');
line-height: 1;
margin: 0;
text-decoration: none;
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
}
.button:hover,
.button:focus {
filter: brightness(1.2);
}
.button:focus:hover {
outline: none;
}
.button:focus:not(:hover) {
outline: 1px solid var(--color-action-text);
outline-offset: -4px;
}
.button:active {
transform: scale(.99);
}
.btn {
display: inline-block;
padding: 1.25rem 2rem;
border: 0;
border-radius: 1.75rem;
line-height: 1;
text-decoration: none;
font-size: 1.125rem;
font-weight: 600;
+ .btn {
margin-left: 2rem;
}
&.btn-primary {
color: var(--color-dark);
background-color: var(--color-primary);
font-weight: 700;
}
&.btn-secondary {
color: var(--color-white);
background-color: var(--color-secondary);
}
}

View File

@ -0,0 +1,87 @@
/* Form */
form {
max-width: 35rem;
}
form br {
display: none;
}
label {
display: block;
font-weight: 600;
}
input,
select {
line-height: 1;
}
input,
textarea,
select {
@include apply-utility('font', 'base');
background-color: #fff;
font: inherit;
border: 1px solid var(--color-text);
margin-top: .15rem;
padding: .5rem 1rem;
width: 100%;
}
label input {
margin: -.25rem .5rem 0 0;
width: auto;
vertical-align: middle;
}
fieldset {
border: 0;
margin: 0;
padding: 0;
}
legend {
display: block;
font-weight: bold;
}
.field-list {
margin: 0;
padding: 0;
list-style: none;
}
.field-list__field-group {
margin-bottom: 2rem;
transition: transform 150ms;
&__description {
display: block;
margin-top: .3rem;
font-size: .875rem;
line-height: 1.25;
}
textarea + &__description {
margin-top: 0;
}
&--confirm {
font-weight: normal;
}
&__list {
list-style: none;
margin: 0;
label {
font-weight: normal;
}
.field-list__field-group__description {
margin: 0 0 0 1.35rem;
}
}
}

View File

@ -0,0 +1,11 @@
.heading-permalink {
color: var(--color-theme-primary-glare);
font-size: .8em;
margin-left: .3em;
margin-top: .2em;
@include media-query('md') {
font-size: .6em;
margin-top: .4em;
}
}

View File

@ -0,0 +1,29 @@
.intro {
// background: var(--color-theme-highlight-block);
// padding: 8rem 0;
margin-top: 8rem;
&__summary {
--flow-space: #{get-size(500)};
font-size: get-size(500);
a {
color: currentColor;
&:hover {
text-decoration: none;
}
}
}
&__heading {
max-width: 44rem;
color: var(--color-text);
margin-bottom: 4rem;
font-size: 2.5rem;
&--compact {
max-width: 20ex;
}
}
}

View File

@ -0,0 +1,33 @@
.nav {
&__list {
overflow-x: auto;
// Add padding and neg margin to allow focus style visibility
padding: .5rem;
margin: -.5rem;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
> * + * {
margin-left: 2rem;
}
}
&__item {
padding: get-size(300) 0;
flex-shrink: 0;
a {
@include apply-utility('weight', 'mid');
color: currentColor;
&:not(:hover) {
text-decoration: none;
}
}
}
}

View File

@ -0,0 +1,93 @@
.news-list {
margin-top: 8rem;
margin-bottom: 24rem;
position: relative;
// > svg {
// position: absolute;
// z-index: -1;
// top: -18rem;
// }
.wrapper {
// display: flex;
// flex-direction: column;
position: relative;
}
&__inner {
position: absolute;
top: -9rem;
display: flex;
flex-direction: column;
}
&__heading {
margin-bottom: 3rem;
color: var(--color-white);
font-weight: 100;
}
&__items {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: 1fr;
grid-column-gap: 1.5rem;
grid-row-gap: 0;
}
&__item {
display: flex;
flex-direction: column;
background-color: var(--color-primary);
color: var(--color-dark);
border-radius: 1rem;
&-heading {
font-size: 1.125rem;
font-weight: 600;
padding: 4.5rem 1.5rem 4rem;
}
&-date {
font-size: 1rem;
font-weight: 600;
background-color: var(--color-light-gray);
border-radius: 0 0 1rem 1rem;
padding: 1rem 1.5rem;
margin-top: auto;
}
}
&__link {
&,
&:visited {
color: var(--color-dark);
text-decoration: none;
}
&:hover {
text-decoration: underline;
}
}
&__see-all {
font-size: 1.125rem;
font-weight: 700;
line-height: 1;
align-self: flex-end;
margin-top: 3rem;
padding: .875rem 1.5rem;
&,
&:visited {
color: var(--color-dark);
}
&:hover {
background-color: var(--color-primary);
border-radius: 1.5rem;
text-decoration: none;
}
}
}

View File

@ -0,0 +1,29 @@
.pagination {
flex-wrap: wrap;
a {
color: var(--color-theme-primary);
&:not(:hover) {
text-decoration: none;
}
// Flip content if direction is backwards
&[data-direction='backwards'] {
flex-direction: row-reverse;
svg {
transform: rotate(-180deg);
}
}
// If only child and forwards, push out to the right
&[data-direction='forwards']:only-child {
margin-left: auto;
}
}
svg {
pointer-events: none;
}
}

View File

@ -0,0 +1,17 @@
.post-list {
&__item {
--flow-space: #{get-size(700)};
}
&__link {
&,
&:visited {
color: var(--color-theme-primary);
text-decoration: none;
}
&:hover {
text-decoration: underline;
}
}
}

View File

@ -0,0 +1,137 @@
.post {
&__body {
--flow-space: #{get-size(800)};
/**
* Generic HTML styles
*/
h2 + *,
h3 + * {
--flow-space: #{get-size(500)};
}
h2,
h3 {
@include apply-utility('leading', 'tight');
position: relative;
/*display: flex;*/
}
a:not([class]) {
@include apply-utility('leading', 'tight');
color: var(--color-dark);
position: relative;
display: inline-block;
background: var(--color-theme-highlight);
padding: .2rem .4rem .3rem;
text-decoration: none;
word-break: break-word;
}
a:not([class]):hover {
text-decoration: underline;
}
code {
font-size: 1.2em;
color: var(--color-theme-primary);
font-weight: 600;
margin-left: .01ch;
margin-right: .01ch;
}
pre > code {
margin-right: 0;
border: 1px solid rgba(255, 255, 255, .1);
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
blockquote {
border-left: .4rem solid var(--color-theme-primary-glare);
margin-left: 0;
padding-left: get-size(500);
font-style: italic;
font-size: get-size(600);
p {
opacity: .85;
padding: get-size(500) 0;
}
}
ol:not([class]),
ul:not([class]) {
margin-left: get-size(800);
li + li {
margin-top: get-size(300);
}
}
figure,
figure + *,
pre > code,
.video-player,
.video-player + *,
video {
--flow-space: #{get-size('max')};
}
figure,
pre > code,
.video-player,
video {
width: 100vw;
max-width: map-get($metrics, 'wrap-max-width');
margin-left: 50%; /*changing this value to 47% removes the horizontal scrollbar once the viewport is < 930px */
transform: translateX (-50%); /* changing this value to 49% allows for the suggestion above to also eliminate the horizontal scroll. */
position: relative;
}
figure img,
pre > code,
.video-player {
box-shadow: 0 10px 30px rgba(0, 0, 0, .15);
}
figure img {
position: relative;
z-index: 1;
}
figcaption {
font-size: .8em;
font-style: italic;
max-width: map-get($metrics, 'wrap-inner-max-width');
margin: .5rem auto 0;
padding: 0 get-size(500);
}
pre > code {
display: block;
background: var(--color-dark);
padding: get-size(700);
font-size: get-size(500);
}
}
&__footer {
background: var(--color-theme-highlight);
h2 {
flex-shrink: 0;
margin-right: get-size('base');
color: var(--color-dark);
}
h2 a {
@extend %visually-hidden;
}
a {
background: var(--color-bg);
padding: .4rem .6rem;
}
}
}

View File

@ -0,0 +1,36 @@
.site-foot {
background: var(--color-secondary);
color: var(--color-white);
&__inner {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: 1fr;
grid-column-gap: 1.5rem;
grid-row-gap: 0;
padding: 3.5rem 0 2.5rem;
font-weight: 300;
h3 {
font-size: 1rem;
font-weight: 400;
margin-bottom: 1.5rem;
}
p {
margin-bottom: 1rem;
}
}
a {
color: currentColor;
&:hover {
text-decoration: none;
}
}
&__credit {
// text-align: center;
}
}

View File

@ -0,0 +1,10 @@
.site-head {
padding-top: 3rem;
padding-bottom: 1.5rem;
&__site-name {
font-weight: 700;
text-decoration: none;
color: var(--color-text);
}
}

View File

@ -0,0 +1,17 @@
// Hide skip link visually by default
.skip-link:not(:focus) {
@extend %visually-hidden;
}
.skip-link:focus {
display: inline-block;
position: absolute;
top: 0;
left: 0;
padding: get-size(300) get-size(500) get-size('base') get-size(500);
background-color: var(--color-action-bg);
color: var(--color-action-text);
line-height: 1;
text-decoration: none;
font-weight: 700;
}

View File

@ -0,0 +1,146 @@
/**
* a11y-dark theme for JavaScript, CSS, and HTML
* Based on the okaidia theme: https://github.com/PrismJS/prism/blob/gh-pages/themes/prism-okaidia.css
* @author ericwbailey
*/
code[class*='language-'],
pre[class*='language-'] {
color: #f8f8f2;
background: none;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Inline code */
:not(pre) > code[class*='language-'] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #d4d0ab;
}
.token.punctuation {
color: #fefefe;
}
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
color: #ffa07a;
}
.token.boolean,
.token.number {
color: #00e0e0;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #abe338;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #00e0e0;
}
.token.atrule,
.token.attr-value,
.token.function {
color: #ffd700;
}
.token.keyword {
color: #00e0e0;
}
.token.regex,
.token.important {
color: #ffd700;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
@media screen and (-ms-high-contrast: active) {
code[class*='language-'],
pre[class*='language-'] {
color: windowText;
background: window;
}
:not(pre) > code[class*='language-'],
pre[class*='language-'] {
background: window;
}
.token.important {
background: highlight;
color: window;
font-weight: normal;
}
.token.atrule,
.token.attr-value,
.token.function,
.token.keyword,
.token.operator,
.token.selector {
font-weight: bold;
}
.token.attr-value,
.token.comment,
.token.doctype,
.token.function,
.token.keyword,
.token.operator,
.token.property,
.token.string {
color: highlight;
}
.token.attr-value,
.token.url {
font-weight: normal;
}
}

View File

@ -0,0 +1,23 @@
.video-player {
position: relative;
padding-top: 56.25%;
@include media-query('md') {
.post & {
padding-top: 66%;
}
}
> iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
// If its bleeding out in a post, it needs more padding
.post & {
padding-top: 63%;
}
}

View File

@ -0,0 +1,54 @@
.presentation {
article {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: auto;
grid-column-gap: 2.125rem;
grid-row-gap: 2.125rem;
&:nth-child(2n + 1) {
.content {
grid-column: 1;
}
figure {
grid-column: 2;
}
}
&:nth-child(2n) {
.content {
grid-column: 2;
grid-row: 1;
}
figure {
grid-column: 1;
grid-row: 1;
}
}
}
.content {
h2 {
font-size: 2.125rem;
margin-bottom: 2rem;
+ p {
font-size: 1.25rem;
margin-bottom: 8rem;
}
}
}
figure {
display: flex;
flex-direction: column;
// justify-content: center;
align-items: center;
img {
margin-top: 3.5rem;
}
}
}

79
src/scss/global.scss Normal file
View File

@ -0,0 +1,79 @@
@import 'config';
// Pull in the stalfos lib
@import '../../node_modules/stalfos/stalfos';
// Local mixins
@import 'mixins/dark-mode';
@import 'theme';
// Local dependencies
@import 'typography';
/**
* GLOBAL STYLES
*/
html,
body {
height: 100%;
}
body {
scroll-behavior: smooth;
display: flex;
flex-direction: column;
}
main {
flex: 1 0 auto;
&:focus {
outline: none;
}
}
hr {
display: block;
height: 1px;
max-width: 500px;
background: var(--color-stroke);
border: 0;
margin: get-size(900) auto;
}
// For when metric attributes are added to img elements
img {
height: auto;
}
:focus {
outline: 1px solid var(--color-theme-primary-glare);
outline-offset: 0.25rem;
}
/**
* PROJECT IMPORTS
*/
// Utils
@import 'utilities/inner-wrapper';
@import 'utilities/visually-hidden';
@import 'utilities/wrapper';
// Components
@import 'components/button';
@import 'components/form';
@import 'components/heading-permalink';
@import 'components/intro';
@import 'components/nav';
@import 'components/pagination';
@import 'components/post';
@import 'components/post-list';
@import 'components/news-list';
@import 'components/presentation';
@import 'components/site-head';
@import 'components/site-foot';
@import 'components/skip-link';
@import 'components/syntax-highlighting';
@import 'components/video-player';

View File

@ -0,0 +1,21 @@
/**
* DARK MODE MIXIN
*
* A little wrapper that lets you define your dark mode custom
* properties in a way that supports the theme toggle web component
*/
@mixin dark-mode() {
@media (prefers-color-scheme: dark) {
:root {
--color-mode: 'dark';
}
:root:not([data-user-color-scheme]) {
@content;
}
}
[data-user-color-scheme='dark'] {
@content;
}
}

View File

@ -0,0 +1,7 @@
.inner-wrapper {
max-width: map-get($metrics, 'wrap-inner-max-width');
margin-left: auto;
margin-right: auto;
padding-left: get-size(500);
padding-right: get-size(500);
}

View File

@ -0,0 +1,12 @@
%visually-hidden,
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: auto;
margin: 0;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
white-space: nowrap;
}

View File

@ -0,0 +1,7 @@
.wrapper {
max-width: map-get($metrics, 'wrap-max-width');
margin-left: auto;
margin-right: auto;
padding-left: get-size(500);
padding-right: get-size(500);
}

5
src/service-worker.njk Normal file
View File

@ -0,0 +1,5 @@
---
permalink: '/service-worker.js'
---
const VERSION = '{{ global.random() }}';
{% include "partials/global/service-worker.js" %}

149
src/styleguide.njk Normal file
View File

@ -0,0 +1,149 @@
---
title: 'Styleguide'
permalink: /styleguide/
---
{% extends 'layouts/base.njk' %}
{# Intro content #}
{% set introHeading = title %}
{% block head %}
<style>
.swatches {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(17.5rem, 1fr));
grid-gap: 2.5rem 2rem;
margin-left: 0;
}
.swatches > * {
margin-top: 0 !important;
}
.props dt {
font-weight: 600;
}
.props dd + dt {
margin-top: 0.5rem;
display: block;
}
.color {
border: 1px solid var(--color-mid);
}
.bg-dark code {
color: var(--color-light);
}
</style>
{% endblock %}
{% block content %}
<main id="main-content" tabindex="-1">
{% include "partials/components/intro.njk" %}
<article class="[ inner-wrapper ] [ post ]">
<section class="[ post__body ] [ sf-flow ] [ pad-top-900 pad-bottom-900 ]">
<h2>Colours</h2>
<p class="visually-hidden">Colour swatches with various values that you can copy.</p>
<ul class="swatches">
{% for color in styleguide.colors() %}
<li class="sf-flow">
<div class="[ color ] [ pad-top-900 pad-bottom-900 ]" style="background: {{ color.value }}"></div>
<h3 class="font-base text-500">{{ color.key }}</h3>
<dl class="props">
<dt>Value</dt>
<dd><code>{{ color.value }}</code></dd>
<dt>Sass function</dt>
<dd><code>get-color('{{ color.key }}')</code></dd>
<dt>Custom Property</dt>
<dd><code>var(--color-{{ color.key }})</code></dd>
<dt>Text util class</dt>
<dd><code>color-{{ color.key }}</code></dd>
<dt>Background util class</dt>
<dd><code>bg-{{ color.key }}</code></dd>
</dl>
</li>
{% endfor %}
</ul>
<h2>Fonts</h2>
<h3>Base — System stack</h3>
<p class="sf-flow">
<span aria-hidden="true" role="presentation" class="font-base text-600 md:text-800 box-block leading-tight">The quick brown fox jumps over the lazy fox</span>
<code>.font-base</code>
</p>
<h3>Serif — Lora</h3>
<p class="sf-flow">
<span aria-hidden="true" role="presentation" class="font-serif text-600 md:text-800 box-block leading-tight">The quick brown fox jumps over the lazy fox</span>
<code>.font-serif</code>
</p>
<h2>Text sizes</h2>
<p>Text sizes are available as standard classes or media query prefixed, such as <code>lg:text-500</code>.</p>
{% for size in styleguide.sizes() %}
<p class="text-{{ size.key }}" style="--flow-space: 1.5rem">{{ size.value }} - <code>text-{{ size.key }}</code></p>
{% endfor %}
<h2>Spacing</h2>
<p>Theres size ratio utilities that give you margin (<code>gap-top, gap-bottom</code>) and padding (<code>pad-top, pad-left, pad-bottom</code>).
<h3>Margin</h3>
<p class="visually-hidden">Margin token classes that you can copy</p>
<div>
{% for size in styleguide.sizes() %}
<div class="[ bg-dark color-light pad-top-base pad-bottom-base pad-left-base md:width-half ] [ gap-top-{{ size.key }} ]">
<code>gap-top-{{ size.key }}</code>
</div>
{% endfor %}
</div>
<h3>Padding</h3>
<p class="visually-hidden">Padding token classes that you can copy</p>
{% for size in styleguide.sizes() %}
<div class="[ bg-dark pad-left-base color-light md:width-half ] [ pad-top-{{ size.key }} ]">
<code>pad-top-{{ size.key }}</code>
</div>
<div class="[ bg-dark pad-left-base color-light md:width-half ] [ pad-bottom-{{ size.key }} ]">
<code>pad-bottom-{{ size.key }}</code>
</div>
<div class="[ bg-dark color-light md:width-half ] [ pad-left-{{ size.key }} ]">
<code>pad-left-{{ size.key }}</code>
</div>
{% endfor %}
<h2>Forms</h2>
{% from "macros/form.njk" import label, field, textarea, confirm, select, radios, checkboxes, hidden_field, button %}
<form>
<ol class="[ field-list ]">
<li class="[ field-list__field-group ]">
{{ label("Text Label", "field-text-name") }}
{{ field( "text", "field-text-name", { required: true, placeholder: "Katherine Johnson", autocomplete: "name", autocorrect: "off", autocapitalize: "off", description: "Optional description. Note: This field type can take any valid input type." } ) }}
</li>
<li class="[ field-list__field-group ]">
{{ label("Email Label", "field-email-name") }}
{{ field( "email", "field-email-name", { required: true, placeholder: "katherine@johnson.tld", autocomplete: "email" } ) }}
</li>
<li class="[ field-list__field-group ]">
{{ label("Textarea Label", "field-textarea-name") }}
{{ textarea( "field-textarea-name", { required: true, autocapitalize: "sentences", spellcheck: "true" } ) }}
</li>
<li class="[ field-list__field-group ]">
{{ label("Select Label", "field-select-name") }}
{{ select( "select", [ "option 1", {label: "Option II", value: "option 2"} ], { required: true, options_before: ["prepended option"], options_after: ["appended option"], description: "Optional description." } ) }}
</li>
<li class="[ field-list__field-group field-list__field-group--confirm ]">
{{ confirm("Confirm this statement", "confirm") }}
</li>
<li class="[ field-list__field-group ]">
{{ radios("Radio Legend", "field-radio-name", [ "Yes", { label: "Not really", value: "No", note: "Note about choice." } ], { description: "Optional description." } ) }}
</li>
<li class="[ field-list__field-group ]">
{{ checkboxes("Checkbox Legend", "field-checkbox-name", [ "Choice 1", { label: "Choice 2", value: "Choice 2", note: "Note about choice." }, "Choice 3" ], { description: "Optional description." } ) }}
</li>
<li class="[ field-list__field-group ]">
There is a hidden field here…
{{ hidden_field("hidden-name", "hidden-value") }}
</li>
</ol>
{{ button("Button Text") }}
</form>
</section>
</article>
</main>
{% endblock %}

35
src/tags.njk Normal file
View File

@ -0,0 +1,35 @@
---
title: Tag Archive
pagination:
data: collections
size: 1
alias: tag
filter:
- all
- nav
- post
- posts
- tagList
- postFeed
addAllPagesToCollections: true
permalink: /tags/{{ tag }}/
---
{% extends 'layouts/base.njk' %}
{# Intro content #}
{% set introHeading %}Posts filed under “{{ tag }}”{% endset %}
{% set introHeadingLevel = '2' %}
{# Post list content #}
{% set postListHeadingLevel = '2' %}
{% set postListHeading = 'Posts' %}
{% set postListItems = collections[tag] %}
{% block content %}
<main id="main-content" tabindex="-1">
{% include "partials/components/intro.njk" %}
{% include "partials/components/post-list.njk" %}
{% include "partials/components/pagination.njk" %}
</main>
{% endblock %}

View File

@ -0,0 +1,14 @@
const htmlmin = require('html-minifier');
module.exports = function htmlMinTransform(value, outputPath) {
if (outputPath.indexOf('.html') > -1) {
let minified = htmlmin.minify(value, {
useShortDoctype: true,
removeComments: true,
collapseWhitespace: true,
minifyCSS: true
});
return minified;
}
return value;
};

View File

@ -0,0 +1,95 @@
const jsdom = require('@tbranyen/jsdom');
const {JSDOM} = jsdom;
const minify = require('../utils/minify.js');
const slugify = require('slugify');
const getSize = require('image-size');
module.exports = function(value, outputPath) {
if (outputPath.endsWith('.html')) {
const DOM = new JSDOM(value, {
resources: 'usable'
});
const document = DOM.window.document;
const articleImages = [...document.querySelectorAll('main article img, .intro img')];
const articleHeadings = [
...document.querySelectorAll('main article h2, main article h3')
];
const articleEmbeds = [...document.querySelectorAll('main article iframe')];
if (articleImages.length) {
articleImages.forEach(image => {
image.setAttribute('loading', 'lazy');
const file = image.getAttribute('src');
// if preprod env TODO remove when prod
if(this.config.pathPrefix.localeCompare('/')) { // pathPrefix is different from '/'
// if(process.env.ELEVENTY_ENV == "preprod") { // not working
image.setAttribute('src', this.config.pathPrefix + file);
}
if (file.indexOf('http') < 0) {
const dimensions = getSize('src' + file);
image.setAttribute('width', dimensions.width);
image.setAttribute('height', dimensions.height);;
}
// Replace p tags by figure tag for img
const figure = document.createElement('figure');
image.removeAttribute('title');
figure.appendChild(image.cloneNode(true));
// If an image has a title it means that the user added a caption
// so replace the image with a figure containing that image and a caption
if (image.hasAttribute('title')) {
const figCaption = document.createElement('figcaption');
figCaption.innerHTML = image.getAttribute('title');
figure.appendChild(figCaption);
}
image.parentNode.replaceWith(figure);
});
}
if (articleHeadings.length) {
// Loop each heading and add a little anchor and an ID to each one
articleHeadings.forEach(heading => {
const headingSlug = slugify(heading.textContent.toLowerCase());
const anchor = document.createElement('a');
anchor.setAttribute('href', `#heading-${headingSlug}`);
anchor.classList.add('heading-permalink');
anchor.innerHTML = minify(`
<span class="visually-hidden"> permalink</span>
<svg fill="currentColor" aria-hidden="true" focusable="false" width="1em" height="1em" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M9.199 13.599a5.99 5.99 0 0 0 3.949 2.345 5.987 5.987 0 0 0 5.105-1.702l2.995-2.994a5.992 5.992 0 0 0 1.695-4.285 5.976 5.976 0 0 0-1.831-4.211 5.99 5.99 0 0 0-6.431-1.242 6.003 6.003 0 0 0-1.905 1.24l-1.731 1.721a.999.999 0 1 0 1.41 1.418l1.709-1.699a3.985 3.985 0 0 1 2.761-1.123 3.975 3.975 0 0 1 2.799 1.122 3.997 3.997 0 0 1 .111 5.644l-3.005 3.006a3.982 3.982 0 0 1-3.395 1.126 3.987 3.987 0 0 1-2.632-1.563A1 1 0 0 0 9.201 13.6zm5.602-3.198a5.99 5.99 0 0 0-3.949-2.345 5.987 5.987 0 0 0-5.105 1.702l-2.995 2.994a5.992 5.992 0 0 0-1.695 4.285 5.976 5.976 0 0 0 1.831 4.211 5.99 5.99 0 0 0 6.431 1.242 6.003 6.003 0 0 0 1.905-1.24l1.723-1.723a.999.999 0 1 0-1.414-1.414L9.836 19.81a3.985 3.985 0 0 1-2.761 1.123 3.975 3.975 0 0 1-2.799-1.122 3.997 3.997 0 0 1-.111-5.644l3.005-3.006a3.982 3.982 0 0 1 3.395-1.126 3.987 3.987 0 0 1 2.632 1.563 1 1 0 0 0 1.602-1.198z"/>
</svg>`);
heading.setAttribute('id', `heading-${headingSlug}`);
heading.appendChild(anchor);
});
}
// Look for videos are wrap them in a container element
if (articleEmbeds.length) {
articleEmbeds.forEach(embed => {
if (embed.hasAttribute('allowfullscreen')) {
const player = document.createElement('div');
player.classList.add('video-player');
player.appendChild(embed.cloneNode(true));
embed.replaceWith(player);
}
});
}
return '<!DOCTYPE html>\r\n' + document.documentElement.outerHTML;
}
return value;
};

3
src/utils/minify.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = function minify(input) {
return input.replace(/\s{2,}/g, '').replace(/\'/g, '"');
};