11---
22import type { Freebie } from " ../data/freebies"
3- import Tag from " ./Tag "
3+ import FreebieSidebarContent from " ./FreebieSidebarContent.astro "
44
55export interface Props {
66 freebie: Freebie
@@ -10,56 +10,57 @@ const { freebie } = Astro.props
1010---
1111
1212<script async src =" https://f.convertkit.com/ckjs/ck.6.js" ></script >
13- <aside class =" freebie-sidebar" data-freebie-sidebar >
14- <img src ={ freebie .image } alt ={ freebie .title } class =" freebie-image" />
15- <span class =" freebie-badge" >FREE</span >
16- <h3 class =" freebie-title" >{ freebie .title } </h3 >
17- <p class =" freebie-description" >{ freebie .description } </p >
18- <form
19- action ={ ` https://app.kit.com/forms/${freebie .kitFormId }/subscriptions ` }
20- method =" post"
21- class =" freebie-form"
22- data-sv-form ={ freebie .kitFormId }
23- data-uid ={ freebie .kitFormUrlId }
24- data-freebie-form
25- data-element =" content"
26- >
27- <div data-element =" errors" class =" hidden" ></div >
28- <div data-element =" fields" style =" display: contents;" data-fields-wrapper >
29- <input
30- type =" text"
31- name =" fields[first_name]"
32- placeholder =" First Name"
33- class =" freebie-input"
34- aria-label =" First Name"
35- />
36- <input
37- type =" email"
38- name =" email_address"
39- placeholder =" Email Address"
40- required
41- class =" freebie-input"
42- aria-label =" Email Address"
43- />
44- <button data-element =" submit" type =" submit" class =" freebie-submit"
45- >Download Now</button
46- >
47- </div >
48- </form >
49- <p class =" freebie-privacy" data-freebie-privacy >
50- No spam. Unsubscribe anytime.
51- </p >
52- <p class =" freebie-success hidden" data-freebie-success >
53- Thanks for signing up for my { freebie .title } ! Check your email to download
54- it!
55- </p >
13+
14+ <!-- Desktop sidebar -->
15+ <aside class =" freebie-sidebar" >
16+ <FreebieSidebarContent freebie ={ freebie } />
5617</aside >
5718
58- <style >
59- .hidden {
60- display: none;
61- }
19+ <!-- Mobile button + dialog drawer -->
20+ <button class =" freebie-mobile-btn freebie-btn" data-freebie-open >
21+ <svg
22+ width =" 800px"
23+ height =" 800px"
24+ viewBox =" 0 0 24 24"
25+ fill =" none"
26+ xmlns =" http://www.w3.org/2000/svg"
27+ >
28+ <path
29+ d =" M3 15C3 17.8284 3 19.2426 3.87868 20.1213C4.75736 21 6.17157 21 9 21H15C17.8284 21 19.2426 21 20.1213 20.1213C21 19.2426 21 17.8284 21 15"
30+ stroke =" currentColor"
31+ stroke-width =" 1.5"
32+ stroke-linecap =" round"
33+ stroke-linejoin =" round" ></path >
34+ <path
35+ d =" M12 3V16M12 16L16 11.625M12 16L8 11.625"
36+ stroke =" currentColor"
37+ stroke-width =" 1.5"
38+ stroke-linecap =" round"
39+ stroke-linejoin =" round" ></path >
40+ </svg >Free { freebie .shortTitle } </button
41+ >
42+
43+ <dialog class =" freebie-dialog" data-freebie-dialog >
44+ <button class =" freebie-close-btn" data-freebie-close aria-label =" Close" >
45+ <svg
46+ width =" 24"
47+ height =" 24"
48+ viewBox =" 0 0 24 24"
49+ fill =" none"
50+ xmlns =" http://www.w3.org/2000/svg"
51+ >
52+ <path
53+ d =" M18 6L6 18M6 6L18 18"
54+ stroke =" currentColor"
55+ stroke-width =" 2"
56+ stroke-linecap =" round"
57+ stroke-linejoin =" round" ></path >
58+ </svg >
59+ </button >
60+ <FreebieSidebarContent freebie ={ freebie } />
61+ </dialog >
6262
63+ <style >
6364 .freebie-sidebar {
6465 position: sticky;
6566 top: 1rem;
@@ -72,121 +73,135 @@ const { freebie } = Astro.props
7273 border: 1px solid var(--theme-tangent-border);
7374 border-radius: 0.75rem;
7475 padding: 1.25rem;
75- display: flex;
76- flex-direction: column;
77- gap: 0.5rem;
78- }
79-
80- .freebie-image {
81- width: 100%;
82- object-fit: cover;
83- object-position: top;
84- filter: drop-shadow(0 0 10px #000b);
85- min-height: 0;
86- margin-bottom: 0.5rem;
87- }
88-
89- .freebie-badge {
90- width: fit-content;
91- background-color: var(--theme-freebie-button);
92- color: var(--theme-freebie-button-text);
93- font-size: 0.75rem;
94- font-weight: 600;
95- padding: 0.25rem 0.5rem;
96- border-radius: 0.25rem;
97- text-transform: uppercase;
98- }
9976
100- .freebie-title {
101- margin: 0;
102- font-size: 1.25rem;
103- font-weight: 600;
104- color: var(--theme-text);
105- line-height: 1.3;
106- margin-bottom: -0.25rem;
107- }
108-
109- .freebie-description {
110- margin: 0;
111- font-size: 0.9rem;
112- color: var(--theme-text-light);
113- line-height: 1.5;
114- white-space: pre-wrap;
115- }
116-
117- .freebie-form {
11877 display: flex;
119- flex-direction: column;
120- gap: 0.5rem;
12178 }
12279
123- .freebie-input {
124- padding: 0.75rem 1rem;
125- font-size: 0.95rem;
126- border: 1px solid var(--theme-tangent-border);
127- border-radius: 0.5rem;
128- background-color: var(--theme-bg);
129- color: var(--theme-text);
130- font-family: inherit;
131- transition:
132- border-color 0.2s ease,
133- box-shadow 0.2s ease;
134- }
135-
136- .freebie-input:focus-visible {
137- outline: none;
138- border-color: var(--theme-accent);
139- box-shadow: 0 0 0 3px var(--theme-bg-accent);
140- }
141-
142- .freebie-input::placeholder {
143- color: var(--theme-text-lighter);
144- }
145-
146- .freebie-submit {
80+ .freebie-btn {
14781 padding: 0.75rem 1rem;
14882 font-size: 1rem;
149- font-weight: 600;
15083 font-family: inherit;
15184 color: var(--theme-freebie-button-text);
15285 background-color: var(--theme-freebie-button);
15386 border: none;
15487 border-radius: 0.5rem;
15588 cursor: pointer;
156- transition:
157- background-color 0.2s ease,
158- transform 0.1s ease;
89+ transition: background-color 0.2s ease;
15990 }
16091
161- .freebie-submit :hover {
92+ .freebie-btn :hover {
16293 background-color: var(--theme-freebie-button-hover);
16394 }
16495
165- .freebie-submit:active {
166- transform: scale(0.98);
96+ .freebie-mobile-btn {
97+ display: none;
98+ position: fixed;
99+ bottom: 1rem;
100+ right: 1rem;
101+ align-items: center;
102+ gap: 0.5rem;
103+ z-index: 999;
167104 }
168105
169- .freebie-privacy {
170- margin: 0;
171- font-size: 0.75rem;
172- color: var(--theme-text-light);
173- text-align: center;
106+ .freebie-mobile-btn svg {
107+ width: 1.25rem;
108+ height: 1.25rem;
109+ vertical-align: middle;
174110 }
175111
176- .freebie-success {
177- margin: 0;
178- font-size: 0.9rem;
179- background: var(--theme-green);
112+ /* Dialog drawer styles */
113+ .freebie-dialog {
114+ display: none;
115+ }
116+
117+ .freebie-dialog::backdrop {
118+ background: rgba(0, 0, 0, 0.5);
119+ opacity: 0;
120+ }
121+
122+ .freebie-dialog[open]::backdrop {
123+ opacity: 1;
124+ }
125+
126+ .freebie-close-btn {
127+ position: absolute;
128+ top: 1rem;
129+ right: 1rem;
130+ align-self: flex-end;
131+ background: none;
132+ border: none;
180133 color: var(--theme-text);
181- border-radius: 0.5rem;
182- padding: 0.75em 1em;
183- text-align: center;
134+ cursor: pointer;
135+ padding: 0.25rem;
136+ border-radius: 0.25rem;
137+ display: flex;
138+ align-items: center;
139+ justify-content: center;
140+ transition: background-color 0.2s ease;
141+ }
142+
143+ .freebie-close-btn:hover {
144+ background-color: var(--theme-tangent-border);
184145 }
185146
186147 @media (max-width: 1100px) {
187148 .freebie-sidebar {
188149 display: none;
189150 }
151+
152+ .freebie-mobile-btn {
153+ display: flex;
154+ }
155+
156+ .freebie-dialog {
157+ position: fixed;
158+ top: 0;
159+ right: 0;
160+ bottom: 0;
161+ left: auto;
162+ margin: 0;
163+ margin-left: auto;
164+ width: 100%;
165+ max-width: 360px;
166+ height: 100dvh;
167+ max-height: 100dvh;
168+
169+ background-color: var(--theme-tangent-bg);
170+ border: none;
171+ border-left: 1px solid var(--theme-tangent-border);
172+ padding: 1.25rem;
173+ align-items: center;
174+
175+ translate: 100%;
176+ transition:
177+ translate 0.3s ease-in-out,
178+ overlay 0.3s allow-discrete,
179+ display 0.3s allow-discrete;
180+ }
181+
182+ .freebie-dialog[open] {
183+ display: flex;
184+ translate: 0;
185+ }
186+
187+ @starting-style {
188+ .freebie-dialog[open] {
189+ translate: 100%;
190+ }
191+ }
192+
193+ .freebie-dialog::backdrop {
194+ transition:
195+ opacity 0.3s ease-in-out,
196+ overlay 0.3s allow-discrete,
197+ display 0.3s allow-discrete;
198+ }
199+
200+ @starting-style {
201+ .freebie-dialog[open]::backdrop {
202+ opacity: 0;
203+ }
204+ }
190205 }
191206
192207 :root {
@@ -203,27 +218,30 @@ const { freebie } = Astro.props
203218</style >
204219
205220<script >
206- const forms = document.querySelectorAll("[data-freebie-form]")
207-
208- const observer = new MutationObserver(mutations => {
209- mutations.forEach(mutation => {
210- if (mutation.type !== "childList") return
211-
212- mutation.addedNodes.forEach(node => {
213- if (node instanceof HTMLElement && node.matches("[data-element]")) {
214- const sidebar = node.closest("[data-freebie-sidebar]")
215- if (sidebar == null) return
216- node.remove()
217- sidebar.querySelector("[data-freebie-privacy]")?.remove()
218- sidebar
219- .querySelector("[data-freebie-success]")
220- ?.classList.remove("hidden")
221- }
222- })
223- })
221+ const openBtn = document.querySelector("[data-freebie-open]")
222+ const dialog = document.querySelector(
223+ "[data-freebie-dialog]",
224+ ) as HTMLDialogElement
225+ const closeBtn = document.querySelector("[data-freebie-close]")
226+
227+ openBtn?.addEventListener("click", () => {
228+ dialog?.showModal()
229+ })
230+
231+ closeBtn?.addEventListener("click", () => {
232+ dialog?.close()
224233 })
225234
226- forms.forEach(form => {
227- observer.observe(form, { childList: true })
235+ // Close when clicking the backdrop
236+ dialog?.addEventListener("click", e => {
237+ const rect = dialog.getBoundingClientRect()
238+ if (
239+ e.clientX < rect.left ||
240+ e.clientX > rect.right ||
241+ e.clientY < rect.top ||
242+ e.clientY > rect.bottom
243+ ) {
244+ dialog.close()
245+ }
228246 })
229247</script >
0 commit comments