diff --git a/.gitignore b/.gitignore index e8d7f08b..278c5ec6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ old-versions .Trashes ehthumbs.db Thumbs.db +docs/public diff --git a/docs.zip b/docs.zip new file mode 100644 index 00000000..28a07149 Binary files /dev/null and b/docs.zip differ diff --git a/docs/.codespace.json b/docs/.codespace.json new file mode 100644 index 00000000..69d5e32b --- /dev/null +++ b/docs/.codespace.json @@ -0,0 +1 @@ +{"engine": "zola"} \ No newline at end of file diff --git a/docs/assets/blueprint.png b/docs/assets/blueprint.png deleted file mode 100644 index d8facfaa..00000000 Binary files a/docs/assets/blueprint.png and /dev/null differ diff --git a/docs/assets/cerberus-docs.css b/docs/assets/cerberus-docs.css deleted file mode 100644 index d458b716..00000000 --- a/docs/assets/cerberus-docs.css +++ /dev/null @@ -1,467 +0,0 @@ -/* Fonts */ - - -/* Reset */ -html, body, div, span, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -abbr, address, cite, code, -del, dfn, em, img, ins, kbd, q, samp, -small, strong, sub, sup, var, -b, i, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, figcaption, figure, -footer, header, hgroup, menu, nav, section, summary, -time, mark, audio, video { - margin:0; - padding:0; - border:0; - outline:0; - font-size:100%; - vertical-align:baseline; - background:transparent; -} -body { - line-height: 1; - margin:0; - padding:0; -} -ol, ul { - list-style: none; - padding: 0; -} -table { - border-collapse: collapse; - border-spacing: 0; -} -.cf:before, -.cf:after { - content: ""; - display: table; -} -.cf:after { - clear: both; -} - -/* Core CSS */ -body { - font-family: 'Muli', sans-serif; - color: #555; - font-size: 17px; - line-height: 1.4; -} -h2 { - font-family: 'Open Sans', sans-serif; - font-weight: 800; - font-size: 1.75em; - text-transform: uppercase; - color: #111; - margin-bottom: 1.5em; -} -h3 { - font-family: 'Open Sans', sans-serif; - font-weight: 800; - font-size: 1.4em; - color: #111; - margin-bottom: 1.5em; -} -p { - margin-bottom: 1.5em; -} -strong { - color: #111; -} -a, -a:visited { - color: #48b586; -} -a:hover, -a:focus, -a:active { - color: #207c6f; -} -code { - background: #eee; - padding: 2px 4px; - font-family: monospace; - font-size: 14px; - font-weight: bold; -} -.button { - display: inline-block; - letter-spacing: .025em; - border-radius: 3px; - text-decoration: none; - line-height: 1; - font-size: 15px; - padding: 15px 25px; - border: 1px solid transparent; - transition: all 150ms ease-in; -} -hr { - height: 3px; - border: 0; - width: 100%; - display: block; - margin: 3em 0 4.5em; - background: #f4f4f4; -} -.fluid { - max-width: 100% !important; - height: auto; -} - -/* Base Layout */ -.outside { - text-align: center; -} -.inside { - margin: 0 auto; - width: 1100px; - text-align: left; - position: relative; -} - -/* Title */ -.title { - background: #215a78; /* Old browsers */ - background: -moz-linear-gradient(top, #215a78 0%, #207c6f 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#215a78), color-stop(100%,#207c6f)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #215a78 0%,#207c6f 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #215a78 0%,#207c6f 100%); /* Opera 11.10+ */ - background: -ms-linear-gradient(top, #215a78 0%,#207c6f 100%); /* IE10+ */ - background: linear-gradient(to bottom, #57545d 0%,#3d3a41 100%); /* W3C */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#215a78', endColorstr='#207c6f',GradientType=0 ); /* IE6-9 */ - color: #fff; - position: relative; - overflow: hidden; -} -.blueprint { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-image: url("blueprint.png"); -} -.title-text { - padding: 100px 0; - position: relative; - z-index: 1; - float: left; - width: 50%; -} -h1 { - font-size: 1.5em; - font-weight: 100; - line-height: 1.2; -} -h1 strong { - font-weight: normal; - color: #fff; - font-weight: 100; - display: block; - font-size: 2.5em; - margin-bottom: .25em; -} -.title-buttons-ul { - margin-top: 3em; -} -.title-buttons-li { - display: inline-block; - margin: 0 .5em 1em 0; -} -.title-button.button-solid, -.title-button.button-solid:visited { - background: #48b586; - color: #fff; -} -.title-button.button-solid:hover, -.title-button.button-solid:active, -.title-button.button-solid:focus { - background: #fff; - color: #111; -} -.title-button.button-outline, -.title-button.button-outline:visited { - color: #48b586; - border-color: #48b586; -} -.title-button.button-outline:hover, -.title-button.button-outline:active, -.title-button.button-outline:focus { - border-color: #fff; - color: #fff; -} -.title-figure { - position: absolute; - top: 50px; - right: 0; - width: 50%; - text-align: center; -} - -/* Docs (Body) */ -/* Table of Contents */ -.toc { - float: left; - width: 25%; -} -.toc-padding { - padding: 2.5em 0; -} -.toc-ul { - border-top: 4px double #ddd; - padding: 2.5em 1em; -} -.toc-li { - padding: 8px 0; -} -.toc-a, -.toc-a:visited { - font-family: 'Open Sans', sans-serif; - font-weight: 800; - font-size: 13px; - text-transform: uppercase; - letter-spacing: .05em; - color: #222; - text-decoration: none -} -.toc-a:hover { - color: #48b586; -} -.active, -.active:visited, -.active:hover, -.active:active, -.active:focus { - color: #48b586 ; -} -.toc-li.small { - border-left: 2px solid #eee; - padding-left: 10px; - line-height: 1; -} -.toc-li.small a { - font-size: 12px; -} - -/* Sections */ -.sections { - padding: 4.5em 0; - width: 70%; - float: right; -} -.section { - border-top: 4px double #ddd; - padding: 4.5em 0; -} -blockquote { - border-left: 3px solid #48b586; - margin: 0 0 3em 0; - padding: 0 0 0 1.5em; -} -blockquote p { - font-size: 1.5em; - font-weight: 300; - margin: 0; - color: #999; -} -.sections ul { - margin-bottom: 1.5em; -} -.sections li { - list-style: disc; - margin: 0 0 1em 1em; -} -.row { - width: 100%; - padding-top: 1em; - margin-bottom: 1.5em; - background: #f8f8f8; -} -.col-1-2 { - width: 48%; - margin-left: 2%; - float: left; -} -.col-1-3 { - width: 30%; - margin-left: 3%; - float: left; -} -.col-1-2 li, -.col-1-3 li { - font-size: .85em; -} -.sections .li-title { - list-style: none; - margin-left: 0; - font-weight: 700; - color: #111; -} -.sections .buttons-ul { - margin-top: 3em; -} -.sections .buttons-li { - display: inline-block; - margin: 0 .5em 0 0; - list-style: none; -} -.sections-button.button-solid, -.sections-button.button-solid:visited { - background: #111; - color: #fff; -} -.sections-button.button-solid:hover, -.sections-button.button-solid:active, -.sections-button.button-solid:focus { - background: #48b586; -} -.sections-button.button-outline, -.sections-button.button-outline:hover { - color: #111; - border-color: #111; -} -.sections-button.button-outline:hover, -.sections-button.button-outline:active, -.sections-button.button-outline:focus { - color: #48b586; - border-color: #48b586; -} -.figure-wireframe { - display: block; - margin-bottom: 3em; - text-align: center; -} -.caption { - display: block; - font-size: 14px; - color: #ccc; - margin-top: 10px; -} -.warning { - background: #fcf8e3; - border: 1px solid #faebcc; - color: #946d3b; - padding: 10px 15px; - border-radius: 3px; -} - -/* Icons */ -@font-face { - font-family: 'fontello'; - src: url('fontello.eot?73648191'); - src: url('fontello.eot?73648191#iefix') format('embedded-opentype'), - url('fontello.woff?73648191') format('woff'), - url('fontello.ttf?73648191') format('truetype'), - url('fontello.svg?73648191#fontello') format('svg'); - font-weight: normal; - font-style: normal; -} - [class^="icon-"]:before, [class*=" icon-"]:before { - font-family: "fontello"; - font-style: normal; - font-weight: normal; - speak: none; - display: inline-block; - text-decoration: inherit; - width: 1em; - margin-right: .2em; - text-align: center; - font-variant: normal; - text-transform: none; - line-height: 1; -} -.icon-github-circled:before { content: '\e801'; } /* '' */ -.icon-twitter:before { content: '\e802'; } /* '' */ -.icon-code:before { content: '\e803'; } /* '' */ - -/* Media QUeries */ -@media only screen and (max-width: 1100px) { - .inside { - width: 94%; - margin: 0 3%; - } -} - -a.htmlio-ad { - margin-top: 2.5em; - text-decoration: none; - border: 1px solid #eee; - display: grid; - justify-content: center; - flex-direction: column; - grid-template-columns: 180px auto; - transition: 75ms all ease-in; -} -a.htmlio-ad:hover, -a.htmlio-ad:focus, -a.htmlio-ad:active { - background: #e6dee1; - border-color: #ee5291; -} -a.htmlio-ad:hover .htmlio-ad-text .link, -a.htmlio-ad:focus .htmlio-ad-text .link, -a.htmlio-ad:active .htmlio-ad-text .link { - color: #ee5291 -} - -.htmlio-ad-img { - line-height: 0; -} -.htmlio-ad-text { - padding: 15px; -} -.htmlio-ad-text h3 { - text-decoration: none; - font-size: 1.1em; - margin: 0 0 .67em; -} -.htmlio-ad-text p { - font-size: .9em; - margin: 0; -} -.htmlio-ad-text .regular-p { - color: #333; - text-decoration: none; - margin-bottom: .67em; -} -.htmlio-ad-text .link { - text-decoration: underline; -} - -@media only screen and (max-width: 700px) { - .title-text { - width: 100%; - padding: 50px 0; - text-align: center; - } - .title-buttons-li { - margin: 0 .5em 1em; - } - .title-figure { - display: none; - } - .toc { - display: none; - } - .sections { - width: 100%; - float: none; - } - .col-1-2, - .col-1-3 { - width: 100%; - margin: 0 0 3% 3%; - float: none; - } - a.htmlio-ad { - flex-direction: row; - grid-template-columns: none; - } - .htmlio-ad-img { - padding: 10px 0 0 10px; - } -} \ No newline at end of file diff --git a/docs/assets/cerberus-mark.png b/docs/assets/cerberus-mark.png deleted file mode 100644 index 214203e0..00000000 Binary files a/docs/assets/cerberus-mark.png and /dev/null differ diff --git a/docs/assets/favicon.ico b/docs/assets/favicon.ico deleted file mode 100644 index 7b706b74..00000000 Binary files a/docs/assets/favicon.ico and /dev/null differ diff --git a/docs/assets/fontello.eot b/docs/assets/fontello.eot deleted file mode 100644 index 9c3ab4f6..00000000 Binary files a/docs/assets/fontello.eot and /dev/null differ diff --git a/docs/assets/fontello.svg b/docs/assets/fontello.svg deleted file mode 100644 index fa75b8ad..00000000 --- a/docs/assets/fontello.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - -Copyright (C) 2015 by original authors @ fontello.com - - - - - - - - - - \ No newline at end of file diff --git a/docs/assets/fontello.ttf b/docs/assets/fontello.ttf deleted file mode 100644 index 405bf5ec..00000000 Binary files a/docs/assets/fontello.ttf and /dev/null differ diff --git a/docs/assets/fontello.woff b/docs/assets/fontello.woff deleted file mode 100644 index a0ab7158..00000000 Binary files a/docs/assets/fontello.woff and /dev/null differ diff --git a/docs/assets/jquery.sticky.js b/docs/assets/jquery.sticky.js deleted file mode 100644 index 238fcb9d..00000000 --- a/docs/assets/jquery.sticky.js +++ /dev/null @@ -1,175 +0,0 @@ -// Sticky Plugin v1.0.0 for jQuery -// ============= -// Author: Anthony Garand -// Improvements by German M. Bravo (Kronuz) and Ruud Kamphuis (ruudk) -// Improvements by Leonardo C. Daronco (daronco) -// Created: 2/14/2011 -// Date: 2/12/2012 -// Website: http://labs.anthonygarand.com/sticky -// Description: Makes an element on the page stick on the screen as you scroll -// It will only set the 'top' and 'position' of your element, you -// might need to adjust the width in some cases. - -(function($) { - var defaults = { - topSpacing: 0, - bottomSpacing: 0, - className: 'is-sticky', - wrapperClassName: 'sticky-wrapper', - center: false, - getWidthFrom: '', - responsiveWidth: false - }, - $window = $(window), - $document = $(document), - sticked = [], - windowHeight = $window.height(), - scroller = function() { - var scrollTop = $window.scrollTop(), - documentHeight = $document.height(), - dwh = documentHeight - windowHeight, - extra = (scrollTop > dwh) ? dwh - scrollTop : 0; - - for (var i = 0; i < sticked.length; i++) { - var s = sticked[i], - elementTop = s.stickyWrapper.offset().top, - etse = elementTop - s.topSpacing - extra; - - if (scrollTop <= etse) { - if (s.currentTop !== null) { - s.stickyElement - .css('width', '') - .css('position', '') - .css('top', ''); - s.stickyElement.trigger('sticky-end', [s]).parent().removeClass(s.className); - s.currentTop = null; - } - } - else { - var newTop = documentHeight - s.stickyElement.outerHeight() - - s.topSpacing - s.bottomSpacing - scrollTop - extra; - if (newTop < 0) { - newTop = newTop + s.topSpacing; - } else { - newTop = s.topSpacing; - } - if (s.currentTop != newTop) { - s.stickyElement - .css('width', s.stickyElement.width()) - .css('position', 'fixed') - .css('top', newTop); - - if (typeof s.getWidthFrom !== 'undefined') { - s.stickyElement.css('width', $(s.getWidthFrom).width()); - } - - s.stickyElement.trigger('sticky-start', [s]).parent().addClass(s.className); - s.currentTop = newTop; - } - } - } - }, - resizer = function() { - windowHeight = $window.height(); - - for (var i = 0; i < sticked.length; i++) { - var s = sticked[i]; - if (typeof s.getWidthFrom !== 'undefined' && s.responsiveWidth === true) { - s.stickyElement.css('width', $(s.getWidthFrom).width()); - } - } - }, - methods = { - init: function(options) { - var o = $.extend({}, defaults, options); - return this.each(function() { - var stickyElement = $(this); - - var stickyId = stickyElement.attr('id'); - var wrapperId = stickyId ? stickyId + '-' + defaults.wrapperClassName : defaults.wrapperClassName - var wrapper = $('
') - .attr('id', stickyId + '-sticky-wrapper') - .addClass(o.wrapperClassName); - stickyElement.wrapAll(wrapper); - - if (o.center) { - stickyElement.parent().css({width:stickyElement.outerWidth(),marginLeft:"auto",marginRight:"auto"}); - } - - if (stickyElement.css("float") == "right") { - stickyElement.css({"float":"none"}).parent().css({"float":"right"}); - } - - var stickyWrapper = stickyElement.parent(); - stickyWrapper.css('height', stickyElement.outerHeight()); - sticked.push({ - topSpacing: o.topSpacing, - bottomSpacing: o.bottomSpacing, - stickyElement: stickyElement, - currentTop: null, - stickyWrapper: stickyWrapper, - className: o.className, - getWidthFrom: o.getWidthFrom, - responsiveWidth: o.responsiveWidth - }); - }); - }, - update: scroller, - unstick: function(options) { - return this.each(function() { - var unstickyElement = $(this); - - var removeIdx = -1; - for (var i = 0; i < sticked.length; i++) - { - if (sticked[i].stickyElement.get(0) == unstickyElement.get(0)) - { - removeIdx = i; - } - } - if(removeIdx != -1) - { - sticked.splice(removeIdx,1); - unstickyElement.unwrap(); - unstickyElement.removeAttr('style'); - } - }); - } - }; - - // should be more efficient than using $window.scroll(scroller) and $window.resize(resizer): - if (window.addEventListener) { - window.addEventListener('scroll', scroller, false); - window.addEventListener('resize', resizer, false); - } else if (window.attachEvent) { - window.attachEvent('onscroll', scroller); - window.attachEvent('onresize', resizer); - } - - $.fn.sticky = function(method) { - if (methods[method]) { - return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); - } else if (typeof method === 'object' || !method ) { - return methods.init.apply( this, arguments ); - } else { - $.error('Method ' + method + ' does not exist on jQuery.sticky'); - } - }; - - $.fn.unstick = function(method) { - if (methods[method]) { - return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); - } else if (typeof method === 'object' || !method ) { - return methods.unstick.apply( this, arguments ); - } else { - $.error('Method ' + method + ' does not exist on jQuery.sticky'); - } - - }; - $(function() { - setTimeout(scroller, 0); - }); -})(jQuery); -$(document).ready(function(){ - $(".toc-padding").sticky({topSpacing:0}); - }); diff --git a/docs/assets/logo.png b/docs/assets/logo.png deleted file mode 100644 index 4d1cc323..00000000 Binary files a/docs/assets/logo.png and /dev/null differ diff --git a/docs/assets/phone-outline.png b/docs/assets/phone-outline.png deleted file mode 100644 index 9cce1ad0..00000000 Binary files a/docs/assets/phone-outline.png and /dev/null differ diff --git a/docs/assets/scrollIt.js b/docs/assets/scrollIt.js deleted file mode 100644 index c38ec266..00000000 --- a/docs/assets/scrollIt.js +++ /dev/null @@ -1,152 +0,0 @@ -/** - * ScrollIt - * ScrollIt.js(scroll•it•dot•js) makes it easy to make long, vertically scrolling pages. - * - * Latest version: https://github.com/cmpolis/scrollIt.js - * - * License - */ -(function($) { - 'use strict'; - - var pluginName = 'ScrollIt', - pluginVersion = '1.0.3'; - - /* - * OPTIONS - */ - var defaults = { - upKey: 38, - downKey: 40, - easing: 'linear', - scrollTime: 600, - activeClass: 'active', - onPageChange: null, - topOffset : -50 - }; - - $.scrollIt = function(options) { - - /* - * DECLARATIONS - */ - var settings = $.extend(defaults, options), - active = 0, - lastIndex = $('[data-scroll-index]:last').attr('data-scroll-index'); - - /* - * METHODS - */ - - /** - * navigate - * - * sets up navigation animation - */ - var navigate = function(ndx) { - if(ndx < 0 || ndx > lastIndex) return; - - var targetTop = $('[data-scroll-index=' + ndx + ']').offset().top + settings.topOffset + 1; - $('html,body').animate({ - scrollTop: targetTop, - easing: settings.easing - }, settings.scrollTime); - }; - - /** - * doScroll - * - * runs navigation() when criteria are met - */ - var doScroll = function (e) { - var target = $(e.target).closest("[data-scroll-nav]").attr('data-scroll-nav') || - $(e.target).closest("[data-scroll-goto]").attr('data-scroll-goto'); - navigate(parseInt(target)); - }; - - /** - * keyNavigation - * - * sets up keyboard navigation behavior - */ - var keyNavigation = function (e) { - var key = e.which; - if($('html,body').is(':animated') && (key == settings.upKey || key == settings.downKey)) { - return false; - } - if(key == settings.upKey && active > 0) { - navigate(parseInt(active) - 1); - return false; - } else if(key == settings.downKey && active < lastIndex) { - navigate(parseInt(active) + 1); - return false; - } - return true; - }; - - /** - * updateActive - * - * sets the currently active item - */ - var updateActive = function(ndx) { - if(settings.onPageChange && ndx && (active != ndx)) settings.onPageChange(ndx); - - active = ndx; - $('[data-scroll-nav]').removeClass(settings.activeClass); - $('[data-scroll-nav=' + ndx + ']').addClass(settings.activeClass); - }; - - /** - * watchActive - * - * watches currently active item and updates accordingly - */ - var watchActive = function() { - var winTop = $(window).scrollTop(); - - var visible = $('[data-scroll-index]').filter(function(ndx, div) { - return winTop >= $(div).offset().top + settings.topOffset && - winTop < $(div).offset().top + (settings.topOffset) + $(div).outerHeight() - }); - var newActive = visible.first().attr('data-scroll-index'); - updateActive(newActive); - }; - - /* - * runs methods - */ - $(window).on('scroll',watchActive).scroll(); - - $(window).on('keydown', keyNavigation); - - $('body').on('click','[data-scroll-nav], [data-scroll-goto]', function(e){ - e.preventDefault(); - doScroll(e); - }); - - }; -}(jQuery)); - -$(function(){ - $.scrollIt(); -}); - -// This is mainly for the two template links, since ScrollIt cannot cover them. -// http://www.learningjquery.com/2007/10/improved-animated-scrolling-script-for-same-page-links -$(document).ready(function(){ - $('.toc-a').click(function() { - if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') - && location.hostname == this.hostname) { - var $target = $(this.hash); - $target = $target.length && $target - || $('[name=' + this.hash.slice(1) +']'); - if ($target.length) { - var targetOffset = $target.offset().top; - $('html,body') - .animate({scrollTop: targetOffset}, 500); - return false; - } - } - }); -}); diff --git a/docs/assets/wireframe-default.png b/docs/assets/wireframe-default.png deleted file mode 100644 index 7201bc39..00000000 Binary files a/docs/assets/wireframe-default.png and /dev/null differ diff --git a/docs/assets/wireframe-fluid.png b/docs/assets/wireframe-fluid.png deleted file mode 100644 index c1ec7af7..00000000 Binary files a/docs/assets/wireframe-fluid.png and /dev/null differ diff --git a/docs/assets/wireframe-hybrid.png b/docs/assets/wireframe-hybrid.png deleted file mode 100644 index e99b2851..00000000 Binary files a/docs/assets/wireframe-hybrid.png and /dev/null differ diff --git a/docs/assets/wireframe-responsive.png b/docs/assets/wireframe-responsive.png deleted file mode 100644 index 1f8af0c2..00000000 Binary files a/docs/assets/wireframe-responsive.png and /dev/null differ diff --git a/docs/assets/wireframe-with-media-queries.png b/docs/assets/wireframe-with-media-queries.png deleted file mode 100644 index 2be36d19..00000000 Binary files a/docs/assets/wireframe-with-media-queries.png and /dev/null differ diff --git a/docs/assets/wireframe-without-media-queries.png b/docs/assets/wireframe-without-media-queries.png deleted file mode 100644 index a16e93ad..00000000 Binary files a/docs/assets/wireframe-without-media-queries.png and /dev/null differ diff --git a/docs/config.toml b/docs/config.toml new file mode 100644 index 00000000..da30898a --- /dev/null +++ b/docs/config.toml @@ -0,0 +1,11 @@ +# The URL the site will be built for +base_url = "https://example.com" + +# Whether to automatically compile all Sass files in the sass directory +compile_sass = true + +# Highlight all code blocks found +highlight_code = true + +# Which theme to use for the code highlighting. (https://www.getgutenberg.io/documentation/getting-started/configuration/) +highlight_theme = "cheerfully-light" diff --git a/docs/content/best-practices.md b/docs/content/best-practices.md new file mode 100644 index 00000000..f99b9341 --- /dev/null +++ b/docs/content/best-practices.md @@ -0,0 +1,57 @@ ++++ +title = "Best Practices" +description = "Emails don’t need to look the same in every email client, but there are some guidelines to make sure they render properly in email clients and are as accessible as possible." +template = "default-template.html" +[extra] +page_id = "best-practices" ++++ + +# Best Practices + +[Emails don’t need to look the same in every email client](http://doemailshavetolookthesameineveryclient.com/), Emails don’t need to look the same in every email client, but there are some guidelines to make sure they render properly in email clients and are as accessible as possible. + +## General rules and principles + +Cerberus strives to support email clients with low levels of HTML & CSS support (namely Microsoft Outlook, a [few versions of Gmail](https://emails.hteumeuleu.com/trying-to-make-sense-of-gmail-css-support-after-the-2016-update-53c15151063a), and a handful of [mostly non-US clients](https://emails.hteumeuleu.com/should-we-stop-inlining-styles-in-emails-8c3b64f0d407)). It’s safest to code emails like it’s 1999 (s)till). + +That means: + +- CSS2 instead of CSS3 +- ``s instead of `
`s +- Raster images (like PNGs) instead of vector (like SVGs) +- Inline CSS instead of embedded styles or external stylesheets + +[Can I Email?](https://www.caniemail.com/) and [Campaign Monitor’s guide to CSS](https://www.campaignmonitor.com/css/) are good references for HTML & CSS support in email. + +## HTML and CSS + +1. **Use `
` when creating new tables.** This negates any unwanted spacing and borders and tells screen readers to skip over the table’s tags and move straight into the content. +2. **When in doubt, nest another table.** For finer control of your HTML, nest tables when building emails. +3. **Use padding for spacing in table cells.** Margins aren’t fully supported on tables and container elements. +4. **Use margin for typography.** Margins *are* fully supported for headlines, paragraphs, and lists. +5. **Use `align` for layout instead of `float`, `grid`, or `flexbox`.** Floats aren’t supported in Outlook and email clients don’t have good support for modern CSS layout properties in general. +6. **HTML attributes are still relevant.** Most styling is done using CSS. But because some email clients use antiquated rendering engines, they tend to better understand HTML attributes like `align`, `valign`, `height`, and `width`. +7. **Define color as `#ffffff` instead of `#fff` or `rgb(1,2,3)`.** Six-digit hex is supported in inline CSS as well as HTML attributes like `bgcolor` that are still supported in email. +8. **Don’t forget about [preview text](https://stackoverflow.design/email/guidelines/faq#what-is-preview-text?).** We can specify the text that appears beneath subject lines in many email clients. If preview text is not included, this space will be populated by the email’s content. + +## Images + +1. **Save images as PNG, GIF, or JPG instead of SVG.** SVG has almost no support in email, no matter how it’s referenced (inline, Base64, `.svg`). +2. Save images as @2x and scale them down using HTML attributes. Since SVG isn’t supported, a 20x20 raster image coded like `` displays crisply on high-definition screens. + +## Accessibility + +1. Include `role="presentation"` on all tables used for layout. This prevents screen readers from reading aloud the structure of each table cell. +2. **Include `aria-hidden="true"` on presentational elements.** This prevents screen readers from reading aloud something that's not content. +3. **Use HTML1 semantic tags whenever possible.** Tags like `

