diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7e6c763 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 233 + +[*.json] +indent_style = space +indent_size = 2 + +[*.yml] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..51b5f2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# IDE files # +############# +.idea/ + +# Third Party # +############### +node_modules/ + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +static/js/webslides* diff --git a/CHANGELOG.md b/CHANGELOG.md index b2619e0..0452183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,50 @@ -## 0.2 (2017-02-22) +<<<<<<< HEAD +# 1.0.0 (2017-02-23) -- Auto slide (Demo: why-webslides.html) +This release is a special one since it sets up in the path of a better development environment. Although it's far from +perfect, it's a solid beginning. -## 0.1.1 (2017-02-11) +All the code has been migrated from jQuery with ES5 to vanilla JavaScript with ES2015 (or ES6) and is fully modular. +This means that WebSlides is a (base module)[src/js/modules/webslides.js] with a solid API (few public methods) and +it's extended by (plugins)[src/js/plugins]. This leads to more granularity and less code to dive through while fixing a +bug. + +The benefit from this approach is that now it's really easy to extend WebSlides to achieve what you need. You can also +overwrite current plugins. Say you don't like the current navigation with arrows and want to create a menu instead, you +can just write that for yourself with your custom needs and register it as `nav` and it will overwrite our nav with +your code. + +We hope this leads to a better environment in which WebSlides can grow better. + +All the technical specs live now in [this document](docs/technical.md). + +## Bugfixes + +- Fixed a bug with back/next buttons on the browser which lead the nav bar to not work. + +## New Features + +- Linking to slides without window open. +- Added custom events to listen for. `ws:init` whenever webslides is ready and `ws:slide-change` whenever a slide changes. +- Added play/stop methods. + +## Breaking Changes + +- This "stable" release drops the jQuery requirement and leans on ES2015 for the architecture. Hence, it's no longer possible +to use the library as before. + +# 0.2.0 (2017-02-22) + +## New Features + +- Adding autoslide option. + +# 0.1.1 (2017-02-11) - Transform the library into an object. -- .tabs removed. -- webslides-lite.js removed. +- `.tabs` removed. +- `webslides-lite.js` removed. -## 0.1 (2017-01-08) +# 0.1.0 (2017-01-08) - Initial release. diff --git a/README.md b/README.md index 31c857c..92a711d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # WebSlides = Good Karma -Finally, everything you need to make HTML presentations in a beautiful way. Just the essentials. You can create your own presentation instantly. Simply choose a demo and customize it in minutes — [https://webslides.tv/demos](https://webslides.tv/demos). -A new release (at least) every 8th day of the month. Version 0.1: Jan 8, 2017. +[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT) +[![Twitter](https://img.shields.io/twitter/url/https/github.com/jlantunez/webslides.svg?style=social)](https://twitter.com/webslides) + +Finally, everything you need to make HTML presentations in a beautiful way. Just the essentials. You can create your own presentation instantly. Simply choose a demo and customize it in minutes — https://webslides.tv/demos. + +A new release (at least) every 8th day of the month. ### Why WebSlides? Good karma and productivity. Just a basic knowledge of HTML and CSS is required. Designers, marketers, and journalists can now focus on the content. @@ -18,54 +22,34 @@ Good karma and productivity. Just a basic knowledge of HTML and CSS is required. - Fonts: Roboto, Maitree (Serif), and San Francisco. - Vertical rhythm (use multiples of 8). -### Markup +## Markup - Code is clean and scalable. It uses intuitive markup with popular naming conventions. There's no need to overuse classes or nesting. -- Each parent <section> in the #webslides element is an individual slide. +- Each parent `
` in the `#webslides` element is an individual slide. -
<article id="webslides">
-    <section>
-    	<h1>Slide 1</h1>
-    </section>
-    <section class="bg-black aligncenter">
-    <!-- .wrap = container 1200px -->
-    	<div class="wrap">
-    		<h1>Slide 2</h1>
-    	</div>
-    </section>
-</article>
- -#### Vertical Sliding - -
<article id="webslides" class="vertical">
- -### How it works - -You need to add the follow javascript to initialize the webslides object. - -```javascript -var slide = jQuery('#webslides').webslides(); +```html +
+
+

Slide 1

+
+
+ +
+

Slide 2

+
+
+
``` -#### Auto slide +### Vertical Sliding -```javascript -var slide = jQuery('#webslides').webslides({interval: 5000}); -``` -Now you can use the slide with these functions: - -```javascript -// Moving to next slide -slide.nextSlide(); -// Moving to previous slide -slide.previousSlide(); -// Moving to a specific slide -slide.goToSlide(n); +```html +
``` ### What's in the download? -The download includes demos and images (devices and logos). +The download includes demos and images (devices and logos). All content is for demo purposes only. Images are property of their respective owners. ``` @@ -84,16 +68,17 @@ webslides/ ### CSS Syntax (classes) -- Typography: .text-landing, .text-data, .text-intro... -- Background Colors: .bg-primary, .bg-apple, .bg-blue... -- Background Images: .background,.background-center-bottom... -- Cards: .card-50, .card-40... -- Flexible Blocks: .flexblock.clients, .flexblock.metrics... +- Typography: `.text-landing`, `.text-data`, `.text-intro`... +- Background Colors: `.bg-primary`, `.bg-apple`, `.bg-blue`... +- Background Images: `.background`,`.background-center-bottom`... +- Cards: `.card-50`, `.card-40`... +- Flexible Blocks: `.flexblock.clients`, `.flexblock.metrics`... ### Extensions You can add: + - [Unsplash](https://unsplash.com) photos - [animate.css](https://daneden.github.io/animate.css) - [particles.js](https://github.com/VincentGarreau/particles.js) @@ -101,9 +86,16 @@ You can add: ### License -WebSlides is licensed under the [MIT License](https://opensource.org/licenses/MIT). +WebSlides is licensed under the [MIT License](https://opensource.org/licenses/MIT). Use it to make something cool. +### Dive In! + +Please check out: + + - Want to get techie? Read [our technical docs](docs/technical.md) + - Do not miss [our demos](https://webslides.tv/) + ### Credits - WebSlides was created by [@jlantunez](https://twitter.com/jlantunez) using [Cactus](https://github.com/eudicots/Cactus). diff --git a/demos/classes.html b/demos/classes.html index d3bf374..c49013c 100644 --- a/demos/classes.html +++ b/demos/classes.html @@ -1933,16 +1933,13 @@ - - + - - - - + diff --git a/demos/components.html b/demos/components.html index dd421b8..17804e2 100644 --- a/demos/components.html +++ b/demos/components.html @@ -319,7 +319,11 @@

Navigation

- +
    +
  • ul.tabs
  • +
  • columns
  • +
+

nav.navbar

- -
+
+ +

Company

@@ -430,6 +435,8 @@
+
+
@@ -1796,8 +1803,49 @@
-

Tell a Story

-

Hi, this is WebSlides. HTML presentations made simple.
I'm a cute solution with clean markup and lovely CSS.

+
+

Tell a Story

+

Hi, this is WebSlides. HTML presentations made simple.
I'm a cute solution with clean markup and lovely CSS.

+
+
+
    +
  • +
    + + + +

    Indexed content

    + Sharing is caring. +
    +
  • +
  • +
    +

    + + + + Just essential features +

    + Keyboard navigation... +
    +
  • +
  • +
    + + + +

    + Prototype faster +

    + with clean code +
    +
  • +
+
+
    +
  • Purpose
  • +
  • Benefits
  • +
