Rebuild website with Eleventy and interactive hero system#8
Rebuild website with Eleventy and interactive hero system#8JasperNoBoxDev wants to merge 1 commit intomainfrom
Conversation
Migrate from static HTML/CSS/JS to Eleventy 3.x with Nunjucks templates, design tokens, scroll reveals, and 10 interactive hero variants with a live selector. Preserves all content, contact form, and GitHub Pages setup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughMigration from static HTML pages to Eleventy static site generator with Nunjucks templates, introducing reusable layout components ( Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes The review requires assessment of heterogeneous changes across architecture, templating, styling, and interactive logic. main.js presents high-density canvas effect implementations with multiple variants; styles.css introduces a comprehensive design system requiring verification of responsive behavior and token consistency; multiple page templates must be validated for Eleventy structure and metadata; configuration requires confirmation of static passthrough and template engine setup. The scale and diversity of change categories demand substantial reasoning per file type.
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (6)
_includes/nav.njk (1)
5-13: Add ARIA wiring for the mobile menu toggle.
Line 5-13: consider addingaria-controlsandaria-expandedso assistive tech can understand menu state. You’ll also need to togglearia-expandedinopenNav()/closeNav().Suggested markup diff
- <div class="nav-links"> + <div class="nav-links" id="primary-navigation"> @@ - <button class="nav-toggle" aria-label="Toggle menu"> + <button class="nav-toggle" aria-label="Toggle menu" aria-controls="primary-navigation" aria-expanded="false">To complement this, update
main.jsto togglearia-expandedwhen opening/closing.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@_includes/nav.njk` around lines 5 - 13, Add ARIA attributes to the mobile toggle button and update the JS to keep them in sync: give the menu container (the div.nav-links) a stable id and add aria-controls="that-id" and aria-expanded="false" to the button.nav-toggle in the template, then update the openNav() and closeNav() functions in main.js to set button.setAttribute('aria-expanded', 'true') when opening and 'false' when closing (and ensure the initial state is false on page load); also ensure any toggle handler that both opens and closes flips aria-expanded accordingly.about.njk (1)
31-33: Lazy‑load the mission image to reduce CLS and bandwidth.
Line 32: addloading="lazy"anddecoding="async"(and width/height if known) to reduce layout shift.Suggested diff
- <img src="/assets/images/noboxdevcontact.png" alt="No Box Dev team working" style="border-radius: var(--radius);"> + <img src="/assets/images/noboxdevcontact.png" alt="No Box Dev team working" loading="lazy" decoding="async" style="border-radius: var(--radius);">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@about.njk` around lines 31 - 33, The mission image <img> in about.njk currently lacks lazy loading and async decoding which can increase CLS and bandwidth; update the <img> element (the line containing the img tag with src="/assets/images/noboxdevcontact.png") to add loading="lazy" and decoding="async", and include explicit width and height attributes if you know the intrinsic dimensions to lock the layout and prevent cumulative layout shift.styles.css (1)
25-63: Stylelint: Font-family names and keyframe naming conventions.Static analysis flags stylistic issues that can be optionally addressed:
Font-family quotes: Stylelint prefers unquoted font names in
@font-facedeclarations (e.g.,font-family: Aeonikinstead offont-family: 'Aeonik').Keyframe names: Stylelint expects kebab-case (e.g.,
page-in,glow-drift-1) instead of camelCase (pageIn,glowDrift1).Both are valid CSS and function correctly. Address if aligning with project linting rules.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@styles.css` around lines 25 - 63, Remove the quotes from the font-family declarations in the `@font-face` rules (change 'Aeonik' and 'Arbeit' to unquoted Aeonik and Arbeit in the declarations for Aeonik and Arbeit) and, separately, rename any keyframe identifiers to kebab-case to match linting (e.g., change pageIn → page-in, glowDrift1 → glow-drift-1) and update any references to those keyframes in your CSS/animations to use the new kebab-case names._includes/base.njk (1)
19-21: Missingtwitter:imagemeta tag.Twitter Cards require
twitter:imagefor large image previews. Currently relying on Open Graph fallback, which may not work reliably for all Twitter clients. Also note that the defaultog:imageis an SVG logo, which Twitter may not render properly.🔧 Suggested fix
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:title" content="{{ ogTitle or title }}"> <meta name="twitter:description" content="{{ ogDescription or description }}"> + <meta name="twitter:image" content="{{ ogImage or 'https://noboxdev.com/assets/images/og-default.png' }}">Consider using a PNG/JPG image (1200×630px recommended) for better social media preview compatibility.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@_includes/base.njk` around lines 19 - 21, Add an explicit twitter:image meta tag to the template so Twitter Cards get a raster image instead of relying on OG fallback; update _includes/base.njk to include <meta name="twitter:image" content="{{ twitterImage or ogImage or image }}"> (or similar variable used for og:image) and ensure the default image points to a PNG/JPG (recommended 1200×630) rather than the SVG logo.contact.njk (1)
38-41: Inconsistent form validation: message field lacksrequiredattribute.The message textarea here is not required, but the same field in
index.njk(Line 279) hasrequired. Consider making this consistent across both contact forms.🔧 Suggested fix
<div class="form-group"> <label for="message">Tell us about your project</label> - <textarea id="message" name="message" rows="5" placeholder="Describe your project, goals, and timeline..."></textarea> + <textarea id="message" name="message" rows="5" placeholder="Describe your project, goals, and timeline..." required></textarea> </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@contact.njk` around lines 38 - 41, The message textarea in the contact form (element with id="message" and name="message") is missing the required attribute; update the <textarea id="message" name="message" rows="5" placeholder="Describe your project, goals, and timeline..."></textarea> to include required (e.g. ... placeholder="..." required></textarea>) so validation matches the other form that uses the same id/name.index.njk (1)
111-116: Placeholder links withhref="#"may cause accessibility and UX issues.The case study links go to
#but are visually covered by "Coming soon" overlays. Screen readers will still announce these as actionable links, and clicking scrolls to top unexpectedly. Consider one of these alternatives:🔧 Option A: Remove the link and style the overlay as the primary element
<div class="case-study-link-wrapper"> - <a href="#" class="case-study-link">View Case Study <span>→</span></a> - <div class="coming-soon-overlay"> - <span class="coming-soon-btn">Coming soon</span> - </div> + <span class="coming-soon-btn">Coming soon</span> </div>🔧 Option B: Add aria-disabled to indicate non-functional state
- <a href="#" class="case-study-link">View Case Study <span>→</span></a> + <a href="#" class="case-study-link" aria-disabled="true" tabindex="-1">View Case Study <span>→</span></a>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@index.njk` around lines 111 - 116, Replace the placeholder anchor to avoid an actionable link: remove href="#" from the element with class "case-study-link" and either (A) convert the <a class="case-study-link"> into a non-interactive element (e.g., <span> or <div>) and style it as before so the "coming-soon-overlay" and "coming-soon-btn" remain visible, or (B) if keeping an anchor, add aria-disabled="true" and tabindex="-1" and prevent default click behavior so screen readers and keyboard users know it is inactive; update the DOM around the "case-study-link-wrapper" to reflect the chosen approach and ensure no click/scroll-to-top occurs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@contact.njk`:
- Line 20: The form with id "contact-form" currently posts to a suspicious test
address via its action attribute ("meme@noboxdev.com"); update the action to the
intended recipient (e.g., change the email in the action URL to
hello@noboxdev.com) or confirm and replace it with the correct production
address so the contact form posts to the same displayed contact email.
In `@index.njk`:
- Line 259: The form with id "contact-form" currently uses the placeholder
recipient meme@noboxdev.com in its action URL; update the action value on the
<form id="contact-form"> (or make it configurable) to the correct recipient
address or to a template/config variable (e.g., CONTACT_EMAIL) instead of the
hardcoded meme@noboxdev.com so it no longer duplicates the placeholder used in
contact.njk.
In `@main.js`:
- Around line 86-94: The current submit handler on form (the listener added to
form via form.addEventListener('submit', ...)) triggers native navigation so the
setTimeout success UI (formOk, btn) is never shown; update the handler to accept
the event parameter, call e.preventDefault(), disable the submit button (btn)
and then perform an async submit (e.g., fetch) to the formsubmit endpoint, and
on success hide the form and add class 'show' to formOk; alternatively, if you
want to keep native submission, remove the inline success UI code (the
setTimeout block) so unreachable code is not present.
In `@package.json`:
- Around line 6-9: The package.json scripts are missing a "start" entry
referenced by the PR test plan; add a "start" script in the scripts object
(alongside "build" and "serve") that runs the Eleventy dev server (e.g., set
"start" to the same command as "serve" such as "npx `@11ty/eleventy` --serve") so
npm start works as expected.
In `@styles.css`:
- Around line 96-102: The global rule on the selector "button, input, textarea,
select" removes default focus outlines, harming keyboard accessibility; either
remove "outline: none" from that rule or restrict that reset and add explicit
visible focus styles for all interactive controls (e.g., ensure selectors like
".form-group input:focus", ".form-group textarea:focus", "button:focus",
"select:focus" define clear outlines/box-shadows) so every interactive element
has a keyboard-visible focus state.
---
Nitpick comments:
In `@_includes/base.njk`:
- Around line 19-21: Add an explicit twitter:image meta tag to the template so
Twitter Cards get a raster image instead of relying on OG fallback; update
_includes/base.njk to include <meta name="twitter:image" content="{{
twitterImage or ogImage or image }}"> (or similar variable used for og:image)
and ensure the default image points to a PNG/JPG (recommended 1200×630) rather
than the SVG logo.
In `@_includes/nav.njk`:
- Around line 5-13: Add ARIA attributes to the mobile toggle button and update
the JS to keep them in sync: give the menu container (the div.nav-links) a
stable id and add aria-controls="that-id" and aria-expanded="false" to the
button.nav-toggle in the template, then update the openNav() and closeNav()
functions in main.js to set button.setAttribute('aria-expanded', 'true') when
opening and 'false' when closing (and ensure the initial state is false on page
load); also ensure any toggle handler that both opens and closes flips
aria-expanded accordingly.
In `@about.njk`:
- Around line 31-33: The mission image <img> in about.njk currently lacks lazy
loading and async decoding which can increase CLS and bandwidth; update the
<img> element (the line containing the img tag with
src="/assets/images/noboxdevcontact.png") to add loading="lazy" and
decoding="async", and include explicit width and height attributes if you know
the intrinsic dimensions to lock the layout and prevent cumulative layout shift.
In `@contact.njk`:
- Around line 38-41: The message textarea in the contact form (element with
id="message" and name="message") is missing the required attribute; update the
<textarea id="message" name="message" rows="5" placeholder="Describe your
project, goals, and timeline..."></textarea> to include required (e.g. ...
placeholder="..." required></textarea>) so validation matches the other form
that uses the same id/name.
In `@index.njk`:
- Around line 111-116: Replace the placeholder anchor to avoid an actionable
link: remove href="#" from the element with class "case-study-link" and either
(A) convert the <a class="case-study-link"> into a non-interactive element
(e.g., <span> or <div>) and style it as before so the "coming-soon-overlay" and
"coming-soon-btn" remain visible, or (B) if keeping an anchor, add
aria-disabled="true" and tabindex="-1" and prevent default click behavior so
screen readers and keyboard users know it is inactive; update the DOM around the
"case-study-link-wrapper" to reflect the chosen approach and ensure no
click/scroll-to-top occurs.
In `@styles.css`:
- Around line 25-63: Remove the quotes from the font-family declarations in the
`@font-face` rules (change 'Aeonik' and 'Arbeit' to unquoted Aeonik and Arbeit in
the declarations for Aeonik and Arbeit) and, separately, rename any keyframe
identifiers to kebab-case to match linting (e.g., change pageIn → page-in,
glowDrift1 → glow-drift-1) and update any references to those keyframes in your
CSS/animations to use the new kebab-case names.
|
|
||
| <div class="contact-grid reveal"> | ||
| <div> | ||
| <form id="contact-form" action="https://formsubmit.co/meme@noboxdev.com" method="POST"> |
There was a problem hiding this comment.
Suspicious form action email address.
The form action uses meme@noboxdev.com which appears to be a test/placeholder email. This differs from the displayed contact email hello@noboxdev.com on Line 56. Verify this is the intended recipient address before deploying.
🔧 Suggested fix
- <form id="contact-form" action="https://formsubmit.co/meme@noboxdev.com" method="POST">
+ <form id="contact-form" action="https://formsubmit.co/hello@noboxdev.com" method="POST">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <form id="contact-form" action="https://formsubmit.co/meme@noboxdev.com" method="POST"> | |
| <form id="contact-form" action="https://formsubmit.co/hello@noboxdev.com" method="POST"> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@contact.njk` at line 20, The form with id "contact-form" currently posts to a
suspicious test address via its action attribute ("meme@noboxdev.com"); update
the action to the intended recipient (e.g., change the email in the action URL
to hello@noboxdev.com) or confirm and replace it with the correct production
address so the contact form posts to the same displayed contact email.
| </div> | ||
|
|
||
| <div> | ||
| <form id="contact-form" action="https://formsubmit.co/meme@noboxdev.com" method="POST"> |
There was a problem hiding this comment.
Same placeholder email as contact.njk.
This form also uses meme@noboxdev.com. See comment on contact.njk Line 20.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@index.njk` at line 259, The form with id "contact-form" currently uses the
placeholder recipient meme@noboxdev.com in its action URL; update the action
value on the <form id="contact-form"> (or make it configurable) to the correct
recipient address or to a template/config variable (e.g., CONTACT_EMAIL) instead
of the hardcoded meme@noboxdev.com so it no longer duplicates the placeholder
used in contact.njk.
| const form = document.querySelector('#contact-form'); | ||
| const formOk = document.querySelector('.form-success'); | ||
| if (form) { | ||
| form.addEventListener('submit', () => { | ||
| const btn = form.querySelector('button[type="submit"]'); | ||
| btn.textContent = 'Sending...'; | ||
| btn.disabled = true; | ||
| if (formOk) setTimeout(() => { form.style.display = 'none'; formOk.classList.add('show'); }, 1000); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find the contact form markup
rg -n -C3 'id="contact-form"' -SRepository: No-Box-Dev/website
Length of output: 996
Remove unreachable inline success UI or prevent form submission and handle async.
Lines 89–94: The form submits natively to formsubmit.co with a _next redirect, so the page navigates away before the setTimeout callback renders the success message. Either call e.preventDefault() and submit asynchronously via fetch, or remove the inline success state since the user is redirected server-side anyway.
Async-submit approach if staying on page
- form.addEventListener('submit', () => {
+ form.addEventListener('submit', async (e) => {
+ e.preventDefault();
const btn = form.querySelector('button[type="submit"]');
btn.textContent = 'Sending...';
btn.disabled = true;
- if (formOk) setTimeout(() => { form.style.display = 'none'; formOk.classList.add('show'); }, 1000);
+ try {
+ await fetch(form.action, {
+ method: form.method || 'POST',
+ body: new FormData(form),
+ headers: { 'Accept': 'application/json' },
+ });
+ if (formOk) { form.style.display = 'none'; formOk.classList.add('show'); }
+ } finally {
+ btn.disabled = false;
+ }
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const form = document.querySelector('#contact-form'); | |
| const formOk = document.querySelector('.form-success'); | |
| if (form) { | |
| form.addEventListener('submit', () => { | |
| const btn = form.querySelector('button[type="submit"]'); | |
| btn.textContent = 'Sending...'; | |
| btn.disabled = true; | |
| if (formOk) setTimeout(() => { form.style.display = 'none'; formOk.classList.add('show'); }, 1000); | |
| }); | |
| const form = document.querySelector('#contact-form'); | |
| const formOk = document.querySelector('.form-success'); | |
| if (form) { | |
| form.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const btn = form.querySelector('button[type="submit"]'); | |
| btn.textContent = 'Sending...'; | |
| btn.disabled = true; | |
| try { | |
| await fetch(form.action, { | |
| method: form.method || 'POST', | |
| body: new FormData(form), | |
| headers: { 'Accept': 'application/json' }, | |
| }); | |
| if (formOk) { form.style.display = 'none'; formOk.classList.add('show'); } | |
| } finally { | |
| btn.disabled = false; | |
| } | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@main.js` around lines 86 - 94, The current submit handler on form (the
listener added to form via form.addEventListener('submit', ...)) triggers native
navigation so the setTimeout success UI (formOk, btn) is never shown; update the
handler to accept the event parameter, call e.preventDefault(), disable the
submit button (btn) and then perform an async submit (e.g., fetch) to the
formsubmit endpoint, and on success hide the form and add class 'show' to
formOk; alternatively, if you want to keep native submission, remove the inline
success UI code (the setTimeout block) so unreachable code is not present.
| "scripts": { | ||
| "build": "npx @11ty/eleventy", | ||
| "serve": "npx @11ty/eleventy --serve" | ||
| }, |
There was a problem hiding this comment.
Add a start script to match the documented test plan.
Line 6-9: the PR test plan says npm start, but there’s no start script, so it will error.
Suggested diff
"scripts": {
"build": "npx `@11ty/eleventy`",
- "serve": "npx `@11ty/eleventy` --serve"
+ "serve": "npx `@11ty/eleventy` --serve",
+ "start": "npm run serve"
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "scripts": { | |
| "build": "npx @11ty/eleventy", | |
| "serve": "npx @11ty/eleventy --serve" | |
| }, | |
| "scripts": { | |
| "build": "npx `@11ty/eleventy`", | |
| "serve": "npx `@11ty/eleventy` --serve", | |
| "start": "npm run serve" | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package.json` around lines 6 - 9, The package.json scripts are missing a
"start" entry referenced by the PR test plan; add a "start" script in the
scripts object (alongside "build" and "serve") that runs the Eleventy dev server
(e.g., set "start" to the same command as "serve" such as "npx `@11ty/eleventy`
--serve") so npm start works as expected.
| button, input, textarea, select { | ||
| font: inherit; | ||
| color: inherit; | ||
| border: none; | ||
| background: none; | ||
| outline: none; | ||
| } |
There was a problem hiding this comment.
Removing outline: none degrades keyboard accessibility.
Setting outline: none on form elements removes the default focus indicator. While custom focus styles are defined for .form-group input:focus etc. (Lines 964-969), this reset applies globally and may affect interactive elements without custom focus styles. Consider removing this or ensuring all interactive elements have visible focus states.
🔧 Suggested fix: Remove outline: none or add visible focus fallback
button, input, textarea, select {
font: inherit;
color: inherit;
border: none;
background: none;
- outline: none;
}
+
+/* Provide visible focus for all interactive elements */
+:focus-visible {
+ outline: 2px solid var(--coral);
+ outline-offset: 2px;
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| button, input, textarea, select { | |
| font: inherit; | |
| color: inherit; | |
| border: none; | |
| background: none; | |
| outline: none; | |
| } | |
| button, input, textarea, select { | |
| font: inherit; | |
| color: inherit; | |
| border: none; | |
| background: none; | |
| } | |
| /* Provide visible focus for all interactive elements */ | |
| :focus-visible { | |
| outline: 2px solid var(--coral); | |
| outline-offset: 2px; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@styles.css` around lines 96 - 102, The global rule on the selector "button,
input, textarea, select" removes default focus outlines, harming keyboard
accessibility; either remove "outline: none" from that rule or restrict that
reset and add explicit visible focus styles for all interactive controls (e.g.,
ensure selectors like ".form-group input:focus", ".form-group textarea:focus",
"button:focus", "select:focus" define clear outlines/box-shadows) so every
interactive element has a keyboard-visible focus state.
Summary
What changed
index.html,css/,js/)styles.csswith design tokens (coral/purple palette), gradient mesh glows, rotating headline animationmain.jswith 10 hero variant renderers, word rotation, scroll reveals, mobile nav, contact form handlingTest plan
npm install && npm startand verify homepage loads with hero variants🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Documentation