<a href="https://twitter.com/irkopal" class="twitter" target="_blank">
        <span class="twitter__icon">
            <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="640" height="640" viewBox="0 0 640 640">
                <path d="M554.112 199.872c0.256 5.184 0.352 10.432 0.352 15.616 0 159.68-121.504 343.744-343.68 343.744-68.256 0-131.712-20-185.184-54.304 9.472 1.12 19.072 1.696 28.8 1.696 56.64 0 108.704-19.328 150.016-51.68-52.832-0.992-97.472-35.872-112.832-83.872 7.36 1.376 14.944 2.112 22.72 2.112 11.040 0 21.728-1.44 31.84-4.192-55.264-11.136-96.896-59.936-96.896-118.496 0-0.512 0-0.992 0-1.504 16.288 9.056 34.944 14.496 54.72 15.136-32.416-21.696-53.76-58.624-53.76-100.576 0-22.112 5.952-42.88 16.384-60.736 59.552 73.12 148.608 121.184 248.992 126.24-2.048-8.864-3.104-18.048-3.104-27.552 0-66.688 54.048-120.736 120.768-120.736 34.752 0 66.144 14.624 88.192 38.112 27.488-5.44 53.344-15.488 76.704-29.312-9.024 28.192-28.192 51.872-53.12 66.816 24.448-2.944 47.68-9.376 69.376-19.008-16.192 24.256-36.672 45.504-60.288 62.496z"></path>
            </svg>
        </span>
        <span class="twitter__name">
            @irkopal
        </span>
    </a>
    <div id="app"></div>
@import url('https://fonts.googleapis.com/css?family=Roboto+Condensed:300,400');

$spot-color: #cf00f1;