@@ -3078,16 +3126,14 @@ - - + - - - + diff --git a/demos/index.html b/demos/index.html index 4400af9..9de1f39 100644 --- a/demos/index.html +++ b/demos/index.html @@ -92,7 +92,7 @@
-
+
@@ -222,16 +222,14 @@ - - + - - - + diff --git a/demos/keynote.html b/demos/keynote.html index fb2cfc6..1cc31f1 100644 --- a/demos/keynote.html +++ b/demos/keynote.html @@ -843,16 +843,14 @@ - - + - - - + diff --git a/demos/landings.html b/demos/landings.html index 5f92152..bf5eb81 100644 --- a/demos/landings.html +++ b/demos/landings.html @@ -1918,16 +1918,13 @@ - - + - - - diff --git a/demos/portfolios.html b/demos/portfolios.html index a37757c..8f00bbe 100644 --- a/demos/portfolios.html +++ b/demos/portfolios.html @@ -1858,16 +1858,14 @@ - - + - - - + diff --git a/demos/why-webslides.html b/demos/why-webslides.html index c534449..10a7830 100644 --- a/demos/why-webslides.html +++ b/demos/why-webslides.html @@ -66,7 +66,6 @@ -
@@ -339,13 +338,11 @@ - - + - - - diff --git a/docs/technical.md b/docs/technical.md new file mode 100644 index 0000000..edde70b --- /dev/null +++ b/docs/technical.md @@ -0,0 +1,135 @@ +## Project Setup + +This project assumes you have NodeJS. You should also have npm installed as well (this usually comes packaged with Node). Once you have it cloned, you should run `npm install` to get all the dependencies. + +Finally, run one of the following commands in the cloned directory: + +- `npm run dev`: This starts a dev server with autoreload on the port `8080`. +- `npm run build`: This creates the `dist` files. + +## JavaScript + +In order to bootstrap the WebSlides you'll need to create a instance of it: + +```javascript +const ws = new WebSlides(); +``` + +That'll make everything run without any hassle. + +### Options + +WebSlides constructor accepts an object with options. + +| Param | Type | Default | Description | +|-----------|----------------|-----------|-------------------------------------------------------------------------------| +| `autoslide` | `number` or `boolean` | `false` | Amount of milliseconds to wait to go to next slide automatically. | + + +```javascript +const ws = new WebSlides({ + autoslide: false +}); +``` + +### API + +Do you want to get your hands dirty? This is the API for the WebSlides module: + +
+
goToSlide(slideIndex, opt_forward)
+

Goes to a given slide.

+
goNext()
+

Goes to the next slide.

+
goPrev()
+

Goes to the previous slide.

+
play()
+

Starts autosliding.

+
stop()
+

Stops autosliding.

+
+
registerPlugin(key, cto)
+

Registers a plugin to be loaded when the instance is created. It allows + (on purpose) to replace default plugins. + Those being:

+
    +
  • Navigation
  • +
  • Hash
  • +
  • Keyboard
  • +
