├── 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 | {{ content | markdown | safe }} 3 |
4 | -------------------------------------------------------------------------------- /src/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /perch 3 | 4 | User-agent: Mediapartners-Google* 5 | Disallow: 6 | -------------------------------------------------------------------------------- /src/assets/images/logo-perch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/24ways/frontend/HEAD/src/assets/images/logo-perch.png -------------------------------------------------------------------------------- /src/components/common/continue/continue.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | label: More articles by Drew 3 | href: /authors/drewmclellan/ 4 | -------------------------------------------------------------------------------- /src/tokens/layers.json: -------------------------------------------------------------------------------- 1 | { 2 | "modal": 9000, 3 | "backdrop": 8000, 4 | "overlay": 1000, 5 | "default": 1, 6 | "underlay": -1 7 | } 8 | -------------------------------------------------------------------------------- /src/components/common/avatar/avatar.html: -------------------------------------------------------------------------------- 1 | {{ alt }} 2 | -------------------------------------------------------------------------------- /src/components/common/search-form/search-form.html: -------------------------------------------------------------------------------- 1 |
2 | {%- include "@field--combined" -%} 3 |
4 | -------------------------------------------------------------------------------- /src/assets/scripts/prism.js: -------------------------------------------------------------------------------- 1 | // Import modules 2 | import prism from 'prismjs'; 3 | 4 | // Run 5 | prism; // eslint-disable-line no-unused-expressions 6 | -------------------------------------------------------------------------------- /src/components/templates/article-template/article-template.html: -------------------------------------------------------------------------------- 1 | {% extends "@page" %} 2 | {% block main %} 3 | {% include "@article" %} 4 | {% endblock %} 5 | -------------------------------------------------------------------------------- /src/tokens/sizes.json: -------------------------------------------------------------------------------- 1 | { 2 | "xsmall": "0.25rem", 3 | "small": "0.75rem", 4 | "medium": "1.5rem", 5 | "large": "3rem", 6 | "xlarge": "4.5rem" 7 | } 8 | -------------------------------------------------------------------------------- /src/components/common/avatar/avatar.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | src: https://cloud.24ways.org/authors/drewmclellan280.jpg 3 | alt: Drew McLellan 4 | size: 280 5 | -------------------------------------------------------------------------------- /src/components/scopes/prose/prose.html: -------------------------------------------------------------------------------- 1 |
2 | {{ content | markdown | safe }} 3 |
4 | -------------------------------------------------------------------------------- /src/components/common/article/article.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | title: "Starting Your HTML5 Project on the Right Foot (and Keeping It There)" 3 | author: Drew McLellan 4 | -------------------------------------------------------------------------------- /src/tokens/spaces.json: -------------------------------------------------------------------------------- 1 | { 2 | "xsmall": "0.25rem", 3 | "small": "0.5rem", 4 | "medium": "1rem", 5 | "large": "1.5rem", 6 | "xlarge": "3rem", 7 | "xxlarge": "4.5rem" 8 | } 9 | -------------------------------------------------------------------------------- /src/components/common/meta/meta.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/assets/scripts/app.js: -------------------------------------------------------------------------------- 1 | // Import modules 2 | import {loadWebfonts} from './modules/webfont-loader'; 3 | import {menu} from './modules/menu'; 4 | 5 | // Run 6 | menu(); 7 | loadWebfonts(); 8 | -------------------------------------------------------------------------------- /src/components/scopes/embed/embed.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/components/scopes/note/note.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | content: | 3 | **A note from the editors:** While brilliant for its time, this article no longer reflects modern best practices. 4 | -------------------------------------------------------------------------------- /src/assets/vectors/next.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/assets/vectors/prev.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/tokens/breakpoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "min": "280px", 3 | "xsmall": "360px", 4 | "small": "480px", 5 | "medium": "504px", 6 | "large": "800px", 7 | "xlarge": "960px", 8 | "max": "1280px" 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /src/components/common/meta/meta.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | items: 3 | - "" 4 | - "Published in [Code](/topics/code/)" 5 | - "[8 comments](#comments)" 6 | -------------------------------------------------------------------------------- /src/tokens/borders.json: -------------------------------------------------------------------------------- 1 | { 2 | "width-thin": "1px", 3 | "width-thick": "2px", 4 | "width-thicker": "4px", 5 | "width-thickest": "8px", 6 | "radius-small": "0.125rem", 7 | "radius-default": "0.25rem", 8 | "radius-circle": "50%" 9 | } 10 | -------------------------------------------------------------------------------- /src/components/templates/_page.html: -------------------------------------------------------------------------------- 1 | {% include "@banner" %} 2 | {% include "@menu" %} 3 | 4 |
5 | {% block main %} 6 | {% endblock %} 7 |
8 | 9 | {% include "@traverse-nav" %} 10 | {% include "@contentinfo" %} 11 | -------------------------------------------------------------------------------- /src/assets/vectors/clip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/common/field/field.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |

5 | -------------------------------------------------------------------------------- /src/components/common/listing/listing.html: -------------------------------------------------------------------------------- 1 |
    2 | {%- for item in items -%} 3 |
  1. 4 | {%- render "@" + component, item -%} 5 |
  2. 6 | {%- endfor -%} 7 |
8 | -------------------------------------------------------------------------------- /src/components/scopes/embed/embed.config.yaml: -------------------------------------------------------------------------------- 1 | collated: true 2 | context: 3 | src: https://www.youtube-nocookie.com/embed/yyPFT3Ajkzc 4 | variants: 5 | - name: widescreen 6 | context: 7 | mods: [widescreen] 8 | src: https://www.youtube-nocookie.com/embed/rGkQDrUcX18 9 | -------------------------------------------------------------------------------- /src/components/common/field/field--textarea.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |

5 | -------------------------------------------------------------------------------- /src/components/templates/index-template/index-template.html: -------------------------------------------------------------------------------- 1 | {% extends "@page" %} 2 | {% block main %} 3 |
4 | {% include "@preface" %} 5 | {%- for section in sections -%} 6 | {% render "@section", section %} 7 | {%- endfor -%} 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /src/components/templates/content-template/content-template.html: -------------------------------------------------------------------------------- 1 | {% extends "@page" %} 2 | {% block main %} 3 |
4 | {% include "@preface" %} 5 | {%- for section in sections -%} 6 | {% render "@section", section %} 7 | {%- endfor -%} 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /src/components/common/promo/promo.html: -------------------------------------------------------------------------------- 1 | 2 | {{ img.alt }} 3 |

{{ message | markdownInline | safe }}

4 |

{{ url }}

5 |
6 | -------------------------------------------------------------------------------- /src/components/global/banner/banner.html: -------------------------------------------------------------------------------- 1 |
2 | Skip to content 3 |

4 | 24 ways to impress your friends 5 |

6 |
7 | -------------------------------------------------------------------------------- /src/assets/styles/app.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | Application styles 3 | ===================================================== */ 4 | 5 | @import 'config/**/*.css'; 6 | @import 'helpers/**/*.css'; 7 | @import 'base/**/*.css'; 8 | @import '../../components/**/*.css'; 9 | -------------------------------------------------------------------------------- /src/components/global/traverse-nav/traverse-nav.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | traverse: 3 | type: article 4 | prev: 5 | url: /2015/how-tabs-should-work/ 6 | title: "How Tabs Should Work" 7 | next: 8 | url: /2015/solve-the-hard-problems/ 9 | title: "Solve the Hard Problems" 10 | -------------------------------------------------------------------------------- /src/components/templates/listing-template/listing-template.html: -------------------------------------------------------------------------------- 1 | {% extends "@page" %} 2 | {% block main %} 3 |
4 | {% include "@preface" %} 5 | {% render "@listing--inset", config %} 6 | {% if results %} 7 | {% render "@button--more" %} 8 | {% endif %} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /src/components/common/promo/promo.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | href: https://grabaperch.com/?ref=24w01 3 | img: 4 | src: /assets/images/logo-perch.png 5 | alt: Perch - A really little CMS 6 | message: The easiest way to publish **fast, flexible HTML5 websites** your clients will love. 7 | url: grabaperch.com 8 | -------------------------------------------------------------------------------- /src/components/common/author/author.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ author }} 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 | 2 | 3 | 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 |
2 | {% render "@avatar", avatar %} 3 |
4 |

“{{ quote | markdownInline }}”

