<!DOCTYPE html>
<html>

  <head>
    <base href="." />
    <title>Imperative Animations Example</title>
    <link rel="stylesheet" href="app.css">
    <link href="https://fonts.googleapis.com/css?family=Lobster|Open+Sans:400,600" rel="stylesheet">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script>
    <script src="https://unpkg.com/zone.js/dist/zone.js"></script>
    <script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
    <script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
    <script src="config.js"></script>
    <script>
      System.import('app').catch(console.error.bind(console));
    </script>
  </head>

  <body>
    <dashboard>
      Loading...
    </dashboard>
  </body>

</html>
.title {
  color: white;
  font-weight: bold;
  margin-right: auto;
}

header {
  height: 40px;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  background-color: #272727;
  padding: 0.5em 1em;
}

.profile-button {
  width: 40px;
  cursor: pointer;
  border-radius: 50%;
}

.image-container {
  display: flex;
  align-items: center;
}

.image-container:hover:before {
  opacity: 1;
  transform: translateX(-10px);
}

.image-container:before {
  display: inline-block;
  content: attr(data-tooltip);
  color: white;
  opacity: 0;
  transform: translateX(-30px);
  transition: opacity .2s ease, transform .2s ease;
}
System.config({
  //use typescript for compilation
  transpiler: 'typescript',
  //typescript compiler options
  typescriptOptions: {
    emitDecoratorMetadata: true
  },
  paths: {
    'npm:': 'https://unpkg.com/'
  },
  //map tells the System loader where to look for things
  map: {
    
    'app': './src',
    
    '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
    '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
    '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
    '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
    '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
    '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
    '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
    '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
    
    '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
    '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
    '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
    '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
    '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
    '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
    '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
    
    'rxjs': 'npm:rxjs',
    'typescript': 'npm:typescript@2.0.2/lib/typescript.js'
  },
  //packages defines our app package
  packages: {
    app: {
      main: './main.ts',
      defaultExtension: 'ts'
    },
    rxjs: {
      defaultExtension: 'js'
    }
  }
});
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {DashboardModule} from './dashboard';

platformBrowserDynamic().bootstrapModule(DashboardModule)
import {Component, NgModule} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import {ProfileDetailsComponent} from './profile-details'

export interface User {
  name: string;
}

@Component({
  selector: 'dashboard',
  styleUrls: ['src/dashboard.css'],
  template: `
    <div>
      <header>
        <span class="title">Dashboard</span>
        <div class="image-container" (click)="toggleProfileDetails()" data-tooltip="Profile" >
          <img class="profile-button" src="https://api.adorable.io/avatars/60/me@you.com.png" />
        </div>
      </header>
      
      <profile-details [user]="user" *ngIf="showProfileDetails"></profile-details>
    </div>
  `,
})
export class DashboardComponent {
  showProfileDetails: boolean = false;
  user: User;
  
  constructor() {
    this.user = {
      name: 'Dominic Elm',
      title: 'Frontend Developer',
      location: 'Germany',
      hearts: 235,
      views: 23500
    };
  }
  
  toggleProfileDetails() {
    this.showProfileDetails = !this.showProfileDetails;
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ DashboardComponent, ProfileDetailsComponent ],
  bootstrap: [ DashboardComponent ]
})
export class DashboardModule {}
import {Component, Input, ElementRef, ViewChild, ViewChildren, QueryList} from '@angular/core'
import {User} from './dashboard.ts'

