rawfeed 0.2.11 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/{LICENSE.txt → LICENSE} +1 -1
  3. data/README.md +12 -0
  4. data/_data/options.yml +47 -0
  5. data/_includes/layout/data.liquid +2 -0
  6. data/_layouts/contact.html +130 -50
  7. data/_layouts/default.html +13 -6
  8. data/_layouts/donation.html +113 -0
  9. data/_sass/base/_index.scss +4 -7
  10. data/_sass/components/_markdown.scss +1 -2
  11. data/_sass/layouts/_donation.scss +93 -0
  12. data/_sass/layouts/_index.scss +1 -0
  13. data/_sass/layouts/_post.scss +146 -0
  14. data/_sass/main.scss +10 -0
  15. data/_sass/theme/_dark.scss +5 -0
  16. data/_sass/theme/_light.scss +5 -0
  17. data/assets/images/icons/bitcoin.svg +6 -0
  18. data/assets/images/icons/card.svg +17 -0
  19. data/assets/images/icons/donation.svg +11 -0
  20. data/assets/images/icons/lightning_network.svg +11 -0
  21. data/assets/images/icons/paypal.svg +7 -0
  22. data/assets/images/icons/pix.svg +1 -0
  23. data/assets/images/icons/wos.png +0 -0
  24. data/assets/images/qrcode_btc_binance.jpg +0 -0
  25. data/assets/images/qrcode_inter.jpg +0 -0
  26. data/assets/images/qrcode_wos.jpg +0 -0
  27. data/assets/js/donation.js +30 -0
  28. data/assets/js/{fallback/page.js → page.js} +94 -167
  29. data/lib/rawfeed/version.rb +1 -1
  30. metadata +26 -41
  31. data/_includes/toc +0 -20
  32. data/assets/js/blog.coffee +0 -102
  33. data/assets/js/contact.coffee +0 -105
  34. data/assets/js/default.coffee +0 -172
  35. data/assets/js/discus.coffee +0 -30
  36. data/assets/js/fallback/README.md +0 -3
  37. data/assets/js/fallback/google_analytics.js +0 -15
  38. data/assets/js/home.coffee +0 -250
  39. data/assets/js/no_inframe.coffee +0 -9
  40. data/assets/js/page.coffee +0 -379
  41. data/assets/js/pixels.coffee +0 -2
  42. data/assets/js/resume.coffee +0 -9
  43. data/assets/js/tags.coffee +0 -2
  44. data/assets/js/theme_load.coffee +0 -6
  45. /data/assets/js/{fallback/blog.js → blog.js} +0 -0
  46. /data/assets/js/{fallback/contact.js → contact.js} +0 -0
  47. /data/assets/js/{fallback/default.js → default.js} +0 -0
  48. /data/assets/js/{fallback/discus.js → discus.js} +0 -0
  49. /data/assets/js/{fallback/home.js → home.js} +0 -0
  50. /data/assets/js/{fallback/no_inframe.js → no_inframe.js} +0 -0
  51. /data/assets/js/{fallback/pixels.js → pixels.js} +0 -0
  52. /data/assets/js/{fallback/resume.js → resume.js} +0 -0
  53. /data/assets/js/{fallback/tags.js → tags.js} +0 -0
  54. /data/assets/js/{fallback/theme_load.js → theme_load.js} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03a342b47ad00dfaf348d03b249341e4b27491cf888fa2d2725812098524ce8d
4
- data.tar.gz: e8e66e6391f21181e61642764582c4c389481548d318f7b5e70e2e30a2ba146d
3
+ metadata.gz: e90f2046af539b18abf2577a734e3f9103451339cd514c8d6991cab4159f2014
4
+ data.tar.gz: b8caefb8c8490ea72d2ef54bc63d81b9dd52e6289a948ebb68ea835b9c56e1d3
5
5
  SHA512:
