Bläddra i källkod

feat(docs): Add intro animation to landing page

Michael Bromley 7 år sedan
förälder
incheckning
26c1d0226c

+ 2 - 0
.gitignore

@@ -396,6 +396,8 @@ server/src/email/preview/output
 docs/resources/_gen/*
 docs/static/main.js*
 docs/static/main.css*
+docs/static/intro.js*
+docs/static/intro.css*
 docs/public
 docs/content/docs/configuration/*
 !docs/content/docs/configuration/_index.md

+ 74 - 0
docs/assets/scripts/intro/intro.ts

@@ -0,0 +1,74 @@
+import { Sequencer } from './sequencer';
+import { TerminalTyper } from './terminal-typer';
+
+// tslint:disable-next-line
+require('../../styles/intro/intro.scss');
+
+const textArea = document.querySelector('.vendure-intro .intro-text-area');
+const scene = document.querySelector('.vendure-intro .scene');
+const controls = document.querySelector('.vendure-intro .intro-controls');
+const title = document.querySelector('.vendure-intro .intro-title');
+
+if (textArea && scene && controls && title) {
+    const replayButton = controls.querySelector('#replay');
+    const terminalCommands = `$ install @vendure/core\n` +
+        `$ vendure init\n` +
+        `$ start\n`;
+    const terminal = new TerminalTyper(textArea as HTMLDivElement, terminalCommands);
+    const onTransition = (className: string) => {
+        controls.querySelectorAll('button').forEach(button => button.classList.remove('active'));
+        const active = controls.querySelector(`#${className}-button`);
+        if (active) {
+            active.classList.add('active');
+        }
+        if (className === 'scene-6') {
+            title.classList.add('visible');
+            if (replayButton) {
+                replayButton.classList.add('visible');
+            }
+        }
+    };
+    const sequencer = new Sequencer(scene as HTMLDivElement, terminal, onTransition);
+
+    sequencer.play();
+
+
+    if (replayButton) {
+        replayButton.addEventListener('click', () => sequencer.play());
+    }
+
+    controls.addEventListener('click', event => {
+        const target = event.target as HTMLButtonElement;
+        const command = target.id;
+        if (command) {
+            switch (command) {
+                case 'replay':
+                    sequencer.play();
+                    break;
+                case 'scene-0-button':
+                    sequencer.jumpTo(0);
+                    break;
+                case 'scene-1-button':
+                    sequencer.jumpTo(1);
+                    break;
+                case 'scene-2-button':
+                    sequencer.jumpTo(2);
+                    break;
+                case 'scene-3-button':
+                    sequencer.jumpTo(3);
+                    break;
+                case 'scene-4-button':
+                    sequencer.jumpTo(4);
+                    break;
+                case 'scene-5-button':
+                    sequencer.jumpTo(5);
+                    break;
+                case 'scene-6-button':
+                    sequencer.jumpTo(6);
+                    break;
+                default:
+                    sequencer.jumpTo(0);
+            }
+        }
+    });
+}

+ 87 - 0
docs/assets/scripts/intro/sequencer.ts

@@ -0,0 +1,87 @@
+import { TerminalTyper } from './terminal-typer';
+
+export class Sequencer {
+
+    private readonly playTimer: { [name: string]: number; };
+    private readonly onTransition?: (className: string) => void;
+
+    constructor(private sceneElement: HTMLDivElement,
+                private terminal: TerminalTyper,
+                onTransitionFn?: (className: string) => void) {
+        this.sceneElement = sceneElement;
+        this.playTimer = {};
+        this.onTransition = onTransitionFn;
+    }
+
+    async play() {
+        Object.values(this.playTimer).forEach(k => clearTimeout(k));
+        await this.sleep('initial', 0);
+        this.scene0();
+        await this.sleep('start-typing', 2000);
+        this.scene1();
+        await this.sleep('zoom-out', 2500);
+        this.scene2();
+        await this.sleep('data-flow', 1200);
+        this.scene3();
+        await this.sleep('websites', 1000);
+        this.scene4();
+        await this.sleep('logo', 3000);
+        this.scene5();
+        await this.sleep('final', 1000);
+        this.scene6();
+    }
+
+    jumpTo(scene: 0 | 1 | 2 | 3 | 4 | 5 | 6) {
+        Object.values(this.playTimer).forEach(k => clearTimeout(k));
+        (this as any)['scene' + scene]();
+    }
+
+    private scene0() {
+        this.setScene('scene-0');
+        this.terminal.clear();
+    }
+
+    private scene1() {
+        this.setScene('scene-1');
+        this.terminal.start();
+    }
+
+    private scene2() {
+        this.setScene('scene-2');
+        this.terminal.fill();
+    }
+
+    private scene3() {
+        this.setScene('scene-3');
+        this.terminal.fill();
+    }
+
+    private scene4() {
+        this.setScene('scene-4');
+        this.terminal.fill();
+    }
+
+    private scene5() {
+        this.setScene('scene-5');
+        this.terminal.fill();
+    }
+
+    private scene6() {
+        this.setScene('scene-6');
+        this.terminal.fill();
+    }
+
+    private setScene(className: string) {
+        this.sceneElement.classList.value = `visible scene ${className}`;
+        if (typeof this.onTransition === 'function') {
+            this.onTransition(className);
+        }
+    }
+
+    private sleep(id: string, duration: number) {
+        clearTimeout(this.playTimer[id]);
+        return new Promise(resolve => {
+            this.playTimer[id] = setTimeout(resolve, duration);
+        });
+    }
+}

+ 51 - 0
docs/assets/scripts/intro/terminal-typer.ts

@@ -0,0 +1,51 @@
+export class TerminalTyper {
+    private lines: string[];
+    private timer: number;
+
+    constructor(private terminalElement: HTMLElement, commands: string) {
+        this.terminalElement = terminalElement;
+        this.lines = commands.split('\n');
+    }
+
+    start() {
+        this.clear();
+        const terminalEl = this.terminalElement;
+        let pos = 0;
+        let currLine = 0;
+        const type = () => {
+            if (pos < this.lines[currLine].length) {
+                const char = this.lines[currLine][pos];
+                terminalEl.innerHTML = terminalEl.innerHTML + char;
+                pos++;
+            } else {
+                terminalEl.innerHTML = terminalEl.innerHTML + '<br>';
+                currLine++;
+                pos = 0;
+            }
+            if (this.lines[currLine] && pos <= this.lines[currLine].length) {
+                this.timer = window.setTimeout(type, pos === 0 ? 400 : 20);
+            }
+        };
+        type();
+    }
+
+    clear() {
+        window.clearTimeout(this.timer);
+        this.terminalElement.innerHTML = '';
+    }
+
+    fill() {
+        this.clear();
+        const terminalEl = this.terminalElement;
+        for (let currLine = 0; currLine < this.lines.length; currLine++) {
+            // tslint:disable-next-line
+            for (let pos = 0; pos < this.lines[currLine].length; pos++) {
+                const char = this.lines[currLine][pos];
+                terminalEl.innerHTML = terminalEl.innerHTML + char;
+            }
+            if (currLine < this.lines.length - 1) {
+                terminalEl.innerHTML = terminalEl.innerHTML + '<br>';
+            }
+        }
+    }
+}

+ 6 - 4
docs/assets/scripts/main.ts

@@ -23,10 +23,12 @@ document.addEventListener('DOMContentLoaded', () => {
     const tocHighlighter = new TocHighlighter(toc);
     tocHighlighter.highlight();
 
-    const searchInput = document.querySelector('#searchInput') as HTMLInputElement;
-    const searchWidget = new SearchWidget(searchInput);
-    const searchButton = document.querySelector('button.search-icon') as HTMLButtonElement;
-    searchButton.addEventListener('click', () => searchWidget.toggleActive());
+    const searchInput = document.querySelector('#searchInput');
+    if (searchInput) {
+        const searchWidget = new SearchWidget(searchInput as HTMLInputElement);
+        const searchButton = document.querySelector('button.search-icon') as HTMLButtonElement;
+        searchButton.addEventListener('click', () => searchWidget.toggleActive());
+    }
 
     initTabs();
 

+ 0 - 38
docs/assets/styles/_landing-page.scss

@@ -1,43 +1,5 @@
 @import "variables";
 
-.hero a:link, .hero a:visited {
-    color: #d4f5ff;
-    text-decoration: none;
-}
-.hero a:hover {
-    color: #fafbff;
-}
-.hero {
-    padding: 10px;
-    text-align: center;
-    background: #1c1c1c;
-    background-image: url("/header-bg.png");
-    background-attachment: fixed;
-}
-.logo {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    img {
-        width: 100%;
-        max-width: 100px;
-        height: 100%;
-        margin-right: 30px;
-    }
-    h1 {
-        margin: 0;
-        color: $brand-color;
-        font-size: 3em;
-        margin-bottom: 15px;
-        font-family: 'Didact Gothic', sans-serif;
-    }
-}
-.subhead {
-    font-size: 1em;
-    color: #74aec3;
-    max-width: 800px;
-    margin: auto;
-}
 .top-header {
     display: flex;
     align-items: center;

+ 20 - 0
docs/assets/styles/_top-bar.scss

@@ -10,6 +10,7 @@
     display: flex;
     align-items: center;
     transition: background-color 0.7s;
+    z-index: 10;
 
     @media all and (max-width: $sm-breakpoint){
         padding: 12px;
@@ -17,9 +18,28 @@
 
     &.landing-page {
         background-color: transparent;
+        .right {
+            a:link, a:visited {
+                color: $gray-700;
+            }
+            a:hover {
+                color: $gray-500;
+                text-decoration: none;
+            }
+        }
 
         &.floating {
             background-color: #1e1e1e;
+
+            .right {
+                a:link, a:visited {
+                    color: $gray-400;
+                }
+                a:hover {
+                    color: $gray-100;
+                    text-decoration: none;
+                }
+            }
         }
     }
 

+ 64 - 0
docs/assets/styles/intro/_admin-ui.scss

@@ -0,0 +1,64 @@
+@import "variables";
+
+.intro-admin-ui {
+    display: flex;
+    flex-direction: column;
+    padding: calc(0.05 * var(--width-unit));
+    width: calc(2 * var(--width-unit));
+    height: calc(2 * var(--width-unit));
+    transition: opacity $transition-duration * 2;
+    .intro-top-bar {
+        background-color: #494949;
+        color: #999;
+        height: calc(0.3 * var(--width-unit));
+        font-size: calc(0.2 * var(--width-unit));
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        text-transform: uppercase;
+        user-select: none;
+    }
+    .intro-content {
+        background-color: #fff;
+        flex: 1;
+        display: flex;
+
+        .intro-nav {
+            background-color: #ccc;
+            width: calc(0.3 * var(--width-unit));
+            padding: calc(0.05 * var(--width-unit));
+            margin: 0;
+            list-style-type: none;
+            li {
+                position: relative;
+                background-color: #999a9e;
+                height: calc(0.05 * var(--width-unit));
+                width: calc(0.15 * var(--width-unit));
+                margin-bottom: calc(0.08 * var(--width-unit));
+                border-radius: calc(0.01 * var(--width-unit));
+            }
+        }
+        .intro-list-view {
+            flex: 1;
+            padding: calc(0.05 * var(--width-unit));
+            margin: calc(0.05 * var(--width-unit));
+            list-style-type: none;
+            li {
+                position: relative;
+                background-color: #dedede;
+                height: calc(0.05 * var(--width-unit));
+                margin-bottom: calc(0.08 * var(--width-unit));
+                padding-left: calc(0.08 * var(--width-unit));
+                &:before {
+                    position: absolute;
+                    left: 0;
+                    content: '';
+                    background-color: #87a8dd;
+                    padding: 0;
+                    width: calc(0.1 * var(--width-unit));
+                    height: calc(0.05 * var(--width-unit));
+                }
+            }
+        }
+    }
+}

+ 119 - 0
docs/assets/styles/intro/_cube.scss

@@ -0,0 +1,119 @@
+@import "variables";
+
+
+.cube {
+    width: calc(2 * var(--width-unit));
+    height: calc(2 * var(--width-unit));
+    transform-style: preserve-3d;
+    transition: transform $transition-duration, margin $transition-duration, width $transition-duration, height $transition-duration;
+}
+
+.cube1 {
+    transform: translateZ(calc(-1.75 * var(--width-unit)))
+     translateX(calc(.25 * var(--width-unit))) 
+     rotateY(45deg) ;
+} 
+
+.cube2 {
+    transform: rotateY(45deg);
+}
+
+.cube3 {
+    transform: translateZ(calc(-1.75 * var(--width-unit)))
+     translateX(calc(-.25 * var(--width-unit))) 
+     rotateY(45deg);
+}
+
+
+.cube__face--top {
+    background: var(--color-top);
+    border: var(--front-border-width) var(--border-style) var(--border-color) !important;
+}
+
+.cube1 .cube__face--left {
+    background-color: var(--color-left);
+    border: var(--front-border-width) var(--border-style) var(--border-color);
+}
+
+.cube1 .cube__face--front {
+    background-color: var(--color-shadow);
+    border: var(--front-border-width) var(--border-style) var(--border-color);
+}
+
+.cube2 .cube__face--left {
+    background-color: var(--color-left);
+    border: var(--front-border-width) var(--border-style) var(--border-color);
+}
+
+.cube2 .cube__face--front {
+    background-color: var(--color-right);
+    border: var(--front-border-width) var(--border-style) var(--border-color);
+}
+
+.cube3 .cube__face--front {
+    background-color: var(--color-right);
+    border: var(--front-border-width) var(--border-style) var(--border-color);
+}
+
+.cube3 .cube__face--left {
+    background-color: var(--color-shadow);
+    border: var(--front-border-width) var(--border-style) var(--border-color);
+}
+
+.cube {
+    &.show-front {
+        transform: translateZ(calc(-1 * var(--width-unit))) rotateY( 0deg);
+    }
+
+    &.show-right {
+        transform: translateZ(calc(-1 * var(--width-unit))) rotateY( -90deg);
+    }
+
+    &.show-back {
+        transform: translateZ(calc(-1 * var(--width-unit))) rotateY(-180deg);
+    }
+
+    &.show-left {
+        transform: translateZ(calc(-1 * var(--width-unit))) rotateY( 90deg);
+    }
+
+    &.show-top {
+        transform: translateZ(calc(-1 * var(--width-unit))) rotateX( -90deg);
+    }
+
+    &.show-bottom {
+        transform: translateZ(calc(-1 * var(--width-unit))) rotateX( 90deg);
+    }
+}
+
+.cube__face {
+    position: absolute;
+    width: calc(2 * var(--width-unit));
+    height: calc(2 * var(--width-unit));
+    border: var(--border-width) var(--border-style) var(--border-color);
+    transition: background-color $transition-duration;
+    --projection: 1;
+    &.cube__face--front {
+        transform: rotateY( 0deg) translateZ(calc(var(--projection) * var(--width-unit)));
+    }
+    
+    &.cube__face--right { 
+        transform: rotateY( 90deg) translateZ(calc(var(--projection) * var(--width-unit)));
+    }
+    
+    &.cube__face--back {
+        transform: rotateY(180deg) translateZ(calc(var(--projection) * var(--width-unit)));
+    }
+    
+    &.cube__face--left {
+        transform: rotateY(-90deg) translateZ(calc(var(--projection) * var(--width-unit)));
+    }
+    
+    &.cube__face--top { 
+        transform: rotateX( 90deg) translateZ(calc(var(--projection) * var(--width-unit)));
+    }
+    
+    &.cube__face--bottom {
+        transform: rotateX(-90deg) translateZ(calc(var(--projection) * var(--width-unit)));
+    }
+}

+ 42 - 0
docs/assets/styles/intro/_data-flow.scss

@@ -0,0 +1,42 @@
+@import "variables";
+
+.data-flow-left, .data-flow-right {
+    background-color: $brand-color;
+    width: 0;
+    height: calc(0.05 * var(--width-unit));
+    position: absolute;
+    top: calc(0.95 * var(--width-unit));
+    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAGklEQVQYV2MU3v75PwMDA8NbT15GEM1IugAAWIwQBUszJIoAAAAASUVORK5CYII=) repeat;
+}
+.data-flow-left {
+    left: 0;
+    animation: flowLeft;
+    animation-duration: 0.3s;
+    animation-timing-function: linear;
+    animation-iteration-count: infinite;
+}
+.data-flow-right {
+    right: 0;
+    animation: flowRight;
+    animation-duration: 0.3s;
+    animation-timing-function: linear;
+    animation-iteration-count: infinite;
+}
+
+@keyframes flowLeft {
+    0% {
+        background-position-x: 0;
+    }
+    100% {
+        background-position-x: -4px;
+    }
+}
+
+@keyframes flowRight {
+    0% {
+        background-position-x: 0;
+    }
+    100% {
+        background-position-x: 4px;
+    }
+}

+ 68 - 0
docs/assets/styles/intro/_storefront.scss

@@ -0,0 +1,68 @@
+@import "variables";
+
+.vendure-intro {
+    .intro-storefront {
+        display: flex;
+        flex-direction: column;
+        padding: calc(0.05 * var(--width-unit));
+        width: calc(2 * var(--width-unit));
+        height: calc(2 * var(--width-unit));
+        transition: opacity $transition-duration;
+
+        .intro-top-bar {
+            background-color: #494949;
+            color: #999;
+            height: calc(0.3 * var(--width-unit));
+            font-size: calc(0.2 * var(--width-unit));
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            text-transform: uppercase;
+            user-select: none;
+        }
+
+        .intro-content {
+            background-color: #fff;
+            flex: 1;
+            margin: 0;
+            padding: 0;
+            list-style-type: none;
+            display: grid;
+            grid-template-columns: 1fr 1fr 1fr;
+            grid-template-rows: 1fr 1fr;
+            grid-gap: calc(0.05 * var(--width-unit));
+            padding: calc(0.05 * var(--width-unit));
+
+            li {
+                position: relative;
+                background-color: #c0c1c7;
+                height: 100%;
+                width: 100%;
+                margin-bottom: calc(0.08 * var(--width-unit));
+                border-radius: calc(0.01 * var(--width-unit));
+
+                &:before {
+                    content: '';
+                    position: absolute;
+                    background-color: #e2e4ee;
+                    background: linear-gradient(to bottom, rgba(238, 238, 238, 1) 0%, rgb(231, 228, 228) 100%);
+                    width: calc(0.5 * var(--width-unit));
+                    height: calc(0.5 * var(--width-unit));
+                    top: calc(0.03 * var(--width-unit));
+                    left: calc(0.03 * var(--width-unit));
+                }
+
+                &:after {
+                    content: '';
+                    position: absolute;
+                    background-color: #2a82bea1;
+                    width: calc(0.2 * var(--width-unit));
+                    height: calc(0.1 * var(--width-unit));
+                    bottom: calc(0.05 * var(--width-unit));
+                    right: calc(0.03 * var(--width-unit));
+                    border-radius: calc(0.01 * var(--width-unit));
+                }
+            }
+        }
+    }
+}

+ 72 - 0
docs/assets/styles/intro/_terminal.scss

@@ -0,0 +1,72 @@
+@import "variables";
+
+$terminal-easing: cubic-bezier(.17,.67,.26,1.54);
+
+.intro-terminal {
+    position: absolute;
+    color: #e5e5e5;
+    display: flex;
+    flex-direction: column;
+    background-color: $terminal-color;
+    font-family: "Courier New", Courier, monospace;
+    font-size: calc(0.1 * var(--width-unit));
+    width: calc(2 * var(--width-unit));
+    height: calc(2 * var(--width-unit));
+    top: 0;
+    left: 0;
+    transition: all $transition-duration;
+    transition-timing-function: $terminal-easing;
+    border-radius: calc(0.03 * var(--width-unit));
+}
+
+@mixin dot($color) {
+    width: calc(0.07 * var(--width-unit));
+    height: calc(0.07 * var(--width-unit));
+    background-color: $color;
+    border-radius: 50%;
+}
+
+.intro-terminal-controls {
+    position: relative;
+    margin-left: calc(0.15 * var(--width-unit));
+    margin-top: calc(0.05 * var(--width-unit));
+    @include dot(#ffbd2e);
+    transition: margin $transition-duration, width $transition-duration, height $transition-duration;
+    transition-timing-function: $terminal-easing;
+    &:before {
+        position: absolute;
+        left: calc(-0.10 * var(--width-unit));
+        content: '';
+        @include dot(#ff5f56);
+        transition: width $transition-duration, height $transition-duration, left $transition-duration;
+        transition-timing-function: $terminal-easing;
+    }
+    &:after {
+        position: absolute;
+        left: calc(0.10 * var(--width-unit));
+        content: '';
+        @include dot(#27c93f);
+        transition: width $transition-duration, height $transition-duration, left $transition-duration;
+        transition-timing-function: $terminal-easing;
+    }
+}
+
+.intro-text-area {
+    padding: calc(0.2 * var(--width-unit)) calc(0.09 * var(--width-unit));
+    padding-bottom: 0;
+    transition: padding $transition-duration;
+    transition-timing-function: $terminal-easing;
+}
+
+.intro-gql-logo {
+    display: flex;
+    justify-content: center;
+   
+    img {
+        width: calc(1 * var(--width-unit));
+        height: calc(1 * var(--width-unit));
+        transform: translateY(calc(-0.2 * var(--width-unit)));
+        transition: transform 1s ease-out;
+    }
+    opacity: 0;
+}

+ 4 - 0
docs/assets/styles/intro/_variables.scss

@@ -0,0 +1,4 @@
+$transition-duration: 0.5s;
+$terminal-color: #333;
+$brand-color: #13b7f3;
+$shadow: 0px 6px 15px -2px rgba(50, 50, 50, 0.7);

+ 476 - 0
docs/assets/styles/intro/intro.scss

@@ -0,0 +1,476 @@
+@import "../variables";
+@import "variables";
+@import "terminal";
+@import "storefront";
+@import "admin-ui";
+@import "cube";
+@import "data-flow";
+
+.vendure-intro {
+    --color-top: #17c1ff;
+    --color-left: #30c6fd;
+    --color-right: #13b7f3;
+    --color-shadow: rgba(94, 94, 94, 0);
+    --width-unit: 10vw;
+    --border-width: calc(.0 * var(--width-unit));
+    --border-style: dashed;
+    --front-border-width: 0;
+    --border-color: rgb(95, 95, 95);
+
+    margin: 0;
+    box-sizing: border-box;
+    height: 100vh;
+    padding-top: 10vh;
+    @media all and (max-width: $sm-breakpoint) {
+        height: initial;
+    }
+}
+
+.scene {
+    perspective: calc(10 * var(--width-unit));
+    transform-style: preserve-3d;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 50vw;
+    transform: rotateX(-45deg);
+    transition: transform $transition-duration, perspective $transition-duration, opacity 0.2s 0.5s;
+    font-family: sans-serif;
+    opacity: 0;
+
+    &.visible {
+        opacity: 1;
+    }
+
+    @media all and (min-width: 1200px) {
+        --width-unit: 10vh;
+        height: 50vh;
+    }
+
+}
+
+.intro-title {
+    text-align: center;
+    padding: 0 16px;
+    max-width: 1200px;
+    margin: auto;
+    opacity: 0;
+    transform: translateY(-20px);
+    visibility: hidden;
+    transition: visibility 0s, opacity 2s, transform 2.5s ease-out;
+
+    &.visible {
+        opacity: 1;
+        transform: translateY(0);
+        visibility: visible;
+    }
+
+    a:link, a:visited {
+        color: $color-link;
+        text-decoration: none;
+    }
+
+    a:hover {
+        color: $color-link;
+    }
+
+    h1 {
+        color: $brand-color;
+        font-size: calc(1.1 * var(--width-unit));
+        margin: 0 0 15px;
+        font-family: 'Didact Gothic', sans-serif;
+    }
+
+    .subhead {
+        font-size: calc(0.35 * var(--width-unit));
+        color: desaturate($brand-color, 50%);
+        margin: auto;
+    }
+    @media all and (min-width: 1200px) {
+        h1 {
+            font-size: 7em;
+        }
+        .subhead {
+            font-size: 2.5em;
+        }
+    }
+    @media all and (max-width: $sm-breakpoint) {
+        h1 {
+            font-size: 4em;
+        }
+        .subhead {
+            font-size: 1.5em;
+        }
+    }
+}
+
+.intro-controls {
+    z-index: 1;
+    text-align: center;
+
+    .jump {
+
+        button {
+            border-radius: 50%;
+            border: 1px;
+            padding: 0;
+            background-color: $gray-100;
+            width: 16px;
+            height: 16px;
+            transition: background-color 0.8s;
+            &.active {
+                background-color: $gray-200;
+            }
+        }
+    }
+
+    button#replay {
+        border: none;
+        background: none;
+        opacity: 0;
+        transition: opacity 4s;
+        &.visible {
+            opacity: 0.3;
+        }
+    }
+}
+
+/*
+ * Makes the cubes aligned in a row
+ */
+@mixin aligned {
+    &.scene {
+        transform: rotateX(0);
+    }
+    .cube {
+        transform: rotateY(0);
+        margin: calc(0.5 * var(--width-unit));
+    }
+    .cube__face {
+        background-color: transparent;
+    }
+    .cube__face--front {
+        background-color: $terminal-color;
+        box-shadow: $shadow;
+    }
+}
+
+@mixin focus-terminal {
+    .cube1, .cube3 {
+        max-width: 0px;
+        .cube__face {
+            background-color: transparent;
+            box-shadow: 0px 6px 15px -2px transparent;
+        }
+    }
+    .cube2 {
+        .cube__face {
+            background-color: transparent;
+            box-shadow: 0px 6px 15px -2px transparent;
+        }
+    }
+    .intro-terminal {
+        font-size: calc(0.24 * var(--width-unit));
+        width: calc(4 * var(--width-unit));
+        height: calc(4 * var(--width-unit));
+        top: calc(-1 * var(--width-unit));
+        left: calc(-1 * var(--width-unit));
+        box-shadow: $shadow;
+    }
+    .intro-text-area {
+        padding: calc(0.4 * var(--width-unit)) calc(0.18 * var(--width-unit));
+    }
+    .intro-terminal-controls {
+        position: relative;
+        margin-left: calc(0.21 * var(--width-unit));
+        margin-top: calc(0.07 * var(--width-unit));
+        width: calc(0.1 * var(--width-unit));
+        height: calc(0.1 * var(--width-unit));
+        &:before {
+            left: calc(-0.15 * var(--width-unit));
+            width: calc(0.1 * var(--width-unit));
+            height: calc(0.1 * var(--width-unit));
+        }
+        &:after {
+            left: calc(0.15 * var(--width-unit));
+            width: calc(0.1 * var(--width-unit));
+            height: calc(0.1 * var(--width-unit));
+        }
+    }
+}
+
+@mixin dataFlowing {
+    .data-flow-left {
+        width: calc(1 * var(--width-unit));
+        left: calc(-1 * var(--width-unit));
+        transition: width 0.5s 0.3s, left 0.5s 0.3s;
+    }
+    .data-flow-right {
+        width: calc(1 * var(--width-unit));
+        right: calc(-1 * var(--width-unit));
+        transition: width 0.5s 0.6s, right 0.5s 0.6s;
+    }
+    .cube1 {
+        .cube__face--front {
+            background-color: $brand-color;
+            transition: background-color 0.3s 0.8s;
+        }
+    }
+    .cube3 {
+        .cube__face--front {
+            background-color: $brand-color;
+            transition: background-color 0.3s 1.1s;
+        }
+    }
+    .intro-gql-logo {
+        opacity: 1;
+        transition: opacity 0.6s;
+        img {
+            transform: translateY(0);
+        }
+    }
+}
+
+.scene-0 {
+    @include aligned;
+    @include focus-terminal;
+    .intro-admin-ui, .intro-storefront {
+        opacity: 0;
+    }
+}
+
+.scene-1 {
+    @include aligned;
+    @include focus-terminal;
+    .intro-admin-ui, .intro-storefront {
+        opacity: 0;
+    }
+}
+
+.scene-2 {
+    @include aligned;
+    .intro-admin-ui, .intro-storefront {
+        opacity: 0;
+    }
+    .cube__face--front {
+        background-color: $terminal-color;
+    }
+    .cube1 {
+        .cube__face {
+            transition: background-color 0.8s 0s, box-shadow 0.5s 0s;
+        }
+    }
+    .cube2 {
+        .cube__face {
+            transition: background-color 0.8s 1s, box-shadow 0.5s 0.4s;
+        }
+    }
+    .cube3 {
+        .cube__face {
+            transition: background-color 0.8s 0.3s, box-shadow 0.5s 0.3s;
+        }
+    }
+}
+
+.scene-3 {
+    @include aligned;
+    @include dataFlowing;
+    .intro-admin-ui, .intro-storefront {
+        opacity: 0;
+    }
+}
+
+.scene-4 {
+    @include aligned;
+    @include dataFlowing;
+    .intro-admin-ui {
+        opacity: 1;
+        transition: opacity 0.8s 0.3s;
+    }
+    .intro-storefront {
+        opacity: 1;
+        transition: opacity 0.8s 0.5s;
+    }
+}
+
+.scene-5 {
+    .intro-admin-ui {
+        opacity: 0;
+        transition: opacity 0.2s;
+    }
+    .intro-storefront {
+        opacity: 0;
+        transition: opacity 0.2s;
+    }
+    .intro-gql-logo {
+        opacity: 1;
+    }
+    .intro-terminal {
+        opacity: 0;
+    }
+    .cube2, .cube3 {
+        .cube__face--front {
+            background-color: var(--color-right);
+        }
+    }
+    $cube-easing: cubic-bezier(.11,.73,.84,.55);
+    $cube-duration: 0.5s;
+    .cube1 {
+        .cube__face {
+            transition: transform $cube-duration, background-color 0.1s 0.1s;
+        }
+    }
+    .cube2 {
+        .cube__face {
+            transition: transform $cube-duration, background-color 0.3s 0.1s;
+        }
+    }
+    .cube3 {
+        .cube__face {
+            transition: transform $cube-duration, background-color 0.1s 0.1s;
+        }
+    }
+    .cube__face {
+        --projection: 1.3;
+        &.cube__face--front {
+            animation: projectFront;
+            animation-duration: $cube-duration;
+            animation-fill-mode: both;
+            animation-timing-function: $cube-easing;
+        }
+
+        &.cube__face--right {
+            transform: rotateY( 90deg) translateZ(calc(1 * var(--width-unit)));
+        }
+
+        &.cube__face--back {
+            transform: rotateY(180deg) translateZ(calc(1 * var(--width-unit)));
+        }
+
+        &.cube__face--left {
+            animation: projectLeft;
+            animation-duration: $cube-duration;
+            animation-fill-mode: both;
+            animation-timing-function: $cube-easing;
+        }
+
+        &.cube__face--top {
+            animation: projectTop;
+            animation-duration: $cube-duration;
+            animation-fill-mode: both;
+            animation-timing-function: $cube-easing;
+        }
+
+        &.cube__face--bottom {
+            transform: rotateX(-90deg) translateZ(calc(1 * var(--width-unit)));
+        }
+    }
+}
+
+.scene-6 {
+    transition: none;
+    .cube {
+        transition: none;
+    }
+    .intro-admin-ui {
+        opacity: 0;
+        transition: opacity 0.2s;
+    }
+    .intro-storefront {
+        opacity: 0;
+        transition: opacity 0.2s;
+    }
+    .intro-gql-logo {
+        opacity: 1;
+    }
+    .intro-terminal {
+        opacity: 0;
+    }
+    .cube2, .cube3 {
+        .cube__face--front {
+            background-color: var(--color-right);
+        }
+    }
+    .cube__face {
+        transition: none;
+    }
+    $shimmerDuration: 8s;
+    $shimmerTiming: ease-in-out;
+    .cube1 {
+        .cube__face--top { animation: shimmerTop $shimmerDuration $shimmerTiming $shimmerDuration * 0.02 infinite; }
+        .cube__face--left { animation: shimmerLeft $shimmerDuration $shimmerTiming infinite; }
+    }
+    .cube2 {
+        .cube__face--top { animation: shimmerTop $shimmerDuration $shimmerTiming $shimmerDuration * 0.05 infinite; }
+        .cube__face--left { animation: shimmerLeft $shimmerDuration $shimmerTiming $shimmerDuration * 0.05 infinite; }
+        .cube__face--front { animation: shimmerRight $shimmerDuration $shimmerTiming $shimmerDuration * 0.082 infinite; }
+    }
+    .cube3 {
+        .cube__face--top { animation: shimmerTop $shimmerDuration $shimmerTiming $shimmerDuration * 0.1 infinite; }
+        .cube__face--front { animation: shimmerRight $shimmerDuration $shimmerTiming $shimmerDuration * 0.12 infinite; }
+    }
+}
+
+@keyframes projectFront {
+    0% {
+        transform: rotateY(0deg) translateZ(calc(1 * var(--width-unit)));
+    }
+    20% {
+        transform: rotateY(0deg) translateZ(calc(var(--projection) * var(--width-unit)));
+    }
+    100% {
+        transform: rotateY(0deg) translateZ(calc(1 * var(--width-unit)));
+    }
+}
+
+@keyframes projectLeft {
+    0% {
+        transform: rotateY(-90deg) translateZ(calc(1 * var(--width-unit)));
+    }
+    20% {
+        transform: rotateY(-90deg) translateZ(calc(var(--projection) * var(--width-unit)));
+    }
+    100% {
+        transform: rotateY(-90deg) translateZ(calc(1 * var(--width-unit)));
+    }
+}
+
+@keyframes projectTop {
+    0% {
+        transform: rotateX( 90deg) translateZ(calc(1 * var(--width-unit)));
+    }
+    20% {
+        transform: rotateX( 90deg) translateZ(calc(var(--projection) * var(--width-unit)));
+    }
+    100% {
+        transform: rotateX( 90deg) translateZ(calc(1 * var(--width-unit)));
+    }
+}
+
+@keyframes shimmerTop {
+    10% {
+        background-color: #99e9ff;
+    }
+    20% {
+        background-color: var(--color-top);
+    }
+
+}
+
+@keyframes shimmerLeft {
+    10% {
+        background-color: #99e4ff;;
+    }
+    20% {
+        background-color: var(--color-left);
+    }
+}
+
+@keyframes shimmerRight {
+    10% {
+        background-color: #40ccff;;
+    }
+    20% {
+        background-color: var(--color-right);
+    }
+}

