Skip to content

Cleanup#1

Merged
kwsantiago merged 8 commits intomainfrom
sandbox
Dec 26, 2025
Merged

Cleanup#1
kwsantiago merged 8 commits intomainfrom
sandbox

Conversation

@wksantiago
Copy link
Contributor

@wksantiago wksantiago commented Dec 26, 2025

Summary by CodeRabbit

  • Refactor
    • Centralized contact form settings with configurable endpoint, honeypot and rate‑limit protections; simplified render flow and removed the typing animation.
  • New Features
    • Consolidated products and resources grids with click-to-open product modals and async resource loading; expanded product and team listings.
  • Bug Fixes / Security
    • Strengthened metadata and CSP/referrer guidance, added script integrity attributes and hidden anti-spam field.
  • Style
    • Streamlined green theme rules, tightened focus styles, removed hero animation, and refined mobile/responsive layout.
  • Chores
    • Added LICENSE and SECURITY policy, removed one LNURL payRequest entry, updated manifest icons, robots and sitemap dates.

✏️ Tip: You can customize this high-level summary in your review settings.

@wksantiago wksantiago linked an issue Dec 26, 2025 that may be closed by this pull request
@wksantiago wksantiago self-assigned this Dec 26, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 26, 2025

Walkthrough

Adds client-side form rate-limiting and honeypot with CONFIG-driven endpoint; consolidates product/opensource rendering and async resources fetching; updates site metadata, integrity attributes, mobile/responsive and theme CSS; removes an LNURLP payRequest; adds LICENSE and SECURITY.md; updates manifest icons and robots/_headers notes.

Changes

Cohort / File(s) Summary
Contact form & client logic
assets/js/main.js
Adds frozen CONFIG (FORM_ENDPOINT, RATE_LIMIT_MS, MAX_SUBMISSIONS), formState, honeypot check, rate-limit guard (checkRateLimit), swaps hard-coded Formspree URL for CONFIG.FORM_ENDPOINT, adds 429/retry-after handling and user messaging, removes typing-effect init, introduces renderProducts and async renderResources, wires product/resource click handlers.
Site HTML / form markup / meta
index.html
Replaces enterprise copy with self-sovereign Bitcoin/Nostr branding, updates title/description/OG/Twitter/JSON-LD, adds CSP/X-UA-Compatible/referrer meta tags, adds honeypot _gotcha input, adds crossorigin on font links and integrity+crossorigin on Bootstrap, adds Products nav item, simplifies hero and modals, updates footer text and social links.
Open source UI & CSS
assets/js/main.js, assets/css/plain-html.css
Consolidates techModules/openSourceProjects into a products/opensource model, adds .opensource-* selectors and responsive grid, removes .skip-nav:focus, relaxes .social-icon !important.
Theme & focus adjustments
assets/css/colors/green.css, assets/css/essential-only.css
Consolidates green-theme rules; removes global *:focus and skip-link focus styles, scopes focus to button:focus, a:focus; removes hero particle @keyframes float.
Mobile responsive fixes
assets/css/mobile-responsive-fixes.css
Large mobile refinements: revamped full-screen menu close button, hero/typing layout adjustments and .custom-typing-wrapper, responsive font/padding tweaks, centered footer social icons, team-bio overlay mobile changes and small-screen typography tweaks.
Resources fetching & rendering
assets/js/main.js
Adds async renderResources to fetch RSS/JSON, render article grid with click handlers and failure fallback; replaces static resources array.
Icons & manifest
manifest.json
Updates icons[].src for 192x192 and 512x512 to assets/images/LOGO_Green_new_transparent.png.
LNURLP payload removal
.well-known/lnurlp/william
Removes the LNURL PayRequest JSON payload (callback, metadata, min/max sendable, payerData, nostr fields).
License & security docs
LICENSE, SECURITY.md
Adds MIT LICENSE file and SECURITY.md with vulnerability reporting instructions.
Robots & header notes
robots.txt, _headers
Removes Disallow: /static/js/ and Disallow: /static/css/ from robots.txt; adds a commented block enumerating recommended HTTP security headers in _headers.
Sitemap & minor metadata
sitemap.xml, index.html
Updates <lastmod> dates in sitemap.xml; additional inline copy/heading adjustments and ID renames (e.g., technologyproducts).

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A little rabbit found CONFIG at play,
I slowed the hops and hid the bad buffet.
Products lined up, resources fetch with care,
A payload leapt away — now breezy air.
I nibbled greens; the site breathes fresh and fair 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Cleanup' is vague and generic, lacking specificity about the actual changes made in the pull request. Use a more descriptive title that highlights the main changes, such as 'Rebrand to self-sovereign Bitcoin infrastructure' or 'Redesign homepage and update product structure' to clearly communicate the primary intent.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sandbox

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kwsantiago kwsantiago changed the title Extract form endpoint to config Cleanup Dec 26, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
SECURITY.md (1)

