<div id="app">
  <main class="ui-items">
    <div class="ui-item -yesterday">
      <i class="fa fa-heart"></i>
      <div class="ui-date">Yesterday</div>
      <div class="ui-heart-rate">90/320</div>
      <div class="ui-footer">
        <div class="ui-link">More</div>
        <div class="ui-link">Settings</div>
      </div>
    </div>
    <div class="ui-item -today">
      <i class="fa fa-heart"></i>
      <div class="ui-date">Today</div>
      <div class="ui-heart-rate">120/280</div>
      <div class="ui-footer">
        <div class="ui-link">More</div>
        <div class="ui-link">Settings</div>
      </div>
    </div>
    <div class="ui-item -yesterday">
      <i class="fa fa-heart"></i>
      <div class="ui-date">Yesterday</div>
      <div class="ui-heart-rate">90/320</div>
      <div class="ui-footer">
        <div class="ui-link">More</div>
        <div class="ui-link">Settings</div>
      </div>
    </div>
    <div class="ui-item -today">
      <i class="fa fa-heart"></i>
      <div class="ui-date">Today</div>
      <div class="ui-heart-rate">120/280</div>
      <div class="ui-footer">
        <div class="ui-link">More</div>
        <div class="ui-link">Settings</div>
      </div>
    </div>
  </main>
</div>
@import url('https://fonts.googleapis.com/css?family=Lato|Oswald:300');

$color-primary: var(--color-primary, #F82462);
$color-secondary: var(--color-secondary, #1D2063);
$easing: cubic-bezier(.56,.01,.19,1.4);

:root {
  --stopped: calc(-1 * var(--pan-moving) + 1);
  --duration: calc(var(--stopped) * 0.6s);
  --drag: calc(var(--pan-y) * var(--pan-y) / 300000);
  font-size: 2.5vh;
}

#app {
  font-family: Lato, sans-serif;
  height: 90vh;
  width: 100%;
  max-width: 55vh;
  text-transform: uppercase;
  overflow: hidden;
  border-radius: 1rem;
  box-shadow: 0 0 2rem rgba(black, 0.2);
  backface-visibility: hidden;
  transform: translate3d(0,0,0);
  background: white;
  letter-spacing: 1px;
}

.ui-items {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  transform: translateY(calc(
    var(--pan-y)  * 0.5px
    - var(--pan-item) * 50%
  ));
  transition: transform var(--duration) $easing;
}

.ui-item {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 50%;
  width: 100%;
  
  &:before {
    content: '';
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    transform:
      translateY(calc(var(--pan-y) * 0.5px))
      scaleY(calc(1 + var(--drag)))
      skewY(calc(var(--pan-y) * 0.02deg));
    transform-origin: bottom center;
    transition:
      transform var(--duration) $easing,
      background-color 0.3s ease-in-out;
  }
  
  &.-yesterday {
    color: white;
    
    &:before {
      background-color: $color-primary;
    }
    
    .fa-heart {
      -webkit-text-stroke-color: white;
    }
  }
  
  &.-today {
    color: $color-primary;
    
     &:before {
      background-color: white;
    }
    
    .fa-heart {
      -webkit-text-stroke-color: $color-primary;
    }
  }
  
  > .ui-footer {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    padding: 1rem;
  }
}

.ui-date {
  font-size: 0.8rem;
}

.ui-heart-rate {
  font-family: Oswald, sans-serif;
  margin: 0.5rem 0;
  font-size: 2.5rem;
}

.ui-date, .ui-heart-rate {
  transform: scale(calc(1 - var(--drag)));
  transition: transform var(--duration) $easing;
}

.fa-heart {
  transform:
    scale(calc(1 - var(--drag)))
    translateY(calc(var(--pan-y) * 0.1px))
  ;
  transition: transform var(--duration) $easing;
  -webkit-text-stroke-width: 2px;
  margin-bottom: 1rem;
  font-size: 2rem;
  color: transparent;
}

.ui-footer {
  opacity: var(--stopped);
  transition: opacity 0.2s $easing;
}

*, *:before, *:after {
  box-sizing: border-box;
  position: relative;
}

body, html {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
}

body {
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  
  &:before {
    content: '';
    position: absolute;
    top: -150%;
    left: calc(50% - 50vh);
    width: 200%;
    height: 200%;
    border: 25vh solid white;
    border-radius: 7rem;
    background: $color-secondary;
    box-shadow:
      -25vh 25vh 0 $color-primary,
      -50vh 50vh 0 $color-secondary,
    ;
    transition: all 0.3s ease-in-out;
  }
}
View Compiled
const { fromEventPattern } = rxjs;
const { scan } = rxjs.operators;

const app = document.getElementById('app');
const hearts = document.querySelectorAll('.fa-heart');
const styleVars = (vars, el = document.documentElement) => {
  Object.keys(vars).forEach(key => {
    el.style.setProperty(`--${key}`, vars[key]);
  });
};

const hApp = new Hammer(app, {
  direction: Hammer.DIRECTION_ALL,
  threshold: 0,
});

hApp.get('pan')
  .set({ direction: Hammer.DIRECTION_ALL });

const pan$ = fromEventPattern((cb) =>
    hApp.on('panstart panend panup pandown', cb));

const initialState = {
  item: 0,
  y: 0,
  moving: false,
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'panup':
    case 'pandown':
      return {
        ...state,
        y: action.deltaY,
        moving: true,
      };
      
    case 'panend': {
      const newItem = action.deltaY > 100
        ? Math.max(state.item - 1, 0)
        : action.deltaY < 100
          ? Math.min(state.item + 1, 2)
          : state.item;
      
      return {
        y: 0,
        moving: false,
        item: newItem,
      };
    }
      
    default:
      return state;  
  }
}

const state$ = pan$.pipe(scan(reducer, initialState));

state$.subscribe(values => {
  styleVars({
    'pan-item': values.item,
    'pan-y': values.y,
    'pan-moving': +values.moving
  });
});





















































































// Since you've scrolled this far down,
// here's an easter egg!

const colorPairs = [
  ['#F82462', '#1D2063'],
  ['#24B8F8', '#24315A'],
  ['#FF5C43', '#343AA6'],
  ['#3EBB8C', '#FFD8C0'],
];

hearts.forEach((heart, i) => {
  const [primary, secondary] = colorPairs[i];
  
  heart.addEventListener('click', () => {
    RxCSS.styledash(document.documentElement).set({
      color: { primary, secondary },
    });
  });
});
View Compiled

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js