diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..3b4b13d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "importOrder": ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"], + "importOrderSeparation": true, + "importOrderSortSpecifiers": true, + "plugins": [ + "@trivago/prettier-plugin-sort-imports", + "prettier-plugin-tailwindcss" + ] +} diff --git a/bun.lockb b/bun.lockb index bba0046..1346b9d 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 53cd408..33c12b6 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,15 @@ "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-slot": "^1.0.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "front-matter": "^4.0.2", + "lucide-react": "^0.390.0", "marked": "^12.0.2", "next": "14.2.3", + "next-themes": "^0.3.0", "react": "^18", "react-dom": "^18", "tailwind-merge": "^2.3.0", @@ -26,12 +29,16 @@ "vaul": "^0.9.1" }, "devDependencies": { + "@tailwindcss/typography": "^0.5.13", + "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", "eslint-config-next": "14.2.3", "postcss": "^8", + "prettier": "^3.3.1", + "prettier-plugin-tailwindcss": "^0.6.1", "tailwindcss": "^3.4.1", "typescript": "^5" } diff --git a/public/assets/images/bg-beach.jpg b/public/assets/images/bg-beach.jpg new file mode 100644 index 0000000..f1b3dcd Binary files /dev/null and b/public/assets/images/bg-beach.jpg differ diff --git a/public/assets/images/logos/presentday.svg b/public/assets/images/logos/presentday.svg new file mode 100644 index 0000000..1172503 --- /dev/null +++ b/public/assets/images/logos/presentday.svg @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/api/certifications.md b/src/api/certifications.md index 2993e59..83f2f6a 100644 --- a/src/api/certifications.md +++ b/src/api/certifications.md @@ -1,26 +1,26 @@ --- certificates: - - title: "Project Management Simplified" - issuer: "Lynda.com" - issued: "2017-02-01T00:00:00Z" - expired: "2017-02-01T00:00:00Z" - credential_id: "5E92D8" + - title: Project Management Simplified + issuer: Lynda.com + issued: 2017-02-01T00:00:00Z + expired: 2017-02-01T00:00:00Z + credential_id: 5E92D8 show_credential: true, - url: "https://www.linkedin.com/learning/project-management-simplified-2019/project-management-a-priceless-skill" + url: https://www.linkedin.com/learning/project-management-simplified-2019/project-management-a-priceless-skill - - title: "Becoming a Triple-Threat Project Manager" - issuer: "Lynda.com" - issued: "2016-12-01T00:00:00Z" - expired: "2016-12-01T00:00:00Z" - credential_id: "0A74EE" + - title: Becoming a Triple-Threat Project Manager + issuer: Lynda.com + issued: 2016-12-01T00:00:00Z + expired: 2016-12-01T00:00:00Z + credential_id: 0A74EE show_credential: true, - url: "https://www.linkedin.com/learning/triple-threat-project-professional" + url: https://www.linkedin.com/learning/triple-threat-project-professional - - title: "Professional Networking" - issuer: "Lynda.com" - issued: "2016-12-01T00:00:00Z" - expired: "2016-12-01T00:00:00Z" - credential_id: "244268" + - title: Professional Networking + issuer: Lynda.com + issued: 2016-12-01T00:00:00Z + expired: 2016-12-01T00:00:00Z + credential_id: 244268 show_credential: true, - url: "https://www.linkedin.com/learning/professional-networking" + url: https://www.linkedin.com/learning/professional-networking --- diff --git a/src/api/education.md b/src/api/education.md index 4032d69..2a93bd9 100644 --- a/src/api/education.md +++ b/src/api/education.md @@ -1,28 +1,28 @@ --- education: - - institution: "Virginia Commonwealth University" - logo: "https://media.licdn.com/dms/image/C4E0BAQHY-pnQLhvFtQ/company-logo_100_100/0/1630600618760/virginia_commonwealth_university_logo?e=1725494400&v=beta&t=9JX-Sml6gaQSwifkuiUqwAFvQ-XtknCeVxX13sGmaXk" - name: "Bachelor of Science, Mass Communications / Creative Advertising / Media Studies / Art Direction" - start_date: "2002-09-01" - end_date: "2007-06-01" - activities: "Ad Club, Linux Users Group, Mass Comm Lab" - details: "Handpicked to take a job as Mac Lab Monitor due to my expertise in Mac OS, PC networking, concepting, and design. Supervised and maintained the Macintosh Lab for all Mass Communications majors. Assisted people in concepting their ideas, troubleshooting issues, and broadening my own knowledge." - - institution: "General Assembly" - logo: "" - name: "Back-end Web Development" - start_date: "2013-09-01" - end_date: "2014-06-01" - details: "Studied fundamentals of the Ruby language and Ruby on Rails framework." - additional_info: "Learn from top practitioners of programming, business, and design at General Assembly." - thumbnail: "https://media.licdn.com/dms/image/sync/D4E27AQFzuvqh95PKrA/articleshare-shrink_160/0/1711217661716?e=1718053200&v=beta&t=s6WdyNGu1UHr9dNMrJe-dwHRB8-FuSoSlCQiWy4BUKk" - - institution: "New York University" - logo: "https://media.licdn.com/dms/image/C4D0BAQF6TmzLkch0dQ/company-logo_100_100/0/1630556159190/new_york_university_logo?e=1725494400&v=beta&t=hSzPxRXg4lnyAZJwAk1_aOpBA8rvE78GsraxwXfw62w" - name: "Advanced Javascript" - start_date: "2014-06-01" - end_date: "2014-07-01" - - institution: "The New School" - logo: "https://media.licdn.com/dms/image/C560BAQFdlPN2GDZNZQ/company-logo_100_100/0/1630603907744/the_new_school_logo?e=1725494400&v=beta&t=5SRqoj5ZoTZRQj8ttzgSDLL4RGRx_I5CRWQ0G-ottPE" - name: "Fundamentals of Digital Product Design" - start_date: "2019-06-01" - end_date: "2019-08-01" + - institution: Virginia Commonwealth University + logo: https://media.licdn.com/dms/image/C4E0BAQHY-pnQLhvFtQ/company-logo_100_100/0/1630600618760/virginia_commonwealth_university_logo?e=1725494400&v=beta&t=9JX-Sml6gaQSwifkuiUqwAFvQ-XtknCeVxX13sGmaXk + name: Bachelor of Science, Mass Communications; Creative Advertising / Media Studies / Art Direction + start_date: 2002-09-01 + end_date: 2007-06-01 + activities: Ad Club, Linux Users Group, Mass Comm Lab + details: Handpicked to take a job as Mac Lab Monitor due to my expertise in Mac OS, PC networking, concepting, and design. Supervised and maintained the Macintosh Lab for all Mass Communications majors. Assisted people in concepting their ideas, troubleshooting issues, and broadening my own knowledge. + - institution: General Assembly + logo: + name: Back-end Web Development + start_date: 2013-09-01 + end_date: 2014-06-01 + details: Studied fundamentals of the Ruby language and Ruby on Rails framework. + additional_info: Learn from top practitioners of programming, business, and design at General Assembly. + thumbnail: https://media.licdn.com/dms/image/sync/D4E27AQFzuvqh95PKrA/articleshare-shrink_160/0/1711217661716?e=1718053200&v=beta&t=s6WdyNGu1UHr9dNMrJe-dwHRB8-FuSoSlCQiWy4BUKk + - institution: New York University + logo: https://media.licdn.com/dms/image/C4D0BAQF6TmzLkch0dQ/company-logo_100_100/0/1630556159190/new_york_university_logo?e=1725494400&v=beta&t=hSzPxRXg4lnyAZJwAk1_aOpBA8rvE78GsraxwXfw62w + name: Advanced Javascript + start_date: 2014-06-01 + end_date: 2014-07-01 + - institution: The New School + logo: https://media.licdn.com/dms/image/C560BAQFdlPN2GDZNZQ/company-logo_100_100/0/1630603907744/the_new_school_logo?e=1725494400&v=beta&t=5SRqoj5ZoTZRQj8ttzgSDLL4RGRx_I5CRWQ0G-ottPE + name: Fundamentals of Digital Product Design + start_date: 2019-06-01 + end_date: 2019-08-01 --- diff --git a/src/api/endorsements.md b/src/api/endorsements.md index e003a6f..7426641 100644 --- a/src/api/endorsements.md +++ b/src/api/endorsements.md @@ -1,101 +1,101 @@ --- endorsements: - - category: "Industry Knowledge" + - category: Industry Knowledge skills: - - name: "Communication" - experiences: "7 experiences across Unqork and 5 other companies" + - name: Communication + experiences: 7 experiences across Unqork and 5 other companies endorsements: 1 recent_endorsements: 1 - - name: "Web Standards" - experiences: "7 experiences across Unqork and 5 other companies" + - name: Web Standards + experiences: 7 experiences across Unqork and 5 other companies endorsements: 1 recent_endorsements: 1 - - category: "Tools & Technologies" + - category: Tools & Technologies skills: - - name: "Node.js" - experiences: "4 experiences across Unqork and 2 other companies" + - name: Node.js + experiences: 4 experiences across Unqork and 2 other companies endorsements: 1 recent_endorsements: 1 - - name: "JavaScript" - experiences: "7 experiences across Unqork and 5 other companies" + - name: JavaScript + experiences: 7 experiences across Unqork and 5 other companies endorsements: 1 - - name: "React.js" - experiences: "5 experiences across Unqork and 3 other companies" + - name: React.js + experiences: 5 experiences across Unqork and 3 other companies endorsements: 1 - - name: "Web Design" + - name: Web Design endorsements: 58 - - name: "User Experience" + - name: User Experience endorsements: 45 - - name: "Graphic Design" + - name: Graphic Design endorsements: 36 - - name: "Front-end" + - name: Front-end endorsements: 24 - - name: "CSS" + - name: CSS endorsements: 17 - - name: "Adobe Creative Suite" + - name: Adobe Creative Suite endorsements: 13 - - name: "HTML 5" + - name: HTML 5 endorsements: 10 - - name: "Email Marketing" + - name: Email Marketing endorsements: 10 - - name: "Mac" + - name: Mac endorsements: 10 - - name: "Web Development" + - name: Web Development endorsements: 13 - - name: "jQuery" + - name: jQuery endorsements: 8 - - name: "User Interface" + - name: User Interface endorsements: 8 - - name: "Information Architecture" + - name: Information Architecture endorsements: 7 - - name: "User Interface Design" + - name: User Interface Design endorsements: 8 - - name: "Advertising" + - name: Advertising endorsements: 10 - - name: "Interaction Design" + - name: Interaction Design endorsements: 6 - - name: "Copywriting" + - name: Copywriting endorsements: 5 - - name: "Ruby on Rails" + - name: Ruby on Rails endorsements: 5 - - name: "Creative Direction" + - name: Creative Direction endorsements: 8 - - name: "Networking" + - name: Networking endorsements: 2 - - name: "Brochures" + - name: Brochures endorsements: 4 - - name: "Art Direction" + - name: Art Direction endorsements: 3 - - name: "PHP" + - name: PHP endorsements: 3 - - name: "Operating Systems" + - name: Operating Systems endorsements: 2 - - name: "LESS" + - name: LESS endorsements: 1 - - name: "jQuery Mobile" + - name: jQuery Mobile endorsements: 1 - - name: "Ruby" + - name: Ruby endorsements: 1 - - name: "HTML5" + - name: HTML5 endorsements: 5 - - name: "Jade" - - name: "Stylus" - - name: "CoffeeScript" - - name: "Progressive Enhancement" - - name: "Semantic Markup" - - name: "Computer Hardware" - - name: "JSON" - - name: "Twitter Bootstrap" - - name: "Google Apps" - - name: "Technical Direction" - - name: "Wordpress" - - name: "Responsive Development" - - name: "Modernizr" - - name: "Linux" - - name: "Meteor" - - name: "React" - - name: "Blaze" - - name: "Sass" - - name: "Cascading Style Sheets (CSS)" + - name: Jade + - name: Stylus + - name: CoffeeScript + - name: Progressive Enhancement + - name: Semantic Markup + - name: Computer Hardware + - name: JSON + - name: Twitter Bootstrap + - name: Google Apps + - name: Technical Direction + - name: Wordpress + - name: Responsive Development + - name: Modernizr + - name: Linux + - name: Meteor + - name: React + - name: Blaze + - name: Sass + - name: Cascading Style Sheets (CSS) endorsements: 4 --- diff --git a/src/api/experience.md b/src/api/experience.md index aaca8e3..93bfb2e 100644 --- a/src/api/experience.md +++ b/src/api/experience.md @@ -1,14 +1,14 @@ --- experience: - - company: "Unqork" - location: "New York, New York, United States · Remote" - logo: "${basePath}/assets/images/logos/unqork.svg" + - company: Unqork + location: New York, New York, United States · Remote + logo: ${basePath}/assets/images/logos/unqork.svg roles: - - title: "Senior Software Engineer" - type: "Full-time" - start_date: "2020-01-01" - end_date: null - duration: "4 yrs 6 mos" + - title: Senior Software Engineer + type: Full-time + date: + start: 2020-01-01 + end: null description: > Founding member of the Platform UI team. While living in Argentina in 2019, instrumental in interviewing and recruiting key team members who have significantly contributed to the team's success. @@ -19,17 +19,22 @@ experience: Currently part of the Experience Engineering team, working on innovative component creation using the Runtime team's Vega engine. Involved in pioneering efforts to build and refine new methodologies in a dynamic, real-time development environment. skills: [ - "React.js", - "Communication", - "JavaScript", - "Node.js", - "Web Standards", + React, + Communication, + JavaScript, + Node, + Fullstack, + Web Standards, + Design Systems, + Recruitment, + Module Development, + Real-time Development, ] - - title: "Senior Software Engineer" - type: "Contract" - start_date: "2018-02-01" - end_date: "2020-01-01" - duration: "2 yrs" + - title: Senior Software Engineer + type: Contract + date: + start: 2018-02-01 + end: 2020-01-01 description: > Initially joined as an original member of the “Theme Team” before the creation of Unqork Digital Services. Collaborated in a team to develop over 50 custom themes and demos, laying the foundation for future design initiatives. @@ -38,41 +43,51 @@ experience: Played a key role in transitioning the Module/App Builder from v1 to v2, with much of my input on design, styles, and behavior still in use today. skills: [ - "React.js", - "Communication", - "JavaScript", - "Node.js", - "Web Standards", + React, + Communication, + Node, + Node, + Fullstack, + Web Standards, + Theme Development, + Utility Classes, + Module Transition, ] - - title: "Principal Engineer" - type: "Contract" - start_date: "2017-04-01" - end_date: "2020-01-01" - duration: "2 yrs 10 mos" - location: "Greater New York City Area · Remote" + - company: Present Day + logo: ${basePath}/assets/images/logos/presentday.svg + roles: + - title: Principal Engineer + type: Contract + date: + start: 2017-04-01 + end: 2020-01-01 + location: Greater New York City Area · Remote description: > Actively engaged in every phase of project development, ensuring comprehensive involvement from inception to completion. - Collaborated with an engineer based in Tokyo to develop a full-stack application for Paraguay's largest testing laboratory. + The application featured integration with Wordpress REST API and utilized a server-rendered architecture comprising Node, Express, GraphQL, Apollo, React, and Redux, demonstrating advanced full-stack development capabilities. - The application featured integration with Wordpress REST API and utilized a server-rendered architecture comprising Node, Express, GraphQL, Apollo, React, and Redux, demonstrating advanced full-stack development capabilities. skills: [ - "React.js", - "Communication", - "JavaScript", - "Node.js", - "Web Standards", + React, + Node, + JavaScript, + Node, + Fullstack, + Web Standards, + Full-stack Development, + REST API Integration, + Server-rendered Architecture, ] - - company: "Joey" + - company: Joey visible: false - location: "Brooklyn, New York City · Remote" + location: Brooklyn, New York City · Remote roles: - - title: "UX, Designer and Developer" - type: "Self-employed" - start_date: "2009-11-01" - end_date: "2017-04-01" - duration: "7 yrs 6 mos" + - title: UX, Designer and Developer + type: Self-employed + date: + start: 2009-11-01 + end: 2017-04-01 description: > Managed and collaborated with a team of designers and developers, overseeing the creation of digital materials. @@ -85,21 +100,27 @@ experience: Contributed to a variety of projects including the production of print and digital materials for annual reports, brochures, advertisements, logos, presentation binders, CD covers, websites, and interactive kiosks. skills: [ - "React.js", - "Communication", - "JavaScript", - "Node.js", - "Web Standards", + Node, + Communication, + JavaScript, + Node, + Fullstack, + Fullstack, + Web Standards, + Team Management, + Technical Consulting, + Email Marketing, + Brand Development, ] - - company: "Adoptive" - logo: "${basePath}/assets/images/logos/adoptive.jpg" - location: "New York, NY · Remote" + - company: Adoptive + logo: ${basePath}/assets/images/logos/adoptive.jpg + location: New York, NY · Remote roles: - - title: "Designer/Developer" - type: "Full-time" - start_date: "2014-03-01" - end_date: "2016-08-01" - duration: "2 yrs 6 mos" + - title: Designer/Developer + type: Full-time + date: + start: 2014-03-01 + end: 2016-08-01 description: > Actively engaged in the creation and advancement of key technical projects, including YaleMedicine.org and Bundoo.com. @@ -110,16 +131,26 @@ experience: My significant contributions to Bundoo were recognized with an honorable mention at the Webby Awards. Led the deployment of the Yale School of Medicine website, utilizing a tech stack that included Gulp, Webpack, React, and PostCSS, alongside aiding in the integration with .NET back-end technologies. - skills: ["React.js", "Communication", "JavaScript", "Web Standards"] - - company: "Studiografica" - logo: "${basePath}/assets/images/logos/sgc.svg" - location: "SoHo, New York City · On-site" + skills: + [ + React, + Communication, + JavaScript, + Web Standards, + UX Design, + Strategic Planning, + Development Standards, + Award-winning Development, + ] + - company: Studiografica + logo: ${basePath}/assets/images/logos/sgc.svg + location: SoHo, New York City · On-site roles: - - title: "UX, Tech Director, Lead Front-end Designer / Developer" - type: "Full-time" - start_date: "2011-01-01" - end_date: "2013-06-01" - duration: "2 yrs 6 mos" + - title: UX, Tech Director, Lead Front-end Designer / Developer + type: Full-time + date: + start: 2011-01-01 + end: 2013-06-01 description: > Oversaw all physical technology resources within the agency. @@ -130,107 +161,168 @@ experience: Spearheaded the development of progressive-enhanced front-end solutions. Worked with high-profile brands such as Ciroc Vodka, Macy’s, Casio, W.W. Glass, and David’s Bridal, as well as the Venture Development Center, contributing significantly to both client-facing and internal projects. - skills: ["Communication", "JavaScript", "Web Standards"] - - company: "OurWeddingDay / David's Bridal" - logo: "${basePath}/assets/images/logos/davids-bridal.svg" - location: "Midtown, New York City" + skills: + [ + Communication, + JavaScript, + Web Standards, + Technology Management, + Recruitment, + UI/UX Consultancy, + Progressive Enhancement, + ] + - company: OurWeddingDay / David's Bridal + logo: ${basePath}/assets/images/logos/davids-bridal.svg + location: Midtown, New York City roles: - - title: "Senior Designer / Front-end Web Developer" - type: "Full-time" - start_date: "2008-11-01" - end_date: "2011-04-01" - duration: "2 yrs 6 mos" + - title: Senior Designer / Front-end Web Developer + type: Full-time + date: + start: 2008-11-01 + end: 2011-04-01 description: > Oversaw the design and development of email blasts, web banner advertisements, and features for web and email, as well as landing pages for one of the largest bridal retail stores in the country and its affiliate site, OurWeddingDay. Led the redesign of the corporate style guide, the main website, branding elements, the vendor control panel for logged-in users, and various content pages. Managed the relationship with an outsourced back-end development firm until late 2009. This collaboration continued until the company broadened its focus and created specialized roles for designers and developers. - skills: ["Communication", "JavaScript", "Web Standards"] - - company: "Aquent" - logo: "${basePath}/assets/images/logos/aquent.svg" + skills: + [ + Communication, + JavaScript, + Web Standards, + Email Marketing, + Corporate Style Guide, + Vendor Management, + ] + - company: Aquent + logo: ${basePath}/assets/images/logos/aquent.svg + visible: false roles: - - title: "Designer / Front-end Web Developer" - type: "Full-time" - start_date: "2007-01-01" - end_date: "2008-10-01" - duration: "1 yr 10 mos" - - company: "TMP Worldwide" - logo: "${basePath}/assets/images/logos/tmp.svg" + - title: Designer / Front-end Web Developer + type: Full-time + date: + start: 2007-01-01 + end: 2008-10-01 + - company: TMP Worldwide + logo: ${basePath}/assets/images/logos/tmp.svg roles: - - title: "Designer" - type: "Full-time" - start_date: "2008-01-01" - end_date: "2008-03-01" - duration: "3 mos" + - title: Designer + type: Full-time + date: + start: 2008-01-01 + end: 2008-03-01 description: > Assisted Creative Director and Art Director on an advertising campaign spanning web and print media for the Department of Defense Missile Defense Agency. - - company: "National Crime Prevention Council" - logo: "${basePath}/assets/images/logos/ncpc.png" + skills: + [ + Communication, + JavaScript, + Web Standards, + Advertising Campaigns, + Print Media, + ] + - company: National Crime Prevention Council + logo: ${basePath}/assets/images/logos/ncpc.png + visible: false roles: - - title: "Public/Media Relations, Program Associate" - type: "Full-time" - start_date: "2007-07-01" - end_date: "2008-01-01" - duration: "7 mos" + - title: Public/Media Relations, Program Associate + type: Full-time + date: + start: 2007-07-01 + end: 2008-01-01 description: > Responsible for all aspects of public/media relations including: leveraging the positive assets of the organization’s brand icon, McGruff the Crime Dog®; foster non-traditional media and advertising opportunities for NCPC and conduct aggressive media outreach for the National Citizens’ Crime Prevention Campaign; assist in the development process of production for television public service announcements; act as liaison between NCPC spokespeople and the mass media including major television networks, newspapers, and magazines; handle daily media inquiries, schedule television and phone interviews; prepare talking points, press releases, and media kits; and help promote NCPC story lines. - - title: "Media Assistant" - type: "Full-time" - start_date: "2004-06-01" - end_date: "2006-08-01" - duration: "2 yrs 3 mos" + skills: + [ + Communication, + Media Relations, + Public Relations, + Media Outreach, + Press Releases, + ] + - title: Media Assistant + type: Full-time + date: + start: 2004-06-01 + end: 2006-08-01 description: > Maintained a full load of duties working right below the directors of three separate departments and fulfilled work with creativity and diligence. Reorganized and redesigned the Statistic Summary Report system so further maintenance was minimal. Organized email addresses for press releases to thousands of media personnel and other interested individuals/corporations. Critiqued and solved problems of editing source to sites that were maintained by proprietary management systems. Remained as a telecommuting intern developing web design and other freelance work during the school semester. - - company: "Fishbowl Marketing" - location: "Alexandria, Virginia" + skills: + [ + Communication, + Media Relations, + Public Relations, + Report System Design, + Email Management, + ] + - company: Fishbowl Marketing + location: Alexandria, Virginia roles: - - title: "Production Designer" - type: "Full-time" - start_date: "2008-01-01" - end_date: "2008-12-01" - duration: "Less than a year" + - title: Production Designer + type: Full-time + date: + start: 2008-01-01 + end: 2008-12-01 description: > - Developed a high number of highly conceptual email campaign templates for restaurants in the United States and Europe. - - company: "Virginia Commonwealth University" - logo: "${basePath}/assets/images/logos/vcu.svg" + Developed a high number of highly conceptual email campaign templates for restaurants in the United States and European markets. + skills: [Communication, Email Marketing, Campaign Design] + - company: Virginia Commonwealth University + logo: ${basePath}/assets/images/logos/vcu.svg roles: - - title: "Web Host, Master, & Designer" - type: "Full-time" - start_date: "2006-08-01" - end_date: "2007-05-01" - duration: "10 mos" + - title: Web Host, Master, & Designer + type: Full-time + date: + start: 2006-08-01 + end: 2007-05-01 description: > - Managed design, development, and hosting for the VCU speaker series website, "Creating and Consuming Culture in the Digital Age". Collaborated with teams from Boing Boing and Wikipedia. + Managed design, development, and hosting for the VCU speaker series website, Creating and Consuming Culture in the Digital Age. Collaborated with teams from Boing Boing and Wikipedia. Played a pivotal role in ensuring the website effectively represented the series' focus on digital technologies' impact on culture, humanities, and arts. This series was a collaboration between VCU's Department of English, School of Mass Communications, and the School of the Arts. - - title: "Art Director" - type: "Full-time" - start_date: "2006-06-01" - end_date: "2007-05-01" - duration: "1 yr" + skills: [Communication, Web Design, Hosting, Digital Technologies] + - title: Art Director + type: Full-time + date: + start: 2006-06-01 + end: 2007-05-01 description: > - Worked closely with Creative Director Bridget Camden on print concepts for "Creating & Consuming Culture in the Digital Age", a collection of VCU-hosted speaker series. + Worked closely with Creative Director Bridget Camden on print concepts for Creating & Consuming Culture in the Digital Age, a collection of VCU-hosted speaker series. Oversaw the design, copywriting, and production of four posters, prominently displayed on campus, highlighting the series' exploration of digital technology's influence on contemporary culture and the arts. Coordinated with various departments within VCU to ensure the series' successful representation through visual media. - - company: "Independent" + skills: + [Communication, Art Direction, Print Design, Digital Technologies] + - company: Independent + visible: false roles: - - title: "Web Master/Designer" - type: "Full-time" - start_date: "1999-06-01" - end_date: "2006-09-01" - duration: "7 yrs 4 mos" + - title: Web Master/Designer + type: Full-time + date: + start: 1999-06-01 + end: 2006-09-01 description: > Designed, implemented, operated, and maintained a major Internet site for several years, with clientele from 45 countries. This volunteer business grew to include four part-time web designers. - - company: "Mediaflux" + skills: [Web Design, Web Development, Team Management] + - company: Mediaflux + visible: false roles: - - title: "Advertising/Design Intern" - type: "Full-time" - start_date: "2006-01-01" - end_date: "2006-12-01" - duration: "Less than a year" + - title: Advertising/Design Intern + type: Full-time + date: + start: 2006-01-01 + end: 2006-12-01 + skills: [Communication, Advertising, Design] --- + +My career has been an exciting journey through various roles, always with a focus on blending technology and design to deliver top-notch user experiences. + +At Unqork, I’ve been a key player since day one, helping to build the Platform UI team and develop the Unqork Design System (UQDS). This system has become a cornerstone of the platform, enhancing its functionality and user experience. I’ve also worked on integrating this system into component settings and pioneering innovative component creation using cutting-edge methodologies. + +Before Unqork, I worked at Present Day, where I led full-stack development projects, including a major application for Paraguay’s largest testing laboratory. My work demonstrated my ability to deliver advanced solutions while collaborating across time zones and cultures. + +At Adoptive, I played a crucial role in projects like YaleMedicine.org and Bundoo.com, earning an honorable mention at the Webby Awards. My contributions to user experience design and strategic planning were instrumental in these successes. + +I’m passionate about communication, mentoring, and driving innovation. Whether it’s through developing robust applications or enhancing user interfaces, I strive to create impactful and enjoyable digital experiences. diff --git a/src/api/favicon.md b/src/api/favicon.md index fcbaa72..bad3c45 100644 --- a/src/api/favicon.md +++ b/src/api/favicon.md @@ -1,16 +1,16 @@ --- -title: "Favicon and Meta Information" +title: Favicon and Meta Information favicon: - appleTouchIcon: "${bathPath}/assets/images/favicon/apple-touch-icon.png" - icon32x32: "${bathPath}/assets/images/favicon/favicon-32x32.png" - icon16x16: "${bathPath}/assets/images/favicon/favicon-16x16.png" - manifest: "${bathPath}/assets/images/favicon/site.webmanifest" + appleTouchIcon: ${bathPath}/assets/images/favicon/apple-touch-icon.png + icon32x32: ${bathPath}/assets/images/favicon/favicon-32x32.png + icon16x16: ${bathPath}/assets/images/favicon/favicon-16x16.png + manifest: ${bathPath}/assets/images/favicon/site.webmanifest maskIcon: - href: "${bathPath}/assets/images/favicon/safari-pinned-tab.svg" - color: "#5bbad5" - shortcutIcon: "${bathPath}/assets/images/favicon/favicon.ico" + href: ${bathPath}/assets/images/favicon/safari-pinned-tab.svg + color: #5bbad5 +shortcutIcon: ${bathPath}/assets/images/favicon/favicon.ico meta: - msapplicationTileColor: "#ffffff" - msapplicationConfig: "${bathPath}/assets/images/favicon/browserconfig.xml" - themeColor: "#ffffff" + msapplicationTileColor: #ffffff + msapplicationConfig: ${bathPath}/assets/images/favicon/browserconfig.xml + themeColor: #ffffff --- diff --git a/src/api/index.ts b/src/api/index.ts index 5832f1c..a776635 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,8 +1,10 @@ -import fs from "fs/promises" import fm from "front-matter" -import path from "path" +import fs from "fs/promises" import { marked } from "marked" +import path from "path" + import nextConfig from "../../next.config.mjs" + interface FrontMatterAttributes { [key: string]: any; } diff --git a/src/api/intro.md b/src/api/intro.md index b8ce5e0..e9ed359 100644 --- a/src/api/intro.md +++ b/src/api/intro.md @@ -1,7 +1,6 @@ --- -title: "Intro" -logo: "${basePath}/assets/images/logo--solid.svg" +title: Intro +logo: ${basePath}/assets/images/logo--solid.svg --- -With over two decades of experience, I help companies grow their products -with users in mind through strategic and creative solutions. +### With over two decades of experience, I help companies grow their products with users in mind through strategic and creative solutions. diff --git a/src/api/profile.md b/src/api/profile.md index ee3615b..a6d0f41 100644 --- a/src/api/profile.md +++ b/src/api/profile.md @@ -1,23 +1,42 @@ --- -name: "Joe Burdick" -title: "Senior Software Engineer" -location: "Brooklyn, New York" -born: "1984-06-18" -email: "josephdburdick@gmail.com" +name: Joe Burdick +title: Senior Software Engineer +location: Brooklyn, New York +coordinates: [40.7143, 73.9442] +email: josephdburdick@gmail.com +picture: + src: ${basePath}/assets/images/profile.png + width: 96 + height: 96 + alt: Joseph Burdick profile picture availableForWork: true +bg: + src: ${basePath}/assets/images/bg-beach.jpg + width: 6240 + height: 1799 + alt: Me at the beach with friends + links: email: - url: "mailto:josephdburdick@gmail.com" - label: "Email" + url: mailto:josephdburdick@gmail.com + label: Email + icon: mail + instagram: + url: https://instagr.am/d00m.exe + label: Instagram + icon: instagram linkedin: - url: "https://linkedin.com/in/josephdburdick" - label: "LinkedIn" + url: https://linkedin.com/in/josephdburdick + label: LinkedIn + icon: linkedIn readcv: - url: "https://read.cv/josephdburdick" - label: "Read.cv" + url: https://read.cv/josephdburdick + label: Read.cv + icon: readCV github: - url: "https://github.com/josephdburdick" - label: "Github" + url: https://github.com/josephdburdick + label: Github + icon: github intro: > j0e.me is the eponymous online home of Joe Burdick— web designer, software engineer, leader, and photographer. --- diff --git a/src/api/recommendations.md b/src/api/recommendations.md index d5091e3..894844f 100644 --- a/src/api/recommendations.md +++ b/src/api/recommendations.md @@ -1,52 +1,52 @@ --- recommendations: received: - - name: "Eric Ma" - connection: "1st" - title: "Engineering Manager + Caregivers ERSG Engagement Co-Chair at Unqork" - date: "2024-06-03" - relationship: "Eric was senior to Joseph but didn't manage Joseph directly" - visibility: "All LinkedIn members" + - name: Eric Ma + connection: 1st + title: Engineering Manager + Caregivers ERSG Engagement Co-Chair at Unqork + date: 2024-06-03 + relationship: Eric was senior to Joseph but didn't manage Joseph directly + visibility: All LinkedIn members recommendation: > - Joe epitomizes the qualities of what like to call a "responsible engineer". It's one thing to be good at writing code (which Joe is) and it's another to go the extra mile. + Joe epitomizes the qualities of what like to call a responsible engineer. It's one thing to be good at writing code (which Joe is) and it's another to go the extra mile. He just doesn't do the work that he's assigned to do. Joe makes sure to understand the why. This is how you get output from an engineering team that is high quality, that catches edge cases, and that truly solves the problems presenting your users, by having an engineer like Joe asking the right questions and leading by example. I count myself lucky I had the opportunity to rely on his work and envy whoever gets to work with him in his career. - - name: "Travis Hubbard" - connection: "1st" - title: "Staff Engineer | Technical Leadership & Mentorship" - date: "2024-06-03" - relationship: "Travis was senior to Joseph but didn't manage Joseph directly" - visibility: "All LinkedIn members" + - name: Travis Hubbard + connection: 1st + title: Staff Engineer | Technical Leadership & Mentorship + date: 2024-06-03 + relationship: Travis was senior to Joseph but didn't manage Joseph directly + visibility: All LinkedIn members recommendation: > I had the pleasure of working with Joe at Unqork, and he is a fantastic engineer with a remarkable eye for UI/UX and accessibility. Joe excels at asking insightful questions and raising important considerations, helping teams break down work and fully understand requirements. His deeply analytical mindset enables him to solve business challenges in a holistic and effective manner. Joe is not only a skilled problem-solver but also a great team player. He actively supports his teammates, always being one of the first to jump on calls to answer questions or provide a second pair of eyes. His commitment to driving code quality is evident through his thorough PR comments and willingness to pair with other developers. I thoroughly enjoyed my time working with Joe, and I believe he would be a brilliant addition to any team. - - name: "Nana T. Baffour-Awuah" - connection: "1st" - title: "Writer | Strategist | Consultant" - date: "2016-07-23" - relationship: "Nana T. worked with Joseph but on different teams" - visibility: "All LinkedIn members" + - name: Nana T. Baffour-Awuah + connection: 1st + title: Writer | Strategist | Consultant + date: 2016-07-23 + relationship: Nana T. worked with Joseph but on different teams + visibility: All LinkedIn members recommendation: > Over the two years that I've known Joe, since meeting at Adoptive, I've had the opportunity to work with and get to know one of the most hardworking, creative, resolute and generous people I've met in my career thus far. Joe's work ethic is impeccable, and he is a genuinely generous person who gives of himself to both his work and his team. Joe is an asset--any team would be very lucky to have him! - - name: "Matt Staudt" - connection: "1st" - title: "Bootstrap Entrepreneur + Marketing Pro | CEO @ GAME UP® Nutrition | President STAUDT agency | Created/Sold High Rollerz® Jiu Jitsu" - date: "2014-03-06" - relationship: "Matt worked with Joseph but they were at different companies" - visibility: "All LinkedIn members" + - name: Matt Staudt + connection: 1st + title: Bootstrap Entrepreneur + Marketing Pro | CEO @ GAME UP® Nutrition | President STAUDT agency | Created/Sold High Rollerz® Jiu Jitsu + date: 2014-03-06 + relationship: Matt worked with Joseph but they were at different companies + visibility: All LinkedIn members recommendation: > I wanted to take a moment to recommend Joe Burdick. I have worked with Joe not only in his current role as Founder of Joey Labs but also during both of our time at SGC. Having worked with Joe on multiple projects I am extremely confident in his abilities and work ethic which both exceed what I am accustomed to. Joe is my first call when I have a technical question and he has never ceased to amaze me with his insight and can-do attitude. He is extremely knowledgable and is a problem solver. Joe is a rare find, a pleasure to work with and as a result of his expertise and professionalism is an extremely valuable addition to any team. - - name: "Todd Post" - connection: "1st" - title: "Strategic Communications Executive | Senior Public Affairs Advisor | Communications Specialist | Vice President of Communications / In-House + Agency → Humanizing integrated strategic communications" - date: "2007-01-24" - relationship: "Todd managed Joseph directly" - visibility: "All LinkedIn members" + - name: Todd Post + connection: 1st + title: Strategic Communications Executive | Senior Public Affairs Advisor | Communications Specialist | Vice President of Communications / In-House + Agency → Humanizing integrated strategic communications + date: 2007-01-24 + relationship: Todd managed Joseph directly + visibility: All LinkedIn members recommendation: > Joe's creativity and attention to detail coupled with his great attitude and sense of humor made him a valuable asset to the department, especially when it came to graphic design, web page management, and other technical skills. --- diff --git a/src/api/site.md b/src/api/site.md index f37c09a..93e9e43 100644 --- a/src/api/site.md +++ b/src/api/site.md @@ -1,10 +1,10 @@ --- -name: "Joe Burdick" -url: "j0e.me" +name: Joe Burdick +url: j0e.me logo: - url: "${basePath}/assets/images/logo--solid.svg" - width: 100 - height: 32 - alt: "Joe Logo" + url: ${basePath}/assets/images/logo--solid.svg + width: 80 + height: 26 + alt: Joe Logo --- diff --git a/src/app/_content/About.tsx b/src/app/_content/About.tsx new file mode 100644 index 0000000..4535c9b --- /dev/null +++ b/src/app/_content/About.tsx @@ -0,0 +1,39 @@ +"use client" + +import { useApi } from "@/components/providers/DataProvider" +import Image from "next/image" + +export default function Footer() { + const { data } = useApi() + const profile = data.profile.attributes + const { width, height, src, alt } = profile.bg + return ( +
+ {alt} + +
+
+
+
About this site
+

