mirror of
https://github.com/ReVanced/revanced-website.git
synced 2026-01-31 06:41:03 +00:00
Merge branch 'dev' into feat/docusaurus
This commit is contained in:
@@ -53,6 +53,7 @@ body {
|
||||
--grey-nine: hsla(240, 6%, 7%, 0.3);
|
||||
--grey-ten: hsl(230, 9.5%, 17.5%);
|
||||
--grey-eleven: hsl(208, 10%, 40%);
|
||||
--red-one: hsl(333, 84%, 62%);
|
||||
--bezier-one: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
--drop-shadow-one: 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12),
|
||||
0px 2px 4px -1px rgba(0, 0, 0, 0.2);
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import * as settings from './settings';
|
||||
|
||||
// API Endpoints
|
||||
import type { Patch, Repository, Metadata, Asset } from '$lib/types';
|
||||
import type {
|
||||
Patch,
|
||||
Repository,
|
||||
Metadata,
|
||||
Asset,
|
||||
TeamMember,
|
||||
DonationPlatform,
|
||||
CryptoWallet
|
||||
} from '$lib/types';
|
||||
|
||||
export type ReposData = Repository[];
|
||||
export type PatchesData = { patches: Patch[]; packages: string[] };
|
||||
export type ReleaseData = { metadata: Metadata; assets: Asset[] };
|
||||
export type TeamData = { members: TeamMember[] };
|
||||
export type DonationData = { wallets: CryptoWallet[]; platforms: DonationPlatform[] };
|
||||
|
||||
async function get_json(endpoint: string) {
|
||||
const url = `${settings.api_base_url()}/${endpoint}`;
|
||||
@@ -42,6 +52,17 @@ async function patches(): Promise<PatchesData> {
|
||||
return { patches: json.patches, packages };
|
||||
}
|
||||
|
||||
async function team(): Promise<TeamData> {
|
||||
const json = await get_json('v2/team/members');
|
||||
return { members: json.members };
|
||||
}
|
||||
|
||||
async function donate(): Promise<DonationData> {
|
||||
const json = await get_json('v2/donations');
|
||||
|
||||
return { wallets: json.donations.wallets, platforms: json.donations.links };
|
||||
}
|
||||
|
||||
export const staleTime = 5 * 60 * 1000;
|
||||
export const queries = {
|
||||
manager: {
|
||||
@@ -58,5 +79,15 @@ export const queries = {
|
||||
queryKey: ['repositories'],
|
||||
queryFn: repositories,
|
||||
staleTime
|
||||
},
|
||||
team: {
|
||||
queryKey: ['team'],
|
||||
queryFn: team,
|
||||
staleTime
|
||||
},
|
||||
donate: {
|
||||
queryKey: ['donate'],
|
||||
queryFn: donate,
|
||||
staleTime
|
||||
}
|
||||
};
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
<li><a href="/download">Download</a></li>
|
||||
<li><a href="/patches">Patches</a></li>
|
||||
<li><a href="/contributors">Contributors</a></li>
|
||||
<li><a href="/donate">Donate</a></li>
|
||||
<li><a href="{new URL($page.url).origin}/docs/">Docs</a></li>
|
||||
</FooterSection>
|
||||
<FooterSection title="Repositories">
|
||||
@@ -90,7 +91,7 @@
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<div id="logo-name"><span>Re</span>Vanced</div>
|
||||
<a href="https://liberapay.com/ReVanced/donate"><div>Donate</div></a>
|
||||
<a href="/donate"><div>Donate</div></a>
|
||||
<a href="mailto:contact@revanced.app"><div>Email</div></a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -7,13 +7,21 @@
|
||||
const client = useQueryClient();
|
||||
|
||||
export let href: string;
|
||||
export let queryKey: null | keyof typeof queries = null;
|
||||
export let queryKey: null | keyof typeof queries | Array<keyof typeof queries> = null;
|
||||
|
||||
function prefetch() {
|
||||
if (queryKey !== null) {
|
||||
const query = queries[queryKey];
|
||||
dev_log('Prefetching', query);
|
||||
client.prefetchQuery(query as any);
|
||||
if (Array.isArray(queryKey)) {
|
||||
queryKey.forEach((key) => {
|
||||
const query = queries[key];
|
||||
dev_log('Prefetching', query);
|
||||
client.prefetchQuery(query as any);
|
||||
});
|
||||
} else {
|
||||
const query = queries[queryKey];
|
||||
dev_log('Prefetching', query);
|
||||
client.prefetchQuery(query as any);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
<svelte:window bind:scrollY={y} />
|
||||
|
||||
<nav class:scrolled={y > 10}>
|
||||
<a class="menu-btn skiptab-btn" href="#skiptab">Skip navigation</a>
|
||||
|
||||
<button
|
||||
class="menu-btn mobile-only"
|
||||
on:click={() => (menuOpen = !menuOpen)}
|
||||
@@ -73,6 +75,7 @@
|
||||
<Navigation queryKey="manager" href="/download">Download</Navigation>
|
||||
<Navigation queryKey="patches" href="/patches">Patches</Navigation>
|
||||
<Navigation queryKey="repositories" href="/contributors">Contributors</Navigation>
|
||||
<Navigation queryKey={['donate', 'team']} href="/donate">Donate</Navigation>
|
||||
<Navigation href="{new URL($page.url).origin}/docs">Docs</Navigation>
|
||||
</div>
|
||||
</div>
|
||||
@@ -206,8 +209,7 @@
|
||||
}
|
||||
|
||||
.scrolled {
|
||||
box-shadow: 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12),
|
||||
0px 2px 4px -1px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: var(--drop-shadow-one);
|
||||
}
|
||||
|
||||
.overlay {
|
||||
@@ -323,4 +325,21 @@
|
||||
.menu-btn.open .menu-btn__burger::after {
|
||||
transform: rotate(-45deg) translate(10px, 10px);
|
||||
}
|
||||
|
||||
.skiptab-btn {
|
||||
position: fixed;
|
||||
left: -300px;
|
||||
border-radius: 100px;
|
||||
text-decoration: none;
|
||||
background-color: var(--accent-color);
|
||||
z-index: 10;
|
||||
color: var(--grey-four);
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.skiptab-btn:focus {
|
||||
left: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
color: var(--white);
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
border-radius: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@@ -122,7 +122,6 @@
|
||||
border-radius: 26px;
|
||||
background-color: var(--grey-six);
|
||||
display: flex;
|
||||
user-select: none;
|
||||
gap: 5%;
|
||||
white-space: normal;
|
||||
display: flex;
|
||||
|
||||
61
src/lib/components/Snackbar.svelte
Normal file
61
src/lib/components/Snackbar.svelte
Normal file
@@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { slide, fade } from 'svelte/transition';
|
||||
import { expoOut } from 'svelte/easing';
|
||||
|
||||
export let open = false;
|
||||
export let closeIcon = false;
|
||||
export let dismissTime = 3000;
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
$: if (open) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => (open = false), dismissTime);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<div
|
||||
class="snackbar"
|
||||
class:closeIcon
|
||||
in:slide|local={{ duration: 400, easing: expoOut }}
|
||||
out:fade|local={{ duration: 300, easing: expoOut }}
|
||||
>
|
||||
<div class="text">
|
||||
<slot name="text" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.snackbar {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
justify-content: space-between;
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 0.5rem;
|
||||
height: 3rem;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
border-radius: 0.25rem;
|
||||
min-width: 12.5rem;
|
||||
max-width: 35rem;
|
||||
position: fixed;
|
||||
margin-left: 2.25rem;
|
||||
margin-right: 2.25rem;
|
||||
z-index: 9999;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 2rem;
|
||||
background-color: var(--white);
|
||||
transition: all 0.4s var(--bezier-one);
|
||||
box-shadow: var(--drop-shadow-one);
|
||||
}
|
||||
|
||||
.text {
|
||||
color: var(--grey-two);
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
@@ -9,8 +9,8 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="none"
|
||||
viewBox="0 0 {viewBoxHeight} {viewBoxWidth}"
|
||||
style:height={svgHeight+'px'}
|
||||
style:width={svgWidth+'px'}
|
||||
style:height={svgHeight + 'px'}
|
||||
style:width={svgWidth + 'px'}
|
||||
>
|
||||
<slot />
|
||||
</svg>
|
||||
|
||||
@@ -47,3 +47,23 @@ export interface Metadata {
|
||||
published_at: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface TeamMember {
|
||||
login: string;
|
||||
avatar_url: string;
|
||||
html_url: string;
|
||||
bio?: string;
|
||||
}
|
||||
|
||||
export interface CryptoWallet {
|
||||
network: string;
|
||||
currency_code: string;
|
||||
address: string;
|
||||
preferred: boolean;
|
||||
}
|
||||
|
||||
export interface DonationPlatform {
|
||||
name: string;
|
||||
url: string;
|
||||
preferred: boolean;
|
||||
}
|
||||
|
||||
@@ -105,10 +105,12 @@
|
||||
</svelte:fragment>
|
||||
</Dialogue>
|
||||
|
||||
{#if $show_loading_animation}
|
||||
<Spinner />
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
<div id="skiptab">
|
||||
{#if $show_loading_animation}
|
||||
<Spinner />
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
</div>
|
||||
<!-- <Footer> -->
|
||||
</QueryClientProvider>
|
||||
|
||||
@@ -46,13 +46,13 @@
|
||||
<div class="wrapper">
|
||||
<div class="text-container" in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
|
||||
<h2>Made possible by the community.</h2>
|
||||
<h4>
|
||||
<p>
|
||||
Want to show up here? <span
|
||||
><a href="https://github.com/revanced" target="_blank" rel="noreferrer"
|
||||
>Become a contributor</a
|
||||
></span
|
||||
>
|
||||
</h4>
|
||||
</p>
|
||||
</div>
|
||||
<div class="repos">
|
||||
<Query {query} let:data>
|
||||
@@ -82,7 +82,7 @@
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
p {
|
||||
text-align: center;
|
||||
color: var(--grey-four);
|
||||
}
|
||||
|
||||
323
src/routes/donate/+page.svelte
Normal file
323
src/routes/donate/+page.svelte
Normal file
@@ -0,0 +1,323 @@
|
||||
<script lang="ts">
|
||||
import { fly } from 'svelte/transition';
|
||||
import { quintOut } from 'svelte/easing';
|
||||
|
||||
import { queries } from '$data/api';
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
|
||||
import Meta from '$lib/components/Meta.svelte';
|
||||
import Footer from '$layout/Footer/FooterHost.svelte';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import Snackbar from '$lib/components/Snackbar.svelte';
|
||||
import Query from '$lib/components/Query.svelte';
|
||||
import Dialogue from '$lib/components/Dialogue.svelte';
|
||||
|
||||
import QRCode from './QRCode.svelte';
|
||||
import DonateHeartAnimation from './DonateHeartAnimation.svelte';
|
||||
import TeamMember from './TeamMember.svelte';
|
||||
|
||||
import { supportsWebP } from '$util/supportsWebP';
|
||||
|
||||
const teamQuery = createQuery(['team'], queries.team);
|
||||
const donateQuery = createQuery(['donate'], queries.donate);
|
||||
|
||||
let qrCodeDialogue = false;
|
||||
let cryptoDialogue = false;
|
||||
let addressSnackbar = false;
|
||||
let qrCodeValue = '';
|
||||
let qrCodeDialogueName = '';
|
||||
|
||||
async function copyToClipboard(walletAddress: string) {
|
||||
addressSnackbar = true;
|
||||
qrCodeDialogue = false;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(walletAddress);
|
||||
} catch (error) {
|
||||
console.error('Failed to copy crypto wallet:', error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Meta title="Donate" />
|
||||
|
||||
<main class="wrapper" in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
|
||||
<section>
|
||||
<div>
|
||||
<h2>🎉 Support <span style="color: var(--accent-color);">ReVanced</span></h2>
|
||||
<p>
|
||||
ReVanced offers a variety of patches, including ad-blocking, custom themes, and innovative
|
||||
features. All of which is completely open source and free of charge. Donating will allow
|
||||
ReVanced maintain our servers and develop new features.
|
||||
</p>
|
||||
</div>
|
||||
<div id="heart">
|
||||
<DonateHeartAnimation
|
||||
backgroundImageUrl="/revanced-logo-background.svg"
|
||||
foregroundImageUrl="/icons/heart.svg"
|
||||
alt="ReVanced Logo"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<h3>Donate</h3>
|
||||
<Query query={donateQuery} let:data>
|
||||
<div class="donate-cards">
|
||||
{#if data.platforms}
|
||||
{#each data.platforms as platform}
|
||||
<a class="donate-card" target="_blank" rel="noreferrer" href={platform.url}>
|
||||
<!-- not using <img/> because we want the image height to always be 200px -->
|
||||
<div
|
||||
style="background-image: url('/donate/card-images/{platform.name}.{supportsWebP() ? 'webp' : 'png'}'), url('/donate/card-images/fallback.png');"
|
||||
role="img"
|
||||
aria-label="{platform.name} preview image"
|
||||
>
|
||||
</div>
|
||||
<span>{platform.name}</span>
|
||||
</a>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if data.wallets}
|
||||
<button class="donate-card" on:click={() => (cryptoDialogue = !cryptoDialogue)}>
|
||||
<div
|
||||
style="background-image: url('/donate/card-images/Cryptocurrencies.{supportsWebP() ? 'webp' : 'png'}'), url('/donate/card-images/fallback.png');"
|
||||
role="img"
|
||||
aria-label="Cryptocurrencies preview image"
|
||||
/>
|
||||
<span>Cryptocurrencies</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</Query>
|
||||
<Query query={teamQuery} let:data>
|
||||
<h3>Meet the team</h3>
|
||||
{#if data.members.length > 0}
|
||||
<section class="team">
|
||||
<!-- randomize team members because equality -->
|
||||
{#each data.members.sort(() => (Math.random() > 0.5 ? -1 : 1)) as member, i}
|
||||
<TeamMember {member} {i} />
|
||||
{/each}
|
||||
</section>
|
||||
{/if}
|
||||
</Query>
|
||||
</main>
|
||||
|
||||
<Dialogue bind:modalOpen={cryptoDialogue}>
|
||||
<svelte:fragment slot="icon">
|
||||
<img class="qr-code" src="/icons/coins.svg" alt="Cryptocurrencies" />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">Cryptocurrencies</svelte:fragment>
|
||||
<svelte:fragment slot="description">
|
||||
<hr style="margin: 1rem 0;" />
|
||||
<div class="wallets">
|
||||
<Query query={donateQuery} let:data>
|
||||
{#each data.wallets as wallet}
|
||||
<button
|
||||
on:click={() => {
|
||||
qrCodeValue = wallet.address;
|
||||
qrCodeDialogueName = wallet.currency_code;
|
||||
qrCodeDialogue = !qrCodeDialogue;
|
||||
// when the user clicks a wallet the crypto wallet goes away
|
||||
// because multi page dialogues aren't implemented yet oops
|
||||
cryptoDialogue = false;
|
||||
}}
|
||||
>
|
||||
<div class="wallet-name">
|
||||
<img
|
||||
src="/donate/crypto/{wallet.currency_code}.svg"
|
||||
onerror="this.onerror=null; this.src='/donate/fallback.svg'"
|
||||
alt={`${wallet.network} icon.'`}
|
||||
/>
|
||||
{`${wallet.network} (${wallet.currency_code})`}
|
||||
</div>
|
||||
<img id="arrow" src="/icons/expand_less.svg" alt="continue" />
|
||||
</button>
|
||||
{/each}
|
||||
</Query>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="buttons">
|
||||
<Button type="filled" on:click={() => (cryptoDialogue = false)}>Close</Button>
|
||||
</svelte:fragment>
|
||||
</Dialogue>
|
||||
|
||||
<Dialogue bind:modalOpen={qrCodeDialogue}>
|
||||
<svelte:fragment slot="icon">
|
||||
<img class="qr-code" src="/icons/wallet.svg" alt="QR Code" />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">{qrCodeDialogueName} Wallet</svelte:fragment>
|
||||
<svelte:fragment slot="description">
|
||||
<div class="qr-code-body">
|
||||
{qrCodeValue}
|
||||
<QRCode codeValue={qrCodeValue} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="buttons">
|
||||
<Button
|
||||
type="text"
|
||||
on:click={() => {
|
||||
qrCodeDialogue = false;
|
||||
cryptoDialogue = true;
|
||||
}}>Back</Button
|
||||
>
|
||||
<Button type="filled" on:click={() => copyToClipboard(qrCodeValue)}>Copy Address</Button>
|
||||
</svelte:fragment>
|
||||
</Dialogue>
|
||||
|
||||
<Snackbar bind:open={addressSnackbar} closeIcon>
|
||||
<svelte:fragment slot="text">Address copied to clipboard</svelte:fragment>
|
||||
</Snackbar>
|
||||
|
||||
<Footer />
|
||||
|
||||
<style lang="scss">
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 7rem;
|
||||
|
||||
// support revanced and heart thingy
|
||||
section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 2rem;
|
||||
width: 60%;
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// COPEEEE
|
||||
@media screen and (max-width: 768px) {
|
||||
#heart {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.donate-cards {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 3rem;
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.donate-card {
|
||||
text-decoration: none;
|
||||
background-color: var(--grey-ten);
|
||||
border-radius: 1.5rem;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
transition: 0.3s border-radius var(--bezier-one), 0.3s background-color var(--bezier-one);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--accent-low-opacity);
|
||||
}
|
||||
|
||||
&:active {
|
||||
border-radius: 2.75rem;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
color: var(--grey-five);
|
||||
font-size: 1.05rem;
|
||||
font-weight: 500;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
div {
|
||||
height: 200px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.wallets {
|
||||
// i just guessed this
|
||||
width: clamp(200px, 75vw, 375px);
|
||||
#arrow {
|
||||
height: 20px;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
font-size: 0.9rem;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: var(--grey-five);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background-color: var(--grey-six);
|
||||
padding: 0.75rem 1.25rem;
|
||||
transition: filter 0.4s var(--bezier-one);
|
||||
|
||||
&:hover {
|
||||
filter: brightness(85%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
// crypto icon
|
||||
img {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.qr-code-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
word-break: break-word;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.team {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(325px, 1fr));
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
</style>
|
||||
124
src/routes/donate/DonateHeartAnimation.svelte
Normal file
124
src/routes/donate/DonateHeartAnimation.svelte
Normal file
@@ -0,0 +1,124 @@
|
||||
<script lang="ts">
|
||||
export let backgroundImageUrl: string;
|
||||
export let foregroundImageUrl: string;
|
||||
export let alt: string;
|
||||
</script>
|
||||
|
||||
<div id="pulsating-image-scale">
|
||||
<div id="pulsating-image-hover">
|
||||
<div id="background" style:background-image="url({backgroundImageUrl})">
|
||||
<img src={foregroundImageUrl} {alt} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
#pulsating-image-scale {
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
transition: transform 0.4s ease, filter 0.2s ease;
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&:active {
|
||||
filter: brightness(1.3);
|
||||
transform: scaleY(0.95) rotate(-5deg);
|
||||
}
|
||||
|
||||
#pulsating-image-hover {
|
||||
height: 225px;
|
||||
width: 225px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
animation: wiggle 1s;
|
||||
|
||||
@keyframes wiggle {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(5deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(5deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#background {
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
background-repeat: no-repeat;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
animation: pulse-size 1.2s infinite;
|
||||
will-change: transform;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
img {
|
||||
height: 50%;
|
||||
animation: double-pulse-size 1.2s infinite, pulse-glow 1.2s infinite;
|
||||
will-change: transform, box-shadow;
|
||||
|
||||
@keyframes double-pulse-size {
|
||||
0% {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
13% {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
30% {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
45% {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
30% {
|
||||
filter: drop-shadow(0 0 0rem var(--red-one));
|
||||
}
|
||||
45% {
|
||||
filter: drop-shadow(0 0 0.5rem var(--red-one));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-size {
|
||||
0% {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
30% {
|
||||
filter: brightness(1);
|
||||
}
|
||||
39% {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
32% {
|
||||
transform: scale(0.68);
|
||||
}
|
||||
50% {
|
||||
transform: scale(0.78);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
25
src/routes/donate/QRCode.svelte
Normal file
25
src/routes/donate/QRCode.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import QRious from 'qrious/dist/qrious';
|
||||
|
||||
export let codeValue: string;
|
||||
export let squareSize: number = 150;
|
||||
|
||||
onMount(() => {
|
||||
new QRious({
|
||||
element: document.getElementById('qrcode'),
|
||||
value: codeValue,
|
||||
size: squareSize
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<canvas id="qrcode"/>
|
||||
|
||||
<style>
|
||||
canvas {
|
||||
border-radius: 0.5rem;
|
||||
background-color: white;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
71
src/routes/donate/TeamMember.svelte
Normal file
71
src/routes/donate/TeamMember.svelte
Normal file
@@ -0,0 +1,71 @@
|
||||
<script lang="ts">
|
||||
import { fly } from 'svelte/transition';
|
||||
import { quintOut } from 'svelte/easing';
|
||||
|
||||
import type { TeamMember } from '$lib/types';
|
||||
|
||||
export let member: TeamMember;
|
||||
export let i: number;
|
||||
</script>
|
||||
|
||||
<a
|
||||
class="member"
|
||||
href={member.html_url}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
in:fly={{ y: 10, easing: quintOut, duration: 750, delay: 50 * i }}
|
||||
>
|
||||
<img src={member.avatar_url} alt={`${member.login}'s profile picture.'`} />
|
||||
|
||||
<div class="member-text">
|
||||
<h4>{member.login}</h4>
|
||||
{#if member.bio}
|
||||
<h6>{member.bio}</h6>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<style lang="scss">
|
||||
.member {
|
||||
width: 100%;
|
||||
color: var(--white);
|
||||
border: 1px solid var(--grey-three);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
padding: 1rem;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
transition: 0.3s background-color var(--bezier-one);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--grey-six);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.member-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
transition: transform 0.4s var(--bezier-one);
|
||||
user-select: none;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
margin-bottom: 0;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
3
src/util/supportsWebP.ts
Normal file
3
src/util/supportsWebP.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function supportsWebP() {
|
||||
return document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0;
|
||||
}
|
||||
Reference in New Issue
Block a user