5-5: Consider formatting the email as a markdown link.

The bare email URL triggers a linting warning. For consistency with markdown best practices, consider formatting it as a link:

-If you discover a security vulnerability, please report it to **security@privkey.io**.
+If you discover a security vulnerability, please report it to [security@privkey.io](mailto:security@privkey.io).
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef8b2d5 and a2a7792.

📒 Files selected for processing (7)
  • LICENSE
  • SECURITY.md
  • assets/css/colors/green.css
  • assets/css/essential-only.css
  • assets/css/mobile-responsive-fixes.css
  • assets/css/plain-html.css
  • index.html
✅ Files skipped from review due to trivial changes (2)
  • LICENSE
  • assets/css/essential-only.css
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
SECURITY.md

5-5: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (8)
assets/css/colors/green.css (1)

25-33: LGTM!

The consolidation of button state rules into a single cohesive block is clean and maintains the correct color theming for all button states.

assets/css/plain-html.css (2)

18-19: LGTM!

The hover color for mail/phone links is consistent with the theme's primary green color (#27ae60).


15-15: Sizing change is safe; CSS specificity maintains consistency across contexts.

The base rule's 44px sizing won't affect the footer because .social-icons-list .social-icon (32px, mobile-responsive-fixes.css) has higher specificity and will override it regardless of !important. Other contexts (team box uses font-size; contact section has its own styling) don't conflict with the width/height properties. Removing !important from width and height is correct practice here.

assets/css/mobile-responsive-fixes.css (4)

353-364: LGTM!

The explicit sizing and flex centering properties stabilize the hero text container layout on mobile, preventing layout shifts. The transition: none is particularly good for avoiding visual glitches during text updates.


374-377: LGTM!

The custom typing wrapper with minimum height prevents layout shifts during the typing animation.


638-675: LGTM!

The new .social-icons-list styling provides proper centering and sizing for footer social icons. The 32px sizing with 20px SVG/icon sizing is appropriate for the footer context and integrates well with the HTML changes in index.html.


707-709: LGTM!

The mobile team bio overlay padding and centering adjustments improve the mobile layout presentation.

index.html (1)

384-390: LGTM!

The transition from inline styling to the class-based .social-icons-list approach is a clean improvement aligned with the new CSS definitions in mobile-responsive-fixes.css. The retained list-inline-item classes properly integrate with the parent styling through explicit margin override (margin: 0) that harmonizes with the flexbox gap property, ensuring proper spacing while avoiding Bootstrap conflicts.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
assets/js/main.js (3)

154-154: Consider using event listeners instead of inline onclick.

The window.open call with noopener,noreferrer is secure, but inline event handlers are generally less maintainable than adding event listeners after rendering. This would also align with the pattern used elsewhere (e.g., Line 112 for service buttons).

🔎 Alternative approach using event listeners
-                <div class="project-card p-4 text-center rounded shadow-sm h-100" onclick="window.open('${p.url}','_blank','noopener,noreferrer')">
+                <div class="project-card p-4 text-center rounded shadow-sm h-100" data-url="${p.url}">

Then add after the innerHTML assignment:

document.getElementById('opensource-projects').querySelectorAll('.project-card').forEach(card => {
    card.addEventListener('click', () => window.open(card.dataset.url, '_blank', 'noopener,noreferrer'));
});

210-210: Consider using event listeners instead of inline onclick.

