Avatar

Avatar Small (Stories)

Zoom: 50% | 100% →

Stories

Seems to be trending to add stories on top of your feed these few days.

import=recipes/avatar/recipes-avatar-small.vue padding=0 zoom
<template>
  <main>
    <div class="header">
      <h1>Stories</h1>
      <p>Seems to be trending to add stories on top of your feed these few days.</p>
    </div>

    <div class="feed">
      <div class="stories">
        <vue-horizontal class="horizontal" :button-between="false" snap="none">
          <div class="item" v-for="item in items" :key="item.id">
            <div class="ring">
              <div class="avatar" :style="{background: `url(${item.img})`}">
              </div>
            </div>
          </div>
        </vue-horizontal>
      </div>

      <div class="content">
        <div v-for="i in [0,1,2,3,4,5,6]" :key="i" class="feed-item">
          <placeholder-component></placeholder-component>
        </div>
      </div>
    </div>
  </main>
</template>

<script>
// For convenience sake, I import a collection of images from unsplash.
import {portrait} from '../../../../assets/img'

export default {
  data() {
    return {
      items: portrait.items.map(({id, img: {srcset: {sm}}}) => {
        return {
          id: id,
          img: sm
        };
      })
    }
  }
}
</script>

<!-- Content Design -->
<style scoped>
.item {
  padding: 14px 6px;
}

.item:first-child {
  padding-left: 21px;
}

.item:last-child {
  padding-right: 21px;
}

.ring {
  width: 64px;
  height: 64px;
  border-radius: 32px;

  border: 3px solid transparent;
  background: #e2e8f0;

  cursor: pointer;
  transition: All 0.3s ease;
  overflow: hidden;
}

.ring:hover {
  /* This is a very simple trick to animation a ring of colors, you should go find a library for this*/
  transform: rotate(9deg) scale(1.05) translate(1px);
  animation: colors 1s ease infinite;
  background-size: 200% 200%;
  background-color: #663dff;
  border: 4px solid transparent;
  animation-direction: alternate;
  background-image: linear-gradient(319deg, #7d5fee 0%, #b72bff 33%, #ff2eb0 66%, #7eee40 100%);
}

@keyframes colors {
  0% {
    background-position: 10% 0
  }
  100% {
    background-position: 91% 100%
  }
}

.avatar {
  background-position: center !important;
  background-size: cover !important;
  background-repeat: no-repeat !important;
  height: 100%;
  width: 100%;
}

/* You can add your own button or you could just, */
/* Override default button design to make it smaller. */
.horizontal >>> .v-hl-btn svg {
  padding: 2px;
  height: 24px;
  width: 24px;
}
</style>

<!-- Parent CSS (.container) and other stuff -->
<style scoped>
main {
  padding: 24px;
  max-width: 700px;
  margin-left: auto;
  margin-right: auto;
}

.stories {
  border-bottom: 1px solid #e2e8f0;
}

.feed {
  border-radius: 5px;
  border: 1px solid #e2e8f0;
}

.content {
  padding: 12px;
  display: flex;
  flex-wrap: wrap;
  opacity: 0.25;
}

.feed-item {
  padding: 12px;
  width: 50%;
}

.header {
  margin-bottom: 24px;
}

@media (min-width: 768px) {
  main {
    padding: 48px;
  }
}
</style>

Responsive Avatar

Zoom: 50% | 100% →

Large Avatar

Images of people in a responsive grid

Jessica Felicio

Janko Ferlič

Alexander Krivitskiy

Shane Devlin

Amir Seilsepour

Seth Doyle

Qasim Sadiq

Julian Wan

Shayan Rostami

Ahmadreza Najafi

import=recipes/avatar/recipes-avatar-large.vue padding=0 zoom
<template>
  <main>
    <div class="header">
      <h1>Large Avatar</h1>
      <p>Images of people in a responsive grid</p>
    </div>

    <vue-horizontal class="horizontal">
      <div class="item" v-for="item in items" :key="item.id">
        <div class="avatar" :style="{background: `url(${item.img})`}">
          <div class="aspect-ratio"></div>
          <div class="content">
            <h4>{{ item.name }}</h4>
          </div>
        </div>
      </div>
    </vue-horizontal>
  </main>
</template>

<script>
// For convenience sake, I import a collection of images from unsplash.
import {portrait} from '../../../../assets/img'

export default {
  data() {
    return {
      items: portrait.items.map(({id, img: {srcset: {sm}, credit: {name}}}) => {
        return {
          id: id,
          img: sm,
          name: name,
        };
      })
    }
  }
}
</script>

<!-- Content Design -->
<style scoped>
.avatar {
  background-position: center !important;
  background-size: cover !important;
  background-repeat: no-repeat !important;
  border-radius: 50%;
  position: relative;
  overflow: hidden;
}

.aspect-ratio {
  padding-top: 100%;
}

.content {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: #00000010;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px;
}

.content > * {
  color: white;
  line-height: 1.25;
}
</style>

<!-- Parent CSS (.container) -->
<style scoped>
main {
  padding: 24px;
}

.header {
  margin-bottom: 24px;
}

@media (min-width: 768px) {
  main {
    padding: 48px;
  }
}
</style>

<!-- Responsive Breakpoints -->
<style scoped>
.horizontal {
  --count: 2;
  --gap: 16px;
  --margin: 24px;
}

@media (min-width: 640px) {
  .horizontal {
    --count: 4;
  }
}

@media (min-width: 768px) {
  .horizontal {
    --count: 5;
    --margin: 0;
  }
}

@media (min-width: 1024px) {
  .horizontal {
    --gap: 24px;
    --count: 6;
  }
}

@media (min-width: 1280px) {
  .horizontal {
    --gap: 32px;
    --count: 8;
  }
}
</style>

<!-- Responsive Logic -->
<style scoped>
@media (max-width: 767.98px) {
  /* The --margin removes the padding from the parent container and add it into vue-horizontal.
   If the gap is less than margin, this causes overflow to show and peeks into the next content for better UX.
   You can replace this section entirely for basic responsive CSS logic if you don't want this "peeking" experience
   for the mobile web. */
  /* This responsive logic is hyper sensitive to your design choices, it's not a one size fit all solution. */
  .item {
    width: calc((100% - (var(--gap) * 2)) / var(--count));
    padding: 0 calc(var(--gap) / 2);
  }

  .item:first-child {
    width: calc(var(--gap) + (100% - (var(--gap) * 2)) / var(--count));
    padding-left: var(--margin);
  }

  .item:last-child {
    width: calc(var(--gap) + (100% - (var(--gap) * 2)) / var(--count));
    padding-right: var(--margin);
  }

  .item:only-child {
    width: calc((var(--gap) * 2) + (100% - (var(--gap) * 2)) / var(--count));
  }

  .horizontal {
    margin: 0 calc(var(--margin) * -1);
  }

  .horizontal >>> .v-hl-container {
    scroll-padding-left: var(--gap);
    scroll-padding-right: var(--gap);
  }

  .horizontal >>> .v-hl-btn {
    display: none;
  }
}

@media (min-width: 768px) {
  .item {
    width: calc((100% - ((var(--count) - 1) * var(--gap))) / var(--count));
    margin-right: var(--gap);
  }
}
</style>