<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>CodePen - Crypto Donation User Card</title>
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Merriweather+Sans:700i'><link rel="stylesheet" href="./style.css">
</head>
<body>
<!-- partial:index.partial.html -->
<!-- user donation card -->
<div class="card-wrap" id="card">
<!-- user info -->
<header class="card-user">
<div class="card-user-hero" :style="'background-image: url(' + userInfo.hero + ')'"></div>
<div class="card-user-row">
<div class="card-user-avatar">
<img :src="userInfo.avatar" :alt="userInfo.name" />
</div>
<div class="card-user-details">
<div class="card-user-name">{{ userInfo.name }}</div>
<div class="card-user-info">{{ userInfo.info }}</div>
</div>
<div class="card-user-cta">
<a :href="userInfo.website" target="_blank">Website</a>
</div>
</header>
<!-- tabs -->
<main class="tabs-wrap">
<!-- tab buttons -->
<nav class="tabs-nav">
<button v-for="w in walletsList" :key="w.symbol" :class="{ 'active': w.active }" @click="selectWallet( w.symbol )">{{ w.symbol }}</button>
</nav>
<!-- tab content -->
<section class="tabs-info">
<!-- qr image -->
<div class="tabs-info-row">
<div class="tabs-info-qr">
<img :src="getQrImage()" :alt="wallet.name" />
</div>
<div class="tabs-info-details">
<div class="tabs-info-title">Donate {{ wallet.name }}</div>
<div class="tabs-info-warn">Send only {{ wallet.name }} ({{ wallet.symbol }}) to this deposit address. Sending any other coin or token to this address may result in the loss of your donation, Thanks!</div>
</div>
</div>
<!-- address input -->
<div class="tabs-info-input">
<span>Address</span>
<input type="text" :value="wallet.address" />
<button type="button" @click="copyText( wallet.address )">
<span v-if="wallet.copied">✔ Copied</span>
<span v-else>✚ Copy</span>
</button>
</div>
<!-- coin stats -->
<div class="tabs-info-stats">
<div>
<span class="txt-label">Price USD</span> <br />
<span>${{ stats.price | toMoney( 2 ) }}</span>
</div>
<div>
<span class="txt-label">Market Cap</span> <br />
<span>{{ stats.cap | toMoney( 0 ) }}</span>
</div>
<div>
<span class="txt-label">Supply</span> <br />
<span>{{ stats.supply | toMoney( 0 ) }}</span>
</div>
</div>
</section>
</main>
</div>
<!-- partial -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js'></script><script src="./script.js"></script>
</body>
</html>
/**
* Vars
*/
/**
* Reset
*/
*, *:before, *:after {
outline: 0;
margin: 0;
padding: 0;
border: 0;
text-decoration: none;
box-sizing: border-box;
transition: color 300ms ease-out, background 300ms ease-out, border 300ms ease-out, trandform 300ms ease-out, opacity 300ms ease-out;
}
/**
* Doc
*/
html, body {
display: block;
position: relative;
min-height: 100vh;
}
body {
padding: 4em 0;
overflow: hidden;
overflow-y: auto;
background-color: #461146;
background-image: radial-gradient(circle, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.1) 100%);
background-position: center center;
background-repeat: no-repeat;
font-family: "Merriweather Sans", Arial, sans-serif;
font-size: 15px;
line-height: normal;
color: black;
}
/**
* Form elms
*/
input, textarea, select, options, button {
font-family: inherit;
font-size: inherit;
line-height: inherit;
font-weight: normal;
background: none;
}
/**
* Card
*/
.card-wrap {
overflow: hidden;
margin: 0 auto;
max-width: 520px;
background-color: #f0f0f0;
border-radius: 6px;
box-shadow: 0 2px 18px rgba(0, 0, 0, 0.5);
}
.card-wrap .card-user {
position: relative;
}
.card-wrap .card-user .card-user-hero {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 10em;
background-color: purple;
background-position: center center;
background-repeat: no-repeat;
background-size: 120%;
z-index: 0;
}
.card-wrap .card-user .card-user-row {
display: flex;
flex-direction: flex-row;
align-items: center;
justify-content: stretch;
position: relative;
padding: 1em;
padding-top: calc( 10em - 3em );
margin-bottom: 1em;
}
.card-wrap .card-user .card-user-row .card-user-avatar {
margin-right: 1em;
}
.card-wrap .card-user .card-user-row .card-user-avatar > img {
display: block;
width: 6em;
height: 6em;
border: 3px solid #f0f0f0;
border-radius: 100px;
box-shadow: 0 2px 18px rgba(0, 0, 0, 0.5);
}
.card-wrap .card-user .card-user-row .card-user-details {
flex: 1;
margin-right: 1em;
margin-top: .6em;
line-height: normal;
text-wrap: nowrap;
height: 85px;
}
.card-wrap .card-user .card-user-row .card-user-details .card-user-name {
font-size: 200%;
letter-spacing: -1px;
line-height: 1em;
color: #f0f0f0;
text-shadow: 0 2px 18px rgba(0, 0, 0, 0.5);
}
.card-wrap .card-user .card-user-row .card-user-details .card-user-info {
line-height: 1em;
padding-top: 1em;
color: #450845;
}
.card-wrap .card-user .card-user-row .card-user-cta > a {
display: block;
cursor: pointer;
color: #f0f0f0;
font-weight: normal;
padding: 0.8em 1em;
border-radius: 100px;
box-shadow: 0 2px 18px rgba(0, 0, 0, 0.5);
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
background-color: #8a0f8a;
}
.card-wrap .card-user .card-user-row .card-user-cta > a:hover {
background-color: #730d73;
}
.card-wrap .tabs-wrap {
position: relative;
}
.card-wrap .tabs-wrap .tabs-nav {
display: flex;
flex-direction: flex-row;
align-items: center;
justify-content: stretch;
position: relative;
overflow: hidden;
}
.card-wrap .tabs-wrap .tabs-nav > button {
flex: 1;
display: block;
cursor: pointer;
text-align: center;
padding: 0.8em 0;
background-color: rgba(0, 0, 0, 0.09);
border-top: 2px solid rgba(128, 0, 128, 0);
font-weight: bold;
}
.card-wrap .tabs-wrap .tabs-nav > button:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.card-wrap .tabs-wrap .tabs-nav > button.active {
background-color: rgba(0, 0, 0, 0);
border-color: rgba(128, 0, 128, 0.5);
color: purple;
}
.card-wrap .tabs-wrap .tabs-nav > button + button {
margin-left: 1px;
}
.card-wrap .tabs-wrap .tabs-info {
min-height: 100px;
padding-top: 1em;
}
.card-wrap .tabs-wrap .tabs-info .tabs-info-row {
display: flex;
flex-direction: flex-row;
align-items: center;
justify-content: stretch;
margin-bottom: 1em;
padding: 0 1em;
}
.card-wrap .tabs-wrap .tabs-info .tabs-info-row .tabs-info-qr {
margin-right: 1em;
}
.card-wrap .tabs-wrap .tabs-info .tabs-info-row .tabs-info-qr > img {
display: block;
border-radius: 20%;
}
.card-wrap .tabs-wrap .tabs-info .tabs-info-row .tabs-info-title {
font-size: 200%;
margin-bottom: 10px;
}
.card-wrap .tabs-wrap .tabs-info .tabs-info-input {
display: flex;
flex-direction: flex-row;
align-items: center;
justify-content: stretch;
background-color: #fff;
padding: 0.75em 1em;
box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.4);
}
.card-wrap .tabs-wrap .tabs-info .tabs-info-input > span {
margin-right: 1em;
font-weight: bold;
}
.card-wrap .tabs-wrap .tabs-info .tabs-info-input > input {
flex: 1;
margin-right: 1em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: bold;
color: purple;
}
.card-wrap .tabs-wrap .tabs-info .tabs-info-input > button {
cursor: pointer;
font-weight: bold;
}
.card-wrap .tabs-wrap .tabs-info .tabs-info-stats {
display: flex;
flex-direction: flex-row;
align-items: center;
justify-content: space-between;
padding: 1em;
}
.card-wrap .tabs-wrap .tabs-info .tabs-info-stats .txt-label {
font-size: 80%;
text-transform: uppercase;
opacity: 0.5;
}
// user details
const userInfo = {
avatar: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/668895/profile/profile-512.jpg',
hero: 'https://static.codepen.io/assets/profile/profile-bg-8ff33bd9518be912289d4620cb48f21eb5c9a2e0b9577484126cfe10a5fb354f.svg',
website: 'https://rainnerlins.com/',
name: 'Rainner Lins',
info: 'Fullstack Codepen Superstar Wannabe',
};
// crypto wallets
const cryptoWallets = [
{
symbol: 'BTC',
name: 'Bitcoin',
address: '13R8NFPs7oFjDof3pQ832g1RgEkkkFqBAN',
},
{
symbol: 'ETH',
name: 'Etherium',
address: '0x1c2a703e0939389c2b7c09f6422a3f56451e5b42',
},
{
symbol: 'LTC',
name: 'Litecoin',
address: 'LgKxe22pt8qP3RHsNEXw7GzcFdzNBHnZEy',
},
{
symbol: 'NANO',
name: 'Nano',
address: 'xrb_1gwcfutq437fxaiqkckijede91f7binhbttfehufr9xckgwzitnkefwr3d48',
},
{
symbol: 'NEO',
name: 'Neo',
address: 'AHYs2QxbzzoGdDfMjKfnEgMAdUGJswYWVa',
},
];
// number format filter
Vue.filter( 'toMoney', ( num, decimals ) => {
let o = { style: 'decimal', minimumFractionDigits: decimals, maximumFractionDigits: decimals };
return new Intl.NumberFormat( 'en-US', o ).format( num );
});
// vue instance
new Vue({
el: '#card',
// app data
data: {
userInfo,
cryptoWallets,
tab: 'BTC',
wallet: {},
statsCache: {},
stats: {},
},
// computed methods
computed: {
// compute list wallets for tabs
walletsList() {
return this.cryptoWallets.map( w => {
w.active = ( w.symbol === this.tab );
return w;
});
},
},
// custom methods
methods: {
// select active tab wallet
selectWallet( symbol ) {
let wallet = this.cryptoWallets.filter( w => w.symbol === symbol ).shift();
if ( !wallet ) return;
wallet.copied = 0;
this.wallet = wallet;
this.tab = symbol;
this.fetchStats( symbol );
},
// copy text to clipboard
copyText( txt ) {
txt = String( txt || '' ).trim();
if ( !txt ) return;
let input = document.createElement( 'input' );
document.body.appendChild( input );
input.value = txt;
input.select();
document.execCommand( 'Copy' );
document.body.removeChild( input );
this.wallet = Object.assign( {}, this.wallet, { copied: 1 } );
},
// get qr image url for selected wallet
getQrImage() {
const w = 180;
const h = 180;
const a = this.wallet.address;
return `https://chart.googleapis.com/chart?chs=${w}x${h}&cht=qr&choe=UTF-8&chl=${a}`;
},
// set coin stats
setStats( symbol, data ) {
let price = 0;
let cap = 0;
let supply = 0;
let time = Date.now();
let stats = Object.assign( { price, cap, supply, time }, data );
this.statsCache[ symbol ] = stats;
this.stats = stats;
},
// fetch market stats for a symbol
fetchStats( symbol ) {
let stats = this.statsCache[ symbol ] || null;
let price = stats ? stats.price : 0;
let secs = stats ? ( ( Date.now() - stats.time ) / 1000 ) : 0;
// use values from cache
if ( price && secs < 300 ) {
return this.setStats( symbol, stats );
}
// reset and fetch new values from api
this.setStats( symbol );
const xhr = new XMLHttpRequest();
xhr.open( 'GET', 'https://coincap.io/page/'+ symbol, true );
xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
xhr.responseType = 'json';
xhr.addEventListener( 'load', e => {
if ( !xhr.response || !xhr.response.id ) return;
let price = parseFloat( xhr.response.price ) || 0;
let cap = parseFloat( xhr.response.market_cap ) || 0;
let supply = parseFloat( xhr.response.supply ) || 0;
this.setStats( symbol, { price, cap, supply } );
});
xhr.send();
},
},
// when component mounts
mounted() {
this.selectWallet( this.tab );
},
});