Same pattern as Line 154. While the security flags are correct, using event listeners would be more maintainable and consistent with other click handlers in the codebase.

🔎 Alternative approach using event listeners
-                <div class="card resource-card h-100 border-0 shadow-sm" onclick="window.open('${r.link}','_blank','noopener,noreferrer')">
+                <div class="card resource-card h-100 border-0 shadow-sm" data-link="${r.link}">

Then add after the innerHTML assignment:

document.getElementById('resources-grid').querySelectorAll('.resource-card').forEach(card => {
    card.addEventListener('click', () => window.open(card.dataset.link, '_blank', 'noopener,noreferrer'));
});

329-331: Consider providing more specific rate limit feedback.

The honeypot check is well-implemented. However, the rate limit error message at Line 331 could be more specific by telling users how long they need to wait before submitting again.

🔎 Proposed improvement
-            if (!checkRateLimit()) { status.innerHTML = '<div class="alert alert-warning"><i class="mdi mdi-clock me-2"></i>Too many requests. Please wait a moment.</div>'; status.style.display = 'block'; return; }
+            if (!checkRateLimit()) { 
+                const waitSeconds = Math.ceil(CONFIG.RATE_LIMIT_MS / 1000);
+                status.innerHTML = `<div class="alert alert-warning"><i class="mdi mdi-clock me-2"></i>Too many requests. Please wait ${waitSeconds} seconds before trying again.</div>`; 
+                status.style.display = 'block'; 
+                return; 
+            }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2a7792 and 4d1cd30.

📒 Files selected for processing (4)
  • _headers
  • assets/js/main.js
  • index.html
  • robots.txt
💤 Files with no reviewable changes (1)
  • robots.txt
🚧 Files skipped from review as they are similar to previous changes (1)
  • index.html
🔇 Additional comments (3)
_headers (1)

1-5: No action required — security headers are correctly configured.