@Component({
  selector: 'profile-details',
  styleUrls: ['src/profile-details.css'],
  template: `
    <div #wrapper class="wrapper">
      <header #header>
        <div class="profile-image-wrapper">
          <div class="profile-image-border" #profileImageBorder></div>
          <img class="profile-image" #profileImage src="https://api.adorable.io/avatars/90/me@you.com.png" />
        </div>
        <div class="profile-header-content">
          <span class="username" #username>{{ user.name }}</span>
          <span class="username-title" #title>{{ user.title }}</span>
        </div>
      </header>
      <main #main>
        <ul class="stats">
          <li class="stats-item">
            <span class="stats-icon icon-eye" #statsIcon></span>
            <span #statsText>{{ user.views }}</span>
          </li>
          <li class="stats-item">
            <span class="stats-icon icon-location" #statsIcon></span>
            <span #statsText>{{ user.location }}</span>
          </li>
          <li class="stats-item">
            <span class="stats-icon icon-heart" #statsIcon></span>
            <span #statsText>{{ user.hearts }}</span>
          </li>
        </ul>
      </main>
    </div>
  `,
})
export class ProfileDetailsComponent {
  @Input() user: User;
  
  @ViewChild('wrapper') wrapper: ElementRef;
  @ViewChild('main') main: ElementRef;
  
  @ViewChild('profileImageBorder') profileImageBorder: ElementRef;
  @ViewChild('profileImage') profileImage: ElementRef;
  
  @ViewChild('username') username: ElementRef;
  @ViewChild('title') title: ElementRef;
  
  @ViewChildren('statsIcon') statsIconQueryList: QueryList<ElementRef>;
  statsIcons: Element[] = [];
  
  @ViewChildren('statsText') statsTextQueryList: QueryList<ElementRef>;
  statsTexts: Element[] = [];
  
  timeline;
  
  constructor() {}
  
  ngAfterViewInit() {
    this.timeline = new TimelineLite();
    
    this.statsIconQueryList.map((elementRef) => {
      this.statsIcons.push(elementRef.nativeElement);
    });
    
    this.statsTextQueryList.map((elementRef) => {
      this.statsTexts.push(elementRef.nativeElement);
    });
    
    this.timeline
      .add('start')
      .from(this.wrapper.nativeElement, .15, { opacity: 0 }, 'start')
      .to(this.wrapper.nativeElement, .3, { rotationX: 0, y: 0, z: 0,  ease: Power3.easeIn}, 'start')
      .add('image', '-=0.1')
      .add('main', '-=0.15')
      .add('icons', '-=0.1')
      .add('text', '-=0.05')
      .from(this.profileImageBorder.nativeElement, .3, { scale: 0 }, 'image')
      .from(this.profileImage.nativeElement, .3, { scale: 0, delay: .05 }, 'image')
      .from(this.main.nativeElement, .4, { y: '100%' }, 'main')
      .staggerFrom([this.username.nativeElement, this.title.nativeElement], .3, { opacity: 0, left: 50 }, 0.1, 'image')
      .staggerFrom(this.statsIcons, .3, { opacity: 0, top: 10 }, 0.1, 'icons')
      .staggerFrom(this.statsTexts, .3, { opacity: 0 }, 0.1, 'text')
      .play();
  }
  
  ngOnDestory() {
    this.timeline.kill();
    this.timeline = null;
  }
}
:host {
  position: fixed;
  perspective: 500px;
  top: calc(50% + 30px);
  left: 50%;
  transform: translate(-50%, -50%);
  width: 80%;
  max-width: 400px;
}

.wrapper {
  box-shadow: 0px 0 20px 0px rgba(0,0,0,0.2);
  background: white;
  transform-origin: top center;
  transform: rotateX(-90deg) translateY(150px) translateZ(50px);
  height: 320px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

header {
  padding: 2em;
  display: flex;
  justify-content: center;
  z-index: 0;
}

main {
  background: #f8f8f8;
  flex-grow: 1;
}

.profile-image {
  border-radius: 50%;
}

.profile-image-wrapper {
  position: relative;
  display: inline-block;
  height: 90px;
  width: auto;
}

.profile-image-border {
  background: #e3e3e3;
  position: absolute;
  top: -4px;
  right: -4px;
  bottom: -4px;
  left: -4px;
  border-radius: 50%;
  z-index: -1;
}

.profile-header-content {
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-left: 1.5em;
  color: #c8c8c8;
}

.profile-header-content > span {
  position: relative;
}

.username {
  font-weight: 600;
  font-size: 1.3em;
}

.stats {
  list-style: none;
  display: flex;
  justify-content: space-around;
  padding: 2em;
}

.stats-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  color: #ababab;
}

