Add a thanks banner to the donation page's flow

This commit is contained in:
Keavon Chambers
2026-08-29 02:36:24 -07:00
parent c64a9e5cee
commit 1c055bfaab
4 changed files with 32 additions and 38 deletions

View File

@@ -7,11 +7,13 @@ pub fn category_description(category: &str) -> &str {
match category { match category {
"Animation" => indoc!( "Animation" => indoc!(
" "
Nodes in this category enable the creation of animated, real-time, and interactive motion graphics involving paramters that change over time. Nodes in this category enable the creation of animated, real-time, and interactive motion graphics involving parameters that change over time.
These nodes require that playback is activated by pressing the play button above the viewport. These nodes require that playback is activated by pressing the play button above the viewport.
" "
), ),
"Attributes: Read" => "Nodes in this category read the values of a named attribute stored on the items of a list, outputting them as a new list of the requested data type.",
"Attributes: Write" => "Nodes in this category store a named attribute on the items of a list, with a value that is calculated individually for each item.",
"Blending" => "Nodes in this category control how overlapping graphical content is composited together, considering blend modes, opacity, and clipping.", "Blending" => "Nodes in this category control how overlapping graphical content is composited together, considering blend modes, opacity, and clipping.",
"Color" => "Nodes in this category deal with selecting and manipulating colors and palettes.", "Color" => "Nodes in this category deal with selecting and manipulating colors and palettes.",
"Debug" => indoc!( "Debug" => indoc!(
@@ -30,6 +32,7 @@ pub fn category_description(category: &str) -> &str {
"Math: Trig" => "Nodes in this category perform trigonometric operations such as sine, cosine, tangent, and their inverses.", "Math: Trig" => "Nodes in this category perform trigonometric operations such as sine, cosine, tangent, and their inverses.",
"Math: Vec2" => "Nodes in this category perform operations involving `vec2` values (points or arrows in 2D space) such as the dot product, normalization, and distance calculations.", "Math: Vec2" => "Nodes in this category perform operations involving `vec2` values (points or arrows in 2D space) such as the dot product, normalization, and distance calculations.",
"Raster: Adjustment" => "Nodes in this category perform per-pixel color adjustments on raster graphics, such as brightness and contrast modifications.", "Raster: Adjustment" => "Nodes in this category perform per-pixel color adjustments on raster graphics, such as brightness and contrast modifications.",
"Raster: Brush" => "Nodes in this category paint raster imagery from the strokes drawn with the Brush tool, controlling the size, hardness, flow, and color of the marks they leave behind.",
"Raster: Channels" => "Nodes in this category enable channel-specific manipulation of the RGB and alpha channels of raster graphics.", "Raster: Channels" => "Nodes in this category enable channel-specific manipulation of the RGB and alpha channels of raster graphics.",
"Raster: Filter" => "Nodes in this category apply filtering effects to raster graphics such as blurs and sharpening.", "Raster: Filter" => "Nodes in this category apply filtering effects to raster graphics such as blurs and sharpening.",
"Raster: Pattern" => "Nodes in this category generate procedural raster patterns, fractals, textures, and noise.", "Raster: Pattern" => "Nodes in this category generate procedural raster patterns, fractals, textures, and noise.",
@@ -37,6 +40,7 @@ pub fn category_description(category: &str) -> &str {
"Text" => "Nodes in this category support the manipulation, formatting, and rendering of text strings.", "Text" => "Nodes in this category support the manipulation, formatting, and rendering of text strings.",
"Text: Regex" => "Nodes in this category perform string operations involving regular expressions, such as pattern matching and replacement.", "Text: Regex" => "Nodes in this category perform string operations involving regular expressions, such as pattern matching and replacement.",
"Text: JSON" => "Nodes in this category perform string operations involving JSON data, such as parsing and stringifying.", "Text: JSON" => "Nodes in this category perform string operations involving JSON data, such as parsing and stringifying.",
"Type Assertion" => "Nodes in this category assert that a value is of a specific data type, which may be used to invoke automatic conversions to the desired type.",
"Value" => "Nodes in this category supply data values of common types such as numbers, colors, booleans, and strings.", "Value" => "Nodes in this category supply data values of common types such as numbers, colors, booleans, and strings.",
"Vector: Measure" => "Nodes in this category perform measurements and analysis on vector graphics, such as length/area calculations, path traversal, and hit testing.", "Vector: Measure" => "Nodes in this category perform measurements and analysis on vector graphics, such as length/area calculations, path traversal, and hit testing.",
"Vector: Modifier" => "Nodes in this category modify the geometry of vector graphics, such as boolean operations, smoothing, and morphing.", "Vector: Modifier" => "Nodes in this category modify the geometry of vector graphics, such as boolean operations, smoothing, and morphing.",

View File

@@ -8,6 +8,10 @@ js = ["/js/page/donate.js"]
<section> <section>
<div id="donation-thanks"><strong>Thank you for your donation!</strong> Your receipt is on its way by email, and your support goes straight into building Graphite.</div>
<div id="membership-welcome"><strong>Welcome, and thank you for becoming a member!</strong> Your receipt is on its way by email. We're fulfilling membership rewards by hand for now, so please forward that receipt to <a href="mailto:contact@graphite.art">contact<wbr />@graphite<wbr />.art</a> with your Discord username, the name you'd like in the credits, and/or any other details pertinent to your membership level.</div>
<div class="diptych donate-hero"> <div class="diptych donate-hero">
<div class="block"> <div class="block">
@@ -15,7 +19,7 @@ js = ["/js/page/donate.js"]
**Own your tools. Own your art.** Graphite is 100% built and funded by our community. Invest in a sustainable, independent future for high-quality creative software that cannot ever be taken away. **Own your tools. Own your art.** Graphite is 100% built and funded by our community. Invest in a sustainable, independent future for high-quality creative software that cannot ever be taken away.
<div class="statistics" data-statistics> <div class="statistics">
<div class="statistic"> <div class="statistic">
<span class="value" data-statistics-recurring></span> <span class="value" data-statistics-recurring></span>
<span class="label">In memberships</span> <span class="label">In memberships</span>

View File

@@ -1,3 +1,22 @@
// Shown only when Stripe sends a member or one-time donor back to this banner's fragment
#donation-thanks,
#membership-welcome {
display: none;
box-sizing: border-box;
width: 100%;
padding: calc(20 * var(--variable-px)) calc(30 * var(--variable-px));
margin-bottom: calc(40 * var(--variable-px));
background-color: var(--color-seaside);
text-align: center;
text-justify: auto;
// Exceeding the distance from the page top keeps the browser from scrolling the header away
scroll-margin-top: 100vh;
&:target {
display: block;
}
}
// ================================== // ==================================
// HERO SPLIT BETWEEN COPY AND PICKER // HERO SPLIT BETWEEN COPY AND PICKER
// ================================== // ==================================

View File

@@ -1,11 +1,4 @@
const statisticsRequest = fetch("https://graphite.art/donate/sponsorship-stats").then((response) => response.json());
window.addEventListener("DOMContentLoaded", () => { window.addEventListener("DOMContentLoaded", () => {
setUpAmountPresets();
showStatistics();
});
function setUpAmountPresets() {
const amountField = /** @type {HTMLInputElement | null} */ (document.querySelector("[data-donation-amount]")); const amountField = /** @type {HTMLInputElement | null} */ (document.querySelector("[data-donation-amount]"));
const presetButtons = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll("[data-amount]")); const presetButtons = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll("[data-amount]"));
const githubOneTime = /** @type {HTMLAnchorElement | null} */ (document.querySelector("[data-github-one-time]")); const githubOneTime = /** @type {HTMLAnchorElement | null} */ (document.querySelector("[data-github-one-time]"));
@@ -16,7 +9,8 @@ function setUpAmountPresets() {
if (!githubOneTime) return; if (!githubOneTime) return;
const amount = Number(amountField.value); // GitHub accepts only whole-dollar amounts
const amount = Math.round(Number(amountField.value));
const suffix = Number.isFinite(amount) && amount > 0 ? `&amount=${amount}` : ""; const suffix = Number.isFinite(amount) && amount > 0 ? `&amount=${amount}` : "";
githubOneTime.href = `https://github.com/sponsors/GraphiteEditor/sponsorships?frequency=one-time${suffix}`; githubOneTime.href = `https://github.com/sponsors/GraphiteEditor/sponsorships?frequency=one-time${suffix}`;
}; };
@@ -30,31 +24,4 @@ function setUpAmountPresets() {
amountField.addEventListener("input", syncAmount); amountField.addEventListener("input", syncAmount);
syncAmount(); syncAmount();
} });
async function showStatistics() {
const element = /** @type {HTMLElement | null} */ (document.querySelector("[data-statistics]"));
const recurringElement = /** @type {HTMLElement | null} */ (document.querySelector("[data-statistics-recurring]"));
const membersElement = /** @type {HTMLElement | null} */ (document.querySelector("[data-statistics-members]"));
const oneTimeElement = /** @type {HTMLElement | null} */ (document.querySelector("[data-statistics-one-time]"));
const donorsElement = /** @type {HTMLElement | null} */ (document.querySelector("[data-statistics-donors]"));
if (!element || !recurringElement || !membersElement || !oneTimeElement || !donorsElement) return;
try {
const json = await statisticsRequest;
if (!json || !json.recurring || !json.one_time_prior_12_month_sum) throw new Error();
const number = (/** @type {number} */ value) => Math.round(value).toLocaleString("en-US", { useGrouping: "min2" });
const dollars = (/** @type {number} */ value) => `$${number(value)}`;
recurringElement.innerText = dollars(parseInt(json.recurring.cents) / 100);
membersElement.innerText = number(json.recurring.count);
oneTimeElement.innerText = dollars(parseInt(json.one_time_prior_12_month_sum.cents) / 100);
donorsElement.innerText = number(json.one_time_prior_12_month_sum.count);
// Force repaint to work around Safari bug <https://bugs.webkit.org/show_bug.cgi?id=286403> (remove this and its data attribute when the bug is fixed and widely deployed)
element.style.transform = "scale(1)";
} catch {
element.remove();
}
}