Add "Resolution" challenge details, update "InitialPage" hero content, modularize event cards, and integrate DOMPurify for email sanitization.

This commit is contained in:
Dhamari Trice-Hanson 2026-01-02 15:21:34 -05:00
parent 70a192ab55
commit 5e4a1167cb
17 changed files with 666 additions and 162 deletions

BIN
fixed-border.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 MiB

View file

@ -0,0 +1,89 @@
# Resolution
**Bet on yourself. When others quit, their stakes become your reward.**
## What is Resolution?
Resolution is an 8-week goal-setting challenge where you stake real money on yourself and compete against your own limits. Pick your workshops, commit your time, and prove you can follow through. Those who finish win; those who quit fund the winners.
---
## How It Works
1. **Choose Your Workshops** — Pick up to 3 workshops based on your goals and availability (max one 3-star to prevent burnout)
2. **Stake Your Money** — Put skin in the game. Your stake is your commitment
3. **Complete Weekly Goals** — Each week you hit your targets, you unlock part of your stake
4. **Earn Weekly Prizes** — Stickers, exclusive rewards, and recognition for staying on track
5. **Claim Your Reward** — Finish strong and take home your stake plus a share of the pot from those who didn't make it
---
## Workshop Tiers
| Tier | Time Commitment | Duration | Stake |
|------|-----------------|----------|-------|
| ⭐ One Star | ~2 hours/week | 8 weeks | $80 |
| ⭐⭐ Two Star | ~5 hours/week | 8 weeks | $200 |
| ⭐⭐⭐ Three Star | ~9 hours/week | 8 weeks | $360 |
### Stake Breakdown
- **Per Workshop** — Each stake amount is per workshop, not total
- **Max 3 Workshops** — Choose up to 3 workshops to challenge yourself
- **One 3-Star Max** — To prevent going insane 🌀
---
## The Economics
The magic happens because **not everyone finishes**.
- Stakes are calculated at **$5/hour** of committed time
- When someone quits or misses their goals, their remaining stake flows to those who stayed the course
- The fewer people who finish, the bigger the pot for winners
- This creates real incentive to keep going when things get tough
### Example
If 10 people join a Two Star workshop ($200 each = $2,000 pool) and only 6 finish:
- 4 quitters forfeit their remaining stakes
- Those funds get distributed to the 6 finishers
- Winners get their original stake back **plus** bonus rewards
---
## Weekly Prizes
The budget isn't all for the final prize—we allocate for weekly motivation:
- **Custom Stickers** — Dope art like Siege did
- **Milestone Rewards** — Recognition for hitting weekly targets
- **Progress Tracking** — Visual proof of your consistency
Without weekly incentives, there's no way to keep people going. The journey matters as much as the destination.
---
## Rules
1. You're competing against **yourself**, not others
2. Miss a week = forfeit that portion of your stake
3. Complete all 8 weeks = full stake returned + share of the pot
4. Max 3 workshops total
5. Max 1 three-star workshop (sanity protection)
---
## Why This Works
This isn't a subscription you forget about. It's not a course you never finish. It's a **bet on your future self** with real consequences and real rewards.
The model works because:
- **Skin in the game** — You've got money on the line
- **Weekly accountability** — Can't coast until the end
- **Shared stakes** — Others' failures become your gains
- **Community pressure** — You're not alone in this
---
## Ready?
**RSVP now and stake your claim.**

View file