5 |
6 |
7 | — {{ author }} 8 |
9 |
10 | -------------------------------------------------------------------------------- /src/components/global/topics-nav/topics-nav.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | topics: 3 | - title: Business 4 | url: /topics/business/ 5 | - title: Code 6 | url: /topics/code/ 7 | - title: Content 8 | url: /topics/content/ 9 | - title: Design 10 | url: /topics/design/ 11 | - title: Process 12 | url: /topics/process/ 13 | - title: UX 14 | url: /topics/ux/ 15 | -------------------------------------------------------------------------------- /src/components/scopes/embed/embed.css: -------------------------------------------------------------------------------- 1 | .s-embed { 2 | position: relative; 3 | overflow: hidden; 4 | height: 0; 5 | max-width: 100%; 6 | padding-bottom: 75%; /* 4:3 */ 7 | 8 | iframe { 9 | position: absolute; 10 | top: 0; 11 | left: 0; 12 | height: 100%; 13 | width: 100%; 14 | } 15 | } 16 | 17 | .s-embed--widescreen { 18 | padding-bottom: 56.25%; /* 16:9 */ 19 | } 20 | -------------------------------------------------------------------------------- /src/components/og-image/og-image.config.yaml: -------------------------------------------------------------------------------- 1 | title: Open Graph sharing image 2 | label: OG Image 3 | order: 1 4 | context: 5 | quote: "There's no prize for solving problems you don't have yet, and heading further into the desert in search of water is a survival tactic, not an aspiration." 6 | author: Drew McLellan 7 | avatar: 8 | size: 220 9 | src: https://cloud.24ways.org/authors/drewmclellan280.jpg 10 | -------------------------------------------------------------------------------- /src/components/common/avatar/avatar.css: -------------------------------------------------------------------------------- 1 | .c-avatar { 2 | display: inline-block; 3 | height: auto; 4 | max-height: 21rem; 5 | max-width: 21rem; 6 | margin: map(spaces, medium); 7 | clip-path: url('/assets/vectors/clip.svg#avatar'); 8 | clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); 9 | shape-outside: polygon(50% 0, 100% 50%, 50% 100%, 0 50%) content-box; 10 | shape-margin: map(spaces, medium); 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/vectors/topic-process.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/common/backdrop/backdrop.css: -------------------------------------------------------------------------------- 1 | .c-backdrop { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | bottom: 0; 6 | left: 0; 7 | z-index: map(layers, backdrop); 8 | transition: opacity $navigation-duration ease-in; 9 | background-color: $backdrop-color; 10 | opacity: 0; 11 | pointer-events: none; 12 | 13 | [data-menu-expanded=true] & { 14 | opacity: 1; 15 | pointer-events: fill; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/global/topics-nav/topics-nav.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/tokens/colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "primary": { 3 | "light": "#f04", 4 | "base": "#c02", 5 | "dark": "#601" 6 | }, 7 | "secondary": { 8 | "light": "#3be", 9 | "base": "#269", 10 | "dark": "#257" 11 | }, 12 | "neutral": { 13 | "lightest": "#efeff0", 14 | "lighter": "#b9b9c0", 15 | "light": "#8c8c9c", 16 | "base": "#778", 17 | "dark": "#636373", 18 | "darker": "#3f3f46", 19 | "darkest": "#1f1f20" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/vectors/topic-business.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/docs/_partials/_palette-sample.md: -------------------------------------------------------------------------------- 1 |
2 | {% for key, value in values -%} 3 |
4 | 5 | 6 | {{ value }} 7 | 8 |
{{ key }}
9 |
10 | {% endfor -%} 11 |
12 | -------------------------------------------------------------------------------- /src/components/global/search/search.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /src/assets/styles/base/_disabled.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | Disabled elements 3 | w3c.github.io/html/disabled-elements.html 4 | ===================================================== */ 5 | 6 | /** 7 | * Use default cursor for disabled elements. 8 | */ 9 | [disabled] { 10 | cursor: default; 11 | opacity: 0.75; 12 | } 13 | 14 | /** 15 | * Add the correct display in IE 10-. 16 | */ 17 | [hidden] { 18 | display: none !important; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/global/contentinfo/contentinfo.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/components/common/continue/continue.css: -------------------------------------------------------------------------------- 1 | .c-continue { 2 | @apply --typeset-ui; 3 | 4 | font-weight: 700; 5 | 6 | &::after { 7 | display: inline-block; 8 | vertical-align: middle; 9 | height: 0.375em; 10 | width: 0.375em; 11 | border: 0.1875em solid; 12 | border-color: currentColor currentColor transparent transparent; 13 | transform: rotate(45deg); 14 | content: ''; 15 | } 16 | } 17 | 18 | .c-continue--ajax { 19 | &::after { 20 | content: '\2026'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/assets/vectors/topic-content.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/assets/vectors/menu.svg: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /src/components/global/menu/menu.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 10 | 13 |
14 | -------------------------------------------------------------------------------- /src/assets/styles/base/_tables.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | Tablular data 3 | w3c.github.io/html/tabular-data.html 4 | ===================================================== */ 5 | 6 | table { 7 | border-collapse: collapse; 8 | border-spacing: 0; 9 | font-variant-numeric: tabular-nums; 10 | } 11 | 12 | td, 13 | th { 14 | text-align: left; 15 | vertical-align: top; 16 | } 17 | 18 | @media print { 19 | thead { 20 | display: table-header-group; 21 | } 22 | 23 | tr, 24 | img { 25 | page-break-inside: avoid; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/common/preface/preface.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | {%- if titleIcon -%} 4 | {% include titleIcon + ".svg" %} 5 | {%- endif -%} 6 | {{ title }} 7 |

8 | {%- if content -%} 9 |
10 |
11 | {%- if avatar -%} 12 | {% render "@avatar", avatar %} 13 | {%- endif -%} 14 | {{ content | markdown | safe }} 15 |
16 |
17 | {%- endif -%} 18 |
19 | -------------------------------------------------------------------------------- /src/components/global/traverse-nav/traverse-nav.html: -------------------------------------------------------------------------------- 1 | {% if traverse %} 2 | 14 | {% endif %} 15 | -------------------------------------------------------------------------------- /src/components/common/listing/listing.config.yaml: -------------------------------------------------------------------------------- 1 | variants: 2 | - name: default 3 | context: 4 | mods: [summaries] 5 | component: summary 6 | items: 7 | - "@summary" 8 | - "@summary" 9 | - "@summary" 10 | - "@summary" 11 | - name: inset 12 | context: 13 | mods: [summaries, inset] 14 | component: summary 15 | items: 16 | - "@summary" 17 | - "@summary" 18 | - "@summary" 19 | - "@summary" 20 | - name: authors 21 | context: 22 | mods: [authors] 23 | component: author 24 | items: 25 | - "@author" 26 | - "@author" 27 | - "@author" 28 | - "@author" 29 | -------------------------------------------------------------------------------- /src/assets/icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/vectors/topic-design.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/global/site-nav/site-nav.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /src/docs/tokens.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | context: { 7 | borders: require(path.join(process.cwd(), 'src/tokens/borders.json')), 8 | breakpoints: require(path.join(process.cwd(), 'src/tokens/breakpoints.json')), 9 | colors: require(path.join(process.cwd(), 'src/tokens/colors.json')), 10 | fonts: require(path.join(process.cwd(), 'src/tokens/fonts.json')), 11 | layers: require(path.join(process.cwd(), 'src/tokens/layers.json')), 12 | sizes: require(path.join(process.cwd(), 'src/tokens/sizes.json')), 13 | spaces: require(path.join(process.cwd(), 'src/tokens/spaces.json')) 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/assets/styles/docs/styleguide.css: -------------------------------------------------------------------------------- 1 | /* Palette container */ 2 | .palette { 3 | margin: 0 -0.25em; 4 | display: flex; 5 | flex-wrap: wrap; 6 | } 7 | 8 | /* Colour swatch */ 9 | .color { 10 | flex: 1 1 4em; 11 | margin: 0 0.25em 1.5em; 12 | 13 | svg { 14 | width: 100%; 15 | height: 5em; 16 | border-radius: map(borders, radius-default); 17 | } 18 | 19 | code { 20 | display: inline-block; 21 | color: $color-text; 22 | } 23 | 24 | text { 25 | font: 0.75em/1em 'Hack', 'Consolas', 'Monaco', monospace; 26 | font-style: normal; 27 | text-shadow: 0 0 2px $color-text; 28 | } 29 | } 30 | 31 | .color--base { 32 | order: -1; 33 | margin-right: 1em; 34 | } 35 | -------------------------------------------------------------------------------- /src/components/common/button/button.css: -------------------------------------------------------------------------------- 1 | .c-button { 2 | @apply --typeset-ui; 3 | 4 | display: inline-block; 5 | padding: map(spaces, medium) map(spaces, large); 6 | background-color: $color-ui-link; 7 | text-align: center; 8 | text-transform: uppercase; 9 | color: white; 10 | 11 | &:hover { 12 | background-color: $color-ui-link--hover; 13 | color: white; 14 | } 15 | 16 | &:active { 17 | background-color: $color-ui-link--active; 18 | color: white; 19 | } 20 | 21 | @media (-ms-high-contrast: active) { 22 | border: 1px solid; 23 | } 24 | 25 | @media print { 26 | display: none; 27 | } 28 | } 29 | 30 | .c-button--more { 31 | display: block; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/scopes/syntax/syntax.html: -------------------------------------------------------------------------------- 1 | {% set syntax %} 2 | ```html 3 | 4 | ``` 5 | 6 | ```css 7 | selector { 8 | property: value; 9 | } 10 | ``` 11 | 12 | ```js 13 | import express from 'express'; 14 | import http from 'http'; 15 | 16 | const app = express(); 17 | 18 | app.use(express.static('public')); 19 | 20 | app.set('view engine', 'ejs'); 21 | 22 | app.get('*', (req, res) => { 23 | res.render('index'); 24 | }); 25 | 26 | const server = http.createServer(app); 27 | 28 | server.listen(3003); 29 | server.on('listening', () => { 30 | console.log('Listening on 3003'); 31 | }); 32 | ``` 33 | {% endset %} 34 | {{ syntax | markdown | safe }} 35 | -------------------------------------------------------------------------------- /src/assets/vectors/corner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/components.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | site: 3 | title: 24 ways 4 | handle: 24ways 5 | description: 24 ways is the advent calendar for web geeks. Each day throughout December we publish a daily dose of web design and development goodness to bring you all a little Christmas cheer. 6 | url: https://24ways.org 7 | feed: https://feeds.feedburner.com/24ways 8 | theme_color: "#f04" 9 | theme: 10 | # Year hues = 348 - (4 × year increment) 11 | year: [348, 344, 340, 336, 332, 328, 324, 320, 316, 312, 308, 304, 300, 296, 292, 288] 12 | # Day hues = 360 - (7 × day increment) 13 | day: [360, 353, 346, 339, 332, 325, 318, 311, 304, 297, 290, 283, 276, 269, 262, 255, 248, 241, 234, 227, 220, 213, 206, 199] 14 | -------------------------------------------------------------------------------- /src/assets/styles/base/_embedded.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | Embedded content 3 | w3c.github.io/html/semantics-embedded-content.html 4 | ===================================================== */ 5 | 6 | /** 7 | * Remove the border on images inside links in IE 10-. 8 | */ 9 | img { 10 | border: 0; 11 | 12 | @media print { 13 | page-break-inside: avoid; 14 | } 15 | } 16 | 17 | /** 18 | * Hide the overflow in IE. 19 | */ 20 | svg:not(:root) { 21 | display: block; 22 | overflow: hidden; 23 | fill: currentColor; 24 | } 25 | 26 | /** 27 | * Correct `inline-block` display not defined in IE 8/9 28 | */ 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | iframe { 36 | border: 0; 37 | } 38 | -------------------------------------------------------------------------------- /src/assets/styles/theme.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | Fractal UI styles 3 | ===================================================== */ 4 | 5 | @import 'config/**/*.css'; 6 | @import 'docs/**/*.css'; 7 | 8 | /* Theme overrides */ 9 | a { 10 | color: $color-link; 11 | 12 | &:hover { 13 | color: $color-link--hover; 14 | } 15 | 16 | &:active { 17 | color: $color-link--active; 18 | } 19 | } 20 | 21 | .Pen-previewLink svg { 22 | fill: currentColor; 23 | } 24 | 25 | .Header { 26 | background-color: map(colors, primary, dark); 27 | } 28 | 29 | .Prose { 30 | tr td:first-child { 31 | min-width: 12em; 32 | padding-right: 1em; 33 | white-space: nowrap; 34 | } 35 | 36 | tr td:last-child { 37 | width: 100%; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/assets/vectors/topic-code.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/common/section/section.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ title | markdownInline | safe }}

4 |
5 |
6 | {%- if component -%} 7 | {% render "@" + component, config %} 8 | {%- endif -%} 9 | {%- if icon -%} 10 | {% include icon + ".svg" %} 11 | {%- endif -%} 12 | {%- if content -%} 13 |
14 | {{ content | markdown | safe }} 15 |
16 | {%- endif -%} 17 | {%- if comments -%} 18 | {%- for comment in comments -%} 19 | {% render "@comment", comment %} 20 | {%- endfor -%} 21 | {%- endif -%} 22 |
23 |
24 | -------------------------------------------------------------------------------- /src/assets/vectors/topic-ux.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/global/search/search.css: -------------------------------------------------------------------------------- 1 | .c-search { 2 | .c-field__input { 3 | width: 100%; 4 | box-shadow: inset 0 -1px 0 $navigation-color--offset; 5 | padding-left: map(spaces, xlarge); 6 | background-color: $navigation-color--offset; 7 | 8 | &:not(:focus) { 9 | box-shadow: none; 10 | } 11 | 12 | .has-js & { 13 | @media (--from-medium-screen) { 14 | height: $banner-height--large; 15 | } 16 | } 17 | 18 | @media (-ms-high-contrast: active) { 19 | border: 1px solid; 20 | } 21 | } 22 | 23 | .c-field__button { 24 | @apply --focusable; 25 | 26 | position: absolute; 27 | top: 0; 28 | left: 0; 29 | padding: map(spaces, xsmall) map(spaces, small); 30 | 31 | .has-js & { 32 | @media (--from-medium-screen) { 33 | padding: map(spaces, medium) map(spaces, xsmall); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/global/contentinfo/contentinfo.css: -------------------------------------------------------------------------------- 1 | .c-contentinfo { 2 | @apply --typeset-label; 3 | 4 | display: flex; 5 | flex-wrap: wrap; 6 | justify-content: space-between; 7 | padding: map(spaces, small) map(spaces, large); 8 | 9 | .has-js & { 10 | @media (--from-medium-screen) { 11 | margin-right: $navigation-width--large; 12 | } 13 | } 14 | 15 | @media (--from-large-screen) { 16 | padding-left: map(spaces, medium); 17 | padding-right: map(spaces, medium); 18 | } 19 | 20 | @media (-ms-high-contrast: active) { 21 | border-top: 1px solid; 22 | } 23 | 24 | a { 25 | white-space: nowrap; 26 | 27 | @media print { 28 | display: none; 29 | } 30 | } 31 | 32 | > * { 33 | @media (--upto-large-screen) { 34 | line-height: 2; 35 | } 36 | } 37 | } 38 | 39 | .c-contentinfo__social a { 40 | margin-right: map(spaces, medium); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/common/listing/listing.css: -------------------------------------------------------------------------------- 1 | .c-listing { 2 | display: flex; 3 | flex-wrap: wrap; 4 | 5 | @supports (display: grid) { 6 | display: grid; 7 | grid-gap: map(spaces, xsmall); 8 | } 9 | 10 | > * { 11 | display: flex; 12 | flex: 1; 13 | margin: 0 map(spaces, xsmall) map(spaces, xsmall) 0; 14 | 15 | @supports (display: grid) { 16 | margin: 0; 17 | } 18 | } 19 | } 20 | 21 | .c-listing--authors { 22 | grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); 23 | 24 | > * { 25 | flex: 8rem 0 0; 26 | } 27 | } 28 | 29 | .c-listing--summaries { 30 | grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); 31 | 32 | > * { 33 | flex: 16em 1 0; 34 | } 35 | } 36 | 37 | .c-listing--inset { 38 | padding: map(spaces, xsmall) 0 0 map(spaces, xsmall); 39 | 40 | @supports (display: grid) { 41 | padding: map(spaces, xsmall); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /etc/gulp/rollup.js: -------------------------------------------------------------------------------- 1 | const rollup = require('rollup'); 2 | const commonjs = require('@rollup/plugin-commonjs'); 3 | const {nodeResolve} = require('@rollup/plugin-node-resolve'); 4 | const closure = require('rollup-plugin-closure-compiler-js'); 5 | 6 | module.exports = (modules, logger, callback) => { 7 | modules.forEach((module, i) => { 8 | rollup.rollup({ 9 | input: module.input, 10 | plugins: [ 11 | nodeResolve({ 12 | browser: true 13 | }), 14 | commonjs(), 15 | closure() 16 | ] 17 | }) 18 | .then(bundle => { 19 | bundle.write({ 20 | file: module.file, 21 | format: 'iife', 22 | sourcemap: true, 23 | name: module.name 24 | }); 25 | 26 | if (i === modules.length - 1) { 27 | callback(); 28 | } 29 | }) 30 | .catch(error => logger.log(error)); 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /src/components/common/article/article.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ title }}

4 | 10 |
11 | 12 | 15 | 16 |
17 | {% render "@note" %} 18 | {% render "@prose--article" %} 19 |
20 | 21 | {% render "@section--author" %} 22 | {% render "@section--sponsor" %} 23 | {% if related %} 24 | {% render "@section--related" %} 25 | {% endif %} 26 | {% if comments %} 27 | {% render "@section--comments" %} 28 | {% render "@comment-form" %} 29 | {% endif %} 30 |
31 | -------------------------------------------------------------------------------- /src/components/common/author/author.css: -------------------------------------------------------------------------------- 1 | .c-author { 2 | display: block; 3 | position: relative; 4 | overflow: hidden; 5 | } 6 | 7 | .c-author__meta { 8 | @apply --typeset-label; 9 | 10 | display: inline-block; 11 | position: absolute; 12 | right: 0; 13 | bottom: 0; 14 | left: 0; 15 | padding: map(spaces, small); 16 | background-color: color(map(colors, neutral, darkest) a(75%)); 17 | color: white; 18 | 19 | @media print { 20 | color: white !important; 21 | background-color: black !important; 22 | } 23 | 24 | .c-author:hover & { 25 | text-decoration: underline color(white a(66%)); 26 | } 27 | } 28 | 29 | .c-author__image { 30 | display: block; 31 | height: auto; 32 | width: 100%; 33 | transition: transform $author-duration ease-out; 34 | 35 | .c-author:hover & { 36 | transform: scale(1.1); 37 | 38 | @media (prefers-reduced-motion) { 39 | transform: none; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/assets/styles/helpers/_mixins.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | General mixins 3 | ===================================================== */ 4 | 5 | :root { 6 | --hidden: { 7 | position: absolute; 8 | overflow: hidden; 9 | clip: rect(0 0 0 0); 10 | height: 1px; 11 | width: 1px; 12 | margin: -1px; 13 | border: 0; 14 | padding: 0; 15 | white-space: nowrap; 16 | } 17 | 18 | --navigation-link: { 19 | color: $navigation-color--text; 20 | 21 | &:hover { 22 | color: $color-ui-link--hover; 23 | background-color: color(map(colors, neutral, lightest) a(60%)); 24 | } 25 | 26 | &:active { 27 | color: $color-ui-link--active; 28 | background-color: color(map(colors, neutral, lightest) a(80%)); 29 | } 30 | } 31 | 32 | --focusable: { 33 | &:focus { 34 | outline: 0; 35 | box-shadow: inset 0 0 0 2px $color-focus; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/common/comment/comment.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 | {% render "@avatar", avatar %} 6 | {{ author | title }} 7 | 8 |

9 |

10 | 11 |

12 |
13 |
14 | {{ content | markdown | safe }} 15 |
16 | 19 |
20 | -------------------------------------------------------------------------------- /src/components/og-image/og-image.css: -------------------------------------------------------------------------------- 1 | .c-og-image { 2 | width: 600px; 3 | height: 315px; 4 | overflow: hidden; 5 | padding: 2rem; 6 | background-color: $color-year; 7 | background-color: var(--color-year, $color-year); 8 | background-image: inline('diamond.svg'); 9 | background-repeat: repeat; 10 | } 11 | 12 | .c-og-image__quote { 13 | @apply --typeset-prose; 14 | 15 | margin-bottom: map(spaces, small); 16 | font-family: map(fonts, family-serif); 17 | font-size: 2rem; 18 | line-height: 1.15; 19 | text-indent: -0.45em; 20 | color: white; 21 | } 22 | 23 | @supports (hanging-punctuation: first) { 24 | .c-og-image__quote { 25 | text-indent: 0; 26 | } 27 | } 28 | 29 | .c-og-image__quote--small { 30 | font-size: 1.75rem; 31 | } 32 | 33 | .c-og-image__cite { 34 | @apply --typeset-caption; 35 | 36 | font-weight: 600; 37 | font-size: 1.25rem; 38 | color: map(colors, primary, light); 39 | } 40 | 41 | .c-og-image .c-avatar { 42 | float: right; 43 | margin: -2.5rem -3.5rem 0 0; 44 | } 45 | -------------------------------------------------------------------------------- /src/components/common/field/field.css: -------------------------------------------------------------------------------- 1 | .c-field { 2 | @apply --typeset-ui; 3 | 4 | position: relative; 5 | 6 | &:not(:last-of-type) { 7 | margin-bottom: map(spaces, medium); 8 | } 9 | 10 | @media print { 11 | display: none; 12 | } 13 | } 14 | 15 | .c-field__icon { 16 | height: 2.5rem; 17 | width: 2.5rem; 18 | } 19 | 20 | .c-field__input { 21 | width: 100%; 22 | border: 1px solid $prose-color--rule; 23 | border-radius: 0; 24 | padding: map(spaces, medium); 25 | } 26 | 27 | .c-field__label { 28 | display: block; 29 | position: absolute; 30 | top: 0; 31 | bottom: 0; 32 | left: 0; 33 | width: map(sizes, xlarge); 34 | padding: map(spaces, medium); 35 | font-weight: 700; 36 | 37 | + .c-field__input { 38 | padding-left: calc(map(sizes, xlarge) + map(spaces, medium)); 39 | } 40 | } 41 | 42 | .c-field__label--icon { 43 | padding: map(spaces, small); 44 | 45 | + .c-field__input { 46 | padding-left: map(sizes, large); 47 | } 48 | } 49 | 50 | .c-field--combined { 51 | display: flex; 52 | } 53 | -------------------------------------------------------------------------------- /src/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hidden: true 3 | title: Bits, the front-end component library for 24 ways 4 | --- 5 | You are viewing the front-end component library for 24 ways. It is powered by [Fractal](http://fractal.build), a tool that enables the rapid development of components, templates and pages. 6 | 7 | ### Component categories 8 | 9 | * **Utilities:** Components with a single purpose and specific role 10 | * **Common:** Components that may appear anywhere on the site 11 | * **Global:** Components that appear on every page on the site 12 | * **Scopes:** Components used to style scoped areas of content 13 | * **Templates:** Templates for particular page types 14 | 15 | ### Source code 16 | 17 | The [source code is available on GitHub](https://github.com/24ways/frontend). See the [README](https://github.com/24ways/frontend/blob/master/README.md) for installation instructions. 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/scopes/gallery/gallery.css: -------------------------------------------------------------------------------- 1 | .s-gallery { 2 | display: flex; 3 | 4 | @media (--upto-medium-screen) { 5 | flex-direction: column; 6 | } 7 | 8 | img { 9 | display: block; 10 | max-width: 100%; 11 | } 12 | 13 | > * { 14 | flex: 1 1 auto; 15 | width: 100%; 16 | height: 100%; 17 | } 18 | 19 | > *:not(:last-of-type) { 20 | @media (--upto-medium-screen) { 21 | margin-bottom: 2%; 22 | } 23 | 24 | @media (--from-medium-screen) { 25 | margin-right: 2%; 26 | } 27 | } 28 | 29 | > *:nth-last-of-type(2):first-child, 30 | > *:nth-last-of-type(2):first-child ~ * { 31 | @media (--from-medium-screen) { 32 | width: 48%; 33 | } 34 | } 35 | 36 | > *:nth-last-of-type(3):first-child, 37 | > *:nth-last-of-type(3):first-child ~ * { 38 | @media (--from-medium-screen) { 39 | width: 31.3334%; 40 | } 41 | } 42 | 43 | > *:nth-last-of-type(4):first-child, 44 | > *:nth-last-of-type(4):first-child ~ * { 45 | @media (--from-medium-screen) { 46 | width: 23%; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/humans.txt: -------------------------------------------------------------------------------- 1 | /* TEAM */ 2 | Production and server-side development: Drew McLellan 3 | URL: http://allinthehead.com 4 | Twitter: @drewm 5 | 6 | Production: Brian Suda 7 | URL: http://suda.co.uk 8 | Twitter: @briansuda 9 | 10 | Production: Anna Debenham 11 | URL: http://maban.co.uk 12 | Twitter: @anna_debenham 13 | 14 | Editing and production: Owen Gregory 15 | URL: http://fullcreammilk.co.uk 16 | Twitter: @fullcreammilk 17 | 18 | Design and front-end development: Paul Robert Lloyd 19 | URL: https://paulrobertlloyd.com 20 | Twitter: @paulrobertlloyd 21 | 22 | 23 | /* THANKS */ 24 | Made possible only with the help and dedication of our authors. 25 | URL: https://24ways.org/authors 26 | 27 | Animation assistance: Josh Emerson 28 | URL: https://joshemerson.co.uk 29 | Twitter: @joshje 30 | 31 | JavaScript assistance: Scott Jehl 32 | URL: http://scottjehl.com 33 | Twitter: @scottjehl 34 | 35 | 36 | /* SITE */ 37 | Language: English 38 | Doctype: HTML5 39 | CMS: Perch Runway (https://grabaperch.com/products/runway) 40 | Software: PHP, TextMate, Sketch 41 | -------------------------------------------------------------------------------- /src/components/templates/article-template/article-template.config.yaml: -------------------------------------------------------------------------------- 1 | context: 2 | prism: true 3 | title: "Starting Your HTML5 Project on the Right Foot (and Keeping It There)" 4 | description: "Drew McLellan brings our 2015 calendar to a motivational close with some encouragement for the year ahead. Year’s end is a time for reflection and finding new purpose and enthusiasm for what we do. By tackling the thorniest design and development problems, we can make the greatest impact – and have the most fun. Merry Christmas and a happy New Year!" 5 | url: https://24ways.org/2015/starting-on-the-right-foot/ 6 | author: Drew McLellan 7 | handle: drewm 8 | avatar: 9 | size: 160 10 | src: https://cloud.24ways.org/authors/drewmclellan280.jpg 11 | section: article 12 | theme: year-2013 13 | traverse: 14 | type: article 15 | prev: 16 | url: /2015/how-tabs-should-work/ 17 | title: "How Tabs Should Work" 18 | next: 19 | url: /2015/solve-the-hard-problems/ 20 | title: "Solve the Hard Problems" 21 | related: true 22 | comments: 23 | - "@comment" 24 | - "@comment--unhelpful" 25 | -------------------------------------------------------------------------------- /src/components/common/meta/meta.css: -------------------------------------------------------------------------------- 1 | .c-meta { 2 | @apply --typeset-caption; 3 | 4 | display: flex; 5 | flex-flow: row wrap; 6 | margin-bottom: map(spaces, medium); 7 | border-top: 1px solid $prose-color--rule; 8 | padding: map(spaces, small) 0 map(spaces, large); 9 | 10 | @media (--from-large-screen) { 11 | flex-flow: column nowrap; 12 | border: 0; 13 | padding: 0; 14 | } 15 | } 16 | 17 | .c-meta__item { 18 | flex: 0 0 auto; 19 | 20 | @media screen and (max-width: 24.9375em) { 21 | .dt-published span { 22 | display: none; 23 | } 24 | } 25 | 26 | @media (--upto-large-screen) { 27 | &:not(:last-child)::after { 28 | content: '•'; 29 | margin: 0 map(spaces, small); 30 | opacity: 0.5; 31 | } 32 | } 33 | 34 | @media (--from-large-screen) { 35 | display: block; 36 | border-top: 1px solid $prose-color--rule; 37 | padding: map(spaces, small) 0 map(spaces, large); 38 | text-align: right; 39 | } 40 | 41 | @media print { 42 | &:not(:last-child)::after { 43 | content: '•'; 44 | margin: 0 map(spaces, small); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/assets/scripts/modules/webfont-loader.js: -------------------------------------------------------------------------------- 1 | import FontFaceObserver from 'fontfaceobserver'; 2 | 3 | export function loadWebfonts() { 4 | // Setup 5 | const classLoaded = 'fonts-loaded'; 6 | const storageId = 'fonts-loaded'; 7 | const fonts = [ 8 | (new FontFaceObserver('Source Sans Pro', { 9 | weight: 'normal', 10 | style: 'normal' 11 | })).load(), 12 | (new FontFaceObserver('Source Sans Pro', { 13 | weight: '700', 14 | style: 'normal' 15 | })).load(), 16 | (new FontFaceObserver('Source Serif Pro', { 17 | weight: 'normal', 18 | style: 'normal' 19 | })).load(), 20 | (new FontFaceObserver('Source Code Pro', { 21 | weight: 'normal', 22 | style: 'normal' 23 | })).load() 24 | ]; 25 | 26 | // Events 27 | function eventFontsLoaded() { 28 | document.documentElement.classList.add(classLoaded); 29 | sessionStorage[storageId] = true; 30 | } 31 | 32 | // Init 33 | function init() { 34 | Promise.all(fonts) 35 | .then(eventFontsLoaded) 36 | .catch(error => { 37 | console.error(error); 38 | }); 39 | } 40 | 41 | init(); 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Paul Robert Lloyd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/assets/styles/base/_grouping.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | Grouping content 3 | w3c.github.io/html/grouping-content.html 4 | ===================================================== */ 5 | 6 | /** 7 | * 1. Add the correct box sizing in Firefox. 8 | * 2. Show the overflow in Edge and IE. 9 | */ 10 | hr { 11 | box-sizing: content-box; /* 1 */ 12 | height: 0; /* 1 */ 13 | overflow: visible; /* 2 */ 14 | border: 0; 15 | } 16 | 17 | p, 18 | pre, 19 | blockquote, 20 | ul, 21 | ol, 22 | dl, 23 | figure { 24 | margin: 0; 25 | } 26 | 27 | pre { 28 | overflow: auto; 29 | white-space: pre; 30 | hyphens: none; 31 | tab-size: 2; 32 | } 33 | 34 | blockquote { 35 | @media print { 36 | page-break-inside: avoid; 37 | } 38 | } 39 | 40 | /** 41 | * Add the correct display in IE 9-. 42 | */ 43 | figure, 44 | figcaption { 45 | display: block; 46 | } 47 | 48 | /** 49 | * Default to ’bare’ lists (opinionated). 50 | */ 51 | ol, 52 | ul { 53 | padding: 0; 54 | list-style: none; 55 | } 56 | 57 | /** 58 | * Default to no indenting on defintion data (opinionated). 59 | */ 60 | dd { 61 | margin: 0; 62 | } 63 | -------------------------------------------------------------------------------- /src/components/global/site-nav/site-nav.css: -------------------------------------------------------------------------------- 1 | .c-site-nav { 2 | @media (--from-medium-screen) { 3 | margin-top: auto; 4 | } 5 | } 6 | 7 | .c-site-nav__items { 8 | display: flex; 9 | flex-direction: row; 10 | 11 | .has-js & { 12 | @media (--from-medium-screen) { 13 | flex-direction: column; 14 | } 15 | } 16 | } 17 | 18 | .c-site-nav__item { 19 | flex: 1 0 33.3334%; 20 | } 21 | 22 | .c-site-nav__label { 23 | @apply --focusable; 24 | @apply --navigation-link; 25 | @apply --typeset-label; 26 | 27 | display: block; 28 | box-shadow: 1px 0 0 $navigation-color--offset; 29 | padding: map(spaces, small); 30 | text-align: center; 31 | 32 | &:focus { 33 | position: relative; /* Fix border overlap on focus */ 34 | } 35 | 36 | .no-js & { 37 | box-shadow: 1px 0 0 $navigation-color--offset; 38 | } 39 | 40 | @media (--from-shallow-screen) { 41 | padding: map(spaces, medium); 42 | } 43 | 44 | @media (--from-medium-screen) { 45 | box-shadow: 0 -1px 0 $navigation-color--offset; 46 | text-align: left; 47 | } 48 | 49 | @media (-ms-high-contrast: active) { 50 | border: 1px solid; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/scopes/syntax/syntax.css: -------------------------------------------------------------------------------- 1 | pre[class*=language-] { 2 | text-align: left; 3 | direction: ltr; 4 | padding: map(spaces, medium); 5 | 6 | code { 7 | background-color: transparent; 8 | } 9 | } 10 | 11 | .token.cdata, 12 | .token.comment, 13 | .token.doctype, 14 | .token.namespace, 15 | .token.operator, 16 | .token.prolog, 17 | .token.punctuation { 18 | color: map(colors, neutral, dark); 19 | } 20 | 21 | .token.comment { 22 | opacity: 0.9; 23 | font-style: italic; 24 | } 25 | 26 | .token.string, 27 | .token.attr-value { 28 | color: #e3116c; 29 | } 30 | 31 | .token.entity, 32 | .token.url, 33 | .token.symbol, 34 | .token.number, 35 | .token.boolean, 36 | .token.variable, 37 | .token.constant, 38 | .token.property, 39 | .token.regex, 40 | .token.inserted { 41 | color: #066; 42 | } 43 | 44 | .token.atrule, 45 | .token.keyword, 46 | .token.attr-name { 47 | color: #069; 48 | } 49 | 50 | .token.function, 51 | .token.deleted { 52 | color: #900; 53 | } 54 | 55 | .token.tag, 56 | .token.selector { 57 | color: #009; 58 | } 59 | 60 | .token.important, 61 | .token.function, 62 | .token.bold { 63 | font-weight: bold; 64 | } 65 | 66 | .token.italic { 67 | font-style: italic; 68 | } 69 | -------------------------------------------------------------------------------- /src/components/scopes/gallery/gallery.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /src/components/common/summary/summary.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {%- if href -%} 5 | {{ title }} 6 | {%- else -%} 7 | {{ title }} 8 | {%- endif -%} 9 |

10 | {%- if author -%} 11 |

12 | 13 | 14 | {{ author }} 15 | 16 |

17 | {%- endif -%} 18 |
19 |
20 |

{{ content | markdownInline | safe }}

21 |
22 | {%- if datetime -%} 23 | 30 | {%- endif -%} 31 |
32 | -------------------------------------------------------------------------------- /src/assets/styles/config/_media.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | Media 3 | ===================================================== */ 4 | 5 | /* Xtra-small */ 6 | @custom-media --upto-xsmall-screen screen and (width < map(breakpoints, xsmall)); 7 | @custom-media --from-xsmall-screen screen and (width >= map(breakpoints, xsmall)); 8 | 9 | /* Small */ 10 | @custom-media --upto-small-screen screen and (width < map(breakpoints, small)); 11 | @custom-media --from-small-screen screen and (width >= map(breakpoints, small)); 12 | 13 | /* Medium */ 14 | @custom-media --upto-medium-screen screen and (width < map(breakpoints, medium)); 15 | @custom-media --from-medium-screen screen and (width >= map(breakpoints, medium)); 16 | 17 | /* Large */ 18 | @custom-media --upto-large-screen screen and (width < map(breakpoints, large)); 19 | @custom-media --from-large-screen screen and (width >= map(breakpoints, large)); 20 | 21 | /* Shallow/Deep */ 22 | @custom-media --upto-shallow-screen screen and (height < 28rem); 23 | @custom-media --from-shallow-screen screen and (height >= 28rem); 24 | @custom-media --upto-deep-screen screen and (height < 36rem); 25 | @custom-media --from-deep-screen screen and (height >= 36rem); 26 | 27 | /* Other */ 28 | @custom-media --from-xlarge-screen screen and (width >= map(breakpoints, xlarge)); 29 | @custom-media --from-max-screen screen and (width >= map(breakpoints, max)); 30 | -------------------------------------------------------------------------------- /src/components/global/topics-nav/topics-nav.css: -------------------------------------------------------------------------------- 1 | .c-topics-nav__items { 2 | display: flex; 3 | flex-wrap: wrap; 4 | align-items: flex-start; 5 | align-content: flex-start; 6 | box-shadow: inset 0 1px 0 $navigation-color--offset; 7 | 8 | @media (--from-medium-screen) { 9 | box-shadow: 0 -1px 0 $navigation-color--offset; 10 | } 11 | } 12 | 13 | .c-topics-nav__item { 14 | flex: 1 0 33.3334%; 15 | 16 | .has-js & { 17 | @media (--from-medium-screen) { 18 | flex-basis: 50%; 19 | min-width: 50%; 20 | } 21 | } 22 | 23 | @media (--from-large-screen) { 24 | flex-basis: 16.6667%; 25 | } 26 | 27 | svg { 28 | height: map(sizes, medium); 29 | width: map(sizes, medium); 30 | margin: 0 auto map(spaces, xsmall); 31 | 32 | @media (--upto-deep-screen) { 33 | display: none; 34 | } 35 | 36 | @media (--from-medium-screen) { 37 | height: map(sizes, large); 38 | width: map(sizes, large); 39 | } 40 | } 41 | } 42 | 43 | .c-topics-nav__label { 44 | @apply --focusable; 45 | @apply --navigation-link; 46 | @apply --typeset-ui; 47 | 48 | display: block; 49 | box-shadow: 1px 1px 0 $navigation-color--offset; 50 | padding: map(spaces, small); 51 | text-align: center; 52 | 53 | @media (--from-shallow-screen) { 54 | padding: map(spaces, large) map(spaces, medium); 55 | } 56 | 57 | @media (-ms-high-contrast: active) { 58 | border: 1px solid; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/assets/vectors/diamond.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/assets/styles/base/_sections.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | Sections 3 | w3c.github.io/html/sections.html 4 | ===================================================== */ 5 | 6 | /** 7 | * 1. Prevent adjustment of font size on orientation change. 8 | */ 9 | html { 10 | box-sizing: border-box; 11 | height: 100%; 12 | text-size-adjust: 100%; /* 1 */ 13 | position: relative; 14 | overflow-x: hidden; 15 | } 16 | 17 | *, 18 | *::before, 19 | *::after { 20 | box-sizing: inherit; 21 | } 22 | 23 | *:focus { 24 | outline: 2px solid $color-focus; 25 | } 26 | 27 | @media print { 28 | @page { 29 | margin: 2cm; 30 | } 31 | 32 | * { 33 | background: none !important; 34 | color: black !important; 35 | text-shadow: none !important; 36 | box-shadow: none !important; 37 | } 38 | } 39 | 40 | body { 41 | margin: 0; 42 | height: 100%; 43 | background-color: white; 44 | font-family: sans-serif; 45 | -moz-osx-font-smoothing: grayscale; 46 | -webkit-font-smoothing: antialiased; 47 | line-height: 1.5; 48 | color: $color-text; 49 | 50 | .fonts-loaded & { 51 | font-family: map(fonts, family-sans); 52 | } 53 | } 54 | 55 | /** 56 | * Add the correct display in IE 9-. 57 | */ 58 | article, 59 | aside, 60 | footer, 61 | header, 62 | main, 63 | nav, 64 | section { 65 | display: block; 66 | } 67 | 68 | h1, 69 | h2, 70 | h3, 71 | h4, 72 | h5, 73 | h6 { 74 | margin: 0; 75 | text-rendering: optimizeLegibility; 76 | font-size: inherit; 77 | font-weight: inherit; 78 | 79 | @media print { 80 | page-break-after: avoid; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/components/common/preface/preface.config.yaml: -------------------------------------------------------------------------------- 1 | collated: true 2 | context: 3 | title: Preface title 4 | content: Preface content 5 | variants: 6 | - name: topic 7 | context: 8 | title: Business 9 | titleIcon: topic-business 10 | content: | 11 | 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. 12 | - name: author 13 | context: 14 | title: Rachel Andrew 15 | avatar: 16 | src: https://cloud.24ways.org/authors/rachelandrew280.jpg 17 | size: 280 18 | microformats: true 19 | content: | 20 | Rachel Andrew is a Director of edgeofmyseat.com, a UK web development consultancy and creators of the small content management system, [Perch](https://grabaperch.com/). She is the author of a number of [books](https://rachelandrew.co.uk/books), and is a regular columnist for [A List Apart](http://alistapart.com/author/rachelandrew). 21 | 22 | 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. 23 | 24 | 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. 25 | 26 | Photo: [James Duncan Davidson](http://duncandavidson.com/) 27 | -------------------------------------------------------------------------------- /src/components/common/preface/preface.css: -------------------------------------------------------------------------------- 1 | .c-preface { 2 | overflow: hidden; 3 | display: flex; 4 | flex-direction: column; 5 | padding: map(spaces, medium) map(spaces, large) calc(map(spaces, large) / 2); 6 | background-color: $color-year; 7 | background-color: var(--color-year, $color-year); 8 | background-image: inline('diamond.svg'); 9 | background-repeat: repeat; 10 | 11 | @media (--from-medium-screen) { 12 | min-height: 9rem; 13 | } 14 | 15 | @media (--from-large-screen) { 16 | padding-top: map(spaces, xxlarge); 17 | padding-left: 25%; 18 | } 19 | } 20 | 21 | .c-preface__title { 22 | @apply --typeset-title; 23 | 24 | display: flex; 25 | align-items: flex-start; 26 | position: relative; 27 | margin-top: auto; 28 | color: map(colors, primary, light); 29 | 30 | svg { 31 | width: 1em; 32 | height: 1em; 33 | margin-right: map(spaces, small); 34 | } 35 | } 36 | 37 | .c-preface__main { 38 | position: relative; 39 | margin-top: map(spaces, small); 40 | color: color(white a(75%)); 41 | 42 | .s-prose p:first-of-type { 43 | @apply --typeset-lede; 44 | } 45 | 46 | .s-prose p:last-of-type { 47 | margin-bottom: 0; 48 | } 49 | 50 | .c-avatar { 51 | width: 50%; 52 | float: right; 53 | margin-top: -7.5%; 54 | margin-right: -7.5%; 55 | 56 | @media (--from-large-screen) { 57 | width: auto; 58 | float: left; 59 | margin-left: -33%; 60 | margin-right: map(spaces, medium); 61 | } 62 | } 63 | 64 | a { 65 | border-bottom: 1px solid color(white a(25%)); 66 | color: white; 67 | 68 | &:hover { 69 | border-bottom-color: color(white a(25%)); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/assets/styles/config/_constants.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | Constants 3 | ===================================================== */ 4 | 5 | /* Banner */ 6 | $banner-height--small: map(sizes, large); 7 | $banner-height--large: map(sizes, xlarge); 8 | 9 | /* Navigation */ 10 | $navigation-width--large: map(sizes, xlarge); 11 | $navigation-duration: 0.4s; 12 | $navigation-color: white; 13 | $navigation-color--offset: map(colors, neutral, lightest); 14 | $navigation-color--text: map(colors, neutral, dark); 15 | $navigation-color--shadow: color(map(colors, neutral, darker) a(25%)); 16 | 17 | /* Backdrop */ 18 | $backdrop-color: color(map(colors, neutral, darkest) a(33%)); 19 | 20 | /* Author images */ 21 | $author-duration: 0.5s; 22 | 23 | /* Text */ 24 | $color-text: map(colors, neutral, darker); 25 | 26 | /* Prose */ 27 | $prose-color--background: color(map(colors, neutral, base) a(15%)); 28 | $prose-color--rule: color(map(colors, neutral, base) a(25%)); 29 | $prose-color--shadow: color(map(colors, neutral, base) a(25%)); 30 | 31 | /* Links */ 32 | $color-link: map(colors, secondary, base); 33 | $color-link--hover: map(colors, secondary, dark); 34 | $color-link--active: map(colors, secondary, light); 35 | $color-ui-link: map(colors, primary, base); 36 | $color-ui-link--hover: map(colors, primary, light); 37 | $color-ui-link--active: map(colors, primary, dark); 38 | 39 | /* Focus */ 40 | $color-focus: map(colors, secondary, light); 41 | 42 | /* Themes */ 43 | $color-year: hsl(348, 100%, 16%); 44 | $color-year--dark: hsl(348, 100%, 8%); 45 | $color-year--dark-alpha: hsla(348, 100%, 8%, 0.8); 46 | $color-day: hsl(360, 80%, 60%); 47 | $color-day--light: hsl(360, 60%, 98%); 48 | $color-day--dark: hsl(360, 100%, 24%); 49 | $color-day--dark-alpha: hsl(360, 100%, 24%, 0.33); 50 | -------------------------------------------------------------------------------- /src/components/common/section/section.css: -------------------------------------------------------------------------------- 1 | .c-section { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | 6 | @media (--from-large-screen) { 7 | flex-direction: row; 8 | } 9 | } 10 | 11 | .c-section__header { 12 | padding: map(spaces, medium) map(spaces, large); 13 | 14 | @media (--from-large-screen) { 15 | flex: 1 0 20%; 16 | padding-right: 0; 17 | border-top: 1px solid $prose-color--rule; 18 | text-align: right; 19 | } 20 | 21 | @media print { 22 | border-top: 2px solid black; 23 | padding-bottom: 0; 24 | } 25 | } 26 | 27 | .c-section__title { 28 | @apply --typeset-ui; 29 | 30 | text-transform: uppercase; 31 | } 32 | 33 | .c-section__main { 34 | margin-bottom: map(spaces, large); 35 | padding: map(spaces, medium) map(spaces, large); 36 | border-top: 1px solid $prose-color--rule; 37 | 38 | @media (--from-large-screen) { 39 | flex: 1 0 75%; 40 | margin-left: 5%; 41 | padding-left: 0; 42 | } 43 | } 44 | 45 | /* Section - Sponsor */ 46 | .c-article .c-section--sponsor { 47 | @media (--from-large-screen) { 48 | flex-direction: column; 49 | position: absolute; 50 | top: 56rem; 51 | left: 0; 52 | width: 20%; 53 | padding: 0; 54 | 55 | .c-section__title { 56 | @apply --typeset-ui; 57 | } 58 | 59 | .c-section__main { 60 | padding-right: 0; 61 | border-top: 0; 62 | } 63 | 64 | .c-promo { 65 | @apply --typeset-caption; 66 | 67 | text-align: right; 68 | } 69 | } 70 | } 71 | 72 | /* Section - Topic */ 73 | .c-section--topic { 74 | .c-section__main { 75 | display: flex; 76 | flex-direction: row; 77 | } 78 | 79 | svg { 80 | flex-shrink: 0; 81 | width: map(sizes, xlarge); 82 | height: map(sizes, xlarge); 83 | margin: map(spaces, small) map(spaces, large) 0 0; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/components/common/comment/comment.css: -------------------------------------------------------------------------------- 1 | $comment-avatar--small: map(sizes, large); 2 | $comment-avatar--large: map(sizes, xlarge); 3 | 4 | .c-comment { 5 | position: relative; 6 | margin-bottom: map(spaces, large); 7 | padding: map(spaces, large); 8 | padding-left: $comment-avatar--small; 9 | background-color: white; 10 | box-shadow: 1px 1px 0 $prose-color--shadow; 11 | 12 | @media (-ms-high-contrast: active) { 13 | border: 1px solid; 14 | } 15 | 16 | @media (--from-medium-screen) { 17 | padding-left: $comment-avatar--large; 18 | } 19 | 20 | @media (--upto-large-screen) { 21 | margin-left: calc(calc(map(spaces, large) - map(spaces, xsmall)) * -1); 22 | margin-right: calc(calc(map(spaces, large) - map(spaces, xsmall)) * -1); 23 | } 24 | 25 | &.c-comment--unhelpful { 26 | background-color: transparent; 27 | box-shadow: 0 0 0 1px $prose-color--shadow; 28 | } 29 | 30 | .c-avatar { 31 | position: absolute; 32 | top: 0; 33 | left: calc(map(spaces, large) * -1); 34 | width: map(sizes, large); 35 | 36 | @media (--from-medium-screen) { 37 | width: map(sizes, xlarge); 38 | } 39 | } 40 | } 41 | 42 | .c-comment__header { 43 | margin-bottom: map(spaces, large); 44 | 45 | @media print { 46 | border-top: 1px solid black; 47 | padding-top: map(spaces, small); 48 | } 49 | } 50 | 51 | .c-comment__title { 52 | @apply --typeset-summary-title; 53 | } 54 | 55 | .c-comment__meta { 56 | @apply --typeset-label; 57 | 58 | @media (--from-small-screen) { 59 | position: absolute; 60 | top: 1.75rem; 61 | right: map(spaces, large); 62 | } 63 | } 64 | 65 | .c-comment__main { 66 | margin-bottom: map(spaces, large); 67 | } 68 | 69 | .c-comment__footer { 70 | @apply --typeset-label; 71 | 72 | border-top: 1px solid $prose-color--shadow; 73 | padding-top: map(spaces, medium); 74 | text-align: right; 75 | } 76 | -------------------------------------------------------------------------------- /src/components/common/comment-form/comment-form.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | Impress us 6 | 7 |
8 |
9 |

Be friendly / use Textile

10 |
11 |

12 | 13 | 14 |

15 |

16 | 17 | 18 |

19 |

20 | 21 | 22 |

23 |

24 | 25 | 26 |

27 |

28 | 29 | 30 | 31 |

32 |
33 |
34 |
35 |
36 | -------------------------------------------------------------------------------- /src/assets/styles/base/_text.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | Text-level semantics 3 | w3c.github.io/html/textlevel-semantics.html 4 | ===================================================== */ 5 | 6 | /** 7 | * 1. Remove grey background on active links in IE 10. 8 | */ 9 | a { 10 | background-color: transparent; /* 1 */ 11 | text-decoration: none; 12 | text-decoration-color: color($color-link a(33%)); 13 | color: $color-link; 14 | transition: all 0.2s ease; 15 | } 16 | 17 | /** 18 | * 1. Improve readability when focused and also mouse hovered in all browsers 19 | */ 20 | a:active, 21 | a:hover { 22 | color: $color-link--hover; 23 | text-decoration-color: color($color-link--hover a(33%)); 24 | outline: 0; /* 1 */ 25 | } 26 | 27 | a:active { 28 | color: $color-link--active; 29 | text-decoration-color: color($color-link--active a(33%)); 30 | } 31 | 32 | @media print { 33 | a, 34 | a:visited { 35 | color: black !important; 36 | } 37 | } 38 | 39 | /* Add the correct font size in all browsers */ 40 | small { 41 | font-size: 100%; 42 | } 43 | 44 | abbr[title] { 45 | text-decoration: underline dotted rgba(0, 0, 0, 0.33); 46 | 47 | &:hover { 48 | cursor: help; 49 | } 50 | } 51 | 52 | /** 53 | * 1. Correct the inheritance and scaling of font size in all browsers. 54 | * 2. Correct the odd `em` font sizing in all browsers. 55 | */ 56 | code, 57 | samp { 58 | font-family: map(fonts, family-monospace); /* 1 */ 59 | font-size: 0.875em; /* 2 */ 60 | } 61 | 62 | kbd { 63 | font-family: map(fonts, family-system); 64 | } 65 | 66 | /** 67 | * Prevent `sub` and `sup` elements from affecting line height in all browsers. 68 | */ 69 | sub, 70 | sup { 71 | font-size: 75%; 72 | line-height: 0; 73 | position: relative; 74 | vertical-align: baseline; 75 | } 76 | 77 | sub { 78 | bottom: -0.25rem; 79 | } 80 | 81 | sup { 82 | top: -0.5rem; 83 | } 84 | 85 | /** 86 | * Address styling not present in IE 8/9 87 | */ 88 | mark { 89 | background: #ffc; 90 | } 91 | -------------------------------------------------------------------------------- /fractal.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const paths = { 4 | build: path.join(__dirname, 'www'), 5 | src: path.join(__dirname, 'src'), 6 | static: path.join(__dirname, 'tmp') 7 | }; 8 | 9 | const fractal = require('@frctl/fractal').create(); 10 | 11 | const mandelbrot = require('@frctl/mandelbrot')({ 12 | favicon: '/assets/icons/icon.ico', 13 | lang: 'en-gb', 14 | nav: ['search', 'components', 'docs'], 15 | styles: ['default', '/assets/styles/theme.css'], 16 | static: { 17 | mount: 'fractal' 18 | } 19 | }); 20 | 21 | const mdAbbr = require('markdown-it-abbr'); 22 | const mdFootnote = require('markdown-it-footnote'); 23 | const md = require('markdown-it')({ 24 | html: true, 25 | xhtmlOut: true, 26 | typographer: true 27 | }).use(mdAbbr).use(mdFootnote); 28 | const nunjucksDate = require('nunjucks-date'); 29 | const nunjucks = require('@frctl/nunjucks')({ 30 | filters: { 31 | date: nunjucksDate, 32 | markdown(string) { 33 | return md.render(string); 34 | }, 35 | markdownInline(string) { 36 | return md.renderInline(string); 37 | }, 38 | slugify(string) { 39 | return string.toLowerCase().replace(/\W+/g, ''); 40 | }, 41 | stringify() { 42 | return JSON.stringify(this, null, '\t'); 43 | } 44 | }, 45 | paths: [`${paths.static}/assets/vectors`] 46 | }); 47 | 48 | // Project config 49 | fractal.set('project.title', 'Bits of 24 ways'); 50 | 51 | // Components config 52 | fractal.components.engine(nunjucks); 53 | fractal.components.set('default.preview', '@preview'); 54 | fractal.components.set('default.status', null); 55 | fractal.components.set('ext', '.html'); 56 | fractal.components.set('path', `${paths.src}/components`); 57 | 58 | // Docs config 59 | fractal.docs.engine(nunjucks); 60 | fractal.docs.set('ext', '.md'); 61 | fractal.docs.set('path', `${paths.src}/docs`); 62 | 63 | // Web UI config 64 | fractal.web.theme(mandelbrot); 65 | fractal.web.set('static.path', paths.static); 66 | fractal.web.set('builder.dest', paths.build); 67 | fractal.web.set('builder.urls.ext', null); 68 | 69 | // Export config 70 | module.exports = fractal; 71 | -------------------------------------------------------------------------------- /src/components/global/banner/banner.css: -------------------------------------------------------------------------------- 1 | .c-banner { 2 | background-color: $color-year--dark; 3 | background-color: var(--color-year--dark, $color-year--dark); 4 | 5 | .has-js & { 6 | @media (--from-medium-screen) { 7 | margin-right: $navigation-width--large; 8 | } 9 | } 10 | 11 | @media (-ms-high-contrast: active) { 12 | border-bottom: 1px solid; 13 | } 14 | } 15 | 16 | .c-banner__title { 17 | padding: map(spaces, medium) map(spaces, large); 18 | font-size: 1.25em; 19 | line-height: calc(16 / 20); 20 | 21 | .has-js & { 22 | @media (--upto-medium-screen) { 23 | position: fixed; 24 | top: 0; 25 | right: 0; 26 | left: 0; 27 | z-index: calc(map(layers, modal) + 1); 28 | margin-right: map(sizes, large); 29 | } 30 | } 31 | 32 | @media (--from-medium-screen) { 33 | padding: map(spaces, large); 34 | font-size: 1.5em; 35 | line-height: calc(24 / 24); 36 | } 37 | 38 | @media (--from-large-screen) { 39 | padding-left: 25%; 40 | } 41 | } 42 | 43 | .c-banner__home { 44 | text-transform: uppercase; 45 | color: white; 46 | 47 | &:hover { 48 | color: white; 49 | } 50 | 51 | span { 52 | font-size: 0.6667em; 53 | text-transform: lowercase; 54 | white-space: nowrap; 55 | color: map(colors, primary, light); 56 | 57 | @media (--upto-xsmall-screen) { 58 | @apply --hidden; 59 | } 60 | } 61 | } 62 | 63 | .c-banner__skip { 64 | @apply --typeset-ui; 65 | 66 | position: fixed; 67 | top: 0; 68 | left: map(spaces, medium); 69 | z-index: map(layers, overlay); 70 | display: block; 71 | background-color: $navigation-color; 72 | box-shadow: 0 8px 8px 0 $navigation-color--shadow; 73 | padding: map(spaces, xlarge) map(spaces, small) map(spaces, xsmall); 74 | color: $navigation-color--text; 75 | transform: translateY(0); 76 | 77 | &:hover { 78 | color: $color-ui-link--hover; 79 | } 80 | 81 | &:active { 82 | color: $color-ui-link--active; 83 | } 84 | 85 | &:not(:focus) { 86 | @apply --hidden; 87 | 88 | transform: translateY(-100%); 89 | transition: transform 0.5s ease-out; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/components/common/summary/summary.config.yaml: -------------------------------------------------------------------------------- 1 | collated: true 2 | context: 3 | theme: day-15 4 | href: /2015/grid-flexbox-box-alignment-our-new-system-for-layout/ 5 | title: "Grid, Flexbox, Box Alignment: Our New System for Layout" 6 | author: Rachel Andrew 7 | datetime: "2010-12-15T00:00:00-00:00" 8 | comments: 6 9 | content: | 10 | [Rachel Andrew](https://rachelandrew.co.uk/) unwraps the new paradigms of web layout, comparing their features and showing how they can free us from grid-based, `div`-infested frameworks. Beautifully wrapped boxes look lovely under the Christmas tree, but we need to think and break out of them. 11 | variants: 12 | - name: countdown 13 | context: 14 | mods: [countdown] 15 | theme: day-15 16 | href: /2010/real-animation-using-javascript-css3-and-html5-video/ 17 | title: "Real Animation Using JavaScript, CSS3, and HTML5 Video" 18 | author: Dan Mall 19 | datetime: "2010-12-15T00:00:00-00:00" 20 | comments: 11 21 | content: | 22 | [Dan Mall](http://danielmall.com/) breathes life into web standards-based animation. By striving for more than just mechanical movement, we can create more believable animated effects to enhance our users' experience. 23 | - name: short 24 | context: 25 | mods: [countdown] 26 | theme: day-15 27 | href: /2010/real-animation-using-javascript-css3-and-html5-video/ 28 | title: "Real Animation" 29 | author: Dan Mall 30 | datetime: "2010-12-15T00:00:00-00:00" 31 | comments: 11 32 | content: | 33 | *Dan Mall* breathes life into web standards-based animation. 34 | - name: sponsored 35 | context: 36 | mods: [sponsored] 37 | href: mailto:sponsorship@24ways.org?subject=Sponsoring 24 ways on Friday, 1 December 38 | title: "Friday, 1 December" 39 | author: false 40 | datetime: false 41 | content: | 42 | Sponsor this day for $250 USD. 43 | - name: sponsored-taken 44 | context: 45 | mods: [sponsored, taken] 46 | href: false 47 | title: "Saturday, 2 December" 48 | author: false 49 | datetime: false 50 | content: | 51 | Too late, this slot has been taken! 52 | -------------------------------------------------------------------------------- /src/components/common/article/article.css: -------------------------------------------------------------------------------- 1 | .c-article { 2 | position: relative; 3 | overflow-x: hidden; 4 | background-color: $color-day--light; 5 | background-color: var(--color-day--light, $color-day--light); 6 | 7 | @media (--from-large-screen) { 8 | padding-top: 9rem; 9 | padding-bottom: map(spaces, large); 10 | } 11 | 12 | a { 13 | color: $color-day--dark; 14 | color: var(--color-day--dark, $color-day--dark); 15 | 16 | &:hover { 17 | color: $color-day; 18 | color: var(--color-day, $color-day); 19 | } 20 | } 21 | } 22 | 23 | .c-article__header { 24 | padding: map(spaces, medium) map(spaces, large) map(spaces, large); 25 | position: relative; 26 | 27 | @media (--from-large-screen) { 28 | min-height: 16rem; 29 | margin-top: -1px; 30 | padding-bottom: map(spaces, xxlarge); 31 | padding-left: 25%; 32 | border-top: 1px solid $prose-color--rule; 33 | } 34 | } 35 | 36 | .c-article__title { 37 | @apply --typeset-title; 38 | 39 | position: relative; 40 | margin-bottom: map(spaces, small); 41 | z-index: map(layers, overlay); 42 | } 43 | 44 | .c-article__byline { 45 | @apply --typescale-prose; 46 | 47 | .c-avatar { 48 | position: absolute; 49 | margin: 0; 50 | 51 | @media (--upto-large-screen) { 52 | top: -25%; 53 | right: -12.5%; 54 | width: 50%; 55 | opacity: 0.1; 56 | filter: saturate(0) contrast(2); 57 | mix-blend-mode: multiply; 58 | } 59 | 60 | @media (--from-large-screen) { 61 | top: -40%; 62 | right: 80%; 63 | width: 27.5%; 64 | margin-right: calc(map(spaces, medium) * -1); 65 | } 66 | 67 | @media print { 68 | display: none; 69 | } 70 | } 71 | } 72 | 73 | .c-article__main { 74 | padding: 0 map(spaces, large) map(spaces, large); 75 | 76 | @media (--from-large-screen) { 77 | margin-left: 25%; 78 | padding-left: 0; 79 | } 80 | } 81 | 82 | .c-article__footer { 83 | position: relative; 84 | z-index: map(layers, default); 85 | padding: 0 map(spaces, large); 86 | 87 | @media (--from-large-screen) { 88 | float: left; 89 | width: 20%; 90 | padding: 0; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/docs/tokens.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Design tokens" 3 | label: "Design tokens" 4 | --- 5 | [Design tokens](https://medium.com/eightshapes-llc/25dd82d58421) are named entities that store visual design information. These are used in place of hard-coded values (such as hex values for color or pixels for spacing) in order to maintain a scalable, consistent system for UI development. 6 | 7 | ## Colour Palettes 8 | {% for palette, values in colors %} 9 | **{{ palette | capitalize }}** palette values. Accessed via `map(colors, {{ palette }}, )` 10 | {% include "@palette-sample" %} 11 | {% endfor %} 12 | 13 | ## Borders 14 | Width and radii tokens are used to style element borders. Accessed via `map(borders, )`. 15 | 16 | Key | Value 17 | ------------|------------ 18 | {% for key, value in borders -%} 19 | `{{ key }}` | {{ value }} 20 | {% endfor -%} 21 | 22 | ## Breakpoints 23 | Breakpoint tokens are used within `@media` queries. Accessed via `map(breakpoints, )`. 24 | 25 | Key | Value 26 | ------------|------------ 27 | {% for key, value in breakpoints -%} 28 | `{{ key }}` | {{ value }} 29 | {% endfor -%} 30 | 31 | ## Font families 32 | Font family tokens are used for typographic styling. Accessed via `map(fonts, )`. 33 | 34 | Key | Value 35 | ------------|------------ 36 | {% for key, value in fonts -%} 37 | `{{ key }}` | {{ value }} 38 | {% endfor -%} 39 | 40 | ## Layers 41 | Layering tokens set the `z-index` layer value for elements. Accessed via `map(layers, )`. 42 | 43 | Key | Value 44 | ------------|------------ 45 | {% for key, value in layers -%} 46 | `{{ key }}` | {{ value }} 47 | {% endfor -%} 48 | 49 | ## Sizes 50 | Sizing tokens describe the dimensions of elements. Accessed via `map(sizes, )`. 51 | 52 | Key | Value 53 | ------------|------------ 54 | {% for key, value in sizes -%} 55 | `{{ key }}` | {{ value }} 56 | {% endfor -%} 57 | 58 | ## Spacing 59 | Spacing tokens describe the distance between elements. Accessed via `map(spaces, )`. 60 | 61 | Key | Value 62 | ------------|------------ 63 | {% for key, value in spaces -%} 64 | `{{ key }}` | {{ value }} 65 | {% endfor -%} 66 | -------------------------------------------------------------------------------- /src/components/global/traverse-nav/traverse-nav.css: -------------------------------------------------------------------------------- 1 | $traverse-nav__label-width: 18rem; /* 288px */ 2 | 3 | .c-traverse-nav { 4 | display: flex; 5 | 6 | .has-js & { 7 | @media (--from-medium-screen) { 8 | display: block; 9 | position: fixed; 10 | right: 0; 11 | top: $banner-height--large; 12 | z-index: calc(map(layers, modal) + 1); 13 | width: $navigation-width--large; 14 | } 15 | } 16 | } 17 | 18 | .c-traverse-nav__item { 19 | @apply --focusable; 20 | @apply --navigation-link; 21 | 22 | display: flex; 23 | flex: 50% 0 1; 24 | justify-content: center; 25 | position: relative; 26 | width: auto; 27 | box-shadow: inset 0 -1px 0 $navigation-color--offset; 28 | padding: map(spaces, medium); 29 | 30 | @media (-ms-high-contrast: active) { 31 | border: 1px solid; 32 | } 33 | } 34 | 35 | .c-traverse-nav__item[rel] { 36 | &::before, 37 | &::after { 38 | position: absolute; 39 | top: 0; 40 | bottom: 0; 41 | left: 0; 42 | right: 0; 43 | } 44 | 45 | /* Box: covers ear */ 46 | &::before { 47 | z-index: map(layers, underlay); 48 | background-color: $navigation-color; 49 | box-shadow: inset 1px 0 $navigation-color--offset; 50 | content: ''; 51 | } 52 | 53 | /* Ear: slides from under box on hover */ 54 | &::after { 55 | .has-js & { 56 | @media (--from-medium-screen) { 57 | @apply --typeset-label; 58 | 59 | z-index: calc(map(layers, underlay) - 1); 60 | width: $traverse-nav__label-width; 61 | overflow: hidden; 62 | background-color: $color-year--dark-alpha; 63 | background-color: var(--color-year--dark-alpha, $color-year--dark-alpha); 64 | backdrop-filter: blur(4px); 65 | padding: map(spaces, xsmall) map(spaces, small); 66 | white-space: pre-wrap; 67 | color: white; 68 | transition: all 0.3s ease-out; 69 | content: attr(aria-label); 70 | } 71 | } 72 | 73 | [data-menu-expanded=true] & { 74 | display: none; 75 | } 76 | } 77 | 78 | &:hover { 79 | &::after { 80 | left: calc($traverse-nav__label-width * -1); 81 | } 82 | } 83 | } 84 | 85 | .c-traverse-nav__icon { 86 | height: 2.5rem; 87 | width: 2.5rem; 88 | } 89 | -------------------------------------------------------------------------------- /src/components/common/comment/comment.config.yaml: -------------------------------------------------------------------------------- 1 | collated: true 2 | default: helpful 3 | variants: 4 | - name: helpful 5 | context: 6 | id: c0001 7 | href: https://adactio.com/ 8 | author: Jeremy Keith 9 | datetime: "2012-12-01T12:24:48-00:00" 10 | avatar: 11 | src: "https://www.gravatar.com/avatar/5ad82c5ba0264363974af89deb743c20?s=96" 12 | size: 64 13 | content: | 14 | This is great stuff! I'm terrible at regular expressions—my brain just doesn't seem to want to remember any of it—but this article contains the clearest description of regular expressions I've come across. 15 | 16 | I thought I'd share a useful little rewrite rule that I use for cache-busting JavaScript and CSS files. You know the story: you make a change in your JavaScript or CSS and you want to let the browser know that it should grab the new version instead of using what it's got in its cache. 17 | 18 | Now, I could potentially just use a query string when I point to my JS and CSS files ( e.g. /js/myscript.js?20131201 ) …but that can cause issues with proxy servers. 19 | 20 | Instead what I what I do is point to files like this: `/js/myscript.20131201.js` 21 | 22 | Then I need to tell the server to look for the **actual** file in `/js/myscript.js` 23 | 24 | Here's the rewrite rule I'm using: 25 | 26 | ``` 27 | RewriteCond %{REQUEST_FILENAME} !-f 28 | RewriteRule ^(.).(d).(js|css)$ $1.$3 [L] 29 | ``` 30 | 31 | It's basically telling the server that, if the JS or CSS file doesn't actually exist and it matches the pattern of having two dots before the file extension (with only numbers after the first dot), to look at the bit before the first dot, and look at the bit after the second dot, but to ignore the bit in between (the numbers). 32 | 33 | The server serves up the right file, but browsers fetch the new version because, as far as they're concerned, this looks like a brand new file that they haven't got in their cache. 34 | 35 | That was a terrible explanation, wasn't it? I now have even more appreciation for how clearly and concise this article is. 36 | - name: unhelpful 37 | context: 38 | id: c0002 39 | href: https://paulrobertlloyd.com/ 40 | author: Paul Robert Lloyd 41 | datetime: "2012-12-12T12:24:48-00:00" 42 | mods: [unhelpful] 43 | avatar: 44 | src: "https://www.gravatar.com/avatar/15091a37bacfa4bdd011282627eaca2b?s=96" 45 | size: 64 46 | content: | 47 | You fool! Everything you've written here is wrong! 48 | -------------------------------------------------------------------------------- /src/components/common/section/section.config.yaml: -------------------------------------------------------------------------------- 1 | collated: true 2 | default: topic 3 | variants: 4 | - name: topic 5 | context: 6 | id: code 7 | title: "[Code](/topic/code/)" 8 | mods: [topic] 9 | icon: topic-code 10 | content: | 11 | 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. 12 | - name: archive 13 | context: 14 | id: 2013 15 | title: "[2013](/2013/)" 16 | content: | 17 | 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. 18 | - name: author 19 | context: 20 | id: author 21 | title: "About the author" 22 | content: | 23 | Drew McLellan is lead developer on your favourite content management systems, [Perch and Perch Runway](https://grabaperch.com/). He is Director and Senior Developer at edgeofmyseat.com in Bristol, England, and is formerly Group Lead at the Web Standards Project. When not publishing 24 ways, Drew keeps a [personal site](http://allinthehead.com/) covering web development issues and themes, [takes photos](https://flickr.com/drewm/), [tweets a lot](https://twitter.com/drewm) and tries to stay upright on his bicycle. 24 | 25 | Photo: [James Duncan Davidson](http://duncandavidson.com/) 26 | 27 | More articles by Drew 28 | - name: sponsor 29 | context: 30 | id: sponsor 31 | title: "Brought to you by" 32 | mods: [sponsor] 33 | component: promo 34 | - name: comments 35 | context: 36 | id: comments 37 | title: "2 Comments" 38 | content: | 39 | Comments are ordered by helpfulness, as indicated by you. Help us pick out the gems and discourage asshattery by voting on notable comments. 40 | 41 | Got something to add? Leave a comment below 42 | comments: 43 | - "@comment" 44 | - "@comment--unhelpful" 45 | - name: related 46 | context: 47 | id: related 48 | title: "Related articles" 49 | mods: [related] 50 | component: listing 51 | - name: authors 52 | context: 53 | id: a 54 | title: "A" 55 | component: listing--authors 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "24ways-frontend", 3 | "version": "2017.0.0", 4 | "description": "Bits, the front-end component library for 24 ways", 5 | "keywords": [ 6 | "styleguide", 7 | "patterns", 8 | "library" 9 | ], 10 | "homepage": "http://bits.24ways.org", 11 | "bugs": "https://github.com/24ways/frontend/issues", 12 | "license": "SEE LICENSE IN LICENSE", 13 | "author": "Paul Robert Lloyd", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/24ways/frontend.git" 17 | }, 18 | "main": "fractal.config.js", 19 | "scripts": { 20 | "prestart": "npm install", 21 | "start": "node_modules/.bin/gulp start", 22 | "build": "node_modules/.bin/gulp build", 23 | "dev": "node_modules/.bin/gulp dev", 24 | "publish": "node_modules/.bin/gulp publish", 25 | "test": "xo || stylelint src/**/*.css" 26 | }, 27 | "browserslist": [ 28 | ">2%" 29 | ], 30 | "stylelint": { 31 | "extends": "stylelint-config-standard", 32 | "rules": { 33 | "property-no-unknown": [ 34 | true, 35 | { 36 | "ignoreProperties": [ 37 | "font-range" 38 | ] 39 | } 40 | ] 41 | } 42 | }, 43 | "xo": { 44 | "space": true, 45 | "envs": "browser" 46 | }, 47 | "dependencies": { 48 | "@frctl/fractal": "^1.3.0", 49 | "@frctl/mandelbrot": "^1.4.0", 50 | "@frctl/nunjucks": "^2.0.2", 51 | "@rollup/plugin-commonjs": "^13.0.0", 52 | "@rollup/plugin-node-resolve": "^8.1.0", 53 | "autoprefixer": "^9.8.4", 54 | "cssnano": "^4.1.10", 55 | "del": "^5.1.0", 56 | "fontfaceobserver": "^2.1.0", 57 | "gulp": "^4.0.2", 58 | "gulp-gh-pages": "^0.5.0", 59 | "gulp-imagemin": "^7.1.0", 60 | "gulp-postcss": "^8.0.0", 61 | "gulp-sourcemaps": "^2.6.5", 62 | "gulp-util": "^3.0.0", 63 | "markdown-it": "^11.0.0", 64 | "markdown-it-abbr": "^1.0.0", 65 | "markdown-it-footnote": "^3.0.2", 66 | "nunjucks-date": "^1.5.0", 67 | "postcss-apply": "^0.12.0", 68 | "postcss-assets": "^5.0.0", 69 | "postcss-calc": "^7.0.2", 70 | "postcss-color-function": "^4.1.0", 71 | "postcss-custom-media": "^7.0.8", 72 | "postcss-custom-properties": "^9.1.1", 73 | "postcss-easy-import": "^3.0.0", 74 | "postcss-map": "^0.11.0", 75 | "postcss-media-minmax": "^4.0.0", 76 | "postcss-nested": "4.2.3", 77 | "postcss-responsive-type": "^1.0.0", 78 | "postcss-simple-vars": "^5.0.2", 79 | "prismjs": "^1.20.0", 80 | "rollup": "^2.20.0", 81 | "rollup-plugin-closure-compiler-js": "^1.0.0" 82 | }, 83 | "devDependencies": { 84 | "stylelint": "^13.6.1", 85 | "stylelint-config-standard": "^20.0.0", 86 | "xo": "^0.32.1" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/assets/scripts/modules/focusing.js: -------------------------------------------------------------------------------- 1 | // Thanks to https://github.com/edenspiekermann/a11y-dialog for focus trapping 2 | 3 | const focusableElements = [ 4 | 'a[href]', 5 | 'area[href]', 6 | 'input:not([disabled])', 7 | 'select:not([disabled])', 8 | 'textarea:not([disabled])', 9 | 'button:not([disabled])', 10 | 'iframe', 11 | 'object', 12 | 'embed', 13 | '[contenteditable]', 14 | '[tabindex]:not([tabindex^="-"])' 15 | ]; 16 | 17 | function toArray(collection) { 18 | return Array.prototype.slice.call(collection); 19 | } 20 | 21 | function $$(selector, context) { 22 | return toArray((context || document).querySelectorAll(selector)); 23 | } 24 | 25 | function getFocusableChildren(node) { 26 | return $$(focusableElements.join(','), node).filter(child => { 27 | return Boolean(child.offsetWidth || child.offsetHeight || child.getClientRects().length); 28 | }); 29 | } 30 | 31 | function createFirstFocusableChild(node) { 32 | const newDiv = document.createElement('div'); 33 | newDiv.setAttribute('tabindex', '0'); 34 | newDiv.style.cssText = 'outline:none;'; 35 | const firstChild = node.firstChild; 36 | firstChild.before(newDiv); 37 | return newDiv; 38 | } 39 | 40 | function getCurrentFocusable(node, event) { 41 | const focusableChildren = getFocusableChildren(node); 42 | let focusableElement; 43 | 44 | if (focusableChildren.length > 0) { 45 | const focusedItemIndex = focusableChildren.indexOf(safeActiveElement()); 46 | if (event.shiftKey && focusedItemIndex === 0) { 47 | focusableElement = focusableChildren[focusableChildren.length - 1]; 48 | } else if (!event.shiftKey && focusedItemIndex === focusableChildren.length - 1) { 49 | focusableElement = focusableChildren[0]; 50 | } 51 | } 52 | 53 | return focusableElement; 54 | } 55 | 56 | function trapTabKey(node, event) { 57 | const focusableElement = getCurrentFocusable(node, event); 58 | if (focusableElement) { 59 | focusableElement.focus(); 60 | event.preventDefault(); 61 | } 62 | } 63 | 64 | export function safeActiveElement() { 65 | try { 66 | return document.activeElement; 67 | } catch {} 68 | } 69 | 70 | export function bindKeypress(isShown, onExit, node, event) { 71 | if (isShown && event.which === 27) { 72 | event.preventDefault(); 73 | onExit(); 74 | } 75 | 76 | if (isShown && event.which === 9) { 77 | trapTabKey(node, event); 78 | } 79 | } 80 | 81 | export function setInitialFocus(node) { 82 | const firstFocusableChild = getFocusableChildren(node)[0] || createFirstFocusableChild(node); 83 | if (firstFocusableChild) { 84 | firstFocusableChild.focus(); 85 | } 86 | } 87 | 88 | export function removeFocus(node) { 89 | const focusableChildren = getFocusableChildren(node); 90 | if (focusableChildren.length > 0) { 91 | const focusedItemIndex = focusableChildren.indexOf(safeActiveElement()); 92 | if (focusedItemIndex !== -1) { 93 | focusableChildren[focusedItemIndex].blur(); 94 | } 95 | } 96 | } 97 | 98 | export function maintainFocus(isShown, node, event) { 99 | if (isShown && !node.contains(event.target)) { 100 | setInitialFocus(node); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/assets/styles/helpers/_typography.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | Typography mixins 3 | ===================================================== */ 4 | 5 | :root { 6 | /* Title: Typically used on titles of containers and modules */ 7 | --typeset-title: { 8 | font-size: responsive 2rem 4rem; 9 | font-range: 30rem 60rem; 10 | font-weight: 700; 11 | font-variant: common-ligatures lining-nums; 12 | letter-spacing: -0.03125em; 13 | line-height: 0.95; 14 | 15 | @media print { 16 | font-size: 3rem; 17 | } 18 | } 19 | 20 | --typeset-summary-title: { 21 | font-size: 1.25rem; 22 | font-weight: 700; 23 | font-variant: common-ligatures lining-nums; 24 | letter-spacing: -0.03125em; 25 | line-height: 0.95; 26 | } 27 | 28 | /* Prose: Typically used for running text */ 29 | --typeset-prose: { 30 | font-family: serif; 31 | font-variant: common-ligatures oldstyle-nums; 32 | line-height: 1.5; 33 | letter-spacing: -0.0125em; 34 | hyphens: auto; 35 | hanging-punctuation: first; 36 | 37 | .fonts-loaded & { 38 | font-family: map(fonts, family-serif); 39 | } 40 | } 41 | 42 | /* Prose: */ 43 | --typescale-prose: { 44 | font-size: responsive 0.9375rem 1.25rem; 45 | font-range: 30rem 60rem; 46 | 47 | @media print { 48 | font-size: 0.9375rem; 49 | } 50 | } 51 | 52 | /* Heading: Typically used for headings within areas of prose content */ 53 | --typeset-heading: { 54 | font-family: sans-serif; 55 | font-variant: common-ligatures lining-nums; 56 | line-height: 1.25; 57 | letter-spacing: -0.025em; 58 | 59 | .fonts-loaded & { 60 | font-family: map(fonts, family-sans); 61 | } 62 | } 63 | 64 | /* Lede: Typically used for introductory text */ 65 | --typeset-lede: { 66 | font-size: responsive 1.125rem 1.5rem; 67 | font-range: 30rem 60rem; 68 | font-family: sans-serif; 69 | font-weight: 400; 70 | font-variant: common-ligatures oldstyle-nums; 71 | line-height: 1.25; 72 | letter-spacing: -0.025em; 73 | 74 | @media print { 75 | font-size: 1.125rem; 76 | } 77 | 78 | .fonts-loaded & { 79 | font-family: map(fonts, family-sans); 80 | } 81 | } 82 | 83 | /* Caption: Typically used for supporting text */ 84 | --typeset-caption: { 85 | font-size: responsive 0.875rem 1rem; 86 | font-range: 30rem 60rem; 87 | font-family: sans-serif; 88 | font-weight: 400; 89 | font-variant: common-ligatures lining-nums; 90 | line-height: 1.25; 91 | 92 | @media print { 93 | font-size: 0.875rem; 94 | } 95 | 96 | .fonts-loaded & { 97 | font-family: map(fonts, family-sans); 98 | } 99 | } 100 | 101 | /* Label: Typically used on UI labels and controls */ 102 | --typeset-label: { 103 | font-size: 0.8125rem; 104 | line-height: calc(16 / 13); 105 | } 106 | 107 | /* Label: Typically used on UI labels and controls */ 108 | --typeset-ui: { 109 | font-family: sans-serif; 110 | font-size: 1rem; 111 | line-height: calc(20 / 16); 112 | 113 | .fonts-loaded & { 114 | font-family: map(fonts, family-sans); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bits, the front-end component library for 24 ways 2 | 3 | ## Requirements 4 | Bits is built upon [Fractal](https://github.com/frctl/fractal), a tool that enables the rapid development of components, templates and pages. Fractal uses a number of ES6 features, so this project requires [Node.js](https://nodejs.org/) v4.0+ to be installed locally. A global install of Gulp is also recommended. 5 | 6 | ## Installation 7 | To get the project up and running, and view components in the browser, complete the following steps: 8 | 9 | 1. Download and install Node: 10 | 2. Clone this repo: `git clone git@github.com:24ways/frontend.git` (SSH) or `git clone https://github.com/24ways/frontend.git` (HTTPS) 11 | 3. [Optional] Install Gulp globally: `npm install gulp -g` 12 | 4. [Optional] Install Fractal globally: `npm install fractal -g` 13 | 5. Install project dependancies: `npm install` 14 | 6. Start the development environment: `npm start` 15 | 7. Open your browser and visit 16 | 17 | ## Development 18 | When developing components, you may want assets automatically compiled and the browser to refresh automatically. To do this, run the following task: 19 | 20 | * `npm run dev` 21 | 22 | ## Creating a static build 23 | To create a static instance of this project, run the following task: 24 | 25 | * `npm run build` 26 | 27 | This will create a folder called `www`, into which the required files will be created. 28 | 29 | ## Deployment 30 | To make this project publicly accessible, you can deploy a static instance by running the following task: 31 | 32 | * `npm run publish` 33 | 34 | This will publish the contents of `public` to your `gh-pages` branch. 35 | 36 | ## Repo structure 37 | Sometimes it’s helpful to know what all these different files are for… 38 | 39 | ``` 40 | / 41 | ├─ src/ 42 | │ ├─ assets/ # Assets 43 | │ │ ├─ icons/ # Favicon and home screen icons 44 | │ │ ├─ images/ # Raster images (used in component examples) 45 | │ │ ├─ scripts/ # JavaScript files 46 | │ │ ├─ styles/ # CSS files 47 | │ │ └─ vectors/ # SVG images, icons and logos 48 | │ │ 49 | │ ├─ components/ # Components 50 | │ │ ├─ _partials/ # …that render component previews 51 | │ │ ├─ common/ # …that may appear anywhere 52 | │ │ ├─ global/ # …that appear on every page 53 | │ │ ├─ layouts/ # …that govern macro layout 54 | │ │ ├─ scopes/ # …that style undecorated markup 55 | │ │ ├─ templates/ # …that combine components to render page types 56 | │ │ └─ utilities/ # …that have a single purpose/role 57 | │ │ 58 | │ ├─ docs/ # Documentation 59 | │ │ ├─ _partials/ # Partials for rendering documentation 60 | │ │ └─ … # Documentation files 61 | │ │ 62 | │ └─ tokens/ # Design tokens 63 | │ 64 | ├─ tmp/ # Files required for dynamic builds (ignored by Git) 65 | ├─ www/ # Public build (ignored by Git) 66 | │ 67 | ├─ .editorconfig # Code style definitions 68 | ├─ .gitignore # List of files and folders not tracked by Git 69 | ├─ .eslintrc # Linting preferences for JavasScript 70 | ├─ fractal.configjs # Configuration for Fractal 71 | ├─ gulpfile.js # Configuration for Gulp tasks 72 | ├─ LICENSE # License information for this project 73 | ├─ package.json # Project manifest 74 | └─ README.md # This file 75 | ``` 76 | -------------------------------------------------------------------------------- /src/assets/styles/base/_forms.css: -------------------------------------------------------------------------------- 1 | /* ===================================================== 2 | Forms 3 | w3c.github.io/html/sec-forms.html 4 | ===================================================== */ 5 | 6 | /** 7 | * 1. Remove the margin in Firefox and Safari. 8 | * 2. Change text/font properties to `inherit` in all browsers (opinionated). 9 | */ 10 | button, 11 | input, 12 | optgroup, 13 | select, 14 | textarea { 15 | margin: 0; /* 1 */ 16 | font: inherit; /* 2 */ 17 | text-transform: inherit; /* 2 */ 18 | } 19 | 20 | /** 21 | * Remove the default vertical scrollbar in IE. 22 | */ 23 | textarea { 24 | overflow: auto; 25 | } 26 | 27 | /** 28 | * 1. Show the overflow in Edge. 29 | * 2. Show the overflow in Edge, Firefox, and IE. 30 | * 3. Ensure inputs inherit text colour of parent element 31 | */ 32 | button, 33 | input, /* 1 */ 34 | select { /* 2 */ 35 | overflow: visible; 36 | color: inherit; /* 3 */ 37 | } 38 | 39 | /** 40 | * Change the cursor in all browsers (opinionated). 41 | */ 42 | button, 43 | [type=submit], 44 | label { 45 | cursor: pointer; 46 | } 47 | 48 | /** 49 | * 1. Correct the inability to style clickable types in iOS. 50 | * 2. Remove default styling (opinionated). 51 | */ 52 | button, 53 | [type=reset], 54 | [type=submit] { 55 | -webkit-appearance: button; /* 1 */ 56 | background: none; /* 2 */ 57 | border: 0; /* 2 */ 58 | padding: 0; /* 2 */ 59 | } 60 | 61 | /** 62 | * Remove the inner border and padding in Firefox. 63 | * 64 | * 1. Restore the focus styles unset by the previous rule. 65 | */ 66 | button::-moz-focus-inner, 67 | [type=button]::-moz-focus-inner, 68 | [type=reset]::-moz-focus-inner, 69 | [type=submit]::-moz-focus-inner { 70 | border: 0; 71 | padding: 0; 72 | outline: 1px dotted ButtonText; /* 1 */ 73 | } 74 | 75 | /** 76 | * https://thatemil.com/blog/2015/01/03/reset-your-fieldset/ 77 | * 1. Reset minimum width based on content inside fieldset in 78 | * WebKit/Blink/Firefox 79 | * 2. Adjust display mode to ensure 1 works in Firefox 80 | * 3. Remove default margin and border 81 | * 4. Fix margin-collapsing behavior coupled with rendering of legend element 82 | */ 83 | fieldset { 84 | min-width: 0; /* 1 */ 85 | margin: 0; /* 3 */ 86 | border: 0; /* 3 */ 87 | padding: 0.01em 0 0; /* 4 */ 88 | 89 | body:not(:-moz-handler-blocked) & { 90 | display: table-cell; /* 2 */ 91 | } 92 | 93 | @media print { 94 | display: none; 95 | } 96 | } 97 | 98 | /** 99 | * 1. Correct the text wrapping in Edge and IE. 100 | * 2. Correct the color inheritance from `fieldset` elements in IE. 101 | * 3. Remove the padding so developers are not caught out when they zero out 102 | * `fieldset` elements in all browsers. 103 | */ 104 | legend { 105 | box-sizing: border-box; /* 1 */ 106 | color: inherit; /* 2 */ 107 | display: table; /* 1 */ 108 | max-width: 100%; /* 1 */ 109 | padding: 0; /* 3 */ 110 | white-space: normal; /* 1 */ 111 | } 112 | 113 | /** 114 | * 1. Add the correct box sizing in IE 10-. 115 | * 2. Remove the padding in IE 10-. 116 | */ 117 | [type=checkbox], 118 | [type=radio] { 119 | box-sizing: border-box; /* 1 */ 120 | padding: 0; /* 2 */ 121 | } 122 | 123 | /** 124 | * 1. Correct the odd appearance in Chrome and Safari. 125 | * 2. Correct the outline style in Safari. 126 | */ 127 | [type=search] { 128 | -webkit-appearance: none; /* 1 */ 129 | outline-offset: -2px; /* 2 */ 130 | } 131 | 132 | /** 133 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. 134 | */ 135 | [type=search]::-webkit-search-cancel-button, 136 | [type=search]::-webkit-search-decoration { 137 | -webkit-appearance: none; 138 | } 139 | -------------------------------------------------------------------------------- /src/components/_partials/_preview.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | {%- if _target.context.prism -%} 28 | 29 | {%- endif -%} 30 | 31 | 32 | 33 | 34 | 35 | 36 | {%- if _target.context.title -%} 37 | 38 | {%- else -%} 39 | 40 | {%- endif -%} 41 | {%- if _target.context.description -%} 42 | 43 | {%- else -%} 44 | 45 | {%- endif -%} 46 | {%- if _target.context.url -%} 47 | 48 | {%- else -%} 49 | 50 | {%- endif -%} 51 | {%- if _target.context.avatar -%} 52 | 53 | {%- else -%} 54 | 55 | {%- endif -%} 56 | {%- if _target.context.handle -%} 57 | 58 | {%- endif -%} 59 | 60 | 61 | 62 | 65 | 76 | 77 | {{ _target.context.title }} ◆ 24 ways 78 | 79 | 80 | 81 | {{ yield | safe }} 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/components/templates/listing-template/listing-template.config.yaml: -------------------------------------------------------------------------------- 1 | default: home 2 | variants: 3 | - name: home 4 | context: 5 | title: "2015" 6 | content: | 7 | 24 ways is the advent calendar for web geeks. Each day throughout December we publish a daily dose of web design and development goodness to bring you all a little Christmas cheer. [Learn more](/about) 8 | config: 9 | mods: [summaries, inset] 10 | component: summary 11 | reversed: true 12 | items: ["@summary--countdown","@summary--countdown","@summary--countdown","@summary--countdown","@summary--countdown","@summary--countdown","@summary--countdown"] 13 | - name: topic 14 | context: 15 | title: "Business" 16 | titleIcon: topic-business 17 | content: | 18 | 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. 19 | results: true 20 | items: ["@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary", "@summary","@summary","@summary","@summary","@summary","@summary","@summary", "@summary","@summary","@summary"] 21 | section: topics 22 | traverse: 23 | type: topic 24 | next: 25 | url: /topics/code/ 26 | title: "Code" 27 | - name: archive 28 | context: 29 | title: "2013" 30 | content: | 31 | 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. 32 | section: archives 33 | theme: year-2013 34 | traverse: 35 | type: archive 36 | prev: 37 | url: /2012/ 38 | title: "2012" 39 | next: 40 | url: /2014/ 41 | title: "2014" 42 | config: 43 | mods: [summaries, inset] 44 | component: summary 45 | items: 46 | ["@summary--countdown","@summary--countdown","@summary--countdown","@summary--countdown","@summary--countdown"] 47 | - name: author 48 | context: 49 | title: "Rachel Andrew" 50 | section: authors 51 | avatar: 52 | src: https://cloud.24ways.org/authors/rachelandrew280.jpg 53 | size: 280 54 | content: | 55 | Rachel Andrew is a Director of edgeofmyseat.com, a UK web development consultancy and creators of the small content management system, [Perch](https://grabaperch.com/). She is the author of a number of [books](https://rachelandrew.co.uk/books), and is a regular columnist for [A List Apart](http://alistapart.com/author/rachelandrew). 56 | 57 | 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. 58 | 59 | 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. 60 | 61 | Photo: [James Duncan Davidson](http://duncandavidson.com/) 62 | traverse: 63 | type: author 64 | prev: 65 | url: /authors/johnallsopp/ 66 | title: "John Allsopp" 67 | next: 68 | url: /authors/paulannett/ 69 | title: "Paul Annett" 70 | - name: search-results 71 | context: 72 | title: "Search results for ‘design systems’" 73 | results: true 74 | config: 75 | mods: [summaries, inset] 76 | component: summary 77 | items: ["@summary--short","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary","@summary", "@summary","@summary"] 78 | -------------------------------------------------------------------------------- /src/assets/scripts/modules/menu.js: -------------------------------------------------------------------------------- 1 | import * as focusing from './focusing'; 2 | 3 | export function menu() { 4 | // Set timeout so that transitions don't run on page load 5 | const menuElement = document.querySelector('.c-menu'); 6 | setTimeout(() => { 7 | menuElement.classList.remove('no-transition'); 8 | }, 10); 9 | 10 | // Set up menu button 11 | // Mark drawer as being closed 12 | const buttonElement = document.querySelector('.c-menu__button'); 13 | buttonElement.setAttribute('aria-expanded', false); 14 | 15 | // Set up menu drawer 16 | const drawerElement = document.querySelector('.c-menu__drawer'); 17 | drawerElement.setAttribute('role', 'dialog'); 18 | drawerElement.setAttribute('aria-hidden', 'true'); 19 | drawerElement.hidden = true; 20 | 21 | // Set up backdrop 22 | const backdropElement = document.createElement('div'); 23 | document.body.append(backdropElement); 24 | backdropElement.className = 'c-backdrop'; 25 | backdropElement.setAttribute('tabindex', -1); 26 | 27 | // Set up body state 28 | document.body.dataset.menuExpanded = false; 29 | 30 | // Focusing 31 | const focusRegion = drawerElement; 32 | let previousFocusedElement; 33 | 34 | function handleKeypress(event_) { 35 | focusing.bindKeypress(true, () => { 36 | handleRemoveFocus(); 37 | }, focusRegion, event_); 38 | } 39 | 40 | function handleMaintainFocus(event_) { 41 | focusing.maintainFocus(true, focusRegion, event_); 42 | } 43 | 44 | function handleSetFocus() { 45 | previousFocusedElement = focusing.safeActiveElement(); 46 | focusing.setInitialFocus(focusRegion); 47 | document.addEventListener('keydown', handleKeypress); 48 | document.body.addEventListener('focus', handleMaintainFocus, true); 49 | } 50 | 51 | function handleRemoveFocus() { 52 | document.removeEventListener('keydown', handleKeypress); 53 | document.body.removeEventListener('focus', handleMaintainFocus, true); 54 | focusing.removeFocus(focusRegion); 55 | previousFocusedElement.focus(); 56 | } 57 | 58 | // Inertia 59 | function handleInert(state) { 60 | Array.from(document.body.children).forEach(child => { 61 | if (child !== menuElement) { 62 | child.inert = state; 63 | console.log(state); 64 | } 65 | }); 66 | } 67 | 68 | function toggleMenu(state) { 69 | if (state === 'true') { // Open menu 70 | drawerElement.setAttribute('aria-hidden', false); 71 | drawerElement.hidden = false; 72 | handleSetFocus(); 73 | handleInert(true); 74 | } else { // Close menu 75 | setTimeout(() => { 76 | // Leave time for animation to complete before changing state 77 | drawerElement.setAttribute('aria-hidden', true); 78 | drawerElement.hidden = true; 79 | }, 450); 80 | handleRemoveFocus(); 81 | handleInert(false); 82 | } 83 | 84 | // …and only then update the attribute for `aria-expanded` 85 | buttonElement.setAttribute('aria-expanded', state); 86 | 87 | // …and update global value so other elements can query state 88 | document.body.dataset.menuExpanded = state; 89 | } 90 | 91 | if (buttonElement) { 92 | // Remove script and applied style that hides drawer during load 93 | drawerElement.style.display = ''; 94 | document.querySelector('.c-menu__onload').remove(); 95 | 96 | // Toggle drawer on clicking button 97 | buttonElement.addEventListener('click', event_ => { 98 | const state = buttonElement.getAttribute('aria-expanded') === 'false' ? 'true' : 'false'; 99 | toggleMenu(state); 100 | 101 | event_.preventDefault(); 102 | }); 103 | 104 | // Close menu if escape key is pressed 105 | window.addEventListener('keyup', event_ => { 106 | if (event_.key === 'Escape') { 107 | toggleMenu(false); 108 | handleRemoveFocus(); 109 | } 110 | }); 111 | 112 | // Close menu if backdrop (area outside menu) is clicked 113 | backdropElement.addEventListener('click', event_ => { 114 | const state = buttonElement.getAttribute('aria-expanded') === 'false' ? 'true' : 'false'; 115 | toggleMenu(state); 116 | event_.preventDefault(); 117 | }); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/components/global/menu/menu.css: -------------------------------------------------------------------------------- 1 | .c-menu { 2 | .has-js & { 3 | position: fixed; 4 | z-index: map(layers, modal); 5 | 6 | /* Create ‘fake’ banner/sidebar, so drawer can slide from underneath it */ 7 | &::before { 8 | position: fixed; 9 | top: 0; 10 | right: 0; 11 | z-index: calc(map(layers, modal) + 1); 12 | display: block; 13 | content: ''; 14 | 15 | @media (--upto-medium-screen) { 16 | left: 0; 17 | background-color: $color-year--dark; 18 | background-color: var(--color-year--dark, $color-year--dark); 19 | height: $banner-height--small; 20 | width: 100%; 21 | } 22 | 23 | @media (--from-medium-screen) { 24 | background-color: white; 25 | box-shadow: inset 1px 0 $navigation-color--offset; 26 | height: 100%; 27 | width: $navigation-width--large; 28 | } 29 | } 30 | } 31 | 32 | &.no-transition * { 33 | transition: none !important; 34 | } 35 | 36 | @media print { 37 | display: none; 38 | } 39 | } 40 | 41 | .c-menu__button { 42 | @apply --focusable; 43 | 44 | display: block; 45 | position: fixed; 46 | top: 0; 47 | right: 0; 48 | z-index: calc(map(layers, modal) + 1); 49 | 50 | .no-js & { 51 | display: none; 52 | } 53 | 54 | @media (--upto-medium-screen) { 55 | padding: map(sizes, xsmall); 56 | color: white; 57 | 58 | &:hover, 59 | &:active { 60 | color: white; 61 | } 62 | } 63 | 64 | @media (--from-medium-screen) { 65 | @apply --navigation-link; 66 | 67 | padding: map(spaces, medium); 68 | box-shadow: inset 0 -1px 0 $navigation-color--offset; 69 | } 70 | 71 | @media (-ms-high-contrast: active) { 72 | border: 1px solid; 73 | } 74 | 75 | @media print { 76 | display: none; 77 | } 78 | } 79 | 80 | .c-menu__drawer { 81 | display: flex; 82 | flex-direction: column; 83 | background-color: $navigation-color; 84 | 85 | [data-menu-expanded] & { 86 | box-shadow: 0 8px 8px 0 $navigation-color--shadow; 87 | transition: all $navigation-duration ease-in-out; 88 | 89 | @media (--upto-medium-screen) { 90 | width: 100vw; 91 | } 92 | 93 | @media (--from-medium-screen) { 94 | position: fixed; 95 | top: 0; 96 | right: 0; 97 | bottom: 0; 98 | width: 18rem; 99 | } 100 | } 101 | 102 | [data-menu-expanded=false] & { 103 | @media (--upto-medium-screen) { 104 | transform: translateY(-100%); 105 | } 106 | 107 | @media (--from-medium-screen) { 108 | transform: translateX(100%); 109 | } 110 | } 111 | 112 | [data-menu-expanded=true] & { 113 | @media (--upto-medium-screen) { 114 | transform: translateY(0); 115 | } 116 | 117 | @media (--from-medium-screen) { 118 | transform: translateX(calc($navigation-width--large * -1)); 119 | } 120 | } 121 | } 122 | 123 | .c-menu__icon { 124 | height: 2.5rem; 125 | width: 2.5rem; 126 | } 127 | 128 | .c-menu__line { 129 | $lines-animation-duration: 0.2s; 130 | $cross-animation-duration: 0.1s; 131 | 132 | transform-origin: 50% 50%; 133 | 134 | /* Transitions: ☰ */ 135 | &:nth-of-type(1), 136 | &:nth-of-type(2), 137 | &:nth-of-type(5), 138 | &:nth-of-type(6) { 139 | transform: translateY(0); 140 | transition: 141 | transform $lines-animation-duration cubic-bezier(0.8, 0, 0.4, 1.8) $cross-animation-duration, 142 | opacity $lines-animation-duration cubic-bezier(1, 0, 1, 0); 143 | 144 | [aria-expanded=true] & { 145 | opacity: 0; 146 | transition: 147 | transform $lines-animation-duration ease-out, 148 | opacity $lines-animation-duration cubic-bezier(1, 0, 1, 0); 149 | } 150 | } 151 | 152 | /* Transitions: × */ 153 | &:nth-of-type(3), 154 | &:nth-of-type(4) { 155 | transform: rotate(0) scaleX(1); 156 | transition: transform $cross-animation-duration ease-out; 157 | 158 | [aria-expanded=true] & { 159 | transition: transform $cross-animation-duration cubic-bezier(0.8, 0, 0.4, 1.8) $lines-animation-duration; 160 | } 161 | } 162 | 163 | /* Transforms */ 164 | [aria-expanded=true] & { 165 | &:nth-of-type(1) { transform: translateY(25%); } 166 | &:nth-of-type(2) { transform: translateY(12.5%); } 167 | &:nth-of-type(3) { transform: rotate(45deg) scaleX(1.3334); } 168 | &:nth-of-type(4) { transform: rotate(-45deg) scaleX(1.3334); } 169 | &:nth-of-type(5) { transform: translateY(-12.5%); } 170 | &:nth-of-type(6) { transform: translateY(-25%); } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/components/templates/content-template/content-template.config.yaml: -------------------------------------------------------------------------------- 1 | default: about 2 | variants: 3 | - name: about 4 | context: 5 | title: "About 24 ways" 6 | section: about 7 | sections: 8 | - id: background 9 | title: Background 10 | content: | 11 |

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 |

[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 |
—Charles DARWIN, The Descent of Man, and Selection in Relation to Sex, 1871
22 |
23 | 24 | Known as _bumpers_, these short introduction clips help brand a video and make it look a lot more professional. 25 | 26 |
27 | Index cards 28 |
Index cards represent each feature the rental property software would launch with.
29 |
30 | 31 |
32 | 36 |
Index cards represent each feature the rental property software would launch with.
37 |
38 | 39 |
40 | 45 |
Index cards represent each feature the rental property software would launch with.
46 |
47 | 48 |
49 | 55 |
Index cards represent each feature the rental property software would launch with.
56 |
57 | 58 | ## Heading 2 59 | The simplest way to add bumpers to a video would be to edit them on to the start and end of the video file itself. Cooking the bumpers into the video file is easy, but should you ever want to update them it can become a real headache. If the branding needs updating, for example, you'd need to re-edit and re-encode all your videos. Not a fun task. 60 | 61 | Save the document by pressing Ctrl + S 62 | 63 | There are many, many options for recording your screen, including QuickTime Player on Mac OS X (FileNew Screen Recording), GifGrabber, or Giffing Tool on Windows. 64 | 65 | Just sample text. 66 | 67 | ## Heading 2… 68 | ### …followed by a heading 3 69 | What if the bumpers could be added dynamically? That would enable you to use the same bumper for multiple videos (decreasing download time for users who might watch more than one) and to update the bumpers whenever you wanted. You could change them seasonally, update them for special promotions, run different advertising slots, perform multivariate testing, or even target different bumpers to different users. 70 | 71 | [View an example](#) 72 | 73 | > The responsive projects I've worked on have had a lot of success combining design and development into one hybrid phase, bringing the two teams into one highly collaborative group. 74 | > 75 | > * A list inside a blockquote 76 | > * Is a very fine thing 77 | > 78 | > A lot of success combining design and development into one hybrid phase, bringing the two teams into one highly collaborative group. 79 | > 80 | > 1. How about an ordered list 81 | > 2. Inside a blockquote, too? 82 | 83 | ### Heading 3 84 | The trade-off, of course, is that if you dynamically add your bumpers, there's a chance that a user in a given circumstance might not see the bumper. For example, if the main video feature was uploaded to YouTube, you'd have no way to control the playback. As always, you need to weigh up the pros and cons and make your choice. 85 | 86 | #### Heading 4 87 | If you wanted to dynamically add bumpers to your HTML5 video, how would you go about it? That was the question I found myself needing to answer for this particular client project. 88 | 89 | ##### Heading 5 90 | My initial thought was to treat it just like an image slideshow. If I were building a slideshow that moved between images, I'd use CSS absolute positioning with `z-index` to stack the images up on top of each other in a pile, with the first image on top. To transition to the second image, I'd use JavaScript to fade the top image out, revealing the second image beneath it. 91 | 92 |
93 |
94 | 95 |
96 |
Example of responsive video embed.
97 |
98 | 99 | Now that video is just a native object in the DOM, just like an image, why not do the same? Stack the videos up with the opening bumper on top, listen for the video's `onended` event, and fade it out to reveal the main feature behind. Good idea, right? 100 | 101 | | The Very Best `Eggnog` | Serves 12 | Serves 24 | 102 | |--------------------------------|-----------------|-----------| 103 | | Milk | 1 quart | 2 quart | 104 | | Cinnamon Sticks | 1 | 2 | 105 | | Vanilla Bean, Split | 1 | 2 | 106 | | Cloves | 5 | 10 | 107 | | Mace | 10 | 20 | 108 | | Egg Yolks | 12 | 24 | 109 | | Cups Sugar | 1 ½ cups | 3 cups | 110 | | Dark Rum | 1 ½ cups | 3 cups | 111 | | Brandy | 1 ½ cups | 3 cups | 112 | | Vanilla | 1 tbsp | 2 tbsp | 113 | |
  • Light Cream
  • Double Cream
| 1 quart | 2 quart | 114 | 115 | Remember that this is the web. It's never going to be that easy. The problem here is that many non-desktop devices use native, dedicated video players. Think about watching a video on a mobile phone -- when you play the video, the phone often goes full-screen in its native player, leaving the web page behind. There's no opportunity to fade or switch `z-index`, as the video isn't being viewed in the page. Your page is left powerless. Powerless! 116 | 117 | iOS full-screen media player 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 `` elements, and perform a rollover by modifying the `src` attribute of the image. 124 | 125 |
126 | Index cards 127 |
Index cards represent each feature the rental property software would launch with.
128 |
129 | 130 | Turns out, this old trick of modifying the source can help us out with video, too. In most cases, modifying the `src` attribute of a `