.stats-icon {
  position: relative;
  margin-bottom: 1em;
  height: 32px;
  width: 32px;
}

.icon-eye {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACyElEQVRYR+1WwXUaMRCdUQN2KohdgenAuANcgdGs78EVhFRg585KUEFwBbE7IBXEriCmAU3eJ9I+7bK74HDwhb3wQMvMn/9n/ojpgx/+4Px0BHBk4N0MeO9PVfWCmQdEdBqb+E1VV8z8y1r79p7G3gsAkhLRjaqOiGi4I8ETMy+JaLEPmF4ASBxCuGdmJN5Uq6prZl6p6lMOhJmHqjpg5pOMlaUx5q4PSCcA7/0XVZ1mNC9CCMvb21tU1/nMZrORMQaAbxIQIpqIyKLtT1sAvPcDVb3PqH5k5om19gUB2lghIvRArVrv/VksIAGBNDbFSWBqAGLyn6haVV+NMWNrbUV1fp7kwGdOOzNfWWtXKYH3fhhCmDPzZwBtnlcAyrIcM7OPf1yIyDinLHb/7yjJMzMDXMUKGCCiy5jkvKm7c24eZQFbd0VR4Ps/K86Tx8OHpl5ZgGcRaZ0E5xzYAojvIjJpxijLcsLMkBfNbAGCG8k3P7Y1S1mWoO+EmVHdpvLmE3UHSy8ict4Rp2IaINg59ydq3pkcgZxzihEsiiKZT+skJKAi0jlhWdFvYGBTWaKka74AAPqKyKe+MXwPADQ6x7n9ketyqAQIXBTF2S4JQgjXbU3YKkXWhE8ictUWfFcTRnPbNHjVhClQoxnnTQuNBvQSZ75mKrH5MMKw47Ux5iwfw8y8NqOdy71lRCEEBIefIxmcq2ZE2Tlipc1X7QljzLBpRKoKcHBGgKudt1kxXDCZCtCCjW8NK8aMY5zgbnjnlYjw3kOqHKyEEL7CsCLLMK9R06D6RgVJpslmAQRrVkQe+6bAOXepqgCX6F4jTlEUW+ZWOWFXwKgdgMDBqjVLRLh81NaxqsIdq0tKXNsAPf2vddzcAyEEVITKLvoYIKLnuBnnB19IOuwWDTcIIdSuZMYYbMDVPknzuHtdyXZUfNDxEcCRgQ9n4C9KZe8Lt5Mt/QAAAABJRU5ErkJggg==");
  background-repeat: no-repeat;
}