@ -4,10 +4,14 @@
"workspaces": {
"": {
"name": "revolution-frontend",
"dependencies": {
"dompurify": "^3.3.1",
},
"devDependencies": {
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/kit": "^2.49.1",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/dompurify": "^3.2.0",
"svelte": "^5.45.6",
"svelte-check": "^4.3.4",
"typescript": "^5.9.3",
@ -138,8 +142,12 @@
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
"@types/dompurify": ["@types/dompurify@3.2.0", "", { "dependencies": { "dompurify": "*" } }, "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
@ -158,6 +166,8 @@
"devalue": ["devalue@5.6.1", "", {}, "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A=="],
"dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="],
"esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],

View file

@ -15,9 +15,13 @@
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/kit": "^2.49.1",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/dompurify": "^3.2.0",
"svelte": "^5.45.6",
"svelte-check": "^4.3.4",
"typescript": "^5.9.3",
"vite": "^7.2.6"
},
"dependencies": {
"dompurify": "^3.3.1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -0,0 +1,81 @@
<script lang="ts">
import note1 from '$lib/assets/3a0136d1eb0536a623e080cb952a19b2a87af70f.png';
let {
title = "Event 1",
description = "Lorem ipsum dolor sit amet consectetur adipiscing elit",
imageSrc = ""
} = $props();
</script>
<div class="event-card-1">
<img src={note1} alt="" class="note-bg" />
<div class="event-content">
<h3 class="event-title">{title}</h3>
{#if imageSrc}
<div class="event-image">
<img src={imageSrc} alt={title} />
</div>
{/if}
<p class="event-description">{description}</p>
</div>
</div>
<style>
.event-card-1 {
position: relative;
width: 100%;
max-width: 400px;
filter: drop-shadow(0px 8px 4px rgba(0, 0, 0, 0.25));
}
.note-bg {
width: 100%;
height: auto;
display: block;
}
.event-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 8% 10%;
box-sizing: border-box;
}
.event-title {
font-family: 'Kodchasan', sans-serif;
font-weight: 400;
color: #a58d28;
font-size: clamp(1.5rem, 4vw, 3rem);
text-align: center;
margin: 0 0 5% 0;
}
.event-image {
width: 80%;
margin-bottom: 5%;
}
.event-image img {
width: 100%;
height: auto;
display: block;
object-fit: cover;
}
.event-description {
font-family: 'Kodchasan', sans-serif;
font-weight: 400;
color: #a58d28;
font-size: clamp(0.8rem, 2vw, 1.2rem);
text-align: center;
margin: 0;
line-height: 1.4;
}
</style>

View file

@ -0,0 +1,59 @@
<script lang="ts">
import note2 from '$lib/assets/b4cb411457c810c0897133da6ad9d5ae4468ea27.png';
let {
title = "Event 2"
} = $props();
</script>
<div class="event-card-2">
<div class="card-inner">
<img src={note2} alt="" class="note-bg" />
<div class="event-content">
<h3 class="event-title">{title}</h3>
</div>
</div>
</div>
<style>
.event-card-2 {
position: relative;
width: 100%;
max-width: 400px;
}
.card-inner {
position: relative;
transform: rotate(12deg);
filter: drop-shadow(0px 8px 6.3px rgba(0, 0, 0, 0.25));
}
.note-bg {
width: 100%;
height: auto;
display: block;
}
.event-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding: 15% 10%;
box-sizing: border-box;
}
.event-title {
font-family: 'Kodchasan', sans-serif;
font-weight: 400;
color: #a58d28;
font-size: clamp(1.5rem, 4vw, 3rem);
text-align: center;
margin: 0;
}
</style>

View file

@ -0,0 +1,59 @@
<script lang="ts">
import note3 from '$lib/assets/236737bf76b26382113a8a12aa20c3369b57daaa.png';
let {
title = "Event 3"
} = $props();
</script>
<div class="event-card-3">
<div class="card-inner">
<img src={note3} alt="" class="note-bg" />
<div class="event-content">
<h3 class="event-title">{title}</h3>
</div>
</div>
</div>
<style>
.event-card-3 {
position: relative;
width: 100%;
max-width: 380px;
}
.card-inner {
position: relative;
transform: rotate(-21deg);
filter: drop-shadow(0px 8px 15.7px rgba(0, 0, 0, 0.25));
}
.note-bg {
width: 100%;
height: auto;
display: block;
}
.event-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
padding: 15% 10%;
box-sizing: border-box;
}
.event-title {
font-family: 'Kodchasan', sans-serif;
font-weight: 400;
color: #a58d28;
font-size: clamp(1.5rem, 4vw, 3rem);
text-align: center;
margin: 0;
}
</style>

View file

@ -6,12 +6,13 @@
import fireworks from '$lib/assets/e6e6af4f4af7ca8575187a49893a053c27a7a364.png';
import sparklyBorder from '$lib/assets/17ec48a0bb8b41dcf6f66b30c45989d5bf71a03a.png';
import fireworksGif from '$lib/assets/d1b77bc7ffefad4500eb8f570293aeb906cdd6c7.png';
import note1 from '$lib/assets/3a0136d1eb0536a623e080cb952a19b2a87af70f.png';
import note2 from '$lib/assets/b4cb411457c810c0897133da6ad9d5ae4468ea27.png';
import note3 from '$lib/assets/236737bf76b26382113a8a12aa20c3369b57daaa.png';
import stair from '$lib/assets/488278dc5d3cbf49ab57ddf735369a5419789dc6.png';
import running from '$lib/assets/00918d266837a309ad05ff4cce9ab7eca6910219.png';
import vectorLine from '$lib/assets/5dbe0fc43441b95045a7fe03b6870a2d6ca497ac.svg';
import EventCard1 from './EventCard1.svelte';
import EventCard2 from './EventCard2.svelte';
import EventCard3 from './EventCard3.svelte';
interface Step {
title: string;
@ -30,13 +31,13 @@
}
let {
heroDescription = "A sentence or two about what it is Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt",
ctaText = "I'M INSPIRED",
heroDescription = "Bet on yourself. When others quit, their stakes become your reward.",
ctaText = "STAKE YOUR CLAIM",
ctaHref = "/onboarding",
steps = [
{ title: "Step 1", description: "asfdskfhsdsdfasdfsdafdsdsfasdfsdaf" },
{ title: "Step 2", description: "asfdskfhsdsdfasdfsdafdsdsfasdfsdaf" },
{ title: "Step 3", description: "asfdskfhsdsdfasdfsdafdsdsfasdfsdaf" }
{ title: "⭐ One Star", description: "~2hr/week • 8 weeks • $80 stake. Light commitment, real results." },
{ title: "⭐⭐ Two Star", description: "~5hr/week • 8 weeks • $200 stake. Serious growth, bigger rewards." },
{ title: "⭐⭐⭐ Three Star", description: "~9hr/week • 8 weeks • $360 stake. Maximum effort, maximum payout." }
] as Step[],
events = [
{ title: "Event 1", description: "Lorem ipsum dolor sit amet consectetur adipiscing elit" },
@ -65,100 +66,94 @@
<img src={fireworksGif} alt="" />
</div>
<!-- <div class="hero-content">-->
<!-- <p class="hero-description">{heroDescription}</p>-->
<div class="hero-content">
<p class="hero-description">{heroDescription}</p>
<!-- <a href={ctaHref} class="cta-button">-->
<!-- <span>{ctaText}</span>-->
<!-- </a>-->
<a href={ctaHref} class="cta-button">
<span>{ctaText}</span>
</a>
</div>
</section>
<!-- &lt;!&ndash; Sparkly Border Divider &ndash;&gt;-->
<!-- <div class="sparkly-border">-->
<!-- <img src={sparklyBorder} alt="" />-->
<!-- </div>-->
<!-- &lt;!&ndash; Steps Section &ndash;&gt;-->
<!-- <section class="steps-section">-->
<!-- <img src={darkBg} alt="" class="section-bg dark-bg-1" />-->
<!-- <img src={darkBg} alt="" class="section-bg dark-bg-2" />-->
<!-- <img src={swirlBg} alt="" class="swirl-overlay" />-->
<!-- -->
<!-- <div class="big-firework">-->
<!-- <img src={fireworks} alt="" />-->
<!-- </div>-->
</section>
<!-- Sparkly Border Divider -->
<div class="sparkly-border">
<img src={sparklyBorder} alt="" />
</div>
<!-- <div class="steps-content">-->
<!-- <div class="step step-1">-->
<!-- <p class="step-text">{steps[0].title}:<br/>{steps[0].description}</p>-->
<!-- </div>-->
<!-- Steps Section -->
<section class="steps-section">
<img src={darkBg} alt="" class="section-bg dark-bg-1" />
<img src={darkBg} alt="" class="section-bg dark-bg-2" />
<img src={swirlBg} alt="" class="swirl-overlay" />
<!-- <div class="step step-2">-->
<!-- <p class="step-text">{steps[1].title}:<br/>{steps[1].description}</p>-->
<!-- </div>-->
<div class="steps-content">
<div class="step step-1">
<p class="step-text">{steps[0].title}:<br/>{steps[0].description}</p>
</div>
<!-- <div class="fireworks-decoration">-->
<!-- <img src={fireworks} alt="" />-->
<!-- </div>-->
<div class="step step-2">
<p class="step-text">{steps[1].title}:<br/>{steps[1].description}</p>
</div>
<!-- <div class="step step-3">-->
<!-- <p class="step-text">{steps[2].title}:<br/>{steps[2].description}</p>-->
<!-- </div>-->
<!-- </div>-->
<!-- </section>-->
<div class="fireworks-decoration">
<img src={fireworks} alt="" />
</div>
<!-- &lt;!&ndash; Events & FAQ Section (combined on same light blue background) &ndash;&gt;-->
<!-- <section class="events-faq-section">-->
<!-- <img src={lightBlueBg} alt="" class="section-bg" />-->
<!-- -->
<!-- &lt;!&ndash; Stories from Past Events &ndash;&gt;-->
<!-- <h2 class="events-title">Stories from Past Events</h2>-->
<div class="step step-3">
<p class="step-text">{steps[2].title}:<br/>{steps[2].description}</p>
</div>
</div>
</section>
<!-- <div class="events-grid">-->
<!-- <div class="event-card event-1">-->
<!-- <EventCard1 title={events[0].title} description={events[0].description} />-->
<!-- </div>-->
<!-- Events & FAQ Section (combined on same light blue background) -->
<section class="events-faq-section">
<img src={lightBlueBg} alt="" class="section-bg" />
<!-- Stories from Past Events -->
<h2 class="events-title">Stories from Past Events</h2>
<!-- <div class="event-card event-2">-->
<!-- <EventCard2 title={events[1].title} />-->
<!-- </div>-->
<div class="events-grid">
<div class="event-card event-1">
<img src={note1} alt="" class="note-bg" />
<div class="event-content">
<h3>{events[0].title}</h3>
<p>{events[0].description}</p>
</div>
</div>
<!-- <div class="event-card event-3">-->
<!-- <EventCard3 title={events[2].title} />-->
<!-- </div>-->
<!-- </div>-->
<div class="event-card event-2">
<img src={note2} alt="" class="note-bg" />
<div class="event-content">
<h3>{events[1].title}</h3>
</div>
</div>
<!-- &lt;!&ndash; FAQ &ndash;&gt;-->
<!-- <div class="running-decoration">-->
<!-- <img src={running} alt="" />-->
<!-- </div>-->
<div class="event-card event-3">
<img src={note3} alt="" class="note-bg" />
<div class="event-content">
<h3>{events[2].title}</h3>
</div>
</div>
</div>
<!-- <h2 class="faq-title">FAQ</h2>-->
<!-- FAQ -->
<div class="running-decoration">
<img src={running} alt="" />
</div>
<!-- <div class="faq-wrapper">-->
<!-- <div class="faq-list">-->
<!-- {#each faqs as faq, i}-->
<!-- <div class="faq-item">-->
<!-- <span class="faq-question">{faq.question}</span>-->
<!-- </div>-->
<!-- {#if i < faqs.length - 1}-->
<!-- <img src={vectorLine} alt="" class="faq-divider" />-->
<!-- {/if}-->
<!-- {/each}-->
<!-- </div>-->
<h2 class="faq-title">FAQ</h2>
<div class="faq-wrapper">
<div class="faq-list">
{#each faqs as faq, i}
<div class="faq-item">
<span class="faq-question">{faq.question}</span>
</div>
{#if i < faqs.length - 1}
<img src={vectorLine} alt="" class="faq-divider" />
{/if}
{/each}
</div>
<div class="stair-decoration">
<img src={stair} alt="" />
</div>
</div>
</section>
<!-- <div class="stair-decoration">-->
<!-- <img src={stair} alt="" />-->
<!-- </div>-->
<!-- </div>-->
<!-- </section>-->
</div>
<style>
@ -285,7 +280,10 @@
}
.dark-bg-1 {
position: relative;
position: absolute;
top: 0;
right: 0;
left: auto;
}
.dark-bg-2 {
@ -295,9 +293,7 @@
}
.swirl-overlay {
position: absolute;
top: 0;
left: 0;
position: relative;
width: 100%;
height: auto;
z-index: 1;
@ -309,11 +305,12 @@
top: 0;
left: 0;
width: 100%;
height: 100%;
height: 65%;
z-index: 2;
display: flex;
flex-direction: column;
padding: 5% 5%;
justify-content: space-between;
padding: 3% 5% 5%;
}
.step {
@ -322,17 +319,17 @@
.step-1 {
align-self: flex-start;
margin-top: 5%;
margin-left: 25%;
}
.step-2 {
align-self: flex-end;
margin-top: 25%;
margin-right: 15%;
}
.step-3 {
align-self: flex-start;
margin-top: 25%;
align-self: flex-end;
margin-right: 25%;
}
.step-text {
@ -344,13 +341,30 @@
margin: 0;
}
.big-firework {
position: absolute;
left: -125%;
top: 5%;
width: 160%;
/*max-width: 2400px;*/
z-index: 1;
pointer-events: none;
transform: rotate(-40deg);
filter: blur(10px);
opacity: 0.85;
}
.big-firework img {
width: 100%;
height: auto;
}
.fireworks-decoration {
position: absolute;
right: 0;
right: -15%;
top: 40%;
width: 30%;
max-width: 450px;
z-index: 0;
width: 80%;
z-index: 3;
pointer-events: none;
}
@ -363,18 +377,29 @@
.events-faq-section {
position: relative;
width: 100%;
overflow: hidden;
background: #a8d4f5;
}
.events-faq-section .section-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 0;
}
.events-faq-section .events-title,
.events-faq-section .events-grid,
.events-faq-section .faq-title,
.events-faq-section .faq-wrapper,
.events-faq-section .running-decoration {
position: relative;
z-index: 1;
}
.events-title {
position: absolute;
top: 5%;
left: 50%;
transform: translateX(-50%);
font-family: 'Kodchasan', sans-serif;
font-weight: 400;
font-style: italic;
@ -382,20 +407,17 @@
font-size: clamp(1.5rem, 4vw, 3.5rem);
text-align: center;
margin: 0;
padding: 8rem 0 2rem;
white-space: nowrap;
}
.events-grid {
position: absolute;
top: 20%;
left: 0;
width: 100%;
height: 60%;
display: flex;
justify-content: center;
align-items: flex-start;
gap: 2%;
padding: 0 5%;
padding: 2rem 5% 4rem;
flex-wrap: wrap;
}
.event-card {
@ -410,47 +432,15 @@
}
.event-2 {
transform: rotate(12deg);
align-self: center;
margin-top: 10%;
}
.event-3 {
transform: rotate(-21deg);
align-self: flex-start;
margin-left: auto;
}
.note-bg {
width: 100%;
height: auto;
display: block;
filter: drop-shadow(0 8px 6px rgba(0, 0, 0, 0.25));
}
.event-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
width: 80%;
}
.event-content h3 {
color: #a58d28;
font-size: clamp(1rem, 2.5vw, 2rem);
font-weight: 400;
margin: 0;
}
.event-content p {
color: #a58d28;
font-size: clamp(0.7rem, 1.2vw, 1rem);
margin-top: 0.5rem;
line-height: 1.4;
}
/* ========== FAQ ELEMENTS (within combined section) ========== */
.running-decoration {
position: absolute;

View file

@ -1,35 +1,247 @@
<script>
let { visible = false, text = "Your resolution here" } = $props();
<script lang="ts">
import DOMPurify from "dompurify";
let { visible = false, children, text = "" } = $props();
let submitted = $state(false);
let error = $state("");
function handleSubmit(e: SubmitEvent) {
e.preventDefault();
const form = e.target as HTMLFormElement;
const formData = new FormData(form);
const rawEmail = formData.get("email") as string;
const cleanEmail = DOMPurify.sanitize(rawEmail.trim()).slice(0, 254);
error = "";
if (!cleanEmail) {
error = "Please enter your email";
return;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(cleanEmail)) {
error = "Please enter a valid email";
return;
}
console.log("Email submitted:", cleanEmail);
submitted = true;
}
</script>
<div class="resolution-paper" class:visible>
<p class="resolution-text">{text}</p>
<div class="paper-content">
{#if children}
{@render children()}
{:else if text}
<p class="paper-text">{text}</p>
{:else if submitted}
<div class="success-message">
<p class="success-text">You're in!</p>
<p class="success-subtext">Check your email for next steps.</p>
</div>
{:else}
<p class="paper-subtitle">Stake your claim. Join the challenge.</p>
<form class="signup-form" onsubmit={handleSubmit}>
<div class="input-wrapper">
<input
type="email"
name="email"
placeholder="your@email.com"
class="email-input"
required
/>
<button type="submit" class="submit-btn"></button>
</div>
{#if error}
<p class="error-text">{error}</p>
{/if}
</form>
{/if}
</div>
</div>
<style>
.resolution-paper {
position: absolute;
inset: 0;
background-image: url('$lib/assets/resolution_paper.png');
background-image: url("$lib/assets/resolution_paper.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
display: flex;
justify-content: center;
align-items: center;
transform: translateY(100%);
transform: translateY(100%) translateZ(0);
will-change: transform;
backface-visibility: hidden;
transition: transform 0.8s ease-out;
}
.resolution-paper.visible {
transform: translateY(0);
transform: translateY(0) translateZ(0);
}
.resolution-text {
.paper-content {
position: absolute;
/* Area covering the blank space efficiently */
top: 38%;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1.5rem;
padding: 1rem;
/* Area of the notepad paper */
width: 35%;
height: 50%;
overflow-y: auto;
/* Optional: for debugging the border */
/* border: 2px dashed rgba(0,0,0,0.1); */
}
.paper-subtitle {
font-family: "Patrick Hand", cursive;
font-weight: 400;
font-style: normal;
font-size: 2rem;
color: #B5AB9A;
font-size: 1.5rem;
color: #5a5247;
margin: 0;
text-align: center;
font-weight: normal;
}
.paper-text {
font-family: "Patrick Hand", cursive;
font-size: 1.5rem;
color: #5a5247;
margin: 0;
text-align: center;
line-height: 1.4;
}
.signup-form {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.input-wrapper {
display: flex;
background: white;
border: 2px solid #5a5247;
border-radius: 4px; /* More of a paper-like feel than pill-shaped */
overflow: hidden;
box-shadow: 2px 2px 0 0 #5a5247;
width: 100%;
}
.input-wrapper:focus-within {
border-color: #e472ab;
box-shadow: 2px 2px 0 0 #e472ab;
}
.email-input {
flex: 1;
font-family: "Patrick Hand", cursive;
font-size: 1.25rem;
padding: 0.75rem 1.25rem;
border: none;
background: transparent;
color: #333;
outline: none;
}
.email-input::placeholder {
color: #aaa;
}
.email-input.error {
color: #c44;
}
.submit-btn {
font-family: "Patrick Hand", cursive;
font-size: 1.5rem;
padding: 0.75rem 1.5rem;
background: #ffe475;
border: none;
border-left: 2px solid #5a5247;
color: #5a5247;
cursor: pointer;
transition: background 0.2s;
}
.submit-btn:hover:not(:disabled) {
background: #ffd93d;
}
.submit-btn:active:not(:disabled) {
background: #ffcc00;
}
.submit-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.error-text {
font-family: "Patrick Hand", cursive;
font-size: 1rem;
color: #c44;
margin: 0;
text-align: center;
}
.success-message {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.success-text {
font-family: "Patrick Hand", cursive;
font-size: 2.5rem;
color: #4a7c59;
margin: 0;
}
.success-subtext {
font-family: "Patrick Hand", cursive;
font-size: 1.2rem;
color: #7a7060;
margin: 0;
}
@media (max-width: 480px) {
.paper-content {
width: 65%;
height: 50%;
top: 38%;
left: 50%;
}
.paper-title {
font-size: 2.5rem;
}
.paper-subtitle {
font-size: 1.1rem;
}
.email-input {
font-size: 1rem;
padding: 0.6rem 1rem;
}
.submit-btn {
font-size: 1.25rem;
padding: 0.6rem 1.25rem;
}
}
</style>

View file

@ -13,7 +13,7 @@
<div class="onboarding">
<div class="bg-first"></div>
<ResolutionPaper visible={showPaper} text="Your resolution here" />
<ResolutionPaper visible={showPaper} />
</div>
<style>

View file

@ -3,13 +3,13 @@
</script>
<InitialPage
heroDescription="A sentence or two about what it is Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt"
ctaText="I'M INSPIRED"
heroDescription="Bet on yourself. When others quit, their stakes become your reward."
ctaText="STAKE YOUR CLAIM"
ctaHref="/onboarding"
steps={[
{ number: 1, title: "Step 1", description: "Capture Your Intentions - Resolution helps you turn fleeting thoughts into concrete goals." },
{ number: 2, title: "Step 2", description: "Track Your Progress - Follow your journey from the first spark to full realization." },
{ number: 3, title: "Step 3", description: "Celebrate Success - Every milestone reached is a reason to celebrate." }
{ title: "⭐ One Star", description: "~2hr/week • 8 weeks • $80 stake. Light commitment, real results." },
{ title: "⭐⭐ Two Star", description: "~5hr/week • 8 weeks • $200 stake. Serious growth, bigger rewards." },
{ title: "⭐⭐⭐ Three Star", description: "~9hr/week • 8 weeks • $360 stake. Maximum effort, maximum payout." }
]}
events={[
{ title: "Event 1", description: "Join our community workshop on goal setting and accountability." },