` and `` allow screen readers to quickly jump from section to section. Tags like `` and `` give text more importance. +4. **Include an `alt` attribute on every image.** Be descriptive and use `alt` to help readers “see” the email if images aren’t displayed. Use an empty `alt=""` for images a screen reader should skip (eg. decorative images). Screenreaders will dictate the filename of images without an `alt` attribute (eg. "icon dash checkmark dot png"). [More on alt text](https://stackoverflow.design/content/examples/alt-text/). +5. **Avoid “Click Here” or “Learn More” link copy.** It helps an email avoid spam filters and gives context about the link to folks using screen readers or dictation software. +6. **Create a plain text version of every email.** It helps us avoid spam filters, some email clients don’t support HTML, and some people just prefer plain text. They also respond better to things like changing font size, family, and color, and work well with screen magnifiers. + +## Testing + +Tools to ensure emails look as they should when they’re sent. + +- [Litmus](https://www.litmus.com/) and [Email on Acid](https://www.emailonacid.com/) allow us to preview screenshots of our emails across 90+ email clients on multiple devices. Both have code editors built in (Eg. [Litmus Builder](https://litmus.com/email-builder)), which helps troubleshoot and fix bugs in actual email clients. +- [Parcel](https://useparcel.com/) - A web-based code editor built specifically for email development. +- [Putsmail](https://putsmail.com/) - Send yourself test emails. diff --git a/docs/content/components.md b/docs/content/components.md new file mode 100644 index 00000000..b9b6a7bc --- /dev/null +++ b/docs/content/components.md @@ -0,0 +1,359 @@ ++++ +title = "Components" +description = "About John Doe, a good human" +template = "default-template.html" +[extra] +page_id = "components" ++++ + +# Components + +## Tables + +`

