├── .gitignore ├── mock.jpg ├── favicon.ico ├── images ├── hero.jpg ├── pull-1.jpg ├── footer-drupal.svg ├── background.svg ├── search.svg ├── search--white.svg └── rss.svg ├── fonts ├── metropolis │ ├── .DS_Store │ ├── Metropolis-Bold.woff │ ├── Metropolis-Bold.woff2 │ ├── Metropolis-Regular.woff │ └── Metropolis-Regular.woff2 └── lora │ ├── lora-v14-latin-700.woff │ ├── lora-v14-latin-700.woff2 │ ├── lora-v14-latin-italic.woff │ ├── lora-v14-latin-italic.woff2 │ ├── lora-v14-latin-regular.woff │ └── lora-v14-latin-regular.woff2 ├── CONTRIBUTING.md ├── src ├── css │ ├── components │ │ ├── byline.css │ │ ├── search-button-mobile.css │ │ ├── header-buttons-mobile.css │ │ ├── breadcrumb.css │ │ ├── hero.css │ │ ├── footer.css │ │ ├── social-container.css │ │ ├── nav-secondary.css │ │ ├── nav-button-mobile.css │ │ ├── nav-button-wide.css │ │ ├── embedded-media.css │ │ ├── nav-primary-button.css │ │ ├── table.css │ │ ├── header-search-narrow.css │ │ ├── main.css │ │ ├── header.css │ │ ├── header-search-wide.css │ │ └── nav-primary.css │ ├── base │ │ ├── _debug.css │ │ ├── forms.css │ │ ├── utility.css │ │ ├── layout.css │ │ ├── base.css │ │ ├── fonts.css │ │ └── variables.css │ └── style.css └── js │ ├── polyfills.js │ ├── search.js │ ├── second-level-navigation.js │ ├── navigation.js │ └── scripts.js ├── package.json ├── Gulpfile.js ├── README.md ├── LICENSE.txt └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /mock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/mock.jpg -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/favicon.ico -------------------------------------------------------------------------------- /images/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/images/hero.jpg -------------------------------------------------------------------------------- /images/pull-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/images/pull-1.jpg -------------------------------------------------------------------------------- /fonts/metropolis/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/fonts/metropolis/.DS_Store -------------------------------------------------------------------------------- /fonts/lora/lora-v14-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/fonts/lora/lora-v14-latin-700.woff -------------------------------------------------------------------------------- /fonts/lora/lora-v14-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/fonts/lora/lora-v14-latin-700.woff2 -------------------------------------------------------------------------------- /fonts/lora/lora-v14-latin-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/fonts/lora/lora-v14-latin-italic.woff -------------------------------------------------------------------------------- /fonts/lora/lora-v14-latin-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/fonts/lora/lora-v14-latin-italic.woff2 -------------------------------------------------------------------------------- /fonts/lora/lora-v14-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/fonts/lora/lora-v14-latin-regular.woff -------------------------------------------------------------------------------- /fonts/metropolis/Metropolis-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/fonts/metropolis/Metropolis-Bold.woff -------------------------------------------------------------------------------- /fonts/metropolis/Metropolis-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/fonts/metropolis/Metropolis-Bold.woff2 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | The contribution guide can be found here: https://www.drupal.org/docs/8/themes/olivero/how-to-contribute-to-olivero 2 | -------------------------------------------------------------------------------- /fonts/lora/lora-v14-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/fonts/lora/lora-v14-latin-regular.woff2 -------------------------------------------------------------------------------- /fonts/metropolis/Metropolis-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/fonts/metropolis/Metropolis-Regular.woff -------------------------------------------------------------------------------- /fonts/metropolis/Metropolis-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lullabot/olivero-poc/HEAD/fonts/metropolis/Metropolis-Regular.woff2 -------------------------------------------------------------------------------- /src/css/components/byline.css: -------------------------------------------------------------------------------- 1 | .hero__byline { 2 | color: var(--color--gray-20); 3 | font-size: 14px; 4 | line-height: var(--sp); 5 | 6 | a { 7 | font-weight: bold; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/css/base/_debug.css: -------------------------------------------------------------------------------- 1 | /* .mock { 2 | position: absolute; 3 | left: 1px; 4 | top: 1px; 5 | z-index: 50; 6 | opacity: 0.5; 7 | pointer-events: none; 8 | filter: invert(10%); 9 | } 10 | 11 | .site-branding { 12 | background: red !important; 13 | } */ 14 | -------------------------------------------------------------------------------- /src/css/components/search-button-mobile.css: -------------------------------------------------------------------------------- 1 | /* .mobile-search-button { 2 | display: none; 3 | -webkit-appearance: none; 4 | background: transparent; 5 | padding: 0 var(--sp); 6 | border: 0; 7 | cursor: pointer; 8 | 9 | @media (--nav-md) { 10 | display: block; 11 | } 12 | } */ 13 | -------------------------------------------------------------------------------- /src/css/components/header-buttons-mobile.css: -------------------------------------------------------------------------------- 1 | .mobile-buttons { 2 | grid-column: 4 / 7; 3 | -ms-grid-column-align: end; 4 | margin-left: auto; 5 | display: flex; 6 | 7 | @media (--sm) { 8 | align-self: end; 9 | } 10 | 11 | @media (--grid-md) { 12 | grid-column: 11 / 15; 13 | } 14 | 15 | @media (--nav) { 16 | body:not(.is-always-mobile-nav) & { 17 | display: none; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/css/components/breadcrumb.css: -------------------------------------------------------------------------------- 1 | .breadcrumb { 2 | grid-column: 1 / 7; 3 | text-transform: uppercase; 4 | letter-spacing: 0.2em; 5 | font-size: 14px; 6 | line-height: var(--sp); 7 | 8 | @media (--grid-md) { /* 700px */ 9 | grid-column: 3 / 13; 10 | } 11 | 12 | @media (--lg) { 13 | grid-column: 3 / 11; 14 | } 15 | 16 | ul { 17 | margin: 0; 18 | padding: 0; 19 | list-style: none; 20 | } 21 | 22 | a { 23 | color: var(--color--blue-20); 24 | text-decoration: none; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/js/polyfills.js: -------------------------------------------------------------------------------- 1 | if (window.NodeList && !NodeList.prototype.forEach) { 2 | NodeList.prototype.forEach = function (callback, thisArg) { 3 | thisArg = thisArg || window; 4 | for (var i = 0; i < this.length; i++) { 5 | callback.call(thisArg, this[i], i, this); 6 | } 7 | }; 8 | } 9 | 10 | // https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill 11 | if (!Element.prototype.matches) { 12 | Element.prototype.matches = Element.prototype.msMatchesSelector || 13 | Element.prototype.webkitMatchesSelector; 14 | } 15 | -------------------------------------------------------------------------------- /images/footer-drupal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/css/components/hero.css: -------------------------------------------------------------------------------- 1 | .hero__content { 2 | grid-column: 1 / 7; 3 | 4 | @media (--grid-md) { /* 700px */ 5 | grid-column: 3 / 13; 6 | } 7 | 8 | @media (--lg) { 9 | grid-column: 3 / 11; 10 | } 11 | } 12 | 13 | .hero__img { 14 | grid-column: 1 / 7; 15 | margin: var(--sp2) 0; 16 | 17 | @media (--sm) { 18 | margin: var(--sp3) 0; 19 | } 20 | 21 | @media (--grid-md) { 22 | grid-column: 1 / 15; 23 | margin: var(--sp4) 0; 24 | } 25 | 26 | @media (--lg) { 27 | grid-column: 2 / 14; 28 | } 29 | 30 | img { 31 | width: 100%; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/css/base/forms.css: -------------------------------------------------------------------------------- 1 | .button--primary { 2 | display: inline-flex; 3 | align-items: center; 4 | padding: 0 18px; 5 | background-color: var(--color--blue-20); 6 | color: white; 7 | font-weight: bold; 8 | height: var(--sp2); 9 | border-radius: 2px; 10 | white-space: nowrap; 11 | transition: background-color 0.2s; 12 | text-decoration: none; 13 | 14 | &:focus, 15 | &:hover { 16 | background-color: var(--color--blue-50); 17 | } 18 | 19 | &:focus { 20 | outline: 2px solid var(--color--blue-70); 21 | outline-offset: 1px; 22 | } 23 | 24 | &:active { 25 | background-color: var(--color--blue-20); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /images/background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Olivero-PoC", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "gulp build", 8 | "watch": "gulp watch", 9 | "start": "gulp watch" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@babel/core": "^7.6.0", 15 | "@babel/preset-env": "^7.6.0", 16 | "autoprefixer": "^9.6.1", 17 | "gulp": "^4.0.2", 18 | "gulp-babel": "^8.0.0", 19 | "gulp-postcss": "^8.0.0", 20 | "gulp-sourcemaps": "^2.6.5", 21 | "perfectionist": "^2.4.0", 22 | "postcss-calc": "^7.0.1", 23 | "postcss-import": "^12.0.1", 24 | "postcss-nested": "^4.1.2", 25 | "postcss-rtl": "^1.5" 26 | }, 27 | "devDependencies": { 28 | "postcss-custom-media": "^7.0.8", 29 | "postcss-custom-properties": "^9.0.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/css/base/utility.css: -------------------------------------------------------------------------------- 1 | .visually-hidden { 2 | position: absolute; 3 | clip: rect(1px, 1px, 1px, 1px); 4 | overflow: hidden; 5 | height: 1px; 6 | width: 1px; 7 | word-wrap: normal; 8 | 9 | &.focusable { 10 | &:focus { 11 | clip: auto; 12 | overflow: visible; 13 | height: auto; 14 | width: auto; 15 | word-wrap: normal; 16 | } 17 | } 18 | } 19 | 20 | .skip-link { 21 | z-index: 1; /* Appear above header */ 22 | outline: 0; 23 | text-decoration: none; 24 | 25 | @media (--nav) { 26 | body:not(.is-always-mobile-nav) & { 27 | margin-left: var(--content-left); 28 | padding: var(--sp); 29 | color: white; 30 | font-size: 14px; 31 | font-weight: bold; 32 | } 33 | } 34 | 35 | &:after { 36 | content: '➔'; 37 | margin-left: 5px; 38 | transition: 0.2s; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/css/style.css: -------------------------------------------------------------------------------- 1 | @import 'base/_debug.css'; 2 | @import 'base/fonts.css'; 3 | @import 'base/variables.css'; 4 | @import 'base/base.css'; 5 | @import 'base/forms.css'; 6 | @import 'base/layout.css'; 7 | @import 'base/utility.css'; 8 | 9 | /* Components */ 10 | @import 'components/breadcrumb.css'; 11 | @import 'components/byline.css'; 12 | @import 'components/embedded-media.css'; 13 | @import 'components/header.css'; 14 | @import 'components/hero.css'; 15 | @import 'components/footer.css'; 16 | @import 'components/nav-button-mobile.css'; 17 | @import 'components/nav-button-wide.css'; 18 | @import 'components/nav-primary.css'; 19 | @import 'components/nav-primary-button.css'; 20 | @import 'components/nav-secondary.css'; 21 | @import 'components/header-buttons-mobile.css'; 22 | @import 'components/header-search-narrow.css'; 23 | @import 'components/header-search-wide.css'; 24 | @import 'components/social-container.css'; 25 | @import 'components/search-button-mobile.css'; 26 | @import 'components/main.css'; 27 | @import 'components/table.css'; 28 | -------------------------------------------------------------------------------- /src/css/base/layout.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding-left: var(--sp); 3 | padding-right: var(--sp); 4 | width: 100%; 5 | max-width: var(--max-width); 6 | 7 | @media (--nav) { 8 | body:not(.is-always-mobile-nav) & { 9 | padding-left: var(--sp2); 10 | padding-right: var(--sp2); 11 | } 12 | } 13 | } 14 | 15 | .grid-full { 16 | display: grid; 17 | grid-template-columns: repeat(var(--grid-col-count), minmax(0, 1fr)); 18 | grid-template-rows: 1fr; 19 | grid-column-gap: var(--grid-gap); 20 | 21 | @media (--grid-md) { 22 | grid-template-columns: repeat(var(--grid-col-count--md), minmax(0, 1fr)); 23 | grid-column-gap: var(--grid-gap--md); 24 | } 25 | } 26 | 27 | .page-wrapper { 28 | max-width: var(--max-bg-color); 29 | background: white; 30 | } 31 | 32 | .content { 33 | @media (--nav) { 34 | body:not(.is-always-mobile-nav) & { 35 | display: flex; 36 | flex-direction: row-reverse; 37 | } 38 | } 39 | } 40 | 41 | main { 42 | @media (--nav) { 43 | body:not(.is-always-mobile-nav) & { 44 | margin-right: auto; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/css/components/footer.css: -------------------------------------------------------------------------------- 1 | .site-footer { 2 | position: relative; /* stack above left social bar */ 3 | background: black; 4 | 5 | @media (--nav) { 6 | body:not(.is-always-mobile-nav) & { 7 | padding-left: var(--content-left); 8 | } 9 | } 10 | } 11 | 12 | .site-footer__inner { 13 | padding: var(--sp2) 0; 14 | background: linear-gradient(180deg, #0C0D0E 0%, #171E23 100%); 15 | 16 | @media (--nav) { 17 | body:not(.is-always-mobile-nav) & { 18 | padding-top: var(--sp4); 19 | padding-bottom: calc(13 * var(--sp)); 20 | } 21 | } 22 | } 23 | 24 | .powered-by { 25 | grid-column: 1 / -1; 26 | color: var(--color--gray-40); 27 | letter-spacing: 0.02em; 28 | line-height: var(--sp); 29 | font-size: 14px; 30 | 31 | @media(--nav) { 32 | body:not(.is-always-mobile-nav) & { 33 | grid-column-start: 3; 34 | } 35 | } 36 | 37 | a { 38 | color: white; 39 | 40 | &:hover, 41 | &:focus { 42 | text-decoration: none; 43 | } 44 | } 45 | 46 | img { 47 | display: inline-block; 48 | vertical-align: middle; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /images/search.svg: -------------------------------------------------------------------------------- 1 | 2 | Search Icon 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/css/components/social-container.css: -------------------------------------------------------------------------------- 1 | .social-container { 2 | @media (--nav) { 3 | width: var(--content-left); 4 | flex-shrink: 0; 5 | background-color: var(--color--gray-95); 6 | } 7 | } 8 | 9 | .rotate { 10 | display: flex; 11 | align-items: center; 12 | justify-content: flex-end; 13 | 14 | @media (--nav) { 15 | writing-mode: vertical-rl; 16 | transform: rotate(180deg); 17 | width: var(--content-left); 18 | text-align: right; /* IE11 */ 19 | 20 | img { 21 | margin: 10px 5px 10px 10px; /* IE11 */ 22 | 23 | @supports (display: block) { /* Override IE11 values. */ 24 | margin: 10px; 25 | } 26 | } 27 | } 28 | } 29 | 30 | .social-label { 31 | font-size: 12px; 32 | letter-spacing: 4px; 33 | text-transform: uppercase; 34 | color: var(--color--gray-25); 35 | } 36 | 37 | .social-container__inner { 38 | padding: var(--sp0-5) var(--sp); 39 | background-color: var(--color--gray-95); 40 | 41 | @media (--nav) { 42 | position: relative; 43 | width: var(--content-left); 44 | padding: calc(9 * var(--sp)) 0; 45 | 46 | &.js-fixed { 47 | position: fixed; 48 | top: var(--sp6); 49 | left: 0; 50 | height: calc(100vh - 6 * var(--sp)) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /images/search--white.svg: -------------------------------------------------------------------------------- 1 | 2 | Search Icon 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/css/base/base.css: -------------------------------------------------------------------------------- 1 | *, *:before, *:after { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | font-family: var(--font-sans); 8 | line-height: var(--sp); 9 | background-color: #F7F9FA; 10 | background-image: url('/images/background.svg'); 11 | background-position: top left; 12 | 13 | &.js-fixed { 14 | position: fixed; 15 | overflow: hidden; 16 | 17 | &.is-always-mobile-nav { 18 | width: 100%; /* Prevent width from collapsing. */ 19 | } 20 | } 21 | } 22 | 23 | a { 24 | color: inherit; 25 | } 26 | 27 | img { 28 | display: block; 29 | max-width: 100%; 30 | } 31 | 32 | h1 { 33 | margin-top: var(--sp); 34 | margin-bottom: var(--sp); 35 | font-weight: bold; 36 | font-size: 24px; 37 | line-height: var(--sp1-5); 38 | letter-spacing: -0.01em; 39 | color: var(--color--gray-0); 40 | 41 | @media (--md) { 42 | margin-top: var(--sp2); 43 | margin-bottom: var(--sp2); 44 | font-size: 60px; 45 | line-height: var(--sp4); 46 | } 47 | } 48 | 49 | .overlay { 50 | display: none; 51 | position: fixed; 52 | top: 0; 53 | left: 0; 54 | width: 100%; 55 | height: 100vh; 56 | background: var(--color--blue-20); 57 | opacity: 0.2; 58 | 59 | .js-overlay-active & { 60 | display: block; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /images/rss.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/js/search.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const searchWideButton = document.querySelector('.header-nav__search-button'); 3 | const searchWideWrapper = document.querySelector('.search-wide__wrapper'); 4 | const siteHeader = document.querySelector('.site-header'); 5 | 6 | function toggleSearchVisibility(visibility) { 7 | searchWideButton.setAttribute('aria-expanded', visibility == true); 8 | searchWideWrapper.addEventListener('transitionend', handleFocus, { once: true }); 9 | 10 | if (visibility == true) { 11 | searchWideWrapper.classList.add('is-active'); 12 | } 13 | else { 14 | searchWideWrapper.classList.remove('is-active'); 15 | } 16 | } 17 | 18 | drupalSettings.olivero.toggleSearchVisibility = toggleSearchVisibility; 19 | 20 | function handleFocus() { 21 | if (searchIsVisible()) { 22 | searchWideWrapper.querySelector('input[type="search"]').focus(); 23 | } 24 | else { 25 | searchWideButton.focus(); 26 | } 27 | } 28 | 29 | function searchIsVisible() { 30 | return searchWideWrapper.classList.contains('is-active'); 31 | } 32 | drupalSettings.olivero.searchIsVisible = searchIsVisible; 33 | 34 | document.addEventListener('click', e => { 35 | if (e.target.matches('.header-nav__search-button, .header-nav__search-button *')) { 36 | toggleSearchVisibility(!searchIsVisible()); 37 | } 38 | else if (searchIsVisible() && !e.target.matches('.search-wide__wrapper, .search-wide__wrapper *')) { 39 | toggleSearchVisibility(false); 40 | } 41 | }); 42 | })(); 43 | -------------------------------------------------------------------------------- /src/css/base/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'metropolis'; 3 | font-weight: normal; 4 | font-style: normal; 5 | font-display: swap; 6 | src: url('/fonts/metropolis/Metropolis-Regular.woff2') format('woff2'), 7 | url('/fonts/metropolis/Metropolis-Regular.woff') format('woff'); 8 | } 9 | 10 | @font-face { 11 | font-family: 'metropolis'; 12 | font-weight: 700; 13 | font-style: normal; 14 | font-display: swap; 15 | src: url('/fonts/metropolis/Metropolis-Bold.woff2') format('woff2'), 16 | url('/fonts/metropolis/Metropolis-Bold.woff') format('woff'); 17 | } 18 | 19 | /* lora-regular - latin */ 20 | @font-face { 21 | font-family: 'Lora'; 22 | font-style: normal; 23 | font-weight: 400; 24 | font-display: swap; 25 | src: local('Lora Regular'), local('Lora-Regular'), 26 | url('/fonts/lora/lora-v14-latin-regular.woff2') format('woff2'), 27 | url('/fonts/lora/lora-v14-latin-regular.woff') format('woff'); 28 | } 29 | /* lora-italic - latin */ 30 | @font-face { 31 | font-family: 'Lora'; 32 | font-style: italic; 33 | font-weight: 400; 34 | font-display: swap; 35 | src: local('Lora Italic'), local('Lora-Italic'), 36 | url('/fonts/lora/lora-v14-latin-italic.woff2') format('woff2'), 37 | url('/fonts/lora/lora-v14-latin-italic.woff') format('woff'); 38 | } 39 | /* lora-700 - latin */ 40 | @font-face { 41 | font-family: 'Lora'; 42 | font-style: normal; 43 | font-weight: 700; 44 | font-display: swap; 45 | src: local('Lora Bold'), local('Lora-Bold'), 46 | url('/fonts/lora/lora-v14-latin-700.woff2') format('woff2'), 47 | url('/fonts/lora/lora-v14-latin-700.woff') format('woff'); 48 | } 49 | -------------------------------------------------------------------------------- /src/css/components/nav-secondary.css: -------------------------------------------------------------------------------- 1 | .secondary-nav__wrapper { 2 | display: flex; 3 | margin: var(--sp2) 0; 4 | 5 | @media (--nav) { 6 | body:not(.is-always-mobile-nav) & { 7 | justify-content: flex-end; 8 | margin: 0; 9 | } 10 | } 11 | } 12 | 13 | .secondary-nav { 14 | text-transform: uppercase; 15 | letter-spacing: 0.07em; 16 | font-size: 14px; 17 | 18 | @media (--nav) { 19 | body:not(.is-always-mobile-nav) & { 20 | position: relative; 21 | display: flex; 22 | margin-left: var(--sp0-5); 23 | padding-left: var(--sp2); 24 | 25 | &:before { 26 | content: ''; 27 | position: absolute; 28 | left: 0; 29 | top: 50%; 30 | transform: translatey(-50%); 31 | height: var(--sp2); 32 | width: 2px; 33 | background-color: var(--color--gray-70); 34 | } 35 | } 36 | } 37 | 38 | ul { 39 | list-style: none; 40 | margin: 0; 41 | padding: 0; 42 | display: flex; 43 | align-items: center; 44 | } 45 | 46 | li:not(:last-child) { 47 | margin-right: var(--sp1-5); 48 | 49 | @media (--nav) { 50 | body:not(.is-always-mobile-nav) & { 51 | margin-right: var(--sp2); 52 | } 53 | } 54 | } 55 | 56 | a:not(.button--primary) { 57 | position: relative; 58 | display: inline-flex; 59 | align-items: center; 60 | height: var(--sp2); 61 | text-decoration: none; 62 | 63 | &:after { 64 | content: ''; 65 | position: absolute; 66 | left: 0; 67 | bottom: 0; 68 | width: 100%; 69 | height: 0; 70 | border-top: solid 2px currentColor; 71 | opacity: 0; 72 | transform: translatey(5px); 73 | transition: opacity 0.2s, transform 0.2s; 74 | } 75 | 76 | &:focus, 77 | &:hover { 78 | &:after { 79 | opacity: 0.8; 80 | transform: translatey(0); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const sourcemaps = require('gulp-sourcemaps'); 3 | 4 | /** 5 | * JS Compilation 6 | */ 7 | gulp.task('js', () => { 8 | const babel = require('gulp-babel'); 9 | // const eslint = require('gulp-eslint'); 10 | 11 | return gulp.src('./src/js/*.js') 12 | .pipe(sourcemaps.init()) 13 | // .pipe(eslint()) 14 | // .pipe(eslint.format()) 15 | // .pipe(eslint.failAfterError()) 16 | .pipe(babel({ 17 | presets: ['@babel/preset-env'] 18 | })) 19 | .pipe(sourcemaps.write('.')) 20 | .pipe(gulp.dest('./dist/')) 21 | }); 22 | 23 | /** 24 | * CSS Compilation 25 | */ 26 | gulp.task('css', () => { 27 | const autoprefixer = require('autoprefixer') 28 | const postcss = require('gulp-postcss'); 29 | const rtl = require("postcss-rtl"); 30 | const nested = require('postcss-nested'); 31 | const calc = require('postcss-calc'); 32 | const atImport = require('postcss-import'); 33 | const perfectionist = require('perfectionist'); 34 | const postcssCustomMedia = require('postcss-custom-media'); 35 | const postcssCustomProperties = require('postcss-custom-properties'); 36 | 37 | 38 | return gulp.src('./src/css/style.css') 39 | .pipe(sourcemaps.init()) 40 | .pipe(postcss([ 41 | atImport(), 42 | nested(), 43 | postcssCustomMedia(), 44 | postcssCustomProperties({ 45 | preserve: false 46 | }), 47 | calc(), 48 | autoprefixer({ 49 | grid: 'no-autoplace' 50 | }), 51 | perfectionist({ 52 | indentSize: 2 53 | }), 54 | rtl(), 55 | ])) 56 | .pipe(sourcemaps.write('.')) 57 | .pipe(gulp.dest('./dist/')) 58 | }); 59 | 60 | function watchFiles() { 61 | gulp.watch(['./src/css/**/*.css'], css); 62 | gulp.watch(['./src/js/**/*.js'], js); 63 | } 64 | 65 | const css = gulp.series(['css']); 66 | const js = gulp.series(['js']); 67 | const build = gulp.series(gulp.parallel(css, js)); 68 | 69 | exports.watch = gulp.series([build, watchFiles]); 70 | exports.build = build; 71 | -------------------------------------------------------------------------------- /src/css/components/nav-button-mobile.css: -------------------------------------------------------------------------------- 1 | 2 | /* Mobile */ 3 | .mobile-nav-button { 4 | position: relative; 5 | z-index: 10; /* appear above mobile nav */ 6 | align-self: center; 7 | display: flex; 8 | align-items: center; 9 | -webkit-appearance: none; 10 | background: transparent; 11 | position: relative; 12 | width: var(--sp2); 13 | height: var(--sp2); 14 | margin-left: auto; 15 | padding: 0; 16 | border: none; 17 | cursor: pointer; 18 | 19 | @media (--sm) { 20 | display: inline-flex; 21 | width: auto; 22 | padding-left: var(--sp); 23 | } 24 | } 25 | 26 | /* Text that says "menu" */ 27 | .mobile-nav-button__label { 28 | position: absolute; 29 | clip: rect(1px, 1px, 1px, 1px); 30 | overflow: hidden; 31 | height: 1px; 32 | width: 1px; 33 | word-wrap: normal; 34 | 35 | @media (--sm) { 36 | position: static; 37 | clip: auto; 38 | overflow: visible; 39 | height: auto; 40 | width: auto; 41 | text-transform: uppercase; 42 | letter-spacing: 0.07em; 43 | font-size: 14px; 44 | font-weight: 600; 45 | margin-right: 12px; 46 | } 47 | } 48 | 49 | .mobile-nav-button__icon { 50 | position: relative; 51 | width: var(--sp2); 52 | height: 3px; 53 | background-color: var(--color--blue-50); 54 | 55 | &:before { 56 | content: ''; 57 | position: absolute; 58 | top: -8px; 59 | left: 0; 60 | height: 3px; 61 | width: 100%; 62 | background-color: var(--color--blue-50); 63 | transition: all 0.2s; 64 | } 65 | 66 | &:after { 67 | content: ''; 68 | position: absolute; 69 | top: auto; 70 | left: 0; 71 | bottom: -8px; 72 | height: 3px; 73 | width: 100%; 74 | background-color: var(--color--blue-50); 75 | transition: all 0.2s; 76 | } 77 | 78 | .mobile-nav-button[aria-expanded="true"] & { 79 | background-color: transparent; 80 | 81 | &:before { 82 | top: 0; 83 | transform: rotate(-45deg); 84 | } 85 | 86 | &:after { 87 | top: 0; 88 | transform: rotate(45deg); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Olivero - Proof of Concept HTML 2 | 3 | ## Overview 4 | This repository contains prototyping work for the new Drupal new front-end theme called Olivero. The new design that we're proposing in Olivero includes new components/features that are different from its predecessor. We'd like to utilize this static HTML page as a way to validate the new features and help answer potential UI/UX issues that might arise during the design process. 5 | 6 | The following are key items that we're examining: 7 | - Investigate the use of the header on scroll interaction on mobile and tablet devices. 8 | - Validate the use of the CSS grid in legacy browsers such as Internet Explorer 11 and identify if progressive enhancement features will need to be accounted for. 9 | - Verify that the markup is semantic and meets the accessibility requirements. 10 | 11 | This proof of concept is still a work-in-progress and not all of the elements meet accessibility guidelines. Our plan is to give a full sign-off from the Drupal accessibility team, which will hopefully alleviate last-minute time-crunches when the patch is submitted to Drupal core. 12 | 13 | ## Example 14 | 15 | The proof of concept is currently hosted on Netlify. Please check out the full build at https://olivero-poc.netlify.com. 16 | 17 | ## Reporting bugs 18 | 19 | If you need to submit a bug report for this POC, create an issue in the [Olivero issue queue](https://www.drupal.org/project/issues/olivero?component=Proof+of+Concept), with `Proof of Concept` selected as the component. Please follow the Drupal.org issue guidelines as it helps us and the community better understand your report, reproduce the bug, and find related issues. 20 | 21 | ## Installation 22 | 23 | ### Prerequisites: 24 | 25 | - Node (12+) 26 | 27 | ### Install Dev Dependencies 28 | 29 | `$ npm install` 30 | 31 | ## Usage 32 | 33 | `$ npm start` or `$ gulp watch` 34 | 35 | This will watch for any styles and JS changes. 36 | 37 | `$ npm run build` or `$ gulp build` 38 | 39 | This will handle the compilation of all the CSS files, creation of accompanying source maps, concatenation of all CSS files into one CSS stylesheet within the `/dist` directory. 40 | -------------------------------------------------------------------------------- /src/css/components/nav-button-wide.css: -------------------------------------------------------------------------------- 1 | /* Desktop */ 2 | 3 | .nav-primary__button { 4 | display: none; 5 | 6 | @media (--nav) { 7 | visibility: hidden; 8 | display: flex; 9 | flex-shrink: 0; 10 | align-items: center; 11 | justify-content: center; 12 | width: var(--content-left); 13 | height: calc(6 * var(--sp)); 14 | border: 0; 15 | cursor: pointer; 16 | background-color: var(--color--blue-50); 17 | outline: 0; 18 | 19 | &:focus { 20 | border: solid 1px transparent; /* Will show in IE/Edge high contrast mode. */ 21 | } 22 | 23 | body:not(.is-always-mobile-nav) .js-fixed & { 24 | visibility: visible; 25 | } 26 | 27 | body.is-always-mobile-nav & { 28 | visibility: hidden; 29 | } 30 | } 31 | } 32 | 33 | .nav-primary__icon { 34 | position: relative; 35 | width: var(--sp2); 36 | height: 21px; 37 | pointer-events: none; 38 | transform-style: preserve-3d; 39 | transition: transform 1s; 40 | opacity: 0; 41 | transition: all 0.2s; 42 | 43 | .js-fixed & { 44 | opacity: 1; 45 | } 46 | 47 | [aria-expanded="true"] & { 48 | > div:nth-child(1) { 49 | top: 9px; 50 | transform: rotate(-45deg); 51 | } 52 | 53 | > div:nth-child(2) { 54 | opacity: 0; 55 | } 56 | 57 | > div:nth-child(3) { 58 | top: 9px; 59 | transform: rotate(45deg); 60 | } 61 | } 62 | 63 | > div { 64 | height: 0; 65 | border-top: solid 3px white; 66 | 67 | &:nth-child(1) { 68 | position: absolute; 69 | top: 0; 70 | left: 0; 71 | height: 0; 72 | width: 100%; 73 | background-color: white; 74 | transition: all 0.2s; 75 | } 76 | 77 | &:nth-child(2) { 78 | position: absolute; 79 | top: 9px; 80 | left: 0; 81 | height: 0; 82 | width: 100%; 83 | background-color: white; 84 | transition: opacity 0.2s; 85 | } 86 | 87 | &:nth-child(3) { 88 | position: absolute; 89 | top: auto; 90 | left: 0; 91 | bottom: 0; 92 | height: 0; 93 | width: 100%; 94 | background-color: white; 95 | transition: all 0.2s; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/css/components/embedded-media.css: -------------------------------------------------------------------------------- 1 | figure { 2 | background: var(--color--gray-95); 3 | } 4 | 5 | figcaption { 6 | padding: var(--sp0-5); 7 | font-family: var(--font-serif); 8 | font-size: 14px; 9 | font-style: italic;; 10 | line-height: var(--sp); 11 | color: var(--color--gray-10); 12 | 13 | @media (--sm) { 14 | padding: var(--sp); 15 | } 16 | } 17 | 18 | .pull-right { 19 | margin: var(--sp3) 0; 20 | max-width: 100%; 21 | 22 | @media (--grid-md) { 23 | float: right; 24 | width: calc(5 * var(--grid-col-width--md) + 4 * var(--grid-gap--md)); 25 | margin: var(--sp) 0; 26 | margin-right: calc(-2 * ((var(--grid-col-width--md) + var(--grid-gap--md)))); 27 | margin-left: var(--sp); 28 | } 29 | 30 | @media (--lg) { 31 | width: calc(5 * var(--grid-col-width--lg) + 4 * var(--grid-gap--lg)); 32 | margin-right: calc(-2 * ((var(--grid-col-width--lg) + var(--grid-gap--lg)))); 33 | } 34 | 35 | @media (--nav) { 36 | width: calc(4 * var(--grid-col-width--nav) + 3 * var(--grid-gap--nav)); 37 | margin-right: calc(-3 * ((var(--grid-col-width--nav) + var(--grid-gap--nav)))); 38 | } 39 | 40 | @media (--grid-max) { 41 | width: calc(4 * var(--grid-col-width--max) + 3 * var(--grid-gap--max)); 42 | margin-right: calc(-3 * ((var(--grid-col-width--max) + var(--grid-gap--max)))); 43 | } 44 | } 45 | 46 | .pull-left { 47 | margin: var(--sp3) 0; 48 | max-width: 100%; 49 | 50 | @media (--grid-md) { 51 | float: left; 52 | width: calc(5 * var(--grid-col-width--md) + 4 * var(--grid-gap--md)); 53 | margin: var(--sp) 0; 54 | margin-left: calc(-2 * ((var(--grid-col-width--md) + var(--grid-gap--md)))); 55 | margin-right: var(--sp); 56 | } 57 | 58 | @media (--lg) { 59 | width: calc(5 * var(--grid-col-width--lg) + 4 * var(--grid-gap--lg)); 60 | margin-left: calc(-1 * ((var(--grid-col-width--lg) + var(--grid-gap--lg)))); 61 | } 62 | 63 | @media (--nav) { 64 | width: calc(4 * var(--grid-col-width--nav) + 3 * var(--grid-gap--nav)); 65 | margin-left: calc(-1 * ((var(--grid-col-width--nav) + var(--grid-gap--nav)))); 66 | } 67 | 68 | @media (--grid-max) { 69 | width: calc(4 * var(--grid-col-width--max) + 3 * var(--grid-gap--max)); 70 | margin-left: calc(-1 * ((var(--grid-col-width--max) + var(--grid-gap--max)))); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/css/components/nav-primary-button.css: -------------------------------------------------------------------------------- 1 | /* 2 | Button that expands second level nav when clicked. 3 | */ 4 | .primary-nav__button-toggle { 5 | -webkit-appearance: none; 6 | border: 0; 7 | background: transparent; 8 | position: relative; 9 | width: var(--sp2); 10 | height: var(--sp2); 11 | padding: 0; 12 | text-indent: -999px; 13 | overflow: hidden; 14 | cursor: pointer; 15 | 16 | @media (--nav) { 17 | body:not(.is-always-mobile-nav) & { 18 | align-self: stretch; 19 | width: calc(var(--sp2) + 8px); 20 | height: auto; 21 | margin-right: calc(-1 * var(--sp2)); 22 | 23 | &:focus { 24 | outline: 0; 25 | border: 0; 26 | 27 | .icon--menu-toggle { 28 | border: solid 1px transparent; 29 | background-color: var(--color--blue-50); 30 | 31 | &:after { 32 | border-color: white; 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | .icon--menu-toggle { 40 | position: absolute; 41 | top: 50%; 42 | left: 50%; 43 | transform: translate(-50%, -50%); 44 | width: 16px; 45 | height: 16px; 46 | border-radius: 2px; 47 | transition: background-color 0.2s; 48 | 49 | @media (--nav) { 50 | body:not(.is-always-mobile-nav) & { 51 | left: 3px; 52 | transform: translatey(-50%); 53 | } 54 | } 55 | 56 | &:before, 57 | &:after { 58 | content: ''; 59 | position: absolute; 60 | top: 50%; 61 | left: 50%; 62 | transform: translate(-50%, -50%); 63 | width: var(--sp); 64 | height: 0; 65 | border-top: solid 3px var(--color--blue-50); 66 | 67 | @media (--nav) { 68 | body:not(.is-always-mobile-nav) & { 69 | content: none; 70 | } 71 | } 72 | } 73 | 74 | &:after { 75 | transform: translate(-50%, -50%) rotate(90deg); 76 | transition: opacity 0.2s; 77 | 78 | @media (--nav) { 79 | body:not(.is-always-mobile-nav) & { 80 | content: ''; 81 | top: calc(50% - 2px); 82 | left: 3px; 83 | height: 8px; 84 | width: 8px; 85 | border-right: solid 2px currentColor; 86 | border-bottom: solid 2px currentColor; 87 | border-top: none; 88 | transform: translatey(-50%) rotate(45deg); 89 | background: transparent; 90 | opacity: 0.8; 91 | } 92 | } 93 | } 94 | } 95 | 96 | 97 | &[aria-expanded="true"] { 98 | .icon--menu-toggle:after { 99 | opacity: 0; 100 | 101 | @media (--nav) { 102 | body:not(.is-always-mobile-nav) & { 103 | opacity: 0.8; 104 | } 105 | } 106 | } 107 | } 108 | 109 | html:not(.js) & { 110 | display: none; /* Hide if JS is disabled */ 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/js/second-level-navigation.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | const isDesktopNav = drupalSettings.olivero.isDesktopNav; 4 | const secondLevelNavMenus = document.querySelectorAll('.primary-nav--level-1 .has-children'); 5 | 6 | // Add hover and click event listeners onto each subnav parent and it's button. 7 | secondLevelNavMenus.forEach(el => { 8 | const button = el.querySelector('.primary-nav__button-toggle'); 9 | button.addEventListener('click', e => { 10 | const topLevelMenuITem = e.currentTarget.parentNode; 11 | toggleSubNav(topLevelMenuITem); 12 | }); 13 | 14 | el.addEventListener('mouseover', e => { 15 | if (isDesktopNav()) { 16 | toggleSubNav(e.currentTarget, true); 17 | } 18 | }); 19 | 20 | el.addEventListener('mouseout', e => { 21 | if (isDesktopNav()) { 22 | toggleSubNav(e.currentTarget, false); 23 | } 24 | }); 25 | }); 26 | 27 | /** 28 | * Close all second level subnav menus. 29 | */ 30 | function closeAllSubNav() { 31 | secondLevelNavMenus.forEach(el => { 32 | toggleSubNav(el, false); 33 | }); 34 | } 35 | 36 | drupalSettings.olivero.closeAllSubNav = closeAllSubNav; 37 | 38 | /** 39 | * Checks if any subnavigation items are currently active. 40 | * @returns {boolean} 41 | */ 42 | function areAnySubnavsOpen() { 43 | let subNavsAreOpen = false; 44 | 45 | secondLevelNavMenus.forEach(el => { 46 | const button = el.querySelector('.primary-nav__button-toggle'); 47 | const state = button.getAttribute('aria-expanded') === 'true'; 48 | 49 | if (state) { 50 | subNavsAreOpen = true; 51 | } 52 | }); 53 | 54 | return subNavsAreOpen; 55 | } 56 | 57 | drupalSettings.olivero.areAnySubnavsOpen = areAnySubnavsOpen; 58 | 59 | /** 60 | * Shows and hides the specified menu item's second level submenu. 61 | * 62 | * @param {element} topLevelMenuITem - the
  • element that is the container for the menu and submenus. 63 | * @param {boolean} [toState] - Optional state where we want the submenu to end up. 64 | */ 65 | function toggleSubNav(topLevelMenuITem, toState) { 66 | const button = topLevelMenuITem.querySelector('.primary-nav__button-toggle'); 67 | const state = toState != undefined ? toState : button.getAttribute('aria-expanded') != 'true'; 68 | 69 | if (state) { 70 | button.setAttribute('aria-expanded', 'true'); 71 | topLevelMenuITem.querySelector('.primary-nav--level-2').classList.add('is-active'); 72 | } 73 | else { 74 | button.setAttribute('aria-expanded', 'false'); 75 | topLevelMenuITem.querySelector('.primary-nav--level-2').classList.remove('is-active'); 76 | } 77 | } 78 | 79 | drupalSettings.olivero.toggleSubNav = toggleSubNav; 80 | 81 | // Ensure that desktop submenus close when ESC key is pressed. 82 | document.addEventListener('keyup', e => { 83 | if (e.keyCode === 27 && isDesktopNav()) { 84 | closeAllSubNav(); 85 | } 86 | }); 87 | })(); 88 | -------------------------------------------------------------------------------- /src/js/navigation.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | const isDesktopNav = drupalSettings.olivero.isDesktopNav; 4 | 5 | const mobileNavButton = document.querySelector('.mobile-nav-button'); 6 | const mobileNavWrapperId = 'header-nav'; 7 | const mobileNavWrapper = document.getElementById(mobileNavWrapperId); 8 | const body = document.querySelector('body'); 9 | const overlay = document.querySelector('.overlay'); 10 | 11 | const focusableNavElements = mobileNavWrapper.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); 12 | const firstFocusableEl = focusableNavElements[0]; 13 | const lastFocusableEl = focusableNavElements[focusableNavElements.length - 1]; 14 | 15 | function init() { 16 | mobileNavButton.setAttribute('aria-controls', mobileNavWrapperId); 17 | mobileNavButton.setAttribute('aria-expanded', 'false'); 18 | } 19 | 20 | function isMobileNavOpen() { 21 | return mobileNavWrapper.classList.contains('is-active'); 22 | } 23 | 24 | /** 25 | * Opens or closes the mobile navigation. 26 | * @param {boolean} state - State which to transition the mobile navigation menu into. 27 | */ 28 | function toggleMobileNav(state) { 29 | const value = state ? true : false; 30 | mobileNavButton.setAttribute('aria-expanded', value); 31 | 32 | if (value) { 33 | body.classList.add('js-overlay-active'); 34 | body.classList.add('js-fixed'); 35 | mobileNavWrapper.classList.add('is-active'); 36 | } 37 | else { 38 | body.classList.remove('js-overlay-active'); 39 | body.classList.remove('js-fixed'); 40 | mobileNavWrapper.classList.remove('is-active'); 41 | } 42 | } 43 | 44 | // Initialize everything. 45 | 46 | init(); 47 | 48 | mobileNavButton.addEventListener('click', () => { 49 | toggleMobileNav(!isMobileNavOpen()); 50 | }); 51 | 52 | // Closes any open subnavigation first, then close mobile navigation slideout. 53 | document.addEventListener('keyup', e => { 54 | if (e.keyCode === 27) { 55 | if (drupalSettings.olivero.areAnySubnavsOpen()) { 56 | drupalSettings.olivero.closeAllSubNav(); 57 | } 58 | else { 59 | toggleMobileNav(false); 60 | } 61 | } 62 | }); 63 | 64 | overlay.addEventListener('click', () => { 65 | toggleMobileNav(false); 66 | }); 67 | 68 | overlay.addEventListener('touchstart', () => { 69 | toggleMobileNav(false); 70 | }); 71 | 72 | // Focus trap. 73 | mobileNavWrapper.addEventListener('keydown', function(e) { 74 | if (e.key === 'Tab' || e.keyCode === 9) { 75 | if ( e.shiftKey ) /* shift + tab */ { 76 | if (document.activeElement === firstFocusableEl && !isDesktopNav()) { 77 | mobileNavButton.focus(); 78 | e.preventDefault(); 79 | } 80 | } else /* tab */ { 81 | if (document.activeElement === lastFocusableEl && !isDesktopNav()) { 82 | mobileNavButton.focus(); 83 | e.preventDefault(); 84 | } 85 | } 86 | } 87 | }); 88 | 89 | // Remove overlays when browser is resized and desktop nav appears. 90 | // @todo Use core/drupal.debounce library to throttle when we move into theming. 91 | window.addEventListener('resize', () => { 92 | if (drupalSettings.olivero.isDesktopNav) { 93 | toggleMobileNav(false); 94 | body.classList.remove('js-overlay-active', 'js-fixed'); 95 | } 96 | }); 97 | 98 | })() 99 | -------------------------------------------------------------------------------- /src/js/scripts.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | window.drupalSettings = {}; 4 | window.drupalSettings.olivero = {}; 5 | 6 | // Replicates Drupal's addition of adding a `js` class onto HTML element. 7 | document.documentElement.classList.add('js'); 8 | 9 | function isDesktopNav() { 10 | // @todo, I'm not sure we even need the .mobile-buttons container anymore. 11 | const navButtons = document.querySelector('.mobile-buttons'); 12 | return window.getComputedStyle(navButtons).getPropertyValue('display') === 'none'; 13 | } 14 | 15 | drupalSettings.olivero.isDesktopNav = isDesktopNav; 16 | 17 | const wideNavButton = document.querySelector('.nav-primary__button'); 18 | const siteHeaderToggleElement = document.querySelector('.site-header__inner'); 19 | 20 | function wideNavIsOpen() { 21 | return wideNavButton.getAttribute('aria-expanded') === 'true'; 22 | } 23 | 24 | function showWideNav() { 25 | if (isDesktopNav()) { 26 | wideNavButton.setAttribute('aria-expanded', 'true'); 27 | siteHeaderToggleElement.classList.add('is-active'); 28 | } 29 | } 30 | 31 | // Resets the wide nav button to be closed (it's default state). 32 | function hideWideNav() { 33 | if (isDesktopNav()) { 34 | wideNavButton.setAttribute('aria-expanded', 'false'); 35 | siteHeaderToggleElement.classList.remove('is-active'); 36 | } 37 | } 38 | 39 | 40 | // Only enable scroll effects if the browser supports Intersection Observer. 41 | // @see https://github.com/w3c/IntersectionObserver/blob/master/polyfill/intersection-observer.js#L19-L21 42 | if ('IntersectionObserver' in window && 43 | 'IntersectionObserverEntry' in window && 44 | 'intersectionRatio' in window.IntersectionObserverEntry.prototype) { 45 | 46 | const fixables = document.querySelectorAll('.fixable') 47 | 48 | function monitorNavPosition() { 49 | const primaryNav = document.querySelector('.site-header'); 50 | const options = { 51 | rootMargin: '72px', 52 | threshold: [0.999, 1] 53 | } 54 | 55 | const observer = new IntersectionObserver(toggleDesktopNavVisibility, options); 56 | observer.observe(primaryNav); 57 | } 58 | 59 | function toggleDesktopNavVisibility(entries) { 60 | if (!isDesktopNav()) return; 61 | 62 | entries.forEach(entry => { 63 | // FF doesn't seem to support entry.isIntersecting properly, 64 | // so we check the intersectionRatio. 65 | if (entry.intersectionRatio < 1) { 66 | fixables.forEach(el => el.classList.add('js-fixed')); 67 | } 68 | else { 69 | fixables.forEach(el => el.classList.remove('js-fixed')); 70 | } 71 | }); 72 | } 73 | 74 | wideNavButton.addEventListener('click', () => { 75 | if (!wideNavIsOpen()) { 76 | showWideNav(); 77 | } 78 | else { 79 | hideWideNav(); 80 | } 81 | }); 82 | 83 | siteHeaderToggleElement.addEventListener('focusin', showWideNav); 84 | 85 | // If skip link is clicked, ensure that the wide navigation closes so the header will not be covered up. 86 | document.querySelector('.skip-link').addEventListener('click', function () { 87 | hideWideNav(); 88 | }); 89 | 90 | monitorNavPosition(); 91 | } 92 | 93 | document.addEventListener('keyup', e => { 94 | if (e.keyCode === 27) { 95 | // Close the search form. 96 | if ('toggleSearchVisibility' in drupalSettings.olivero && 'searchIsVisible' in drupalSettings.olivero && drupalSettings.olivero.searchIsVisible()) { 97 | drupalSettings.olivero.toggleSearchVisibility(false); 98 | } 99 | // Close the wide nav. 100 | else { 101 | hideWideNav(); 102 | } 103 | } 104 | }); 105 | })(); 106 | -------------------------------------------------------------------------------- /src/css/components/table.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Styles for Olivero's tables. 4 | */ 5 | 6 | table { 7 | width: fit-content; 8 | max-width: 100%; 9 | border-collapse: collapse; 10 | display: block; 11 | margin: var(--sp2) 0; 12 | border-spacing: 0; 13 | border: 0; 14 | font-family: var(--font-sans); 15 | line-height: var(--sp1-5); 16 | font-size: 16px; 17 | color: var(--color--gray-10); 18 | overflow-x: auto; 19 | -webkit-overflow-scrolling: touch; 20 | 21 | .content-bleed & { 22 | width: 100%; 23 | } 24 | 25 | caption { 26 | margin-bottom: var(--sp1); 27 | text-align: left; 28 | font-family: var(--font-serif); 29 | font-size: 14px; 30 | font-style: italic;; 31 | line-height: var(--sp); 32 | color: var(--color--gray-10); 33 | } 34 | 35 | tr { 36 | &:last-child { 37 | td { 38 | border-bottom: 0; 39 | } 40 | } 41 | } 42 | 43 | td, 44 | th { 45 | padding: var(--sp1) var(--sp1) var(--sp1) 0; 46 | vertical-align: top; 47 | } 48 | 49 | th { 50 | border-bottom: 2px solid var(--color--blue-50); 51 | margin: 0; 52 | letter-spacing: 0.12em; 53 | text-transform: uppercase; 54 | font-family: var(--font-sans); 55 | font-size: 14px; 56 | color: var(--color--gray-0); 57 | text-align: left; 58 | } 59 | 60 | td { 61 | white-space: normal; 62 | min-width: 250px; 63 | border-bottom: 2px solid var(--color--gray-40); 64 | } 65 | } 66 | 67 | /* When a table is placed in the body/text content area */ 68 | .content-bleed { 69 | @media (--grid-md) { 70 | width: calc(12 * var(--grid-col-width--md) + 9 * var(--grid-gap--md)); 71 | } 72 | 73 | @media (--lg) { 74 | width: calc(10 * var(--grid-col-width--lg) + 9 * var(--grid-gap--lg)); 75 | } 76 | 77 | @media (--nav) { 78 | width: calc(10 * var(--grid-col-width--nav) + 9 * var(--grid-gap--nav)); 79 | } 80 | 81 | @media (--grid-max) { 82 | width: calc(10 * var(--grid-col-width--max) + 9 * var(--grid-gap--max)); 83 | } 84 | } 85 | 86 | /* Set Horzontal Scroll gradient direction based on lanuage direction. */ 87 | [dir=ltr] { 88 | table { 89 | background: 90 | /* Left start and right start 'inside' container colors (they overlap the shadows). */ 91 | linear-gradient(90deg, white 0%, rgba(255,255,255,0)), 92 | linear-gradient(-90deg, white 0%, rgba(255,255,255,0)) 100% 0, 93 | /* Left and right scroll shadows. */ 94 | radial-gradient( 95 | farthest-side at 0% 50%, 96 | rgba(0,0,0,0.2), 97 | rgba(0,0,0,0) 98 | ), 99 | radial-gradient( 100 | farthest-side at 100% 50%, 101 | rgba(0,0,0,0.2), 102 | rgba(0,0,0,0) 103 | ) 100% 0%; 104 | 105 | @media screen and (-ms-high-contrast: active) { 106 | background: none; /* FF & Edge's high contrast does show the background image, so we hide it. */ 107 | } 108 | } 109 | } 110 | 111 | [dir=rtl] { 112 | table { 113 | background: 114 | /* Right start and Left start 'inside' container colors (they overlap the shadows). */ 115 | linear-gradient(-90deg, white 0%, rgba(255,255,255,0)), 116 | linear-gradient(90deg, white 0%, rgba(255,255,255,0)) 0 100%, 117 | /* Right and Left scroll shadows. */ 118 | radial-gradient( 119 | farthest-side at 0% 50%, 120 | rgba(0,0,0,0.2), 121 | rgba(0,0,0,0) 122 | ), 123 | radial-gradient( 124 | farthest-side at 100% 50%, 125 | rgba(0,0,0,0.2), 126 | rgba(0,0,0,0) 127 | ) 100% 0%; 128 | } 129 | } 130 | 131 | [dir=ltr], 132 | [dir=rtl] { 133 | table { 134 | background-repeat: no-repeat; 135 | background-color: white; 136 | background-attachment: local, local, scroll, scroll; 137 | background-size: 100px 100%, 100px 100%, 14px 100%, 14px 100%; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/css/components/header-search-narrow.css: -------------------------------------------------------------------------------- 1 | .search-narrow__wrapper { 2 | margin-bottom: var(--sp2); 3 | margin-left: calc(-1 * var(--sp)); 4 | margin-right: calc(-1 * var(--sp)); 5 | background: black; 6 | 7 | /* 500px is the width of the primary nav at mobile. */ 8 | @media (min-width: 500px) { 9 | margin-left: 0; 10 | margin-right: 0; 11 | } 12 | 13 | @media (--nav) { 14 | body:not(.is-always-mobile-nav) & { 15 | display: none; 16 | } 17 | } 18 | 19 | form { 20 | display: flex; 21 | } 22 | 23 | input[type="search"] { 24 | flex-grow: 1; 25 | height: calc(3 * var(--sp)); 26 | -webkit-appearance: none; 27 | padding: 0 var(--sp); 28 | border: solid 1px transparent; 29 | background-color: transparent; 30 | background-image: linear-gradient(var(--color--blue-50), var(--color--blue-50)); /* Two values are needed for IE11 support. */ 31 | background-size: 0% 5px; 32 | background-repeat: no-repeat; 33 | background-position: bottom left; 34 | transition: background-size 0.4s; 35 | width: calc(100% + var(--sp2)); 36 | font-family: serif; 37 | color: white; 38 | font-size: 16px; 39 | 40 | @media (--md) { 41 | padding: var(--sp2); 42 | } 43 | 44 | &::-ms-clear { 45 | width: 40px; 46 | opacity: 0.5; 47 | } 48 | 49 | &:focus { 50 | outline: 0; 51 | background-size: 100% 5px; 52 | 53 | /* 54 | We normally indicate focus by animating background-image width. This isn't 55 | available in IE11 when in Windows high contrast mode. 56 | */ 57 | @media screen and (-ms-high-contrast: active) { 58 | border-bottom-width: 5px; 59 | } 60 | } 61 | } 62 | 63 | button { 64 | position: relative; 65 | width: var(--sp3); 66 | overflow: hidden; 67 | padding: 0; 68 | background-color: transparent; 69 | border-color: transparent; 70 | cursor: pointer; 71 | 72 | @media (--md) { 73 | width: 80px; 74 | } 75 | 76 | /* 77 | When in Windows high contrast mode, FF will will not output either background 78 | images or SVGs that are nested directly within a 38 | 39 | 40 | 41 |
    42 |
    43 |
    44 |
    45 |
    46 | olivero 47 |
    48 |
    49 |
    50 | 54 |
    55 |
    56 |
    57 |
    58 | 59 | 60 | 64 |
    65 |
    66 | 88 |
    89 | 106 |
    107 |
    108 |
    109 |
    110 | 111 | 113 | 117 |
    118 |
    119 |
    120 |
    121 | 127 |
    128 |
    129 |
    130 |
    131 |
    132 | 133 | 134 | 135 |
    136 |
    137 | 138 |
    139 | 144 |
    145 |
    146 |
    147 |
    148 |

    Drupal 9: The Most Beautiful CMS Ever!

    149 | 152 |
    153 |
    154 |
    155 |
    156 | Desktop with two computers showing Olivero design 157 |
    158 |
    159 |
    160 |
    161 |
    162 |

    Welcome to the proof-of-concept HTML for the new Drupal Olivero theme! The 163 | intention of this webpage is to validate the user interactions, design, 164 | architecture, accessibility, and technology that will be used to create the 165 | theme.

    166 |

    About this proof-of-concept

    167 |

    This proof of concept was created in static HTML. The repository is hosted on 168 | GitHub, and the webpage is hosted on Netlify.

    169 |

    We created this proof-of-concept to validate the look and feel of the 170 | scrolling interactions, and moved on to work to make the nav-bar accessible. 171 | This includes ensuring that the menu items are exposed to the accessibility 172 | tree, and activating the nav-bar when it gains focus (this is done through the 173 | focusin event listener).

    174 |

    Commitment to accessibility

    175 |

    To add this theme to Drupal core, we must meet Drupal’s stringent 176 | accessibility standards which stipulate compliance with Web Content Accessibility Guidelines 178 | (WCAG) 2.0 AA.

    179 |

    Throughout the design process, we have been designing with accessibility in 180 | mind. We have been continuously testing the design against contrast 181 | requirements, and adjusting the design based on this.

    182 |
    183 | DrupalCon Vienna Group Photo 184 |
    DrupalCon Vienna group photo. Photo by Dominik Kiss on behalf of 185 | the Drupal Assocation. Creative Commons.
    186 |
    187 |

    Expanding / collapsing HTML elements within the theme will be either properly 188 | removed / added from the accessibility tree, or will become visible when they 189 | gain focus. Elements will be styled against the appropriate ARIA attributes to 190 | ensure that it becomes immediately apparent when the toggling of attributes is 191 | not working correctly.

    192 |

    Browser support

    193 |

    Because this theme is intended to go into Drupal core, we need to support all browsers that core 195 | does.

    196 |

    Currently this browser is tested and working on the following:

    197 |
      198 |
    • Chrome
    • 199 |
    • Firefox
    • 200 |
    • Safari
    • 201 |
    • Mobile Safari
    • 202 |
    • Internet Explorer 11 (some bugs present!)
    • 203 |
    204 |

    We have not yet tested this on the following:

    205 |
      206 |
    • Edge
    • 207 |
    • UC browser
    • 208 |
    • Opera
    • 209 |
    • Opera Mini
    • 210 |
    • Samsung
    • 211 |
    212 |

    Progressive enhancement

    213 |

    We make use of several modern front-end web development technologies 214 | including CSS 215 | Grid, and Intersection 217 | Observer.

    218 |

    Internet Explorer does have CSS Grid support, but the syntax is old and 219 | invalid. Fortunately, the Autoprefixer plugin will automatically add this 220 | support. We can do this because Drupal core can 222 | now use PostCSS (huge thanks to the Claro team for getting this in)!

    223 |

    Internet Explorer also does not support the new Intersection Observer API. To 224 | work around this, we check to ensure that the API exists before invoking it. 225 | This ensures that the menus are fully accessible, and do not generate console 226 | errors — but the fancy scroll interactions will not be available to IE11 227 | users.

    228 | 229 |

    You get a table, and You get a table

    230 |

    If you need to show off some columnar stuff with rows, Drupal 9’s core theme is ready to help. Your data will be beautifully represented for all your readers.

    231 | 232 |
    233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 |
    Table Header Caption 01
    Column Header 1Column Header 2Column Header 3
    Shiver me timbers holystoneNo prey, no pay hulk weighSpike grog blossom
    Killick lad code of conduct swing the lead fire. Shiver me timbers holystone.Shiver me timbers holystoneNo prey, no pay hulk weigh
    Killick lad code of conduct swing the lead fire.Shiver me timbers holystoneNo prey, no pay hulk weigh
    260 |
    261 | 262 |

    You want more tables? We have more for you!

    263 |

    If you need to show off some columnar stuff with rows, Drupal 9’s core theme is ready to help. Your data will be beautifully represented for all your readers.

    264 | 265 |
    266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 |
    Table Header Caption 02
    Column Header 1Column Header 2Column Header 3Column Header 4Column Header 5Column Header 6Column Header 7
    Delectus, doloremque deserunt saepe incidunt, molestias excepturi neque distinctio quos deleniti cum maxime doloribus in dolor exercitationem autem voluptatem quo vero aliquid quia.Dicta dolores nisi tenetur eos non. Hic recusandae repellendus provident sequi vel necessitatibus, quidem autem unde consectetur quas illo possimus cupiditate veniam commodi. Quo ut amet, nisi reprehenderit asperiores vel!Incidunt sint eos perferendis rerum officiis reiciendis veritatis deleniti nisi et reprehenderit. Expedita, exercitationem! Animi temporibus laudantium sed dolorem.Delectus, doloremque deserunt saepe incidunt, molestias excepturi neque distinctio quos deleniti cum maxime doloribus in dolor exercitationem autem voluptatem quo vero aliquid quia.Dicta dolores nisi tenetur eos non. Hic recusandae repellendus provident sequi vel necessitatibus, quidem autem unde consectetur quas illo possimus cupiditate veniam commodi. Quo ut amet, nisi reprehenderit asperiores vel!Incidunt sint eos perferendis rerum officiis reiciendis veritatis deleniti nisi et reprehenderit. Expedita, exercitationem! Animi temporibus laudantium sed dolorem.Killick lad code of conduct swing the lead fire.
    Delectus, doloremque deserunt saepe incidunt, molestias excepturi neque distinctio quos deleniti cum maxime doloribus in dolor exercitationem autem voluptatem quo vero aliquid quia.Dicta dolores nisi tenetur eos non. Hic recusandae repellendus provident sequi vel necessitatibus, quidem autem unde consectetur quas illo possimus cupiditate veniam commodi. Quo ut amet, nisi reprehenderit asperiores vel!Incidunt sint eos perferendis rerum officiis reiciendis veritatis deleniti nisi et reprehenderit. Expedita, exercitationem! Animi temporibus laudantium sed dolorem.Killick lad code of conduct swing the lead fire.Delectus, doloremque deserunt saepe incidunt, molestias excepturi neque distinctio quos deleniti cum maxime doloribus in dolor exercitationem autem voluptatem quo vero aliquid quia.Dicta dolores nisi tenetur eos non. Hic recusandae repellendus provident sequi vel necessitatibus, quidem autem unde consectetur quas illo possimus cupiditate veniam commodi. Quo ut amet, nisi reprehenderit asperiores vel!Incidunt sint eos perferendis rerum officiis reiciendis veritatis deleniti nisi et reprehenderit. Expedita, exercitationem! Animi temporibus laudantium sed dolorem.
    300 |
    301 | 302 |

    Subheadings are the best!

    303 |

    Delectus, doloremque deserunt saepe incidunt, molestias excepturi neque distinctio quos deleniti cum maxime doloribus in dolor exercitationem autem voluptatem quo vero aliquid quia. Enim quidem earum consectetur at, sed placeat?

    304 |

    Nostrum magni ipsa illo quae! Dolores laborum vel sapiente doloremque perspiciatis ab provident voluptas obcaecati ullam, fuga exercitationem laboriosam ad dolor animi totam nesciunt explicabo est natus iure. Aliquid, qui?

    305 | 306 |

    Incidunt sint eos perferendis rerum officiis reiciendis veritatis deleniti nisi et reprehenderit. Expedita, exercitationem! Animi temporibus laudantium sed dolorem. Itaque dignissimos animi natus. Praesentium dolor culpa corporis, qui blanditiis impedit!

    307 |

    Dicta dolores nisi tenetur eos non. Hic recusandae repellendus provident sequi vel necessitatibus, quidem autem unde consectetur quas illo possimus cupiditate veniam commodi. Quo ut amet, nisi reprehenderit asperiores vel!

    308 |

    Deleniti dignissimos tempore vero doloribus laboriosam itaque sint nam, eveniet veritatis non, illo fuga ipsam quaerat dolore. Quidem consectetur expedita iste aliquam laboriosam maxime laborum sunt. Ullam veritatis accusamus quidem?

    309 |
    Oh, and if you thought people pay attention to subheadings, just 310 | wait till you 311 | try leveraging some sweet pull quotes!
    312 |

    Cum veritatis autem deserunt at ad dicta pariatur eum. Sed magni distinctio temporibus similique sapiente molestias accusamus quidem odit, consequuntur sint laborum esse suscipit! Itaque quasi distinctio eligendi nisi. Eligendi.

    313 |

    Nobis voluptates dolorum explicabo nihil culpa veniam. Accusantium, fugiat possimus? Quaerat, totam? Nobis in sed minus odio, corrupti nihil quisquam veniam pariatur veritatis illum, explicabo sunt vel rerum soluta dolore.

    314 |

    Doloribus adipisci repellat eius soluta numquam iusto aut corrupti earum impedit error maiores quis vel, rem dignissimos dolorum sapiente nostrum optio ducimus eveniet vitae, eligendi exercitationem. Cumque ullam optio ipsum!

    315 |

    Sint provident, accusamus iure ut inventore numquam id fugit placeat ad ratione quae repellat obcaecati animi voluptatibus necessitatibus maxime fuga commodi dicta aperiam, sunt culpa veniam corporis alias sed. Praesentium.

    316 |

    Eaque dolorum ratione commodi in ipsum dolore nihil odio iste itaque. Recusandae dolorem ipsum, libero facere dolorum autem quam esse labore laboriosam atque facilis. Similique aspernatur beatae iste aut vitae.

    317 |
    318 | DrupalCon Vienna Group Photo 319 |
    DrupalCon Vienna group photo. Photo by Dominik Kiss on behalf of 320 | the Drupal Assocation. Creative Commons.
    321 |
    322 |

    Ducimus adipisci placeat excepturi, doloremque praesentium aspernatur laudantium nostrum minima deserunt, ab blanditiis provident? Mollitia minima sapiente eaque voluptatibus rerum dolores aut natus, labore assumenda esse cupiditate eveniet quam nulla.

    323 |

    Neque inventore illum illo voluptatum ipsum iste beatae nulla nemo consequatur? Accusantium aperiam fugit repellendus, blanditiis vitae earum nam eligendi ipsa labore culpa quisquam ratione, iure voluptate eius sunt veniam?

    324 |

    Voluptates maiores reiciendis, incidunt aut quaerat labore voluptas iure illo delectus ullam, quis assumenda dolor laudantium necessitatibus ut. Neque eum sit deleniti impedit natus dolorem quaerat, eaque veniam libero quod?

    325 |

    Fuga numquam laudantium sunt dolorum obcaecati cumque porro quo maiores ab. Adipisci atque modi nobis quaerat quisquam laboriosam doloremque illo consequatur impedit veniam! Accusantium, porro quod quasi vitae amet facere.

    326 |

    Lorem ipsum dolor sit amet consectetur, adipisicing elit. Itaque placeat esse libero perspiciatis, rerum quia deleniti cupiditate fuga ipsam molestiae. Quos qui officiis modi quae laborum saepe harum commodi sit?

    327 |

    Ipsa, perferendis, commodi repellendus libero, deleniti corrupti expedita eveniet iure fugit ducimus doloribus ipsam amet unde dolores porro ut. Dolorem enim id nulla maiores vel, hic animi ipsum! Molestias, maiores!

    328 |

    Perspiciatis ea molestiae minima hic veritatis ad in dignissimos harum. Odio praesentium nihil, officiis ad nulla cum iusto, et temporibus illum at sint ipsam dolorum quam autem quidem harum quis.

    329 |

    Consequatur fuga consectetur eveniet recusandae sequi possimus maiores. Possimus eveniet nemo excepturi fugit quam reprehenderit pariatur consequuntur, ipsum ipsam quis, dolor aut, necessitatibus praesentium blanditiis veniam ex exercitationem. Commodi, fuga?

    330 |

    Corrupti nobis ratione distinctio dignissimos itaque pariatur aspernatur saepe modi deleniti, voluptatem illo animi quisquam architecto? A animi tempore qui facere, nemo esse voluptate ipsum ipsa, nam quia voluptatem fugit.

    331 |

    Dignissimos ex porro omnis officia eius nobis rerum harum architecto ducimus quam quo sit nihil, soluta amet dolorum reiciendis nemo praesentium voluptatem placeat perspiciatis voluptatum. Nihil illo vitae sint nam!

    332 |

    Delectus animi maiores repellendus illo rem, eum exercitationem est ipsum dignissimos fuga, et voluptate rerum ratione eos eveniet repudiandae. Harum quos, maxime quasi consectetur eveniet sapiente officia aut corrupti commodi.

    333 |

    Est dolorum tenetur ratione illo incidunt culpa nesciunt earum, deleniti accusamus veritatis ut asperiores blanditiis accusantium nulla impedit, labore laborum voluptate ipsam facere dolorem quis eligendi? Veniam atque voluptate qui.

    334 |

    Cupiditate in, at iure veniam nemo explicabo blanditiis omnis a temporibus, dicta rem culpa? Consequatur tempora dolorem perferendis aliquid fugiat, reprehenderit, veniam assumenda quasi magnam quo labore repudiandae nobis explicabo!

    335 |

    Culpa iste excepturi iure, unde quibusdam, modi animi nesciunt natus repellat, cum beatae maiores earum dolor placeat tempore impedit obcaecati fugit perspiciatis dolorum. Non rerum error tempora necessitatibus dolore voluptate.

    336 |
    337 |
    338 |
    339 | 347 |
    348 | 349 | 360 |
    361 | 362 | 363 | 364 | --------------------------------------------------------------------------------