+ Built with front-matter, statically generated with Next, and + hosted on Github pages.{" "} + + View Source Code + +

+
+
+
+
+ ) +} diff --git a/src/app/_content/Bio.tsx b/src/app/_content/Bio.tsx new file mode 100644 index 0000000..360733a --- /dev/null +++ b/src/app/_content/Bio.tsx @@ -0,0 +1,19 @@ +"use client" + +import MainAvatar from "@/components/global/MainAvatar" +import NameHeader from "@/components/global/NameHeader" +import { useApi } from "@/components/providers/DataProvider" + +export default function Bio() { + const { data } = useApi() + // const {} = data.profile + console.log({ data }) + return ( +
+ Bio +
+ +
+
+ ) +} diff --git a/src/app/_content/Experience.tsx b/src/app/_content/Experience.tsx new file mode 100644 index 0000000..30c90db --- /dev/null +++ b/src/app/_content/Experience.tsx @@ -0,0 +1,89 @@ +"use client" + +import DateSpan from "@/components/global/DateSpan" +import RuleHeader from "@/components/global/RuleHeader" +import { useApi } from "@/components/providers/DataProvider" +import { Badge } from "@/components/ui/badge" +import convertNewLinesToHTML from "@/lib/convertNewLinesToHTML" +import { Experience as ExperienceType, Role } from "@/lib/types" +import { cn } from "@/lib/utils" + +export default function Experience() { + const { data } = useApi() + const experience: ExperienceType[] = data.experience.attributes.experience + const [introBodyText, ...bioBodyText] = data.experience.html + .split(/<\/?p>/) + .filter((paragraph: string) => paragraph.trim().length > 0) + + const renderSkill = (skill: string, key: number) => + !!skill ? ( +
  • + {skill} +
  • + ) : null + + const renderRole = (role: Role, index: number) => ( +
  • +
    +
    + +
    +
    +
    + {role.title} + {role.location} + {role.type} +
    +
    +
    +
    +
    +
    +
      + {role?.skills?.sort().map(renderSkill)} +
    +
    +
    +
  • + ) + const renderExperience = (experience: ExperienceType, index: number) => ( +
  • +
    +
    + {experience.company} +
    +
    + +
  • + ) + + const renderExperiences = ( +
    + +
    + ) + + return ( +
    +
    +

    Experience

    +
    +
    {renderExperiences}
    +
    + ) +} diff --git a/src/app/_content/Intro.tsx b/src/app/_content/Intro.tsx index 9570f9b..4e259e8 100644 --- a/src/app/_content/Intro.tsx +++ b/src/app/_content/Intro.tsx @@ -1,72 +1,81 @@ "use client" -import Image from "next/image" -import { useApi } from "@/lib/providers/DataProvider" -import WorkAvailability from "@/components/global/WorkAvailability" -import ContactLinks from "@/components/global/ContactLinks" +import DarkModeToggle from "@/components/global/DarkModeToggle" +import Icon from "@/components/global/Icon" import LogoMarquee from "@/components/global/LogoMarquee" +import MainHeader from "@/components/global/MainHeader" +import MainNav from "@/components/global/MainNav" +import WeatherComponent from "@/components/global/Weather" +import WorkAvailability from "@/components/global/WorkAvailability" +import { useApi } from "@/components/providers/DataProvider" import { ContactLink } from "@/lib/types" -import MobileNav from "@/components/global/MobileNav" +import { cn } from "@/lib/utils" +import { useEffect, useRef, useState } from "react" function Intro() { + const headerRef = useRef(null) const { data } = useApi() - const { logo } = data.site.attributes - const links: ContactLink[] = Object.values(data.profile.attributes.links) - const filter = ["Email"] + const [isSticky, setIsSticky] = useState(false) + + useEffect(() => { + const handleScroll = () => { + if (!headerRef.current) return + const { bottom } = headerRef.current?.getBoundingClientRect() || {} + setIsSticky(bottom < 0) + } + + window.addEventListener("scroll", handleScroll) + return () => { + window.removeEventListener("scroll", handleScroll) + } + }, []) return ( -
    -
    -
    - {logo.alt} -
    -
    +
    +
    +
    + + + +
    + -
    +
    -
    -
    -
    - -
    -
    -
    -
    - +
    + + +
    +
    +
    - - - - {data.profile.attributes.location} +
    + + {/* {data.profile.attributes.location} */} + +
    + +
    - !filter.includes(label))} - className="hidden md:block" - /> - -
    -
    + +
    ) diff --git a/src/app/globals.css b/src/app/globals.css index 8ccca9f..a3804a7 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,64 +2,70 @@ @tailwind components; @tailwind utilities; +@layer components { + .container { + @apply md:max-w-[768px] lg:max-w-[900px] xl:max-w-[1200px] 2xl:max-w-[1320px]; + @apply px-4 sm:px-8 md:px-12 lg:px-16 xl:px-24; + } +} + +.marquee-wrapper { + mask-image: linear-gradient( + to right, + rgba(0, 0, 0, 0), + rgba(0, 0, 0, 1) 20%, + rgba(0, 0, 0, 1) 80%, + rgba(0, 0, 0, 0) + ); +} + +@keyframes scrollLeft { + to { + left: -200px; + } +} + @layer base { :root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; - --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; - --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; - --primary: 222.2 47.4% 11.2%; --primary-foreground: 210 40% 98%; - --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; - --muted: 210 40% 96.1%; --muted-foreground: 215.4 16.3% 46.9%; - --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; - --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; - --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; --ring: 222.2 84% 4.9%; - --radius: 0.5rem; } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; - --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; - --popover: 222.2 84% 4.9%; --popover-foreground: 210 40% 98%; - --primary: 210 40% 98%; --primary-foreground: 222.2 47.4% 11.2%; - --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; - --muted: 217.2 32.6% 17.5%; --muted-foreground: 215 20.2% 65.1%; - --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; - --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; - --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 212.7 26.8% 83.9%; @@ -74,26 +80,3 @@ @apply bg-background text-foreground; } } - -@layer components { - .container { - @apply md:max-w-[768px] lg:max-w-[900px] xl:max-w-[1200px] 2xl:max-w-[1320px]; - @apply px-4 sm:px-8 md:px-12 lg:px-16 xl:px-24; - } -} - -.marquee-wrapper { - mask-image: linear-gradient( - to right, - rgba(0, 0, 0, 0), - rgba(0, 0, 0, 1) 20%, - rgba(0, 0, 0, 1) 80%, - rgba(0, 0, 0, 0) - ); -} - -@keyframes scrollLeft { - to { - left: -200px; - } -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e934146..4185d05 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,9 +1,10 @@ +import { FavIcon } from "@/lib/types" import type { Metadata } from "next" import { Inter } from "next/font/google" -import "./globals.css" -import api from "../api" import NextHead from "next/head" -import { FavIcon } from "@/lib/types" + +import api from "../api" +import "./globals.css" const inter = Inter({ subsets: ["latin"] }) @@ -53,8 +54,8 @@ async function Head() { href={favicon.maskIcon.href} color={favicon.maskIcon.color} /> + - -
    +
    + +
    ) diff --git a/src/components/MainAvatar.tsx b/src/components/MainAvatar.tsx deleted file mode 100644 index 76531e9..0000000 --- a/src/components/MainAvatar.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Avatar, AvatarFallback } from "./ui/avatar" -import Image from "next/image" -import { useApi } from "@/lib/providers/DataProvider" -const MainAvatar = () => { - const { data } = useApi() - const initials = data.profile.attributes.name - .split(" ") - .map((n: string) => n[0]) - .join("") - - return ( - - {initials} - Profile Picture - - ) -} -export default MainAvatar diff --git a/src/components/global/CanvasAnimation.tsx b/src/components/global/CanvasAnimation.tsx new file mode 100644 index 0000000..ac90033 --- /dev/null +++ b/src/components/global/CanvasAnimation.tsx @@ -0,0 +1,141 @@ +"use client" + +import { cn } from "@/lib/utils" +import React, { useEffect, useRef } from "react" + +interface Particle { + x: number; + y: number; + vx: number; + vy: number; + color: string; + speed: number; + friction: number; +} +type Props = { + className?: string; +}; +const CanvasAnimation: React.FC = (props: Props) => { + const { className } = props + const canvasRef = useRef(null) + + useEffect(() => { + const canvas = canvasRef.current + if (!canvas) return + + const context = canvas.getContext("2d") + if (!context) return + + let width: number, height: number, mouseX: number, mouseY: number + const particles: Particle[] = [] + const maxParticles = 200 + const radius = 3 + const colors = [ + "#0066ff", + "#0099ff", + "#00ccff", + "#33ccff", + "#66ccff", + "#99ccff", + "#00ff99", + "#00ff66", + "#00ff33", + "#00ff00", + ] + + const resize = () => { + width = canvas.width = window.innerWidth + height = canvas.height = window.innerHeight + } + + const createParticles = () => { + for (let i = 0; i < maxParticles; i++) { + particles.push({ + x: Math.random() * width, + y: Math.random() * height, + vx: Math.random() * 2 - 1, + vy: Math.random() * 2 - 1, + color: colors[Math.floor(Math.random() * colors.length)], + speed: Math.random() * 0.5 + 0.5, + friction: 0.95, + }) + } + } + + const drawParticles = () => { + context.clearRect(0, 0, width, height) + particles.forEach((p) => { + context.beginPath() + context.arc(p.x, p.y, radius, 0, Math.PI * 2) + context.fillStyle = p.color + context.fill() + }) + } + + const updateParticles = () => { + particles.forEach((p) => { + p.x += p.vx * p.speed + p.y += p.vy * p.speed + + const dx = p.x - mouseX + const dy = p.y - mouseY + const distance = Math.sqrt(dx * dx + dy * dy) + const maxDistance = 100 + const forceDirectionX = dx / distance + const forceDirectionY = dy / distance + const maxForce = 0.5 + + if (distance < maxDistance) { + const force = (maxDistance - distance) / maxDistance + p.vx += forceDirectionX * force * maxForce + p.vy += forceDirectionY * force * maxForce + } + + p.vx *= p.friction + p.vy *= p.friction + + if (p.x < 0 || p.x > width) p.vx *= -1 + if (p.y < 0 || p.y > height) p.vy *= -1 + }) + } + + const animate = () => { + drawParticles() + updateParticles() + requestAnimationFrame(animate) + } + + const handleMouseMove = (event: MouseEvent) => { + mouseX = event.clientX + mouseY = event.clientY + } + + const handleTouchMove = (event: TouchEvent) => { + mouseX = event.touches[0].clientX + mouseY = event.touches[0].clientY + } + + window.addEventListener("resize", resize) + canvas.addEventListener("mousemove", handleMouseMove) + canvas.addEventListener("touchmove", handleTouchMove) + + resize() + createParticles() + animate() + + return () => { + window.removeEventListener("resize", resize) + canvas.removeEventListener("mousemove", handleMouseMove) + canvas.removeEventListener("touchmove", handleTouchMove) + } + }, []) + + return ( + + ) +} + +export default CanvasAnimation diff --git a/src/components/global/ContactLinks.tsx b/src/components/global/ContactLinks.tsx index a012c9d..6fab8e3 100644 --- a/src/components/global/ContactLinks.tsx +++ b/src/components/global/ContactLinks.tsx @@ -17,7 +17,7 @@ export default function ContactLinks(props: Props) { return ( diff --git a/src/components/global/DarkModeToggle.tsx b/src/components/global/DarkModeToggle.tsx new file mode 100644 index 0000000..c3d8774 --- /dev/null +++ b/src/components/global/DarkModeToggle.tsx @@ -0,0 +1,51 @@ +"use client" + +import { buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils" +import { useEffect, useState } from "react" + +import Icon from "./Icon" + +const DarkModeToggle = () => { + const [darkMode, setDarkMode] = useState(false) + + useEffect(() => { + const storedPreference = localStorage.getItem("darkMode") === "true" + setDarkMode(storedPreference) + }, []) + + useEffect(() => { + if (darkMode) { + document.documentElement.classList.add("dark") + localStorage.setItem("darkMode", "true") + } else { + document.documentElement.classList.remove("dark") + localStorage.setItem("darkMode", "false") + } + }, [darkMode]) + + return ( + + ) +} + +export default DarkModeToggle diff --git a/src/components/global/DateSpan.tsx b/src/components/global/DateSpan.tsx new file mode 100644 index 0000000..858ef27 --- /dev/null +++ b/src/components/global/DateSpan.tsx @@ -0,0 +1,35 @@ +type Props = { + date: { + start: string; + end: string | null; + }; +}; + +export default function DateSpan({ date }: Props) { + const renderDate = (dateProp: string | null) => { + if (!dateProp) return "Now" + + const date = new Date(dateProp) + const month = date.toLocaleString("default", { + month: "short", + timeZone: "UTC", + }) + + const year = date.toLocaleString("default", { + year: "2-digit", + timeZone: "UTC", + }) + + return `${month} '${year}` + } + + return ( +
    + + + + + +
    + ) +} diff --git a/src/components/global/Icon.tsx b/src/components/global/Icon.tsx new file mode 100644 index 0000000..7ff85c2 --- /dev/null +++ b/src/components/global/Icon.tsx @@ -0,0 +1,14 @@ +import icons from "@/lib/icons" +import { LucideProps } from "lucide-react" +import React from "react" + +const Icon: { [key: string]: React.FC } = {} + +Object.keys(icons).forEach((key) => { + Icon[key] = (props: LucideProps) => { + const IconComponent = icons[key] + return IconComponent ? : null + } +}) + +export default Icon diff --git a/src/components/global/LogoMarquee.tsx b/src/components/global/LogoMarquee.tsx index 6632561..dbc96cb 100644 --- a/src/components/global/LogoMarquee.tsx +++ b/src/components/global/LogoMarquee.tsx @@ -1,6 +1,7 @@ -import { useApi } from "@/lib/providers/DataProvider" +import { useApi } from "@/components/providers/DataProvider" import { Job } from "@/lib/types" -import { cn } from "@/lib/utils" + +import { Card, CardContent } from "../ui/card" type MarqueeProps = { itemWidth?: string; @@ -14,13 +15,13 @@ export default function LogoMarquee(props: MarqueeProps) { ) return ( -
    +
    {jobs.map((job, index) => ( -
    -
    + {`${job.company} -
    -
    + + ))}
    ) diff --git a/src/components/global/MainAvatar.tsx b/src/components/global/MainAvatar.tsx new file mode 100644 index 0000000..68ad69f --- /dev/null +++ b/src/components/global/MainAvatar.tsx @@ -0,0 +1,25 @@ +import { useApi } from "@/components/providers/DataProvider" +import { Avatar, AvatarFallback } from "@/components/ui/avatar" +import Image from "next/image" + +const MainAvatar = () => { + const { data } = useApi() + const { name, picture } = data.profile.attributes + const initials = name + .split(" ") + .map((n: string) => n[0]) + .join("") + return ( + + {initials} + {picture.alt} + + ) +} +export default MainAvatar diff --git a/src/components/global/MainHeader.tsx b/src/components/global/MainHeader.tsx new file mode 100644 index 0000000..1c4dc60 --- /dev/null +++ b/src/components/global/MainHeader.tsx @@ -0,0 +1,48 @@ +"use client" + +import { useApi } from "@/components/providers/DataProvider" +import { cn } from "@/lib/utils" +import Image from "next/image" +import type { ForwardedRef, PropsWithChildren, ReactNode } from "react" +import { forwardRef } from "react" + +type Props = PropsWithChildren & { + className?: string; +}; + +export const MainHeader = forwardRef( + (props: Props, ref: ForwardedRef): ReactNode => { + const { className } = props + const { data } = useApi() + const { logo } = data.site.attributes + + return ( +
    +

    + {logo.alt} + Joe Burdick +

    +
    +
    + {props.children} +
    +
    +
    + ) + }, +) + +MainHeader.displayName = "MainHeader" + +export default MainHeader diff --git a/src/components/global/MainNav.tsx b/src/components/global/MainNav.tsx new file mode 100644 index 0000000..ac45d8f --- /dev/null +++ b/src/components/global/MainNav.tsx @@ -0,0 +1,96 @@ +"use client" + +import { Button } from "@/components/ui/button" +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer" +import { ContactLink } from "@/lib/types" + +import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover" +import Icon from "./Icon" + +type Props = { + links: ContactLink[]; + title?: string; + description?: string; +}; + +export default function MainNav(props: Props) { + const { links: linksProp = [], title, description } = props + const renderTrigger = ( + + ) + + const Nav = () => ( + + ) + + return ( + <> +
    + + {renderTrigger} + +
    +

    {title}

    +

    {description}

    +
    +
    +
    + + {renderTrigger} + +
    + + {title} + {description} + + +
    +
    +
    +
    + + ) +} diff --git a/src/components/global/MobileNav.tsx b/src/components/global/MobileNav.tsx deleted file mode 100644 index 0c4476b..0000000 --- a/src/components/global/MobileNav.tsx +++ /dev/null @@ -1,88 +0,0 @@ -"use client" -import { buttonVariants, Button } from "@/components/ui/button" -import { - Drawer, - DrawerClose, - DrawerContent, - DrawerDescription, - DrawerFooter, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from "@/components/ui/drawer" - -import { ContactLink } from "@/lib/types" -import { cn } from "@/lib/utils" - -type Props = { - className?: string - links: ContactLink[] -} - -export default function MobileNav(props: Props) { - const { className = "", links: linksProp = [] } = props - - const links = linksProp.map(({ url, label }, index: number) => ( -
  • - - {label} - -
  • - )) - - return ( - - - - - -
    - - Let's connect - - Find me on social media or send me an email - - - - - - - - -
    -
    -
    - ) -} diff --git a/src/components/MainHeader.tsx b/src/components/global/NameHeader.tsx similarity index 60% rename from src/components/MainHeader.tsx rename to src/components/global/NameHeader.tsx index 30bb348..013f984 100644 --- a/src/components/MainHeader.tsx +++ b/src/components/global/NameHeader.tsx @@ -1,4 +1,4 @@ -import { useApi } from "@/lib/providers/DataProvider" +import { useApi } from "@/components/providers/DataProvider" const NameHeader = () => { const { data } = useApi() @@ -7,7 +7,7 @@ const NameHeader = () => { return (

    {name}

    -

    {title}

    +

    {title}

    ) } diff --git a/src/components/global/RuleHeader.tsx b/src/components/global/RuleHeader.tsx new file mode 100644 index 0000000..8f832fc --- /dev/null +++ b/src/components/global/RuleHeader.tsx @@ -0,0 +1,10 @@ +import type { PropsWithChildren } from "react" + +export default function RuleHeader(props: PropsWithChildren) { + return ( +
    + {props.children} +
    +
    + ) +} diff --git a/src/components/global/Weather.tsx b/src/components/global/Weather.tsx new file mode 100644 index 0000000..03675cf --- /dev/null +++ b/src/components/global/Weather.tsx @@ -0,0 +1,72 @@ +"use client" + +import { useEffect, useState } from "react" + +const coords = { + latitude: 40.6782, + longitude: -73.9442, +} + +const WeatherComponent: React.FC = () => { + const [temperature, setTemperature] = useState(null) + const [unit, setUnit] = useState<"fahrenheit" | "celsius">("fahrenheit") + + // Fetch the temperature unit preference from local storage + useEffect(() => { + const savedUnit = localStorage.getItem("temperatureUnit") + if (savedUnit === "celsius" || savedUnit === "fahrenheit") { + setUnit(savedUnit) + } + }, []) + + const fetchWeather = async () => { + const params = { + ...coords, + hourly: "temperature_2m", + temperature_unit: "fahrenheit", + wind_speed_unit: "mph", + } + + const queryString = new URLSearchParams(params as any).toString() + const url = `https://api.open-meteo.com/v1/forecast?${queryString}` + + try { + const response = await fetch(url) + if (!response.ok) { + throw new Error("Network response was not ok") + } + const data = await response.json() + const weatherData = data.hourly + setTemperature(weatherData.temperature_2m[0]) + } catch (error) { + console.error("Error fetching weather data:", error) + } + } + + useEffect(() => { + fetchWeather() + }, []) + + const toggleUnit = () => { + const newUnit = unit === "fahrenheit" ? "celsius" : "fahrenheit" + setUnit(newUnit) + localStorage.setItem("temperatureUnit", newUnit) // Save the preference to local storage + } + + const getDisplayText = () => { + if (unit === "fahrenheit" && temperature !== null) { + return `${Math.round(temperature)}º F in` + } else if (unit === "celsius" && temperature !== null) { + const celsius = ((temperature - 32) * 5) / 9 + return `${celsius.toFixed(2)}º C in` + } else { + return "" + } + } + + return ( + + ) +} + +export default WeatherComponent diff --git a/src/components/global/WorkAvailability.tsx b/src/components/global/WorkAvailability.tsx index 090ec01..429d645 100644 --- a/src/components/global/WorkAvailability.tsx +++ b/src/components/global/WorkAvailability.tsx @@ -1,20 +1,20 @@ import { cn } from "@/lib/utils" type Props = { - reverse?: boolean -} + reverse?: boolean; +}; export default function WorkAvailability(props: Props) { const { reverse = false } = props return (
    - - + + Available for work
    diff --git a/src/lib/providers/DataProvider.tsx b/src/components/providers/DataProvider.tsx similarity index 100% rename from src/lib/providers/DataProvider.tsx rename to src/components/providers/DataProvider.tsx diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..2c57536 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,35 @@ +import { cn } from "@/lib/utils" +import { type VariantProps, cva } from "class-variance-authority" +import * as React from "react" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
    + ) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..77e9fb7 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,76 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
    +)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
    +)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

    +)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

    +)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

    +)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
    +)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 0000000..29c7bd2 --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,33 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverAnchor = PopoverPrimitive.Anchor + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/src/lib/convertNewLinesToHTML.ts b/src/lib/convertNewLinesToHTML.ts new file mode 100644 index 0000000..0150824 --- /dev/null +++ b/src/lib/convertNewLinesToHTML.ts @@ -0,0 +1,10 @@ +function convertNewLinesToHTML(inputString: string) { + const paragraphs = inputString.split("\n") + + const htmlParagraphs = paragraphs.map((paragraph) => `

    ${paragraph}

    `) + + const htmlString = htmlParagraphs.join("") + + return htmlString +} +export default convertNewLinesToHTML diff --git a/src/lib/icons.tsx b/src/lib/icons.tsx new file mode 100644 index 0000000..9e49f92 --- /dev/null +++ b/src/lib/icons.tsx @@ -0,0 +1,63 @@ +import { + File, + Mail, + MapPin, + MoonStar, + Send, + SquareTerminal, + Sun, +} from "lucide-react" + +type IconsMap = { + [key: string]: React.FC>; +}; + +const InstagramSVG = () => ( + + + + + +) + +const LinkedInSVG = () => ( + + + + + + + +) + +const icons: IconsMap = { + send: Send, + mail: Mail, + moon: MoonStar, + sun: Sun, + linkedIn: LinkedInSVG, + github: SquareTerminal, + readCV: File, + mapPin: MapPin, + instagram: InstagramSVG, +} + +export default icons diff --git a/src/lib/types.ts b/src/lib/types.ts index 5cbd601..6fbc0c0 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,13 +1,16 @@ export type ContactLink = { url: string; label: string; + icon?: string; }; export interface Role { title: string; type: string; - start_date: string; - end_date: string | null; + date: { + start: string; + end: string | null; + }; duration: string; location?: string; description: string; @@ -28,3 +31,11 @@ export type FavIcon = { sizes: string; type?: string; }; + +export type Experience = { + company: string; + location?: string; + logo?: string; + visible?: boolean; + roles: Role[]; +}; diff --git a/tailwind.config.ts b/tailwind.config.ts index 66d2c55..6493e2f 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,13 +1,14 @@ import type { Config } from "tailwindcss" import colors from "tailwindcss/colors" -import defaultTheme from "tailwindcss/defaultTheme" + const config = { - darkMode: ["class"], + darkMode: "class", content: [ "./pages/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}", + "./lib/responsiveClasses.tsx", ], prefix: "", theme: { @@ -18,13 +19,38 @@ const config = { }, }, extend: { - fontSize: { - "heading-xs": ["clamp(1rem, 1.5vw + 0.5rem, 1.5rem)", {}], - "heading-sm": ["clamp(1.25rem, 2vw + 0.75rem, 2rem)", {}], - "heading-md": ["clamp(1.5rem, 2.5vw + 1rem, 2.5rem)", {}], - "heading-lg": ["clamp(1.75rem, 3vw + 1.25rem, 3rem)", {}], - "heading-xl": ["clamp(2rem, 3.5vw + 1.5rem, 3.5rem)", {}], - body: ["clamp(0.875rem, 1vw + 0.5rem, 1.5rem)", {}], + typography: { + scale: { + css: { + fontSize: "clamp(0.875rem, 1vw + 0.5rem, 1.5rem)", + p: { + fontSize: "clamp(0.875rem, 1vw + 0.5rem, 1.5rem)", + }, + h1: { + fontSize: "clamp(2rem, 3.5vw + 1.5rem, 3.5rem)", + fontWeight: "normal", + lineHeight: "1.25", + }, + h2: { + fontSize: "clamp(1.75rem, 3vw + 1.25rem, 3rem)", + fontWeight: "normal", + lineHeight: "1.25", + }, + h3: { + fontSize: "clamp(1.5rem, 2.5vw + 1rem, 2.5rem)", + fontWeight: "normal", + lineHeight: "1.25", + }, + h4: { + fontSize: "clamp(1.25rem, 2vw + 0.75rem, 2rem)", + lineHeight: "1.25", + }, + h5: { + fontSize: "clamp(1rem, 1.5vw + 0.5rem, 1.5rem)", + lineHeight: "1.25", + }, + }, + }, }, colors: { border: "hsl(var(--border))", @@ -68,6 +94,10 @@ const config = { sm: "calc(var(--radius) - 4px)", }, keyframes: { + fadeIn: { + "0%": { opacity: "0" }, + "100%": { opacity: "1" }, + }, "accordion-down": { from: { height: "0" }, to: { height: "var(--radix-accordion-content-height)" }, @@ -80,10 +110,12 @@ const config = { animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", + + fadeIn: "fadeIn 1s ease-in-out", }, }, }, - plugins: [require("tailwindcss-animate")], + plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")], } satisfies Config export default config