`s are still the most dependable way to create layouts for HTML emails. + +
+
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
+  <tr>
+    <td> … </td>
+    <td> … </td>
+  </tr>
+</table>
+
+
+ + + + +
Column 1Column 2
+ + + + + + + + + + +
AttributeDescription
role="presentation"Tells screen readers to skip over the table’s tags and move straight into the content.
cellspacing="0"Negates unwanted spacing
cellpadding="0"Negates unwanted padding
border="0"Negates unwanted borders
width="100%"(Optional) Forces table take up all available horizontal space.
+ + +## Spacers + +The best way to control spacing between components in HTML email is to use `padding` (applied to ``’s) and `margin` (applied to `` tags, `

`’s, `

    `’s, `
  1. `’s, etc.). + +However `padding` and `margin` cannot be used reliably to space out ``’s or ``’s. In these cases, it's best to use a spacer to create separation. + +
    <tr>
    +  <td aria-hidden="true" height="30" style="font-size: 0; line-height: 0px;">
    +    &nbsp;
    +  </td>
    +</tr>
    + +
    + + + + + +
    AttributeDescription
    heightSize of the spacer.
    aria-hidden="true"Hide the &nbsp; from screen readers.
    &nbsp;Some email clients will collapse the spacer’s height if there’s no content.
    style="font-size: 0px;line-height: 0px;"Some clients will add additional space inherited from the &nbsp;’s font-size and line-height.
    + +## Typography + +It’s safe and accessible to use semantic HTML tags like ``, `

    `, and `

      ` for text in email just as we do for the web. The main difference in email is that [CSS should be written inline](https://stackoverflow.design/email/guidelines/faq#why-do-we-have-to-write-css-inline?) to specify intended styles (like the color of an anchor tag) and zero out unintended defaults (like the default margin around a `

      ` tag). + +### Headlines + +

      +
      <h1 style="">Heading level 1</h1>
      +<h2 style="">Heading level 2</h2>
      +<h3 style="">Heading level 3</h3>
      +
      + Heading 1 + Heading 2 + Heading 3 +
      +
      + +### Paragraphs + +
      +
      <p style="">Paragraph text</p>
      +<p style="">More paragraph text</p>
      +
      +

      Paragraph text lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer varius eros suscipit, tincidunt leo eget, consequat libero.

      +

      More paragraph text

      +
      +
      + +### Lists + +
      +
      <ul style="padding: 0; margin: 0 0 15px 0; list-style-type: disc;">
      +  <li style="margin: 0 0 10px 30px;">Unordered list item</li>
      +  <li style="margin: 0 0 10px 30px;">Unordered list item</li>
      +  <li style="margin: 0 0 10px 30px;">Unordered list item</li>
      +</ul>
      + 
      +<ol style="padding: 0; margin: 0 0 15px 0; list-style-type: decimal;">
      +  <li style="margin: 0 0 10px 30px;">Ordered list item</li>
      +  <li style="margin: 0 0 10px 30px;">Ordered list item</li>
      +  <li style="margin: 0 0 10px 30px;">Ordered list item</li>
      +</ol>
      +
      +
        +
      • Unordered list item
      • +
      • Unordered list item
      • +
      • Unordered list item
      • +
      +
        +
      1. Ordered list item
      2. +
      3. Ordered list item
      4. +
      5. Ordered list item
      6. +
      +
      +
      + +### Links + +
      +
      <a href="" style="color: teal;">Link text</a>
      +
      + Link text +
      +
      + +#### Auto-detected links + +Some email clients auto-detect certain text strings (like dates, times, and locations) and automatically convert them into hyperlinks. We can’t *remove* the link, but we can make the link *appear* like the text around it by negating a few CSS styles. + +
      +
      <style>
      +  a[x-apple-data-detectors],  /* iOS */
      +  .aBn,  /* Gmail */
      +  .unstyle-auto-detected-links a {
      +    border-bottom: 0 !important;
      +    cursor: default !important;
      +    color: inherit !important;
      +    text-decoration: none !important;
      +    font-size: inherit !important;
      +    font-family: inherit !important;
      +    font-weight: inherit !important;
      +    line-height: inherit !important;
      +  }
      +</style>
      + 
      +<p class="unstyle-auto-detected-links">
      +  Our mailing address is 123 Fake St.
      +</p>
      +
      +

      Our mailing address is 123 Fake St.

      +

      The mail address is a link ☝️

      +
      +
      + + +In this example, some email clients will detect "123 Fake St." as a location and automatically add an unstyled `` tag that links to a maps product like Google Maps or Apple Maps. We can't prevent this from happening, but we can make the link appear like the surrounding body text by including a `unstyle-auto-detected-links` class in the container tag. + + + +### Prevent Text Wrapping + +A non-breaking space (` `) can be used to prevent a group of words from breaking onto multiple lines. Useful for keeping names together and preventing typographic orphans and widows. + +
       I want these&nbsp;words to stay together and prevent&nbsp;widows.
      + +🙏 [Rob Berinti’s TEDC ’15 talk on Typography](https://drive.google.com/open?id=0B2uzG2bvD431aWsyTXRnVG94S3c) + +### Web fonts + +Sometimes we use web fonts in emails to match the aesthetic of a marketing campaign or announcement. Web fonts [don’t have great support in email clients](https://www.campaignmonitor.com/css/text-fonts/font-face/), so here’s how we ensure our web font displays in as many email clients as possible: + +1. Include an external stylesheet at the top of the email’s `` tag. Sites like Google Fonts provide this code. +2. Windows Outlook sometimes chokes on web font references and defaults everything to Times New Roman. To avoid this, wrap the web font reference in a ` -
      -
      -
      - -
      -
      -

      Cerberus -
      - Between mobile, Gmail, and Outlook, HTML email is a three-headed dog from hell. -

      -
      -
      -

      Introduction

      -

      Coding regular emails is hard enough by itself. Making them responsive shouldn’t add to the headache. A few simple, but solid patterns are all that’s needed to optimize emails for small screens.

      -

      That’s what Cerberus is. -

      It’s just a few responsive email patterns that go a long way. The code blocks are compartmentalized so they may be used, removed, combined, and nested to build an email.

      -

      Each template contains code comments and has good support among popular email clients.

      -

      I wrote a blog post that goes into more detail on why I made this in the first place. Cerberus is Responsive Email XX’s successor.

      -
      -
      -

      Things To Know

      -
        -
      • Cerberus is a small collection layout patterns for HTML email. The code is meant to be edited, adapted, and built upon.
      • -
      • The code is compartmentalized and annotated in an effort to explain what’s happening so you can add, edit, and remove code with some confidence.
      • -
      • This has been tested in all “popular” email clients, but not every email client out there. More on that here.
      • -
      -
      -
      -

      The Templates

      -
      -
      - - As the layout narrows, the email content reflows and resizes gracefully. -
      -

      Fluid Template

      -

      Good for simple layouts such as transactional and single column emails.

      -

      This template focuses on a fluid layout that sizes itself using percentage-based widths to shrink horizontally on narrow screens. This email layout does not reconfigure at different screen sizes.

      -

      If you want a basic template to handle rich text and images, this is a good baseline.

      - -
      -
      -
      -
      - - The email layout changes at breakpoints using media queries. -
      -

      Responsive Template

      -

      Good for more complicated, shape-shifting email layouts that work on some mobile clients.

      -

      This template uses media queries to reconfigure the layout for different screen sizes for email clients that support media queries. However, mobile clients that don’t support media queries or the <style> tag will display a shrunk version of the desktop layout instead. This applies to some versions of Gmail (still) and Yahoo, as well as a number of international email clients (more info on that here).

      -

      If you’re already comfortable with media queries, the learning curve is relatively low. If total device coverage isn’t required, you can create a responsive email the same way you create a responsive website.

      - -
      -
      -
      -
      - - The email layout stacks without media queries. -
      -

      Hybrid Template

      -

      This template uses a hybrid approach to reconfigure the layout for different screen sizes for email clients regardless of media query support. At its core, it uses max-width and min-width to impose rigid baselines (allowing some movement) and imposes a fixed, wide width for Outlook who is shackled to desktop anyway. Once a mobile-friendly baseline is set, media queries progressively enhance the email further in clients that support it.

      -

      If you have some email design experience, this template optimizes every popular email client. All the extra Outlook code can make these templates quite large and your maths have to be spot on for multi-column layouts.

      - -
      -
      -
      -

      /archived-versions/

      -

      Templates in the archived-versions folder are not currently being maintained and should be considered unsupported and deprecated. They are kept around for historical purposes. 💫

      -
      -
      -
      -

      Client Support

      -

      Cerberus is tested in the most popular email clients as reported by Litmus and my own email campaigns. I’ve focused on the following clients:

      -
      -
        -
      • Desktop
      • -
      • Outlook 2000/02/03/07/10/13/16 (Windows)
      • -
      • Windows 10 Mail
      • -
      • Outlook 2011/16 (Mac)
      • -
      • Apple Mail 9/10
      • -
      • Thunderbird
      • -
      -
        -
      • Web (Chrome, FF, IE)
      • -
      • Gmail (IMAP, Business
      • -
      • Outlook.com
      • -
      • Yahoo
      • -
      • Aol
      • -
      • Office 365 Web
      • -
      • Comcast
      • -
      • Web.de
      • -
      • GMX.de
      • -
      • freenet.de
      • -
      • T-Online.de
      • -
      • Mail.ru
      • -
      • Terra Mail
      • -
      -
        -
      • Mobile Apps
      • -
      • iOS Mail (iPhone 5 and up, iPad, iPad Mini)
      • -
      • Gmail (iOS+Android, all account types)
      • -
      • Google Inbox (iOS)
      • -
      • Mail (Android 6.0)
      • -
      • Yahoo (iOS)
      • -
      • Outlook (iOS)
      • -
      • Alto Mail (iOS)
      • -
      -
      -

      When I say “tested”, I mean “email doesn’t fall apart”. I don’t mean "Everything is pixel perfect in Outlook" or “I found a way to make media queries work everywhere.” They don’t.

      -

      There are multiple versions of Gmail. Cerberus works pretty well in all of them. Rémi Parmentier created an excellent graphic explaining Gmail’s capabilities in this article.

      -

      Any client not listed above should be considered untested. If you feel I’ve left out a popular email client or can suggest a non-destabilizing fix for one, please submit an issue!

      -

      For what it’s worth, Litmus (who does test every email client) includes Cerberus in its Email Builder, so there’s that.

      -
      -
      -

      Known Issues

      -

      Not necessarily a bug with the code in this repo, but a few things that could trip you up.

      -
        -
      • Some ESPs don’t like URLs in HTML comments (example issue). In this case, you can remove the URLs or even the entire HTML comment.
      • -
      -
      -
      -

      A Word on CSS Inliners

      -

      I recommend against using a CSS inliners with Cerberus. Here’s why:

      -
        -
      • Cerberus is supposed to be simple and should not require a dependency like a CSS inliner.
      • -
      • The placement of Cerberus’s CSS is already optimized. The CSS in the <head> is meant only for email clients that parse CSS in this location. It doesn’t need to be inlined.
      • -
      • There are some CSS selectors like :hover that don’t inline so well and throw errors in some CSS inliners.
      • -
      • Inlining leads to code bloat. Not only does this impact download speeds, but some email clients like Gmail and iOS Outlook truncate messages after they exceed a certain file size.
      • -
      -

      I’m a fan of using snippets, available most code editors. I understand CSS inliners help many folks, but if you use one with Cerberus, please do so at your own risk. I recommend Lee Munroe’s CSS inliner and hear good things about Roadie.

      -
      -
      -

      Contributing

      -

      If you would like to help, please @reply me on Twitter or open an issue to discuss your idea. I’m forever interested in reducing the entropy of this code. I’m most interested in:

      -
        -
      • Fixing existing code that’s broken.
      • -
      • Reducing the amount of code in existing patterns.
      • -
      • Improving the documentation.
      • -
      -

      Please be mindful there are three templates that share a lot of code. Many changes apply to all three.

      -

      Please branch off the main branch instead of master. -

      - -
      -

      Author

      -

      Hello! I’m Ted Goas, the core author and maintainer. I’m a designer & front-end developer working on websites, web apps, and HTML emails. And I enjoy talking shop on Twitter.

      -

      -

      -
      -
      -
      -
      - - - - - - diff --git a/docs/public/best-practices/index.html b/docs/public/best-practices/index.html new file mode 100644 index 00000000..4d7c2a11 --- /dev/null +++ b/docs/public/best-practices/index.html @@ -0,0 +1,112 @@ + + + + + + Best Practices + + + + + + + + + + + + + + + + + + + + + + +
      + + +
      + +

      Best Practices

      +

      Emails don’t need to look the same in every email client, Emails don’t need to look the same in every email client, but there are some guidelines to make sure they render properly in email clients and are as accessible as possible.

      +

      General rules and principles

      +

      Cerberus strives to support email clients with low levels of HTML & CSS support (namely Microsoft Outlook, a few versions of Gmail, and a handful of mostly non-US clients). It’s safest to code emails like it’s 1999 (s)till).

      +

      That means:

      +
        +
      • CSS2 instead of CSS3
      • +
      • <table>s instead of <div>s
      • +
      • Raster images (like PNGs) instead of vector (like SVGs)
      • +
      • Inline CSS instead of embedded styles or external stylesheets
      • +
      +

      Can I Email? and Campaign Monitor’s guide to CSS are good references for HTML & CSS support in email.

      +

      HTML and CSS

      +
        +
      1. Use <table border="0" cellpadding="0" cellspacing="0" role="presentation"> when creating new tables. This negates any unwanted spacing and borders and tells screen readers to skip over the table’s tags and move straight into the content.
      2. +
      3. When in doubt, nest another table. For finer control of your HTML, nest tables when building emails.
      4. +
      5. Use padding for spacing in table cells. Margins aren’t fully supported on tables and container elements.
      6. +
      7. Use margin for typography. Margins are fully supported for headlines, paragraphs, and lists.
      8. +
      9. Use align for layout instead of floatgrid, or flexbox. Floats aren’t supported in Outlook and email clients don’t have good support for modern CSS layout properties in general.
      10. +
      11. HTML attributes are still relevant. Most styling is done using CSS. But because some email clients use antiquated rendering engines, they tend to better understand HTML attributes like alignvalignheight, and width.
      12. +
      13. Define color as #ffffff instead of #fff or rgb(1,2,3). Six-digit hex is supported in inline CSS as well as HTML attributes like bgcolor that are still supported in email.
      14. +
      15. Don’t forget about preview text. We can specify the text that appears beneath subject lines in many email clients. If preview text is not included, this space will be populated by the email’s content.
      16. +
      +

      Images

      +
        +
      1. Save images as PNG, GIF, or JPG instead of SVG. SVG has almost no support in email, no matter how it’s referenced (inline, Base64, .svg).
      2. +
      3. Save images as @2x and scale them down using HTML attributes. Since SVG isn’t supported, a 20x20 raster image coded like <img src="40x40-image.png" height="20" width="20"> displays crisply on high-definition screens.
      4. +
      +

      Accessibility

      +
        +
      1. Include role="presentation" on all tables used for layout. This prevents screen readers from reading aloud the structure of each table cell.
      2. +
      3. Include aria-hidden="true" on presentational elements. This prevents screen readers from reading aloud something that's not content.
      4. +
      5. Use HTML1 semantic tags whenever possible. Tags like <p> and <h> allow screen readers to quickly jump from section to section. Tags like <strong> and <em> give text more importance.
      6. +
      7. Include an alt attribute on every image. Be descriptive and use alt to help readers “see” the email if images aren’t displayed. Use an empty alt="" for images a screen reader should skip (eg. decorative images). Screenreaders will dictate the filename of images without an alt attribute (eg. "icon dash checkmark dot png"). More on alt text.
      8. +
      9. Avoid “Click Here” or “Learn More” link copy. It helps an email avoid spam filters and gives context about the link to folks using screen readers or dictation software.
      10. +
      11. Create a plain text version of every email. It helps us avoid spam filters, some email clients don’t support HTML, and some people just prefer plain text. They also respond better to things like changing font size, family, and color, and work well with screen magnifiers.
      12. +
      +

      Testing

      +

      Tools to ensure emails look as they should when they’re sent.

      +
        +
      • Litmus and Email on Acid allow us to preview screenshots of our emails across 90+ email clients on multiple devices. Both have code editors built in (Eg. Litmus Builder), which helps troubleshoot and fix bugs in actual email clients.
      • +
      • Parcel - A web-based code editor built specifically for email development.
      • +
      • Putsmail - Send yourself test emails.
      • +
      +
      + +
      +
      +
      + + + + + diff --git a/docs/public/components/index.html b/docs/public/components/index.html new file mode 100644 index 00000000..432de9ed --- /dev/null +++ b/docs/public/components/index.html @@ -0,0 +1,367 @@ + + + + + + Components + + + + + + + + + + + + + + + + + + + + + + +
      + + +
      + +

      Components

      +

      Tables

      +

      <table>s are still the most dependable way to create layouts for HTML emails.

      +
      +
      <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
      +  <tr>
      +    <td> … </td>
      +    <td> … </td>
      +  </tr>
      +</table>
      +
      + + + + + +
      Column 1Column 2
      +
      +
      + + + + + + + +
      AttributeDescription
      role="presentation"Tells screen readers to skip over the table’s tags and move straight into the content.
      cellspacing="0"Negates unwanted spacing
      cellpadding="0"Negates unwanted padding
      border="0"Negates unwanted borders
      width="100%"(Optional) Forces table take up all available horizontal space.
      +

      Spacers

      +

      The best way to control spacing between components in HTML email is to use padding (applied to <td>’s) and margin (applied to <h> tags, <p>’s, <ol>’s, <li>’s, etc.).

      +

      However padding and margin cannot be used reliably to space out <table>’s or <tr>’s. In these cases, it's best to use a spacer to create separation.

      +
      <tr>
      +  <td aria-hidden="true" height="30" style="font-size: 0; line-height: 0px;">
      +    &nbsp;
      +  </td>
      +</tr>
      + + + + + + +
      AttributeDescription
      heightSize of the spacer.
      aria-hidden="true"Hide the &nbsp; from screen readers.
      &nbsp;Some email clients will collapse the spacer’s height if there’s no content.
      style="font-size: 0px;line-height: 0px;"Some clients will add additional space inherited from the &nbsp;’s font-size and line-height.
      +

      Typography

      +

      It’s safe and accessible to use semantic HTML tags like <h><p>, and <ul> for text in email just as we do for the web. The main difference in email is that CSS should be written inline to specify intended styles (like the color of an anchor tag) and zero out unintended defaults (like the default margin around a <p> tag).

      +

      Headlines

      +
      +
      <h1 style="">Heading level 1</h1>
      +<h2 style="">Heading level 2</h2>
      +<h3 style="">Heading level 3</h3>
      +
      + Heading 1 + Heading 2 + Heading 3 +
      +
      +

      Paragraphs

      +
      +
      <p style="">Paragraph text</p>
      +<p style="">More paragraph text</p>
      +
      +

      Paragraph text lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer varius eros suscipit, tincidunt leo eget, consequat libero.

      +

      More paragraph text

      +
      +
      +

      Lists

      +
      +
      <ul style="padding: 0; margin: 0 0 15px 0; list-style-type: disc;">
      +  <li style="margin: 0 0 10px 30px;">Unordered list item</li>
      +  <li style="margin: 0 0 10px 30px;">Unordered list item</li>
      +  <li style="margin: 0 0 10px 30px;">Unordered list item</li>
      +</ul>
      + 
      +<ol style="padding: 0; margin: 0 0 15px 0; list-style-type: decimal;">
      +  <li style="margin: 0 0 10px 30px;">Ordered list item</li>
      +  <li style="margin: 0 0 10px 30px;">Ordered list item</li>
      +  <li style="margin: 0 0 10px 30px;">Ordered list item</li>
      +</ol>
      +
      +
        +
      • Unordered list item
      • +
      • Unordered list item
      • +
      • Unordered list item
      • +
      +
        +
      1. Ordered list item
      2. +
      3. Ordered list item
      4. +
      5. Ordered list item
      6. +
      +
      +
      + +
      +
      <a href="" style="color: teal;">Link text</a>
      +
      + Link text +
      +
      + +

      Some email clients auto-detect certain text strings (like dates, times, and locations) and automatically convert them into hyperlinks. We can’t remove the link, but we can make the link appear like the text around it by negating a few CSS styles.

      +
      +
      <style>
      +  a[x-apple-data-detectors],  /* iOS */
      +  .aBn,  /* Gmail */
      +  .unstyle-auto-detected-links a {
      +    border-bottom: 0 !important;
      +    cursor: default !important;
      +    color: inherit !important;
      +    text-decoration: none !important;
      +    font-size: inherit !important;
      +    font-family: inherit !important;
      +    font-weight: inherit !important;
      +    line-height: inherit !important;
      +  }
      +</style>
      + 
      +<p class="unstyle-auto-detected-links">
      +  Our mailing address is 123 Fake St.
      +</p>
      +
      +

      Our mailing address is 123 Fake St.

      +

      The mail address is a link ☝️

      +
      +
      +

      In this example, some email clients will detect "123 Fake St." as a location and automatically add an unstyled <a href=""> tag that links to a maps product like Google Maps or Apple Maps. We can't prevent this from happening, but we can make the link appear like the surrounding body text by including a unstyle-auto-detected-links class in the container tag.

      + +

      Prevent Text Wrapping

      +

      A non-breaking space (&nbsp;) can be used to prevent a group of words from breaking onto multiple lines. Useful for keeping names together and preventing typographic orphans and widows.

      +
       I want these&nbsp;words to stay together and prevent&nbsp;widows.
      +

      🙏 Rob Berinti’s TEDC ’15 talk on Typography

      +

      Web fonts

      +

      Sometimes we use web fonts in emails to match the aesthetic of a marketing campaign or announcement. Web fonts don’t have great support in email clients, so here’s how we ensure our web font displays in as many email clients as possible:

      +
        +
      1. Include an external stylesheet at the top of the email’s <head> tag. Sites like Google Fonts provide this code.
      2. +
      3. Windows Outlook sometimes chokes on web font references and defaults everything to Times New Roman. To avoid this, wrap the web font reference in a <!--[if !mso]> tag (so Windows Outlook ignores it) and define a fallback font for Windows Outlook inside a <!--[if mso]> tag.
      4. +
      5. Lastly, reference the web font at the beginning of the font stack, followed by a system fallback font for email clients that can’t display (and ignore) the web font.
      6. +
      +
      +
      <head>
      +  <!--[if mso]>
      +  <style>
      +    * {
      +      font-family: sans-serif !important;
      +    }
      +  </style>
      +  <![endif]-->
      + 
      +  <!--[if !mso]>
      +  <link href='https://fonts.googleapis.com/css2?family=Lobster&display=swap' rel='stylesheet' type='text/css'>
      +  <![endif]-->
      +</head>
      + 
      +<body>
      +  <p style="font-family: 'Lobster', cursive;">Text in Lobster in email clients that support web fonts.</p>
      +</body>
      +
      + + + +

      Text in Lobster in email clients that support web fonts.

      +

      But seriously please don't actually use Lobster in your email, I'm just trying to show how web fonts work.

      +
      +
      +

      Images

      +

      All email clients can display .png.gif, and .jpg images displayed with the <img> tag. .svg images are not well supported, regardless of how they’re referenced, so avoid using these.

      +

      Most images should be coded responsive by default, meaning they’ll scale down proportionately in small viewports. It’s safest to code all images this way, even if they don’t end up scaling in practice. However if we’re confident that an image will never scale, we can display a non-responsive image using less code.

      +
      +
      <!-- Responsive -->
      +<img src="https://fakeimg.pl/1280x600" width="640" height="" alt="alt_text" border="0" style="width: 100%; max-width: 640px; height: auto; display: block;" class="g-img">
      + 
      +<!-- Static -->
      +<img src="https://fakeimg.pl/256" width="128" height="128" alt="alt_text" border="0" style="display: block;">
      +
      + +
      + +
      +
      + + + + + + + + + + + + +
      NameTypeNotes
      srcattributeUse full https:// absolute path reference.
      heightattributeSet to intended desktop width.
      widthattributeOptional. Use only for images that won’t scale.
      borderattributeAlways set to 0 to avoid blue outlines on image links.
      altattributeAlways include but can be left empty if image is ornamental (Eg. alt="").
      widthinline CSS[Responsive] Always set to 100% for responsive images. Optional for static images.
      max-widthinline CSS[Responsive] Always set to intended desktop width. Optional for static images.
      heightinline CSS[Responsive] Always set to auto for responsive images. Optional for static images.
      displayinline CSSGenerally good practice to use display:block; when possible since it negates a few pixels of unwanted space below images in some clients.
      .g-imgclassAdvisable for images larger than ~300px wide. Prevents gmail from displaying an image download icon over images.
      + +

      Backgrounds

      +

      Background Colors

      +

      Solid background colors are very well supported in email clients using the bgcolor attribute, or background-color or background CSS properties.

      +
      +
      <!-- Using HTML attributes -->
      +<table bgcolor="#9C36B5">
      +  <tr>
      +    <td> … </td>
      +  </tr>
      +</table>
      + 
      +<!-- Using inline CSS -->
      +<table style="background-color: #9C36B5;">
      +  <tr>
      +    <td> … </td>
      +  </tr>
      +</table>
      +
      + My text +
      +
      +

      Background Images

      +

      Background images allow us to place additional HTML content on top of them, one of the few reliable ways to provide layering possibilities in email. A benefit of using background images over foreground images is, when paired with a background color, the HTML content on top of the background image remains accessible even when images are disabled.

      +

      Background images can be complicated to implement in email, as many properties need to be defined once in CSS and again in VML for Windows Outlook and Win10 Mail.

      +
      +
      <td valign="middle" style="background-image: url('https://www.website.com/path/to/image.png'); background-position: center center; background-size: cover; background-color: #000000;">
      +    <!--[if gte mso 9]>
      +    <v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:680px;height:220px;">
      +    <v:fill type="tile" src="https://www.website.com/path/to/image.png" color="#000000"/>
      +    <v:textbox inset="0,0,0,0">
      +    <![endif]-->
      +    <div>
      +      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation">
      +        <tr>
      +          <td style="padding: 60px; color: #ffffff; font-family: arial, sans-serif; font-size: 15px; text-align: center;">
      +            Foreground HTML content.
      +          </td>
      +        </tr>
      +      </table>
      +    </div>
      +    <!--[if gte mso 9]>
      +    </v:textbox>
      +    </v:rect>
      +    <![endif]-->
      +  </td>
      +
      + My text +
      +
      + + + + + + + + + + +
      NameTypeNotes
      background-imageinline CSSAlways set and use full https:// reference.
      background-positioninline CSSOptional to set the size of the image.
      background-sizeinline CSSOptional to set the position of the image.
      background-colorinline CSSAlways set to make foreground HTML legible if background image doesn’t load.
      widthVML in <v:rect>Always set to full container width. VML doesn’t account for padding, adjust as necessary.
      heightVML in <v:rect>Always set to full container height. VML doesn’t account for padding, adjust as necessary.
      srcVML in <v:fill>Always set and use full https:// reference.
      colorVML in <v:fill>Always set to make foreground HTML legible if background image doesn’t load.
      + +

      Buttons

      +

      Buttons are the primary way for users to take action from an email. Buttons should have ample click / tap space and describe their actions. Creating a button that displays consistently across email clients requires multiple HTML tags.

      +
      +
      <style>
      +  .button-td-primary:hover,
      +  .button-td-primary:focus {
      +    background: #555555 !important;
      +  }
      +</style>
      +   
      +<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
      +  <tr>
      +    <td class="button-td button-td-primary" style="border-radius: 4px; background: #222222;">
      +      <a class="button-a button-a-primary" href="https://google.com/" style="background: #222222; border: 1px solid #000000; font-family: sans-serif; font-size: 15px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Primary Button</a>
      +    </td>
      +  </tr>
      +</table>
      +
      + + Button Text +
      +
      + + + + + + +
      NameDescription
      .button-tdProvides transition effects on :hover where supported.
      .button-aProvides transition effects on :hover where supported.
      .button-td-primaryAlso provides :hover and dark mode styles where supported.
      .button-a-primaryAlso provides :hover and dark mode styles where supported.
      + +
      + +
      +
      +
      + + + + + diff --git a/docs/public/dark-mode/index.html b/docs/public/dark-mode/index.html new file mode 100644 index 00000000..ecdf6326 --- /dev/null +++ b/docs/public/dark-mode/index.html @@ -0,0 +1,120 @@ + + + + + + Dark Mode + + + + + + + + + + + + + + + + + + + + + + +
      + + +
      + +

      Dark Mode

      +

      Cerberus includes patterns for dark mode using the prefers-color-scheme media feature to detect if the user’s operating system has requested a light or dark color theme.

      +

      Overview

      +

      Cerberus defines dark mode styles in each template’s . These styles can override styles for components (like buttons and text) and create utility classes that can be applied anywhere in a template’s HTML.

      +

      Examples

      +
      +
      <!DOCTYPE html>
      +<html>
      +  <head>
      +    <style>
      +      @media (prefers-color-scheme: dark) {
      +        .darkmode-bg {
      +          background: #222222 !important;
      +        }
      +        p {
      +          color: #F7F7F9 !important;
      +        }
      +      }
      +    </style>
      +  </head>
      +  <body>
      +    <table>
      +      <tr>
      +        <td style="background-color: #F7F7F9; color: #111;" class="darkmode-bg">
      +          <p>Some text goes here.</p>
      +        </td>
      +      </tr>
      +    </table>
      +  </body>
      +</html>
      +
      + + + + + +
      + Text in light mode + + Text in dark mode +
      +
      +
      +

      In this example, the color of the <p> tag is automatically changed and the background of the <td> is changed using a utility class.

      +

      Cerberus provides a pattern for creating and applying dark mode styles. These dark mode patterns are meant to be edited and built upon.

      +

      Removing Dark Mode

      +

      If you’d rather not include dark mode, you can safely remove the dark mode styles inside the <head>:

      +
      /* Dark Mode Styles : BEGIN */
      +@media (prefers-color-scheme: dark) {
      +  …
      +}
      +/* Dark Mode Styles : END */
      +
      + +
      +
      +
      + + + + + diff --git a/docs/public/design.css b/docs/public/design.css new file mode 100644 index 00000000..84ed25df --- /dev/null +++ b/docs/public/design.css @@ -0,0 +1,7 @@ +*,*::before,*::after{box-sizing:border-box}body,figure,blockquote,dl,dd{margin:0}ul.reset,ol.reset{list-style:none;margin:0;padding:0}html:focus-within{scroll-behavior:smooth}body{min-height:100vh;text-rendering:optimizeSpeed;line-height:1.5}a:not([class]){text-decoration-skip-ink:auto}img,picture{max-width:100%;display:block;object-fit:contain}table{margin:0;padding:0;border:none;border-collapse:collapse;border-spacing:0}@media (prefers-reduced-motion: reduce){*,*::before,*::after{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important}}:root{--pink: #e64980;--purple: #9c36b5;--teal: #087f5b;--light-yellow: #fff3bf;--yellow: #ffe066;--gray-darkest: #0F172A;--gray-darker: #334155;--gray-dark: #64748B;--gray-lightest:#E2E8F0;--gray-lighter: #CBD5E1;--gray-light: #94A3B8;--white: #fff;--bg-site: #fff;--link: var(--pink);--link-hover: var(--fc-darker);--link-visited: var(--purple);--fc-dark: var(--gray-darkest);--fc-medium: var(--gray-darker);--fc-light: var(--gray-dark);--bg-aside: var(--light-yellow);--border-aside: var(--yellow);--spacing2: 0.2rem;--spacing4: 0.4rem;--spacing8: 0.8rem;--spacing12: 1.2rem;--spacing16: 1.6rem;--spacing24: 2.4rem;--spacing36: 3.6rem;--spacing48: 4.8rem;--spacing64: 6.4rem;--spacing96: 9.6rem;--spacing128: 12.8rem;--margin-bottom: var(--spacing16);--section-spacer: var(--spacing36);--fs-xs: 0.875rem;--fs-sm: 1rem;--fs-md: 1.25rem;--fs-lg: 1.5rem;--fs-xl: 2rem;--fs-xxl: 3rem;--lh-sm: 1.3rem;--lh-md: 1.75rem;--lh-lg: 2rem;--ff-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace}html{scroll-behavior:smooth;color-scheme:light dark}body{font-family:'Jost', sans-serif;font-size:var(--fs-md);color:var(--fc-medium);line-height:var(--lh-md);letter-spacing:-0.15px;background-color:var(--bg-site)}h1{font-size:var(--fs-xxl);color:var(--fc-dark)}h2{font-size:var(--fs-xl);color:var(--fc-dark)}h3{font-size:var(--fs-lg);color:var(--fc-dark)}h4{font-size:var(--fs-sm);color:var(--fc-dark)}small{display:inline-block;font-size:var(--fs-xs);line-height:var(--lh-sm);color:var(--fc-light)}.nowrap{white-space:nowrap}article :not(.reset) li:not(:last-child){margin-bottom:var(--spacing4)}a{color:var(--link)}a:hover,a:focus,a:active{color:var(--link-hover)}a:not(.button):visited{color:var(--link-visited)}article h2{padding-top:var(--section-spacer);margin:var(--section-spacer) 0 calc(var(--section-spacer)/2);border-top:1px solid var(--gray-lightest)}article h3{padding-top:calc(var(--section-spacer) * .67)}code{font-size:var(--fs-sm)}@media (prefers-color-scheme: light){.light-hide{display:none !important}}body::before{content:"";display:block;position:fixed;z-index:2;top:0;left:0;right:0;height:4px;background:linear-gradient(90deg, var(--pink) 0%, var(--purple) 50%, blue 100%);background-image:linear-gradient(90deg, var(--purple) 10%, var(--pink) 50%, var(--yellow) 90%)}.site{max-width:84rem;margin:auto;display:grid;grid-template-columns:260px 1fr 260px;grid-gap:var(--spacing36)}.sitenav{padding:var(--spacing24);position:sticky;top:0;align-self:start;height:100vh;border-right:1px solid var(--gray-lightest)}.sitenav .logo{margin-bottom:var(--margin-bottom)}.sitenav .mobile-menu{display:none}.sitenav li:not(.minor){margin-bottom:var(--spacing4)}.sitenav a,.sitenav a:visited{color:var(--gray-darkest);font-size:var(--fs-sm);text-decoration:none}.sitenav a:hover,.sitenav a:focus,.sitenav a:active{color:var(--link)}#page-home .sitenav-home,#page-templates .sitenav-templates,#page-components .sitenav-components,#page-hybrid-responsive .sitenav-hybrid-responsive,#page-outlook .sitenav-outlook,#page-dark-mode .sitenav-dark-mode,#page-best-practices .sitenav-best-practices{position:relative;display:flex;align-items:center;color:var(--link)}#page-home .sitenav-home::before,#page-templates .sitenav-templates::before,#page-components .sitenav-components::before,#page-hybrid-responsive .sitenav-hybrid-responsive::before,#page-outlook .sitenav-outlook::before,#page-dark-mode .sitenav-dark-mode::before,#page-best-practices .sitenav-best-practices::before{display:block;content:"";height:14px;width:8px;position:absolute;left:-14px;top:7px;background:url("images/icon-active.svg") no-repeat}.first-minor{margin-top:var(--spacing12)}.sitenav .minor a,.sitenav .minor a:visited,.sitenav button{color:var(--gray-dark);font-size:var(--fs-xs);display:flex;align-items:center;gap:var(--spacing4)}article{padding-bottom:var(--section-spacer)}header{padding-top:var(--spacing48)}header h1{font-size:var(fs-xxl);margin:0 0 var(--spacing16);letter-spacing:-1px}header p{margin:0;font-size:var(--fs-lg);line-height:var(--lh-lg)}header .button{margin-top:var(--spacing12)}.email-logos{display:flex;flex-wrap:wrap;gap:var(--spacing12);align-items:center;margin:var(--spacing16) 0}code{font-family:var(--ff-mono)}code:not(.hljs){font-size:var(--fs-xs);padding:var(--spacing2) var(--spacing4);background:var(--gray-lightest);background:rgba(0,0,0,0.08);color:var(--teal);opacity:.8;border-radius:4px}pre{white-space:pre-wrap}pre code{border-radius:4px;max-width:100%;overflow-x:auto;display:block;padding:var(--spacing8)}pre code[data-lang]{position:relative}pre code[data-lang]::before{content:attr(data-lang);padding:var(--spacing2) var(--spacing4);background:rgba(255,255,255,0.1);color:#fff;position:absolute;top:0;right:0;font-size:var(--fs-xs);line-height:1.5;letter-spacing:1px}figure pre{margin-bottom:0}figure pre code{border-bottom-right-radius:0;border-bottom-left-radius:0}.example{border-bottom-right-radius:4px;border-bottom-left-radius:4px;border:1px solid var(--gray-lighter);border-top:0;margin-bottom:var(--margin-bottom)}.example *{font-family:sans-serif}.example-padded{padding:var(--spacing16)}.data-table{width:100%;margin-bottom:var(--spacing36);font-size:var(--fs-sm);margin-bottom:var(--margin-bottom)}.data-table th,.data-table td{padding:var(--spacing8) var(--spacing4);line-height:var(--lh-sm)}.data-table th{text-align:left;border-bottom:2px solid var(--gray-lighter)}.data-table td{border-bottom:1px solid var(--gray-lighter)}.data-table code{white-space:nowrap}aside{border-radius:4px;background-color:var(--bg-aside);border:1px solid var(--border-aside);padding:var(--spacing8);margin-bottom:var(--margin-bottom)}aside img{display:block;margin-top:var(--spacing8)}aside[data-emoji]{position:relative;padding-left:var(--spacing36)}aside[data-emoji]::before{content:attr(data-emoji);padding:var(--spacing2) var(--spacing4);position:absolute;top:0;left:0;bottom:0;background:var(--border-aside);padding:0 var(--spacing8);display:flex;align-items:center;font-size:var(--fs-lg)}.buttons{display:flex;gap:var(--spacing4)}.button{display:inline-flex;align-items:center;gap:var(--spacing4);padding:var(--spacing8) var(--spacing12);text-decoration:none;border-radius:4px;font-size:var(--fs-md)}.button-primary{background-color:var(--link);color:var(--white)}.button-primary:hover,.button-primary:focus,.button-primary:active{background-color:var(--gray-darker);color:var(--gray-lightest)}.button-secondary{border:1px solid var(--link)}.button-secondary:hover,.button-secondary:focus,.button-secondary:active{border-color:var(--link-hover)}.email-client-support{border:3px solid var(--gray-lighter);padding:var(--spacing8);border-radius:4px;margin-bottom:var(--margin-bottom);font-size:var(--fs-sm)}.three-columns{display:grid;grid-template-columns:repeat(auto-fit, minmax(12rem, 1fr));grid-gap:1rem;margin-bottom:var(--margin-bottom)}.htmlio-ad{display:flex;gap:var(--spacing12);align-items:center;text-decoration:none;border:1px solid var(--link);padding:var(--spacing2);margin-top:var(--spacing24)}.htmlio-ad .htmlio-ad-img{flex:1 0 auto}.htmlio-ad h4{margin:0;font-size:var(--fs-md)}.htmlio-ad p{font-size:var(--fs-sm);color:var(--gray-dark);line-height:var(--lh-sm);margin:var(--spacing4) 0}.htmlio-ad .link{color:var(--link);text-decoration:underline;font-weight:bold}.on-this-page{position:sticky;top:0;align-self:start;padding-top:var(--spacing64)}.on-this-page h4{margin:0 0 var(--spacing4)}.on-this-page a,.on-this-page a:visited{color:var(--gray-darkest);font-size:var(--fs-sm);text-decoration:none}.on-this-page a:after{content:"";display:table;clear:both}.on-this-page a:hover,.on-this-page a:focus,.on-this.page a:active{color:var(--link)}.on-this-page .toc-H3{font-size:var(--fs-xs);margin-left:var(--spacing8)}@media (max-width: 84rem){.data-table code{white-space:normal}}@media (max-width: 1000px){.site{grid-template-columns:250px 1fr var(--spacing16);grid-gap:var(--spacing16)}.on-this-page{display:none}}@media (max-width: 700px){:root{--section-spacer: var(--spacing12)}.site{display:block}.sitenav,article{padding-left:var(--spacing12);padding-right:var(--spacing12)}.sitenav{border-right:0;border-bottom:1px solid var(--gray-lightest);display:block;height:auto;padding-top:var(--spacing4);padding-bottom:var(--spacing4);background:var(--bg-site);line-height:0;z-index:1}.sitenav .logo{width:80px;height:50px;margin-bottom:0;display:inline-block}.sitenav .mobile-menu{display:block;float:right;background-color:transparent;display:flex;gap:var(--spacing8);align-items:center;border:0;padding:var(--spacing8);border-radius:4px;border:1px solid var(--gray-lightest);cursor:pointer;margin-top:var(--spacing4);color:var(--gray-dark)}.sitenav .menu-icon{position:relative;height:14px;width:18px}.sitenav .icon-bar{height:2px;position:absolute;left:0;border-radius:2px}.sitenav .bar1{top:0;width:100%;background-color:var(--purple)}.sitenav .bar2{top:calc(50% - 1px);width:70%;background-color:var(--pink)}.sitenav .bar3{bottom:0;width:90%;background-color:var(--purple)}.sitenav ul{height:0;overflow:hidden;opacity:0;transition:all 150ms ease-in-out;line-height:var(--lh-md)}.sitenav li:first-child{margin-top:var(--spacing12)}.sitenav li:last-child{margin-bottom:var(--spacing12)}.sitenav ul.open{height:auto;overflow:visible;opacity:1}}@media (prefers-color-scheme: dark){:root{--pink: #e64980;--purple: #da77f2;--teal: #63e6be;--light-yellow: #854D0E;--yellow: #713F12;--white: var(--gray-lightest);--gray-lightest:#0F172A;--gray-lighter: #334155;--gray-light: #64748B;--gray-darkest: #E2E8F0;--gray-darker: #CBD5E1;--gray-dark: #94A3B8;--bg-site: var(--gray-lightest)}.dark-hide{display:none !important}.sitenav,article h2{border-color:var(--gray-lighter)}.dark-fill-white{fill:var(--gray-dark)}code:not(.hljs){background:var(--gray-lighter)}}/*! + Theme: Dracula + Author: Mike Barkmin (http://github.com/mikebarkmin) based on Dracula Theme (http://github.com/dracula) + License: ~ MIT (or more permissive) [via base16-schemes-source] + Maintainer: @highlightjs/core-team + Version: 2021.09.0 +*/.hljs{color:#e9e9f4;background:#282936}.hljs ::selection,.hljs::selection{background-color:#4d4f68;color:#e9e9f4}.hljs-comment{color:#94A3B8;opacity:0.8}.hljs-tag{color:#62d6e8}.hljs-operator,.hljs-punctuation,.hljs-subst{color:#e9e9f4}.hljs-operator{opacity:.7}.hljs-bullet,.hljs-deletion,.hljs-name,.hljs-selector-tag,.hljs-template-variable,.hljs-variable{color:#ea51b2}.hljs-attr,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:#b45bcf}.hljs-class .hljs-title,.hljs-title,.hljs-title.class_{color:#00f769}.hljs-strong{font-weight:700;color:#00f769}.hljs-addition,.hljs-code,.hljs-string,.hljs-title.class_.inherited__{color:#ebff87}.hljs-built_in,.hljs-doctag,.hljs-keyword.hljs-atrule,.hljs-quote,.hljs-regexp{color:#a1efe4}.hljs-attribute,.hljs-function .hljs-title,.hljs-section,.hljs-title.function_,.ruby .hljs-property{color:#62d6e8}.diff .hljs-meta,.hljs-keyword,.hljs-template-tag,.hljs-type{color:#b45bcf}.hljs-emphasis{color:#b45bcf;font-style:italic}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:#00f769}.hljs-meta .hljs-keyword,.hljs-meta-keyword{font-weight:700} diff --git a/docs/public/hybrid-responsive/index.html b/docs/public/hybrid-responsive/index.html new file mode 100644 index 00000000..d93ec82e --- /dev/null +++ b/docs/public/hybrid-responsive/index.html @@ -0,0 +1,108 @@ + + + + + + Hybrid vs. Responsive + + + + + + + + + + + + + + + + + + + + + + +
      + + +
      + +

      Hybrid vs. Responsive

      +

      Responsive

      +

      Media queries are a standard way to optimize layouts for small screen sizes on the web. In the email world, though, there are some small screen scenarios where media queries aren’t supported (eg. some Gmail Apps) or only partially supported (eg. some Android device native mail apps).

      + + + + +
      ClassDescription
      stack-columnMakes table columns 100% wide and stacks them in source order.
      stack-column-centerMakes table columns 100% wide, stacks them in source order, and centers everything inside.
      + +

      Hybrid

      +

      Hybrid design uses inline-blockmax-widthmin-width, and ghost tables stack columns without media queries while imposing a fixed, desktop width for Outlook.

      +
      <tr>
      +  <td>
      +    <!--[if mso]>
      +    <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
      +    <tr>
      +    <td width="300">
      +    <![endif]-->
      +    <div style="display:inline-block; width:100%; min-width:200px; max-width:300px;">
      +      Column 1
      +    </div>
      +    <!--[if mso]>
      +    </td>
      +    <td width="300">
      +    <![endif]-->
      +    <div style="display:inline-block; width:100%; min-width:200px; max-width:300px;">
      +      Column 2
      +    </div>
      +    <!--[if mso]>
      +    </td>
      +    </tr>
      +    </table>
      +    <![endif]-->
      +  </td>
      +</tr>
      +

      In this example, the two columns will display side-by-side on wide, desktop displays and stack on top of each other in narrow, mobile displays.

      +

      Once a hybrid baseline is set, media queries can be used to fine-tune a responsive email layout further in email clients that support it.

      +

      🙏 Fabio Carneiro’s TEDC15 talk files, as well as Action Rocket's and Nicole Merlin's articles on hybrid email design.

      +
      + +
      +
      +
      + + + + + diff --git a/docs/public/images/android.svg b/docs/public/images/android.svg new file mode 100644 index 00000000..ce1f14d8 --- /dev/null +++ b/docs/public/images/android.svg @@ -0,0 +1 @@ + diff --git a/docs/public/images/aol-dark.svg b/docs/public/images/aol-dark.svg new file mode 100644 index 00000000..591b0717 --- /dev/null +++ b/docs/public/images/aol-dark.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/public/images/aol-light.svg b/docs/public/images/aol-light.svg new file mode 100644 index 00000000..7906ecef --- /dev/null +++ b/docs/public/images/aol-light.svg @@ -0,0 +1,2 @@ + + diff --git a/docs/public/images/apple.svg b/docs/public/images/apple.svg new file mode 100644 index 00000000..c8a48945 --- /dev/null +++ b/docs/public/images/apple.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/public/images/figure-button.svg b/docs/public/images/figure-button.svg new file mode 100644 index 00000000..0406f6a4 --- /dev/null +++ b/docs/public/images/figure-button.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/docs/public/images/figure-unsplash.avif b/docs/public/images/figure-unsplash.avif new file mode 100644 index 00000000..930db4e2 Binary files /dev/null and b/docs/public/images/figure-unsplash.avif differ diff --git a/docs/public/images/gmail.svg b/docs/public/images/gmail.svg new file mode 100644 index 00000000..81095fa2 --- /dev/null +++ b/docs/public/images/gmail.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/htmlio-180x150.png b/docs/public/images/htmlio-180x150.png similarity index 100% rename from docs/assets/htmlio-180x150.png rename to docs/public/images/htmlio-180x150.png diff --git a/docs/public/images/icon-active.svg b/docs/public/images/icon-active.svg new file mode 100644 index 00000000..1efe9e80 --- /dev/null +++ b/docs/public/images/icon-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/public/images/icon-darkmode.svg b/docs/public/images/icon-darkmode.svg new file mode 100644 index 00000000..84fd5e6f --- /dev/null +++ b/docs/public/images/icon-darkmode.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/public/images/icon-download.svg b/docs/public/images/icon-download.svg new file mode 100644 index 00000000..42b818dc --- /dev/null +++ b/docs/public/images/icon-download.svg @@ -0,0 +1,2 @@ + + diff --git a/docs/public/images/icon-github.svg b/docs/public/images/icon-github.svg new file mode 100644 index 00000000..9c8ac65b --- /dev/null +++ b/docs/public/images/icon-github.svg @@ -0,0 +1 @@ + diff --git a/docs/public/images/icon-heart.svg b/docs/public/images/icon-heart.svg new file mode 100644 index 00000000..56e08bba --- /dev/null +++ b/docs/public/images/icon-heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/public/images/icon-lightmode.svg b/docs/public/images/icon-lightmode.svg new file mode 100644 index 00000000..86048bf4 --- /dev/null +++ b/docs/public/images/icon-lightmode.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/docs/public/images/logo-cerberus-dark.svg b/docs/public/images/logo-cerberus-dark.svg new file mode 100644 index 00000000..ab6d7fa5 --- /dev/null +++ b/docs/public/images/logo-cerberus-dark.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/docs/public/images/logo-cerberus-light.svg b/docs/public/images/logo-cerberus-light.svg new file mode 100644 index 00000000..623fb5cc --- /dev/null +++ b/docs/public/images/logo-cerberus-light.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/docs/public/images/og-image.png b/docs/public/images/og-image.png new file mode 100644 index 00000000..13567203 Binary files /dev/null and b/docs/public/images/og-image.png differ diff --git a/docs/public/images/outlook.svg b/docs/public/images/outlook.svg new file mode 100644 index 00000000..259dc0a4 --- /dev/null +++ b/docs/public/images/outlook.svg @@ -0,0 +1 @@ + diff --git a/docs/public/images/template-fluid.svg b/docs/public/images/template-fluid.svg new file mode 100644 index 00000000..0e72839f --- /dev/null +++ b/docs/public/images/template-fluid.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/images/template-hybrid.svg b/docs/public/images/template-hybrid.svg new file mode 100644 index 00000000..7640e567 --- /dev/null +++ b/docs/public/images/template-hybrid.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/images/template-responsive.svg b/docs/public/images/template-responsive.svg new file mode 100644 index 00000000..081dbc15 --- /dev/null +++ b/docs/public/images/template-responsive.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/images/yahoo.svg b/docs/public/images/yahoo.svg new file mode 100644 index 00000000..fb00b1bc --- /dev/null +++ b/docs/public/images/yahoo.svg @@ -0,0 +1 @@ + diff --git a/docs/public/index.html b/docs/public/index.html new file mode 100644 index 00000000..d296185a --- /dev/null +++ b/docs/public/index.html @@ -0,0 +1,189 @@ + + + + + + Cerberus - Patterns for Responsive HTML Email Templates + + + + + + + + + + + + + + + + + + + + + + +
      + + +
      + +
      +

      Cerberus

      +

      A few simple, but solid patterns for responsive, accessible HTML email in (almost) every email client.

      + +

      Because between mobile, Gmail, and Outlook, HTML email is a three-headed dog from hell.

      +

      + View on Github

      +
      +

      Overview

      +

      Coding regular emails is hard enough by itself. Making them responsive and accessible shouldn’t add to the headache. A few simple, but solid patterns are all that’s needed to optimize emails for small screens and assistive technologies.

      +

      That’s what Cerberus is. It’s just a few email patterns that go a long way.

      +

      The code blocks are compartmentalized so they may be used, removed, combined, and nested to build an email. Each template is annotated with code comments explaining what’s happening so you can add, edit, and remove code with some confidence.

      +

      Here's an example of a button:

      +
      +
      <!-- Button : BEGIN -->
      +<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
      +<tr>
      +  <td class="button-td button-td-primary" style="border-radius: 4px; background: #222222;">
      +    <a class="button-a button-a-primary" href="#" style="background: #222222; border: 1px solid #000000; font-family: sans-serif; font-size: 15px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Button Text</a>
      +  </td>
      +</tr>
      +</table>
      +<!-- Button : END -->
      +
      + + Button Text +
      +
      +

      Cerberus is plain 'ol HTML and CSS, so it an be used in any framework or email service provider (ESP), though it's not written for any specific framework or ESP.

      +

      I wrote a blog post that goes into more detail on why I made this in the first place.

      +

      Email Client Support

      +

      Cerberus is tested in the most popular email clients as reported by Litmus and my own email campaigns. I’ve focused on the following clients:

      + + +

      Any client not listed above should be considered untested. If you feel I’ve left out a popular email client or can suggest a non-destabilizing fix for one, please submit an issue!

      +

      For what it’s worth, Litmus (who does test every email client) includes Cerberus in its Email Builder, so there’s that. +

      Contributing

      +

      If you would like to help, please @reply me on Twitter or open an issue to discuss your idea. I’m forever interested in reducing the entropy of this code. I’m most interested in:

      +
        +
      • Fixing existing code that’s broken.
      • +
      • Reducing the amount of code in existing patterns.
      • +
      • Improving the documentation.
      • +
      +

      Please be mindful there are three templates that share a lot of code. Many changes apply to all three.

      +

      Please branch off the main branch instead of master.

      +

      Author

      +

      👋 I’m Ted Goas, the core author and maintainer. I’m a product designer & front-end developer with a soft spot for HTML emails. Some day I'd love to combine my professional craft and hobby to work on an email platform.
      + @tedgoastedgoas.com +

      +

      Special thanks

      + + +
      + +
      +
      +

      Ready Made HTML Email Templates

      +

      If you’d prefer something that’s ready out of the box, I vouch for htmlemail.io. Responsive email templates, fully tested, and ready for your ESP or codebase. I know Lee personally and he knows his stuff!

      + +
      +
      + +
      +
      +
      + + + + + diff --git a/docs/public/outlook/index.html b/docs/public/outlook/index.html new file mode 100644 index 00000000..ab7760bb --- /dev/null +++ b/docs/public/outlook/index.html @@ -0,0 +1,132 @@ + + + + + + Outlook Conditional CSS + + + + + + + + + + + + + + + + + + + + + + +
      + + +
      + +

      Outlook Conditional CSS

      +

      Windows Outlook 2003 and above use Microsoft Word as a rendering engine, which can lead to some weird rendering issues. Outlook conditional comments allow us to add bits of HTML that are only read by the Word-based versions of Outlook.

      +

      Basic syntax

      +

      We can use MSO (Microsoft Office) tags to add HTML / CSS anywhere in an email template. This code will be ignored by other email clients. Here’s what it looks like:

      +
      <!--[if mso]>
      +  <table><tr><td>
      +    /* Outlook-specific HTML content goes here. */
      +  </td></tr></table>
      +<![endif]-->
      +

      Only Outlook will render this table.

      +

      MSO tags can also be used to add styles targeting Outlook (Outlook supports CSS in the <head>):

      +
      <!--[if mso]>
      +  <style>
      +    .example-class {
      +      /* Outlook-specific CSS goes here. */
      +    }
      +  </style>
      +<![endif]-->
      +

      It’s the same thing we used to do to target old versions of Internet Explorer, except it targets Microsoft Office.

      +

      Ghost tables

      +

      The main way we use MSO tags in our emails is to create “ghost tables” so hybrid emails don’t fall apart in Outlook. Hybrid design uses inline-blockmax-widthmin-width to stack table columns. Outlook doesn’t support these CSS properties, so we use MSO tags to create “ghost tables” that apply a fixed width just for Outlook.

      +
      <!--[if mso]>
      +<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
      +<tr>
      +<td width="340">
      +<![endif]-->
      +  <div style="display:inline-block; width:100%; min-width:200px; max-width:340px;">
      +    Outlook can’t render the CSS in this DIV but other email clients can, so we wrap this in a ghost table that replicates the DIV’s desktop style. In this case, a container 340px wide.
      +  </div>
      +<!--[if mso]>
      +</td>
      +</tr>
      +</table>
      +<![endif]-->
      +

      Without the ghost table above, Outlook would display the <div> at 100% width. Learn how we use ghost tables to make our emails responsive.

      +

      Targeting specific Outlook versions

      +

      We usually target all versions of Outlook using <!--[if mso]>. But sometimes when testing emails in Litmus, an email looks ok in one Outlook version but is broken in another. It’s not common but it happens, and there are a few ways to target specific versions of Outlook while omitting others.

      +

      Outlook versions

      +

      Using Microsoft Office version numbers allows you to target a specific Outlook version.

      + + + + + + + + + + +
      Outlook version(s)Code
      All Windows Outlook<!--[if mso]> your code <![endif]-->
      Outlook 2000<!--[if mso 9]> your code <![endif]-->
      Outlook 2002<!--[if mso 10]> your code <![endif]-->
      Outlook 2003<!--[if mso 11]> your code <![endif]-->
      Outlook 2007<!--[if mso 12]> your code <![endif]-->
      Outlook 2010<!--[if mso 14]> your code <![endif]-->
      Outlook 2013<!--[if mso 15]> your code <![endif]-->
      Outlook 2016<!--[if mso 16]> your code <![endif]-->
      +

      Conditional logic

      +

      Using operators allows you to create conditional expressions for targeting multiple Outlook versions.

      + + + + + + + + + +
      CodeDescriptionExample
      <gt>greater than<!--[if gt mso 14]>
      Everything above Outlook 2010
      <![endif]-->
      <lt>less than<!--[if lt mso 14]>
      Everything below Outlook 2010
      <![endif]-->
      <gte>greater than or equal to<!--[if gte mso 14]>
      Outlook 2010 and above
      <![endif]-->
      <lte>less than or equal to<!--[if lte mso 14]>
      Outlook 2010 and below
      <![endif]-->
      <|>or<!--[if (mso 12)|(mso 16)]>
      Outlook 2007 / 2016 only
      <![endif]-->
      <!>not<!--[if !mso]>>!-->
      All Outlooks will ignore this
      <![endif]-->
      +
      + +
      +
      +
      + + + + + diff --git a/docs/public/scripts/mailchimp-form-validation.js b/docs/public/scripts/mailchimp-form-validation.js new file mode 100644 index 00000000..91d2e356 --- /dev/null +++ b/docs/public/scripts/mailchimp-form-validation.js @@ -0,0 +1,130 @@ +/*! jQuery v1.9.0 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license */(function(e,t){"use strict";function n(e){var t=e.length,n=st.type(e);return st.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}function r(e){var t=Tt[e]={};return st.each(e.match(lt)||[],function(e,n){t[n]=!0}),t}function i(e,n,r,i){if(st.acceptData(e)){var o,a,s=st.expando,u="string"==typeof n,l=e.nodeType,c=l?st.cache:e,f=l?e[s]:e[s]&&s;if(f&&c[f]&&(i||c[f].data)||!u||r!==t)return f||(l?e[s]=f=K.pop()||st.guid++:f=s),c[f]||(c[f]={},l||(c[f].toJSON=st.noop)),("object"==typeof n||"function"==typeof n)&&(i?c[f]=st.extend(c[f],n):c[f].data=st.extend(c[f].data,n)),o=c[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[st.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[st.camelCase(n)])):a=o,a}}function o(e,t,n){if(st.acceptData(e)){var r,i,o,a=e.nodeType,u=a?st.cache:e,l=a?e[st.expando]:st.expando;if(u[l]){if(t&&(r=n?u[l]:u[l].data)){st.isArray(t)?t=t.concat(st.map(t,st.camelCase)):t in r?t=[t]:(t=st.camelCase(t),t=t in r?[t]:t.split(" "));for(i=0,o=t.length;o>i;i++)delete r[t[i]];if(!(n?s:st.isEmptyObject)(r))return}(n||(delete u[l].data,s(u[l])))&&(a?st.cleanData([e],!0):st.support.deleteExpando||u!=u.window?delete u[l]:u[l]=null)}}}function a(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(Nt,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:wt.test(r)?st.parseJSON(r):r}catch(o){}st.data(e,n,r)}else r=t}return r}function s(e){var t;for(t in e)if(("data"!==t||!st.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(){return!0}function l(){return!1}function c(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function f(e,t,n){if(t=t||0,st.isFunction(t))return st.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return st.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=st.grep(e,function(e){return 1===e.nodeType});if(Wt.test(t))return st.filter(t,r,!n);t=st.filter(t,r)}return st.grep(e,function(e){return st.inArray(e,t)>=0===n})}function p(e){var t=zt.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function d(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function h(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function g(e){var t=nn.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function m(e,t){for(var n,r=0;null!=(n=e[r]);r++)st._data(n,"globalEval",!t||st._data(t[r],"globalEval"))}function y(e,t){if(1===t.nodeType&&st.hasData(e)){var n,r,i,o=st._data(e),a=st._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)st.event.add(t,n,s[n][r])}a.data&&(a.data=st.extend({},a.data))}}function v(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!st.support.noCloneEvent&&t[st.expando]){r=st._data(t);for(i in r.events)st.removeEvent(t,i,r.handle);t.removeAttribute(st.expando)}"script"===n&&t.text!==e.text?(h(t).text=e.text,g(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),st.support.html5Clone&&e.innerHTML&&!st.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Zt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function b(e,n){var r,i,o=0,a=e.getElementsByTagName!==t?e.getElementsByTagName(n||"*"):e.querySelectorAll!==t?e.querySelectorAll(n||"*"):t;if(!a)for(a=[],r=e.childNodes||e;null!=(i=r[o]);o++)!n||st.nodeName(i,n)?a.push(i):st.merge(a,b(i,n));return n===t||n&&st.nodeName(e,n)?st.merge([e],a):a}function x(e){Zt.test(e.type)&&(e.defaultChecked=e.checked)}function T(e,t){if(t in e)return t;for(var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Nn.length;i--;)if(t=Nn[i]+n,t in e)return t;return r}function w(e,t){return e=t||e,"none"===st.css(e,"display")||!st.contains(e.ownerDocument,e)}function N(e,t){for(var n,r=[],i=0,o=e.length;o>i;i++)n=e[i],n.style&&(r[i]=st._data(n,"olddisplay"),t?(r[i]||"none"!==n.style.display||(n.style.display=""),""===n.style.display&&w(n)&&(r[i]=st._data(n,"olddisplay",S(n.nodeName)))):r[i]||w(n)||st._data(n,"olddisplay",st.css(n,"display")));for(i=0;o>i;i++)n=e[i],n.style&&(t&&"none"!==n.style.display&&""!==n.style.display||(n.style.display=t?r[i]||"":"none"));return e}function C(e,t,n){var r=mn.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function k(e,t,n,r,i){for(var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;4>o;o+=2)"margin"===n&&(a+=st.css(e,n+wn[o],!0,i)),r?("content"===n&&(a-=st.css(e,"padding"+wn[o],!0,i)),"margin"!==n&&(a-=st.css(e,"border"+wn[o]+"Width",!0,i))):(a+=st.css(e,"padding"+wn[o],!0,i),"padding"!==n&&(a+=st.css(e,"border"+wn[o]+"Width",!0,i)));return a}function E(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=ln(e),a=st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=un(e,t,o),(0>i||null==i)&&(i=e.style[t]),yn.test(i))return i;r=a&&(st.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+k(e,t,n||(a?"border":"content"),r,o)+"px"}function S(e){var t=V,n=bn[e];return n||(n=A(e,t),"none"!==n&&n||(cn=(cn||st("