Skip to content

Add NavBar, ButtonPill, and RMLogoDrawOn components to intro pages#23

Open
dallasbpeters wants to merge 5 commits intomainfrom
add-framer-navbar
Open

Add NavBar, ButtonPill, and RMLogoDrawOn components to intro pages#23
dallasbpeters wants to merge 5 commits intomainfrom
add-framer-navbar

Conversation

@dallasbpeters
Copy link
Member

@dallasbpeters dallasbpeters commented Feb 13, 2026

Summary

  • Converted Framer NavBar to Next.js — Full-featured navigation bar with scroll-based color changing via IntersectionObserver, dropdown mega-menu with 3 link columns (About Us, Our Approach, Solutions + Company), partner spotlight card with circle clip-path hover effect, CTA card, and responsive mobile/tablet/desktop layouts.
  • Added ButtonPill and RMLogoDrawOn components — ButtonPill supports multiple variants, grow overlay hover animations, and dynamic Hugeicons (stroke/solid/duotone). RMLogoDrawOn renders an animated SVG draw-on logo with once-per-session playback via sessionStorage.
  • Integrated all three components across intro pages (LandingPageA, LandingPageB, LandingPageC) with proper props for links, colors, spotlight content, and logo theming.

Notable fixes

  • All TypeScript errors resolved (implicit any types, unused variables, motion/react Easing type compatibility)
  • Hydration mismatches fixed (breakpoint state initialization, sessionStorage access moved to useEffect)
  • Replaced @hugeicons-pro/core-twotone-rounded with @hugeicons-pro/core-duotone-rounded for correct icon style
  • Motion animate prop no longer uses CSS variables (browser handles transition via CSS transition property instead)
  • CaseStudyCard now accepts configurable height prop; logo sized appropriately
  • Blog moved from dropdown column to header button row
  • All nav links point to rolemodelsoftware.com domain
  • Removed redundant standalone logo from intro page hero sections
  • Cleaned up unused Framer-specific functions and variables
image

Convert the Framer NavBar component to a standard Next.js React component
and integrate it with ButtonPill and RMLogoDrawOn across all intro pages.

Key changes:
- Add NavBar with scroll-based color changing, dropdown menu with link
  columns, partner spotlight card, and CTA card
- Add ButtonPill component with grow overlay hover effect, multiple
  variants, and dynamic icon support
- Add RMLogoDrawOn animated SVG logo with draw-on effect and
  once-per-session support
- Fix all TypeScript errors across NavBar and ButtonPill
- Fix hydration mismatches (breakpoint state, sessionStorage access)
- Fix motion/react animation compatibility (easing types, CSS variables)
- Replace @hugeicons-pro/core-twotone-rounded with
  @hugeicons-pro/core-duotone-rounded for correct icon style
- Install @hugeicons-pro/core-solid-standard for ButtonPill icons
- Update all nav links to rolemodelsoftware.com domain
- Add Blog to header button row, remove from dropdown column
- Add spotlight card hover grow effect with circle clip-path animation
- Make CaseStudyCard height configurable via prop
- Remove redundant standalone logo from intro page hero sections
- Remove unused functions and variables from NavBar cleanup
- Configure next.config.ts for external image domains

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel
Copy link
Contributor

vercel bot commented Feb 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
rolemodel-ai-leads Ready Ready Preview, Comment, Open in v0 Feb 13, 2026 11:57pm

Request Review

…ility

Key changes:
- Adjusted alignment of nav link label container to 'start'
- Increased opacity of description text for better visibility
- Changed label container from span to div for semantic correctness
- Updated icon size for better responsiveness
- Enhanced description display logic to conditionally render based on item presence
- Renamed third column title from 'Company' to 'People' for clarity
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds a comprehensive navigation system to the intro pages by converting a Framer-based NavBar to Next.js and introducing supporting components (ButtonPill and RMLogoDrawOn). The implementation includes scroll-based color changing, a dropdown mega-menu with multiple link columns, partner spotlight functionality, and responsive layouts.

Changes:

  • Converted Framer NavBar component to Next.js with IntersectionObserver-based scroll color changes, hover/click menu interactions, and responsive breakpoints
  • Created ButtonPill component with multiple color variants, hover animations, grow overlay effects, and dynamic Hugeicons support
  • Created RMLogoDrawOn component featuring SVG draw-on animation with once-per-session playback control via sessionStorage

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
src/styles/theme.css Updated blue-green-900 color value for theming consistency
src/components/ui/case-study-card.tsx Added configurable height prop and adjusted logo sizing with objectFit positioning
src/components/ui/RMLogoDrawOn.tsx New animated logo component with theme support, session-based playback control, and hydration-safe rendering
src/components/ui/ButtonPill.tsx New button component with extensive variants, icon support, and hover animations
src/components/layout/NavBar.tsx New navigation bar with mega-menu, scroll-based theming, and responsive layouts
src/components/intro/LandingPageC.tsx Integrated NavBar, removed standalone Logo component
src/components/intro/LandingPageB.tsx Integrated NavBar, removed standalone Logo component
src/components/intro/LandingPageA.tsx Integrated NavBar, replaced @hugeicons-pro/core-twotone-rounded import
package.json Added @hugeicons-pro/core-duotone-rounded and core-solid-standard dependencies, removed core-twotone-rounded
next.config.ts Added framerusercontent.com to image domains allowlist

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 1748 to 1766
onClick={() => closeMenu()}
style={{
position: "absolute",
inset: 0,
zIndex: 3,
cursor: "pointer",
}}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (
e.key === "Enter" ||
e.key === " "
) {
e.preventDefault()
closeMenu()
}
}}
aria-label="Close menu"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The fallback div element when there's no spotlightLink has aria-label="Close menu", which is misleading. This element appears to be intended as a clickable area over the CaseStudyCard, not specifically to close the menu. The aria-label should describe the actual action, such as "View spotlight details" or be removed if the element is purely decorative. Additionally, since there's no spotlightLink, clicking should probably do nothing rather than close the menu.

