|
1 | 1 | <script lang="ts">
|
| 2 | + import settings from "$lib/state/settings"; |
| 3 | +
|
2 | 4 | import { donate } from "$lib/env";
|
| 5 | + import { device } from "$lib/device"; |
3 | 6 | import { t } from "$lib/i18n/translations";
|
4 | 7 |
|
5 | 8 | import DonateCardContainer from "$components/donate/DonateCardContainer.svelte";
|
|
11 | 14 | import IconPizza from "@tabler/icons-svelte/IconPizza.svelte";
|
12 | 15 | import IconToolsKitchen2 from "@tabler/icons-svelte/IconToolsKitchen2.svelte";
|
13 | 16 | import IconPaperBag from "@tabler/icons-svelte/IconPaperBag.svelte";
|
14 |
| - import IconArrowRight from "@tabler/icons-svelte/IconArrowRight.svelte"; |
15 | 17 | import IconSoup from "@tabler/icons-svelte/IconSoup.svelte";
|
16 | 18 | import IconFridge from "@tabler/icons-svelte/IconFridge.svelte";
|
17 | 19 | import IconArmchair from "@tabler/icons-svelte/IconArmchair.svelte";
|
|
20 | 22 | import IconPhoto from "@tabler/icons-svelte/IconPhoto.svelte";
|
21 | 23 | import IconWorldWww from "@tabler/icons-svelte/IconWorldWww.svelte";
|
22 | 24 | import IconBath from "@tabler/icons-svelte/IconBath.svelte";
|
23 |
| - import OuterLink from "$components/misc/OuterLink.svelte"; |
| 25 | +
|
| 26 | + import IconArrowLeft from "@tabler/icons-svelte/IconArrowLeft.svelte"; |
| 27 | + import IconArrowRight from "@tabler/icons-svelte/IconArrowRight.svelte"; |
| 28 | +
|
| 29 | + let donateList: HTMLElement; |
24 | 30 |
|
25 | 31 | let customInput: HTMLInputElement;
|
26 | 32 | let customInputValue: number | null;
|
|
39 | 45 | 4900: IconApple,
|
40 | 46 | 7398: IconDeviceLaptop,
|
41 | 47 | 8629: IconPhoto,
|
42 |
| - 9433: IconBath |
| 48 | + 9433: IconBath, |
43 | 49 | };
|
44 | 50 |
|
45 | 51 | type Processor = "stripe" | "liberapay";
|
|
66 | 72 | }
|
67 | 73 |
|
68 | 74 | const amount = Math.floor(Number(customInputValue) * 100);
|
69 |
| - return window.open(donationMethods[processor](amount), '_blank'); |
| 75 | + return window.open(donationMethods[processor](amount), "_blank"); |
| 76 | + }; |
| 77 | +
|
| 78 | + const scrollBehavior = $settings.appearance.reduceMotion |
| 79 | + ? "instant" |
| 80 | + : "smooth"; |
| 81 | +
|
| 82 | + $: showLeftScroll = false; |
| 83 | + $: showRightScroll = true; |
| 84 | +
|
| 85 | + const scroll = (direction: "left" | "right") => { |
| 86 | + const currentPos = donateList.scrollLeft; |
| 87 | + const newPos = direction === "left" ? currentPos - 150 : currentPos + 150; |
| 88 | + const maxPos = donateList.scrollWidth - donateList.getBoundingClientRect().width; |
| 89 | +
|
| 90 | + donateList.scroll({ |
| 91 | + left: newPos, |
| 92 | + behavior: scrollBehavior, |
| 93 | + }); |
| 94 | +
|
| 95 | + setTimeout(() => { |
| 96 | + showLeftScroll = newPos > 0; |
| 97 | + showRightScroll = newPos < maxPos && newPos !== maxPos; |
| 98 | + }, 150) |
70 | 99 | };
|
71 | 100 | </script>
|
72 | 101 |
|
|
84 | 113 | <div class="donation-type-text">
|
85 | 114 | <div class="donate-card-title">{$t("donate.card.once")}</div>
|
86 | 115 | <div class="donate-card-subtitle">
|
87 |
| - {$t("donate.card.processor", { value: 'stripe' })} |
| 116 | + {$t("donate.card.processor", { value: "stripe" })} |
88 | 117 | </div>
|
89 | 118 | </div>
|
90 | 119 | </button>
|
| 120 | + |
91 | 121 | <button
|
92 | 122 | id="donation-type-monthly"
|
93 | 123 | class="donation-type"
|
|
100 | 130 | <div class="donation-type-text">
|
101 | 131 | <div class="donate-card-title">{$t("donate.card.monthly")}</div>
|
102 | 132 | <div class="donate-card-subtitle">
|
103 |
| - {$t("donate.card.processor", { value: 'liberapay' })} |
| 133 | + {$t("donate.card.processor", { value: "liberapay" })} |
104 | 134 | </div>
|
105 | 135 | </div>
|
106 | 136 | </button>
|
107 | 137 | </div>
|
108 |
| - <div id="donation-options"> |
109 |
| - {#each Object.entries(PRESET_DONATION_AMOUNTS) as [ amount, component ]} |
110 |
| - <DonationOption |
111 |
| - price={+amount} |
112 |
| - desc={$t(`donate.card.option.${amount}`)} |
113 |
| - href={donationMethods[processor](+amount * 100)} |
114 |
| - > |
115 |
| - <svelte:component this={component} /> |
116 |
| - </DonationOption> |
117 |
| - {/each} |
| 138 | + |
| 139 | + <div |
| 140 | + id="donation-options-container" |
| 141 | + aria-hidden="true" |
| 142 | + class:mask-both={!device.is.mobile && showLeftScroll && showRightScroll} |
| 143 | + class:mask-left={!device.is.mobile && showLeftScroll && !showRightScroll} |
| 144 | + class:mask-right={!device.is.mobile && showRightScroll && !showLeftScroll} |
| 145 | + > |
| 146 | + {#if !device.is.mobile} |
| 147 | + <div id="donation-options-scroll"> |
| 148 | + <button |
| 149 | + class="scroll-button left" |
| 150 | + class:hidden={!showLeftScroll} |
| 151 | + on:click={() => { |
| 152 | + scroll("left"); |
| 153 | + }} |
| 154 | + > |
| 155 | + <IconArrowLeft /> |
| 156 | + </button> |
| 157 | + <button |
| 158 | + class="scroll-button right" |
| 159 | + class:hidden={!showRightScroll} |
| 160 | + on:click={() => { |
| 161 | + scroll("right"); |
| 162 | + }} |
| 163 | + > |
| 164 | + <IconArrowRight /> |
| 165 | + </button> |
| 166 | + </div> |
| 167 | + {/if} |
| 168 | + |
| 169 | + <div |
| 170 | + id="donation-options" |
| 171 | + bind:this={donateList} |
| 172 | + on:scroll={(e) => {}} |
| 173 | + > |
| 174 | + {#each Object.entries(PRESET_DONATION_AMOUNTS) as [amount, component]} |
| 175 | + <DonationOption |
| 176 | + price={+amount} |
| 177 | + desc={$t(`donate.card.option.${amount}`)} |
| 178 | + href={donationMethods[processor](+amount * 100)} |
| 179 | + > |
| 180 | + <svelte:component this={component} /> |
| 181 | + </DonationOption> |
| 182 | + {/each} |
| 183 | + </div> |
118 | 184 | </div>
|
| 185 | + |
119 | 186 | <div id="donation-custom">
|
120 | 187 | <div id="input-container" class:focused={customFocused}>
|
121 | 188 | {#if customInputValue || customInput?.validity.badInput}
|
122 | 189 | <span id="input-dollar-sign">$</span>
|
123 | 190 | {/if}
|
| 191 | + |
124 | 192 | <input
|
125 | 193 | id="donation-custom-input"
|
126 | 194 | type="number"
|
|
137 | 205 | on:keydown={(e) => e.key === "Enter" && sendCustom()}
|
138 | 206 | />
|
139 | 207 | </div>
|
| 208 | + |
140 | 209 | <button
|
141 | 210 | id="donation-custom-submit"
|
142 | 211 | on:click={sendCustom}
|
|
146 | 215 | <IconArrowRight />
|
147 | 216 | </button>
|
148 | 217 | </div>
|
| 218 | + |
149 | 219 | <div class="donate-card-subtitle processor-mobile">
|
150 | 220 | {$t("donate.card.processor", { value: processor })}
|
151 | 221 | </div>
|
|
288 | 358 | text-align: center;
|
289 | 359 | }
|
290 | 360 |
|
| 361 | + #donation-options-container { |
| 362 | + display: flex; |
| 363 | + flex-direction: column; |
| 364 | + gap: calc(var(--donate-card-main-padding) / 2); |
| 365 | + position: relative; |
| 366 | + } |
| 367 | +
|
| 368 | + #donation-options-scroll { |
| 369 | + display: flex; |
| 370 | + flex-direction: row; |
| 371 | + justify-content: space-between; |
| 372 | + align-items: center; |
| 373 | + position: absolute; |
| 374 | + pointer-events: none; |
| 375 | + width: 100%; |
| 376 | + height: 100%; |
| 377 | + z-index: 3; |
| 378 | + overflow: hidden; |
| 379 | + opacity: 0; |
| 380 | + transition: opacity 0.2s; |
| 381 | + } |
| 382 | +
|
| 383 | + .scroll-button { |
| 384 | + pointer-events: all; |
| 385 | + color: white; |
| 386 | + padding: 0 16px; |
| 387 | + background-color: transparent; |
| 388 | + height: 100%; |
| 389 | + transition: opacity 0.2s; |
| 390 | + } |
| 391 | +
|
| 392 | + #donation-options-container:hover #donation-options-scroll { |
| 393 | + opacity: 1; |
| 394 | + } |
| 395 | +
|
| 396 | + .scroll-button.hidden { |
| 397 | + opacity: 0; |
| 398 | + visibility: hidden; |
| 399 | + } |
| 400 | +
|
| 401 | + #donation-options-container.mask-both:hover #donation-options { |
| 402 | + mask-image: linear-gradient( |
| 403 | + 90deg, |
| 404 | + rgba(0, 0, 0, 0) 0%, |
| 405 | + rgba(0, 0, 0, 1) 20%, |
| 406 | + rgba(0, 0, 0, 1) 50%, |
| 407 | + rgba(0, 0, 0, 1) 80%, |
| 408 | + rgba(0, 0, 0, 0) 100% |
| 409 | + ); |
| 410 | + } |
| 411 | +
|
| 412 | + #donation-options-container.mask-left:hover #donation-options { |
| 413 | + mask-image: linear-gradient( |
| 414 | + 90deg, |
| 415 | + rgba(0, 0, 0, 0) 0%, |
| 416 | + rgba(0, 0, 0, 1) 20%, |
| 417 | + rgba(0, 0, 0, 1) 50%, |
| 418 | + rgba(0, 0, 0, 1) 96%, |
| 419 | + rgba(0, 0, 0, 0) 100% |
| 420 | + ); |
| 421 | + } |
| 422 | +
|
| 423 | + #donation-options-container.mask-right:hover #donation-options { |
| 424 | + mask-image: linear-gradient( |
| 425 | + 90deg, |
| 426 | + rgba(0, 0, 0, 0) 0%, |
| 427 | + rgba(0, 0, 0, 1) 4%, |
| 428 | + rgba(0, 0, 0, 1) 50%, |
| 429 | + rgba(0, 0, 0, 1) 80%, |
| 430 | + rgba(0, 0, 0, 0) 100% |
| 431 | + ); |
| 432 | + } |
| 433 | +
|
291 | 434 | @media screen and (max-width: 550px) {
|
292 | 435 | :global(#donation-box) {
|
293 | 436 | min-width: unset;
|
|
0 commit comments