.icon-location {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADJElEQVRYR61XwVEcMRDUiACACAwRGCIAIuAcAUjizxEBEAH4j6QjAkMEPiLgHIFxBOAA2HbNlnSl25K0OvD+dksatXp6emZJrPF477cAHAOYENGOEGIvbF8AeCGiByJ6VEq9tYal1oXW2isiOhdCbIU9T4O9B+H9jYhulVLXLbFHAYRb/+TbAvgDYLqxsTEf3pLXvb+/H/LhRPRFCLEgoqMxNqoABodfG2OuWm4V2LpsAVEF4JybCyEOAChjzGx4uPf+EAC01sN0CGvtKRF5IcRca31UAl4EEAMAWLm5936n67pLIjpNgwKYSSmvlVIv8XtkonQBXlcD0AcyxrDa+8d7zzpgPbAQHwEs+iBEXA3HQggWIOe9/86PtZarg1nazbGQBXB3dzeRUv4AcGGMuQ2Hcwn+BkBSyolSitOzfDgdXddxGYKIdqP4rLVTIrrpuu7b2dnZwxBEFoBzjvN9EgL1TDjnGMh5KRCvSYAv08YpY+BCiO9a62krgDmAPWNMrPmeymFKcpTmKLfWcmoWWuvDZgC8MN3gnAPnXWs9qZVirByt9ZLd8G0lXoxRSgHnd1NrvR8XBgBPuVukgHIAAisv6zDAYjnO3OJrKrAhE8G4XodM1djLMhA9IBVc4gszY4zKpcE51wNP6z4RZtbMsgAS5d5rrZeGkxzwIKW8iKYTzOmGiFgfKzpJKmo71xeKRhRyuUJ5oJjL8SQwENturJZ7IprGg8J6LsFfJe2sbcXBlLgHMDPRJdntZkNzilZc845qMxqz0Vo5Rhse844xAL2N1ppJCUQi2qz4qj6QBv0oC63OOToRldpyjf6WNtzMQNJSN2smFANG5QP4m7byEuBRBkpdrpJ7Hl4va8pP9zYBCO24H8/SFp2x4th6R3vGWikItc/T0HNtxouNiIj206moppdmBgILxaEken5p8PiUBlKBdV3HrvcabtlbcRDeM4BtKeXO2L/AhzQQNyXj9nLEahnX/gsDyXASBckDCw+rPCk3C+9TDATK+d+Af7240wkAu1LKvfSfYKxPrF0Fw4Bx3A4AluN768GfBpB4Q3bYbAXyD+fiQj/I0Pj/AAAAAElFTkSuQmCC");
  background-repeat: no-repeat;
}

.icon-heart {
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACuUlEQVRYR8VX7XETMRDdtQuIqYC4AkwFmApIB7GkAogrIFQAKUCS04GpgKQCnAowHTgF+JZ5N9KN7nKf9kE8kx+Jtbtvn1bvbZhe+cN19Z1zn0TkipkXRISfnYjsmXmrtb5vw+ycuw6xl0nsLsT+qMaWAHjvFyLyjYiWLUWQTCmldumZEOtD0abwB2Zep7EFgJDgJxHNQvR9lmXb6XR6OB6Ps8lkchU6uyCiAzN/jInSWBF5RrfVWCK6DnlLsTkA7/1MRH6juIg8oZhSal9tw3t/icTM/C6CwBkRyYF3xC6yLNsksXOl1CEH4JxDgqWI/JlMJgt80cQhwGZZBipzEOFcLL7sEYsrfEtE91rrFaOr0D0FWh+6HkYFBBgAa63FY07vPRpFw6g3Z+fcdyL6TESPWuu24Svhwr2DCfwxFC8NZcdLQdwHIroDgPwXEVkbYwDmn3+stTfMjNf2CAAS6MBUd9I/BrrSNbw6AGst3uVF3wEck4FcM5IZ+GqMuR2jQFcOa+0tM3/JZyD5Za+1nncFj/G9cw6ih+e/LunA/3gJyQuADryJShi1oKTTY3RbY1jRb+601jeFGVlrIZGFxlfd7lwwFcN6MsbA5il1w5LGQybbdH0IoCD3vxLDKmT7xT4QjAaWC0YgTo3G1AdEcFrQjl3jOZhd4bQvNqKo8dCGc0HUFEfnJc+oXcnGANGneGkGapaPwjZFZGOMUX0oj2estZ6ZV8Fn3jcNdS0DSZIVM2PPg+f3BpEWFxFljNk0gW8FgCBr7SAQQ4q3XkGKuAKi0TMSWQdjrZ3H/J0MxIPOOdCYb7Z1yVOQ2HSgcn1mpjcAJGsCUSmeL5t9ive+gjRZFUSY8nxQ46bbt/hJAAITcaks1vKhS+3gGUi7Omctr7IzaAaqIERkG64A/0md5BknAxhyz21n/wLMOAqHi8kHLQAAAABJRU5ErkJggg==');
  background-repeat: no-repeat;
  transform: scale(0.8);
}
body {
  font-family: 'Open Sans', sans-serif;
}