The _headers file is properly formatted for Netlify deployment. The /* on line 1 is a path glob pattern (not a comment), and the indented headers below it will be applied to all paths when deployed. This is the standard Netlify format for setting HTTP headers. The security headers (HSTS, X-Content-Type-Options, X-Frame-Options, Permissions-Policy) are syntactically valid and will be deployed correctly.

Likely an incorrect or invalid review comment.

assets/js/main.js (2)

4-8: LGTM! Good use of Object.freeze for configuration.

The frozen CONFIG object is a best practice that prevents accidental mutations. The rate limit settings (60 seconds, max 3 submissions) are reasonable for a contact form.


319-325: Verify server-side rate limiting is in place.

The client-side rate limiting provides good UX feedback but can be easily bypassed (e.g., by refreshing the page, clearing browser state, or disabling JavaScript). Ensure that FormSpree or another server-side mechanism enforces actual rate limiting to prevent abuse.

Note: Client-side rate limiting is appropriate for improving user experience, but should not be relied upon as a security control.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
manifest.json (1)

11-22: Provide appropriately-sized images for each icon declaration.

Both the 192x192 and 512x512 icons reference the same source file (assets/images/LOGO_Green_new_transparent.png), which is 8206x3909 pixels—a landscape image. When rendered in square icon contexts, this will result in poor rendering due to aspect ratio mismatch. Create separate, square-cropped icons at the declared sizes (192x192 and 512x512) for optimal PWA experience.

🧹 Nitpick comments (1)
index.html (1)

6-8: Good security additions, but CSP could be strengthened.

The CSP includes 'unsafe-inline' for both scripts and styles, which significantly reduces XSS protection. Consider:

  1. Adding object-src 'none' to prevent plugin-based attacks
  2. Adding frame-ancestors 'self' to prevent clickjacking (or use X-Frame-Options header)
  3. Moving CSP to HTTP headers (via _headers file) for report-uri support and broader coverage

Note: Meta-tag CSP doesn't support frame-ancestors or report-uri directives.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d1cd30 and abd3433.

📒 Files selected for processing (6)
  • _headers
  • assets/css/plain-html.css
  • assets/js/main.js
  • index.html
  • manifest.json
  • robots.txt
💤 Files with no reviewable changes (1)
  • robots.txt
✅ Files skipped from review due to trivial changes (1)
  • _headers
🚧 Files skipped from review as they are similar to previous changes (1)
  • assets/js/main.js
🔇 Additional comments (9)
assets/css/plain-html.css (2)

15-15: Good improvement: Removed unnecessary !important from dimensions.

Removing !important from width and height reduces specificity conflicts and improves maintainability. Ensure no other styles were relying on this specificity to override these values.


23-30: Well-structured opensource component styles.

The new grid layout and hover effects are consistent with the existing design. Consider verifying that .opensource-desc with color:rgba(255,255,255,.5) meets WCAG contrast requirements against the dark background.

index.html (7)

52-52: LGTM!

Adding crossorigin="anonymous" is correct for cross-origin font requests and aligns with the CSP configuration.


160-160: Content updates look good.

The streamlined messaging across services, open source, and contact sections is clearer and more concise.


305-305: Good anti-spam implementation.

The honeypot field is correctly configured with aria-hidden="true", tabindex="-1", and off-screen positioning. This provides bot protection without impacting accessibility or requiring user-facing CAPTCHA.


299-301: LGTM!

Clean simplification with proper rel="noopener noreferrer" for the external scheduling link.


390-390: Good accessibility fix.

Removing static aria-hidden is correct—Bootstrap dynamically manages this attribute when toggling modal visibility.


366-372: LGTM!

The class update aligns with the CSS refactoring and maintains proper semantic structure for the social links.


402-402: Good security practice with SRI properly implemented.

The integrity hash correctly matches the official Bootstrap 5.3.0 release, and the crossorigin="anonymous" attribute is properly configured for SRI validation. The CDN script is securely configured.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
assets/js/main.js (1)

10-10: Remove unused blocked property.

The formState.blocked property is never read. It's set to true at Line 307 but has no effect on the application logic.

🔎 Proposed fix
-const formState = { submissions: [], blocked: false };
+const formState = { submissions: [] };

Also remove the assignment at Line 307:

-if (formState.submissions.length >= CONFIG.MAX_SUBMISSIONS) { formState.blocked = true; return false; }
+if (formState.submissions.length >= CONFIG.MAX_SUBMISSIONS) { return false; }

This issue was previously flagged in past reviews.

🧹 Nitpick comments (2)
assets/js/main.js (1)

4-8: Consider externalizing the form endpoint.

The CONFIG object correctly uses Object.freeze for immutability. However, the hardcoded FormSpree endpoint could be moved to an environment variable or external configuration for easier management across environments.

💡 Optional: Externalize configuration
 const CONFIG = Object.freeze({
-    FORM_ENDPOINT: 'https://formspree.io/f/mwkwqwkl',
+    FORM_ENDPOINT: window.ENV?.FORM_ENDPOINT || 'https://formspree.io/f/mwkwqwkl',
     RATE_LIMIT_MS: 60000,
     MAX_SUBMISSIONS: 3
 });

This allows overriding via a separate config file while maintaining the fallback.

index.html (1)

7-7: CSP uses 'unsafe-inline' for styles.

The Content Security Policy includes style-src 'self' 'unsafe-inline', which weakens protection against CSS injection attacks. While this may be necessary for Bootstrap and inline styles, consider moving to a nonce-based or hash-based approach if feasible.

💡 Recommended: Evaluate nonce-based CSP

Investigate whether inline styles can be moved to external stylesheets or implement a nonce-based CSP:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' 'nonce-RANDOM_NONCE' https://fonts.googleapis.com; ...">

This would require adding nonce="RANDOM_NONCE" to inline <style> tags.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between abd3433 and 5ae12bc.

📒 Files selected for processing (5)
  • _headers
  • assets/css/mobile-responsive-fixes.css
  • assets/js/main.js
  • index.html
  • sitemap.xml
✅ Files skipped from review due to trivial changes (1)
  • sitemap.xml
🔇 Additional comments (13)
assets/js/main.js (4)

12-91: LGTM! Data structures updated correctly.

The updated data structures reflect the new branding focus on self-sovereign Bitcoin and Nostr infrastructure. The consolidation of products and expansion of contribution categories improves content organization.


147-161: LGTM! Simplified rendering logic.

The consolidated renderProducts function effectively replaces the previous separate rendering paths. Security attributes (rel="noopener noreferrer") are correctly applied to external links.


207-220: LGTM! Resource cards now interactive.

The addition of click handlers to resource cards improves UX. The implementation correctly uses window.open with security parameters ('noopener,noreferrer').


312-330: LGTM! Form submission with anti-spam measures.

The honeypot (Line 314-315) and rate limiting (Line 316) provide basic spam protection. The implementation correctly:

  • Silently rejects honeypot-trapped submissions
  • Provides user feedback for rate limit violations
  • Uses the CONFIG endpoint for maintainability
assets/css/mobile-responsive-fixes.css (2)

657-694: LGTM! Social icons styling aligns with markup changes.

The new .social-icons-list styles correctly support the list-based social icon rendering introduced in assets/js/main.js. The flexbox layout and consistent sizing improve visual presentation.


723-793: LGTM! Responsive team bio presentation.

The mobile/desktop toggle provides an optimal reading experience on each device type. Mobile bullet points are more scannable, while desktop users get the full biography.

index.html (6)

6-11: LGTM! Security-conscious meta tags.

The addition of X-UA-Compatible, CSP, and referrer policy headers improves security posture. The strict-origin-when-cross-origin referrer policy balances privacy with analytics needs.


52-52: LGTM! Correct use of crossorigin attribute.

The crossorigin="anonymous" attribute is correctly applied to the Google Fonts link, enabling CORS and improving resource loading security.


107-107: LGTM! Hero height reduction.

Reducing the hero from 90vh to 70vh allows more content above the fold, potentially improving engagement. This aligns with modern web design practices.


275-275: LGTM! Proper honeypot implementation.

The honeypot field is correctly configured with:

  • aria-hidden="true" to hide from screen readers
  • position:absolute; left:-9999px to hide visually
  • tabindex="-1" to prevent keyboard focus
  • autocomplete="off" to prevent browser autofill

This provides effective bot protection without impacting accessibility.


360-361: LGTM! Modal structure updated for Bootstrap 5.

The removal of the aria-hidden attribute is correct—Bootstrap 5 manages this dynamically. The addition of modal-dialog-centered improves modal presentation by centering it vertically.


372-372: LGTM! Bootstrap loaded with integrity check.

The Bootstrap script correctly includes the integrity attribute for Subresource Integrity (SRI) protection and crossorigin="anonymous" for CORS. This prevents execution if the CDN-served file is tampered with. The integrity hash is the official Bootstrap 5.3.0 SRI.

_headers (1)

1-8: The _headers file format is correct and no closing tag is needed.

The /* on line 1 is a valid path pattern (wildcard matching all paths), not a comment opener. According to Netlify and Cloudflare Pages specifications, the _headers file uses plain-text block syntax where a path pattern at column 0 is followed by indented header directives—there is no C-style /* */ comment syntax. The current file with /* as the path pattern followed by properly indented headers (2-space indentation) is valid configuration and requires no modifications.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
assets/js/main.js (1)

4-10: Unused formState.blocked property is dead code.

The formState.blocked property (line 10) is set to true in checkRateLimit() (line 334) but is never read or used elsewhere. This should be removed.

🔎 Proposed fix
-    const formState = { submissions: [], blocked: false, rateLimited: false, retryAfter: null };
+    const formState = { submissions: [], rateLimited: false, retryAfter: null };

Also at line 334:

-            if (formState.submissions.length >= CONFIG.MAX_SUBMISSIONS) { formState.blocked = true; return false; }
+            if (formState.submissions.length >= CONFIG.MAX_SUBMISSIONS) { return false; }
assets/css/mobile-responsive-fixes.css (2)

384-395: Fixed height with overflow:hidden may still truncate content.

Lines 385-391 set height: 100px with overflow: hidden on .hero-text-container. This could still truncate longer text on mobile devices. Consider using only min-height without a fixed height.

🔎 Suggested fix
   .hero-text-container {
     padding: 12px 18px !important;
     margin: 0 1rem !important;
     max-width: calc(100% - 2rem) !important;
     min-height: 100px !important;
-    height: 100px !important;
+    height: auto !important;
     line-height: 1.6 !important;

405-423: Orphaned CSS rules for typing-related selectors remain.

The CSS defines styles for .custom-typing-wrapper, .typing-container, .static-text, and .typed-text, but the typing effect was removed from the JavaScript. These appear to be dead CSS rules that should be removed.

#!/bin/bash
# Verify if typing-related classes are used anywhere in HTML or JS
echo "=== Searching for typing-related classes in HTML ==="
rg -n "typing-container|custom-typing-wrapper|static-text|typed-text" --type=html

echo -e "\n=== Searching for typing-related classes in JS ==="
rg -n "typing-container|custom-typing-wrapper|static-text|typed-text" --type=js
🧹 Nitpick comments (1)
index.html (1)

79-79: Minor naming inconsistency between section ID and content.

The navigation links to #technology but the section content is titled "Our Products". Consider renaming the section ID to #products for consistency, or this is fine if #technology is intentional for SEO/legacy reasons.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ae12bc and 07c79c4.

📒 Files selected for processing (3)
  • assets/css/mobile-responsive-fixes.css
  • assets/js/main.js
  • index.html
🔇 Additional comments (11)
assets/js/main.js (4)

203-215: Good XSS protection with escapeHtml and sanitizeUrl.

These utility functions properly sanitize user-controlled content from the RSS feed before rendering. The protocol whitelist in sanitizeUrl prevents javascript: URL injection.


217-247: Well-structured async resource loading with proper security.

The implementation correctly:

  • Shows loading state and graceful fallback on error
  • Sanitizes all user-controlled content (title, description, URL) before DOM insertion
  • Uses noopener,noreferrer when opening external links

354-366: Good implementation of server-side rate limit (429) handling.

This addresses the previous review feedback. The code now explicitly handles FormSpree's 429 response by reading the Retry-After header, disabling the submit button, and providing clear user feedback with an automatic re-enable timer.


341-343: Honeypot anti-spam implementation is correct.

The check silently rejects submissions where the hidden _gotcha field is filled, which only bots would do. Combined with the client-side rate limiting, this provides reasonable spam protection.

assets/css/mobile-responsive-fixes.css (2)

684-720: Clean social icons list styling.

The new .social-icons-list rules provide consistent horizontal layout and sizing for social icons. The flexbox approach with centered alignment works well for both the team section and footer.


79-91: Good mobile close button accessibility.

The 44x44px touch target meets accessibility guidelines for minimum touch target size. The visual styling with border, background, and transition provides clear affordance.

index.html (5)

6-8: Good security headers in meta tags.

The Content-Security-Policy is well-configured:

  • Restricts script sources to self and CDN
  • Allows necessary font and style sources
  • frame-ancestors 'none' prevents clickjacking
  • upgrade-insecure-requests enforces HTTPS

The connect-src correctly includes all external APIs used (formspree.io, cdn.jsdelivr.net, api.rss2json.com).


273-273: Honeypot field properly implemented.

The hidden honeypot input is correctly positioned off-screen with aria-hidden="true" and tabindex="-1" to prevent legitimate users from filling it while remaining accessible to bots.


358-359: Improved modal accessibility.

Removing the static aria-hidden attribute is correct as Bootstrap dynamically manages this when the modal opens/closes. The modal-dialog-centered class improves the visual presentation.


100-100: External links properly secured.

All target="_blank" links include rel="noopener noreferrer" to prevent the opened page from accessing the opener window and to protect user privacy.


370-370: Correct use of Subresource Integrity (SRI) for Bootstrap.

The integrity hash for Bootstrap 5.3.0 is correct and verified against official sources. The SRI implementation protects against tampering of the CDN-hosted script, and the crossorigin="anonymous" attribute is properly configured to enable the security check.

@kwsantiago kwsantiago merged commit 28b7219 into main Dec 26, 2025
4 checks passed
@kwsantiago kwsantiago deleted the sandbox branch December 26, 2025 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Harden Website for production

2 participants