├── src ├── components │ ├── utilities │ │ ├── utilities.config.yaml │ │ └── hidden │ │ │ ├── hidden.css │ │ │ └── hidden.html │ ├── common │ │ ├── field │ │ │ ├── field.config.yaml │ │ │ ├── field.html │ │ │ ├── field--textarea.html │ │ │ ├── field--combined.html │ │ │ └── field.css │ │ ├── author │ │ │ ├── author.config.yaml │ │ │ ├── author.html │ │ │ └── author.css │ │ ├── backdrop │ │ │ ├── backdrop.html │ │ │ └── backdrop.css │ │ ├── continue │ │ │ ├── continue.html │ │ │ ├── continue.config.yaml │ │ │ └── continue.css │ │ ├── avatar │ │ │ ├── avatar.html │ │ │ ├── avatar.config.yaml │ │ │ └── avatar.css │ │ ├── search-form │ │ │ └── search-form.html │ │ ├── article │ │ │ ├── article.config.yaml │ │ │ ├── article.html │ │ │ └── article.css │ │ ├── meta │ │ │ ├── meta.html │ │ │ ├── meta.config.yaml │ │ │ └── meta.css │ │ ├── listing │ │ │ ├── listing.html │ │ │ ├── listing.config.yaml │ │ │ └── listing.css │ │ ├── promo │ │ │ ├── promo.html │ │ │ ├── promo.config.yaml │ │ │ └── promo.css │ │ ├── button │ │ │ ├── button.config.yaml │ │ │ ├── button.html │ │ │ └── button.css │ │ ├── preface │ │ │ ├── preface.html │ │ │ ├── preface.config.yaml │ │ │ └── preface.css │ │ ├── section │ │ │ ├── section.html │ │ │ ├── section.css │ │ │ └── section.config.yaml │ │ ├── comment │ │ │ ├── comment.html │ │ │ ├── comment.css │ │ │ └── comment.config.yaml │ │ ├── summary │ │ │ ├── summary.html │ │ │ ├── summary.config.yaml │ │ │ └── summary.css │ │ └── comment-form │ │ │ └── comment-form.html │ ├── scopes │ │ ├── syntax │ │ │ ├── syntax.config.yaml │ │ │ ├── syntax.html │ │ │ └── syntax.css │ │ ├── note │ │ │ ├── note.html │ │ │ ├── note.config.yaml │ │ │ └── note.css │ │ ├── prose │ │ │ ├── prose.html │ │ │ ├── prose.css │ │ │ └── prose.config.yaml │ │ ├── embed │ │ │ ├── embed.html │ │ │ ├── embed.config.yaml │ │ │ └── embed.css │ │ └── gallery │ │ │ ├── gallery.css │ │ │ └── gallery.html │ ├── templates │ │ ├── article-template │ │ │ ├── article-template.html │ │ │ └── article-template.config.yaml │ │ ├── _page.html │ │ ├── index-template │ │ │ ├── index-template.html │ │ │ └── index-template.config.yaml │ │ ├── content-template │ │ │ ├── content-template.html │ │ │ └── content-template.config.yaml │ │ └── listing-template │ │ │ ├── listing-template.html │ │ │ └── listing-template.config.yaml │ ├── global │ │ ├── banner │ │ │ ├── banner.html │ │ │ └── banner.css │ │ ├── traverse-nav │ │ │ ├── traverse-nav.config.yaml │ │ │ ├── traverse-nav.html │ │ │ └── traverse-nav.css │ │ ├── main │ │ │ └── main.css │ │ ├── topics-nav │ │ │ ├── topics-nav.config.yaml │ │ │ ├── topics-nav.html │ │ │ └── topics-nav.css │ │ ├── search │ │ │ ├── search.html │ │ │ └── search.css │ │ ├── contentinfo │ │ │ ├── contentinfo.html │ │ │ └── contentinfo.css │ │ ├── menu │ │ │ ├── menu.html │ │ │ └── menu.css │ │ └── site-nav │ │ │ ├── site-nav.html │ │ │ └── site-nav.css │ ├── og-image │ │ ├── og-image.html │ │ ├── og-image.config.yaml │ │ └── og-image.css │ ├── components.config.yaml │ └── _partials │ │ └── _preview.html ├── assets │ ├── icons │ │ ├── icon.ico │ │ ├── icon.png │ │ └── icon.svg │ ├── images │ │ ├── gravatar.png │ │ └── logo-perch.png │ ├── scripts │ │ ├── prism.js │ │ ├── app.js │ │ └── modules │ │ │ ├── webfont-loader.js │ │ │ ├── focusing.js │ │ │ └── menu.js │ ├── vectors │ │ ├── next.svg │ │ ├── prev.svg │ │ ├── clip.svg │ │ ├── search.svg │ │ ├── topic-process.svg │ │ ├── topic-business.svg │ │ ├── topic-content.svg │ │ ├── menu.svg │ │ ├── topic-design.svg │ │ ├── corner.svg │ │ ├── topic-code.svg │ │ ├── topic-ux.svg │ │ └── diamond.svg │ └── styles │ │ ├── app.css │ │ ├── base │ │ ├── _disabled.css │ │ ├── _tables.css │ │ ├── _embedded.css │ │ ├── _grouping.css │ │ ├── _sections.css │ │ ├── _text.css │ │ └── _forms.css │ │ ├── docs │ │ └── styleguide.css │ │ ├── theme.css │ │ ├── helpers │ │ ├── _mixins.css │ │ └── _typography.css │ │ └── config │ │ ├── _media.css │ │ └── _constants.css ├── robots.txt ├── tokens │ ├── layers.json │ ├── sizes.json │ ├── spaces.json │ ├── breakpoints.json │ ├── borders.json │ ├── fonts.json │ └── colors.json ├── app.webmanifest ├── docs │ ├── _partials │ │ └── _palette-sample.md │ ├── tokens.config.js │ ├── index.md │ └── tokens.md └── humans.txt ├── .gitignore ├── .editorconfig ├── etc └── gulp │ └── rollup.js ├── LICENSE ├── fractal.config.js ├── package.json ├── README.md └── gulpfile.js /src/components/utilities/utilities.config.yaml: -------------------------------------------------------------------------------- 1 | order: 1 2 | -------------------------------------------------------------------------------- /src/components/common/field/field.config.yaml: -------------------------------------------------------------------------------- 1 | collated: true 2 | -------------------------------------------------------------------------------- /src/components/scopes/syntax/syntax.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | prism: true 3 | -------------------------------------------------------------------------------- /src/components/common/author/author.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | author: Drew McLellan 3 | -------------------------------------------------------------------------------- /src/components/utilities/hidden/hidden.css: -------------------------------------------------------------------------------- 1 | .u-hidden { 2 | @apply --hidden; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/common/backdrop/backdrop.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | node_modules 3 | www 4 | tmp 5 | 6 | # Files 7 | .DS_Store 8 | .publish 9 | -------------------------------------------------------------------------------- /src/assets/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/24ways/frontend/HEAD/src/assets/icons/icon.ico -------------------------------------------------------------------------------- /src/assets/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/24ways/frontend/HEAD/src/assets/icons/icon.png -------------------------------------------------------------------------------- /src/components/common/continue/continue.html: -------------------------------------------------------------------------------- 1 | {{ label }} 2 | -------------------------------------------------------------------------------- /src/components/utilities/hidden/hidden.html: -------------------------------------------------------------------------------- 1 | This text is visually hidden. 2 | -------------------------------------------------------------------------------- /src/assets/images/gravatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/24ways/frontend/HEAD/src/assets/images/gravatar.png -------------------------------------------------------------------------------- /src/components/scopes/note/note.html: -------------------------------------------------------------------------------- 1 |2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /src/components/common/listing/listing.html: -------------------------------------------------------------------------------- 1 |2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /src/components/templates/index-template/index-template.html: -------------------------------------------------------------------------------- 1 | {% extends "@page" %} 2 | {% block main %} 3 |{{ url }}
5 | 6 | -------------------------------------------------------------------------------- /src/components/global/banner/banner.html: -------------------------------------------------------------------------------- 1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/tokens/fonts.json:
--------------------------------------------------------------------------------
1 | {
2 | "family-sans": "'Source Sans Pro', sans-serif",
3 | "family-serif": "'Source Serif Pro', serif",
4 | "family-monospace": "'Source Code Pro', monospace",
5 | "family-system": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/global/main/main.css:
--------------------------------------------------------------------------------
1 | .c-main {
2 | background-color: $navigation-color--offset;
3 |
4 | .has-js & {
5 | @media (--upto-medium-screen) {
6 | margin-top: $banner-height--small;
7 | }
8 |
9 | @media (--from-medium-screen) {
10 | margin-right: $navigation-width--large;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/common/button/button.config.yaml:
--------------------------------------------------------------------------------
1 | collated: true
2 | variants:
3 | - name: default
4 | context:
5 | label: Button
6 | - name: disabled
7 | context:
8 | label: Disabled button
9 | disabled: true
10 | - name: more
11 | context:
12 | label: Show me another 24 ways…
13 | href: ?page=2
14 | mods: [more]
15 |
--------------------------------------------------------------------------------
/src/components/scopes/note/note.css:
--------------------------------------------------------------------------------
1 | .s-note {
2 | @apply --typeset-caption;
3 |
4 | margin-bottom: map(spaces, large);
5 | border: solid $prose-color--rule;
6 | border-width: 1px 0;
7 | padding: map(spaces, medium) 0;
8 |
9 | p:not(:last-child) {
10 | margin: map(spaces, medium);
11 | font-size: 1rem;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/common/button/button.html:
--------------------------------------------------------------------------------
1 | {%- if href -%}
2 | {{ label }}
3 | {%- else -%}
4 |
5 | {%- endif -%}
6 |
--------------------------------------------------------------------------------
/src/components/common/field/field--combined.html:
--------------------------------------------------------------------------------
1 | 2 | 3 | 4 | {% render "@button", { label: "Search", submit: true } %} 5 |
6 | -------------------------------------------------------------------------------- /src/assets/vectors/search.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/common/promo/promo.css: -------------------------------------------------------------------------------- 1 | .c-promo { 2 | display: block; 3 | margin-bottom: map(spaces, small); 4 | } 5 | 6 | .c-promo__image { 7 | height: auto; 8 | max-width: 100%; 9 | } 10 | 11 | .c-promo__message { 12 | margin-bottom: map(spaces, small); 13 | color: $color-text; 14 | } 15 | 16 | .c-promo__url { 17 | @apply --typeset-label; 18 | } 19 | -------------------------------------------------------------------------------- /src/app.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "lang": "en-gb", 3 | "name": "24 ways", 4 | "short_name": "24 ways", 5 | "icons": [{ 6 | "src": "/assets/icons/icon.png", 7 | "sizes": "180x180", 8 | "type": "image/png" 9 | }], 10 | "theme_color": "#302", 11 | "background_color": "#f04", 12 | "display": "browser", 13 | "scope": "/", 14 | "start_url": "/" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/og-image/og-image.html: -------------------------------------------------------------------------------- 1 |4 |6 |“{{ quote | markdownInline }}”
5 |
{{ key }}
3 |
4 |
8 |
9 |
10 |
14 |
15 |
16 |
17 | {{ content | markdownInline | safe }}
21 |24 ways is the advent calendar for web geeks. For twenty-four days each December we publish a daily dose of web design and development goodness to bring you all a little Christmas cheer.
12 | 13 | Back in December 2005, Drew McLellan set up what he called "a festive blog" with a new article each day written by experts sharing their knowledge. Articles present ideas, techniques or experiences that you can take and apply to your own work. Code snippets, advice about business and clients, workflow and workshopping, design inspiration: 24 ways has it all. 14 | 15 | With over one hundred authors and almost two hundred articles, 24 ways is very proud to have become an annual fixture in the calendars of web geeks. Since 2005, 24 ways has always combined learning and sharing, both vital aspects of the continued strength of the web community. As Drew wrote, ‘There’s a lot of fun to be had in learning something that will impress those you work with – especially if then you can share what you know to everyone’s benefit.’ 16 | - id: credits 17 | title: Credits 18 | content: | 19 | * 24 ways is brought to you by Perch and is built on [Perch Runway](https://grabaperch.com/?ref=24w01) 20 | * Produced by [Drew McLellan](http://allinthehead.com/), [Brian Suda](http://suda.co.uk/), [Anna Debenham](http://maban.co.uk/) and [Owen Gregory](http://fullcreammilk.co.uk/). 21 | * Designed by [Paul Robert Lloyd](https://paulrobertlloyd.com/). 22 | * Possible only with the help and dedication of [our authors](/authors/). 23 | - id: colophon 24 | title: Colophon 25 | content: | 26 | * Content managed with [Perch Runway](https://grabaperch.com/?ref=24w01). 27 | * Hosted by [Memset Dedicated Servers](https://www.memset.com/dedicated-servers/). 28 | * Standing on the shoulders of Varnish, Nginx, Apache, PHP, MySQL, Amazon S3 and CloudFront. 29 | * Type set in [Source Sans Pro](https://adobe-fonts.github.io/source-sans-pro/), [Source Serif Pro](https://adobe-fonts.github.io/source-serif-pro/) and [Source Code Pro](https://adobe-fonts.github.io/source-code-pro/) by Adobe. 30 | * Fonts served via [Google Fonts](https://fonts.google.com/). 31 | * [Minicons](http://www.webalys.com/minicons/) by Vincent Le Miogn. 32 | * [prism.js](https://github.com/leaverou/prism/) by Lea Verou. 33 | - name: "404" 34 | context: 35 | title: "404" 36 | sections: 37 | - id: error 38 | title: Page not found 39 | content: | 40 |Sorry, but we can't find that page.
41 | 42 | The page you requested wasn't found in the location specified. You may have an incorrect URL, or the file could have been moved or renamed. 43 | 44 | If you're having problems finding a particular page, try searching the site or return to the homepage. 45 | - id: search 46 | title: Search 24 ways 47 | component: search-form 48 | - name: sponsorship 49 | context: 50 | title: "Sponsoring 24 ways" 51 | section: sponsorship 52 | sections: 53 | - id: sponsorship 54 | title: Sponsorship 55 | content: | 56 |For thirteen years, 24 ways has been a fixture of the web design and development calendar, providing fresh and interesting artiles thoughout the month of December.
57 | 58 | All our authors and the production team give up their time and skills for free as a gift to the industry. We do have out-of-pocket expenses, however, which provides an opportunity for like-minded companies to partner with us and bring this valuable resource to our readers. 59 | 60 | Each article carries just a single, non-animated 150px@2x graphic ad with brief accompanying text. Rather than running for just one day, this ad runs for the lifetime of the article it accompanies. That’s potentially years of advertising for just one flat payment. We’ll also include the ad in our RSS feed and post to our social media accounts. 61 | 62 | Available days are listed below. If you’d like to discuss sponsoring one or more articles, please [drop us a line](#) to discuss. 63 | - id: slots 64 | title: Available slots 65 | component: listing 66 | config: 67 | mods: [summaries] 68 | component: summary 69 | items: 70 | - "@summary--sponsored" 71 | - "@summary--sponsored-taken" 72 | - "@summary--sponsored" 73 | - "@summary--sponsored" 74 | -------------------------------------------------------------------------------- /src/components/common/summary/summary.css: -------------------------------------------------------------------------------- 1 | $summary__author-size: map(sizes, xlarge); 2 | 3 | @keyframes corner-forward { 4 | from { 5 | z-index: map(layers, default); 6 | background-position: -0.5em; 7 | } 8 | 9 | to { 10 | z-index: calc(map(layers, default) + 1); 11 | background-position: -50.5em; 12 | } 13 | } 14 | 15 | @keyframes corner-reverse { 16 | from { 17 | z-index: calc(map(layers, default) + 1); 18 | background-position: -50.5em; 19 | } 20 | 21 | to { 22 | z-index: map(layers, default); 23 | background-position: -0.5em; 24 | } 25 | } 26 | 27 | .c-summary { 28 | display: flex; 29 | flex-direction: column; 30 | flex: 1; 31 | position: relative; 32 | padding: map(spaces, medium) calc(map(spaces, large) - map(spaces, xsmall)); 33 | background-color: white; 34 | box-shadow: 1px 1px 0 $prose-color--shadow; 35 | 36 | @media (-ms-high-contrast: active) { 37 | border: 1px solid; 38 | } 39 | 40 | @media print { 41 | padding-bottom: 0; 42 | height: auto !important; 43 | min-height: 12em; 44 | page-break-inside: avoid; 45 | } 46 | } 47 | 48 | .c-summary__header { 49 | min-height: map(sizes, large); 50 | margin-bottom: map(spaces, medium); 51 | 52 | @media print { 53 | border-top: 1px solid black; 54 | padding-top: map(spaces, medium); 55 | } 56 | } 57 | 58 | .c-summary__title { 59 | @apply --typeset-summary-title; 60 | 61 | padding-right: calc($summary__author-size - map(spaces, medium)); 62 | 63 | a::before { 64 | content: ''; 65 | overflow: hidden; 66 | position: absolute; 67 | top: 0; 68 | right: 0; 69 | bottom: 0; 70 | left: 0; 71 | z-index: map(layers, default); 72 | white-space: nowrap; 73 | text-indent: 200%; 74 | } 75 | 76 | a:focus { 77 | outline: none; 78 | 79 | &::before { 80 | outline: 2px solid $color-focus; 81 | } 82 | } 83 | } 84 | 85 | .c-summary__main { 86 | @apply --typeset-prose; 87 | 88 | max-width: 75ch; 89 | font-size: 0.875rem; 90 | 91 | a, 92 | em { 93 | font-family: map(fonts, family-sans); 94 | font-style: normal; 95 | font-weight: 700; 96 | color: inherit; 97 | } 98 | } 99 | 100 | .c-summary__footer { 101 | @media screen { 102 | margin-top: auto; 103 | padding-top: map(spaces, medium); 104 | } 105 | } 106 | 107 | .c-summary__meta { 108 | @apply --typeset-label; 109 | 110 | color: map(colors, neutral, dark); 111 | 112 | .dt-published { 113 | margin-right: map(spaces, medium); 114 | } 115 | } 116 | 117 | .c-summary__author { 118 | position: absolute; 119 | top: 0; 120 | right: 0; 121 | margin: 0; 122 | pointer-events: none; 123 | 124 | @media print { 125 | display: none; 126 | } 127 | 128 | img { 129 | display: block; 130 | height: $summary__author-size; 131 | width: $summary__author-size; 132 | transition: transform $author-duration ease-out; 133 | 134 | @media (prefers-reduced-motion) { 135 | transition: none; 136 | } 137 | } 138 | 139 | span { 140 | @apply --hidden; 141 | } 142 | } 143 | 144 | .c-summary__author-url { 145 | display: block; 146 | overflow: hidden; 147 | 148 | &::before { 149 | content: ''; 150 | display: block; 151 | position: absolute; 152 | top: 0; 153 | right: 0; 154 | z-index: map(layers, default); 155 | height: $summary__author-size; 156 | width: $summary__author-size; 157 | background-image: linear-gradient(to top right, white, white 50%, transparent 50%, transparent); 158 | } 159 | 160 | &::after { 161 | content: ''; 162 | display: block; 163 | position: absolute; 164 | top: 0; 165 | right: 0; 166 | height: $summary__author-size; 167 | width: $summary__author-size; 168 | background: inline('corner.svg') -0.5em 0 no-repeat; 169 | background-size: size('corner.svg'); 170 | } 171 | 172 | .c-summary:hover &, 173 | &:focus { 174 | img { 175 | transform: scale(1.2); 176 | transform-origin: bottom left; 177 | } 178 | 179 | &::after { 180 | z-index: calc(map(layers, default) + 1); 181 | animation: corner-forward 0.15s steps(10); 182 | animation-fill-mode: forwards; 183 | } 184 | } 185 | 186 | &:focus { 187 | box-shadow: 0 0 2px 0 $color-focus; 188 | } 189 | } 190 | 191 | .c-summary--countdown { 192 | .c-summary__header { 193 | padding-left: calc(map(spaces, large) + map(spaces, xsmall)); 194 | } 195 | 196 | .c-summary__footer { 197 | padding-top: 0; 198 | } 199 | 200 | .dt-published { 201 | @apply --typeset-summary-title; 202 | 203 | font-weight: 400; 204 | position: absolute; 205 | top: map(spaces, medium); 206 | left: calc(map(spaces, medium) + map(spaces, xsmall)); 207 | z-index: 0; 208 | 209 | span { 210 | @apply --hidden; 211 | } 212 | 213 | @media print { 214 | top: 2rem; 215 | } 216 | } 217 | } 218 | 219 | .c-summary--taken { 220 | .c-summary__title { 221 | opacity: 0.66; 222 | text-decoration: line-through; 223 | } 224 | 225 | background-image: repeating-linear-gradient(-45deg, transparent, transparent 4px, color(map(colors, neutral, lightest) a(75%)) 4px, color(map(colors, neutral, lightest) a(75%)) 8px); 226 | } 227 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------- 2 | // Dependencies 3 | // -------------------------------------------------------- 4 | 5 | // Utils 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | const del = require('del'); 9 | const gulp = require('gulp'); 10 | const util = require('gulp-util'); 11 | 12 | // CSS 13 | const postcss = require('gulp-postcss'); 14 | const apply = require('postcss-apply'); 15 | const assets = require('postcss-assets'); 16 | const autoprefixer = require('autoprefixer'); 17 | const calc = require('postcss-calc'); 18 | const colorFunction = require('postcss-color-function'); 19 | const customMedia = require('postcss-custom-media'); 20 | const importer = require('postcss-easy-import'); 21 | const mapper = require('postcss-map'); 22 | const mediaMinMax = require('postcss-media-minmax'); 23 | const nano = require('cssnano'); 24 | const nested = require('postcss-nested'); 25 | const responsiveType = require('postcss-responsive-type'); 26 | const simpleVars = require('postcss-simple-vars'); 27 | 28 | // Misc 29 | const ghPages = require('gulp-gh-pages'); 30 | const imagemin = require('gulp-imagemin'); 31 | const sourcemaps = require('gulp-sourcemaps'); 32 | 33 | // JavaScript 34 | const rollup = require('./etc/gulp/rollup'); 35 | 36 | // Fractal 37 | const pkg = require('./package.json'); 38 | const fractal = require('./fractal.config.js'); 39 | 40 | const logger = fractal.cli.console; 41 | 42 | // -------------------------------------------------------- 43 | // Configuration 44 | // -------------------------------------------------------- 45 | 46 | // Paths 47 | const paths = { 48 | build: path.join(__dirname, 'www'), 49 | dest: path.join(__dirname, 'tmp'), 50 | src: path.join(__dirname, 'src'), 51 | modules: path.join(__dirname, 'node_modules') 52 | }; 53 | 54 | // PostCSS plugins 55 | const processors = [ 56 | importer({ 57 | glob: true 58 | }), 59 | mapper({ 60 | maps: [ 61 | `${paths.src}/tokens/borders.json`, 62 | `${paths.src}/tokens/breakpoints.json`, 63 | `${paths.src}/tokens/colors.json`, 64 | `${paths.src}/tokens/fonts.json`, 65 | `${paths.src}/tokens/layers.json`, 66 | `${paths.src}/tokens/sizes.json`, 67 | `${paths.src}/tokens/spaces.json` 68 | ] 69 | }), 70 | assets({ 71 | loadPaths: [`${paths.src}/assets/vectors`] 72 | }), 73 | simpleVars, 74 | apply, 75 | calc, 76 | customMedia, 77 | colorFunction, 78 | mediaMinMax, 79 | nested, 80 | responsiveType, 81 | autoprefixer, 82 | nano 83 | ]; 84 | 85 | // -------------------------------------------------------- 86 | // Tasks 87 | // -------------------------------------------------------- 88 | 89 | // Build static site 90 | function build() { 91 | const builder = fractal.web.builder(); 92 | 93 | builder.on('progress', (completed, total) => logger.update(`Exported ${completed} of ${total} items`, 'info')); 94 | builder.on('error', error => logger.error(error.message)); 95 | 96 | return builder.build().then(() => { 97 | logger.success('Fractal build completed!'); 98 | }); 99 | } 100 | 101 | // Serve dynamic site 102 | function serve() { 103 | const server = fractal.web.server({ 104 | sync: true, 105 | syncOptions: { 106 | https: true 107 | } 108 | }); 109 | 110 | server.on('error', error => logger.error(error.message)); 111 | 112 | return server.start().then(() => { 113 | logger.success(`Fractal server is now running at ${server.url}`); 114 | }); 115 | } 116 | 117 | // Clean 118 | function clean() { 119 | return del(`${paths.dest}/assets/`); 120 | } 121 | 122 | // Deploy to GitHub pages 123 | function deploy() { 124 | // Generate CNAME file from `homepage` value in package.json 125 | const cname = pkg.homepage.replace(/.*?:\/\//g, ''); 126 | fs.writeFileSync(`${paths.build}/CNAME`, cname); 127 | 128 | // Push contents of build folder to `gh-pages` branch 129 | return gulp.src(`${paths.build}/**/*`) 130 | .pipe(ghPages({ 131 | force: true 132 | })); 133 | } 134 | 135 | // Meta 136 | function meta() { 137 | return gulp.src(`${paths.src}/*.{txt,json}`) 138 | .pipe(gulp.dest(paths.dest)); 139 | } 140 | 141 | // Icons 142 | function icons() { 143 | return gulp.src(`${paths.src}/assets/icons/**/*`) 144 | .pipe(imagemin()) 145 | .pipe(gulp.dest(`${paths.dest}/assets/icons`)); 146 | } 147 | 148 | // Images 149 | function images() { 150 | return gulp.src(`${paths.src}/assets/images/**/*`) 151 | .pipe(imagemin({ 152 | progressive: true 153 | })) 154 | .pipe(gulp.dest(`${paths.dest}/assets/images`)); 155 | } 156 | 157 | // Vectors 158 | function vectors() { 159 | return gulp.src(`${paths.src}/assets/vectors/**/*`) 160 | .pipe(gulp.dest(`${paths.dest}/assets/vectors`)); 161 | } 162 | 163 | // Scripts 164 | function scripts(callback) { 165 | const modules = [{ 166 | input: `${paths.src}/assets/scripts/app.js`, 167 | file: `${paths.dest}/assets/scripts/app.js`, 168 | name: 'app' 169 | }, { 170 | input: `${paths.src}/assets/scripts/prism.js`, 171 | file: `${paths.dest}/assets/scripts/prism.js`, 172 | name: 'prism' 173 | }]; 174 | 175 | rollup(modules, util, callback); 176 | } 177 | 178 | // Styles 179 | function styles() { 180 | return gulp.src(`${paths.src}/assets/styles/*.css`) 181 | .pipe(sourcemaps.init()) 182 | .pipe(postcss(processors)) 183 | .pipe(sourcemaps.write('./')) 184 | .pipe(gulp.dest(`${paths.dest}/assets/styles`)); 185 | } 186 | 187 | // Watch 188 | function watch() { 189 | serve(); 190 | gulp.watch(`${paths.src}/assets/icons`, icons); 191 | gulp.watch(`${paths.src}/assets/images`, images); 192 | gulp.watch(`${paths.src}/assets/vectors`, images); 193 | gulp.watch(`${paths.src}/**/*.js`, scripts); 194 | gulp.watch(`${paths.src}/**/*.css`, styles); 195 | } 196 | 197 | // Task sets 198 | const compile = gulp.series(clean, gulp.parallel(meta, icons, images, vectors, scripts, styles)); 199 | 200 | gulp.task('start', gulp.series(compile, serve)); 201 | gulp.task('build', gulp.series(compile, build)); 202 | gulp.task('dev', gulp.series(compile, watch)); 203 | gulp.task('publish', gulp.series(build, deploy)); 204 | -------------------------------------------------------------------------------- /src/components/scopes/prose/prose.css: -------------------------------------------------------------------------------- 1 | .s-prose { 2 | @apply --typeset-prose; 3 | 4 | max-width: 75ch; 5 | 6 | > * { 7 | margin-bottom: 0.75em; 8 | } 9 | 10 | /* Text */ 11 | strong, 12 | dt { 13 | font-family: map(fonts, family-sans); 14 | } 15 | 16 | pre, 17 | code, 18 | samp { 19 | font-variant: lining-nums tabular-nums; 20 | hanging-punctuation: none; 21 | hyphens: none; 22 | } 23 | 24 | pre, 25 | code { 26 | margin: -0.125em 0.125em; 27 | padding: 0.0625em 0.125em; 28 | background-color: color(white a(50%)); 29 | } 30 | 31 | /* Grouping content */ 32 | pre { 33 | margin: 1em -0.5em; 34 | padding: 0.25em 0.5em; 35 | 36 | code { 37 | margin: 0; 38 | background-color: transparent; 39 | padding: 0; 40 | } 41 | } 42 | 43 | > ol, 44 | > ul { 45 | list-style-position: inside; 46 | 47 | @media (--from-large-screen) { 48 | list-style-position: outside; 49 | } 50 | } 51 | 52 | li { 53 | margin-bottom: 0.25em; 54 | } 55 | 56 | > ul > li { 57 | list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='0.75em' height='0.75em' viewBox='0 0 2 2'%3E%3Cpolygon fill='%233F3F46' fill-opacity='.75' points='0 1 1 0 2 1 1 2'/%3E%3C/svg%3E "); 58 | } 59 | 60 | ul li { 61 | list-style-type: circle; 62 | } 63 | 64 | ol li { 65 | list-style-type: decimal; 66 | } 67 | 68 | li li, 69 | dd { 70 | margin-left: 2em; 71 | } 72 | 73 | dt { 74 | font-weight: 700; 75 | } 76 | 77 | .caps { 78 | font-variant: lining-nums small-caps; 79 | } 80 | 81 | .caption { 82 | @apply --typeset-caption; 83 | 84 | opacity: 0.9; 85 | } 86 | 87 | .lede { 88 | @apply --typeset-lede; 89 | 90 | position: relative; 91 | top: calc(map(spaces, xsmall) * -1); 92 | } 93 | } 94 | 95 | .s-prose--article { 96 | @apply --typescale-prose; 97 | 98 | > *:first-child { 99 | margin-top: 0; 100 | } 101 | 102 | pre, 103 | figure, 104 | table { 105 | margin-top: map(spaces, large); 106 | margin-bottom: map(spaces, large); 107 | } 108 | 109 | /* Sections */ 110 | h2, 111 | h3, 112 | h4, 113 | h5 { 114 | @apply --typeset-heading; 115 | 116 | margin-top: map(spaces, large); 117 | margin-bottom: map(spaces, small); 118 | } 119 | 120 | h2 { 121 | margin-top: map(spaces, xlarge); 122 | font-size: 1.5em; 123 | font-weight: 700; 124 | } 125 | 126 | h2, 127 | h3 { 128 | color: $color-day--dark; 129 | color: var(--color-day--dark, $color-day--dark); 130 | } 131 | 132 | h3 { 133 | font-size: 1.25em; 134 | opacity: 0.9; 135 | } 136 | 137 | h2 + h3 { 138 | margin-top: 0; 139 | } 140 | 141 | h4 { 142 | font-size: 1.125em; 143 | } 144 | 145 | h5 { 146 | font-size: 0.875em; 147 | letter-spacing: 0.05em; 148 | text-transform: uppercase; 149 | opacity: 0.9; 150 | } 151 | 152 | hr { 153 | text-align: center; 154 | margin: map(spaces, large) 0 map(spaces, xlarge); 155 | 156 | &::after { 157 | content: '\25C6 \25C6 \25C6'; 158 | height: 0; 159 | font-family: sans-serif; 160 | letter-spacing: map(spaces, medium); 161 | color: color($color-text a(25%)); 162 | } 163 | } 164 | 165 | /* Grouping content */ 166 | blockquote { 167 | border-left: 0.25rem solid $prose-color--rule; 168 | padding-left: map(spaces, medium); 169 | margin-bottom: map(spaces, large); 170 | 171 | > p, 172 | > ul, 173 | > ol { 174 | margin-bottom: 0.75em; 175 | font-size: 1.125em; 176 | line-height: 1.25; 177 | list-style-position: inside; 178 | } 179 | 180 | > footer, 181 | > cite { /* ! Legacy */ 182 | @apply --typeset-caption; 183 | } 184 | } 185 | 186 | figure, 187 | p.image { /* ! Legacy */ 188 | margin-left: calc(map(spaces, large) * -1); 189 | margin-right: calc(map(spaces, large) * -1); 190 | max-width: none; 191 | 192 | @media (--from-large-screen) { 193 | margin-left: 0; 194 | margin-right: 0; 195 | } 196 | 197 | @media (--from-max-screen) { 198 | margin-right: -20%; 199 | } 200 | } 201 | 202 | figcaption, 203 | p.image span.caption { /* ! Legacy */ 204 | @apply --typeset-caption; 205 | 206 | display: block; 207 | border-bottom: 1px solid $prose-color--rule; 208 | margin-right: map(spaces, large); 209 | margin-left: map(spaces, large); 210 | padding: map(spaces, medium) 0; 211 | 212 | @media (--from-large-screen) { 213 | margin: 0; 214 | } 215 | } 216 | 217 | /* Embedded content */ 218 | img, 219 | video { 220 | display: block; 221 | max-width: 100%; 222 | } 223 | 224 | /* Key press within user input sequence. */ 225 | kbd > kbd { 226 | font-size: 90%; 227 | background-color: white; 228 | margin: 0 0.125rem; 229 | border: 1px solid $prose-color--shadow; 230 | border-radius: map(borders, radius-default); 231 | padding: map(spaces, xsmall) map(spaces, small); 232 | box-shadow: 0 1px 0 $prose-color--shadow; 233 | } 234 | 235 | /* Menu selection within user input sequence. */ 236 | kbd > samp { 237 | font-family: inherit; 238 | font-size: 1em; 239 | background-color: white; 240 | margin: -0.375rem -0.625rem; /* -6px -10px */ 241 | border-radius: 0; 242 | padding: 0.375rem 0.625rem; /* 6px 10px */ 243 | box-shadow: none; 244 | } 245 | 246 | /* Links */ 247 | a { 248 | text-decoration: underline; 249 | text-decoration-color: var(--color-day--dark-alpha, $color-day--dark-alpha); 250 | 251 | &:hover { 252 | text-decoration-color: var(--color-day, $color-day); 253 | } 254 | 255 | @media print { 256 | border-bottom: 1px dotted #999; 257 | 258 | &::after { 259 | font-size: 0.75em; 260 | content: ' [' attr(href) ']'; 261 | } 262 | } 263 | } 264 | 265 | /* Tables */ 266 | table { 267 | @apply --typeset-ui; 268 | 269 | ol, 270 | ul { 271 | margin-left: map(spaces, large); 272 | } 273 | } 274 | 275 | caption { 276 | @apply --typeset-caption; 277 | 278 | color: map(colors, neutral, base); 279 | caption-side: bottom; 280 | text-align: left; 281 | margin: map(spaces, medium) 0; 282 | } 283 | 284 | td, 285 | th { 286 | border-bottom: 1px solid $prose-color--rule; 287 | padding: map(spaces, small) map(spaces, large) map(spaces, small) map(spaces, small); 288 | hyphens: none; 289 | } 290 | 291 | th { 292 | line-height: 1.25; 293 | background-color: $prose-color--background; 294 | 295 | code { 296 | background: none; 297 | } 298 | } 299 | 300 | .pull-right { 301 | @media (--from-large-screen) { 302 | width: 40%; 303 | float: right; 304 | margin-top: 0; 305 | margin-left: map(spaces, large); 306 | } 307 | } 308 | 309 | .footnotes { 310 | @apply --typeset-caption; 311 | 312 | li { 313 | margin-bottom: map(spaces, small); 314 | } 315 | 316 | a:last-of-type { 317 | /* Chrome doesn’t seem to use fallback glyph in font stack */ 318 | font-family: sans-serif; 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/components/templates/index-template/index-template.config.yaml: -------------------------------------------------------------------------------- 1 | default: topics 2 | variants: 3 | - name: topics 4 | context: 5 | title: "Topics" 6 | section: topics 7 | sections: 8 | - id: business 9 | title: "Business" 10 | mods: [topic] 11 | icon: topic-business 12 | content: | 13 | Where there's muck, there are clients, deliverables and contracts. Occasionally, there's brass. Here are articles on developing and improving relationships with clients; writing contracts and presenting your work; and encouraging success and avoiding failure. 14 | 15 | 16 ways to improve your business 16 | - id: code 17 | title: "Code" 18 | mods: [topic] 19 | icon: topic-code 20 | content: | 21 | Poetry? Not likely (though [Dan Cederholm wrote some doggerel](/2006/gravity-defying-page-corners/) back in 2006). Here are articles on hypertext markup language, its attributes and other minutiae; crafty ~~hacks~~ tips for your cascading style sheets; JavaScript legerdemain; and tinkering with application programming interfaces. 22 | 23 | 45 ways to craft your code 24 | - id: content 25 | title: "Content" 26 | mods: [topic] 27 | icon: topic-content 28 | content: | 29 | If code is our bread, then content is our butter. It's also been king for a while. A buttery smooth king. *[Ed: Mixing metaphors is inelegant, reveals a want of sophistication and causes confusion. Please stop.]* Whatever your preference, you'll find a wealth of useful information in our articles on copy, including its micro and macro aspects; content planning, strategy and management for sites big and small; as well as ideas on generating it. 30 | 31 | 23 ways to sharpen your content 32 | - id: design 33 | title: "Design" 34 | mods: [topic] 35 | icon: topic-design 36 | content: | 37 | Coloured crayons for felt-tip fairies. *[Ed: What has got into you? Sort this out.]* Alright, alright. Sheesh! There's much more to web design than mere surface. Markup minutiae and clever CSS techniques; art direction and photography; inspiration and theoretical application; layout and typography; systems of design and modularization; even music gets a look in. 38 | 39 | 67 ways to enhance your design 40 | - id: process 41 | title: "Process" 42 | mods: [topic] 43 | icon: topic-process 44 | content: | 45 | The great work of web, where it all comes together. Except when it doesn't and we need to re-examine the distance between how things are done and how they could and should be done. Advent articles revealed here cover managing projects, both business and personal; smoothing the flow of work; thoughts and techniques for working smarter and being better; and getting the most out of what we do. 46 | 47 | 15 ways to hone your process 48 | - id: ux 49 | title: "UX" 50 | mods: [topic] 51 | icon: topic-ux 52 | content: | 53 | Everything we make is made to be used: read, heard, pushed, swiped, clicked, followed, interacted with. Hard edges must be smoothed and blunt instruments sharpened. Users carry the web we've made with them and it must be ready to work on whatever they have to hand. From prototypes to product, from wireframe to web app, and all the research, analysis and testing in between, there are at least twenty-four ways. 54 | 55 | 34 ways to enhance your UX 56 | - name: archives 57 | context: 58 | title: "Archives" 59 | section: archives 60 | sections: 61 | - id: 2015 62 | title: "2015" 63 | content: | 64 | Ports and protocols were the name of the game, with swathes of the web switching to HTTPS connections. HTTP2 also started to gain adoption, and in doing so turned all we had learned about performance optimisation on its head. 24 ways saw increasing exploration of animation on the web, as well as renewed interest in accessibility, style guides and progressive enhancement. 65 | 66 | 24 ways to impress your friends in 2015 67 | - id: 2014 68 | title: "2014" 69 | content: | 70 | The web turned twenty-five and showed no sign of settling down in semi-detached suburbia. In October, HTML5 was released as a W3C Recommendation. Back in May, 24 ways was very excited and grateful to win the net award for best collaborative project – a huge thank you to all our authors, readers and supporters! 71 | 72 | 24 ways to impress your friends in 2014 73 | - id: 2013 74 | title: "2013" 75 | content: | 76 | Scourge of browser vendors everywhere, WaSP buzzed its last in March. Dave Shea's CSS Zen Garden celebrated its tenth anniversary in May, and Google Glass was released. Ever broad in its interests, 24 ways tamed Grunt, URLs and GitHub Pages, encouraged readers to write and publish books, and leavened all that with goodies on project management, web typography and SVG. 77 | 78 | 24 ways to impress your friends in 2013 79 | - id: 2012 80 | title: "2012" 81 | content: | 82 | During the same month that HTML5 was designated a Candidate Recommendation by the W3C, 24 ways covered issues of performance as part of responsive web design, CSS and preprocessing, responsive images (again) and design systems. 83 | 84 | 24 ways to impress your friends in 2012 85 | - id: 2011 86 | title: "2011" 87 | content: | 88 | In October, Steve Jobs died. The thorniest part of responsive web design, and an arena for many competing and dissenting voices was images. 24 ways tackled that and many other issues head on: conditional loading; front-end style guides; icon fonts; and the importance of side projects. 89 | 90 | 24 ways to impress your friends in 2011 91 | - id: 2010 92 | title: "2010" 93 | content: | 94 | In April, the iPad; in May, Ethan Marcotte's "Responsive Web Design" was published by A List Apart, and A Book Apart published HTML5 for Web Designers by Jeremy Keith; and then in June, the iPhone 4's Retina screen changed the web development landscape. 24 ways sprinkled its Christmas pudding with CSS3, including animations and transforms, a little light content strategy, and some thoughts about the web designer of tomorrow. 95 | 96 | 24 ways to impress your friends in 2010 97 | - id: 2009 98 | title: "2009" 99 | content: | 100 | A year when books were winning (Five Simple Steps published A Practical Guide to Designing for the Web by Mark Boulton and Designing with Web Standards by Jeffrey Zeldman and Ethan Marcotte reached its third edition) and the web was losing (Yahoo! closed Geocities). Significant progress was made with web fonts and HTML5, and 24 ways delivered the Christmas gifts again. 101 | 102 | 24 ways to impress your friends in 2009 103 | - id: 2008 104 | title: "2008" 105 | content: | 106 | This year saw Apple's App Store open, and the release of Android 1.0 and Google Chrome 1.0. Taking all that in its stride, 24 ways brought its seasonal perspective to bear on business, with articles on project management, the path from design to development, how to charm clients, and killer contracts. Also, a first look at modular layout systems. Pulse, meet finger. 107 | 108 | 24 ways to impress your friends in 2008 109 | - id: 2007 110 | title: "2007" 111 | content: | 112 | Apple launched the iPhone in June; Amazon released the Kindle in November — a big year. At three, 24 ways was as diverse as ever, taking a detailed look at font stacks, website performance, working with clients and markup. 113 | 114 | 24 ways to impress your friends in 2007 115 | - id: 2006 116 | title: "2006" 117 | content: | 118 | In March, the first tweets were tweeted; in August, jQuery 1.0 appeared. In its second year, 24 ways wrote responsible JavaScript and hinted at a mobile web, although mobile phones didn't yet have proper browsers. Using CSS3 in client work was still a pipedream. And in October, IE7 was officially released by Microsoft — no words. 119 | 120 | 24 ways to impress your friends in 2006 121 | - id: 2005 122 | title: "2005" 123 | content: | 124 | It all started here, in the heady days of Web 2.0. Ajax was the first new browser technology we'd seen in years, and combined with a new breed of libraries such as Prototype, it kick-started the JavaScript renaissance. 125 | 126 | 24 ways to impress your friends in 2005 127 | - name: authors 128 | context: 129 | title: "Authors" 130 | section: authors 131 | sections: 132 | - id: a 133 | title: "A" 134 | component: listing--authors 135 | config: 136 | mods: [authors] 137 | component: author 138 | items: 139 | - author: John Allsopp 140 | - author: Rachel Andrew 141 | - author: Paul Annett 142 | - id: b 143 | title: "B" 144 | component: listing--authors 145 | config: 146 | mods: [authors] 147 | component: author 148 | items: 149 | - author: Ashley Baxter 150 | - author: Darren Beale 151 | - author: Gavin Bell 152 | - author: Frances Berriman 153 | - author: Kimberly Blessing 154 | - author: Paul Boag 155 | - author: Ben Bodien 156 | - author: Jina Bolton 157 | - author: Mark Boulton 158 | - author: Cennydd Bowles 159 | - author: Ross Bruniges 160 | - author: Andy Budd 161 | - author: Heather Burns 162 | - id: c 163 | title: "C" 164 | component: listing--authors 165 | config: 166 | mods: [authors] 167 | component: author 168 | items: 169 | - author: Dan Cederholm 170 | - author: Andy Clarke 171 | - author: Geri Coady 172 | - author: Dave Collins 173 | - author: Simon Collison 174 | - author: Rebecca Cottrell 175 | - author: Chris Coyier 176 | - author: Matt Curry 177 | -------------------------------------------------------------------------------- /src/components/scopes/prose/prose.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | content: | 3 |Rachel Andrew is a Director of edgeofmyseat.com, a UK web development consultancy and creators of the small content management system, Perch. She is the author of a number of books, and is a regular columnist for A List Apart.
4 | 5 | She curates a popular [email newsletter on CSS Layout](http://csslayout.news/), and will be launching a [CSS Layout online workshop](https://thecssworkshop.com/) in early 2016. 6 | 7 | When not writing about business and technology on her blog at [rachelandrew.co.uk](https://rachelandrew.co.uk) or [speaking at conferences](http://lanyrd.com/profile/rachelandrew/), you will usually find Rachel running up and down one of the giant hills in Bristol. 8 | 9 | Photo: [James Duncan Davidson](http://duncandavidson.com/) 10 | variants: 11 | - name: article 12 | context: 13 | mods: [article] 14 | content: | 15 |Video is a bigger part of the web experience than ever before. With native browser support for HTML5 video elements freeing us from the tyranny of plugins, and the availability of faster internet connections to the workplace, home and mobile networks, it’s now pretty straightforward to publish video in a way that can be consumed in all sorts of ways on all sorts of different web devices.
16 | 17 | I recently worked on a project where the client had shot some dedicated video shorts to publish on their site. They also had some five-second motion graphics produced to top and tail the videos with context and branding. This pretty common requirement is a great idea on the web, where a user might land at your video having followed a link and be viewing a page without much context. 18 | 19 |20 |23 | 24 | Known as _bumpers_, these short introduction clips help brand a video and make it look a lot more professional. 25 | 26 |[I]t appears probable that the progenitors of man, either the males or females or both sexes, before acquiring the power of expressing their mutual love in articulate language, endeavoured to charm each other with musical notes and rhythm.
21 | 22 |
28 |
34 |
35 |
42 |
43 |
44 |
51 |
52 |
53 |
54 |
118 |
119 | So what can we do? What can we control?
120 |
121 | ***
122 |
123 | Those of us with particularly long memories might recall a time before CSS, when we'd have to use JavaScript to perform image rollovers. As CSS background images weren't a practical reality, we would use lots of `
127 |