diff --git a/public/assets/images/archimedesjs.png b/public/assets/images/archimedesjs.png new file mode 100644 index 0000000..34ff2de Binary files /dev/null and b/public/assets/images/archimedesjs.png differ diff --git a/public/assets/images/blog.png b/public/assets/images/blog.png new file mode 100644 index 0000000..892b1f0 Binary files /dev/null and b/public/assets/images/blog.png differ diff --git a/src/app/globals.css b/src/app/globals.css index 18dee26..1433ef6 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -101,6 +101,10 @@ h6 { @apply text-lg; } + + .wrapper { + @apply mx-auto max-w-xl; + } } .full-width-section { @@ -111,3 +115,13 @@ margin-left: -50vw; margin-right: -50vw; } + +@screen lg { + .full-width-section { + @apply max-w-xl m-auto w-full relative left-0 right-0; + } +} + +.bleed-width-section { + @apply max-w-screen-xl m-auto; +} diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..8371f03 --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,29 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +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/core/components/hero/hero.tsx b/src/core/components/hero/hero.tsx index 65c854c..5fcac76 100644 --- a/src/core/components/hero/hero.tsx +++ b/src/core/components/hero/hero.tsx @@ -4,7 +4,7 @@ import { cn } from '@/lib/utils' export const Hero: FC> = ({ image, className, children }) => { return ( -
+
{image && ( = ({ children }) => { } const handleMouseLeave = () => { - controls.start({ opacity: 0, transition: { duration: 0.5, ease: 'easeInOut' } }) + controls.start({ opacity: 0.25, transition: { duration: 0.5, ease: 'easeInOut' } }) } return ( @@ -64,6 +64,16 @@ export const LeetBackground: FC = ({ children }) => { { + if (decoRef.current && itemRef.current) { + controls.start({ opacity: 1, transition: { duration: 0.5, ease: 'easeInOut' } }) + itemRef.current.style.setProperty('--x', `${0}px`) + itemRef.current.style.setProperty('--y', `${0}px`) + x.set(linearInterpolation(x.get(), 0, 0.1)) + y.set(linearInterpolation(y.get(), 0, 0.1)) + decoRef.current.innerHTML = getRandomString(2000) + } + }} className={cn( 'absolute top-0 left-0 h-full w-full font-mono text-xs break-words leading-4 text-white opacity-0', styles.mask, diff --git a/src/core/components/leet-card/leet-card.module.css b/src/core/components/leet-card/leet-card.module.css index c463a06..9bac0e0 100644 --- a/src/core/components/leet-card/leet-card.module.css +++ b/src/core/components/leet-card/leet-card.module.css @@ -1,9 +1,13 @@ +.radial-background { + transition: all 0.5s ease-in-out; +} + .radial-background::after { background: radial-gradient(rgb(23, 24, 37) 40%, rgb(102, 51, 238) 50%, rgb(142, 100, 255), rgb(249, 38, 114)); mix-blend-mode: darken; } .mask { - -webkit-mask-image: radial-gradient(300px circle at var(--x) var(--y), black 20%, rgba(0, 0, 0, 0.25), transparent); - mask-image: radial-gradient(300px circle at var(--x) var(--y), black 20%, rgba(0, 0, 0, 0.25), transparent); + -webkit-mask-image: radial-gradient(350px circle at var(--x) var(--y), black 20%, rgba(0, 0, 0, 0.25), transparent); + mask-image: radial-gradient(350px circle at var(--x) var(--y), black 20%, rgba(0, 0, 0, 0.25), transparent); } diff --git a/src/core/components/leet-card/leet-card.tsx b/src/core/components/leet-card/leet-card.tsx index ecaae65..a85a2af 100644 --- a/src/core/components/leet-card/leet-card.tsx +++ b/src/core/components/leet-card/leet-card.tsx @@ -3,11 +3,11 @@ import React, { type FC, type PropsWithChildren, type ReactNode } from 'react' import { LeetBackground } from '@/core/components/leet-card/leet-background' -export const LeetCard: FC> = ({ children, icon }) => { +export const LeetCard: FC> = ({ children, center }) => { return (
- {icon} + {center} {children}
diff --git a/src/core/components/markdown/markdown.tsx b/src/core/components/markdown/markdown.tsx index b09aded..26bc144 100644 --- a/src/core/components/markdown/markdown.tsx +++ b/src/core/components/markdown/markdown.tsx @@ -2,11 +2,12 @@ import type { FC } from 'react' import { Prism as SyntaxHighlighter, type SyntaxHighlighterProps } from 'react-syntax-highlighter' import dark from 'react-syntax-highlighter/dist/esm/styles/prism/synthwave84' import ReactMarkdown from 'react-markdown' +import { cn } from '@/lib/utils' -export const Markdown: FC<{ value: string }> = async ({ value }) => { +export const Markdown: FC<{ value: string; className?: string }> = async ({ value, className }) => { return ( = ({ children }) => { return (
-
{children}
+
{children}
) diff --git a/src/core/components/tilt-card/tilt-card.tsx b/src/core/components/tilt-card/tilt-card.tsx index 41e112d..d139787 100644 --- a/src/core/components/tilt-card/tilt-card.tsx +++ b/src/core/components/tilt-card/tilt-card.tsx @@ -6,7 +6,7 @@ import { MouseIcon } from 'lucide-react' import { cn } from '@/lib/utils' const RATIO = 32.5 -const ROTATION_RANGE = RATIO +const ROTATION_RANGE = 20 const HALF_ROTATION_RANGE = RATIO / 2 export const TiltCard: FC< @@ -60,12 +60,14 @@ export const TiltCard: FC< }} className={cn('relative rounded-xl bg-accent', className)} > - + > + +
10 years of experience in Frontend Development and after having deployed to production projects using React, Angular and Vue, I can help you to improve your team's skills in best practices, testing and architecture.", + "consultancy": "Consultancy", + "consultancyDescription": "I can help you to improve your team's skills in best practices, testing and architecture. I can provide consultancy in different formats, from code reviews to architecture design.", + "training": "Training", + "trainingDescription": "I will help you to improve your team's skills in best practices, testing and architecture. I can provide training in different formats, from workshops to online courses.", + "mentoring": "Mentoring", + "mentoringDescription": "I can help you to improve your skills in frontend development, architecture and testing. I can provide mentoring in different formats, from pair programming to code reviews." } }, - "article": { "title": "Articles", "shareArticle": "Share article", diff --git a/src/features/home/ui/home.page.tsx b/src/features/home/ui/home.page.tsx index 6507063..4cc714d 100644 --- a/src/features/home/ui/home.page.tsx +++ b/src/features/home/ui/home.page.tsx @@ -11,6 +11,7 @@ import { Services } from './services' import { SocialMedia } from '@/core/components/social-media/social-media' import { Hero } from '@/core/components/hero/hero' import { ScrambleList } from '@/core/components/scramble-list/scramble-list' +import { ScrambleText } from '@/core/components/scramble-text/scramble-text' export const Section: FC< PropsWithChildren<{ @@ -19,7 +20,11 @@ export const Section: FC< > = ({ children, title }) => { return (
- {title &&

{title}

} + {title && ( +

+ {title} +

+ )} {children}
) @@ -51,7 +56,7 @@ export const HomePage: FC<{ articles: Article[] }> = ({ articles }) => {
- +
@@ -63,7 +68,9 @@ export const HomePage: FC<{ articles: Article[] }> = ({ articles }) => {
- +
+ +
diff --git a/src/features/home/ui/projects.tsx b/src/features/home/ui/projects.tsx index ba713b4..0f3a9d0 100644 --- a/src/features/home/ui/projects.tsx +++ b/src/features/home/ui/projects.tsx @@ -1,6 +1,4 @@ import type { FC } from 'react' -import { Card } from '../../../core/components/card/card' -import { Link } from '../../../core/components/link/link' import { Url } from '../../../core/types/url' import { useTranslations } from 'next-intl' import Image from 'next/image' @@ -8,6 +6,8 @@ import { cn } from '@/lib/utils' import { TiltCard } from '@/core/components/tilt-card/tilt-card' import { Markdown } from '@/core/components/markdown/markdown' import { Technologies } from '@/features/home/ui/technologies' +import { Button } from '@/components/ui/button' +import Link from 'next/link' interface Project { title: string @@ -15,6 +15,7 @@ interface Project { link?: Url code: Url image: string + technologies: string[] } export const Projects: FC = () => { @@ -26,45 +27,60 @@ export const Projects: FC = () => { description: t('home.projects.katasDescription'), code: Url.fromValue('https://github.com/cesalberca/katas'), image: '/assets/images/katas.png', + technologies: ['React', 'TypeScript', 'Jest', 'TDD', 'Testing'], }, { - title: t('home.projects.whoAmITitle'), - description: t('home.projects.whoAmIDescription'), - link: Url.fromValue('https://github.com/cesalberca/who-am-i'), - code: Url.fromValue('https://who-am-i-e6a66.web.app/'), - image: '/assets/images/katas.png', + title: t('home.projects.archimedesJs'), + description: t('home.projects.archimedesJsDescription'), + link: Url.fromValue('https://archimedesfw.io/'), + code: Url.fromValue('https://github.com/archimedes-projects/archimedes-js/'), + image: '/assets/images/archimedesjs.png', + technologies: ['TypeScript', 'Jest', 'Architecture', 'Isomorphic'], }, { - title: t('home.projects.frontendReformsTitle'), - description: t('home.projects.frontendReformsDescription'), - link: Url.fromValue('https://cesalberca.github.io/reformas-de-frontaneria/'), - code: Url.fromValue('https://github.com/cesalberca/reformas-de-frontaneria'), - image: '/assets/images/katas.png', + title: t('home.projects.blog'), + description: t('home.projects.blogDescription'), + code: Url.fromValue('https://github.com/cesalberca/blog'), + image: '/assets/images/blog.png', + technologies: ['React', 'TypeScript', 'Jest', 'NextJS'], }, ] return ( -
- {projects.map((x, i) => ( -
- - {x.title} -
- {t('home.projects.live')} - {x.link?.value && ( - - {t('home.projects.code')} - - )} -
-
- -
- ))} +
+

{t('home.projects.description')}

+
+ {projects.map((x, i) => ( +
+ + {x.title} +
+ + {x.link?.value && ( + + )} +
+
+ +
+ ))} +
) } diff --git a/src/features/home/ui/services.tsx b/src/features/home/ui/services.tsx index 3a7c5f2..51836bb 100644 --- a/src/features/home/ui/services.tsx +++ b/src/features/home/ui/services.tsx @@ -1,19 +1,34 @@ -import type { FC } from 'react' +import type { FC, ReactNode } from 'react' import { useTranslations } from 'next-intl' import { LeetCard } from '@/core/components/leet-card/leet-card' -import { SearchIcon } from 'lucide-react' -import { LeetBackground } from '@/core/components/leet-card/leet-background' +import { GraduationCap, Signpost, Users } from 'lucide-react' + +const Title: FC<{ + value: string + icon: ReactNode +}> = ({ value, icon }) => ( +
+ {icon} +

{value}

+
+) export const Services: FC = () => { const t = useTranslations() return ( -
-

After 10 years of experience in Frontend Development . I can provide

-
- -

Architecture

-
+
+

{t.rich('home.services.description')}

+
+ } />}> +

{t('home.services.consultancyDescription')}

+
+ } />}> +

{t('home.services.trainingDescription')}

+
+ } />}> +

{t('home.services.mentoringDescription')}

+
) diff --git a/src/features/home/ui/technologies.tsx b/src/features/home/ui/technologies.tsx index 4f85aba..a3b70aa 100644 --- a/src/features/home/ui/technologies.tsx +++ b/src/features/home/ui/technologies.tsx @@ -1,4 +1,7 @@ import type { FC } from 'react' +import Image from 'next/image' + +// Consolidate icon imports into a single object for easier management and access import javascript from '../../../../public/assets/icons/javascript.svg' import react from '../../../../public/assets/icons/react.svg' import css from '../../../../public/assets/icons/css.svg' @@ -20,102 +23,61 @@ import nextjs from '../../../../public/assets/icons/nextjs.svg' import storybook from '../../../../public/assets/icons/storybook.svg' import typescript from '../../../../public/assets/icons/typescript.svg' import sass from '../../../../public/assets/icons/sass.svg' -import Image from 'next/image' +import { Badge } from '@/components/ui/badge' + +interface Technology { + image?: string // Optional since not all technologies have an image + label: string +} + +// Create an array of all available technologies +const allTechnologies: Technology[] = [ + { image: javascript, label: 'JavaScript' }, + { image: typescript, label: 'TypeScript' }, + { image: react, label: 'React' }, + { image: css, label: 'CSS' }, + { image: html, label: 'HTML' }, + { image: cypress, label: 'Cypress' }, + { image: playwright, label: 'Playwright' }, + { image: jest, label: 'Jest' }, + { image: angular, label: 'Angular' }, + { image: vue, label: 'Vue' }, + { image: node, label: 'Node' }, + { image: deno, label: 'Deno' }, + { image: capacitor, label: 'Capacitor' }, + { image: docker, label: 'Docker' }, + { image: intellij, label: 'Intellij' }, + { image: java, label: 'Java' }, + { image: kotlin, label: 'Kotlin' }, + { image: lit, label: 'Lit' }, + { image: nextjs, label: 'NextJS' }, + { image: storybook, label: 'Storybook' }, + { image: sass, label: 'Sass' }, +] -export const Technologies: FC = () => { - const technologies = [ - { - image: javascript, - label: 'JavaScript', - }, - { - image: typescript, - label: 'TypeScript', - }, - { - image: react, - label: 'React', - }, - { - image: css, - label: 'CSS', - }, - { - image: html, - label: 'HTML', - }, - { - image: cypress, - label: 'Cypress', - }, - { - image: playwright, - label: 'Playwright', - }, - { - image: jest, - label: 'Jest', - }, - { - image: angular, - label: 'Angular', - }, - { - image: vue, - label: 'Vue', - }, - { - image: node, - label: 'Node', - }, - { - image: deno, - label: 'Deno', - }, - { - image: capacitor, - label: 'Capacitor', - }, - { - image: docker, - label: 'Docker', - }, - { - image: intellij, - label: 'Intellij', - }, - { - image: java, - label: 'Java', - }, - { - image: kotlin, - label: 'Kotlin', - }, - { - image: lit, - label: 'Lit', - }, - { - image: nextjs, - label: 'NextJS', - }, - { - image: storybook, - label: 'Storybook', - }, - { - image: sass, - label: 'Sass', - }, - ] +export const Technologies: FC<{ technologies?: string[] }> = ({ technologies }) => { + // Determine which technologies to render + const renderedTechnologies = + technologies?.map(technology => { + // Attempt to find the matching technology + const matchedTechnology = allTechnologies.find(t => t.label.toLowerCase() === technology.toLowerCase()) + // Return either the matched technology or a placeholder with just the label + return matchedTechnology || { label: technology } + }) || allTechnologies // Default to all technologies if none specified return (
- {technologies.map(x => ( -
- {x.label} - {x.label} + {renderedTechnologies.map(tech => ( +
+ {tech.image ? ( + // Wrap the image and label in a single span to avoid repetition + + {tech.label} + {tech.label} + + ) : ( + {tech.label} + )}
))}