*, *::after, *::before {
    box-sizing: border-box;
}
html, body {
    height: 100%;
    min-height: 100%;
}
body {
    margin: 0;  
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: 'Roboto Condensed', sans-serif;
    background: linear-gradient(15deg, #00a9f1, #cf00f1);
    background-repeat: no-repeat;
    background-attachment: fixed;
    -webkit-font-smoothing: antialiased;
}
[v-cloak] { display:none; }

.twitter {
    position: absolute;
    top: 1em;
    left: 1em;
    text-decoration: none;
    color: rgba(#524ad0, .8);

    display: flex;
    align-items: center;

    > * {
        line-height: 1;
    }

    &__icon {
        width: 32px;
        height: 32px;
        background: #fff;
        padding: 5px;
        border-radius: 50%;
        display: flex;
        justify-content: center;
        align-items: center;
    }

    &__name {
        display: inline-block;
        margin-left: .5em;
        color: #fff;
    }

    svg {
        fill: currentColor;
        width: 1em;
        height: 1em;
    }
}

.icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  stroke-width: 0;
  stroke: currentColor;
  fill: currentColor;
} 

.app {
    width: 90vw;
    box-shadow: 0 .7em 1em rgba(#000, .2);
    font-weight: 300;

    width: 95vw;

    @media (min-width: 768px) {
        width: 770px;
    }

}

.image-hotspot {
    position: relative;
    padding-top: 66.6%;
    overflow: hidden;

    &.is-selected {
        > img {
            filter: blur(5px);
            transition: all .5s 1s;
        }
    }

    > img {
        position: absolute;
        top: 0;
        left: 0;
        display: block;
        width: 100%;
        height: auto;
        transition: all .5s;
    }
}

.hotspot-point {
    z-index: 2;
    position: absolute;
    display: block;
    
    span {
        position: relative;
        display: flex;
        justify-content: center;
        align-items: center;
        width: 1.8em;
        height: 1.8em;
        background: $spot-color;
        border-radius: 50%;
        animation: pulse 3s ease infinite;
        transition: background .3s;
        box-shadow: 0 2px 10px rgba(#000, .2);

        &:after {
            content: attr(data-price);
            position: absolute;
            bottom: 130%;
            left: 50%;
            color: white;
            text-shadow: 0 1px black;
            font-weight: 600;
            font-size: 1.2em;
            opacity: 0;
            transform: translate(-50%, 10%) scale(.5);
            transition: all .25s;
        }
    }

    svg {
        opacity: 0;
        color: $spot-color;
        font-size: 1.4em;
        transition: opacity .2s;
    }

    &:before,
    &:after  {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        border-radius: 50%;
        pointer-events: none;
    }

    &:before {
        z-index: -1;
        border: .15em solid rgba(#fff, .9);
        opacity: 0;
        transform: scale(2);
        transition: transform .25s, opacity .2s;
    }

    &:after {
        z-index: -2;
        background:#fff;
        animation: wave 3s linear infinite;
    }
    
    &:hover{ 

        span {
            animation: none;
            background: #fff;

            &:after {
                opacity: 1;
                transform: translate(-50%, 0) scale(1);
            }
        }

        svg {
            opacity: 1;
        }

        &:before {
            opacity: 1;
            transform: scale(1.5);
            animation: borderColor 2s linear infinite;
        }
        &:after {
            animation: none;
            opacity: 0;
        }
    }
}

.hotspots-enter-active {
  transition: all .5s 1s;
} 
.hotspots-leave-active {
  transition: all .5s;
}
.hotspots-enter, .hotspots-leave-to {
  opacity: 0;
  transform: scale(.3);
}

@keyframes pulse{
    0%, 100% {
        transform: scale(1);
    }
    50% {
        transform: scale(1.1);
    }
}
@keyframes borderColor{
    0% {
        border-color: #fff;
    }
    50% {
        border-color: $spot-color;
    }
    100% {
        border-color: #fff;
    }
}

@keyframes wave{
    0% {
        opacity: 1;
        transform: scale(.8);
    }
    100% {
        opacity: 0;
        transform: scale(2);
    }
}

.hotspot-details {
  --top: 0;
  --left: 0;

  z-index: 5;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  will-change: clip-path;
  transform: translateZ(0);

  &.is-loaded {
    .hotspot-details__content {
      opacity: 1;
      transform: scale(1);
      transition: opacity .3s, transform .3s;
      backface-visibility: hidden;
    }
    .hotspot-details__nav-item {
      transform: translate(0, 0);
      transition: transform .3s;
      backface-visibility: hidden;
      
      @for $i from 2 through 3 {
        &:nth-child(#{$i}) {
            transition-delay: 75ms * $i;
        }
      }
    }
  }

  &__left,
  &__right {
    position: absolute;
    top: 0;
    width: 100%;
    height: 100%;
    background: rgba(#00a9f1, 0.8);
    will-change: background;
  }
  &__left {
    clip-path: polygon(0 0, 52.1% 0, 47.1% 100%, 0% 100%);
    .hotspot-details__content {
      padding: 0;
    }
  }
  &__right {
    background: rgba(#fff, 0.8);
    clip-path: polygon(52% 0, 100% 0, 100% 100%, 47% 100%);

    .hotspot-details__content {
      position: relative;
      left: 50%;
      padding-left: 2.5em;
    }
  }

  &__content {
    opacity: 0;
    width: 50%;
    height: 100%;
    padding: 4em 2em 2em 2em;
    transform: scale(.9);
    transition: opacity .3s .3s, transform .3s .3s;

    img {
      width: 80%;
    }
  }

  &__title {
    margin-top: 0;
    font-weight: 300;
    font-size: 24px;
    @media (min-width: 768px) {
      font-size: 38px;
    }
  }
  &__description {
    margin-bottom: 32px;
    font-size: 14px;

    @media (min-width: 768px) {
      font-size: 16px;
    }
  }
  &__price {
    font-size: 38px;
    font-weight: 400;
    color: #00a9f1;
    text-shadow: 1px 1px white;
  }

  &__nav {
    position: absolute;
    bottom: 1em;
    left: 0;
    width: 47%;
    display: flex;
  }

  &__nav-item {
    width: 33%;
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 1em;
    transform: translate(0, 120%);
    transition: transform .3s;

    @for $i from 2 through 3 {
      &:nth-child(#{$i}) {
          transition-delay: 75ms * $i;
      }
    }

    &:hover {
      img {
        transform: scale(1.2);
        opacity: 1;
      }
    }

    img {
      width: 35px;
      opacity: .8;
      transform: scale(1);
      transition: transform .2s, opacity .2s;
      backface-visibility: hidden;
    }
  }

  &__close {
    display: block;
    z-index: 2;
    position: absolute;
    top: 10px;
    right: 10px;
    color: black;
    font-size: 2em;
    line-height: 1;
    text-decoration: none;

    &:hover,
    &:active {
      color: #000;
      transition: transform .3s;
    }
    &:active {
      transform: scale(1.1);
    }
  }
}
.detail-enter-active {
  animation: detailsReveal .8s cubic-bezier(0.645, 0.045, 0.355, 1);

  .hotspot-details__left {
    background: rgba(#cf00f1, .8);
  }
  .hotspot-details__right {
    background: rgba(#cf00f1, .8);
  }
}
.detail-enter-to {
  .hotspot-details__left {
    background: rgba(#00a9f1, 0.8);
    transition: background .3s .2s;
  }
  .hotspot-details__right {
    background: rgba(#fff, 0.8);
    transition: background .3s .2s;
  }
}
.detail-leave-active {
  animation: detailsReveal .8s .5s cubic-bezier(0.645, 0.045, 0.355, 1) reverse;
  .hotspot-details__left {
    background: rgba(#cf00f1, .8);
    transition: background .3s .7s;
  }
  .hotspot-details__right {
    background: rgba(#cf00f1, .8);
    transition: background .3s .7s;
  }
}
.product-fade-enter-active {
  transition: all .25s ease;

  @for $i from 2 through 3 {
    &.animated:nth-child(#{$i}) {
        transition-delay: 50ms * $i;
    }
  }
}
.product-fade-leave-active {
  transition: all .25s ease;

  @for $i from 2 through 3 {
    &.animated:nth-child(#{$i}) {
        transition-delay: 50ms * $i;
    }
  }
}
.product-fade-enter, .product-fade-leave-to {
  transform: translate3d(0,10px,0);
  opacity: 0;
}


@keyframes detailsReveal {
  0% {
    clip-path: circle(.9em at calc(var(--left) + .9em) calc(var(--top) + .9em));
  }
  30% {
    clip-path: circle(5vw at 50% 50%);
  }
  100% {
    clip-path: circle(130% at 50% 50%);
  }
}

/*! Flickity v2.0.8
http://flickity.metafizzy.co
---------------------------------------------- */

.flickity-enabled {
  position: relative;
}

.flickity-enabled:focus { outline: none; }

.flickity-viewport {
  overflow: hidden;
  position: relative;
  height: 100%;
}

.flickity-slider {
  position: absolute;
  width: 100%;
  height: 100%;
}

/* draggable */

.flickity-enabled.is-draggable {
  -webkit-tap-highlight-color: transparent;
          tap-highlight-color: transparent;
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
}

.flickity-enabled.is-draggable .flickity-viewport {
  cursor: move;
  cursor: -webkit-grab;
  cursor: grab;
}

.flickity-enabled.is-draggable .flickity-viewport.is-pointer-down {
  cursor: -webkit-grabbing;
  cursor: grabbing;
}
.carousel {
  height: 100%;
}
.carousel-cell {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  transform: scale(.6);
  opacity: 0;
  backface-visibility: hidden;
  transition: transform .3s, opacity .3s;
  will-change: transform, opacity;

  &.is-selected {
    opacity: 1;
    transform: scale(1);
    transition: transform .3s .2s,  opacity .3s .1s;
  }
}
View Compiled
const hotspots = [
  {
    id: 1,
    title: 'iMac 27"',
    description: 'Accuracy. Brightness. Clarity. Regardless of how you measure the quality of a display, Retina is in a class by itself. The pixel density is so high that you won’t detect a single one while using iMac. Text is so sharp, you’ll feel like you’re reading email and documents on a printed page.',
    price: '$1,299.00',
    image: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/437221/imac.png',
    position: { top: '20%', left: '38%'}
  },
  {
    id: 2,
    title: 'Magic Mouse 2',
    description: 'Featuring a new design, Magic Mouse 2 is completely rechargeable, so you’ll eliminate the use of traditional batteries. It’s lighter, has fewer moving parts thanks to its built-in battery and continuous bottom shell, and has an optimized foot design.',
    price: '$99.00',
    image: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/437221/magic-mouse.png',
    position: { top: '85%', left: '75%'}
  },
  {
    id: 3,
    title: 'Magic Keyboard',
    description: 'The Magic Keyboard combines a sleek new design with a built-in rechargeable battery and enhanced key features. With an improved scissor mechanism beneath each key for increased stability, as well as optimized key travel and a lower profile, the Magic Keyboard provides a remarkably comfortable and precise typing experience.',
    price: '$79.00',
    image: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/437221/magic-keyboard.png',
    position: { top: '85%', left: '48%'}
  }
]

const HotspotDetails = {
  name: 'HotspotDetails',
  template: `
    <transition
      name="detail"
      @before-enter="beforeEnter"
      @after-enter="afterEnter"
      @before-leave="beforeLeave"
    >
      <div class="hotspot-details">
        <a href="#" @click.prevent="close" class="hotspot-details__close">
          <svg class="icon icon-close" viewBox="0 0 24 24">
            <path d="M18.984 6.422l-5.578 5.578 5.578 5.578-1.406 1.406-5.578-5.578-5.578 5.578-1.406-1.406 5.578-5.578-5.578-5.578 1.406-1.406 5.578 5.578 5.578-5.578z"></path>
          </svg>
        </a>
        <div class="hotspot-details__left">
          <div class="hotspot-details__content">
            <div class="carousel" ref="carousel">
              <div class="carousel-cell" v-for="item in allItems">
                <img :src="item.image" width="80%" />
              </div>
            </div>
          </div>
        </div>
        <div class="hotspot-details__right">
          <div class="hotspot-details__content">
            <transition name="product-fade" mode="out-in">
              <h3 class="hotspot-details__title animated" :key="selectedItem.id">{{ selectedItem.title }}</h3>
            </transition>
            <transition name="product-fade" mode="out-in">
              <div class="hotspot-details__description animated" :key="selectedItem.id">{{ selectedItem.description }}</div>
            </transition>
            <transition name="product-fade" mode="out-in">
              <div class="hotspot-details__price animated" :key="selectedItem.id">{{ selectedItem.price }}</div>
            </transition>
          </div>
        </div>
        <div class="hotspot-details__nav">
          <a href="#" @click.prevent="selectProduct(index)" class="hotspot-details__nav-item" v-for="(item, index) in allItems">
            <img :src="item.image" width="80%" />
          </a>
        </div>
      </div>
    </transition>
  `,
  props: {
    item: { type: Object },
    selectedIndex: { type: Number },
    allItems: { type: Array }
  },
  data() {
    return {
      selectedItem: this.item
    }
  },
  mounted() {
    this.carousel = new Flickity(this.$refs.carousel, {
      cellAlign: 'left',
      contain: true,
      draggable: false,
      initialIndex: this.selectedIndex,
      imagesLoaded: true,
      prevNextButtons: false,
      pageDots: false
    });
    this.carousel.on('select', this.onProductSelected);
  },
  beforeDestroy() {
    setTimeout(() => {
      this.carousel.off('select', this.onProductSelected);
      this.carousel.destroy();
    }, 600);
  },
  methods: {
    close() {
      this.$emit('close');
    },
    selectProduct(index) {
      this.carousel.select(index);
    },
    onProductSelected() {
      this.selectedItem = this.allItems[this.carousel.selectedIndex];
    },
    beforeEnter(el) {
      el.style.setProperty(`--top`, this.item.position.top);
      el.style.setProperty(`--left`, this.item.position.left);
    },
    afterEnter(el) {
      el.classList.add('is-loaded');
    },
    beforeLeave(el) {
      el.classList.remove('is-loaded');
    }
  }
}

const App = {
  name: 'app',
  components: {
    HotspotDetails
  },
  template: `
    <div class="app">
      <div class="image-hotspot" :class="{'is-selected': open }">
        <hotspot-details 
          :item="selectedHotspot"
          :selected-index="selectedIndex"
          :all-items="hotspots"
          @close="closeDetails"
          v-if="open"
        ></hotspot-details>
        <transition-group name="hotspots">
          <a 
            href="#" 
            class="hotspot-point" 
            v-for="(hotspot, index) in hotspotItems"
            :style="{ top: hotspot.position.top, left: hotspot.position.left }"
            @click.prevent="hotspotClicked(hotspot, index)"
            :key="index"
          >
              <span :data-price="hotspot.price">
                <svg class="icon icon-close" viewBox="0 0 24 24">
                  <path d="M18.984 12.984h-6v6h-1.969v-6h-6v-1.969h6v-6h1.969v6h6v1.969z"></path>
                </svg>
              </span>
          </a>
        </transition-group>  
        <img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/437221/hotspot-image.jpg" alt="" @click="closeDetails">
      </div>
    </div>
  `,
  data () {
    return {
      hotspots,
      open: false,
      hotspotPosition: null,
      selectedHotspot: null
    }
  },
  computed: {
    hotspotItems() {
      return this.open ? [] : this.hotspots;
    }
  },
  methods: {
    closeDetails() {
      this.open = false;
    },
    hotspotClicked(hotspot, index) {
      this.selectedHotspot = hotspot;
      this.selectedIndex = index;

      this.open = true;
    }
  }
}

new Vue({
  el: '#app',
  components: {
    App
  },
  render: h => h(App)
})
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.com/flickity@2/dist/flickity.pkgd.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js