6
- metadata.gz: 9e7d68af9e545e550fddf9f9595e06ce68181636f41f952a5a0272d2a6e48e009d6e4feba38774dc86c35038a4856e0cb07d1afb2922316f00cf9b563e962f60
7
- data.tar.gz: 8f6dd9a0401d5435cb8ef04bde376c6ae56b3f0f8cf61416b442aa947bf6fd56976bf5de73a31108b65ceddf7a3eb0062ab5b7829c2096897cbdedf8562125d7
6
+ metadata.gz: bd6bc37c291b21dcc26065d64d2fced0d99e03a37b186821608c8b467715adebaa93950c074ec628eb0b2cd400829c778047542fcfaa251c4fe59e08dd2a248a
7
+ data.tar.gz: 56c7159e3db7c4508c3c03acd1523bb615ae1c49a47f0bc927b3e06fb05893245bccce957df55b0d601ee0df2351c14cc63b76ee3bf02672683fef7d50a6dab6
@@ -1,6 +1,6 @@
1
1
  Creative Commons Attribution 4.0 International (CC BY 4.0)
2
2
 
3
- Copyright (c) 2025 {rawfeed} <https://rawfeed.github.io>
3
+ Copyright (c) 2026 {rawfeed} <https://rawfeed.github.io>
4
4
 
5
5
  This theme is licensed under the Creative Commons Attribution 4.0 International License.
6
6
 