Suggested change
onClick={() => closeMenu()}
style={{
position: "absolute",
inset: 0,
zIndex: 3,
cursor: "pointer",
}}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (
e.key === "Enter" ||
e.key === " "
) {
e.preventDefault()
closeMenu()
}
}}
aria-label="Close menu"
style={{
position: "absolute",
inset: 0,
zIndex: 3,
}}

Copilot uses AI. Check for mistakes.
Comment on lines 499 to 500
// Fallback: poll every 100ms for Framer environments where scroll events don't bubble
intervalRef.current = setInterval(checkSections, 100)
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The useScrollColorChange hook runs checkSections() every 100ms via setInterval as a fallback for Framer environments. This continuous polling can negatively impact performance, especially on mobile devices or lower-end hardware. The function performs multiple DOM queries (getElementById, querySelector with multiple selectors) and getBoundingClientRect() calls on every poll. Consider increasing the interval duration (e.g., 200-300ms) or using IntersectionObserver as the primary mechanism with the interval as a true fallback only when IntersectionObserver is unavailable.

Suggested change
// Fallback: poll every 100ms for Framer environments where scroll events don't bubble
intervalRef.current = setInterval(checkSections, 100)
// Fallback: poll every 250ms for Framer environments where scroll events don't bubble
intervalRef.current = setInterval(checkSections, 250)

Copilot uses AI. Check for mistakes.
flexShrink: 0,
color: finalTextColor,
paddingInline: isPhone ? 30 : 24,
dislay: "grid",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

There's a typo in the CSS property name: 'dislay' should be 'display'. This will cause the intended display style to not be applied.

Suggested change
dislay: "grid",
display: "grid",

Copilot uses AI. Check for mistakes.
/>
<LinkList
isPhone={isPhone}
listId="col5"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The listId for this LinkList is set to "col4" but it's rendering col3Links with col3Title. This mismatch could cause confusion when debugging or tracking list identities. Consider changing the listId to "col3" for consistency.

Suggested change
listId="col5"
listId="col3"

Copilot uses AI. Check for mistakes.
textColor = "#FFFFFF",
openTextColor = "#FFFFFF",
logoColor = "#000000",
openLogoColor = "#FFFFFF",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Unused variable openLogoColor.

Suggested change
openLogoColor = "#FFFFFF",

Copilot uses AI. Check for mistakes.
],
previewOpen,
previewMode = "auto",
shellMaxWidth = "1200px",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Unused variable shellMaxWidth.

Suggested change
shellMaxWidth = "1200px",

Copilot uses AI. Check for mistakes.
Copy link

@scriswell scriswell left a comment

Choose a reason for hiding this comment

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

@dallasbpeters It doesn't look like we've used Optics for this at all. That's OK, we don't need to force that here. Approved. @mark-kraemer You will likely want to review the code as well before we ship.

--blue-green-700: #0e2e34ff;
--blue-green-800: #0b252a;
--blue-green-900: #041f20ff;
--blue-green-900: #04242B;

Choose a reason for hiding this comment

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

@dallasbpeters Doesn't look like we're using optics with this project. Is that correct?

Copy link
Member Author

Choose a reason for hiding this comment

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

We are using optics.

Comment on lines 11 to 30
function getIconComponent(
iconName: string,
variant: 'stroke' | 'solid' | 'duotone' | 'bulk' = 'stroke'
) {
// Convert kebab-case to PascalCase and add Icon suffix
const pascalCase = iconName
.split('-')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join('')
const iconKey = `${pascalCase}Icon`

// Select the icon set based on variant
const iconSet =
variant === 'solid'
? HugeIconsSolid
: variant === 'duotone'
? HugeIconsDuotone
: HugeIconsStroke

return (iconSet as Record<string, IconSvgElement>)[iconKey] || null

Choose a reason for hiding this comment

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

@mark-kraemer Thoughts on this?

Comment on lines 356 to 378
const renderSvg = (raw: string) => {
if (!raw) return null
const sanitize = (svg: string) =>
svg
.replace(/fill="(?!none)[^"]*"/gi, 'fill="currentColor"')
.replace(/stroke="(?!none)[^"]*"/gi, 'stroke="currentColor"')
.replace(/fill='(?!none)[^']*'/gi, "fill='currentColor'")
.replace(/stroke='(?!none)[^']*'/gi, "stroke='currentColor'")
const cleaned = sanitize(raw)
return (
<span
aria-hidden="true"
className="button-icon-sanitize"
style={{
display: 'inline-flex',
width: '100%',
height: '100%',
color: 'inherit',
}}
dangerouslySetInnerHTML={{ __html: cleaned }}
/>
)
}

Choose a reason for hiding this comment

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

@mark-kraemer again, what are your thoughts here?

…and accessibility

Key changes:
- Removed TypeScript no-check directive from NavBar
- Fixed display property typo in NavBar styles
- Adjusted scroll event polling interval for better performance
- Updated spotlight link in NavBar for clarity
- Removed unused logo color props in NavBar
- Simplified icon variant options in ButtonPill
- Enhanced SVG sanitization in ButtonPill for better security
…, and LandingPageC for consistency and improved functionality

Key changes:
- Replaced direct imports of ButtonPill with updated import paths
- Simplified icon handling by using endIconName and showEndIcon props
- Removed unused icon imports and adjusted button properties for better performance
- Ensured hydration compatibility for icon rendering in ButtonPill
- Deleted the old button-animated component to streamline the codebase
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants