├── .npmignore
├── .travis.yml
├── static
├── images
│ ├── avatar.jpg
│ ├── iphone.png
│ ├── setup.png
│ ├── android.png
│ ├── davinci.png
│ ├── bt-appstore.png
│ ├── iphone-hand.png
│ ├── bt-playstore.png
│ ├── favicons
│ │ ├── favicon.png
│ │ ├── favicon-120.png
│ │ ├── favicon-152.png
│ │ ├── favicon-180.png
│ │ ├── favicon-192.png
│ │ ├── favicon-32.png
│ │ └── favicon-76.png
│ ├── share-webslides.jpg
│ ├── logos
│ │ ├── logo.svg
│ │ ├── netflix.svg
│ │ ├── facebook.svg
│ │ ├── microsoft.svg
│ │ ├── apple.svg
│ │ ├── instagram.svg
│ │ ├── nyt.svg
│ │ └── airbnb.svg
│ └── swipe.svg
└── js
│ └── svg-icons.js
├── src
├── scss
│ ├── utils
│ │ ├── _mixins.scss
│ │ ├── _clear.scss
│ │ ├── _bugs.scss
│ │ ├── _animations.scss
│ │ └── _reset.scss
│ ├── modules
│ │ ├── _button.scss
│ │ ├── _logo.scss
│ │ ├── _avatars.scss
│ │ ├── _tables.scss
│ │ ├── _flexblock-metrics.scss
│ │ ├── _flexblock-steps.scss
│ │ ├── _browser.scss
│ │ ├── _flexblock-clients.scss
│ │ ├── _badges.scss
│ │ ├── _flexblock-specs.scss
│ │ ├── _flexblock-reasons.scss
│ │ ├── _flexblock-activity.scss
│ │ ├── _print.scss
│ │ ├── _grid.scss
│ │ ├── _header-footer.scss
│ │ ├── _slides-navigation.scss
│ │ ├── _media.scss
│ │ ├── _longform.scss
│ │ ├── _quotes.scss
│ │ ├── _flexblock-features.scss
│ │ ├── _toc.scss
│ │ ├── _navigation.scss
│ │ ├── _flexblock-plans.scss
│ │ ├── _promos.scss
│ │ ├── _flexblock.scss
│ │ ├── _work.scss
│ │ ├── _slides.scss
│ │ ├── _slides-bg.scss
│ │ ├── _flexblock-gallery.scss
│ │ ├── _form.scss
│ │ ├── _cards.scss
│ │ └── _zoom.scss
│ ├── _vars.scss
│ ├── full.scss
│ └── _base.scss
└── js
│ ├── full.js
│ ├── utils
│ ├── easing.js
│ ├── keys.js
│ ├── custom-event.js
│ ├── scroll-to.js
│ ├── mobile-detector.js
│ └── dom.js
│ ├── plugins
│ ├── plugins.js
│ ├── click-nav.js
│ ├── grid.js
│ ├── video.js
│ ├── autoslide.js
│ ├── hash.js
│ ├── keyboard.js
│ ├── scroll.js
│ ├── navigation.js
│ ├── zoom.js
│ ├── touch.js
│ └── youtube.js
│ └── modules
│ └── slide.js
├── postcss.config.js
├── .babelrc
├── .gitignore
├── .editorconfig
├── test
├── utils
│ ├── keys.test.js
│ ├── scroll-to.test.js
│ └── hash.test.js
├── plugins
│ ├── click-nav.test.js
│ ├── autoslide.test.js
│ ├── video.test.js
│ ├── scroll.test.js
│ ├── navigation.test.js
│ ├── zoom.test.js
│ ├── touch.test.js
│ ├── youtube.test.js
│ └── keyboard.test.js
└── modules
│ └── slide.test.js
├── zip-release.js
├── LICENSE
├── webpack.config.js
├── .sass-lint.yml
├── package.json
├── error.html
├── .eslintrc
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | .babelrc
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10"
4 |
--------------------------------------------------------------------------------
/static/images/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/avatar.jpg
--------------------------------------------------------------------------------
/static/images/iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/iphone.png
--------------------------------------------------------------------------------
/static/images/setup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/setup.png
--------------------------------------------------------------------------------
/src/scss/utils/_mixins.scss:
--------------------------------------------------------------------------------
1 | @mixin square($width) {
2 | height: $width;
3 | width: $width;
4 | }
5 |
--------------------------------------------------------------------------------
/static/images/android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/android.png
--------------------------------------------------------------------------------
/static/images/davinci.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/davinci.png
--------------------------------------------------------------------------------
/static/images/bt-appstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/bt-appstore.png
--------------------------------------------------------------------------------
/static/images/iphone-hand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/iphone-hand.png
--------------------------------------------------------------------------------
/static/images/bt-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/bt-playstore.png
--------------------------------------------------------------------------------
/static/images/favicons/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/favicons/favicon.png
--------------------------------------------------------------------------------
/static/images/share-webslides.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/share-webslides.jpg
--------------------------------------------------------------------------------
/static/images/favicons/favicon-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/favicons/favicon-120.png
--------------------------------------------------------------------------------
/static/images/favicons/favicon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/favicons/favicon-152.png
--------------------------------------------------------------------------------
/static/images/favicons/favicon-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/favicons/favicon-180.png
--------------------------------------------------------------------------------
/static/images/favicons/favicon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/favicons/favicon-192.png
--------------------------------------------------------------------------------
/static/images/favicons/favicon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/favicons/favicon-32.png
--------------------------------------------------------------------------------
/static/images/favicons/favicon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webslides/WebSlides/HEAD/static/images/favicons/favicon-76.png
--------------------------------------------------------------------------------
/src/js/full.js:
--------------------------------------------------------------------------------
1 | import WebSlides from './modules/webslides';
2 | require('../scss/full.scss');
3 |
4 | window.WebSlides = WebSlides;
5 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer')({
4 | browsers: ['last 2 versions'],
5 | })
6 | ]
7 | };
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", {"modules": false}]
4 | ],
5 |
6 | "env": {
7 | "test": {
8 | "plugins": ["transform-es2015-modules-commonjs"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/js/utils/easing.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Swing easing function.
3 | * @param {number} p The percentage of time that has passed.
4 | * @return {number}
5 | */
6 | function swing(p) {
7 | return 0.5 - Math.cos(p * Math.PI) / 2;
8 | }
9 |
10 | export default {swing};
11 |
--------------------------------------------------------------------------------
/src/scss/modules/_button.scss:
--------------------------------------------------------------------------------
1 | /* Buttons/Badges */
2 | [class*='button'] {
3 | @media (min-width: 500px) {
4 | & + & {
5 | margin-left: 1.8rem;
6 | }
7 | }
8 |
9 | @media (max-width: 499px) {
10 | & + & {
11 | margin-top: .8rem;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/scss/utils/_clear.scss:
--------------------------------------------------------------------------------
1 | /*=== Clearing === */
2 |
3 | header,
4 | main,
5 | section,
6 | aside,
7 | footer,
8 | .clear,
9 | .wrap {
10 | &:before,
11 | &:after {
12 | content: '';
13 | display: table;
14 | }
15 |
16 | &:after {
17 | clear: both;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE files #
2 | #############
3 | .idea/
4 |
5 | # Third Party #
6 | ###############
7 | node_modules/
8 | coverage
9 | .eslintcache
10 |
11 | # OS generated files #
12 | ######################
13 | .DS_Store
14 | .DS_Store?
15 | ._*
16 | .Spotlight-V100
17 | .Trashes
18 | ehthumbs.db
19 | Thumbs.db
20 | webslides.zip
21 |
--------------------------------------------------------------------------------
/src/js/utils/keys.js:
--------------------------------------------------------------------------------
1 | const Keys = {
2 | ENTER: 13,
3 | SPACE: 32,
4 | RE_PAGE: 33,
5 | AV_PAGE: 34,
6 | END: 35,
7 | HOME: 36,
8 | LEFT: 37,
9 | UP: 38,
10 | RIGHT: 39,
11 | DOWN: 40,
12 | PLUS: [107, 171, 187],
13 | MINUS: [109, 173, 189],
14 | ESCAPE: 27,
15 | F: 70
16 | };
17 |
18 | export default Keys;
19 |
--------------------------------------------------------------------------------
/src/scss/utils/_bugs.scss:
--------------------------------------------------------------------------------
1 | /*=========================================
2 | 16. SAFARI BUGS (flex-wrap)
3 | Solution: stackoverflow.com/questions/34250282/flexbox-safari-bug-flex-wrap
4 | =========================================== */
5 |
6 | .flexblock:before,
7 | .flexblock:after,
8 | .grid:before,
9 | .grid:after,
10 | .cta:before,
11 | .cta:after {
12 | width: 0;
13 | }
14 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 | max_line_length = 233
10 | end_of_line = lf
11 |
12 | [*.json]
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.yml]
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/src/scss/modules/_logo.scss:
--------------------------------------------------------------------------------
1 | /*=== 3.1. Logo === */
2 |
3 | .logo {
4 | text-transform: lowercase;
5 |
6 | a {
7 | background: url('../images/logos/logo.svg') no-repeat 0 0;
8 | background-size: 4.8rem;
9 | float: left;
10 | height: 4.8rem;
11 | text-indent: -4000px;
12 | /*If you remove text-indent and add: */
13 | /*padding-left: 6rem;*/
14 | vertical-align: middle;
15 | width: 4.8rem;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/test/utils/keys.test.js:
--------------------------------------------------------------------------------
1 | import Keys from '../../src/js/utils/keys';
2 |
3 | test('Keys are present', () => {
4 | expect(Keys.ENTER).toBe(13);
5 | expect(Keys.SPACE).toBe(32);
6 | expect(Keys.RE_PAGE).toBe(33);
7 | expect(Keys.AV_PAGE).toBe(34);
8 | expect(Keys.END).toBe(35);
9 | expect(Keys.HOME).toBe(36);
10 | expect(Keys.LEFT).toBe(37);
11 | expect(Keys.UP).toBe(38);
12 | expect(Keys.RIGHT).toBe(39);
13 | expect(Keys.DOWN).toBe(40);
14 | });
15 |
--------------------------------------------------------------------------------
/src/scss/modules/_avatars.scss:
--------------------------------------------------------------------------------
1 | /*=========================================
2 | 12. Avatars - uifaces.com
3 | =========================================== */
4 |
5 | cite img,
6 | img[class*='avatar-'] {
7 | display: inline-block;
8 | margin-right: 6px;
9 | vertical-align: middle;
10 | }
11 |
12 | img[class*='avatar-'] {
13 | border-radius: 50%;
14 | }
15 |
16 | $avatar-sizes: 40, 48, 56, 64, 72, 80;
17 |
18 | @each $size in $avatar-sizes {
19 | img.avatar-#{$size} {
20 | @include square(#{$size}px);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/zip-release.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | const fs = require('fs');
3 | const archiver = require('archiver');
4 | const pck = require('./package.json');
5 |
6 | const output = fs.createWriteStream(`${__dirname}/${pck.name}.zip`);
7 | const archive = archiver('zip', { zlib: { level: 9 } });
8 |
9 | output.on('close', () => {
10 | console.log(`${archive.pointer()} total bytes`);
11 | });
12 |
13 | archive.on('error', err => {
14 | throw err;
15 | });
16 |
17 | archive.pipe(output);
18 |
19 | pck.release.files.forEach(f => archive.glob(f));
20 |
21 | archive.finalize();
22 |
--------------------------------------------------------------------------------
/src/js/plugins/plugins.js:
--------------------------------------------------------------------------------
1 | import AutoSlide from './autoslide';
2 | import ClickNav from './click-nav';
3 | import Grid from './grid';
4 | import Hash from './hash';
5 | import Keyboard from './keyboard';
6 | import Navigation from './navigation';
7 | import Scroll from './scroll';
8 | import Touch from './touch';
9 | import Video from './video';
10 | import YouTube from './youtube';
11 | import Zoom from './zoom';
12 |
13 | export default {
14 | AutoSlide,
15 | ClickNav,
16 | Grid,
17 | Hash,
18 | Keyboard,
19 | Navigation,
20 | Scroll,
21 | Touch,
22 | Video,
23 | YouTube,
24 | Zoom
25 | };
26 |
--------------------------------------------------------------------------------
/src/scss/modules/_tables.scss:
--------------------------------------------------------------------------------
1 | /*=========================================
2 | 13. Tables
3 | =========================================== */
4 |
5 | table {
6 | margin-bottom: 3.2rem;
7 | margin-top: 3.2rem;
8 | }
9 |
10 | td,
11 | th,
12 | thead {
13 | border-spacing: 0;
14 | padding: .7rem 2.4rem;
15 | }
16 |
17 | thead th,
18 | th {
19 | cursor: default;
20 | font-weight: 600;
21 | text-align: left;
22 | text-transform: uppercase;
23 | white-space: nowrap;
24 | }
25 |
26 | thead,
27 | td.goals {
28 | font-weight: 600;
29 | text-shadow: none;
30 | }
31 |
32 | tr > td {
33 | font-weight: 400;
34 | }
35 |
--------------------------------------------------------------------------------
/src/scss/modules/_flexblock-metrics.scss:
--------------------------------------------------------------------------------
1 | /*=================================================
2 | 6.4 Block Numbers -
3 | =================================================== */
4 |
5 | .metrics li {
6 | text-align: center;
7 | width: 100%;
8 |
9 | @media (min-width: 568px) {
10 | width: 50%;
11 | }
12 |
13 | @media (min-width: 1024px) {
14 | width: 25%;
15 | }
16 | }
17 |
18 | .metrics li strong {
19 | display: block;
20 | }
21 |
22 | .metrics li span,
23 | .metrics li svg {
24 | display: block;
25 | font-size: 6.4rem;
26 | line-height: 7.2rem;
27 | margin: 0 auto;
28 | }
29 |
30 | .card-50 .metrics li {
31 | width: 50%;
32 | }
33 |
--------------------------------------------------------------------------------
/test/plugins/click-nav.test.js:
--------------------------------------------------------------------------------
1 | import DOM from '../../src/js/utils/dom';
2 | import ClickNav from '../../src/js/plugins/click-nav';
3 |
4 | beforeAll(() => {
5 | document.body.innerHTML =
6 | ``;
7 | });
8 |
9 | test('Click nav plugin', () => {
10 | const next = jest.fn();
11 | const ws = document.getElementById('webslides');
12 |
13 | const webslides = {
14 | options: {
15 | changeOnClick: true
16 | },
17 | goNext: next,
18 | el: ws
19 | };
20 |
21 | expect(next).not.toBeCalled();
22 |
23 | new ClickNav(webslides);
24 | DOM.fireEvent(ws, 'click');
25 | expect(next.mock.calls.length).toBe(1);
26 | });
27 |
--------------------------------------------------------------------------------
/src/scss/modules/_flexblock-steps.scss:
--------------------------------------------------------------------------------
1 | /*==================================================
2 | 6.3 flexblock.steps
3 | About, Philosophy...
4 | =================================================== */
5 |
6 | .steps li {
7 | width: 100%;
8 |
9 | img,
10 | span {
11 | display: block;
12 | margin: 0 auto .8rem;
13 | }
14 |
15 | span {
16 | font-size: 6.4rem;
17 | }
18 |
19 | @media (min-width: 768px) {
20 | width: 50%;
21 | }
22 | }
23 |
24 | @media (min-width: 1024px) {
25 | .steps li {
26 | width: 25%;
27 | }
28 |
29 | .process {
30 | border-left-style: solid;
31 | border-left-width: 15px;
32 | height: 0;
33 | left: 0;
34 | position: absolute;
35 | top: 60px;
36 | width: 0;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/scss/modules/_browser.scss:
--------------------------------------------------------------------------------
1 | /*=== HTML Browser (Screenshots) ================ */
2 | /* img */
3 |
4 | .browser {
5 | border-radius: .3rem;
6 | margin: 0 auto 3.2rem;
7 | max-width: 1024px;
8 | overflow: hidden;
9 |
10 | li & {
11 | margin-bottom: 0;
12 | }
13 |
14 | h1 + &,
15 | h2 + &,
16 | p + & {
17 | margin-top: 4.8rem;
18 | }
19 |
20 | figcaption {
21 | padding: 2.4rem;
22 | }
23 |
24 | &:before {
25 | content: '● ● ●';
26 | font-size: .8rem;
27 | left: 0;
28 | line-height: 0;
29 | padding: 1.6rem;
30 | position: absolute;
31 | text-align: left;
32 | top: 0;
33 | width: 100%;
34 |
35 | @media (min-width: 768px) {
36 | font-size: 1.6rem;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/scss/modules/_flexblock-clients.scss:
--------------------------------------------------------------------------------
1 | /*=====================================================================
2 | 6.2 Clients Logos
3 | ======================================================================= */
4 |
5 | .flexblock.clients.blink li > a,
6 | .flexblock.clients li {
7 | padding: 0;
8 | }
9 |
10 | .flexblock.clients li figcaption {
11 | padding: 0 2.4rem 2.4rem;
12 | }
13 |
14 | .flexblock.clients.border li figcaption {
15 | padding-top: 2.4rem;
16 | }
17 |
18 | .clients.blink li>a,
19 | .clients li {
20 | justify-content: inherit;
21 | }
22 |
23 | .clients li img,
24 | .clients li svg {
25 | display: block;
26 | padding: 2.4rem;
27 | }
28 |
29 | .clients.border li img,
30 | .clients.border li svg {
31 | display: block;
32 | margin-left: auto;
33 | margin-right: auto;
34 | }
35 |
36 | .clients li:hover {
37 | z-index: 1;
38 | }
39 |
--------------------------------------------------------------------------------
/src/scss/modules/_badges.scss:
--------------------------------------------------------------------------------
1 | /*=== App Store Badges === */
2 | /* Change width and height: 216x64px, 162x48px, 135x40... */
3 |
4 | [class*='badge-'] {
5 | background-repeat: no-repeat;
6 | background-size: cover;
7 | border-radius: .6rem;
8 | display: inline-block;
9 | height: 40px;
10 | line-height: 4rem;
11 | text-indent: -4000px;
12 | width: 135px;
13 |
14 | &:hover {
15 | opacity: .7;
16 | }
17 |
18 | @media (min-width: 1024px) {
19 | height: 48px;
20 | line-height: 4.8rem;
21 | width: 162px;
22 | }
23 |
24 | @media (min-width: 500px) {
25 | & + & {
26 | margin-left: 1.8rem;
27 | }
28 | }
29 |
30 | @media (max-width: 499px) {
31 | & + & {
32 | margin-top: .8rem;
33 | }
34 | }
35 | }
36 |
37 | .badge-ios {
38 | background-image: url('../images/bt-appstore.png');
39 | }
40 |
41 | .badge-android {
42 | background-image: url('../images/bt-playstore.png');
43 | }
44 |
--------------------------------------------------------------------------------
/src/scss/modules/_flexblock-specs.scss:
--------------------------------------------------------------------------------
1 | /*=====================================================
2 | 6.5 Specs/Items:
3 | ======================================================= */
4 |
5 | .specs li {
6 | text-align: left;
7 | width: 100%;
8 |
9 | &:after {
10 | bottom: -2.4rem;
11 | content: '';
12 | display: block;
13 | height: 1px;
14 | position: relative;
15 | }
16 |
17 | &:hover {
18 | transform: translateX(.2rem);
19 | }
20 |
21 | span,
22 | svg {
23 | display: block;
24 | font-size: 6.4rem;
25 | line-height: 1;
26 | margin: 0;
27 | }
28 |
29 | img {
30 | width: 6.4rem;
31 | }
32 |
33 | span {
34 | font-weight: 300;
35 |
36 | sup {
37 | font-size: 3rem;
38 | }
39 | }
40 |
41 | @media (min-width: 1024px) {
42 | span,
43 | svg,
44 | img {
45 | float: left;
46 | margin-right: 2.4rem;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/scss/modules/_flexblock-reasons.scss:
--------------------------------------------------------------------------------
1 | /*=================================================
2 | 6.6 Reasons/Why/Numbers (counter-increment)
3 |
4 | =================================================== */
5 | .flexblock.reasons {
6 | li {
7 | counter-increment: list;
8 | text-align: left;
9 | width: 100%;
10 |
11 | &:hover {
12 | transform: translateY(-.2rem);
13 | }
14 |
15 | &:after {
16 | bottom: -2.4rem;
17 | content: '';
18 | display: block;
19 | height: 1px;
20 | position: relative;
21 | }
22 |
23 | &:before {
24 | content: counter(list)'.';
25 | font-size: 6.4rem;
26 | line-height: 1;
27 | }
28 |
29 | @media (min-width: 768px) {
30 | padding-left: 8.8rem;
31 | /* You need two digits? (1-10)*/
32 | /*padding-left: 12rem; */
33 |
34 | &:before {
35 | left: 2.4rem;
36 | position: absolute;
37 | }
38 | }
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/src/scss/modules/_flexblock-activity.scss:
--------------------------------------------------------------------------------
1 | /*===========================================
2 | 6.9 Block Activity
3 | CV / News
4 | ============================================= */
5 |
6 | .flexblock.activity {
7 | flex-direction: column;
8 |
9 | li {
10 | flex: 1;
11 | position: relative;
12 | width: auto;
13 | }
14 |
15 | p {
16 | margin-bottom: 0;
17 | vertical-align: top;
18 | }
19 |
20 | img {
21 | display: block;
22 | }
23 |
24 | .year,
25 | .title {
26 | display: inline;
27 | font-weight: 600;
28 | }
29 |
30 | .summary {
31 | width: 100%;
32 | }
33 |
34 | .title {
35 | margin-left: 1rem;
36 | }
37 |
38 |
39 | @media (min-width: 768px) {
40 | p {
41 | float: left;
42 | }
43 |
44 | .year {
45 | width: 15%;
46 | }
47 |
48 | .title {
49 | margin-left: 4%;
50 | margin-right: 4%;
51 | width: 27%;
52 | }
53 |
54 | .summary {
55 | width: 50%;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/scss/modules/_print.scss:
--------------------------------------------------------------------------------
1 | /*=========================================
2 | 17. PRINT
3 | =========================================== */
4 |
5 | // sass-lint:disable no-important
6 | @media print {
7 | @page {
8 | margin: .5cm;
9 | size: A4 landscape;
10 | }
11 |
12 | // Black prints faster
13 | * {
14 | background: transparent !important;
15 | color: $black !important;
16 | filter: none !important;
17 | text-shadow: none !important;
18 | }
19 |
20 | html,
21 | body,
22 | #webslides {
23 | height: auto !important;
24 | overflow: auto !important;
25 | width: auto !important;
26 | }
27 |
28 | #webslides {
29 | overflow-x: auto !important;
30 | overflow-y: auto !important;
31 | }
32 |
33 | section,
34 | .slide {
35 | display: flex !important;
36 | height: auto !important;
37 | }
38 |
39 | section * {
40 | animation: none;
41 | }
42 |
43 | table,
44 | figure {
45 | page-break-inside: avoid;
46 | }
47 |
48 | #counter,
49 | #navigation {
50 | display: none;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/test/plugins/autoslide.test.js:
--------------------------------------------------------------------------------
1 | import DOM from '../../src/js/utils/dom';
2 | import AutoSlide from '../../src/js/plugins/autoslide';
3 |
4 | jest.useFakeTimers();
5 |
6 | beforeAll(() => {
7 | document.body.innerHTML =
8 | ``;
9 | });
10 |
11 | test('AutoSlide plugin', () => {
12 | const next = jest.fn();
13 | const ws = document.getElementById('webslides');
14 | const webslides = {
15 | options: {
16 | autoslide: 100
17 | },
18 | goNext: next,
19 | el: ws
20 | };
21 |
22 | expect(next).not.toBeCalled();
23 |
24 | new AutoSlide(webslides);
25 | DOM.fireEvent(ws, 'ws:init');
26 |
27 | // Wait until next execution
28 | jest.runTimersToTime(101);
29 |
30 | expect(next.mock.calls.length).toBe(1);
31 |
32 | // Wait until next execution
33 | jest.runTimersToTime(101);
34 |
35 | expect(next.mock.calls.length).toBe(2);
36 |
37 | // Pause on focus
38 | document.getElementById('focusable').focus();
39 | DOM.fireEvent(document.body, 'focus');
40 | jest.runTimersToTime(101);
41 |
42 | expect(next.mock.calls.length).toBe(2);
43 | });
44 |
--------------------------------------------------------------------------------
/src/scss/modules/_grid.scss:
--------------------------------------------------------------------------------
1 | /*=== 1.4. Basic Grid (Flexible blocks)
2 | Auto-fill & Equal height === */
3 |
4 | .grid {
5 | clear: both;
6 | display: flex;
7 | flex-wrap: wrap;
8 | margin-left: auto;
9 | margin-right: auto;
10 |
11 | &:after {
12 | clear: both;
13 | }
14 |
15 | &:before {
16 | content: '';
17 | display: table;
18 | }
19 |
20 | & > .column {
21 | display: flex;
22 | flex: auto;
23 | flex-direction: column;
24 | padding: 2.4rem;
25 | position: relative;
26 | transition: .3s;
27 | width: 100%;
28 | }
29 |
30 | &.vertical-align .column {
31 | justify-content: center;
32 | }
33 |
34 | @media (min-width: 768px) {
35 | & > .column {
36 | width: 25%;
37 | }
38 |
39 | &.sm .column:nth-child(1) {
40 | width: 30%;
41 | }
42 |
43 | &.sm .column:nth-child(2) {
44 | width: 70%;
45 | }
46 |
47 | &.ms .column:nth-child(1) {
48 | width: 70%;
49 | }
50 |
51 | &.ms .column:nth-child(2) {
52 | width: 30%;
53 | }
54 |
55 | &.sms .column:nth-child(2) {
56 | width: 50%;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 José Luis Antúnez
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/js/plugins/click-nav.js:
--------------------------------------------------------------------------------
1 | const CLICKABLE_ELS = [
2 | 'INPUT',
3 | 'SELECT',
4 | 'OPTION',
5 | 'BUTTON',
6 | 'A',
7 | 'TEXTAREA'
8 | ];
9 |
10 | /**
11 | * ClickNav plugin that allows to click on the page to get to the next slide.
12 | */
13 | export default class ClickNav {
14 | /**
15 | * @param {WebSlides} wsInstance The WebSlides instance
16 | * @constructor
17 | */
18 | constructor(wsInstance) {
19 | /**
20 | * @type {WebSlides}
21 | * @private
22 | */
23 | this.ws_ = wsInstance;
24 |
25 | if (wsInstance.options.changeOnClick) {
26 | this.ws_.el.addEventListener('click', this.onClick_.bind(this));
27 | }
28 | }
29 |
30 | /**
31 | * Reacts to the click event. It will go to the next slide unless the element
32 | * has a data-prevent-nav attribute or is on the list of CLICKABLE_ELS.
33 | * @param {MouseEvent} event The click event.
34 | * @private
35 | */
36 | onClick_(event) {
37 | if (CLICKABLE_ELS.indexOf(event.target.tagName) < 0 &&
38 | typeof event.target.dataset.preventNav === 'undefined') {
39 | this.ws_.goNext();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/static/js/svg-icons.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Thanks fontastic.me (Font Awesome 4.4 as SVG Icons).
3 | * Do you want to use other icons?
4 | * Go to https://fontastic.me > Create account > Select icons > Customize > Publish (SVG Sprite) > Paste .js here
5 | */
6 |
7 | /*
8 | Important!
9 | /css/svg-icons.css is required.
10 | */
11 |
12 | (function(a,b,c,d){function e(b,c){if(c){var d=c.getAttribute('viewBox'),e=a.createDocumentFragment(),f=c.cloneNode(true);if(d)b.setAttribute('viewBox',d);while(f.childNodes.length)e.appendChild(f.childNodes[0]);b.appendChild(e);}}function f(){var b=this,c=a.createElement('x'),d=b.s;c.innerHTML=b.responseText;b.onload=function(){d.splice(0).map(function(a){e(a[0],c.querySelector('#'+a[1].replace(/(\W)/g,'\\$1')));});};b.onload();}function g(){var a;while((a=b[0])){var e=a.parentNode,h=a.getAttribute('xlink:href').split('#')[1],i='https://file.myfontastic.com/bLfXNBF36ByeujCbT5PohZ/sprites/1477146123.svg';e.removeChild(a);var j=d[i]=d[i]||new XMLHttpRequest();if(!j.s){j.s=[];j.open('GET',i);j.onload=f;j.send();}j.s.push([e,h]);if(j.readyState===4)j.onload();}c(g);}g();})(document,document.getElementsByTagName('use'),window.requestAnimationFrame||window.setTimeout,{});
--------------------------------------------------------------------------------
/src/scss/modules/_header-footer.scss:
--------------------------------------------------------------------------------
1 | /*=========================================
2 | 3. Header & Footer
3 | =========================================== */
4 |
5 | /* -- If you want an unique, global header/footer,read this:
6 | https://github.com/webslides/webslides/issues/57 -- */
7 |
8 | header,
9 | footer,
10 | #navigation {
11 | padding: 2.4rem;
12 | transition: all .4s ease-in-out;
13 | width: 100%;
14 | }
15 |
16 | header p,
17 | footer p {
18 | line-height: 4.8rem;
19 | margin-bottom: 0;
20 | }
21 |
22 | header[role='banner'] img,
23 | footer img {
24 | height: 4rem;
25 | vertical-align: middle;
26 | }
27 |
28 | footer {
29 | position: relative;
30 | }
31 |
32 | header,
33 | footer {
34 | z-index: 3;
35 | }
36 |
37 | header,
38 | .ws-ready footer {
39 | left: 0;
40 | position: absolute;
41 | top: 0;
42 | }
43 |
44 | .ws-ready footer {
45 | bottom: 0;
46 | top: auto;
47 | }
48 |
49 | // Remove "opacity=0" if you want an unique, visible header on each slide
50 | header[role='banner'] {
51 | opacity: 0;
52 |
53 | &:hover {
54 | opacity: 1;
55 | }
56 | }
57 |
58 | @media (max-width: 767px) {
59 | footer .alignleft,
60 | footer .alignright {
61 | display: block;
62 | float: none;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/test/plugins/video.test.js:
--------------------------------------------------------------------------------
1 | import Video from '../../src/js/plugins/video';
2 | import DOM from '../../src/js/utils/dom';
3 |
4 | beforeAll(() => {
5 | const slides = '12345'.replace(/(\d)/g,
6 | '');
7 | document.body.innerHTML = `${slides}
`;
8 | });
9 |
10 | test('Video utility', () => {
11 | const ws = document.getElementById('webslides');
12 | const slides = ws.querySelectorAll('.slide');
13 | const videos = ws.querySelectorAll('video');
14 | const play = jest.fn();
15 | const pause = jest.fn();
16 | videos.forEach(video => {
17 | video.play = play;
18 | video.pause = pause;
19 | });
20 |
21 | const webslides = {
22 | el: ws,
23 | slides: []
24 | };
25 | slides.forEach(slide => webslides.slides.push({el: slide}));
26 |
27 | expect(ws.querySelectorAll('video[autoplay]').length).toBe(5);
28 |
29 | new Video(webslides);
30 |
31 | expect(ws.querySelectorAll('video[autoplay]').length).toBe(0);
32 | expect(pause.mock.calls.length).toBe(5);
33 |
34 | webslides.slides.forEach(slide => {
35 | DOM.fireEvent(slide.el, 'slide:enable', {slide: slide});
36 | DOM.fireEvent(slide.el, 'slide:disable', {slide: slide});
37 | });
38 |
39 | expect(pause.mock.calls.length).toBe(10);
40 | });
41 |
--------------------------------------------------------------------------------
/src/scss/modules/_slides-navigation.scss:
--------------------------------------------------------------------------------
1 | /* === 5.2 Counter / Navigation Slides === */
2 |
3 | #navigation {
4 | animation: fadeIn 8s;
5 | bottom: 0;
6 | left: 0;
7 | margin-left: auto;
8 | margin-right: auto;
9 | opacity: 0;
10 | position: fixed;
11 | right: 0;
12 | width: 24.4rem;
13 | /* hover/visibility */
14 | z-index: 4;
15 |
16 | &:hover {
17 | opacity: 1;
18 | }
19 |
20 | p {
21 | margin-bottom: 0;
22 | }
23 | }
24 |
25 | #counter {
26 | display: block;
27 | line-height: 4.8rem;
28 | margin-left: auto;
29 | margin-right: auto;
30 | position: relative;
31 | text-align: center;
32 | width: 10rem;
33 |
34 | a:hover {
35 | padding: .8rem;
36 | }
37 | }
38 |
39 | a#next,
40 | a#previous {
41 | border-radius: .4rem;
42 | cursor: pointer;
43 | font-size: 2.4rem;
44 | height: 4rem;
45 | padding: .8rem;
46 | position: absolute;
47 | text-align: center;
48 | width: 4rem;
49 | }
50 |
51 | a#next {
52 | right: 3.2rem;
53 | }
54 |
55 | a#previous {
56 | left: 3.2rem;
57 | }
58 |
59 | @media (max-width: 1024px) {
60 | #navigation {
61 | animation: fadeIn 6s;
62 | background: url('../images/swipe.svg') no-repeat center top;
63 | background-size: 4.8rem;
64 | }
65 |
66 | #navigation a,
67 | #counter {
68 | display: none;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/scss/modules/_media.scss:
--------------------------------------------------------------------------------
1 | /*=== 1.3 Responsive Media (videos, iframe...) === */
2 |
3 | .embed {
4 | height: 0;
5 | overflow: hidden;
6 | /*aspect ratio:16:9*/
7 | padding-bottom: 56.6%;
8 | /*aspect ratio: 4:3*/
9 | /*padding-bottom: 75%;*/
10 | position: relative;
11 |
12 | iframe,
13 | object,
14 | embed,
15 | video {
16 | height: 100%;
17 | left: 0;
18 | margin: 0;
19 | position: absolute;
20 | top: 0;
21 | width: 100%;
22 | }
23 |
24 | /* -- Responsive background video
25 | https://fvsch.com/code/video-background/ -- */
26 |
27 | .fullscreen > & {
28 | bottom: 0;
29 | height: auto;
30 | left: 0;
31 | padding-bottom: 0;
32 | position: fixed;
33 | right: 0;
34 | top: 0;
35 |
36 | /* 1. No object-fit support: */
37 | & > iframe,
38 | & > object,
39 | & > embed,
40 | & > video {
41 | @media (min-aspect-ratio: 16 / 9) {
42 | height: 300%;
43 | top: -100%;
44 | }
45 |
46 | @media (max-aspect-ratio: 16 / 9) {
47 | left: -100%;
48 | width: 300%;
49 | }
50 |
51 | /* 2. If supporting object-fit, overriding (1): */
52 | @supports (object-fit: cover) {
53 | height: 100%;
54 | left: 0;
55 | object-fit: cover;
56 | top: 0;
57 | width: 100%;
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/scss/modules/_longform.scss:
--------------------------------------------------------------------------------
1 | /*=========================================
2 | 15. Longform
3 | =========================================== */
4 | /* -- Posts = .wrap.longform -- */
5 |
6 | .longform {
7 | width: 72rem;
8 | /* Why 72rem=720px?
9 | 90-95 characters per line = better reading speed */
10 |
11 | & .alignleft,
12 | & .alignright {
13 | max-width: 40%;
14 | }
15 |
16 | img.aligncenter,
17 | figure.aligncenter {
18 | margin-bottom: 3.2rem;
19 | margin-top: 3.2rem;
20 | }
21 |
22 | ul,
23 | ol {
24 | margin-bottom: 3.2rem;
25 | }
26 |
27 | ul ol,
28 | ol ul,
29 | ul ul,
30 | ol ol {
31 | margin-bottom: 0;
32 | }
33 |
34 | figcaption p,
35 | [class*='text-pull-'] p {
36 | font-size: 1.6rem;
37 | line-height: 2.4rem;
38 | }
39 |
40 | /* Mobile: video full width */
41 | .text-pull.embed {
42 | margin-left: -2.4rem;
43 | margin-right: -2.4rem;
44 | padding-bottom: 60.6%;
45 | }
46 |
47 | @media (min-width: 1280px) {
48 | [class*='text-pull-'] {
49 | max-width: 32%;
50 | }
51 |
52 | .text-pull-right {
53 | margin-right: -256px;
54 | }
55 |
56 | .text-pull-left {
57 | margin-left: -256px;
58 | }
59 | }
60 |
61 | @media (min-width: 1024px) {
62 | .text-quote {
63 | margin-left: -4.8rem;
64 | margin-right: -4.8rem;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/js/utils/custom-event.js:
--------------------------------------------------------------------------------
1 | const NativeCustomEvent = window.CustomEvent;
2 |
3 | /**
4 | * Check for the usage of native support for CustomEvents which is lacking
5 | * completely on IE.
6 | * @return {boolean} Whether it can be used or not.
7 | */
8 | function canIuseNativeCustom() {
9 | try {
10 | const p = new NativeCustomEvent('t', {
11 | detail: {
12 | a: 'b'
13 | }
14 | });
15 | return 't' === p.type && 'b' === p.detail.a;
16 | } catch (e) { }
17 |
18 | /* istanbul ignore next: hard to reproduce on test environment */
19 | return false;
20 | }
21 |
22 | /**
23 | * Lousy polyfill for the Custom Event constructor for IE.
24 | * @param {!string} type The type of the event.
25 | * @param {?Object} params Additional information for the event.
26 | * @return {Event}
27 | * @constructor
28 | */
29 | /* istanbul ignore next: hard to reproduce on test environment */
30 | const IECustomEvent = function CustomEvent(type, params) {
31 | const e = document.createEvent('CustomEvent');
32 |
33 | if (params) {
34 | e.initCustomEvent(type, params.bubbles, params.cancelable, params.detail);
35 | } else {
36 | e.initCustomEvent(type, false, false, undefined);
37 | }
38 |
39 | return e;
40 | };
41 |
42 | /* istanbul ignore next: hard to reproduce on test environment */
43 | const WSCustomEvent = canIuseNativeCustom() ? NativeCustomEvent : IECustomEvent;
44 |
45 | export default WSCustomEvent;
46 |
--------------------------------------------------------------------------------
/src/scss/modules/_quotes.scss:
--------------------------------------------------------------------------------
1 | /*=========================================
2 | 11. Quotes
3 | =========================================== */
4 |
5 | blockquote {
6 | display: inline-block;
7 | position: relative;
8 |
9 | p {
10 | font-size: 2.4rem;
11 | line-height: 4rem;
12 |
13 | &:last-child {
14 | margin-bottom: 3.2rem;
15 | }
16 | }
17 | }
18 |
19 | /* -- Interviews dl.text-interview -- */
20 | dd blockquote p:last-child {
21 | margin-bottom: 0;
22 | }
23 |
24 | cite {
25 | display: block;
26 | text-align: center;
27 |
28 | &:before {
29 | content: '\2014 \2009';
30 | margin-right: 6px;
31 | }
32 | }
33 |
34 | cite span {
35 | display: block;
36 | }
37 |
38 | /* -- A big Blockquote -- */
39 | /* .wall will be deprecated soon. Use .text-quote ;) */
40 | .text-quote,
41 | .wall {
42 | /* Versatility: blockquote, p, h2... */
43 | position: relative;
44 |
45 | &:before {
46 | content: '\201C';
47 | font-family: arial, sans-serif;
48 | font-size: 12rem;
49 | height: 5.6rem;
50 | left: -.8rem;
51 | line-height: 1;
52 | position: absolute;
53 | text-align: center;
54 | top: -4rem;
55 | width: 5.6rem;
56 | }
57 |
58 | @media (min-width: 768px) {
59 | padding-left: 6.4rem;
60 |
61 | p {
62 | font-size: 3.2rem;
63 | line-height: 4.8rem;
64 | }
65 |
66 | &:before {
67 | left: .8rem;
68 | top: -1.6rem;
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const SmartBannerPlugin = require('smart-banner-webpack-plugin');
2 | const path = require('path');
3 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
4 |
5 | const src = path.join(__dirname, 'src');
6 | const pkg = require('./package.json');
7 |
8 | module.exports = {
9 | context: src,
10 | entry: {
11 | webslides: './js/full.js'
12 | },
13 | output: {
14 | filename: '[name].js',
15 | path: path.join(__dirname, 'static/js'),
16 | publicPath: '/static/js/'
17 | },
18 | devServer: {
19 | contentBase: __dirname,
20 | host: '0.0.0.0'
21 | },
22 | module: {
23 | rules: [
24 | {
25 | test: /\.js$/,
26 | use: [
27 | 'babel-loader',
28 | 'eslint-loader'
29 | ],
30 | include: src
31 | },
32 | {
33 | test: /\.scss$/,
34 | use: ExtractTextPlugin.extract({
35 | fallback: 'style-loader',
36 | use: 'css-loader?url=false!postcss-loader!sass-loader'
37 | }),
38 | include: src
39 | }
40 | ]
41 | },
42 | plugins: [
43 | new ExtractTextPlugin('../css/webslides.css'),
44 | new SmartBannerPlugin({
45 | banner: `Name: WebSlides\nVersion: ${pkg.version}\nDate: ${new Date().toISOString().slice(0,10)}\nDescription: ${pkg.description}\nURL: ${pkg.homepage}\nCredits: @jlantunez, @LuisSacristan, @Belelros`,
46 | raw: false,
47 | entryOnly: true
48 | })
49 | ],
50 | };
51 |
--------------------------------------------------------------------------------
/test/utils/scroll-to.test.js:
--------------------------------------------------------------------------------
1 | import scrollTo from '../../src/js/utils/scroll-to';
2 |
3 | jest.useFakeTimers();
4 |
5 | beforeAll(() => {
6 | const brs = '
'.repeat(20);
7 | document.body.innerHTML = `${brs}
`;
8 | });
9 |
10 | afterAll(() => {
11 | jest.clearAllTimers();
12 | });
13 |
14 | test('ScrollTo with defaults', () => {
15 | const ws = document.getElementById('webslides');
16 |
17 | scrollTo(100);
18 |
19 | expect(ws.scrollTop).toBe(0);
20 |
21 | jest.runTimersToTime(400);
22 |
23 | expect(ws.scrollTop).toBeLessThan(100);
24 |
25 | jest.runAllTimers();
26 |
27 | expect(ws.scrollTop).toBe(100);
28 | });
29 |
30 | test('ScrollTo with custom duration', () => {
31 | const ws = document.getElementById('webslides');
32 | ws.scrollTop = 0;
33 | scrollTo(100, 2000);
34 |
35 | expect(ws.scrollTop).toBe(0);
36 | jest.runTimersToTime(500);
37 | expect(ws.scrollTop).toBeLessThan(100);
38 | jest.runTimersToTime(700);
39 | expect(ws.scrollTop).toBeLessThan(100);
40 | jest.runAllTimers();
41 |
42 | expect(ws.scrollTop).toBe(100);
43 | });
44 |
45 | test('ScrollTo with custom callback', () => {
46 | const ws = document.getElementById('webslides');
47 | ws.scrollTop = 0;
48 | const cb = jest.fn();
49 |
50 | scrollTo(100, 500, cb);
51 | expect(ws.scrollTop).toBe(0);
52 | expect(cb).not.toBeCalled();
53 |
54 | jest.runAllTimers();
55 |
56 | expect(ws.scrollTop).toBe(100);
57 | expect(cb).toBeCalled();
58 | });
59 |
--------------------------------------------------------------------------------
/src/scss/modules/_flexblock-features.scss:
--------------------------------------------------------------------------------
1 | /*====================================================================
2 | 6.1 Features
3 | ====================================================================== */
4 |
5 | .flexblock.features {
6 | > li {
7 | border-radius: .4rem;
8 | margin-bottom: 4.8rem;
9 | width: 100%;
10 | }
11 |
12 | li h2 {
13 | text-transform: uppercase;
14 | }
15 |
16 | li span {
17 | font-weight: 300;
18 | }
19 |
20 | li p {
21 | margin: 0;
22 | }
23 |
24 | li p em {
25 | display: block;
26 | }
27 |
28 | li span,
29 | li svg {
30 | display: block;
31 | font-size: 6.4rem;
32 | line-height: 1;
33 | margin: 0;
34 | }
35 |
36 | li img {
37 | width: 6.4rem;
38 | }
39 |
40 | li span sup {
41 | font-size: 3rem;
42 | }
43 |
44 | @media (min-width: 1200px) {
45 | li span,
46 | li svg,
47 | li img {
48 | float: left;
49 | margin-right: .8rem;
50 | }
51 | }
52 | }
53 |
54 | @media (min-width: 768px) {
55 | .flexblock.features {
56 | margin-left: -2%;
57 | margin-right: -2%;
58 | }
59 |
60 | .flexblock.features > li {
61 | margin-left: 2%;
62 | margin-right: 2%;
63 | width: 29%;
64 | }
65 |
66 | .size-50 .flexblock.features > li {
67 | width: 46%;
68 | }
69 |
70 | .column .flexblock.features > li {
71 | width: 100%;
72 | }
73 |
74 | footer .flexblock.features>li {
75 | margin-bottom: 0;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/scss/modules/_toc.scss:
--------------------------------------------------------------------------------
1 | /*===========================================
2 | 9. Table of contents
3 | ============================================= */
4 |
5 | .toc,
6 | .toc ol > li:before,
7 | .chapter {
8 | position: relative;
9 | z-index: 2;
10 | }
11 |
12 | .toc {
13 | ol {
14 | counter-reset: item;
15 | position: relative;
16 |
17 | & > li:before {
18 | content: counters(item, '.') '. ';
19 | display: table-cell;
20 | padding-right: .8rem;
21 | width: 2.4rem;
22 | }
23 |
24 | li li:before {
25 | content: counters(item, '.') ' ';
26 | }
27 | }
28 |
29 | li {
30 | counter-increment: item;
31 | display: table;
32 | font-weight: 400;
33 | margin-bottom: .8rem;
34 | margin-left: 0;
35 | transition: .3s;
36 | width: 100%;
37 |
38 | li {
39 | font-weight: 300;
40 | margin-bottom: 0;
41 | margin-left: 0;
42 | }
43 |
44 | .toc-page:before {
45 | content: '';
46 | display: block;
47 | left: 0;
48 | margin-top: 1.8rem;
49 | position: absolute;
50 | right: 4rem;
51 | }
52 |
53 | & > a {
54 | display: inline-block;
55 | width: 100%;
56 | }
57 |
58 | a:hover span {
59 | font-weight: 600;
60 | }
61 |
62 | a:hover .toc-page:before {
63 | border-bottom-width: 2px;
64 | }
65 | }
66 | }
67 |
68 | .chapter {
69 | display: inline-block;
70 | font-size: 1.8rem;
71 | line-height: 3.2rem;
72 | padding-right: .8rem;
73 | }
74 |
75 | .toc-page {
76 | float: right;
77 | }
78 |
--------------------------------------------------------------------------------
/src/js/utils/scroll-to.js:
--------------------------------------------------------------------------------
1 | import Easings from './easing';
2 |
3 | let SCROLLABLE_CONTAINER = document.getElementById('webslides');
4 |
5 | /**
6 | * Smoothly scrolls to a given Y position using Easing.Swing. It'll run a
7 | * callback upon finishing.
8 | * @param {number} y Offset of the page to scroll to.
9 | * @param {number} duration Duration of the animation. 500ms by default.
10 | * @param {function} cb Callback function to call upon completion.
11 | * @param {HTMLElement} container The HTML element where to scroll
12 | */
13 | export default function scrollTo(
14 | y, duration = 500, cb = () => {}, container = null) {
15 | SCROLLABLE_CONTAINER = container ?
16 | container : document.getElementById('webslides');
17 |
18 | const delta = y - SCROLLABLE_CONTAINER.scrollTop;
19 | const startLocation = SCROLLABLE_CONTAINER.scrollTop;
20 | const increment = 16;
21 |
22 | if (!duration) {
23 | SCROLLABLE_CONTAINER.scrollTop = y;
24 | cb();
25 | return;
26 | }
27 |
28 | const animateScroll = elapsedTime => {
29 | elapsedTime += increment;
30 | const percent = Math.min(1, elapsedTime / duration);
31 | const easingP = Easings.swing(
32 | percent,
33 | elapsedTime * percent,
34 | y,
35 | delta,
36 | duration);
37 |
38 | SCROLLABLE_CONTAINER.scrollTop = Math.floor(startLocation +
39 | (easingP * delta));
40 |
41 | if (elapsedTime < duration) {
42 | setTimeout(() => animateScroll(elapsedTime), increment);
43 | } else {
44 | cb();
45 | }
46 | };
47 |
48 | animateScroll(0);
49 | }
50 |
--------------------------------------------------------------------------------
/src/js/plugins/grid.js:
--------------------------------------------------------------------------------
1 | import Keys from '../utils/keys';
2 |
3 | const GRID_IMAGE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYAg' +
4 | 'MAAACdGdVrAAAACVBMVEUAAAAtXsUtXcPDDPUWAAAAA3RSTlMAZmHzZFkxAAAAFklEQVQI12M' +
5 | 'AA9bBR3ExhAJB1iooBQBGwgVEs/QtuAAAAABJRU5ErkJggg==';
6 |
7 | /**
8 | * Grid plugin that shows a grid on top of the WebSlides for easy prototyping.
9 | */
10 | export default class Grid {
11 | /**
12 | * @param {WebSlides} wsInstance The WebSlides instance
13 | * @constructor
14 | */
15 | constructor(wsInstance) {
16 | /**
17 | * @type {WebSlides}
18 | * @private
19 | */
20 | this.ws_ = wsInstance;
21 |
22 | const CSS = `body.baseline {
23 | background: url(${GRID_IMAGE}) left top .8rem/.8rem;
24 | }`;
25 | const head = document.head || document.getElementsByTagName('head')[0];
26 | const style = document.createElement('style');
27 |
28 | style.type = 'text/css';
29 |
30 | if (style.styleSheet) {
31 | style.styleSheet.cssText = CSS;
32 | } else {
33 | style.appendChild(document.createTextNode(CSS));
34 | }
35 |
36 | head.appendChild(style);
37 |
38 | document.addEventListener('keydown', this.onKeyPress_.bind(this), false);
39 | }
40 |
41 | /**
42 | * Reacts to the keydown event. It reacts to ENTER key to toggle the class.
43 | * @param {KeyboardEvent} event The key event.
44 | * @private
45 | */
46 | onKeyPress_(event) {
47 | if (event.which === Keys.ENTER) {
48 | document.body.classList.toggle('baseline');
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/scss/modules/_navigation.scss:
--------------------------------------------------------------------------------
1 | /*=========================================
2 | 4. Navigation
3 | =========================================== */
4 |
5 | /*=== 4.1. Navbars === */
6 |
7 | nav ul {
8 | display: flex;
9 | flex-wrap: wrap;
10 | /*====align left====*/
11 | justify-content: flex-start;
12 | /* ==== align center ====*/
13 | /*justify-content: center; */
14 | /*====align right====*/
15 | /* justify-content: flex-end; */
16 | /*====separated columns li a====*/
17 | /* justify-content: space-between; */
18 | /*====separated columns centered li a====*/
19 | /*justify-content: space-around;*/
20 |
21 | li {
22 | float: left;
23 | list-style: none;
24 | position: relative;
25 | }
26 | }
27 |
28 | nav ul li:first-child,
29 | nav[role='navigation'] ul li {
30 | margin-left: 0;
31 | }
32 |
33 | nav[role='navigation'] li a {
34 | display: flex;
35 | justify-content: center;
36 | line-height: 4.8rem;
37 | max-width: 100%;
38 | padding: 0 1.6rem;
39 | position: relative;
40 | text-decoration: none;
41 |
42 | svg {
43 | margin: 1.5rem .4rem 1.5rem 0;
44 | }
45 | }
46 |
47 | header nav ul {
48 | justify-content: flex-end;
49 | margin: 0;
50 | }
51 |
52 | nav.aligncenter ul,
53 | .aligncenter nav ul {
54 | /* ==== align center ====*/
55 | justify-content: center;
56 | }
57 |
58 | nav.navbar ul li {
59 | /*====full float li a ====*/
60 | flex: 1 1 auto;
61 | }
62 |
63 | @media (max-width: 568px) {
64 | nav.navbar ul {
65 | flex-flow: column wrap;
66 | padding: 0;
67 | }
68 |
69 | nav.navbar li a {
70 | justify-content: flex-start;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/js/plugins/video.js:
--------------------------------------------------------------------------------
1 | import DOM from '../utils/dom';
2 | import {default as Slide, Events as SlideEvents} from '../modules/slide';
3 |
4 | /**
5 | * Video plugin. Video plugin that allows to autoplay videos once the slide gets
6 | * active.
7 | */
8 | export default class Video {
9 | /**
10 | * @param {WebSlides} wsInstance The WebSlides instance.
11 | * @constructor
12 | */
13 | constructor(wsInstance) {
14 | /**
15 | * @type {WebSlides}
16 | * @private
17 | */
18 | this.ws_ = wsInstance;
19 |
20 | const videos = DOM.toArray(this.ws_.el.querySelectorAll('video'));
21 |
22 | if (videos.length) {
23 | videos.forEach(video => {
24 | if (!video.hasAttribute('autoplay')) {
25 | return;
26 | }
27 |
28 | video.removeAttribute('autoplay');
29 | video.pause();
30 | video.currentTime = 0;
31 | const {i} = Slide.getSectionFromEl(video);
32 | const slide = wsInstance.slides[i - 1];
33 |
34 | slide.video = video;
35 |
36 | slide.el.addEventListener(SlideEvents.ENABLE, Video.onSectionEnabled);
37 | slide.el.addEventListener(SlideEvents.DISABLE, Video.onSectionDisabled);
38 | });
39 | }
40 | }
41 |
42 | /**
43 | * On Section enable hook. Will play the video.
44 | * @param {CustomEvent} event
45 | */
46 | static onSectionEnabled(event) {
47 | event.detail.slide.video.play();
48 | }
49 |
50 | /**
51 | * On Section enable hook. Will pause the video.
52 | * @param {CustomEvent} event
53 | */
54 | static onSectionDisabled(event) {
55 | event.detail.slide.video.pause();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/js/utils/mobile-detector.js:
--------------------------------------------------------------------------------
1 | const UA = window.navigator.userAgent;
2 |
3 | /**
4 | * Mobile detector helper class. Tests the User Agent to see if we're, likely,
5 | * on a mobile device.
6 | */
7 | export default class MobileDetector {
8 | /**
9 | * Whether the device is Android or not.
10 | * @return {Boolean}
11 | */
12 | static isAndroid() {
13 | return !!UA.match(/Android/i);
14 | }
15 |
16 | /**
17 | * Whether the device is BlackBerry or not.
18 | * @return {Boolean}
19 | */
20 | static isBlackBerry() {
21 | return !!UA.match(/BlackBerry/i);
22 | }
23 |
24 | /**
25 | * Whether the device is iOS or not.
26 | * @return {Boolean}
27 | */
28 | static isiOS() {
29 | return !!UA.match(/iPad|iPhone|iPod/i);
30 | }
31 |
32 | /**
33 | * Whether the device is Opera or not.
34 | * @return {Boolean}
35 | */
36 | static isOpera() {
37 | return !!UA.match(/Opera Mini/i);
38 | }
39 |
40 | /**
41 | * Whether the device is Windows or not.
42 | * @return {Boolean}
43 | */
44 | static isWindows() {
45 | return !!UA.match(/IEMobile/i);
46 | }
47 |
48 | /**
49 | * Whether the device is Windows Phone or not.
50 | * @return {Boolean}
51 | */
52 | static isWindowsPhone() {
53 | return !!UA.match(/Windows Phone/i);
54 | }
55 |
56 | /**
57 | * Whether the device is any mobile device or not.
58 | * @return {Boolean}
59 | */
60 | static isAny() {
61 | return MobileDetector.isAndroid() ||
62 | MobileDetector.isBlackBerry() ||
63 | MobileDetector.isiOS() ||
64 | MobileDetector.isOpera() ||
65 | MobileDetector.isWindows() ||
66 | MobileDetector.isWindowsPhone();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/scss/modules/_flexblock-plans.scss:
--------------------------------------------------------------------------------
1 | /*===============================================
2 | 6.8 Plans / Pricing
3 | ================================================= */
4 |
5 | .flexblock.plans {
6 | > li {
7 | border-radius: 3px;
8 | margin-bottom: 4.8rem;
9 | text-align: center;
10 | z-index: 1;
11 | }
12 |
13 | li,
14 | &.blink li>a {
15 | padding: 0;
16 | }
17 |
18 | &.blink li > a div,
19 | li div {
20 | padding-bottom: 3.2rem;
21 | }
22 |
23 | li p,
24 | li h2 {
25 | padding: .8rem 3.2rem;
26 | }
27 |
28 | li h2 {
29 | float: left;
30 | font-weight: 400;
31 | letter-spacing: .1rem;
32 | text-transform: uppercase;
33 | width: 100%;
34 | }
35 |
36 | .price {
37 | clear: both;
38 | display: block;
39 | font-size: 4.8rem;
40 | font-weight: 400;
41 | line-height: 6.2rem;
42 | padding: 2.4rem;
43 |
44 | sup {
45 | font-size: 1.8rem;
46 | margin-right: .4rem;
47 | }
48 |
49 | li ul {
50 | margin-bottom: 2.4rem;
51 | }
52 | }
53 |
54 | li ul li {
55 | display: block;
56 | padding: .8rem 3.2rem;
57 | text-align: left;
58 | width: 100%;
59 | }
60 |
61 |
62 | @media (min-width: 1024px) {
63 | margin-left: -2%;
64 | margin-right: -2%;
65 |
66 | >li {
67 | margin-left: 2%;
68 | margin-right: 2%;
69 | width: 29%;
70 | }
71 |
72 | >li:hover,
73 | >li:nth-child(2) {
74 | position: relative;
75 | transform: scale(1.08);
76 | z-index: 2;
77 | }
78 |
79 | &:hover li:nth-child(2):not(:hover) {
80 | position: relative;
81 | transform: scale(1);
82 | z-index: 1;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/scss/modules/_promos.scss:
--------------------------------------------------------------------------------
1 | /*=============================================
2 | 7. Promos/Offers (pricing, tagline, CTA...)
3 | =============================================== */
4 |
5 | .cta {
6 | display: flex;
7 | flex-wrap: wrap;
8 | justify-content: center;
9 | }
10 |
11 | .number,
12 | .cta .benefit {
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: center;
16 | max-width: 100%;
17 | padding: .8rem;
18 | }
19 |
20 | .number {
21 | text-align: center;
22 | }
23 |
24 | .cta .benefit {
25 | max-width: 100%;
26 | text-align: center;
27 | }
28 |
29 | .number span {
30 | display: block;
31 | font-size: 8rem;
32 | line-height: 8rem;
33 | }
34 |
35 | .number span sup {
36 | font-size: 4rem;
37 | }
38 |
39 | .cta p {
40 | margin-bottom: 0;
41 | }
42 |
43 | @media (min-width: 768px) {
44 | .number,
45 | .cta .benefit {
46 | max-width: 50%;
47 | padding: 4.8rem;
48 | }
49 |
50 | .cta .benefit {
51 | text-align: left;
52 | }
53 |
54 | .number span {
55 | font-size: 16rem;
56 | line-height: 16rem;
57 |
58 | sup {
59 | font-size: 6rem;
60 | vertical-align: middle;
61 | }
62 | }
63 | }
64 |
65 | /* --- Header CTA --- */
66 | .cta-cover {
67 | display: table;
68 | width: 100%;
69 |
70 | h1 strong {
71 | font-weight: 400;
72 | }
73 |
74 | @media (min-width: 1024px) {
75 | h1 {
76 | float: left;
77 | max-width: 80%;
78 | }
79 |
80 | h1 strong {
81 | display: block;
82 | }
83 |
84 | .button {
85 | margin-top: 1.2rem;
86 | }
87 |
88 | .try {
89 | text-align: center;
90 | }
91 | }
92 |
93 | @media (max-width: 1023px) {
94 | .alignright {
95 | float: none;
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/scss/_vars.scss:
--------------------------------------------------------------------------------
1 | // sass-lint:disable no-color-literals
2 |
3 | // =========
4 | // Colors. Names from http://chir.ag/projects/name-that-color/
5 | // =========
6 | $black: #000;
7 | $white: #fff;
8 | $mine-shaft: #333;
9 | $royal-blue: #44d;
10 | $havelock-blue: #67d;
11 | $catskill-white: #f7f9fb;
12 | $cod-gray: #111;
13 | $big-stone: #123;
14 | $rhino: #346;
15 | $athens-gray: #f8f8f9;
16 | $mischka: #d5d9e2;
17 | $pine-green: #077;
18 | $purple-heart: #62b;
19 | $cardinal: #c23;
20 | $mirage: #1a2028;
21 | $pickled-bluewood: #293845;
22 | $facebook: #3b5998;
23 | $spindle: #bce;
24 | $dodger-blue: #3af;
25 | $pattens-blue: #def;
26 | $stratos: #001450;
27 | $gray-brown: #f9f8f2;
28 |
29 | $bg-colors: (
30 | 'primary': $royal-blue,
31 | 'secondary': $havelock-blue,
32 | 'light': $catskill-white,
33 | 'black': $cod-gray,
34 | 'black-blue': $big-stone,
35 | 'blue': $rhino,
36 | 'brown': $gray-brown,
37 | 'gray': $mischka,
38 | 'green': $pine-green,
39 | 'purple': $purple-heart,
40 | 'red': $cardinal,
41 | 'white': $white,
42 | // Branding
43 | 'facebook': $facebook
44 | ) !default;
45 |
46 | $social-nav: (
47 | 'twitter': #1da1f3,
48 | 'facebook': $facebook,
49 | 'linkedin': #1683bb,
50 | 'dribbble': #ea4c89,
51 | 'github': #60b044,
52 | 'email': #dd4b39
53 | ) !default;
54 |
55 | $body-color: $mine-shaft !default;
56 | $body-bg: $catskill-white !default;
57 | $focus-box-shadow: 0 0 2px rgba(150, 187, 238, 1) !default;
58 | $link-color: $royal-blue !default;
59 | $link-color-secondary: $cardinal !default;
60 | $link-hover: $dodger-blue !default;
61 | $hr-bg: radial-gradient(ellipse at center, rgba(0, 20, 80, .2) 0, rgba(255, 255, 255, 0) 75%) !default;
62 | $current-zoomed-slide-shadow: 0 0 7px rgba(0, 187, 255, .5);
63 | $index-overlay: rgba(0, 10, 40, .8);
64 |
--------------------------------------------------------------------------------
/test/utils/hash.test.js:
--------------------------------------------------------------------------------
1 | import DOM from '../../src/js/utils/dom';
2 | import Hash from '../../src/js/plugins/hash';
3 |
4 | describe('Hash utility', () => {
5 | document.body.innerHTML =
6 | ``;
7 |
8 | document.location.hash = '#slide=1';
9 | const goto = jest.fn();
10 | const ws = document.getElementById('webslides');
11 |
12 |
13 | const webslides = {
14 | options: {
15 | changeOnClick: true
16 | },
17 | goToSlide: goto,
18 | el: ws
19 | };
20 |
21 | test('Make sure it has not changed the slide', () => {
22 | expect(goto).not.toBeCalled();
23 | });
24 |
25 | new Hash(webslides);
26 |
27 | test('Move to slide 1', () => {
28 | expect(Hash.getSlideNumber()).toBe(0);
29 | DOM.fireEvent(ws, 'ws:slide-change', {
30 | slides: 3,
31 | currentSlide0: 1,
32 | currentSlide: 2
33 | });
34 | expect(Hash.getSlideNumber()).toBe(1);
35 | expect(document.location.hash).toBe('#slide=2');
36 |
37 | DOM.fireEvent(window, 'hashchange');
38 | expect(goto.mock.calls.length).toBe(1);
39 | });
40 |
41 | test('Forces slide change', () => {
42 | Hash.setSlideNumber(5);
43 | const state = history.state;
44 | expect(state.slideI).toBe(4);
45 | });
46 |
47 | test('Wrong hash', () => {
48 | document.location.hash = 'slide=NaN';
49 | DOM.fireEvent(window, 'hashchange');
50 | // It shouldn't be call.
51 | expect(goto.mock.calls.length).toBe(1);
52 | expect(Hash.getSlideNumber()).toBe(null);
53 | });
54 |
55 | test('Repeat slide change', () => {
56 | Hash.setSlideNumber(5);
57 | let state = history.state;
58 | expect(state.slideI).toBe(4);
59 | Hash.setSlideNumber(5);
60 | state = history.state;
61 | expect(state.slideI).toBe(4);
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/test/plugins/scroll.test.js:
--------------------------------------------------------------------------------
1 | import Scroll from '../../src/js/plugins/scroll';
2 |
3 | jest.useFakeTimers();
4 |
5 | // Copy of DOM.fireEvent, but using wheel deltas
6 | const fireEvent = (target, eventType, deltaX, deltaY) => {
7 | const event = new CustomEvent(eventType);
8 | event.deltaX = deltaX;
9 | event.deltaY = deltaY;
10 | event.deltaMode = 0;
11 |
12 | target.dispatchEvent(event);
13 | };
14 |
15 | beforeAll(() => {
16 | const brs = '
'.repeat(20);
17 | document.body.innerHTML = `${brs}
`;
18 | });
19 |
20 | test('Scroll utility', () => {
21 | // Forces mobile detection
22 | window.navigator = {
23 | userAgent: 'Android'
24 | };
25 | const ws = document.getElementById('webslides');
26 | const next = jest.fn();
27 | const prev = jest.fn();
28 |
29 | let disabled = true;
30 | const webslides = {
31 | el: ws,
32 | isDisabled: () => disabled,
33 | isMoving: false,
34 | isVertical: false,
35 | goNext: next,
36 | goPrev: prev,
37 | options: {
38 | navigateOnScroll: true,
39 | scrollWait: 200,
40 | minWheelDelta: 50
41 | }
42 | };
43 |
44 | new Scroll(webslides);
45 | fireEvent(ws, 'wheel', 300, 200);
46 |
47 | expect(next).not.toBeCalled();
48 | expect(prev).not.toBeCalled();
49 |
50 | // Wait until next execution
51 | jest.runTimersToTime(201);
52 | expect(next.mock.calls.length).toBe(0);
53 | expect(prev.mock.calls.length).toBe(0);
54 |
55 | disabled = false;
56 | fireEvent(ws, 'wheel', 300, 200);
57 |
58 | jest.runTimersToTime(201);
59 | expect(next.mock.calls.length).toBe(1);
60 | expect(prev.mock.calls.length).toBe(0);
61 |
62 | fireEvent(ws, 'wheel', -200, -300);
63 |
64 | jest.runTimersToTime(201);
65 | expect(next.mock.calls.length).toBe(1);
66 | expect(prev.mock.calls.length).toBe(1);
67 | });
68 |
--------------------------------------------------------------------------------
/src/js/plugins/autoslide.js:
--------------------------------------------------------------------------------
1 | import DOM from '../utils/dom';
2 |
3 | /**
4 | * Autoslide plugin.
5 | */
6 | export default class AutoSlide {
7 | /**
8 | * @param {WebSlides} wsInstance The WebSlides instance
9 | * @constructor
10 | */
11 | constructor(wsInstance) {
12 | /**
13 | * @type {WebSlides}
14 | * @private
15 | */
16 | this.ws_ = wsInstance;
17 | /**
18 | * Interval ID reference for the autoslide.
19 | * @type {?number}
20 | * @private
21 | */
22 | this.interval_ = null;
23 | /**
24 | * Internal stored time.
25 | * @type {?number}
26 | */
27 | this.time = this.ws_.options.autoslide;
28 |
29 | if (this.time) {
30 | DOM.once(wsInstance.el, 'ws:init', this.play.bind(this));
31 | document.body.addEventListener('focus', this.onFocus.bind(this));
32 | }
33 | }
34 |
35 | /**
36 | * On focus handler. Will decide if stops/play depending on the focused
37 | * element if autoslide is active.
38 | */
39 | onFocus() {
40 | if (DOM.isFocusableElement()) {
41 | this.stop();
42 | } else if (this.interval_ === null) {
43 | this.play();
44 | }
45 | }
46 |
47 | /**
48 | * Starts autosliding all the slides if it's not currently doing it and the
49 | * autoslide option was a number greater than 0.
50 | * @param {?number=} time Amount of milliseconds to wait to go to next slide
51 | * automatically.
52 | */
53 | play(time) {
54 | if (typeof time !== 'number') {
55 | time = this.time;
56 | }
57 |
58 | this.time = time;
59 |
60 | if (!this.interval_ && typeof time === 'number' && time > 0) {
61 | this.interval_ = setInterval(this.ws_.goNext.bind(this.ws_), time);
62 | }
63 | }
64 |
65 | /**
66 | * Stops autosliding all the slides.
67 | */
68 | stop() {
69 | if (this.interval_) {
70 | clearInterval(this.interval_);
71 | this.interval_ = null;
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/scss/utils/_animations.scss:
--------------------------------------------------------------------------------
1 | /* === 1.2 Animations ================
2 | Just 5 basic animations:
3 | .fadeIn, .fadeInUp, .zoomIn, .slideInLeft, and .slideInRight
4 | https://github.com/daneden/animate.css */
5 |
6 | /*-- fadeIn -- */
7 |
8 | @keyframes fadeIn {
9 | from {
10 | opacity: 0;
11 | }
12 |
13 | to {
14 | opacity: 1;
15 | }
16 | }
17 |
18 | .fadeIn {
19 | animation: fadeIn 1s;
20 | }
21 |
22 | /*-- fadeInUp -- */
23 | @keyframes fadeInUp {
24 | from {
25 | opacity: 0;
26 | transform: translate3d(0, 100%, 0);
27 | }
28 |
29 | to {
30 | opacity: 1;
31 | transform: none;
32 | }
33 | }
34 |
35 | .fadeInUp {
36 | animation: fadeInUp 1s;
37 | }
38 |
39 | /*-- zoomIn -- */
40 | @keyframes zoomIn {
41 | from {
42 | transform: scale3d(.3, .3, .3);
43 | }
44 |
45 | 50% {
46 | opacity: 1;
47 | }
48 | }
49 |
50 | .zoomIn {
51 | animation: zoomIn 1s;
52 | }
53 |
54 | /*-- slideInLeft -- */
55 | @keyframes slideInLeft {
56 | from {
57 | transform: translate3d(-100%, 0, 0);
58 | visibility: visible;
59 | }
60 |
61 | to {
62 | transform: translate3d(0, 0, 0);
63 | }
64 | }
65 |
66 | .slideInLeft {
67 | animation: slideInLeft 1s;
68 | animation-fill-mode: both;
69 | }
70 |
71 | /*-- slideInRight -- */
72 | @keyframes slideInRight {
73 | from {
74 | transform: translate3d(100%, 0, 0);
75 | visibility: visible;
76 | }
77 |
78 | to {
79 | transform: translate3d(0, 0, 0);
80 | }
81 | }
82 |
83 | .slideInRight {
84 | animation: slideInRight 1s;
85 | animation-fill-mode: both;
86 | }
87 |
88 | /* Animated Background (Matrix) */
89 | @keyframes anim {
90 | 0% {
91 | transform: translateY(0);
92 | }
93 |
94 | 100% {
95 | transform: translateY(-1200px);
96 | }
97 | }
98 |
99 | /* Duration */
100 | .slow {
101 | animation-duration: 4s;
102 |
103 | & + & {
104 | animation-duration: 5s;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/scss/modules/_flexblock.scss:
--------------------------------------------------------------------------------
1 | /*===============================================================
2 | 6. Magic blocks with flexbox (Auto-fill & Equal Height)
3 | Blocks Links li>a = .flexblock.blink (.blink required)
4 | ================================================================= */
5 |
6 | .flexblock {
7 | clear: both;
8 | display: flex;
9 | flex-wrap: wrap;
10 | margin-left: auto;
11 | margin-right: auto;
12 | padding: 0;
13 |
14 | &:after {
15 | clear: both;
16 | }
17 |
18 | &:before {
19 | content: '';
20 | display: table;
21 | }
22 |
23 | li,
24 | &.blink li > a {
25 | display: flex;
26 | flex-direction: column;
27 | margin: 0;
28 | padding: 2.4rem;
29 | position: relative;
30 | }
31 |
32 | li {
33 | flex: auto;
34 | text-align: left;
35 | transition: .3s;
36 | width: 100%;
37 |
38 | &:hover {
39 | transform: translateY(-.2rem);
40 | }
41 |
42 | @media (min-width: 600px) {
43 | width: 50%;
44 | }
45 |
46 | @media (min-width: 1024px) {
47 | width: 25%;
48 | }
49 | }
50 |
51 | &.aligncenter li {
52 | text-align: center;
53 | }
54 |
55 | &.vertical-align li {
56 | justify-content: center;
57 | }
58 |
59 | &.blink li {
60 | padding: 0;
61 | }
62 |
63 | li h2 svg,
64 | li h3 svg {
65 | margin-top: 0;
66 | }
67 | }
68 |
69 | h1 + .flexblock,
70 | h2 + .flexblock,
71 | h3 + .flexblock,
72 | div + ul,
73 | div + ol {
74 | margin-top: 3.2rem;
75 | }
76 |
77 | .flexblock li h2,
78 | .flexblock li h3,
79 | footer .column h2,
80 | footer .column h3 {
81 | font-size: 1.8rem;
82 | font-weight: 600;
83 | line-height: 3.2rem;
84 | margin-bottom: 0;
85 | }
86 |
87 | .flexblock li li,
88 | .flexblock.blink li li {
89 | padding: 0;
90 | width: 100%;
91 | }
92 |
93 | [class*='content-'] .flexblock li p {
94 | font-size: 1.8rem;
95 | line-height: 3.2rem;
96 | }
97 |
98 | .content-right .flexblock.features li,
99 | .content-left .flexblock.features li {
100 | width: 46%;
101 | }
102 |
--------------------------------------------------------------------------------
/src/scss/modules/_work.scss:
--------------------------------------------------------------------------------
1 | /*=========================================
2 | 8. Work/Resumé/CV
3 | =========================================== */
4 | .work {
5 | clear: both;
6 | display: flex;
7 | flex-direction: column;
8 | text-align: left;
9 |
10 | h1 + &,
11 | h2 + &,
12 | h3 + &,
13 | p + & {
14 | margin-top: 4.8rem;
15 | }
16 |
17 | li {
18 | flex: 1;
19 | list-style: none;
20 | margin: 0;
21 | position: relative;
22 | }
23 |
24 | p {
25 | margin-bottom: 0;
26 | transition: .3s;
27 | }
28 |
29 | li a {
30 | display: block;
31 | float: left;
32 | height: 100%;
33 | padding: 2.4rem 0;
34 | width: 100%;
35 | }
36 |
37 | li p {
38 | padding-left: 1.2rem;
39 | }
40 |
41 | li.work-label p {
42 | padding-left: 0;
43 | }
44 |
45 | li a:hover p:first-child {
46 | padding-left: 1.6rem;
47 | }
48 |
49 | li p:last-child {
50 | position: absolute;
51 | right: 1.2rem;
52 | top: 2.4rem;
53 | }
54 |
55 | li.work-label p:last-child {
56 | right: 0;
57 | top: 0;
58 | }
59 |
60 | &-label {
61 | float: left;
62 | font-weight: 600;
63 | padding: 0 0 2.4rem;
64 | width: 100%;
65 | }
66 |
67 | &-title {
68 | display: block;
69 | padding-right: 1.2rem;
70 | width: 75%;
71 | }
72 | }
73 |
74 | @media (min-width: 768px) {
75 | .work-label p,
76 | .work li p {
77 | float: left;
78 | margin-right: 2%;
79 | width: 25%;
80 | }
81 |
82 | .work li.work-label p:last-child,
83 | .work li p:last-child {
84 | float: right;
85 | margin-right: 0;
86 | padding-right: 1.2rem;
87 | position: relative;
88 | right: auto;
89 | text-align: right;
90 | top: auto;
91 | }
92 |
93 | .work li p.work-date {
94 | width: 120px;
95 | }
96 | }
97 |
98 | @media (max-width: 768px) {
99 | .work-client,
100 | .work-label .work-services {
101 | clip: rect(1px, 1px, 1px, 1px);
102 | height: 1px;
103 | overflow: hidden;
104 | position: absolute;
105 | width: 1px;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/test/plugins/navigation.test.js:
--------------------------------------------------------------------------------
1 | import DOM from '../../src/js/utils/dom';
2 | import Navigation from '../../src/js/plugins/navigation';
3 |
4 | beforeAll(() => {
5 | document.body.innerHTML = ``;
6 | });
7 |
8 | test('Navigation plugin', () => {
9 | const ws = document.getElementById('webslides');
10 |
11 | const zoom = jest.fn();
12 | const next = jest.fn();
13 | const prev = jest.fn();
14 | const webslides = {
15 | el: ws,
16 | goNext: next,
17 | goPrev: prev,
18 | toggleZoom: zoom,
19 | options: {showIndex: true}
20 | };
21 |
22 | const fakeArrow = Navigation.createArrow('arrow', 'Test');
23 | expect(fakeArrow.tagName).toBe('A');
24 | expect(fakeArrow.title).toBe('Arrow Keys');
25 | expect(fakeArrow.id).toBe('arrow');
26 | expect(fakeArrow.textContent).toBe('Test');
27 |
28 | const fakeCounter = Navigation.createCounter('counter', webslides);
29 | expect(fakeCounter.tagName).toBe('SPAN');
30 | expect(fakeCounter.childNodes.length).toBe(1);
31 | expect(fakeCounter.childNodes[0].tagName).toBe('A');
32 | expect(fakeCounter.childNodes[0].href).toBe('http://localhost/#');
33 | expect(fakeCounter.childNodes[0].title).toBe('View all slides');
34 |
35 | new Navigation(webslides);
36 |
37 | const navElem = ws.querySelector('#navigation');
38 | const counter = navElem.querySelector('#counter');
39 | const nextElem = navElem.querySelector('#next');
40 | const prevElem = navElem.querySelector('#previous');
41 | expect(navElem).not.toBe(null);
42 | expect(counter).not.toBe(null);
43 | expect(nextElem).not.toBe(null);
44 | expect(prevElem).not.toBe(null);
45 |
46 | DOM.fireEvent(ws, 'ws:slide-change', {
47 | slides: 3,
48 | currentSlide0: 1,
49 | currentSlide: 2
50 | });
51 | expect(counter.textContent).toBe('2 / 3');
52 |
53 | expect(next.mock.calls.length).toBe(0);
54 | expect(prev.mock.calls.length).toBe(0);
55 | expect(zoom.mock.calls.length).toBe(0);
56 |
57 | DOM.fireEvent(nextElem, 'click');
58 | expect(next.mock.calls.length).toBe(1);
59 |
60 | DOM.fireEvent(prevElem, 'click');
61 | expect(prev.mock.calls.length).toBe(1);
62 |
63 | DOM.fireEvent(counter, 'click');
64 | expect(zoom.mock.calls.length).toBe(1);
65 | });
66 |
--------------------------------------------------------------------------------
/src/js/plugins/hash.js:
--------------------------------------------------------------------------------
1 | const HASH = '#slide';
2 | const slideRegex = /#slide=(\d+)/;
3 |
4 | /**
5 | * Static class with methods to manipulate and extract info from the hash of
6 | * the URL.
7 | */
8 | export default class Hash {
9 | /**
10 | * @param {WebSlides} wsInstance
11 | * @constructor
12 | */
13 | constructor(wsInstance) {
14 | this.ws_ = wsInstance;
15 |
16 | wsInstance.el.addEventListener('ws:slide-change', Hash.onSlideChange_);
17 | window.addEventListener('hashchange', this.onHashChange_.bind(this), false);
18 | }
19 |
20 | /**
21 | * hashchange event handler, makes the WebSlide instance navigate to the
22 | * needed slide.
23 | */
24 | onHashChange_() {
25 | const newSlideIndex = Hash.getSlideNumber();
26 |
27 | if (newSlideIndex !== null) {
28 | this.ws_.goToSlide(newSlideIndex);
29 | }
30 | }
31 |
32 | /**
33 | * Handler for the slide change event which updates the slide on the hash.
34 | * @param {Event} event
35 | * @private
36 | */
37 | static onSlideChange_(event) {
38 | Hash.setSlideNumber(event.detail.currentSlide);
39 | }
40 |
41 | /**
42 | * Gets the slide number from the hash by a regex matching `#slide=` and gets
43 | * the number after it. If the number is invalid or less than 0, it will
44 | * return null as an invalid value.
45 | * @return {?number}
46 | */
47 | static getSlideNumber() {
48 | const results = document.location.hash.match(slideRegex);
49 | let slide = 0;
50 |
51 | if (Array.isArray(results)) {
52 | slide = parseInt(results[1], 10);
53 | }
54 |
55 | if (typeof slide !== 'number' || slide < 0 || !Array.isArray(results)) {
56 | slide = null;
57 | } else {
58 | slide--; // Convert to 0 index
59 | }
60 |
61 | return slide;
62 | }
63 |
64 | /**
65 | * It will update the hash (if it's different) so it reflects the slide
66 | * number being visible.
67 | * @param {number} number The number of the slide we're transitioning to.
68 | */
69 | static setSlideNumber(number) {
70 | if (Hash.getSlideNumber() !== (number - 1)) {
71 | history.pushState({
72 | slideI: number - 1
73 | }, `Slide ${number}`, `${HASH}=${number}`);
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/.sass-lint.yml:
--------------------------------------------------------------------------------
1 | files:
2 | include: 'src/scss/**/*.scss'
3 | options:
4 | formatter: stylish
5 | merge-default-rules: false
6 | rules:
7 | # Extends
8 | extends-before-mixins: 2
9 | extends-before-declarations: 2
10 | placeholder-in-extend: 2
11 |
12 | # Mixins
13 | mixins-before-declarations: 2
14 |
15 | # Line Spacing
16 | one-declaration-per-line: 2
17 | empty-line-between-blocks: 2
18 | single-line-per-selector: 2
19 |
20 | # Disallows
21 | no-attribute-selectors: 0
22 | no-color-hex: 0
23 | no-color-keywords: 2
24 | no-color-literals: 2
25 | no-combinators: 0
26 | no-debug: 2
27 | no-disallowed-properties: 0
28 | no-duplicate-properties: 2
29 | no-empty-rulesets: 2
30 | no-extends: 0
31 | no-ids: 0
32 | no-important: 2
33 | no-invalid-hex: 2
34 | no-mergeable-selectors: 2
35 | no-misspelled-properties: 2
36 | no-qualifying-elements: 0
37 | no-trailing-whitespace: 2
38 | no-trailing-zero: 2
39 | no-transition-all: 0
40 | no-universal-selectors: 0
41 | no-url-domains: 0
42 | no-url-protocols: 0
43 | no-vendor-prefixes: 2
44 | no-warn: 2
45 | property-units: 0
46 |
47 | # Nesting
48 | declarations-before-nesting: 2
49 | force-attribute-nesting: 0
50 | force-element-nesting: 0
51 | force-pseudo-nesting: 0
52 |
53 | # Name Formats
54 | class-name-format: 0
55 | function-name-format: 2
56 | id-name-format: 0
57 | mixin-name-format: 2
58 | placeholder-name-format: 2
59 | variable-name-format: 2
60 |
61 | # Style Guide
62 | attribute-quotes: 2
63 | bem-depth: 0
64 | border-zero: 2
65 | brace-style: 2
66 | clean-import-paths: 2
67 | empty-args: 2
68 | hex-length: 2
69 | hex-notation: 2
70 | indentation: 2
71 | leading-zero: 2
72 | max-line-length: 0
73 | max-file-line-count: 0
74 | nesting-depth: 2
75 | property-sort-order: 2
76 | pseudo-element: 0
77 | quotes: 2
78 | shorthand-values: 2
79 | url-quotes: 2
80 | variable-for-property: 0
81 | zero-unit: 2
82 |
83 | # Inner Spacing
84 | space-after-comma: 2
85 | space-before-colon: 2
86 | space-after-colon: 2
87 | space-before-brace: 2
88 | space-before-bang: 2
89 | space-after-bang: 2
90 | space-between-parens: 2
91 | space-around-operator: 2
92 |
93 | # Final Items
94 | trailing-semicolon: 2
95 | final-newline: 2
96 |
--------------------------------------------------------------------------------
/test/plugins/zoom.test.js:
--------------------------------------------------------------------------------
1 | import Zoom from '../../src/js/plugins/zoom';
2 | import DOM from '../../src/js/utils/dom';
3 |
4 | jest.useFakeTimers();
5 |
6 | // Copy of DOM.fireEvent, but using keydown
7 | const simulateKeyEvent = (el, code) => {
8 | const evt = new KeyboardEvent('keydown', {
9 | bubbles: true,
10 | cancelableCode: true,
11 | which: code,
12 | shiftKey: true});
13 | el.dispatchEvent(evt);
14 | };
15 |
16 | beforeAll(() => {
17 | const slides = '12345'.replace(/(\d)/g,
18 | '')
19 | .replace('section-1" class="', 'section-1" class="current ');
20 | document.body.innerHTML = `${slides}
`;
21 | });
22 |
23 | test('Zoom utility', () => {
24 | const ws = document.getElementById('webslides');
25 | const slides = ws.querySelectorAll('.slide');
26 | const goto = jest.fn();
27 | const enable = jest.fn();
28 | const disable = jest.fn();
29 |
30 | const webslides = {
31 | el: ws,
32 | slides: [],
33 | goToSlide: goto,
34 | enable: enable,
35 | disable: disable,
36 | options: {showIndex: true}
37 | };
38 | slides.forEach(slide => webslides.slides.push({el: slide}));
39 | webslides.currentSlide_ = webslides.slides[0];
40 |
41 | new Zoom(webslides);
42 |
43 | const zws = document.querySelector('#webslides-zoomed');
44 | const zoomSlides = zws.querySelectorAll('.slide');
45 |
46 | expect(zws).not.toBe(null);
47 | expect(zws.className).toMatch('disable');
48 | expect(slides.length).toBe(zoomSlides.length);
49 | expect(zws.childNodes[0].className).toBe('wrap');
50 | expect(zws.childNodes[0].childNodes[0].className).toBe('grid');
51 |
52 | simulateKeyEvent(document.body, 109);
53 |
54 | expect(zws.className).not.toMatch('disable');
55 |
56 |
57 | // Wait until next execution
58 | jest.runTimersToTime(600);
59 | expect(disable.mock.calls.length).toBe(1);
60 | expect(enable.mock.calls.length).toBe(0);
61 | expect(goto.mock.calls.length).toBe(0);
62 |
63 | DOM.fireEvent(zws.querySelector('.zoom-layer'), 'click');
64 |
65 | // Wait until next execution
66 | jest.runTimersToTime(401);
67 | expect(disable.mock.calls.length).toBe(1);
68 | expect(enable.mock.calls.length).toBe(1);
69 | expect(goto.mock.calls.length).toBe(1);
70 | });
71 |
--------------------------------------------------------------------------------
/src/js/plugins/keyboard.js:
--------------------------------------------------------------------------------
1 | import Keys from '../utils/keys';
2 | import DOM from '../utils/dom';
3 |
4 | /**
5 | * Keyboard interaction plugin.
6 | */
7 | export default class Keyboard {
8 | /**
9 | * @param {WebSlides} wsInstance The WebSlides instance
10 | * @constructor
11 | */
12 | constructor(wsInstance) {
13 | /**
14 | * @type {WebSlides}
15 | * @private
16 | */
17 | this.ws_ = wsInstance;
18 |
19 | document.addEventListener('keydown', this.onKeyPress_.bind(this), false);
20 | }
21 |
22 | /**
23 | * Reacts to the keydown event. It reacts to the arrows and space key
24 | * depending on the layout of the page.
25 | * @param {KeyboardEvent} event The key event.
26 | * @private
27 | */
28 | onKeyPress_(event) {
29 | let method;
30 | let argument;
31 |
32 | if (DOM.isFocusableElement() || this.ws_.isDisabled()) {
33 | return;
34 | }
35 |
36 | switch (event.which) {
37 | case Keys.AV_PAGE:
38 | method = this.ws_.goNext;
39 | break;
40 | case Keys.SPACE:
41 | if (event.shiftKey) {
42 | method = this.ws_.goPrev;
43 | } else {
44 | method = this.ws_.goNext;
45 | }
46 | break;
47 | case Keys.RE_PAGE:
48 | method = this.ws_.goPrev;
49 | break;
50 | case Keys.HOME:
51 | method = this.ws_.goToSlide;
52 | argument = 0;
53 | break;
54 | case Keys.END:
55 | method = this.ws_.goToSlide;
56 | argument = this.ws_.maxSlide_ - 1;
57 | break;
58 | case Keys.DOWN:
59 | method = this.ws_.isVertical ? this.ws_.goNext : null;
60 | break;
61 | case Keys.UP:
62 | method = this.ws_.isVertical ? this.ws_.goPrev : null;
63 | break;
64 | case Keys.LEFT:
65 | method = !this.ws_.isVertical ? this.ws_.goPrev : null;
66 | break;
67 | case Keys.RIGHT:
68 | method = !this.ws_.isVertical ? this.ws_.goNext : null;
69 | break;
70 | case Keys.F:
71 | if (!event.metaKey && ! event.ctrlKey) {
72 | method = this.ws_.fullscreen;
73 | }
74 |
75 | break;
76 | }
77 |
78 | if (method) {
79 | method.call(this.ws_, argument);
80 | // Prevents Firefox key events.
81 | event.preventDefault();
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/static/images/logos/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
32 |
--------------------------------------------------------------------------------
/src/scss/modules/_slides.scss:
--------------------------------------------------------------------------------
1 | /*============================================
2 | 5. SLIDES (Full Screen)
3 | Vertically and horizontally centered
4 | ============================================== */
5 |
6 | /* Fade transition to all slides.
7 | * = All HTML elements will have those styles.*/
8 |
9 | section * {
10 | animation: fadeIn .6s ease-in-out;
11 | }
12 |
13 | section .background,
14 | section .light,
15 | section .dark {
16 | animation-duration: 0s;
17 | }
18 |
19 | /*=== Section = Slide === */
20 | section,
21 | .slide {
22 | display: flex;
23 | flex-direction: column;
24 | justify-content: center;
25 | min-height: 100vh; /*Fullscreen*/
26 | /* Prototyping? min-height: 720px (Baseline: 8px = .8rem)*/
27 | padding: 2.4rem;
28 | /*Fixed/Visible header? padding-top: 12rem; */
29 | page-break-after: always;
30 | position: relative;
31 | word-wrap: break-word;
32 |
33 | @media (min-width: 1024px) {
34 | padding-bottom: 12rem;
35 | padding-top: 12rem;
36 | }
37 | }
38 |
39 | /*slide with no padding (full card, .embed youtube video...) */
40 | .fullscreen {
41 | padding: 0;
42 | /* Fixed/Visible header?
43 | padding:8.2rem 0 0 0;
44 | */
45 | }
46 |
47 | /* slide alignment - top */
48 | .slide-top {
49 | justify-content: flex-start;
50 | }
51 |
52 | /* slide alignment - bottom */
53 | .slide-bottom {
54 | justify-content: flex-end;
55 | }
56 |
57 | /*== 5.1. Mini container width:50%
58 | Aligned items [class*="content-"]=== */
59 | [class*='content-'] {
60 | position: relative;
61 | text-align: left;
62 | }
63 |
64 | .wrap[class*='bg-'],
65 | .wrap.frame,
66 | [class*='content-'][class*='bg-'],
67 | [class*='content-'].frame,
68 | [class*='align'][class*='bg-'] {
69 | padding: 4.8rem;
70 | }
71 |
72 | form[class*='bg-'] {
73 | padding: 2.4rem;
74 | }
75 |
76 | [class*='content-'] > [class*='content-'] p {
77 | font-size: 1.8rem;
78 | line-height: 3.2rem;
79 | }
80 |
81 | .content-center {
82 | margin: 0 auto;
83 | text-align: center;
84 | }
85 |
86 | @media (min-width: 768px) {
87 | [class*='content-'] {
88 | width: 50%;
89 |
90 | &:after,
91 | &:before {
92 | content: '';
93 | display: table;
94 | }
95 |
96 | &:after {
97 | clear: both;
98 | }
99 | }
100 |
101 | .content-left {
102 | float: left;
103 | }
104 |
105 | .content-right {
106 | float: right;
107 | }
108 |
109 | [class*='content-'] + [class*='content-'] {
110 | margin-bottom: 4.8rem;
111 | padding-left: 2.4rem;
112 | }
113 |
114 | [class*='content-'] + [class*='size-'] {
115 | clear: both;
116 | margin-top: 6.4rem;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/scss/full.scss:
--------------------------------------------------------------------------------
1 | /*-----------------------------------------------------------------------------------
2 | 0. CSS Reset & Normalize
3 | 1. Base
4 | 1.1 Wrap/Container
5 | 1.2 Animations
6 | 1.3 Responsive Media (videos, iframe, screenshots...)
7 | 1.4 Basic Grid (2,3,4 columns)
8 | 2. Typography & Lists
9 | 2.1 Headings with background
10 | 2.2 Classes: .text-
11 | 2.3 San Francisco Font (Apple)
12 | 3. Header & Footer
13 | 3.1 Logo
14 | 4. Navigation
15 | 4.1 Navbars
16 | 5. SLIDES (vertically and horizontally centered)
17 | 5.1 Mini container & Alignment
18 | 5.2 Counter / Navigation Slides
19 | 5.3 Background Images/Video
20 | 6. Magic blocks = .flexblock (Flexible blocks with auto-fill and equal height).
21 | 6.1 .flexblock.features
22 | 6.2 .flexblock.clients
23 | 6.3 .flexblock.steps
24 | 6.4 .flexblock.metrics
25 | 6.5 .flexblock.specs
26 | 6.6 .flexblock.reasons
27 | 6.7 .flexblock.gallery
28 | 6.8 .flexblock.plans
29 | 6.9. flexblock.activity
30 | 7. Promos/Offers (pricing, tagline, CTA...)
31 | 8. Work / Resume / CV
32 | 9. Table of contents
33 | 10. Cards
34 | 11. Quotes
35 | 12. Avatars
36 | 13. Tables
37 | 14. Forms
38 | 15. Longform Elements
39 | 16. Safari Bug (flex-wrap)
40 | 17. Slidex index (aka zoom)
41 | 18. Print
42 | 19. Colors
43 | ----------------------------------------------------------------------------------- */
44 |
45 | @import 'vars';
46 | @import 'utils/mixins';
47 | @import 'utils/reset';
48 | @import 'utils/clear';
49 |
50 | @import 'base';
51 | @import 'utils/animations';
52 | @import 'modules/media';
53 | @import 'modules/browser';
54 | @import 'modules/grid';
55 |
56 | @import 'typography';
57 |
58 | @import 'modules/header-footer';
59 | @import 'modules/logo';
60 | @import 'modules/navigation';
61 |
62 | @import 'modules/slides';
63 | @import 'modules/slides-bg';
64 | @import 'modules/slides-navigation';
65 |
66 | @import 'modules/flexblock';
67 | @import 'modules/flexblock-features';
68 | @import 'modules/flexblock-clients';
69 | @import 'modules/flexblock-steps';
70 | @import 'modules/flexblock-metrics';
71 | @import 'modules/flexblock-specs';
72 | @import 'modules/flexblock-reasons';
73 | @import 'modules/flexblock-gallery';
74 | @import 'modules/flexblock-plans';
75 | @import 'modules/flexblock-activity';
76 | @import 'modules/promos';
77 | @import 'modules/work';
78 | @import 'modules/toc';
79 |
80 | @import 'modules/cards';
81 |
82 | @import 'modules/quotes';
83 | @import 'modules/avatars';
84 |
85 | @import 'modules/tables';
86 |
87 | @import 'modules/form';
88 | @import 'modules/button';
89 | @import 'modules/badges';
90 |
91 | @import 'modules/longform';
92 |
93 | @import 'utils/bugs';
94 | @import 'modules/zoom';
95 |
96 | @import 'modules/print';
97 |
98 | @import 'color';
99 |
--------------------------------------------------------------------------------
/src/scss/modules/_slides-bg.scss:
--------------------------------------------------------------------------------
1 | /*=== 5.3 Slides - Background Images/Videos === */
2 |
3 | .background,
4 | [class*='background-'] {
5 | background-repeat: no-repeat;
6 | bottom: 0;
7 | left: 0;
8 | position: absolute;
9 | right: 0;
10 | top: 0;
11 | }
12 |
13 | /*=== BG Positions === */
14 |
15 | .background {
16 | background-position: center;
17 | background-size: cover;
18 |
19 | &-top {
20 | background-position: top;
21 | background-size: cover;
22 | }
23 |
24 | &-bottom {
25 | background-position: bottom;
26 | background-size: cover;
27 | }
28 |
29 | &-center {
30 | background-position: center;
31 | }
32 |
33 | &-center-top {
34 | background-position: center top;
35 | }
36 |
37 | &-right-top {
38 | background-position: right top;
39 | }
40 |
41 | &-left-top {
42 | background-position: left top;
43 | }
44 |
45 | &-center-bottom,
46 | &-left-bottom,
47 | &-right-bottom,
48 | &-left,
49 | &-right {
50 | background-position: center bottom;
51 | }
52 |
53 | @media (min-width: 1024px) {
54 | &-left-bottom {
55 | background-position: left bottom;
56 | }
57 |
58 | &-right-bottom {
59 | background-position: right bottom;
60 | }
61 |
62 | &-right {
63 | background-position: right;
64 | }
65 |
66 | &-left {
67 | background-position: left;
68 | }
69 | }
70 |
71 | /*fullscreen video
72 |