mirror of
https://github.com/Retropex/bitfeed.git
synced 2025-05-13 03:30:47 +02:00
Message sliders & supporters overlay
Donation bar and old donation overlay removed. New dropping/sliding message notification system. Thank you page for sponsors & donors.
This commit is contained in:
parent
8db6fef727
commit
e7dff7024e
@ -83,16 +83,16 @@ body {
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgb(0,100,200);
|
||||
color: var(--palette-x);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: rgb(0,80,160);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
label {
|
||||
@ -137,6 +137,23 @@ button:focus {
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
button.action-button {
|
||||
background: var(--bold-a);
|
||||
color: white;
|
||||
padding: 0.5em 2em;
|
||||
margin: 1em 2em 0.5em;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
box-shadow: inset 0px 0px 5px var(--dark-b);
|
||||
text-shadow: 0px 0px 5px var(--dark-b);
|
||||
}
|
||||
button.action-button:hover {
|
||||
box-shadow: inset 0px 0px 2px var(--dark-b);
|
||||
}
|
||||
button.action-button:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {transform:rotate(0deg);}
|
||||
to {transform:rotate(360deg);}
|
||||
|
@ -1,269 +0,0 @@
|
||||
<script>
|
||||
import analytics from '../utils/analytics.js'
|
||||
import { onMount } from 'svelte'
|
||||
import Icon from '../components/Icon.svelte'
|
||||
import config from '../config.js'
|
||||
import { fly, fade } from 'svelte/transition'
|
||||
// import { linear } from 'svelte/easing'
|
||||
import coffeeIcon from '../assets/icon/cib-buy-me-a-coffee.svg'
|
||||
import clipboardIcon from '../assets/icon/cil-clipboard.svg'
|
||||
import qrIcon from '../assets/icon/cil-qr-code.svg'
|
||||
import closeIcon from '../assets/icon/cil-x-circle.svg'
|
||||
import openIcon from '../assets/icon/cil-arrow-circle-bottom.svg'
|
||||
import boltIcon from '../assets/icon/cil-bolt-filled.svg'
|
||||
import { overlay } from '../stores.js'
|
||||
|
||||
let copied = false
|
||||
let addressElement
|
||||
|
||||
let showCopyButton = true
|
||||
let expanded = false
|
||||
let qrHidden = true
|
||||
let qrLocked = false
|
||||
let qrElement
|
||||
|
||||
onMount(() => {
|
||||
showCopyButton = (navigator && navigator.clipboard && navigator.clipboard.writeText) || !!addressElement
|
||||
})
|
||||
|
||||
async function copyAddress () {
|
||||
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
|
||||
await navigator.clipboard.writeText(config.donationAddress)
|
||||
copied = true
|
||||
setTimeout(() => {
|
||||
copied = false
|
||||
}, 2000)
|
||||
} else if (addressElement) {
|
||||
// fallback
|
||||
const range = document.createRange()
|
||||
range.selectNodeContents(addressElement)
|
||||
const selection = window.getSelection()
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
copied = document.execCommand('copy')
|
||||
setTimeout(() => {
|
||||
copied = false
|
||||
selection.removeAllRanges()
|
||||
}, 2000)
|
||||
}
|
||||
analytics.trackEvent('donations', 'on-chain', 'copy-address')
|
||||
}
|
||||
|
||||
function showQR () {
|
||||
qrHidden = false
|
||||
}
|
||||
|
||||
function hideQR () {
|
||||
qrHidden = true
|
||||
}
|
||||
|
||||
function clickaway (event) {
|
||||
if (!qrElement.contains(event.target)) {
|
||||
if (qrLocked) analytics.trackEvent('donations', 'on-chain', 'hide-qr')
|
||||
qrLocked = false
|
||||
window.removeEventListener('click', clickaway)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleQR () {
|
||||
qrLocked = !qrLocked
|
||||
window.addEventListener('click', clickaway)
|
||||
analytics.trackEvent('donations', 'on-chain', qrLocked ? 'show-qr' : 'hide-qr')
|
||||
}
|
||||
|
||||
function toggleExpanded () {
|
||||
expanded = !expanded
|
||||
analytics.trackEvent('donations', 'bar', expanded ? 'open' : 'close')
|
||||
}
|
||||
|
||||
function openLightningOverlay () {
|
||||
expanded = false
|
||||
analytics.trackEvent('donations', 'bar', 'close')
|
||||
$overlay = 'lightning'
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style type="text/scss">
|
||||
.donation-bar {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 32rem;
|
||||
min-width: 100px;
|
||||
max-width: calc(100vw - 8.25rem);
|
||||
margin: auto;
|
||||
transition: width 300ms, max-width 300ms;
|
||||
|
||||
.open-close-button {
|
||||
position: absolute;
|
||||
font-size: 1.2rem;
|
||||
bottom: -0.5em;
|
||||
right: 1rem;
|
||||
transform: rotate(0deg);
|
||||
background: var(--palette-c);
|
||||
border-radius: 50%;
|
||||
color: var(--palette-x);
|
||||
cursor: pointer;
|
||||
|
||||
transition: transform 600ms;
|
||||
}
|
||||
|
||||
.donation-content {
|
||||
padding: 5px 5px;
|
||||
|
||||
background: var(--palette-c);
|
||||
color: var(--palette-x);
|
||||
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
|
||||
.main-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.coffee-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.address-and-copy {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-shrink: 1;
|
||||
min-width: 4em;
|
||||
}
|
||||
|
||||
.address {
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
margin: 0 5px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.copy-button, .qr-button {
|
||||
position: relative;
|
||||
margin: 0 .25em;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
|
||||
.copy-notif {
|
||||
font-size: .7rem;
|
||||
color: var(--palette-x);
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background: var(--palette-d);
|
||||
border: solid 1px var(--palette-x);
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.donation-info {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.expandable-content {
|
||||
max-height: 0;
|
||||
transition: max-height 300ms;
|
||||
overflow: hidden;
|
||||
padding: 0 0.5em 0.5em;
|
||||
font-size: 0.8em;
|
||||
|
||||
.lightning-button {
|
||||
background: var(--bold-a);
|
||||
color: white;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.address-qr {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
max-width: 100vw;
|
||||
max-height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
@media (min-width: 611px) {
|
||||
left: 110px;
|
||||
right: 110px;
|
||||
}
|
||||
|
||||
@media (max-width: 610px) {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
max-width: 100vw;
|
||||
|
||||
.open-close-button {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.donation-content {
|
||||
.main-content {
|
||||
.address-and-copy {
|
||||
.address {
|
||||
word-break: break-all;
|
||||
overflow: visible;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.expandable-content {
|
||||
max-height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="donation-bar" transition:fly={{ y: -10 }} class:expanded>
|
||||
<div class="donation-content">
|
||||
<div class="main-content">
|
||||
<span class="coffee-icon">
|
||||
<Icon icon={coffeeIcon} color="var(--bold-a)" inline />
|
||||
</span>
|
||||
<div class="address-and-copy">
|
||||
<span class="address" bind:this={addressElement}>{ config.donationAddress }</span>
|
||||
{#if showCopyButton}
|
||||
<button class="copy-button" on:click={copyAddress} title="Copy address" alt="Copy address">
|
||||
<Icon icon={clipboardIcon} color="var(--palette-x)" />
|
||||
{#if copied }
|
||||
<span class="copy-notif" transition:fly={{ y: -5 }}>Copied to clipboard!</span>
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
<div class="qr-button" title="Show QR" on:pointerover={showQR} on:pointerenter={showQR} on:pointerleave={hideQR} on:pointerout={hideQR} on:pointercancel={hideQR} on:click={toggleQR} bind:this={qrElement}>
|
||||
<Icon icon={qrIcon} color="var(--palette-x)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="expandable-content">
|
||||
<p class="donation-info">
|
||||
Enjoying Bitfeed? Donations keep this site running. On-chain transactions to the donation address above appear highlighted in green.
|
||||
</p>
|
||||
{#if config.lightningEnabled }
|
||||
<button class="lightning-button" on:click={openLightningOverlay} >
|
||||
<Icon icon={boltIcon} color="white" inline />Prefer Lightning?
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if !qrHidden || qrLocked}
|
||||
<img src="/img/qr.png" alt="" class="address-qr" transition:fade={{ duration: 300 }} >
|
||||
{/if}
|
||||
<div class="open-close-button" on:click={toggleExpanded}>
|
||||
<Icon icon={openIcon} color="var(--palette-x)" />
|
||||
</div>
|
||||
</div>
|
@ -17,7 +17,7 @@ import clipboardIcon from '../assets/icon/cil-clipboard.svg'
|
||||
import twitterIcon from '../assets/icon/cib-twitter.svg'
|
||||
import { fade, fly } from 'svelte/transition'
|
||||
import { durationFormat } from '../utils/format.js'
|
||||
import { overlay } from '../stores.js'
|
||||
import { overlay, tiers } from '../stores.js'
|
||||
import QRCode from 'qrcode'
|
||||
|
||||
let tab = 'form' // form | invoice | success
|
||||
@ -50,12 +50,11 @@ const invoiceHexPlaceholder = 'lnbcxxxxxxt8l4pp5umz5kyakc0u8z3w2y568entyyq2gafgc
|
||||
let qrSrc = null
|
||||
let invoicePayments = []
|
||||
|
||||
let tierThresholds
|
||||
let tiers = []
|
||||
let tierThresholds = []
|
||||
|
||||
$: {
|
||||
if (tierThresholds) {
|
||||
tiers = [
|
||||
if ($tiers) {
|
||||
tierThresholds = [
|
||||
{
|
||||
title: 'Supporter',
|
||||
description: "Help to keep the lights on with a small donation",
|
||||
@ -64,26 +63,26 @@ $: {
|
||||
optional: true,
|
||||
min: 0.00005000,
|
||||
minSats: 5000,
|
||||
max: tierThresholds.hero.min,
|
||||
maxSats: btcToSats(tierThresholds.hero.min),
|
||||
max: $tiers.hero.min,
|
||||
maxSats: btcToSats($tiers.hero.min),
|
||||
},
|
||||
{
|
||||
title: 'Community Hero',
|
||||
description: "Add your Twitter profile to our Heroes Hall of Fame",
|
||||
description: "Add your Twitter profile to our Supporters page",
|
||||
emoji: '🦸',
|
||||
color: 'var(--bold-c)',
|
||||
min: tierThresholds.hero.min,
|
||||
minSats: btcToSats(tierThresholds.hero.min),
|
||||
max: tierThresholds.sponsor.min,
|
||||
maxSats: btcToSats(tierThresholds.sponsor.min),
|
||||
min: $tiers.hero.min,
|
||||
minSats: btcToSats($tiers.hero.min),
|
||||
max: $tiers.sponsor.min,
|
||||
maxSats: btcToSats($tiers.sponsor.min),
|
||||
},
|
||||
{
|
||||
title: 'Enterprise Sponsor',
|
||||
description: "Display your logo on Bitfeed, with a link to your website",
|
||||
emoji: '🕴️',
|
||||
color: 'var(--bold-a)',
|
||||
min: tierThresholds.sponsor.min,
|
||||
minSats: btcToSats(tierThresholds.sponsor.min),
|
||||
min: $tiers.sponsor.min,
|
||||
minSats: btcToSats($tiers.sponsor.min),
|
||||
max: Infinity,
|
||||
maxSats: Infinity,
|
||||
}
|
||||
@ -207,19 +206,8 @@ onMount(() => {
|
||||
console.log('error loading/parsing invoice')
|
||||
}
|
||||
}
|
||||
loadTiers()
|
||||
})
|
||||
|
||||
async function loadTiers () {
|
||||
try {
|
||||
const r = await fetch(`${config.donationRoot}/api/sponsorship/tiers.json`)
|
||||
tierThresholds = await r.json()
|
||||
} catch (e) {
|
||||
console.log('failed to load sponsorship tiers')
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
function resetInvoice () {
|
||||
invoicePaid = false
|
||||
invoiceExpired = false
|
||||
@ -426,7 +414,7 @@ async function copyInvoice () {
|
||||
}
|
||||
</script>
|
||||
|
||||
<Overlay name="donation" fullHeight>
|
||||
<Overlay name="donation" fullSize>
|
||||
<section class="donation-modal">
|
||||
<div class="tab-nav">
|
||||
<button class="to left" class:disabled={!canTabLeft} on:click={tabLeft}>←</button>
|
||||
@ -447,7 +435,7 @@ async function copyInvoice () {
|
||||
</p>
|
||||
|
||||
<div class="support-tiers">
|
||||
{#each tiers as tier}
|
||||
{#each tierThresholds as tier}
|
||||
<TierCard {...tier} active={btc >= tier.min && btc < tier.max} on:click={() => { setAmount(tier.min || 0.00005000, true) }} />
|
||||
{/each}
|
||||
</div>
|
||||
@ -456,7 +444,7 @@ async function copyInvoice () {
|
||||
|
||||
<div class="choose-amount">
|
||||
<div class="amount-slider">
|
||||
<SatoshiSlider value={sats} max={btcToSats(1)} thresholds={tiers} logScale on:input={(e) => { setAmount(e.detail)}} />
|
||||
<SatoshiSlider value={sats} max={btcToSats(1)} thresholds={tierThresholds} logScale on:input={(e) => { setAmount(e.detail)}} />
|
||||
</div>
|
||||
<div class="amount-input">
|
||||
{#if unit === 'sats'}
|
||||
@ -470,8 +458,8 @@ async function copyInvoice () {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if tierThresholds && btc >= tierThresholds.hero.min}
|
||||
{#if btc >= tierThresholds.sponsor.min}
|
||||
{#if $tiers && btc >= $tiers.hero.min}
|
||||
{#if btc >= $tiers.sponsor.min}
|
||||
<p class="credit-info">
|
||||
Enter your email or twitter handle so we can reach you to say thanks and confirm sponsorship details! Or leave these fields blank to donate anonymously.
|
||||
</p>
|
||||
@ -491,7 +479,7 @@ async function copyInvoice () {
|
||||
<input id="twitterHandle" type="text" bind:value={twitter}>
|
||||
</div>
|
||||
</div>
|
||||
{#if btc >= tierThresholds.sponsor.min}
|
||||
{#if btc >= $tiers.sponsor.min}
|
||||
<div class="field">
|
||||
<label for="twitterHandle">Email</label>
|
||||
<div class="text-input email-input">
|
||||
@ -752,14 +740,6 @@ async function copyInvoice () {
|
||||
}
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background: var(--bold-a);
|
||||
color: white;
|
||||
padding: 0.5em 2em;
|
||||
margin: 1em 2em 0.5em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.invoice-area {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -1,359 +0,0 @@
|
||||
<script>
|
||||
import analytics from '../utils/analytics.js'
|
||||
import config from '../config.js'
|
||||
import { onMount } from 'svelte'
|
||||
import Overlay from '../components/Overlay.svelte'
|
||||
import Icon from '../components/Icon.svelte'
|
||||
import boltIcon from '../assets/icon/cil-bolt-filled.svg'
|
||||
import tickIcon from '../assets/icon/cil-check-alt.svg'
|
||||
import timerIcon from '../assets/icon/cil-av-timer.svg'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { durationFormat } from '../utils/format.js'
|
||||
import { overlay } from '../stores.js'
|
||||
import QRCode from 'qrcode'
|
||||
|
||||
let amount = 5000
|
||||
let invoice = null
|
||||
let invoicePaid = false
|
||||
let invoiceExpired = false
|
||||
let invoicePoll
|
||||
let pollingEnabled = false
|
||||
|
||||
let invoiceAmountLabel
|
||||
let invoiceExpiryLabel
|
||||
let invoiceHexLabel
|
||||
const invoiceHexPlaceholder = 'lnbcxxxxxxt8l4pp5umz5kyakc0u8z3w2y568entyyq2gafgc3n5a7khdtk5m9fehkxnqdq6gf5hgen9v4jzqer0deshg6t0dccqzpgxqzjcsp5arlylwgraa2u75g4wh40swvxyvt0cpyrmnl4cha40uj5x2fr0t8q9qy9qsqzh3dtfag0ymaf8dpyxrly9p04jwlgdaxkh6g9ysaxyzz7jtrrkpsxv52mlzl6wgn6l6eur9yrl5q2quh5p8kagmng45gqjz9e2c6uxgqx5ezjr'
|
||||
let qrSrc = null
|
||||
$: {
|
||||
if (invoice && invoice.id && invoice.amount && invoice.BOLT11) {
|
||||
invoiceAmountLabel = `${Number.parseInt(invoice.amount) / 1000} sats`
|
||||
invoiceHexLabel = invoice.BOLT11
|
||||
setQR(invoice.BOLT11)
|
||||
} else {
|
||||
stopExpiryTimer()
|
||||
invoiceAmountLabel = `${amount || 5000} sats`
|
||||
invoiceHexLabel = invoiceHexPlaceholder
|
||||
qrSrc = null
|
||||
}
|
||||
}
|
||||
|
||||
async function setQR(invoice) {
|
||||
try {
|
||||
qrSrc = await QRCode.toDataURL(invoice.toUpperCase())
|
||||
} catch (err) {
|
||||
console.log('error generating QR code: ', err)
|
||||
}
|
||||
}
|
||||
|
||||
let expiryTick
|
||||
let expiresIn = null // time till expiry in seconds
|
||||
$: {
|
||||
invoiceExpiryLabel = expiresIn == null ? '' : durationFormat.format(expiresIn * 1000)
|
||||
}
|
||||
$: {
|
||||
if ($overlay === 'lightning') {
|
||||
startExpiryTimer()
|
||||
stopPollingInvoice()
|
||||
pollingEnabled = true
|
||||
pollInvoice()
|
||||
} else {
|
||||
stopExpiryTimer()
|
||||
stopPollingInvoice()
|
||||
checkResetInvoice()
|
||||
}
|
||||
}
|
||||
function expiryTimer () {
|
||||
if (invoice && invoice.expiresAt) expiresIn = Math.round(((invoice.expiresAt * 1000) - 500000 - Date.now()) / 1000)
|
||||
else expiresIn = null
|
||||
}
|
||||
function stopExpiryTimer () {
|
||||
if (expiryTick) clearInterval(expiryTick)
|
||||
expiryTick = null
|
||||
}
|
||||
function startExpiryTimer () {
|
||||
if (!expiryTick && $overlay === 'lightning' && invoice && invoice.id) {
|
||||
expiryTick = setInterval(expiryTimer, 200)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// check for existing invoice in local storage:
|
||||
const loadedInvoiceJSON = localStorage.getItem(`lightning-invoice`)
|
||||
localStorage.removeItem('lightning-invoice')
|
||||
if (loadedInvoiceJSON) {
|
||||
try {
|
||||
const loadedInvoice = JSON.parse(loadedInvoiceJSON)
|
||||
if (loadedInvoice && loadedInvoice.id && loadedInvoice.status && loadedInvoice.status === 'Unpaid' && (loadedInvoice.expiresAt * 1000) > Date.now()) {
|
||||
invoice = loadedInvoice
|
||||
processInvoice()
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('error loading/parsing invoice')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function resetInvoice () {
|
||||
invoicePaid = false
|
||||
invoiceExpired = false
|
||||
invoice = null
|
||||
qrSrc = null
|
||||
}
|
||||
|
||||
function checkResetInvoice () {
|
||||
if (invoice && invoice.status !== 'Unpaid') resetInvoice()
|
||||
}
|
||||
|
||||
function stopPollingInvoice() {
|
||||
pollingEnabled = false
|
||||
if (invoicePoll) clearTimeout(invoicePoll)
|
||||
invoicePoll = null
|
||||
}
|
||||
|
||||
function processInvoice () {
|
||||
if (invoice) {
|
||||
startExpiryTimer()
|
||||
if (invoice.status === 'Paid') {
|
||||
invoicePaid = true
|
||||
invoiceExpired = false
|
||||
analytics.trackEvent('donations', 'lightning', 'paid', invoice.amount / 1000)
|
||||
localStorage.removeItem('lightning-invoice')
|
||||
} else if (invoice.status === 'Expired' || (invoice.expiresAt * 1000) - 500000 < Date.now()) {
|
||||
invoicePaid = false
|
||||
invoiceExpired = true
|
||||
localStorage.removeItem('lightning-invoice')
|
||||
} else if (invoice.status === 'Unpaid') {
|
||||
invoicePaid = false
|
||||
invoiceExpired = false
|
||||
localStorage.setItem('lightning-invoice', JSON.stringify(invoice))
|
||||
invoicePoll = setTimeout(pollInvoice, 2000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function pollInvoice () {
|
||||
if (pollingEnabled && invoice && invoice.status === 'Unpaid') {
|
||||
const response = await fetch(`${config.lightningRoot}/api/lightning/invoice/${invoice.id}`, {
|
||||
method: 'GET'
|
||||
})
|
||||
invoice = await response.json()
|
||||
processInvoice()
|
||||
}
|
||||
}
|
||||
|
||||
async function generateInvoice () {
|
||||
if (amount) {
|
||||
analytics.trackEvent('donations', 'lightning', 'generate', amount)
|
||||
resetInvoice()
|
||||
const response = await fetch(`${config.lightningRoot}/api/lightning/invoice`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ amount })
|
||||
})
|
||||
invoice = await response.json()
|
||||
if (invoice && invoice.amount) analytics.trackEvent('donations', 'lightning', 'generate-success', invoice.amount / 1000)
|
||||
processInvoice()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style type="text/scss">
|
||||
.info {
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.lightning-form {
|
||||
.sats-input {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
.units-label {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 1.4em;
|
||||
bottom: 0;
|
||||
text-align: right;
|
||||
color: var(--dark-c);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
input {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.lightning-button {
|
||||
background: var(--bold-a);
|
||||
color: white;
|
||||
padding: 5px 8px;
|
||||
margin: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.invoice-area {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.invoice-info {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
width: 280px;
|
||||
min-width: 200px;
|
||||
margin: .5em;
|
||||
flex: 1;
|
||||
|
||||
.field {
|
||||
word-break: break-all;
|
||||
visibility: hidden;
|
||||
|
||||
.field-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hex {
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
background: var(--dark-d);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.placeholder-label {
|
||||
color: var(--light-a);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&.ready {
|
||||
.field {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
width: 280px;
|
||||
min-width: 200px;
|
||||
min-height: 240px;
|
||||
margin: .5em;
|
||||
|
||||
border-radius: 10px;
|
||||
background: var(--dark-d);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.invoice-status {
|
||||
font-weight: bold;
|
||||
font-size: 1.6em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.invoice-icon {
|
||||
font-size: 8em;
|
||||
}
|
||||
|
||||
.qr-image-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.qr-image {
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
}
|
||||
}
|
||||
|
||||
&.paid {
|
||||
background: var(--light-good);
|
||||
}
|
||||
|
||||
&.expired {
|
||||
background: var(--light-unsure);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<Overlay name="lightning">
|
||||
<section class="info">
|
||||
<h2>Donate with Lightning</h2>
|
||||
<p>
|
||||
Enter the amount you would like to donate, then click the button to generate a payment request:
|
||||
</p>
|
||||
|
||||
<div class="lightning-form">
|
||||
<div class="sats-input">
|
||||
<input type="number" bind:value={amount}>
|
||||
<span class="units-label">sats</span>
|
||||
</div>
|
||||
<button class="lightning-button" on:click={generateInvoice} >
|
||||
<Icon icon={boltIcon} color="white" inline />
|
||||
{#if invoice && invoice.id }
|
||||
Generate New Request
|
||||
{:else}
|
||||
Generate Payment Request
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="invoice-area">
|
||||
<div class="invoice-info" class:ready={invoice && invoice.id}>
|
||||
<p class="field invoice-amount"><span class="field-label">Amount:</span> { invoiceAmountLabel }</p>
|
||||
<p class="field invoice-expires"><span class="field-label">Expiry:</span> { invoiceExpiryLabel }</p>
|
||||
<p class="field invoice"><span class="field-label">Payment request:</span> <span class="hex">{ invoiceHexLabel }</span></p>
|
||||
{#if !invoice || !invoice.id }
|
||||
<div class="placeholder-overlay" transition:fade={{ duration: 300 }}>
|
||||
<p class="placeholder-label">Payment Request</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="qr-container" class:paid={invoicePaid} class:expired={invoiceExpired}>
|
||||
{#if invoicePaid }
|
||||
<div class="invoice-icon"><Icon icon={tickIcon} color="white" /></div>
|
||||
<h3 class="invoice-status">Received, Thanks!</h3>
|
||||
{:else if invoiceExpired}
|
||||
<div class="invoice-icon"><Icon icon={timerIcon} color="white" /></div>
|
||||
<h3 class="invoice-status">Invoice Expired</h3>
|
||||
{:else}
|
||||
<div class="invoice-icon"><Icon icon={boltIcon} color="white" /></div>
|
||||
{/if}
|
||||
{#if qrSrc && !invoicePaid && !invoiceExpired}
|
||||
<div class="qr-image-wrapper">
|
||||
<img src={qrSrc} class="qr-image" alt="invoice qr code">
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Overlay>
|
@ -13,6 +13,19 @@
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
|
||||
.mononaut .backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: black;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mononaut .inner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -25,7 +38,6 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(45deg, darkviolet, transparent 100%);
|
||||
}
|
||||
|
||||
@ -93,148 +105,150 @@
|
||||
|
||||
<div class="mononaut">
|
||||
<div class="aspect">
|
||||
<div class="inner">
|
||||
<svg class="avatar"
|
||||
width="334.47833"
|
||||
height="360.19266"
|
||||
viewBox="0 0 88.497389 95.30098"
|
||||
version="1.1"
|
||||
id="svg8">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="linearGradient1874">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.65887851"
|
||||
offset="0"
|
||||
id="stop1870" />
|
||||
<stop
|
||||
style="stop-color:#a7a7a7;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop1872" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient1874"
|
||||
id="linearGradient5760"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.88559909,0,0,0.88559909,2.1865141,-1.4202934)"
|
||||
x1="74.399529"
|
||||
y1="16.307785"
|
||||
x2="15.273721"
|
||||
y2="83.253212" />
|
||||
</defs>
|
||||
<g
|
||||
style="display:inline;opacity:1"
|
||||
transform="translate(-10.793731,-5.4097061)">
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#576c78;fill-opacity:1;stroke:none;stroke-width:0.71934628;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 61.651061,20.553894 1.289922,1.959392 C 59.55236,29.73164 61.960527,32.936832 62.291817,37.57986 69.580914,51.34937 46.284973,74.005931 38.794339,77.275048 18.383317,86.182975 8.0562438,56.701838 11.97544,33.820946 13.409936,25.446125 24.242237,4.0376168 61.651061,20.553894 Z"
|
||||
id="visor-back" />
|
||||
<path
|
||||
style="fill:#784421;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 40.023612,69.217407 c -1.74228,14.114255 19.884147,12.868589 29.800552,15.568451 l 13.964832,-13.8312 C 77.167852,62.564819 61.700977,53.269993 49.110776,59.796156 Z"
|
||||
id="monkey-body" />
|
||||
<g id="monkey">
|
||||
<g
|
||||
style="display:inline"
|
||||
id="monkey-inner"
|
||||
transform="matrix(0.73793571,0.19032465,-0.19032465,0.73793571,8.5451128,-1.5971534)">
|
||||
<path
|
||||
id="ear-right"
|
||||
d="m 35.094888,56.21721 c -6.89655,2.534431 -14.53992,-2.389386 -17.231469,-4.852391 1.23186,-3.401999 4.604949,-5.92599 8.722879,-7.236581 1.66704,-0.53056 3.45615,-0.862263 5.27469,-0.97286 9.17189,1.090452 10.68783,11.787228 3.2339,13.0061832 z"
|
||||
style="display:inline;opacity:1;fill:#deaa87;stroke:none;stroke-width:0.21957469px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="head"
|
||||
d="m 30.308872,23.862935 c -7.896397,9.705521 -12.701615,16.830049 -7.269507,28.957925 4.3062,9.614141 -0.284703,11.721588 5.683872,18.251035 5.45108,5.55247 10.178022,5.892849 13.988071,13.594771 11.299136,14.269193 22.409786,3.337588 26.080359,-5.85863 C 81.867477,56.071791 88.32321,34.989715 70.681545,21.166667 55.190177,12.328814 46.139739,12.585053 30.308872,23.862935 Z"
|
||||
style="display:inline;fill:#784421;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<g id="ear-left">
|
||||
<div class="backdrop">
|
||||
<div class="inner">
|
||||
<svg class="avatar"
|
||||
width="334.47833"
|
||||
height="360.19266"
|
||||
viewBox="0 0 88.497389 95.30098"
|
||||
version="1.1"
|
||||
id="svg8">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="linearGradient1874">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.65887851"
|
||||
offset="0"
|
||||
id="stop1870" />
|
||||
<stop
|
||||
style="stop-color:#a7a7a7;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop1872" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient1874"
|
||||
id="linearGradient5760"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.88559909,0,0,0.88559909,2.1865141,-1.4202934)"
|
||||
x1="74.399529"
|
||||
y1="16.307785"
|
||||
x2="15.273721"
|
||||
y2="83.253212" />
|
||||
</defs>
|
||||
<g
|
||||
style="display:inline;opacity:1"
|
||||
transform="translate(-10.793731,-5.4097061)">
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#576c78;fill-opacity:1;stroke:none;stroke-width:0.71934628;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 61.651061,20.553894 1.289922,1.959392 C 59.55236,29.73164 61.960527,32.936832 62.291817,37.57986 69.580914,51.34937 46.284973,74.005931 38.794339,77.275048 18.383317,86.182975 8.0562438,56.701838 11.97544,33.820946 13.409936,25.446125 24.242237,4.0376168 61.651061,20.553894 Z"
|
||||
id="visor-back" />
|
||||
<path
|
||||
style="fill:#784421;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 40.023612,69.217407 c -1.74228,14.114255 19.884147,12.868589 29.800552,15.568451 l 13.964832,-13.8312 C 77.167852,62.564819 61.700977,53.269993 49.110776,59.796156 Z"
|
||||
id="monkey-body" />
|
||||
<g id="monkey">
|
||||
<g
|
||||
style="display:inline"
|
||||
id="monkey-inner"
|
||||
transform="matrix(0.73793571,0.19032465,-0.19032465,0.73793571,8.5451128,-1.5971534)">
|
||||
<path
|
||||
id="ear-left-outer"
|
||||
d="m 77.833423,50.323879 c 6.47703,-6.03607 5.34152,-16.93279 4.15774,-21.16667 -6.07997,-0.73952 -12.59596,4.00709 -16.44197,10.58333 -3.8176,10.45458 6.85846,17.904101 12.28423,10.58334 z"
|
||||
style="display:inline;opacity:1;fill:#deaa87;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="ear-left-inner"
|
||||
d="m 76.76206,47.72753 c 4.556293,-4.246097 3.757513,-11.911438 2.924778,-14.889778 -4.276979,-0.520219 -8.860678,2.818803 -11.56617,7.444885 -2.685506,7.354316 4.824612,12.594712 8.641392,7.444893 z"
|
||||
style="display:inline;opacity:1;fill:#e9c6af;stroke:none;stroke-width:0.18612219px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
<path
|
||||
id="face"
|
||||
d="m 38.742559,49.51488 c 1.45629,-4.066862 6.298841,-7.992738 12.284225,-6.992558 15.425741,4.293009 4.803701,19.409911 -2.976563,18.473584 18.639296,3.573862 22.324617,26.346482 6.174076,27.17782 -12.717835,0.34267 -8.581608,-9.318628 -16.409445,-12.0269 -5.597227,-1.936527 -2.05593,-13.668839 -2.663067,-12.26885 -6.589222,8.759543 -12.047332,-0.849447 -11.528273,-9.921875 1.774561,-2.871137 4.819803,-3.699497 8.882439,-1.039433 4.789983,2.254962 5.346565,-0.781825 6.236608,-3.401788 z"
|
||||
style="display:inline;fill:#deaa87;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="muzzle"
|
||||
d="M 60.072997,75.352317 C 55.003352,70.027191 55.75107,68.927121 53.34848,66.485233 51.841457,64.88184 47.05277,62.630654 42.069361,63.849024 c -1.699146,0.415416 -4.449014,1.385616 -6.199235,3.299959 -1.682918,1.84073 -1.025774,7.464219 2.777696,9.202331 3.896358,1.78056 4.087632,2.791893 5.88943,6.981181 3.630032,8.440037 20.554285,1.586752 15.535745,-7.980178 z"
|
||||
style="display:inline;fill:#502d16;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="face-inner"
|
||||
d="m 38.957003,52.607487 c 1.042028,-2.641197 2.728748,-4.7652 10.223515,-5.17104 7.241178,0.814991 8.551291,5.869414 4.817338,8.574224 -3.147594,3.346452 -6.33337,3.393592 -8.935703,2.930689 -3.51048,-0.180022 -6.925621,-0.202977 -8.446902,2.89418 -5.220943,5.911145 -10.282881,0.05394 -10.31162,-5.662332 1.650862,-1.605418 3.81304,-2.437929 7.455186,-1.033305 4.247946,1.104042 4.568382,-0.833998 5.198186,-2.532416 z"
|
||||
style="display:inline;fill:#e9c6af;stroke:none;stroke-width:0.19504021px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="mouth"
|
||||
d="m 42.472928,76.398309 c -0.995545,-0.575317 -0.253436,-2.698662 1.044456,-2.998078 4.228867,-0.975576 12.597117,-0.526341 13.187645,0.826512 1.454954,3.573662 -4.175521,8.756925 -10.256469,8.652853 -2.374181,0.259391 -1.069921,-4.879679 -3.975632,-6.481287 z"
|
||||
style="display:inline;fill:#28170b;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="tongue"
|
||||
d="m 46.929215,76.246574 c 0.615399,-1.75537 8.22103,-2.28337 7.216279,4.07586 -0.212053,1.37525 3.358405,1.46548 4.409948,3.34086 1.055887,4.79323 -3.65884,4.36937 -6.94901,2.77293 -4.202979,-2.19163 -5.874033,-5.47644 -4.677217,-10.18965 z"
|
||||
style="display:inline;opacity:1;fill:#da789c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<g id="eye-left" class="monkey-eye">
|
||||
id="ear-right"
|
||||
d="m 35.094888,56.21721 c -6.89655,2.534431 -14.53992,-2.389386 -17.231469,-4.852391 1.23186,-3.401999 4.604949,-5.92599 8.722879,-7.236581 1.66704,-0.53056 3.45615,-0.862263 5.27469,-0.97286 9.17189,1.090452 10.68783,11.787228 3.2339,13.0061832 z"
|
||||
style="display:inline;opacity:1;fill:#deaa87;stroke:none;stroke-width:0.21957469px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="pupil-left"
|
||||
d="m 40.867285,57.273873 c 1.612375,-2.042551 0.701258,-3.847919 2.827551,-5.020606 2.852266,-1.245289 3.874257,0.02912 6.461358,-0.487557 -1.800708,1.927123 -1.087836,3.182768 -3.147949,4.379153 -2.48621,1.342919 -2.404803,0.48141 -6.14096,1.12901 z"
|
||||
style="display:inline;fill:#28170b;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
id="head"
|
||||
d="m 30.308872,23.862935 c -7.896397,9.705521 -12.701615,16.830049 -7.269507,28.957925 4.3062,9.614141 -0.284703,11.721588 5.683872,18.251035 5.45108,5.55247 10.178022,5.892849 13.988071,13.594771 11.299136,14.269193 22.409786,3.337588 26.080359,-5.85863 C 81.867477,56.071791 88.32321,34.989715 70.681545,21.166667 55.190177,12.328814 46.139739,12.585053 30.308872,23.862935 Z"
|
||||
style="display:inline;fill:#784421;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<g id="ear-left">
|
||||
<path
|
||||
id="ear-left-outer"
|
||||
d="m 77.833423,50.323879 c 6.47703,-6.03607 5.34152,-16.93279 4.15774,-21.16667 -6.07997,-0.73952 -12.59596,4.00709 -16.44197,10.58333 -3.8176,10.45458 6.85846,17.904101 12.28423,10.58334 z"
|
||||
style="display:inline;opacity:1;fill:#deaa87;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="ear-left-inner"
|
||||
d="m 76.76206,47.72753 c 4.556293,-4.246097 3.757513,-11.911438 2.924778,-14.889778 -4.276979,-0.520219 -8.860678,2.818803 -11.56617,7.444885 -2.685506,7.354316 4.824612,12.594712 8.641392,7.444893 z"
|
||||
style="display:inline;opacity:1;fill:#e9c6af;stroke:none;stroke-width:0.18612219px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
<path
|
||||
id="gleam-left"
|
||||
d="m 44.577763,52.357009 0.175397,1.00226 1.720546,-0.91874 z"
|
||||
style="display:inline;opacity:1;fill:#f4e3d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
<g id="eye-right" class="monkey-eye">
|
||||
id="face"
|
||||
d="m 38.742559,49.51488 c 1.45629,-4.066862 6.298841,-7.992738 12.284225,-6.992558 15.425741,4.293009 4.803701,19.409911 -2.976563,18.473584 18.639296,3.573862 22.324617,26.346482 6.174076,27.17782 -12.717835,0.34267 -8.581608,-9.318628 -16.409445,-12.0269 -5.597227,-1.936527 -2.05593,-13.668839 -2.663067,-12.26885 -6.589222,8.759543 -12.047332,-0.849447 -11.528273,-9.921875 1.774561,-2.871137 4.819803,-3.699497 8.882439,-1.039433 4.789983,2.254962 5.346565,-0.781825 6.236608,-3.401788 z"
|
||||
style="display:inline;fill:#deaa87;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="pupil-right"
|
||||
d="m 36.526068,59.323301 c -2.29866,-0.610735 -2.053402,-2.430637 -4.251391,-2.123454 -2.768843,0.651852 -2.383872,2.077275 -4.559515,3.116838 2.373186,0.423104 2.10538,1.799062 4.267942,1.545474 2.554554,-0.379861 1.455015,-0.965477 4.542964,-2.538858 z"
|
||||
style="display:inline;fill:#28170b;stroke:none;stroke-width:0.24182333px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
id="muzzle"
|
||||
d="M 60.072997,75.352317 C 55.003352,70.027191 55.75107,68.927121 53.34848,66.485233 51.841457,64.88184 47.05277,62.630654 42.069361,63.849024 c -1.699146,0.415416 -4.449014,1.385616 -6.199235,3.299959 -1.682918,1.84073 -1.025774,7.464219 2.777696,9.202331 3.896358,1.78056 4.087632,2.791893 5.88943,6.981181 3.630032,8.440037 20.554285,1.586752 15.535745,-7.980178 z"
|
||||
style="display:inline;fill:#502d16;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="gleam-right"
|
||||
d="m 31.608594,58.036779 0.803199,0.56105 0.750048,-0.48428 z"
|
||||
style="display:inline;opacity:1;fill:#f4e3d7;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
id="face-inner"
|
||||
d="m 38.957003,52.607487 c 1.042028,-2.641197 2.728748,-4.7652 10.223515,-5.17104 7.241178,0.814991 8.551291,5.869414 4.817338,8.574224 -3.147594,3.346452 -6.33337,3.393592 -8.935703,2.930689 -3.51048,-0.180022 -6.925621,-0.202977 -8.446902,2.89418 -5.220943,5.911145 -10.282881,0.05394 -10.31162,-5.662332 1.650862,-1.605418 3.81304,-2.437929 7.455186,-1.033305 4.247946,1.104042 4.568382,-0.833998 5.198186,-2.532416 z"
|
||||
style="display:inline;fill:#e9c6af;stroke:none;stroke-width:0.19504021px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="mouth"
|
||||
d="m 42.472928,76.398309 c -0.995545,-0.575317 -0.253436,-2.698662 1.044456,-2.998078 4.228867,-0.975576 12.597117,-0.526341 13.187645,0.826512 1.454954,3.573662 -4.175521,8.756925 -10.256469,8.652853 -2.374181,0.259391 -1.069921,-4.879679 -3.975632,-6.481287 z"
|
||||
style="display:inline;fill:#28170b;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="tongue"
|
||||
d="m 46.929215,76.246574 c 0.615399,-1.75537 8.22103,-2.28337 7.216279,4.07586 -0.212053,1.37525 3.358405,1.46548 4.409948,3.34086 1.055887,4.79323 -3.65884,4.36937 -6.94901,2.77293 -4.202979,-2.19163 -5.874033,-5.47644 -4.677217,-10.18965 z"
|
||||
style="display:inline;opacity:1;fill:#da789c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<g id="eye-left" class="monkey-eye">
|
||||
<path
|
||||
id="pupil-left"
|
||||
d="m 40.867285,57.273873 c 1.612375,-2.042551 0.701258,-3.847919 2.827551,-5.020606 2.852266,-1.245289 3.874257,0.02912 6.461358,-0.487557 -1.800708,1.927123 -1.087836,3.182768 -3.147949,4.379153 -2.48621,1.342919 -2.404803,0.48141 -6.14096,1.12901 z"
|
||||
style="display:inline;fill:#28170b;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="gleam-left"
|
||||
d="m 44.577763,52.357009 0.175397,1.00226 1.720546,-0.91874 z"
|
||||
style="display:inline;opacity:1;fill:#f4e3d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
<g id="eye-right" class="monkey-eye">
|
||||
<path
|
||||
id="pupil-right"
|
||||
d="m 36.526068,59.323301 c -2.29866,-0.610735 -2.053402,-2.430637 -4.251391,-2.123454 -2.768843,0.651852 -2.383872,2.077275 -4.559515,3.116838 2.373186,0.423104 2.10538,1.799062 4.267942,1.545474 2.554554,-0.379861 1.455015,-0.965477 4.542964,-2.538858 z"
|
||||
style="display:inline;fill:#28170b;stroke:none;stroke-width:0.24182333px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="gleam-right"
|
||||
d="m 31.608594,58.036779 0.803199,0.56105 0.750048,-0.48428 z"
|
||||
style="display:inline;opacity:1;fill:#f4e3d7;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="helmet">
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#22676d;fill-opacity:1;stroke:none;stroke-width:0.23431474px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 88.570396,58.724343 C 94.62432,54.067131 100.74234,57.883387 98.984914,64.996727 95.801343,75.095656 65.800904,103.41999 49.515946,100.50077 42.891637,97.739253 46.341916,92.001209 46.32058,88.666096 Z"
|
||||
id="collar-one" />
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#bae5e9;fill-opacity:1;stroke:none;stroke-width:0.23431474px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 83.372436,52.659423 C 89.426364,48.002211 95.544387,51.818467 93.786951,58.931806 90.603382,69.030735 60.602943,97.355073 44.317985,94.435853 37.693676,91.674335 41.143955,85.936289 41.122619,82.601171 Z"
|
||||
id="collar-two" />
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#bae5e9;fill-opacity:1;stroke:none;stroke-width:0.23431474px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 28.23215,79.916507 C 40.765843,80.993896 69.099088,55.590613 62.481792,37.486127 59.618529,29.652427 61.621394,25.326412 63.0048283,22.398281 63.451782,21.570257 62.431545,21.170567 61.660924,20.582972 58.721936,18.342009 33.314523,9.1801348 21.414153,19.148253 21.040806,19.46098 16.493514,24.225612 16.466931,23.906261 16.284334,21.712654 15.7061,18.778538 16.842454,17.601749 28.272105,5.7653958 43.244764,5.2528968 51.182613,5.4345318 74.64048,5.9712998 76.979428,18.408564 78.673549,18.329111 92.064018,29.246088 95.017703,40.802051 86.539829,53.0057904 73.279621,74.932862 61.561047,80.045597 49.802622,87.117228 40.441364,90.882866 24.536108,90.048641 21.648484,74.844071 c 2.995365,2.722164 2.788093,4.347644 6.583666,5.072436 z"
|
||||
id="helmet-case" />
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#22676d;fill-opacity:1;stroke:none;stroke-width:0.23431474px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 38.50793,85.407931 c 1.842704,-0.411794 2.387542,-0.346584 3.341095,-0.04751 4.084771,1.281151 4.96339,0.781298 6.513681,0.03974 7.412447,-4.138988 13.715592,-9.704221 19.57263,-15.84302 l 0.241658,-11.116412 c -0.0621,-0.767793 -0.0058,-1.485505 0.329786,-2.085218 l 9.16256,-15.933883 c 1.483253,-4.949488 3.702339,-12.396219 3.313375,-17.72015 -0.162002,-2.217387 -1.329754,-1.991515 -2.30917,-4.372385 13.390473,10.916977 16.344158,22.47294 7.866281,34.728794 -13.26021,21.874955 -24.978782,26.987686 -36.737206,34.059324 -9.361257,3.765637 -18.488123,1.257736 -21.626797,-1.059526 4.264979,0.370603 6.29984,0.400653 10.332107,-0.649754 z"
|
||||
id="helmet-base" />
|
||||
<ellipse
|
||||
style="display:inline;opacity:1;fill:#133a3e;fill-opacity:1;stroke:none;stroke-width:0.24935082;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="helmet-joint"
|
||||
cx="53.400845"
|
||||
cy="67.588089"
|
||||
rx="6.4156117"
|
||||
ry="9.9880085"
|
||||
transform="matrix(0.88495557,-0.46567547,0.48925229,0.8721423,0,0)" />
|
||||
<path
|
||||
style="display:inline;opacity:0.98000004;fill:url(#linearGradient5760);fill-opacity:1;stroke:#000000;stroke-width:0.71934628;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 61.651061,20.553894 1.289918,1.959392 C 59.552359,29.73164 61.960527,32.936833 62.291817,37.57986 69.580918,51.349372 46.284972,74.005931 38.794337,77.275049 18.383317,86.182977 8.0562438,56.70184 11.97544,33.820947 13.409936,25.446126 24.242237,4.0376178 61.651061,20.553894 Z"
|
||||
id="visor" />
|
||||
<path
|
||||
style="display:inline;opacity:0.753;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.23431474;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 35.84597,19.072302 c 7.774683,-2.280832 13.824223,-0.609922 19.290532,2.95867 l 1.301816,4.023793 c -8.1156,-3.761618 -15.343244,-5.578561 -23.314324,-3.432059 z"
|
||||
id="helmet-gleam" />
|
||||
</g>
|
||||
</g>
|
||||
<g id="helmet">
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#22676d;fill-opacity:1;stroke:none;stroke-width:0.23431474px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 88.570396,58.724343 C 94.62432,54.067131 100.74234,57.883387 98.984914,64.996727 95.801343,75.095656 65.800904,103.41999 49.515946,100.50077 42.891637,97.739253 46.341916,92.001209 46.32058,88.666096 Z"
|
||||
id="collar-one" />
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#bae5e9;fill-opacity:1;stroke:none;stroke-width:0.23431474px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 83.372436,52.659423 C 89.426364,48.002211 95.544387,51.818467 93.786951,58.931806 90.603382,69.030735 60.602943,97.355073 44.317985,94.435853 37.693676,91.674335 41.143955,85.936289 41.122619,82.601171 Z"
|
||||
id="collar-two" />
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#bae5e9;fill-opacity:1;stroke:none;stroke-width:0.23431474px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 28.23215,79.916507 C 40.765843,80.993896 69.099088,55.590613 62.481792,37.486127 59.618529,29.652427 61.621394,25.326412 63.0048283,22.398281 63.451782,21.570257 62.431545,21.170567 61.660924,20.582972 58.721936,18.342009 33.314523,9.1801348 21.414153,19.148253 21.040806,19.46098 16.493514,24.225612 16.466931,23.906261 16.284334,21.712654 15.7061,18.778538 16.842454,17.601749 28.272105,5.7653958 43.244764,5.2528968 51.182613,5.4345318 74.64048,5.9712998 76.979428,18.408564 78.673549,18.329111 92.064018,29.246088 95.017703,40.802051 86.539829,53.0057904 73.279621,74.932862 61.561047,80.045597 49.802622,87.117228 40.441364,90.882866 24.536108,90.048641 21.648484,74.844071 c 2.995365,2.722164 2.788093,4.347644 6.583666,5.072436 z"
|
||||
id="helmet-case" />
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#22676d;fill-opacity:1;stroke:none;stroke-width:0.23431474px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 38.50793,85.407931 c 1.842704,-0.411794 2.387542,-0.346584 3.341095,-0.04751 4.084771,1.281151 4.96339,0.781298 6.513681,0.03974 7.412447,-4.138988 13.715592,-9.704221 19.57263,-15.84302 l 0.241658,-11.116412 c -0.0621,-0.767793 -0.0058,-1.485505 0.329786,-2.085218 l 9.16256,-15.933883 c 1.483253,-4.949488 3.702339,-12.396219 3.313375,-17.72015 -0.162002,-2.217387 -1.329754,-1.991515 -2.30917,-4.372385 13.390473,10.916977 16.344158,22.47294 7.866281,34.728794 -13.26021,21.874955 -24.978782,26.987686 -36.737206,34.059324 -9.361257,3.765637 -18.488123,1.257736 -21.626797,-1.059526 4.264979,0.370603 6.29984,0.400653 10.332107,-0.649754 z"
|
||||
id="helmet-base" />
|
||||
<ellipse
|
||||
style="display:inline;opacity:1;fill:#133a3e;fill-opacity:1;stroke:none;stroke-width:0.24935082;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="helmet-joint"
|
||||
cx="53.400845"
|
||||
cy="67.588089"
|
||||
rx="6.4156117"
|
||||
ry="9.9880085"
|
||||
transform="matrix(0.88495557,-0.46567547,0.48925229,0.8721423,0,0)" />
|
||||
<path
|
||||
style="display:inline;opacity:0.98000004;fill:url(#linearGradient5760);fill-opacity:1;stroke:#000000;stroke-width:0.71934628;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 61.651061,20.553894 1.289918,1.959392 C 59.552359,29.73164 61.960527,32.936833 62.291817,37.57986 69.580918,51.349372 46.284972,74.005931 38.794337,77.275049 18.383317,86.182977 8.0562438,56.70184 11.97544,33.820947 13.409936,25.446126 24.242237,4.0376178 61.651061,20.553894 Z"
|
||||
id="visor" />
|
||||
<path
|
||||
style="display:inline;opacity:0.753;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.23431474;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 35.84597,19.072302 c 7.774683,-2.280832 13.824223,-0.609922 19.290532,2.95867 l 1.301816,4.023793 c -8.1156,-3.761618 -15.343244,-5.578561 -23.314324,-3.432059 z"
|
||||
id="helmet-gleam" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,7 +8,7 @@ import { fade, fly } from 'svelte/transition'
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let name = 'none'
|
||||
export let fullHeight = false
|
||||
export let fullSize = false
|
||||
|
||||
let open
|
||||
$: {
|
||||
@ -89,10 +89,12 @@ function close () {
|
||||
max-width: 95%;
|
||||
}
|
||||
|
||||
&.full-height {
|
||||
&.full-size {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
.overlay-inner {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -102,7 +104,7 @@ function close () {
|
||||
{#if open}
|
||||
<div class="overlay-wrapper" >
|
||||
<div class="overlay-background" on:click={close} transition:fade={{ duration: 500 }} />
|
||||
<div class="overlay-outer" class:full-height={fullHeight} transition:fly={{ duration: 500, y: 50 }}>
|
||||
<div class="overlay-outer" class:full-size={fullSize} transition:fly={{ duration: 500, y: 50 }}>
|
||||
<div class="overlay-inner" id="{name}Overlay">
|
||||
<slot />
|
||||
</div>
|
||||
|
@ -18,8 +18,8 @@ let settingConfig = {
|
||||
showFPS: {
|
||||
label: 'FPS'
|
||||
},
|
||||
showDonation: {
|
||||
label: 'Donation Info'
|
||||
showMessages: {
|
||||
label: 'Message Bar'
|
||||
},
|
||||
vbytes: {
|
||||
label: 'Size by',
|
||||
|
@ -13,6 +13,7 @@ import questionIcon from '../assets/icon/help-circle.svg'
|
||||
import infoIcon from '../assets/icon/info.svg'
|
||||
import atIcon from '../assets/icon/cil-at.svg'
|
||||
import gridIcon from '../assets/icon/grid-icon.svg'
|
||||
import peopleIcon from '../assets/icon/cil-people.svg'
|
||||
import MempoolLegend from '../components/MempoolLegend.svelte'
|
||||
import ContactTab from '../components/ContactTab.svelte'
|
||||
|
||||
@ -35,6 +36,10 @@ function openAbout () {
|
||||
$overlay = 'about'
|
||||
}
|
||||
|
||||
function openSupporters () {
|
||||
$overlay = 'supporters'
|
||||
}
|
||||
|
||||
function showBlock () {
|
||||
analytics.trackEvent('viz', 'block', 'show')
|
||||
$blockVisible = true
|
||||
@ -83,6 +88,11 @@ function showBlock () {
|
||||
<Icon icon={questionIcon} color="var(--bold-a)" />
|
||||
</span>
|
||||
</SidebarTab>
|
||||
<SidebarTab on:click={openSupporters} tooltip="Supporters">
|
||||
<span slot="tab">
|
||||
<Icon icon={peopleIcon} color="var(--bold-a)" />
|
||||
</span>
|
||||
</SidebarTab>
|
||||
{#if config.dev && config.debug}
|
||||
<SidebarTab open={$sidebarToggle === 'dev'} on:click={() => {settings('dev')}} tooltip="Debug">
|
||||
<span slot="tab">
|
||||
|
126
client/src/components/SupportersOverlay.svelte
Normal file
126
client/src/components/SupportersOverlay.svelte
Normal file
@ -0,0 +1,126 @@
|
||||
<script>
|
||||
import config from '../config.js'
|
||||
import { onMount } from 'svelte'
|
||||
import Overlay from '../components/Overlay.svelte'
|
||||
import { overlay, tiers, sponsors, heroes } from '../stores.js'
|
||||
|
||||
let displayHeroes = []
|
||||
$: {
|
||||
if ($heroes) {
|
||||
displayHeroes = Object.values($heroes).filter(hero => {
|
||||
return hero && hero.id && hero.img_ext
|
||||
}).map(hero => {
|
||||
return {
|
||||
...hero,
|
||||
img: `${config.donationRoot}/img/avatar/${hero.id}${hero.img_ext}`
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Overlay name="supporters" fullSize>
|
||||
<section class="supporters-modal">
|
||||
<h2>Our Supporters</h2>
|
||||
<p class="info">
|
||||
Bitfeed is only possible thanks to the generosity of our supporters:
|
||||
</p>
|
||||
{#if $sponsors && $sponsors.length}
|
||||
<div class="group">
|
||||
<h3>Enterprise Sponsors</h3>
|
||||
<div class="entries">
|
||||
{#each $sponsors as sponsor}
|
||||
<a class="supporter" target="_blank" href={sponsor.website}>
|
||||
<img src={sponsor.img} alt={sponsor.name}>
|
||||
<span class="label">{ sponsor.name }</span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<button class="action-button" on:click={() => { $overlay = 'donation' }}>
|
||||
Become a Sponsor!
|
||||
</button>
|
||||
{/if}
|
||||
{#if $heroes && displayHeroes.length}
|
||||
<div class="group">
|
||||
<h3>Community Heroes</h3>
|
||||
<div class="entries">
|
||||
{#each displayHeroes as hero}
|
||||
<a class="supporter hero" target="_blank" href="https://twitter.com/{hero.username}">
|
||||
<img src={hero.img} alt={hero.name}>
|
||||
<span class="label">@{ hero.username }</span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<button class="action-button" on:click={() => { $overlay = 'donation' }}>
|
||||
Become a Community Hero!
|
||||
</button>
|
||||
{/if}
|
||||
</section>
|
||||
</Overlay>
|
||||
|
||||
<style type="text/scss">
|
||||
.supporters-modal {
|
||||
font-size: 0.9rem;
|
||||
width: 100%;
|
||||
|
||||
p.info {
|
||||
text-align: center;
|
||||
margin: 0 0 .25em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.group {
|
||||
margin-bottom: 2em;
|
||||
.entries {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
|
||||
.supporter {
|
||||
width: 120px;
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: solid 3px transparent;
|
||||
transition: border 200ms;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
img {
|
||||
border: solid 3px var(--bold-a);
|
||||
}
|
||||
}
|
||||
|
||||
&.hero {
|
||||
&:hover {
|
||||
img {
|
||||
border: solid 3px var(--bold-b);
|
||||
}
|
||||
}
|
||||
.label {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -10,7 +10,8 @@
|
||||
import Sidebar from '../components/Sidebar.svelte'
|
||||
import AboutOverlay from '../components/AboutOverlay.svelte'
|
||||
import DonationOverlay from '../components/DonationOverlay.svelte'
|
||||
import DonationBar from '../components/DonationBar.svelte'
|
||||
import SupportersOverlay from '../components/SupportersOverlay.svelte'
|
||||
import Alerts from '../components/alert/Alerts.svelte'
|
||||
import { integerFormat } from '../utils/format.js'
|
||||
import { exchangeRates, localCurrency, lastBlockId } from '../stores.js'
|
||||
import { formatCurrency } from '../utils/fx.js'
|
||||
@ -69,7 +70,7 @@
|
||||
$devEvents.addManyCallback = fakeTxs
|
||||
$devEvents.addBlockCallback = fakeBlock
|
||||
|
||||
if (!$settings.showDonation) $settings.showDonation = true
|
||||
if (!$settings.showMessages) $settings.showMessages = true
|
||||
})
|
||||
|
||||
function resize () {
|
||||
@ -437,17 +438,18 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if $settings.showDonation }
|
||||
<DonationBar />
|
||||
{/if}
|
||||
<div class="spacer" />
|
||||
{#if $settings.showMessages }
|
||||
<Alerts />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Sidebar />
|
||||
|
||||
<AboutOverlay />
|
||||
{#if config.lightningEnabled }
|
||||
{#if config.donationsEnabled }
|
||||
<DonationOverlay />
|
||||
<SupportersOverlay />
|
||||
{/if}
|
||||
|
||||
{#if config.dev && config.debug && $devSettings.guides }
|
||||
|
199
client/src/components/alert/Alerts.svelte
Normal file
199
client/src/components/alert/Alerts.svelte
Normal file
@ -0,0 +1,199 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import { alerts, heroes, sponsors, overlay, sidebarToggle } from '../../stores.js'
|
||||
import config from '../../config.js'
|
||||
import ByMononaut from './ByMononaut.svelte'
|
||||
import HeroMsg from './Hero.svelte'
|
||||
import SponsoredMsg from './Sponsored.svelte'
|
||||
import GenericAlert from './GenericAlert.svelte'
|
||||
import { fly } from 'svelte/transition'
|
||||
|
||||
const components = {
|
||||
mononaut: ByMononaut,
|
||||
"sponsored-by": SponsoredMsg,
|
||||
// "be-a-hero": BeAHero,
|
||||
"thank-you-hero": HeroMsg,
|
||||
msg: GenericAlert
|
||||
}
|
||||
|
||||
const actions = {
|
||||
support: () => {
|
||||
$overlay = 'donation'
|
||||
},
|
||||
supporters: () => {
|
||||
$overlay = 'supporters'
|
||||
},
|
||||
contact: () => {
|
||||
$sidebarToggle = 'contact'
|
||||
}
|
||||
}
|
||||
|
||||
const sequences = {}
|
||||
let rotating = false
|
||||
|
||||
let processedAlerts = []
|
||||
$: {
|
||||
if ($alerts) {
|
||||
processedAlerts = $alerts.map(processAlert).filter(alert => alert != null)
|
||||
startAlerts()
|
||||
}
|
||||
}
|
||||
|
||||
function processAlert (alert) {
|
||||
if (alert && alert.type && components[alert.type]) {
|
||||
if (!sequences[alert.key]) sequences[alert.key] = 0
|
||||
return {
|
||||
...alert,
|
||||
component: components[alert.type],
|
||||
action: actions[alert.action] || null
|
||||
}
|
||||
} else return null
|
||||
}
|
||||
|
||||
let activeAlerts = [{ key: 'null1' }, { key: 'null2' }]
|
||||
let lastIndex = -1
|
||||
|
||||
onMount(() => {
|
||||
startAlerts()
|
||||
})
|
||||
|
||||
function startAlerts () {
|
||||
if (!rotating && processedAlerts && processedAlerts.length) {
|
||||
rotating = true
|
||||
activeAlerts[0] = processedAlerts[0] || { key: 'null1' }
|
||||
activeAlerts[1] = processedAlerts[1] || { key: 'null2' }
|
||||
lastIndex = processedAlerts[1] ? 1 : 0
|
||||
if (rotateTimer) clearTimeout(rotateTimer)
|
||||
rotateTimer = setTimeout(rotateAlerts, config.alertDuration)
|
||||
}
|
||||
}
|
||||
|
||||
let rotateTimer
|
||||
function rotateAlerts () {
|
||||
if (rotateTimer) clearTimeout(rotateTimer)
|
||||
|
||||
if (processedAlerts && processedAlerts.length > 2) {
|
||||
// find the next alert in the queue
|
||||
let currentIndex = -1
|
||||
if (activeAlerts[1]) {
|
||||
currentIndex = processedAlerts.findIndex(alert => { alert.key === activeAlerts[1].key})
|
||||
}
|
||||
if (currentIndex < 0) currentIndex = lastIndex
|
||||
currentIndex = (currentIndex + 1) % processedAlerts.length
|
||||
// roll over to the next alert if there's a key clash
|
||||
if (processedAlerts[currentIndex].key === activeAlerts[1].key) {
|
||||
currentIndex = (currentIndex + 1) % processedAlerts.length
|
||||
}
|
||||
|
||||
lastIndex = currentIndex
|
||||
let nextAlert = processedAlerts[currentIndex]
|
||||
if (nextAlert)
|
||||
activeAlerts[0] = activeAlerts[1]
|
||||
activeAlerts[1] = { key: 'temp' }
|
||||
setTimeout(() => {
|
||||
activeAlerts[1] = nextAlert
|
||||
sequences[alert.key]++
|
||||
}, 1000)
|
||||
} else if (processedAlerts) {
|
||||
activeAlerts[0] = processedAlerts[0] || { key: 'null1' }
|
||||
activeAlerts[1] = processedAlerts[1] || { key: 'null2' }
|
||||
}
|
||||
|
||||
rotateTimer = setTimeout(rotateAlerts, config.alertDuration)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="alert-bar" transition:fly={{ y: -100 }}>
|
||||
{#each activeAlerts as alert (alert.key)}
|
||||
<div class="alert-wrapper" in:fly|local={{ y: -100 }} out:fly|local={{ x: 400}}>
|
||||
{#if alert && alert.component }
|
||||
{#if alert.href}
|
||||
<a class="alert link" target="_blank" rel="noopener" href={alert.href}>
|
||||
<svelte:component this={alert.component} {...alert} sequence={sequences[alert.key]} />
|
||||
</a>
|
||||
{:else if alert.action}
|
||||
<div class="alert action" on:click={alert.action}>
|
||||
<svelte:component this={alert.component} {...alert} sequence={sequences[alert.key]} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="alert">
|
||||
<svelte:component this={alert.component} {...alert} sequence={sequences[alert.key]} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style type="text/scss">
|
||||
.alert-bar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
height: 3.5em;
|
||||
|
||||
.alert-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 20em;
|
||||
transform: translateX(-110%);
|
||||
transition: transform 500ms ease-in-out;
|
||||
|
||||
.alert {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background: var(--palette-c);
|
||||
transition: background 200ms;
|
||||
|
||||
&:hover {
|
||||
background: var(--palette-d);
|
||||
}
|
||||
|
||||
:global(.alert-content) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: .5em 1em;
|
||||
color: var(--palette-x);
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 850px) {
|
||||
.alert-wrapper {
|
||||
transform: translateX(0);
|
||||
|
||||
&:first-child {
|
||||
transform: translateX(110%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
height: 3em;
|
||||
|
||||
.alert-wrapper {
|
||||
font-size: 0.8em;
|
||||
width: 16em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
41
client/src/components/alert/ByMononaut.svelte
Normal file
41
client/src/components/alert/ByMononaut.svelte
Normal file
@ -0,0 +1,41 @@
|
||||
<script>
|
||||
import Mononaut from '../Mononaut.svelte'
|
||||
</script>
|
||||
|
||||
<div class="alert-content by-mononaut">
|
||||
<h3>Bitfeed</h3>
|
||||
<p class="by">by mononaut</p>
|
||||
<div class="monkey-avatar">
|
||||
<Mononaut />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style type="text/scss">
|
||||
.by-mononaut {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
|
||||
h3 {
|
||||
text-align: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.by {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.monkey-avatar {
|
||||
width: 3em;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.by {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
51
client/src/components/alert/GenericAlert.svelte
Normal file
51
client/src/components/alert/GenericAlert.svelte
Normal file
@ -0,0 +1,51 @@
|
||||
<script>
|
||||
export let msg
|
||||
export let shortmsg = null
|
||||
export let img
|
||||
</script>
|
||||
|
||||
<div class="alert-content generic-alert">
|
||||
<p class="msg">
|
||||
{@html msg }
|
||||
</p>
|
||||
<p class="shortmsg">
|
||||
{@html shortmsg || msg }
|
||||
</p>
|
||||
{#if img}
|
||||
<img src={img} alt="">
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.generic-alert {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
.msg, .shortmsg {
|
||||
margin: 0;
|
||||
font-size: .9em;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.shortmsg {
|
||||
display: none;
|
||||
}
|
||||
img {
|
||||
height: 2.8em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.shortmsg {
|
||||
display: inline;
|
||||
}
|
||||
.msg {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
83
client/src/components/alert/Hero.svelte
Normal file
83
client/src/components/alert/Hero.svelte
Normal file
@ -0,0 +1,83 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import config from '../../config.js'
|
||||
import { heroes } from '../../stores.js'
|
||||
|
||||
let displayHeroes = []
|
||||
|
||||
function chooseRandomHeroes () {
|
||||
displayHeroes = []
|
||||
const validHeroes = Object.values($heroes).filter(hero => {
|
||||
return hero && hero.id && hero.img_ext
|
||||
})
|
||||
const randomIndex = Math.floor(Math.random() * validHeroes.length)
|
||||
for (let i = 0; i < Math.min(3, validHeroes.length); i++) {
|
||||
const randomHero = validHeroes[(randomIndex + i) % validHeroes.length]
|
||||
displayHeroes.push({
|
||||
...randomHero,
|
||||
img: `${config.donationRoot}/img/avatar/${randomHero.id}${randomHero.img_ext}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($heroes && !displayHeroes.length) {
|
||||
chooseRandomHeroes()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="alert-content">
|
||||
<p class="msg">Thank you to all of our Community Heroes!</p>
|
||||
<div class="heros">
|
||||
{#each displayHeroes as hero}
|
||||
<img src={hero.img} alt={hero.username} title={hero.name} class="hero">
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style type="text/scss">
|
||||
.alert-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: var(--palette-x);
|
||||
|
||||
.msg {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.heros {
|
||||
height: 2.5em;
|
||||
width: 7.5em;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
.hero {
|
||||
width: 2.3em;
|
||||
height: 2.3em;
|
||||
margin: 0 0.1em;
|
||||
border-radius: 50%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.heros {
|
||||
max-width: 5em;
|
||||
.hero:nth-child(3) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
38
client/src/components/alert/Sponsored.svelte
Normal file
38
client/src/components/alert/Sponsored.svelte
Normal file
@ -0,0 +1,38 @@
|
||||
<script>
|
||||
export let img
|
||||
export let name
|
||||
</script>
|
||||
|
||||
<div class="alert-content sponsored-by">
|
||||
<p class="msg">Sponsored by</p>
|
||||
<h3>{ name }</h3>
|
||||
{#if img}
|
||||
<img src={img} alt="name" class="logo">
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style type="text/scss">
|
||||
.sponsored-by {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: var(--palette-x);
|
||||
|
||||
h3 {
|
||||
text-align: center;
|
||||
margin: 0 10px;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.msg {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 3em;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -6,10 +6,8 @@ export default {
|
||||
fps: true,
|
||||
websocket_path: '/ws/txs',
|
||||
localSocket: false,
|
||||
nofeed: true,
|
||||
nofeed: false,
|
||||
txDelay: 10000,
|
||||
blockTimeout: 10000,
|
||||
donationAddress: "bc1qthanksv78zs5jnmysvmuuuzj09aklf8jmm49xl",
|
||||
donationHash: "5dfb3b419e38a1494f648337ce7052797b6fa4f2",
|
||||
lightningEnabled: true
|
||||
donationsEnabled: true,
|
||||
alertDuration: 20000,
|
||||
}
|
||||
|
@ -20,14 +20,15 @@ export default class BitcoinTx {
|
||||
|
||||
this.time = time
|
||||
|
||||
if (config.donationHash && this.outputs) {
|
||||
this.outputs.forEach(output => {
|
||||
if (output.script_pub_key.includes(config.donationHash)) {
|
||||
console.log('donation!', this)
|
||||
this.highlight = true
|
||||
}
|
||||
})
|
||||
}
|
||||
// Highlight transactions to the static donation address
|
||||
// if (config.donationHash && this.outputs) {
|
||||
// this.outputs.forEach(output => {
|
||||
// if (output.script_pub_key.includes(config.donationHash)) {
|
||||
// console.log('donation!', this)
|
||||
// this.highlight = true
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
// is a coinbase transaction?
|
||||
if (this.inputs && this.inputs.length === 1 && this.inputs[0].prev_txid === "0000000000000000000000000000000000000000000000000000000000000000") {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { writable, derived } from 'svelte/store'
|
||||
export { exchangeRates } from './utils/pollStore.js'
|
||||
import { makePollStore } from './utils/pollStore.js'
|
||||
import { symbols } from './utils/fx.js'
|
||||
import LocaleCurrency from 'locale-currency'
|
||||
import config from './config.js'
|
||||
@ -42,6 +42,16 @@ function createCachedDict (namespace, defaultValues) {
|
||||
}
|
||||
}
|
||||
|
||||
// refresh exchange rates every minute
|
||||
export const exchangeRates = makePollStore('rates', 'https://blockchain.info/ticker', 60000, {})
|
||||
// refresh messages from donation server every hour
|
||||
// export const alerts = makePollStore('alerts', `${config.donationRoot}/api/sponsorship/msgs.json`, 3600000, [])
|
||||
export const alerts = makePollStore('alerts', `${config.donationRoot}/api/sponsorship/msgs.json`, 10000, [])
|
||||
// refresh sponsor data every hour
|
||||
export const heroes = makePollStore('heroes', `${config.donationRoot}/api/sponsorship/heroes.json`, 3600000, null)
|
||||
export const sponsors = makePollStore('sponsors', `${config.donationRoot}/api/sponsorship/sponsors.json`, 3600000, null)
|
||||
export const tiers = makePollStore('tiers', `${config.donationRoot}/api/sponsorship/tiers.json`, 3600000, null)
|
||||
|
||||
export const darkMode = writable(true)
|
||||
export const serverConnected = writable(false)
|
||||
export const serverDelay = writable(1000)
|
||||
@ -73,7 +83,7 @@ export const settings = createCachedDict('settings', {
|
||||
showFX: true,
|
||||
vbytes: false,
|
||||
fancyGraphics: true,
|
||||
showDonation: true,
|
||||
showMessages: true,
|
||||
noTrack: false
|
||||
})
|
||||
|
||||
@ -88,7 +98,7 @@ export const nativeAntialias = writable(false)
|
||||
|
||||
const newVisitor = !localStorage.getItem('seen-welcome-msg')
|
||||
// export const overlay = writable(newVisitor ? 'about' : null)
|
||||
export const overlay = writable('donation')
|
||||
export const overlay = writable(null)
|
||||
|
||||
let currencyCode = LocaleCurrency.getCurrency(navigator.language)
|
||||
console.log('LOCALE: ', navigator.language, currencyCode)
|
||||
|
@ -1,19 +1,24 @@
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
function makeRatePollStore () {
|
||||
export function makePollStore (name, url, frequency, initialValue={}, responseHandler) {
|
||||
let timer
|
||||
const { subscribe, set, update } = writable({})
|
||||
const { subscribe, set, update } = writable(initialValue)
|
||||
if (!responseHandler) responseHandler = async (response, set) => {
|
||||
try {
|
||||
const data = await response.json()
|
||||
if (data) set(data)
|
||||
} catch (error) {
|
||||
console.log(`failed to parse polled data for ${name}: `, error)
|
||||
}
|
||||
}
|
||||
|
||||
const fetcher = () => {
|
||||
fetch(`https://blockchain.info/ticker?t=${Date.now()}`).then(async response => {
|
||||
const rates = await response.json()
|
||||
set(rates)
|
||||
}).catch(err => {
|
||||
console.log('error fetching exchange rates: ', err)
|
||||
fetch(`${url}?t=${Date.now()}`).then(response => { responseHandler(response, set) }).catch(err => {
|
||||
console.log(`error polling data for ${name}: `, error)
|
||||
})
|
||||
}
|
||||
fetcher()
|
||||
timer = setInterval(fetcher, 60000)
|
||||
timer = setInterval(fetcher, frequency || 60000)
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
@ -21,5 +26,3 @@ function makeRatePollStore () {
|
||||
update
|
||||
}
|
||||
}
|
||||
|
||||
export const exchangeRates = makeRatePollStore()
|
||||
|
Loading…
Reference in New Issue
Block a user