+
+
+ + + +### `goToSlide(slideI, forward)` +Goes to a given slide. + +| Param | Type | Description | +| --- | --- | --- | --- | +| slideIndex | number | The slide index. | +| forward | boolean | Whether we're forcing moving forward/backwards. This parameter is used only from the `goNext`, `goPrev` functions to adjust the scroll animations. | + + + +### `goNext()` +Goes to the next slide. If the page is vertical, it will animate the scroll down. + + + +### `goPrev()` +Goes to the previous slide. If the page is vertical, it will animate the scroll up + + + +### `play(time)` +Autoplays slides. If time is omitted, it will use the default time passed to the constructor. This is useful if you don't want to autoslide from the beginning but you want to add a button to do it. + +| Param | Type | Description | +| --- | --- | --- | --- | +| time | number | Amount of milliseconds to wait to go to next slide automatically. | + + + +### `stop()` +Stops autosliding. + + + +### `registerPlugin(key, cto)` +Registers a plugin to be loaded when the instance is created. It allows +(on purpose) to replace default plugins. + +Those being: + + - Navigation + - Hash + - Keyboard + +| Param | Type | Description | +| --- | --- | --- | +| key | string | They key under which it'll be stored inside of the instance, inside the plugins dict. | +| cto | function | Plugin constructor. | + +### Plugin development + +Almost every single feature of WebSlides is a plugin that can be overwritten and you are able to create your custom plugins. Just call `registerPlugin` (as seen above) **before creating** the instance: + +```javascript +// Adding the constructor to WebSlides +WebSlides.registerPlugin('myPlugin', MyPlugin); + +// Starting WebSlides +// Your plugin will be constructed at this time and it will receive the webslides instance as the only parameter. +const ws = new WebSlides(); +// You can also access ws.plugins.myPlugin now +``` + +This allows you to rewrite the navigation to use a menu (for example) or add that missing piece of functionality you'd like to see. See [this part of the code](../src/js/modules/webslides.js#L11) to see all the plugins we're using and the name they're using. + +Make sure to let us know so it could get added to the repo! + +### Roadmap + +* Fix crossbrowser issues. Safari mostly. +* Write tests diff --git a/index.html b/index.html index 70943a8..bd29b9c 100644 --- a/index.html +++ b/index.html @@ -94,7 +94,6 @@
- - - + - - - - + diff --git a/package.json b/package.json new file mode 100644 index 0000000..28c3a04 --- /dev/null +++ b/package.json @@ -0,0 +1,48 @@ +{ + "name": "webslides", + "version": "1.0.0", + "description": "Making HTML presentations easy", + "main": "index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/jlantunez/webslides.git" + }, + "keywords": [ + "webslides", + "presentation", + "css" + ], + "author": "Jose Luís Antúnez", + "license": "MIT", + "bugs": { + "url": "https://github.com/jlantunez/webslides/issues" + }, + "homepage": "https://github.com/jlantunez/webslides#readme", + "devDependencies": { + "babel-cli": "^6.23.0", + "babel-core": "^6.23.1", + "babel-loader": "^6.3.2", + "babel-preset-es2015": "^6.22.0", + "npm-run-all": "^4.0.2", + "rimraf": "^2.6.0", + "webpack": "^2.2.1", + "webpack-dev-server": "^2.4.1" + }, + "scripts": { + "prebuild": "rimraf static/js/webslide*", + "build": "npm-run-all --parallel build:*", + "build:main": "webpack", + "build:main.min": "webpack --output-filename [name].min.js -p", + "dev": "webpack-dev-server" + }, + "babel": { + "presets": [ + [ + "es2015", + { + "modules": false + } + ] + ] + } +} diff --git a/src/js/full.js b/src/js/full.js new file mode 100644 index 0000000..ad71d64 --- /dev/null +++ b/src/js/full.js @@ -0,0 +1,3 @@ +import WebSlides from './modules/webslides'; + +window.WebSlides = WebSlides; diff --git a/src/js/modules/slide.js b/src/js/modules/slide.js new file mode 100644 index 0000000..6203cb9 --- /dev/null +++ b/src/js/modules/slide.js @@ -0,0 +1,82 @@ +import DOM from '../utils/dom'; + +const CLASSES = { + SLIDE: 'slide', + CURRENT: 'current' +}; + +/** + * Wrapper for the Slide section. + */ +export default class Slide { + /** + * Bootstraps the slide by saving some data, adding a class and hiding it. + * @param {Element} el Section element. + * @param {number} i Zero based index of the slide. + */ + constructor(el, i) { + /** + * @type {Element} + */ + this.el = el; + /** + * The section's parent. + * @type {Node} + */ + this.parent = el.parentNode; + /** + * @type {number} + */ + this.i = i; + + this.el.id = 'section-' + (i + 1); + this.el.classList.add(CLASSES.SLIDE); + + // Hide slides by default + this.hide(); + } + + /** + * Hides the node and removes the class that makes it "active". + */ + hide() { + DOM.hide(this.el); + this.el.classList.remove(CLASSES.CURRENT); + } + + /** + * Shows the node and adds the class that makes it "active". + */ + show() { + DOM.show(this.el); + this.el.classList.add(CLASSES.CURRENT); + } + + /** + * Moves the section to the bottom of the section's list. + */ + moveAfterLast() { + const last = this.parent.childNodes[this.parent.childElementCount - 1]; + + this.parent.insertBefore(this.el, last.nextSibling); + } + + /** + * Moves the section to the top of the section's list. + */ + moveBeforeFirst() { + const first = this.parent.childNodes[0]; + + this.parent.insertBefore(this.el, first); + } + + /** + * Checks whether an element is a valid candidate to be a slide by ensuring + * it's a "section" element. + * @param {Element} el Element to be checked. + * @return {boolean} Whether is candidate or not. + */ + static isCandidate(el) { + return el.nodeType === 1 && el.tagName === 'SECTION'; + } +} diff --git a/src/js/modules/webslides.js b/src/js/modules/webslides.js new file mode 100644 index 0000000..748d03f --- /dev/null +++ b/src/js/modules/webslides.js @@ -0,0 +1,364 @@ +import Plugins from '../plugins/plugins'; +import Slide from './slide'; +import DOM from '../utils/dom'; +import ScrollHelper from '../utils/scroll-to'; + +const CLASSES = { + VERTICAL: 'vertical' +}; + +// Default plugins +const PLUGINS = { + 'grid': Plugins.Grid, + 'hash': Plugins.Hash, + 'keyboard': Plugins.Keyboard, + 'nav': Plugins.Navigation, + 'scroll': Plugins.Scroll, + 'touch': Plugins.touch +}; + +export default class WebSlides { + /** + * Options for WebSlides + * @param {number|boolean} autoslide Is false by default. If a number is + * provided, it will autoslide every given milliseconds. + */ + constructor({ + autoslide = false + } = {}) { + /** + * WebSlide element. + * @type {Element} + */ + this.el = document.getElementById('webslides'); + /** + * Moving flag. + * @type {boolean} + */ + this.isMoving = false; + /** + * Slide's array. + * @type {?Array} + */ + this.slides = null; + /** + * Current slide's index. + * @type {number} + * @private + */ + this.currentSlideI_ = -1; + /** + * Current slide reference. + * @type {?Slide} + * @private + */ + this.currentSlide_ = null; + /** + * Max slide index. + * @type {number} + * @private + */ + this.maxSlide_ = 0; + /** + * Whether the layout is going to be vertical or horizontal. + * @type {boolean} + */ + this.isVertical = this.el.classList.contains(CLASSES.VERTICAL); + /** + * Plugin's dictionary. + * @type {Object} + */ + this.plugins = {}; + /** + * Interval ID reference for the autoslide. + * @type {?number} + * @private + */ + this.interval_ = null; + /** + * Amount of time to wait to go to next slide automatically or false to + * disable the feature. + * @type {boolean|number} + * @private + */ + this.autoslide_ = autoslide; + + if (!this.el) { + throw new Error('Couldn\'t find the webslides container!'); + } + + // Bootstrapping + this.removeChildren_(); + this.grabSlides_(); + this.createPlugins_(); + this.initSlides_(); + this.play(); + // Finished + this.onInit_(); + } + + /** + * Removes all children elements inside of the main container that are not + * eligible to be a Slide Element. + * @private + */ + removeChildren_() { + const nodes = this.el.childNodes; + let i = nodes.length; + + while (i--) { + const node = nodes[i]; + + if (!Slide.isCandidate(node)) { + this.el.removeChild(node); + } + } + } + + /** + * Creates all the registered plugins and store the instances inside of the + * the webslide instance. + * @private + */ + createPlugins_() { + Object.keys(PLUGINS).forEach(pluginName => { + const pluginCto = PLUGINS[pluginName]; + this.plugins[pluginName] = new pluginCto(this); + }); + } + + /** + * Called once the WebSlide instance has finished initialising. + * @private + * @fires WebSlide#ws:init + */ + onInit_() { + DOM.fireEvent(this.el, 'ws:init'); + } + + /** + * Grabs the slides from the DOM and creates all the Slides modules. + * @private + */ + grabSlides_() { + this.slides = Array.from(this.el.childNodes) + .map((slide, i) => new Slide(slide, i)); + + this.maxSlide_ = this.slides.length; + } + + /** + * Goes to a given slide. + * @param {!number} slideI The slide index. + * @param {?boolean} forward Whether we're forcing moving forward/backwards. + * This parameter is used only from the goNext, goPrev functions to adjust the + * scroll animations. + */ + goToSlide(slideI, forward = null) { + if (this.isValidIndexSlide_(slideI) && !this.isMoving) { + this.isMoving = true; + let isMovingForward = false; + + if (forward !== null) { + isMovingForward = forward; + } else { + if (this.currentSlideI_ >= 0) { + isMovingForward = slideI > this.currentSlideI_; + } + } + const nextSlide = this.slides[slideI]; + + if (this.currentSlide_ !== null && this.isVertical && + (!this.plugins.touch || !this.plugins.touch.isEnabled)) { + this.scrollTransitionToSlide_( + isMovingForward, nextSlide, this.onSlideChange_); + } else { + this.transitionToSlide_( + isMovingForward, nextSlide, this.onSlideChange_); + } + } + } + + /** + * Transitions to a slide, doing the scroll animation. + * @param {boolean} isMovingForward Whether we're going forward or backwards. + * @param {Slide} nextSlide Next slide. + * @param {Function} callback Callback to be called upon finishing. This is an + * async function so it'll happen once the scroll animation finishes. + * @private + * @see DOM.lockScroll + * @see DOM.unlockScroll + * @see ScrollHelper.scrollTo + */ + scrollTransitionToSlide_(isMovingForward, nextSlide, callback) { + DOM.lockScroll(); + + if (!isMovingForward) { + nextSlide.moveBeforeFirst(); + nextSlide.show(); + ScrollHelper.scrollTo(this.currentSlide_.el.offsetTop, 0); + } else { + nextSlide.show(); + } + + ScrollHelper.scrollTo(nextSlide.el.offsetTop, 500, () => { + this.currentSlide_.hide(); + + if (isMovingForward) { + this.currentSlide_.moveAfterLast(); + } + + DOM.unlockScroll(); + setTimeout(() => { callback.call(this, nextSlide); }, 150); + }); + } + + /** + * Transitions to a slide, without doing the scroll animation. + * @param {boolean} isMovingForward Whether we're going forward or backwards. + * @param {Slide} nextSlide Next slide. + * @param {Function} callback Callback to be called upon finishing. This is a + * sync function so it'll happen on run time. + * @private + */ + transitionToSlide_(isMovingForward, nextSlide, callback) { + ScrollHelper.scrollTo(0, 0); + + if (!isMovingForward) { + nextSlide.moveBeforeFirst(); + } + + if (this.currentSlide_) { + if (isMovingForward) { + this.currentSlide_.moveAfterLast(); + } + + this.currentSlide_.hide(); + } + + nextSlide.show(); + callback.call(this, nextSlide); + } + + /** + * Whenever a slide is changed, this function gets called. It updates the + * references to the current slide, disables the moving flag and fires + * a custom event. + * @param {Slide} slide The slide we're transitioning to. + * @fires WebSlide#ws:slide-change + * @private + */ + onSlideChange_(slide) { + this.currentSlide_ = slide; + this.currentSlideI_ = slide.i; + this.isMoving = false; + + DOM.fireEvent(this.el, 'ws:slide-change', { + slides: this.maxSlide_, + currentSlide0: this.currentSlideI_, + currentSlide: this.currentSlideI_ + 1 + }); + } + + /** + * Goes to the next slide. + */ + goNext() { + let nextIndex = this.currentSlideI_ + 1; + + if (nextIndex >= this.maxSlide_) { + nextIndex = 0; + } + + this.goToSlide(nextIndex, true); + } + + /** + * Goes to the previous slide. + */ + goPrev() { + let prevIndex = this.currentSlideI_ - 1; + + if (prevIndex < 0) { + prevIndex = this.maxSlide_ - 1; + } + + this.goToSlide(prevIndex, false); + } + + /** + * Check if the given number is a valid index to go to. + * @param {number} i The index to check. + * @return {boolean} Whether you can move to that slide or not. + * @private + */ + isValidIndexSlide_(i) { + return i >= 0 && i < this.maxSlide_; + } + + /** + * Init the shown slide on load. It'll fetch it from the Hash if present + * and, otherwise, it'll default to the first one. + * @private + * @see Hash.getSlideNumber + */ + initSlides_() { + let slideNumber = this.plugins.hash.constructor.getSlideNumber(); + + // Not valid + if (slideNumber === null || + slideNumber >= this.maxSlide_) { + slideNumber = 0; + } + + // Keeping the order + if (slideNumber !== 0) { + let i = 0; + while(i < slideNumber) { + this.slides[i].moveAfterLast(); + i++; + } + } + + this.goToSlide(slideNumber); + } + + /** + * Registers a plugin to be loaded when the instance is created. It allows + * (on purpose) to replace default plugins. + * Those being: + * - Navigation + * - Hash + * - Keyboard + * @param {!string} key They key under which it'll be stored inside of the + * instance, inside the plugins dict. + * @param {!Function} cto Plugin constructor. + */ + static registerPlugin(key, cto) { + PLUGINS[key] = cto; + } + + /** + * Starts autosliding all the slides if it's not currently doing it and the + * autoslide option was a number greater than 0. + * @param {?number} time Amount of milliseconds to wait to go to next slide + * automatically. + */ + play(time) { + time = time || this.autoslide_; + + if (!this.interval_ && Number.isInteger(time) && time > 0) { + this.interval_ = setInterval(this.goNext.bind(this), time); + } + } + + /** + * Stops autosliding all the slides. + */ + stop() { + if (this.interval_) { + clearInterval(this.interval_); + this.interval_ = null; + } + } +} diff --git a/src/js/plugins/grid.js b/src/js/plugins/grid.js new file mode 100644 index 0000000..c6e5989 --- /dev/null +++ b/src/js/plugins/grid.js @@ -0,0 +1,44 @@ +import Keys from '../utils/keys'; + +export default class Keyboard { + /** + * Grid plugin that shows a grid on top of the WebSlides for easy prototyping. + * @param {WebSlides} wsInstance The WebSlides instance + */ + constructor(wsInstance) { + /** + * @type {WebSlides} + * @private + */ + this.ws_ = wsInstance; + + const CSS = `body.baseline { + background: url(../images/baseline.png) left top .8rem/.8rem; + }`; + const head = document.head || document.getElementsByTagName('head')[0]; + const style = document.createElement('style'); + + style.type = 'text/css'; + + if (style.styleSheet){ + style.styleSheet.cssText = CSS; + } else { + style.appendChild(document.createTextNode(CSS)); + } + + head.appendChild(style); + + document.addEventListener('keydown', this.onKeyPress_.bind(this), false); + } + + /** + * Reacts to the keydown event. It reacts to ENTER key to toggle the class. + * @param {KeyboardEvent} event The key event. + * @private + */ + onKeyPress_(event) { + if (event.which === Keys.ENTER) { + document.body.toggleClass('baseline'); + } + } +} diff --git a/src/js/plugins/hash.js b/src/js/plugins/hash.js new file mode 100644 index 0000000..f447d37 --- /dev/null +++ b/src/js/plugins/hash.js @@ -0,0 +1,71 @@ +const HASH = '#slide'; +const slideRegex = /#slide=(\d+)/; + +/** + * Static class with methods to manipulate and extract info from the hash of + * the URL. + */ +export default class Hash { + /** + * Listens to the slide change event and the hash change events. + * @param wsInstance + */ + constructor(wsInstance) { + this.ws_ = wsInstance; + + wsInstance.el.addEventListener('ws:slide-change', Hash.onSlideChange_); + window.addEventListener('hashchange', this.onHashChange_.bind(this), false); + } + + /** + * hashchange event handler, makes the WebSlide instance navigate to the + * needed slide. + */ + onHashChange_() { + const newSlideIndex = Hash.getSlideNumber(); + + if (newSlideIndex !== null) { + this.ws_.goToSlide(newSlideIndex); + } + } + + static onSlideChange_(event) { + Hash.setSlideNumber(event.detail.currentSlide); + } + + /** + * Gets the slide number from the hash by a regex matching `#slide=` and gets + * the number after it. If the number is invalid or less than 0, it will + * return null as an invalid value. + * @return {?number} + */ + static getSlideNumber() { + let results = document.location.hash.match(slideRegex); + let slide = 0; + + if (Array.isArray(results)) { + slide = parseInt(results[1], 10); + } + + if (!Number.isInteger(slide) || slide < 0 || !Array.isArray(results)) { + slide = null; + } else { + slide--; // Convert to 0 index + } + + return slide; + } + + /** + * It will update the hash (if it's different) so it reflects the slide + * number being visible. + * @param {number} number The number of the slide we're transitioning to. + */ + static setSlideNumber(number) { + if (Hash.getSlideNumber() !== (number - 1)) { + history.pushState({ + slideI: number - 1 + }, `Slide ${number}`, `${HASH}=${number}`); + } + } +} diff --git a/src/js/plugins/keyboard.js b/src/js/plugins/keyboard.js new file mode 100644 index 0000000..20f473d --- /dev/null +++ b/src/js/plugins/keyboard.js @@ -0,0 +1,49 @@ +import Keys from '../utils/keys'; + +export default class Keyboard { + /** + * Keyboard interaction plugin. + * @param {WebSlides} wsInstance The WebSlides instance + */ + constructor(wsInstance) { + /** + * @type {WebSlides} + * @private + */ + this.ws_ = wsInstance; + + document.addEventListener('keydown', this.onKeyPress_.bind(this), false); + } + + /** + * Reacts to the keydown event. It reacts to the arrows and space key + * depending on the layout of the page. + * @param {KeyboardEvent} event The key event. + * @private + */ + onKeyPress_(event) { + let method; + + if (event.which === Keys.SPACE) { + method = this.ws_.goNext; + } else { + if (this.ws_.isVertical) { + if (event.which === Keys.DOWN) { + method = this.ws_.goNext; + } else if (event.which === Keys.UP) { + method = this.ws_.goPrev; + } + } else { + if (event.which === Keys.RIGHT) { + method = this.ws_.goNext; + } else if (event.which === Keys.LEFT) { + method = this.ws_.goPrev; + } + } + } + + if (method) { + method.call(this.ws_); + } + } +} diff --git a/src/js/plugins/navigation.js b/src/js/plugins/navigation.js new file mode 100644 index 0000000..59178f4 --- /dev/null +++ b/src/js/plugins/navigation.js @@ -0,0 +1,120 @@ +import DOM from '../utils/dom'; + +const ELEMENT_ID = { + NAV: 'navigation', + NEXT: 'next', + PREV: 'previous', + COUNTER: 'counter' +}; + +const LABELS = { + VERTICAL: { + NEXT: '↓', + PREV: '↑' + }, + HORIZONTAL: { + NEXT: '→', + PREV: '←' + } +}; + +export default class Navigation { + /** + * The Navigation constructor. It'll create all the nodes needed for the + * navigation such as the arrows and the counter. + * @param {WebSlides} wsInstance The WebSlides instance + */ + constructor(wsInstance) { + const arrowLabels = wsInstance.isVertical ? + LABELS.VERTICAL : LABELS.HORIZONTAL; + /** + * Navigation element. + * @type {Element} + */ + this.el = DOM.createNode('div', 'navigation'); + /** + * Next button. + * @type {Element} + */ + this.next = Navigation.createArrow(ELEMENT_ID.NEXT, arrowLabels.NEXT); + /** + * Prev button. + * @type {Element} + */ + this.prev = Navigation.createArrow(ELEMENT_ID.PREV, arrowLabels.PREV); + /** + * Counter Element. + * @type {Element} + */ + this.counter = DOM.createNode('span', ELEMENT_ID.COUNTER); + /** + * @type {WebSlides} + * @private + */ + this.ws_ = wsInstance; + + this.el.appendChild(this.next); + this.el.appendChild(this.prev); + this.el.appendChild(this.counter); + + this.ws_.el.appendChild(this.el); + this.bindEvents_(); + } + + /** + * Bind all events for the navigation. + * @private + */ + bindEvents_() { + this.ws_.el.addEventListener( + 'ws:slide-change', this.onSlideChanged_.bind(this)); + this.next.addEventListener('click', this.onButtonClicked_.bind(this)); + this.prev.addEventListener('click', this.onButtonClicked_.bind(this)); + } + + /** + * Updates the counter inside the navigation. + * @param {string|number} current Current slide number. + * @param {string|number} max Max slide number. + */ + updateCounter(current, max) { + this.counter.textContent = `${current} / ${max}`; + } + + /** + * Creates an arrow to navigate. + * @param {!String} id Desired ID for the arrow. + * @param {!String} text Desired text for the arrow. + * @return {Element} The arrow element. + */ + static createArrow(id, text) { + const arrow = DOM.createNode('a', id, text); + arrow.href = '#'; + arrow.title = 'Arrow Keys'; + + return arrow; + } + + /** + * Slide Change event handler. Will update the text on the navigation. + * @param {CustomEvent} event + * @private + */ + onSlideChanged_(event) { + this.updateCounter(event.detail.currentSlide, event.detail.slides); + } + + /** + * Handles clicks on the next/prev buttons. + * @param {MouseEvent} event + * @private + */ + onButtonClicked_(event) { + event.preventDefault(); + if (event.target === this.next) { + this.ws_.goNext(); + } else { + this.ws_.goPrev(); + } + } +} diff --git a/src/js/plugins/plugins.js b/src/js/plugins/plugins.js new file mode 100644 index 0000000..4e7d6e2 --- /dev/null +++ b/src/js/plugins/plugins.js @@ -0,0 +1,15 @@ +import Grid from './grid'; +import Hash from './hash'; +import Keyboard from './keyboard'; +import Navigation from './navigation'; +import Scroll from './scroll'; +import Touch from './touch'; + +export default { + Grid, + Hash, + Keyboard, + Navigation, + Scroll, + Touch +}; diff --git a/src/js/plugins/scroll.js b/src/js/plugins/scroll.js new file mode 100644 index 0000000..caa6ee2 --- /dev/null +++ b/src/js/plugins/scroll.js @@ -0,0 +1,50 @@ +import ScrollHelper from '../utils/scroll-to'; + +const MIN_WHEEL_DELTA = 40; + +export default class Scroll { + /** + * Scroll handler for the WebSlides. + * @param {WebSlides} wsInstance The WebSlides instance + */ + constructor(wsInstance) { + /** + * @type {WebSlides} + * @private + */ + this.ws_ = wsInstance; + + this.scrollContainer_ = ScrollHelper.getScrollableContainer(); + this.isGoingUp_ = false; + + if (this.ws_.isVertical) { + this.scrollContainer_.addEventListener( + 'wheel', this.onMouseWheel_.bind(this)); + } + } + + /** + * Reacts to the wheel event. Detects whether is going up or down and decides + * if it needs to move the slide based on the amount of delta. + * @param {WheelEvent} event The Wheel Event. + * @private + */ + onMouseWheel_(event) { + if (this.ws_.isMoving) { + return; + } + + const { deltaY: wheelDelta } = event; + this.isGoingUp_ = wheelDelta < 0; + + if (Math.abs(wheelDelta) >= MIN_WHEEL_DELTA) { + if (this.isGoingUp_) { + this.ws_.goPrev(); + } else { + this.ws_.goNext(); + } + + event.preventDefault(); + } + } +}; diff --git a/src/js/plugins/touch.js b/src/js/plugins/touch.js new file mode 100644 index 0000000..eeaea10 --- /dev/null +++ b/src/js/plugins/touch.js @@ -0,0 +1,151 @@ +import MobileDetector from '../utils/mobile-detector'; + +const EVENTS = { + touch: { + START: 'touchstart', + MOVE: 'touchmove', + END: 'touchend' + }, + pointer: { + START: 'pointerdown', + MOVE: 'pointermove', + END: 'pointerup' + } +}; + +const SLIDE_OFFSET = 50; + +export default class Touch { + /** + * @param {WebSlides} wsInstance The WebSlides instance + */ + constructor(wsInstance) { + /** + * @type {WebSlides} + * @private + */ + this.ws_ = wsInstance; + + /** + * Start position for the X coord. + * @type {number} + * @private + */ + this.startX_ = 0; + + /** + * Start position for the Y coord. + * @type {number} + * @private + */ + this.startY_ = 0; + + /** + * Start position for the X coord. + * @type {number} + * @private + */ + this.endX_ = 0; + + /** + * Start position for the Y coord. + * @type {number} + * @private + */ + this.endY_ = 0; + + /** + * Whether is enabled or not. Only enabled for touch devices. + * @type {boolean} + * @private + */ + this.isEnabled = false; + + let events; + + if (MobileDetector.isAny()) { + // Likely IE + if (window.PointerEvent && ( + MobileDetector.isWindows() || MobileDetector.isWindowsPhone())) { + events = EVENTS.pointer; + } else { + events = EVENTS.touch; + } + + this.isEnabled = true; + document.addEventListener(events.START, this.onStart_.bind(this), false); + document.addEventListener(events.MOVE, this.onMove_.bind(this), false); + document.addEventListener(events.MOVE, this.onMove_.bind(this), false); + document.addEventListener(events.END, this.onStop_.bind(this), false); + } + } + + /** + * Start touch handler. Saves starting points. + * @param event + * @private + */ + onStart_(event) { + const info = Touch.normalizeEventInfo(event); + + this.startX_ = info.x; + this.startY_ = info.y; + this.endX_ = info.x; + this.endY_ = info.y; + } + + /** + * Move touch handler. Saves end points. + * @param event + * @private + */ + onMove_(event) { + const info = Touch.normalizeEventInfo(event); + + this.endX_ = info.x; + this.endY_ = info.y; + } + + /** + * Stop touch handler. Checks if it needs to make any actions. + * @private + */ + onStop_() { + const diffX = this.startX_ - this.endX_; + const diffY = this.startY_ - this.endY_; + + // It's an horizontal drag + if (Math.abs(diffX) > Math.abs(diffY)) { + if(diffX < -SLIDE_OFFSET) { + this.ws_.goPrev(); + } else if(diffX > SLIDE_OFFSET) { + this.ws_.goNext(); + } + } + } + + /** + * Normalizes an event to deal with differences between PointerEvent and + * TouchEvent. + * @param event + * @return {*} + */ + static normalizeEventInfo(event) { + let x; + let y; + let touchEvent = { pageX : 0, pageY : 0}; + + if (typeof event.changedTouches !== 'undefined'){ + touchEvent = event.changedTouches[0]; + } + else if (typeof event.originalEvent !== 'undefined' && + typeof event.originalEvent.changedTouches !== 'undefined'){ + touchEvent = event.originalEvent.changedTouches[0]; + } + + x = event.offsetX || event.layerX || touchEvent.pageX; + y = event.offsetY || event.layerY || touchEvent.pageY; + + return { x, y }; + } +}; diff --git a/src/js/utils/custom-event.js b/src/js/utils/custom-event.js new file mode 100644 index 0000000..587af72 --- /dev/null +++ b/src/js/utils/custom-event.js @@ -0,0 +1,38 @@ +const NativeCustomEvent = window.CustomEvent; + +/** + * Check for the usage of native support for CustomEvents which is lacking + * completely on IE. + * @return {boolean} Whether it can be used or not. + */ +function canIuseNativeCustom () { + try { + const p = new NativeCustomEvent('t', { detail: { a: 'b' } }); + return 't' === p.type && 'b' === p.detail.a; + } catch (e) { + } + return false; +} + +/** + * Lousy polyfill for the Custom Event constructor for IE. + * @param {!string} type The type of the event. + * @param {?Object} params Additional information for the event. + * @return {Event} + * @constructor + */ +const IECustomEvent = function CustomEvent(type, params) { + const e = document.createEvent('CustomEvent'); + + if (params) { + e.initCustomEvent(type, params.bubbles, params.cancelable, params.detail); + } else { + e.initCustomEvent(type, false, false, undefined); + } + + return e; +}; + +const WSCustomEvent = canIuseNativeCustom() ? NativeCustomEvent : IECustomEvent; + +export default WSCustomEvent; diff --git a/src/js/utils/dom.js b/src/js/utils/dom.js new file mode 100644 index 0000000..3129c2d --- /dev/null +++ b/src/js/utils/dom.js @@ -0,0 +1,75 @@ +import WSCustomEvent from './custom-event'; + + +/** + * Static class for DOM helper. + */ +export default class DOM { + /** + * Creates a node with optional parameters. + * @param {string} tag The name of the tag of the needed element. + * @param {string} id The desired id for the element. It defaults to an + * empty string. + * @param {string} text The desired text to go inside of the element. It defaults + * to an empty string. + * @return {Element} + */ + static createNode(tag, id = '', text = '') { + const node = document.createElement(tag); + node.id = id; + + if (text) { + node.textContent = text; + } + + return node; + } + + /** + * Hides an element setting the display to none. + * @param {Element} el Element to be hidden. + */ + static hide(el) { + el.style.display = 'none'; + } + + /** + * Shows an element by removing the display property. This is only intended + * to be used in conjunction with DOM.hide. + * @param {Element} el Element to be shown. + */ + static show(el) { + el.style.display = ''; + } + + /** + * Locks the scroll on the document by setting the HTML to have a hidden + * overflow. + */ + static lockScroll() { + document.documentElement.style.overflow = 'hidden'; + } + + /** + * Unlocks the scroll on the document by setting the HTML to have an auto + * overflow. + */ + static unlockScroll() { + document.documentElement.style.overflow = 'auto'; + } + + /** + * Fires a custom event on the given target. + * @param {Element} target The target of the event. + * @param {string} eventType The event type. + * @param {Object} eventInfo Optional parameter to provide additional data + * to the event. + */ + static fireEvent(target, eventType, eventInfo = {}) { + const event = new WSCustomEvent(eventType, { + detail: eventInfo + }); + + target.dispatchEvent(event); + } +} diff --git a/src/js/utils/easing.js b/src/js/utils/easing.js new file mode 100644 index 0000000..4cbc293 --- /dev/null +++ b/src/js/utils/easing.js @@ -0,0 +1,19 @@ +/** + * Swing easing function. + * @param {number} p The percentage of time that has passed. + * @return {number} + */ +function swing (p) { + return 0.5 - Math.cos(p * Math.PI) / 2; +} + +/** + * Linear easing function. + * @param {number} p The percentage of time that has passed. + * @return {number} + */ +function linear(p) { + return p; +} + +export default { swing, linear }; diff --git a/src/js/utils/keys.js b/src/js/utils/keys.js new file mode 100644 index 0000000..02c5a4a --- /dev/null +++ b/src/js/utils/keys.js @@ -0,0 +1,10 @@ +const Keys = { + ENTER: 13, + SPACE: 32, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40 +}; + +export default Keys; diff --git a/src/js/utils/mobile-detector.js b/src/js/utils/mobile-detector.js new file mode 100644 index 0000000..83d403f --- /dev/null +++ b/src/js/utils/mobile-detector.js @@ -0,0 +1,64 @@ +const UA = window.navigator.userAgent; + +export default class MobileDetector { + /** + * Whether the device is Android or not. + * @return {Boolean} + */ + static isAndroid() { + return !!UA.match(/Android/i); + } + + /** + * Whether the device is BlackBerry or not. + * @return {Boolean} + */ + static isBlackBerry() { + return !!UA.match(/BlackBerry/i); + } + + /** + * Whether the device is iOS or not. + * @return {Boolean} + */ + static isiOS() { + return !!UA.match(/iPhone/i); + } + + /** + * Whether the device is Opera or not. + * @return {Boolean} + */ + static isOpera() { + return !!UA.match(/Opera Mini/i); + } + + /** + * Whether the device is Windows or not. + * @return {Boolean} + */ + static isWindows() { + return !!UA.match(/IEMobile/i); + } + + /** + * Whether the device is Windows Phone or not. + * @return {Boolean} + */ + static isWindowsPhone() { + return !!UA.match(/Windows Phone/i); + } + + /** + * Whether the device is any mobile device or not. + * @return {Boolean} + */ + static isAny() { + return MobileDetector.isAndroid() || + MobileDetector.isBlackBerry() || + MobileDetector.isiOS() || + MobileDetector.isOpera() || + MobileDetector.isWindows() || + MobileDetector.isWindowsPhone(); + } +} diff --git a/src/js/utils/scroll-to.js b/src/js/utils/scroll-to.js new file mode 100644 index 0000000..edec499 --- /dev/null +++ b/src/js/utils/scroll-to.js @@ -0,0 +1,74 @@ +import Easings from './easing'; + +let SCROLLABLE_CONTAINER = getScrollableContainer(); + +/** + * Returns the correct DOM element to be used for scrolling the + * page, due to Firefox not scrolling on document.body. + * @return {Element} Scrollable Element. + */ +function getScrollableContainer() { + if (SCROLLABLE_CONTAINER) { + return SCROLLABLE_CONTAINER; + } + + const documentElement = window.document.documentElement; + let scrollableContainer; + + documentElement.scrollTop = 1; + + if (documentElement.scrollTop === 1) { + documentElement.scrollTop = 0; + scrollableContainer = documentElement; + } else { + scrollableContainer = document.body; + } + + SCROLLABLE_CONTAINER = scrollableContainer; + + return scrollableContainer; +} + +/** + * Smoothly scrolls to a given Y position using Easing.Swing. It'll run a + * callback upon finishing. + * @param {number} y Offset of the page to scroll to. + * @param {number} duration Duration of the animation. 500ms by default. + * @param {function} cb Callback function to call upon completion. + */ +function scrollTo(y, duration = 500, cb = () => {}) { + const scrollableContainer = getScrollableContainer(); + const delta = y - scrollableContainer.scrollTop; + const startLocation = scrollableContainer.scrollTop; + const increment = 16; + + if (!duration) { + scrollableContainer.scrollTop = y; + cb(); + return; + } + + const animateScroll = elapsedTime => { + elapsedTime += increment; + const percent = Math.min(1, elapsedTime / duration); + const easingP = Easings.swing( + percent, + elapsedTime * percent, + y, + delta, + duration); + + scrollableContainer.scrollTop = Math.floor(startLocation + + (easingP * delta)); + + if (elapsedTime < duration) { + setTimeout(() => animateScroll(elapsedTime), increment); + } else { + cb(); + } + }; + + animateScroll(0); +} + +export default { getScrollableContainer, scrollTo }; diff --git a/static/css/base.css b/static/css/base.css index 8be370e..ed32ea4 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -1,9 +1,9 @@ /*--------------------------------------------------------------------------------- App: WebSlides - Version: 0.1.1 + Version: 1.0.0 Date: 2017-02-11 - Description: A simple and versatile framework for building HTML presentations, landings, and portfolios. + Description: A simple and versatile framework for building HTML presentations, landings, and portfolios. Author: José Luis Antúnez Author URI: http://twitter.com/jlantunez License: The MIT License (MIT) @@ -23,11 +23,11 @@ 2.3 San Francisco Font (Apple) 3. Header & Footer 3.1 Logo - 4. Navigation + 4. Navigation 4.1 Navbars 5. SLIDES (vertically and horizontally centered) - 5.1 Mini container & Alignment - 5.2 Counter / Navigation Slides + 5.1 Mini container & Alignment + 5.2 Counter / Navigation Slides 5.3 Background Images/Video 6. Magic blocks = .flexblock (Flexible blocks with auto-fill and equal height). 6.1 .flexblock.features @@ -56,11 +56,11 @@ 0. CSS Reset & Normalize =========================================== */ -html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, bbbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { border: 0; font-size: 100%; font: inherit; vertical-align: baseline; margin: 0; padding: 0 } +html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, bbbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { border: 0; font-size: 100%; font: inherit; vertical-align: baseline; margin: 0; padding: 0 } -article, aside, details, figcaption, figure, footer, header, main, menu, nav, section, summary { -display: block; -} +article, aside, details, figcaption, figure, footer, header, main, menu, nav, section, summary { + display: block; +} body { line-height: 1; } @@ -215,7 +215,7 @@ ins { text-decoration: none; padding: 0 4px; text-shadow: none; - + } ::-moz-selection { @@ -291,7 +291,7 @@ dd { /*=== Clearing === */ -.clear:before, .clear:after, header:before, header:after, main:before, main:after, .wrap:before, .wrap:after, group:before, group:after, section:before, section:after, aside:before, aside:after,footer:before, footer:after{ content: ""; display: table; } +.clear:before, .clear:after, header:before, header:after, main:before, main:after, .wrap:before, .wrap:after, group:before, group:after, section:before, section:after, aside:before, aside:after,footer:before, footer:after{ content: ""; display: table; } .clear:after, header:after, main:after, .wrap:after, group:after, section:after, aside:after, footer:after { clear: both; } /*========================================= @@ -302,7 +302,7 @@ body { overflow-x: hidden; } -/* == Prototype faster - Vertical rhythm == */ +/* == Prototype faster - Vertical rhythm == */ body.baseline { background: url(../images/baseline.png) left top .8rem/.8rem; @@ -343,7 +343,7 @@ nav a[rel="external"] em, /*Layer/Box Shadow*/ .shadow { -position: relative; + position: relative; } .shadow:before,.shadow:after { z-index: -1; @@ -472,8 +472,8 @@ pre code { padding: 0; } -/*=== 1.2 Animations ================ -Just 3 basic animations: +/*=== 1.2 Animations ================ +Just 3 basic animations: .fadeIn, .fadeInUp, .zoomIn. https://github.com/daneden/animate.css*/ @@ -674,9 +674,9 @@ li .browser {margin-bottom: 0; content: "● ● ●"; } @media (min-width:768px) { -.browser:before { - font-size: 1.6rem; - } + .browser:before { + font-size: 1.6rem; + } } @@ -718,7 +718,7 @@ Auto-fill & Equal height === */ -webkit-flex-direction: column; flex-direction: column; -webkit-transition: .3s; - transition: .3s; + transition: .3s; padding: 2.4rem; } @@ -732,21 +732,21 @@ Auto-fill & Equal height === */ } /* Grid (Sidebar + Main) .grid.sm */ .grid.sm .column:nth-child(1) { - width: 30% + width: 30% } .grid.sm .column:nth-child(2) { - width: 70%; + width: 70%; } /* Grid (Main + Sidebar) .grid.ms */ .grid.ms .column:nth-child(1) { - width: 70% + width: 70% } .grid.ms .column:nth-child(2) { - width: 30%; + width: 30%; } - /* Grid (Sidebar + Main + Sidebar) .grid.sms */ + /* Grid (Sidebar + Main + Sidebar) .grid.sms */ .grid.sms .column:nth-child(2) { - width: 50%; + width: 50%; } } @@ -807,7 +807,7 @@ ul.description { } .description + p{ -margin-top: 3.2rem; + margin-top: 3.2rem; } .description li { @@ -818,7 +818,7 @@ margin-top: 3.2rem; transition: .3s; } .description li:hover{ -padding-left: .4rem; + padding-left: .4rem; } ul.description li,.column ul li {list-style: none;margin-left: 0;} @@ -830,7 +830,7 @@ h2 svg, h3 svg, h4 svg { margin-top: -.8rem; } .text-intro svg,.wall p svg,.try svg { -margin-top: -.4rem; + margin-top: -.4rem; } .flexblock li h2 svg,.flexblock li h3 svg {margin-top: 0; } @@ -908,10 +908,10 @@ h1+img,h2+img,h3+img { [class*="content-"] > [class*="content-"] h2, [class*="content-"] > [class*="content-"] h3, [class*="content-"] > [class*="content-"] h4 { -font-size: 2.4rem; -line-height: 4rem; + font-size: 2.4rem; + line-height: 4rem; } -/*========================================= +/*========================================= 2.1. Headings with background =========================================== */ @@ -922,11 +922,11 @@ li[class*="bg-"],p[class*="bg-"] { } h1 [class*="bg-"],h2 [class*="bg-"],h3 [class*="bg-"] { -padding: .4rem .8rem; + padding: .4rem .8rem; } -/*========================================= -2.2. Typography Classes = .text- +/*========================================= +2.2. Typography Classes = .text- =========================================== */ .text-intro,[class*="content-"] p { @@ -948,9 +948,9 @@ padding: .4rem .8rem; @media (min-width: 768px) { .text-landing { letter-spacing: 1.6rem; - } + } } -/* -- Subtitle (Before h1, h2) -- +/* -- Subtitle (Before h1, h2) -- p.subtitle + h1/h2 */ p.text-subtitle { @@ -996,7 +996,7 @@ p.text-subtitle svg {vertical-align: text-top;} /* -- Magazine Two Columns -- */ @media (min-width: 768px) { -.text-cols { + .text-cols { -webkit-column-count: 2; -moz-column-count: 2; column-count: 2; @@ -1004,10 +1004,10 @@ p.text-subtitle svg {vertical-align: text-top;} -moz-column-gap: 4.8rem; column-gap: 4.8rem; text-align: left; -} -.text-landing + .text-cols{ -margin-top: 3.2rem; - } + } + .text-landing + .text-cols{ + margin-top: 3.2rem; + } } .text-cols p:first-child:first-letter { font-size: 11rem; @@ -1037,7 +1037,7 @@ margin-top: 3.2rem; margin-bottom: .6rem; } .column .text-context:before { -width:100%; + width:100%; } /* -- Separator/Symbols (stars ***...) -- */ @@ -1047,63 +1047,63 @@ width:100%; /* -- Separator -- */ .text-separator { - margin-top:2.4rem; + margin-top:2.4rem; } .text-separator:before { - position: absolute; - width: 16%; - height: .4rem; - content: ""; - margin-top:-1.6rem; - left: 0; + position: absolute; + width: 16%; + height: .4rem; + content: ""; + margin-top:-1.6rem; + left: 0; } @media (min-width: 568px) { -.text-separator { + .text-separator { width: 80%; margin-top: 0; margin-left: 20%; -} -.text-separator:before { + } + .text-separator:before { margin-top: 1.2rem; - } + } } /* -- Pull Quote (Right/Left) -- */ [class*="text-pull"] { -position: relative; -font-size: 2.4rem; -line-height: 4rem; -font-weight: 400; -margin-right: 2.4rem; -margin-left: 2.4rem; + position: relative; + font-size: 2.4rem; + line-height: 4rem; + font-weight: 400; + margin-right: 2.4rem; + margin-left: 2.4rem; } [class*="text-pull-"] { -padding-top: 1.4rem; -margin-top: .8rem; + padding-top: 1.4rem; + margin-top: .8rem; } -@media (min-width: 1024px) { -[class*="text-pull"] { -margin-right: -4rem; -margin-left: -4rem; - } +@media (min-width: 1024px) { + [class*="text-pull"] { + margin-right: -4rem; + margin-left: -4rem; + } } -@media (min-width: 568px) { -[class*="text-pull-"] { -width: 32rem; -} -.text-pull-right { -float: right; -margin-right: -2.4rem; -margin-left: 2.4rem; -} -.text-pull-left { -float: left; -margin-left: -2.4rem; -margin-right: 2.4rem; - } +@media (min-width: 568px) { + [class*="text-pull-"] { + width: 32rem; + } + .text-pull-right { + float: right; + margin-right: -2.4rem; + margin-left: 2.4rem; + } + .text-pull-left { + float: left; + margin-left: -2.4rem; + margin-right: 2.4rem; + } } /* -- Info Messages (error, warning, success... -- */ @@ -1111,8 +1111,8 @@ margin-right: 2.4rem; } -/*========================================= -2.1. San Francisco Font (Apple's new font) +/*========================================= +2.1. San Francisco Font (Apple's new font) =========================================== */ .text-apple,.bg-apple { @@ -1169,8 +1169,8 @@ footer, header p, footer p { -line-height: 4.8rem; -margin-bottom: 0; + line-height: 4.8rem; + margin-bottom: 0; } header[role=banner] img, @@ -1197,7 +1197,7 @@ section footer { /*desktop only? Add @media (min-width: 1025px)*/ header[role=banner] { - opacity: 0; + opacity: 0; } /*=== Show Header[role=banner] === */ header[role=banner]:hover { @@ -1205,10 +1205,10 @@ header[role=banner]:hover { } @media (max-width: 767px) { -footer .alignleft, footer .alignright { -float: none; -display: block; - } + footer .alignleft, footer .alignright { + float: none; + display: block; + } } @@ -1250,7 +1250,7 @@ nav ul { display: flex; -webkit-flex-wrap: wrap; flex-wrap: wrap; - /*====align left====*/ + /*====align left====*/ justify-content: flex-start; /* ==== align center ====*/ /*justify-content: center; */ @@ -1297,37 +1297,38 @@ nav.aligncenter ul, .aligncenter nav ul { } nav.navbar ul li { -/*====full float li a ====*/ --webkit-flex: 1 1 auto; -flex: 1 1 auto; + /*====full float li a ====*/ + -webkit-flex: 1 1 auto; + flex: 1 1 auto; } @media (max-width: 568px) { -nav.navbar ul { + nav.navbar ul { -webkit-flex-flow: column wrap; flex-flow: column wrap; padding: 0; -} -nav.navbar li a{justify-content:flex-start; - } + } + nav.navbar li a { + justify-content:flex-start; + } } /*============================================ 5. SLIDES (Full Screen) -Vertically and horizontally centered +Vertically and horizontally centered ============================================== */ -/* Fade transition to all slides. +/* Fade transition to all slides. * = All HTML elements will have those styles.*/ -section * { --webkit-animation: fadeIn 0.3s ease-in-out; -animation: fadeIn 0.3s ease-in-out; +section * { + -webkit-animation: fadeIn 0.3s ease-in-out; + animation: fadeIn 0.3s ease-in-out; } section .background,section .background-video, [class*="background-"].light,[class*="background-"].dark { --webkit-animation-duration:0s; -animation-duration:0s; + -webkit-animation-duration:0s; + animation-duration:0s; } /*=== Section = Slide === */ @@ -1349,17 +1350,17 @@ section,.slide } @media (min-width: 1024px) { -section, .slide { + section, .slide { padding-top: 12rem; padding-bottom: 12rem; - } + } } /*slide no padding (full card, .embed> youtube video...) */ .fullscreen { -padding: 0; -/* Fixed/Visible header? -padding:8.2rem 0 0 0; -*/ + padding: 0; + /* Fixed/Visible header? + padding:8.2rem 0 0 0; + */ } /* slide alignment - top */ @@ -1374,7 +1375,7 @@ padding:8.2rem 0 0 0; } -/*== 5.1. Mini container width:50% (600px) +/*== 5.1. Mini container width:50% (600px) .wrap:1200px; / Aligned items [class*="content-"]=== */ [class*="content-"] { @@ -1385,12 +1386,12 @@ padding:8.2rem 0 0 0; .wrap[class*="bg-"],.wrap.frame, [class*="content-"][class*="bg-"], [class*="content-"].frame, [class*="align"][class*="bg-"]{ - padding: 4.8rem; + padding: 4.8rem; } [class*="content-"] > [class*="content-"] p { - font-size: 1.8rem; - line-height: 3.2rem; + font-size: 1.8rem; + line-height: 3.2rem; } .content-center { @@ -1399,7 +1400,7 @@ padding:8.2rem 0 0 0; } @media (min-width: 768px) { -[class*="content-"] { + [class*="content-"] { width: 50%; } .content-left { @@ -1408,45 +1409,45 @@ padding:8.2rem 0 0 0; .content-right { float: right; } -[class*="content-"] + [class*="content-"] { - padding-left:2.4rem; + [class*="content-"] + [class*="content-"] { + padding-left:2.4rem; margin-bottom: 4.8rem; -} -[class*="content-"] + [class*="size-"] { -margin-top: 6.4rem; -clear:both; -} + } + [class*="content-"] + [class*="size-"] { + margin-top: 6.4rem; + clear:both; + } -[class*="content-"]:before, -[class*="content-"]:after { + [class*="content-"]:before, + [class*="content-"]:after { content: ""; display: table; -} + } -[class*="content-"]:after { + [class*="content-"]:after { clear: both; - } -} + } +} /* === 5.2 Counter / Navigation Slides === */ #navigation { -position: fixed; -width: 24.4rem; -margin-right: auto; -margin-left: auto; -right: 0; -bottom: 0; -left: 0; -/* hover/visibility */ -z-index: 3; + position: fixed; + width: 24.4rem; + margin-right: auto; + margin-left: auto; + right: 0; + bottom: 0; + left: 0; + /* hover/visibility */ + z-index: 3; } #navigation { --webkit-animation: fadeIn 16s; -animation: fadeIn 16s; -opacity:0; + -webkit-animation: fadeIn 16s; + animation: fadeIn 16s; + opacity:0; } #navigation:hover { -opacity: 1; + opacity: 1; } /* -- navigation arrow always visible? -- */ @@ -1489,14 +1490,14 @@ a#previous { left: 3.2rem; } @media (max-width:1024px) { -#navigation { -background: url('../images/swipe.svg') no-repeat center top; -background-size: 4.8rem; --webkit-animation: fadeIn 6s; -animation: fadeIn 6s; -} -#navigation a, #counter {display: none; - } + #navigation { + background: url('../images/swipe.svg') no-repeat center top; + background-size: 4.8rem; + -webkit-animation: fadeIn 6s; + animation: fadeIn 6s; + } + #navigation a, #counter {display: none; + } } /*=== 5.3 Slides - Background Images === */ @@ -1516,15 +1517,15 @@ animation: fadeIn 6s; background-size: cover } .background-top { -background-position: top; -background-size: cover; + background-position: top; + background-size: cover; } .background-bottom { -background-position: bottom; -background-size: cover; + background-position: bottom; + background-size: cover; } -/*fullscreen video +/*fullscreen video