data/README.md CHANGED
@@ -51,3 +51,15 @@ See [here](https://rawfeed.github.io/rawfeed-jekyll/changelog/)
51
51
  # License
52
52
 
53
53
  The theme is available as open source under the terms of [this License](https://rawfeed.github.io/rawfeed-jekyll/license/).
54
+
55
+ # Donation
56
+
57
+ Click on the image below to be redirected to the donation forms:
58
+
59
+ <div class="donation">
60
+ <a href="https://williamcanin.github.io/donate/" target="_blank">
61
+ <img width="160" height="100" src="assets/images/icons/donation.svg" alt="Donate"/>
62
+ </a>
63
+ </div>
64
+
65
+ > It is very important that you make a donation to motivate further development of rawfeed-jekyll. :)
data/_data/options.yml CHANGED
@@ -102,6 +102,7 @@ default:
102
102
  width: 860 # default: 860
103
103
  background_focus: true
104
104
  rounding: false
105
+ user_select: none # none | text
105
106
  font:
106
107
  # Fonts Style Options: [default: monospace]
107
108
  # "'Ubuntu', sans-serif"
@@ -221,8 +222,11 @@ pixels:
221
222
  title: "pixels"
222
223
  layout:
223
224
  width: 860 # default: 860
225
+
224
226
  # section: [page]
225
227
  page:
228
+ toc:
229
+ title: "Table of Contents"
226
230
  updated_on: "Page updated on"
227
231
  error:
228
232
  image: https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExNXlxYzc5eXp1Nm01cmtldzM5bHhwdzJ2Njk2c2E4b2RkdTBkeGs5eCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/c20UV66B7zCWA/giphy.gif
@@ -280,3 +284,46 @@ footer:
280
284
  page_url: "/licenses/"
281
285
  message: |
282
286
  Your impactful phrase!
287
+
288
+ # section: [donation]
289
+ # Tip: use svg images for better quality (https://www.iconpacks.net)
290
+ donation:
291
+ title: "Support the Project"
292
+ description: "If this project helps you, consider supporting it"
293
+ copy_button:
294
+ text: "Copy"
295
+ clicked: "Copied!"
296
+ services:
297
+ - name: pix
298
+ logo: "/assets/images/icons/pix.svg"
299
+ description: |
300
+ Make your donation using PIX from Brazil. Use the key below.
301
+ address: "00b53f61-b009-4f05-8c43-444cce86af1d"
302
+ qrcode: "/assets/images/qrcode_inter.jpg"
303
+
304
+ - name: card
305
+ logo: "/assets/images/icons/card.svg"
306
+ description: |
307
+ Donation using card through the service `Buy Me a Coffee`.
308
+ address: "https://buymeacoffee.com/williamcanin"
309
+
310
+ - name: paypal
311
+ logo: "/assets/images/icons/paypal.svg"
312
+ description: |
313
+ Donate quickly and securely using your PayPal balance or linked card.
314
+ address: "https://www.paypal.com/donate/?cmd=_s-xclick&hosted_button_id=YBK2HEEYG8V5W&source=&ssrt=1775261628264"
315
+
316
+ - name: bitcoin (lightning network)
317
+ logo: "/assets/images/icons/lightning_network.svg"
318
+ description: |
319
+ Make an anonymous Bitcoin donation using a Lightning Address.
320
+ address: "williamcanin@walletofsatoshi.com"
321
+ qrcode: "/assets/images/qrcode_wos.jpg"
322
+
323
+ - name: bitcoin (on-chain)
324
+ logo: "/assets/images/icons/bitcoin.svg"
325
+ description: |
326
+ Make an anonymous Bitcoin donation using a On-Chain address (Binance).
327
+ address: "bc1qluh2mrchmtrwedspwhtta8ccgzsl9jqdr47f2p"
328
+ qrcode: "/assets/images/qrcode_btc_binance.jpg"
329
+
@@ -6,6 +6,7 @@
6
6
  {%- assign search_url = "/blog/?search=open" | relative_url -%}
7
7
  {%- assign tags_url = "/blog/tags/" | relative_url -%}
8
8
  {%- assign pub_url = "/pub/" -%}
9
+ {%- assign donate_url = "/donate/" -%}
9
10
  {%- assign feed_url = "/feed.xml" | relative_url -%}
10
11
  {%- assign head_ = site.data.options.head -%}
11
12
  {%- assign site_title = site.data.options.title -%}
@@ -16,6 +17,7 @@
16
17
  {%- assign home_ = site.data.options.home -%}
17
18
  {%- assign default_ = site.data.options.default -%}
18
19
  {%- assign page_ = site.data.options.page -%}
20
+ {%- assign donation_ = site.data.options.donation -%}
19
21
  {%- assign contact_ = site.data.options.contact -%}
20
22
  {%- assign footer_ = site.data.options.footer -%}
21
23
  {%- assign socials_ = site.data.options.socials -%}
@@ -81,82 +81,162 @@ layout: default
81
81
  <p>To use the email submission form, you need to:</p>
82
82
  <p>1 - Copy the script below and implement it in <a href="https://script.google.com" target="_blank">Google Apps Script</a>:</p>
83
83
  <blockquote>
84
- <p>Note1: Don't forget to put your gmail in the script.</p>
85
- <p>Note2: Without editing the script in Google Apps Script, you need to deploy it again.</p>
84
+ <p>Note1: Don't forget to change the constants: TO_ADDRESS, SITE, LOGO_URL, MINIMUM_MESSAGE.</p>
85
+ <p>Note2: Without editing the script in Google Apps Script, you will need to redeploy it.</p>
86
86
  </blockquote>
87
87
 
88
88
  {% highlight javascript linenos %}
89
- // IMPORTANT! You must put your gmail email here.
90
- const TO_ADDRESS = "YOUR EMAIL GMAIL";
89
+ /**
90
+ * GOOGLE APPS SCRIPT - HYBRID VERSION (RECAPTCHA & TURNSTYLE)
91
+ * by William C. Canin
92
+ */
91
93
 
92
- // Get the secret key from the Script Properties
93
- const RECAPTCHA_SECRET_KEY = PropertiesService.getScriptProperties().getProperty('RECAPTCHA_SECRET_KEY');
94
+ const TO_ADDRESS = "example@mail.com";
95
+ const SITE = "https://example.com";
96
+ const LOGO_URL = "https://example.com/assets/images/logo.png";
97
+ const MINIMUM_MESSAGE = 50;
94
98
 
95
- // Function to validate the reCAPTCHA token
96
- function validateRecaptcha(token) {
97
- if (!token) {
98
- throw new Error("Missing reCAPTCHA token.");
99
- }
100
- const url = "https://www.google.com/recaptcha/api/siteverify";
101
- const payload = {
102
- secret: RECAPTCHA_SECRET_KEY,
103
- response: token
104
- };
99
+ // Accessing script properties
100
+ const props = PropertiesService.getScriptProperties();
101
+
102
+ /**
103
+ * HTML sanitization and Header Injection prevention
104
+ */
105
+ function sanitize(str, isHeader = false) {
106
+ if (!str) return "";
107
+ let clean = str.trim();
108
+ if (isHeader) clean = clean.replace(/[\r\n]/g, "");
109
+ return clean.replace(/[&<>"']/g, function(m) {
110
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' }[m];
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Cloudflare Turnstile Validation
116
+ */
117
+ function validateTurnstile(token) {
118
+ if (!token) throw new Error("[Script]: Turnstile token missing.");
119
+ const secret = props.getProperty("TURNSTILE_SECRET_KEY");
105
120
 
106
- const response = UrlFetchApp.fetch(url, {
121
+ const response = UrlFetchApp.fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
107
122
  method: "post",
108
- payload: payload
123
+ payload: { secret: secret, response: token }
109
124
  });
110
125
 
111
126
  const result = JSON.parse(response.getContentText());
127
+ if (!result.success) throw new Error("[Script]: Turnstile validation failed.");
128
+ return true;
129
+ }
112
130
 
113
- if (!result.success) {
114
- throw new Error("reCAPTCHA verification failed: " + (result['error-codes'] || 'Unknown error.'));
115
- }
131
+ /**
132
+ * Google reCAPTCHA Validation
133
+ */
134
+ function validateRecaptcha(token) {
135
+ if (!token) throw new Error("[Script]: reCAPTCHA token missing.");
136
+ const secret = props.getProperty("RECAPTCHA_SECRET_KEY");
137
+
138
+ const response = UrlFetchApp.fetch("https://www.google.com/recaptcha/api/siteverify", {
139
+ method: "post",
140
+ payload: { secret: secret, response: token }
141
+ });
116
142
 
143
+ const result = JSON.parse(response.getContentText());
144
+ if (!result.success) throw new Error("[Script]: reCAPTCHA failed.");
117
145
  return true;
118
146
  }
119
147
 
120
-
148
+ /**
149
+ * Main Function
150
+ */
121
151
  function doPost(e) {
122
152
  try {
123
- const data = JSON.parse(e.postData.contents);
153
+ const contents = e.postData ? e.postData.contents : null;
154
+ if (!contents) throw new Error("No data received");
155
+
156
+ const data = JSON.parse(contents);
124
157
 
125
- // 1. Validate the reCAPTCHA token first!
126
- validateRecaptcha(data['g-recaptcha-response']);
158
+ // Identifies the token coming from the front-end (accepts both names)
159
+ const token = data['g-recaptcha-response'] || data['turnstileToken'] || data['cf-turnstile-response'];
127
160
 
128
- // 2. If validation passed, continue with the rest of the code
129
- const { name, email, message } = data;
161
+ let antiRobot = "Unknown";
130
162
 
131
- if (!name || !email || !message) {
132
- throw new Error("Missing form data.");
163
+ // Hybrid Logic: Validates according to the key present in the Script.
164
+ if (props.getProperty('RECAPTCHA_SECRET_KEY')) {
165
+ antiRobot = "Google reCAPTCHA";
166
+ validateRecaptcha(token);
167
+ } else if (props.getProperty('TURNSTILE_SECRET_KEY')) {
168
+ validateTurnstile(token);
169
+ antiRobot = "Cloudflare Turnstile";
170
+ } else {
171
+ throw new Error("No Captcha Secret Key configured in Script Properties.");
133
172
  }
134
173
 
135
- const subject = "New website message " + name;
136
- const htmlBody = `
137
- <p>You have received a new message from your website.:</p><hr>
138
- <p><b>Name:</b> ${name}</p>
139
- <p><b>Email:</b> <a href="mailto:${email}">${email}</a></p>
140
- <p><b>Message:</b></p>
141
- <p style="white-space: pre-wrap;">${message}</p><hr>
142
- `;
143
-
144
- MailApp.sendEmail({
145
- to: TO_ADDRESS,
146
- subject: subject,
147
- htmlBody: htmlBody,
148
- replyTo: email
174
+ const name = sanitize(data.name, true);
175
+ const email = sanitize(data.email, true);
176
+ const message = data.message || "";
177
+ const pageUrl = data.page_url ? sanitize(data.page_url, true) : SITE;
178
+ const locationInfo = data.location_info || "Unknown";
179
+
180
+ if (!name || !email || !message) throw new Error("[Script]: Missing form data.");
181
+ if (message.length < MINIMUM_MESSAGE) throw new Error("[Script]: Message too short.");
182
+
183
+ // Anti-Spam Rate Limit
184
+ const lastSent = props.getProperty(`last_sent_${email}`);
185
+ const now = Date.now();
186
+ if (lastSent && (now - lastSent < 60000)) throw new Error("[Script]: Please wait 1 minute.");
187
+ props.setProperty(`last_sent_${email}`, now.toString());
188
+
189
+ // --- MESSAGE PROCESSING ---
190
+ let escapedContent = sanitize(message);
191
+ let codeBlocks = [];
192
+ let placeholder = "___CODE_BLOCK_";
193
+
194
+ let cleanMessage = escapedContent.replace(/```([\s\S]+?)```/g, function(match, code) {
195
+ const id = codeBlocks.length;
196
+
197
+ const codeHtml = `<pre style="background: #f6f8fa; color: #24292e; padding: 15px; border-radius: 6px; overflow-x: auto; font-family: 'Fira Code','Consolas',SFMono-Regular, SF Mono, Menlo, monospace; font-size: 15px; line-height: 1.85; border: 1px solid #d1d5da; border-bottom: 3px solid #a1a2a7; border-right: 1px solid #a1a2a7; border-top: 1px solid #a1a2a7; border-left: 1px solid #a1a2a7;">${code.trim()}</pre>`;
198
+
199
+ codeBlocks.push(codeHtml);
200
+ return placeholder + id + "___";
201
+ });
202
+
203
+ cleanMessage = cleanMessage.replace(/\n/g, '<br>');
204
+ codeBlocks.forEach((block, index) => {
205
+ cleanMessage = cleanMessage.replace(placeholder + index + "___", block);
149
206
  });
150
207
 
151
- return ContentService
152
- .createTextOutput(JSON.stringify({ 'result': 'success', 'message': 'Message sent!' }))
153
- .setMimeType(ContentService.MimeType.JSON);
208
+ const subject = `Message from: ${name} (${SITE})`;
209
+ const htmlBody = `
210
+ <div style="font-family: sans-serif; color: #333; max-width: 820px; border: 1px solid #eee; padding: 20px;">
211
+ ${LOGO_URL ? `
212
+ <div style="text-align: center; margin-bottom: 20px;">
213
+ <img src="${LOGO_URL}" alt="Logo" style="width: 150px; padding: 10px; border-radius: 50%; border: 1px solid #ddd;">
214
+ </div>
215
+ ` : ''}
216
+
217
+ <h2 style="color: #333; border-bottom: 2px solid #333; padding-bottom: 10px;"></h2>
218
+
219
+ <div style="margin-top: 20px;">
220
+ <p><b>Name:</b> ${name}</p>
221
+ <p><b>From:</b> <a href="mailto:${email}">${email}</a></p>
222
+ <p><b>Message:</b></p>
223
+ <div style="background: #fff; font-size: 15px; color: black; border: 1px solid transparent; padding: 15px; border-radius: 5px;">
224
+ ${cleanMessage}
225
+ </div>
226
+ </div>
227
+
228
+ <footer style="margin-top: 30px; font-size: 12px; color: #888; border-top: 1px solid #eee; padding-top: 10px;">
229
+ This is an automated email sent by your form ${pageUrl} using ${antiRobot}/Google Script.<br>
230
+ &copy; ${new Date().getFullYear()} ${SITE.replace('https://', '')} - ${new Date().toLocaleString('pt-BR')} - <b>${locationInfo}</b>
231
+ </footer>
232
+ </div>`;
233
+
234
+ MailApp.sendEmail({ to: TO_ADDRESS, subject: subject, htmlBody: htmlBody, replyTo: email });
235
+
236
+ return ContentService.createTextOutput(JSON.stringify({ 'result': 'success' })).setMimeType(ContentService.MimeType.JSON);
154
237
 
155
238
  } catch (err) {
156
- Logger.log(err.toString());
157
- return ContentService
158
- .createTextOutput(JSON.stringify({ 'result': 'error', 'message': err.toString() }))
159
- .setMimeType(ContentService.MimeType.JSON);
239
+ return ContentService.createTextOutput(JSON.stringify({ 'result': 'error', 'debug': err.message })).setMimeType(ContentService.MimeType.JSON);
160
240
  }
161
241
  }
162
242
  {% endhighlight %}
@@ -22,7 +22,13 @@
22
22
  <!-- head -->
23
23
 
24
24
  <!-- body -->
25
- <body data-layout="{{ page.layout | default: '' }}" data-terminal-enabled="{{ home_.terminal.enable | default: false }}" style="font-family: {{ default_.font.style }} !important; font-size: {{ default_.font.size }} !important; letter-spacing: {{ default_.font.spacing }} !important;">
25
+ <body data-layout="{{ page.layout | default: '' }}"
26
+ data-terminal-enabled="{{ home_.terminal.enable | default: false }}"
27
+ style="
28
+ font-family: {{ default_.font.style }} !important;
29
+ font-size: {{ default_.font.size }} !important;
30
+ letter-spacing: {{ default_.font.spacing }} !important;
31
+ user-select: {{ default_.user_select | default: 'none' }};">
26
32
 
27
33
  <!-- container -->
28
34
  <div class="container-fluid">
@@ -38,14 +44,15 @@
38
44
 
39
45
  <!-- scripts -->
40
46
  <script src="{{ '/assets/vendor/bootstrap/js/bootstrap.bundle.min.js' | relative_url }}"></script>
41
- <script src="{{ '/assets/js/fallback/default.js' | relative_url }}"></script>
47
+ <script src="{{ '/assets/js/default.js' | relative_url }}"></script>
42
48
  {%- if page.url contains '/blog/' -%}
43
49
  <script src="{{ '/assets/vendor/simple-jekyll-search.min.js' | relative_url }}"></script>
44
50
  {%- endif -%}
45
- <script src="{{ '/assets/js/fallback/blog.js' | relative_url }}"></script>
46
- <script src="{{ '/assets/js/fallback/home.js' | relative_url }}"></script>
47
- <script src="{{ '/assets/js/fallback/page.js' | relative_url }}"></script>
48
- <script src="{{ '/assets/js/fallback/contact.js' | relative_url }}"></script>
51
+ <script src="{{ '/assets/js/blog.js' | relative_url }}"></script>
52
+ <script src="{{ '/assets/js/home.js' | relative_url }}"></script>
53
+ <script src="{{ '/assets/js/page.js' | relative_url }}"></script>
54
+ <script src="{{ '/assets/js/contact.js' | relative_url }}"></script>
55
+ <script src="{{ '/assets/js/donation.js' | relative_url }}"></script>
49
56
  <!-- scripts -->
50
57
  </body>
51
58
  <!-- body -->
@@ -0,0 +1,113 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+
5
+ {%- include layout/data.liquid -%}
6
+
7
+ <div id="donation" class="donation py-5{% if default_.background_focus %} background_focus{% endif %}{% if default_.rounding %} rounding{% endif %}">
8
+
9
+ <div class="container">
10
+
11
+ {% if donation_.title or donation_.description %}
12
+ <div class="row justify-content-center mb-5">
13
+ <div class="col-lg-8 text-center">
14
+ <h1 class="display-5 fw-bold mb-3 donation__title">{{ donation_.title }}</h1>
15
+ <p class="mb-0 donation__description">
16
+ {{ donation_.description }}
17
+ </p>
18
+ </div>
19
+ </div>
20
+ {% endif %}
21
+
22
+
23
+ <div class="row mt-5">
24
+ <div class="col donation__content">
25
+ {{ content }}
26
+ </div>
27
+ </div>
28
+
29
+ <div class="donation-list d-flex flex-column gap-4">
30
+
31
+ {%- for service in donation_.services -%}
32
+ <div class="donation__box">
33
+ <div class="row align-items-center g-4">
34
+
35
+ <!-- LOGO -->
36
+ <div class="col-md-2 col-12 text-center">
37
+ <img src="{{ service.logo }}"
38
+ class="img-fluid donation__logo"
39
+ alt="{{ service.name }}">
40
+ </div>
41
+
42
+ <!-- INFO -->
43
+ <div class="col-md-7 col-12">
44
+
45
+ <h1 class="mb-2 text-capitalize donation__service__name">{{ service.name }}</h1>
46
+ <p class="mb-4 donation__service__description">
47
+ {{ service.description | markdownify }}
48
+ </p>
49
+
50
+ {%- unless service.name contains "bitcoin" or service.name == "pix" -%}
51
+ <div class="row">
52
+ <div class="col">
53
+ <a class="fw-bold donation__btn" href="{{ service.address }}"
54
+ target="_blank"
55
+ rel="noopener">
56
+ Open
57
+ </a>
58
+ </div>
59
+ </div>
60
+ {%- endunless -%}
61
+
62
+ </div>
63
+
64
+ <!-- QR CODE -->
65
+ <div class="col-md-3 col-12 text-center">
66
+ {%- if service.qrcode -%}
67
+ <img src="{{ service.qrcode }}"
68
+ class="img-fluid clickable-qr donation__qr"
69
+ alt="QR {{ service.name }}"
70
+ data-bs-toggle="modal"
71
+ data-bs-target="#qrModal"
72
+ data-qr="{{ service.qrcode }}">
73
+ {%- endif -%}
74
+ </div>
75
+
76
+ </div>
77
+
78
+ <div class="row">
79
+ <div class="col-12 mt-4 donation__offset_input">
80
+ {% if service.name == "pix" or service.name contains "bitcoin" %}
81
+ <div class="row">
82
+ <div class="input-group">
83
+ {% if service.name contains "pix" %}
84
+ <span class="input-group-text donation__input__label">Key</span>
85
+ {% else %}
86
+ <span class="input-group-text donation__input__label">Address</span>
87
+ {% endif %}
88
+ <input type="text" class="form-control donation__address"
89
+ value="{{ service.address }}" readonly>
90
+ <button class="fw-bold donation__btn donation__btn--copy" data-copy="{{ service.address }}">
91
+ {{ donation_.copy_button.text }}
92
+ </button>
93
+ </div>
94
+ </div>
95
+ {% endif %}
96
+ </div>
97
+ </div>
98
+ </div>
99
+ {%- endfor -%}
100
+
101
+ </div>
102
+
103
+ </div>
104
+ </div>
105
+
106
+ <!-- QR Modal -->
107
+ <div class="modal fade" id="qrModal" tabindex="-1">
108
+ <div class="modal-dialog modal-dialog-centered modal-lg">
109
+ <div class="modal-content bg-transparent border-0 text-center">
110
+ <img id="qrModalImg" class="img-fluid rounded shadow">
111
+ </div>
112
+ </div>
113
+ </div>
@@ -6,9 +6,6 @@ body {
6
6
  background-color: var(--bg-color);
7
7
  color: var(--text-color);
8
8
 
9
- // impede selecionar texto no site
10
- user-select: none;
11
-
12
9
  code,
13
10
  pre,
14
11
  .highlight,
@@ -92,8 +89,8 @@ body[data-layout="mobile"] .home-simple {
92
89
  }
93
90
 
94
91
  a[target="_blank"]::after {
95
- content: "🢅";
96
- font-size: .9em;
97
- margin-left: 0.25em;
98
- vertical-align: middle;
92
+ content: "";
93
+ // font-size: .9em;
94
+ // margin-left: 0.25em;
95
+ // vertical-align: middle;
99
96
  }
@@ -306,14 +306,13 @@
306
306
  position: fixed;
307
307
  top: 0;
308
308
  left: 0;
309
- overflow: hidden;
309
+ overflow-y: auto;
310
310
  display: flex;
311
311
  flex-direction: column;
312
312
  z-index: 9999;
313
313
  min-width: 480px !important;
314
314
  max-width: 480px !important;
315
315
  width: auto !important;
316
- margin: 0 auto;
317
316
  max-height: 100vh;
318
317
  }
319
318
 
@@ -0,0 +1,93 @@
1
+ @use "../components/index";
2
+
3
+ .donation {
4
+ @extend %markdown;
5
+ margin-bottom: var(--margin-bottom);
6
+
7
+ &__offset_input {
8
+ @media (min-width: 768px) {
9
+ padding-left: 18% !important;
10
+ padding-right: 6.5% !important;
11
+ }
12
+ }
13
+
14
+ &__btn {
15
+ @extend %button;
16
+
17
+ &--copy {
18
+ min-width: 100px !important;
19
+ text-align: center !important;
20
+ border: 1px solid var(--background-focus-border-color) !important;
21
+ }
22
+ }
23
+
24
+ &__box {
25
+ padding: 1rem;
26
+ border: 2px dotted var(--card-border-color);
27
+ background-color: var(--card-bg-color);
28
+ transition: all 0.2s ease;
29
+
30
+ &:hover {
31
+ // transform: translateY(-3px);
32
+ border: 2px dotted var(--background-focus-border-color) !important;
33
+ box-shadow: 0 1rem 1.2rem var(--card-box-shadow-color-hover);
34
+ }
35
+ }
36
+
37
+ &__logo {
38
+ max-height: 64px;
39
+ opacity: 0.65;
40
+ }
41
+
42
+ &__input__label {
43
+ font-family: monospace;
44
+ font-size: 0.9rem;
45
+ color: var(--text-secondary);
46
+ background-color: var(--background-focus-color);
47
+ padding: 0.25rem 0.5rem;
48
+ border-radius: 0.25rem;
49
+ border: 1px solid var(--background-focus-border-color);
50
+ width: fit-content;
51
+ }
52
+
53
+ &__qr {
54
+ max-height: 90px;
55
+ cursor: pointer;
56
+ transition: transform .2s ease;
57
+
58
+ &:hover {
59
+ transform: scale(1.08);
60
+ }
61
+ }
62
+
63
+ &__service__description {
64
+ font-size: 0.95rem;
65
+ color: var(--text-secondary);
66
+ }
67
+
68
+ &__service__name {
69
+ margin-top: 0 !important;
70
+ text-transform: capitalize;
71
+ font-size: 1.3rem !important;
72
+ }
73
+
74
+ &__address {
75
+ font-family: monospace;
76
+ font-size: 0.9rem;
77
+ color: var(--text-secondary);
78
+ background-color: var(--background-focus-color);
79
+ padding: 0.25rem 0.5rem;
80
+ border-radius: 0.25rem;
81
+ border: 1px solid var(--background-focus-border-color);
82
+ width: fit-content;
83
+
84
+ &:focus {
85
+ color: inherit !important;
86
+ outline: none !important;
87
+ box-shadow: none !important;
88
+ background-color: var(--background-focus-color) !important;
89
+ border: 1px solid var(--background-focus-border-color) !important;
90
+ }
91
+ }
92
+
93
+ }
@@ -12,3 +12,4 @@
12
12
  @use "pixels";
13
13
  @use "pixel";
14
14
  @use "pub";
15
+ @use "donation";