+ 20 - 1
docs/content/docs/_index.md

@@ -1,7 +1,6 @@
 ---
 title: "Vendure Documentation"
 weight: 0
-showtoc: false
 ---
 
 # Vendure Documentation
@@ -9,3 +8,23 @@ showtoc: false
 {{% alert "warning" %}}
 **Note**: Vendure is currently in alpha and as such, the information and APIs documented here are subject to change.
 {{% /alert %}}
+
+## What is Vendure?
+
+Vendure is a headless ecommerce framework.
+
+* *Headless* is a term which means that it does not concern itself with rendering the HTML pages of a website. Rather, it exposes a GraphQL API which which can be *queried* for data ("Give me a list of available products") or issued with *mutation* instructions ("Add product '123' to the current order") by a *client application*. Thus the client is responsible for how the ecommerce "storefront" looks and how it works. Vendure is responsible for the rest.
+* Vendure is a *framework* in that it supplies core ecommerce functionality, but is open to further extension by the developer.
+
+## Who should use Vendure?
+
+Vendure is intended to be used by developers who wish to create a modern ecommerce solution. While we aim for a seamless and simple developer experience, Vendure is not aimed at non-technical users.
+
+## What technologies is Vendure built on?
+
+* Vendure is written in [TypeScript](https://www.typescriptlang.org/).
+* [Node.js](https://nodejs.org/en/) is the runtime platform.
+* The data layer is handled by [TypeORM](http://typeorm.io/), which is compatible with most popular relational databases.
+* [Nest](https://nestjs.com/) is used as the underlying architecture.
+* The API is [GraphQL](https://graphql.org/) powered by [Apollo Server](https://www.apollographql.com/docs/apollo-server/).
+* The Admin UI application is built with [Angular](https://angular.io/).

+ 3 - 9
docs/layouts/index.en.html

@@ -3,19 +3,12 @@
 <head>
     {{ partial "docs/html-head" . }}
     {{ partial "docs/inject/head" . }}
+    <link rel="stylesheet" href="{{ "intro.css" | absURL }}">
     <link href="https://fonts.googleapis.com/css?family=Didact+Gothic|Open+Sans" rel="stylesheet">
 </head>
 <body>
 {{ partial "top-bar" (dict "isLandingPage" true) }}
-<div class="hero">
-    <div class="logo">
-        <img src="{{ "logo.png" | absURL }}" />
-        <h1>vendure</h1>
-    </div>
-    <div class="subhead">
-        A modern, headless <a href="https://graphql.org/">GraphQL</a>-based ecommerce framework built with <a href="http://www.typescriptlang.org">TypeScript</a> & <a href="https://nodejs.org">Nodejs</a>
-    </div>
-</div>
+{{ partial "intro" }}
 <div class="section-1">
     <div class="content">
         <h2 class="top-header">Ecommerce For The Modern Web
@@ -150,5 +143,6 @@
     &copy; 2018 <a href="https://www.michaelbromley.co.uk/">Michael Bromley</a>
     <a href="https://github.com/vendure-ecommerce/vendure" class="gh-link">Vendure on GitHub</a>
 </div>
+<script src="{{ "intro.js" | absURL }}"></script>
 </body>
 </html>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 38 - 0
docs/layouts/partials/intro.html


+ 1 - 0
docs/static/svg/clr-icon-replay-all.svg

@@ -0,0 +1 @@
+<svg version="1.1" viewBox="0 0 36 36" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" aria-hidden="true" role="img" width="32" height="32" fill="currentColor"><path d="M17.46,26.22a1.4,1.4,0,0,0,1-.42l5.59-5.56a1.43,1.43,0,0,0,.42-1,1.46,1.46,0,0,0-.42-1l-5.59-5.56a1.43,1.43,0,0,0-2.44,1V24.79a1.41,1.41,0,0,0,.88,1.32A1.54,1.54,0,0,0,17.46,26.22Zm.16-12.16,5.19,5.16-5.19,5.17Z" class="clr-i-outline clr-i-outline-path-1"/><path d="M18.06,5h-6.7l2.92-2.64A1,1,0,0,0,12.94.88L7.32,6,12.94,11a1,1,0,0,0,.67.26,1,1,0,0,0,.74-.33,1,1,0,0,0-.07-1.42L11.46,7h6.6A11.78,11.78,0,1,1,7.71,24.41,1,1,0,0,0,6,25.36,13.78,13.78,0,1,0,18.06,5Z" class="clr-i-outline clr-i-outline-path-2"/></svg>

+ 5 - 2
docs/webpack.config.ts

@@ -6,10 +6,13 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 
 const config: webpack.Configuration = {
     mode: 'production',
-    entry: './assets/scripts/main.ts',
+    entry: {
+        main: './assets/scripts/main.ts',
+        intro: './assets/scripts/intro/intro.ts',
+    },
     output: {
         path: path.resolve(__dirname, 'static'),
-        filename: 'main.js',
+        filename: '[name].js',
     },
     resolve: {
         extensions: ['.ts', '.js'],

Vissa filer visades inte eftersom för många filer har ändrats