Refactor imports and improve layout in various components; update styles for consistency and readability.

This commit is contained in:
Craig
2025-04-10 15:04:30 +01:00
parent 55f56123ad
commit 0dc593c817
12 changed files with 116 additions and 106 deletions

View File

@@ -1,6 +1,6 @@
import { ActionIcon, useMantineColorScheme } from '@mantine/core';
import { TbSun, TbMoon } from 'react-icons/tb';
import cx from 'clsx'; import cx from 'clsx';
import { TbMoon, TbSun } from 'react-icons/tb';
import { ActionIcon, useMantineColorScheme } from '@mantine/core';
import classes from './ColorSchemeToggle.module.css'; import classes from './ColorSchemeToggle.module.css';
export function ColorSchemeToggle() { export function ColorSchemeToggle() {

View File

@@ -1,4 +1,4 @@
import { Container, Stack, Title, Text, Anchor } from '@mantine/core'; import { Anchor, Container, Stack, Text, Title } from '@mantine/core';
import classes from './Contact.module.css'; import classes from './Contact.module.css';
export function Contact() { export function Contact() {
@@ -7,15 +7,19 @@ export function Contact() {
<Stack gap="md"> <Stack gap="md">
<Title className={classes.title}>Get in Touch</Title> <Title className={classes.title}>Get in Touch</Title>
<Text className={classes.description}> <Text className={classes.description}>
If you'd like to collaborate, have any questions, or just want to say hello, feel free to reach out! If you'd like to collaborate, have any questions, or just want to say hello, feel free to
reach out!
</Text> </Text>
<Text> <Text>
Email: <Anchor href="mailto:cdmacfadyen@proton.me">cdmacfadyen@proton.me</Anchor> Email: <Anchor href="mailto:cdmacfadyen@proton.me">cdmacfadyen@proton.me</Anchor>
</Text> </Text>
<Text> <Text>
LinkedIn: <Anchor href="https://www.linkedin.com/in/craig-macfadyen-9a2041197" target="_blank">linkedin.com/in/craigmacfadyen</Anchor> LinkedIn:{' '}
<Anchor href="https://www.linkedin.com/in/craig-macfadyen-9a2041197" target="_blank">
linkedin.com/in/craigmacfadyen
</Anchor>
</Text> </Text>
</Stack> </Stack>
</Container> </Container>
); );
} }

View File

@@ -1,4 +1,4 @@
import { TbAt, TbMapPin, TbPhone, TbSun } from 'react-icons/tb'; import { TbAt, TbSun } from 'react-icons/tb';
import { Box, Stack, Text } from '@mantine/core'; import { Box, Stack, Text } from '@mantine/core';
import classes from './ContactIcons.module.css'; import classes from './ContactIcons.module.css';
@@ -25,11 +25,9 @@ function ContactIcon({ icon: Icon, title, description, ...others }: ContactIconP
); );
} }
const MOCKDATA = [ const MOCKDATA = [{ title: 'Email', description: 'hello@mantine.dev', icon: TbAt }];
{ title: 'Email', description: 'hello@mantine.dev', icon: TbAt },
];
export function ContactIconsList() { export function ContactIconsList() {
const items = MOCKDATA.map((item, index) => <ContactIcon key={index} {...item} />); const items = MOCKDATA.map((item, index) => <ContactIcon key={index} {...item} />);
return <Stack>{items}</Stack>; return <Stack>{items}</Stack>;
} }

View File

@@ -15,7 +15,7 @@
.title { .title {
font-family: font-family:
Greycliff CF, "Greycliff CF",
var(--mantine-font-family); var(--mantine-font-family);
color: var(--mantine-color-white); color: var(--mantine-color-white);
line-height: 1; line-height: 1;

View File

@@ -17,7 +17,7 @@ const social = [TbBrandInstagram, TbBrandTwitter, TbBrandYoutube];
export function ContactUs() { export function ContactUs() {
const icons = social.map((Icon, index) => ( const icons = social.map((Icon, index) => (
<ActionIcon key={index} size={28} className={classes.social} variant="transparent"> <ActionIcon key={index} size={28} className={classes.social} variant="transparent">
<Icon size={22} stroke={1.5} /> <Icon size={22} stroke="1.5" />
</ActionIcon> </ActionIcon>
)); ));
@@ -63,4 +63,4 @@ export function ContactUs() {
</SimpleGrid> </SimpleGrid>
</div> </div>
); );
} }

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Button, Text, Title, Container, Stack, Grid, Image, Center } from '@mantine/core';
import { Carousel } from '@mantine/carousel'; import { Carousel } from '@mantine/carousel';
import { Container, Grid, Image, Stack, Text, Title } from '@mantine/core';
import classes from './ExpertiseSection.module.css'; import classes from './ExpertiseSection.module.css';
interface ExpertiseSectionProps { interface ExpertiseSectionProps {
@@ -15,19 +15,23 @@ const ExpertiseSection: React.FC<ExpertiseSectionProps> = ({
title, title,
content, content,
images, images,
viewMoreLink, viewMoreLink: _viewMoreLink,
imageOnLeft = false, imageOnLeft = false,
}) => { }) => {
const slides = images.map((image) => ( const slides = images.map((image) => (
<Carousel.Slide key={image.url}> <Carousel.Slide key={image.url}>
<Image src={image.url} alt={image.alt} style={{ width: '100%', height: 'auto', objectFit: 'cover' }} /> <Image
src={image.url}
alt={image.alt}
style={{ width: '100%', height: 'auto', objectFit: 'cover' }}
/>
</Carousel.Slide> </Carousel.Slide>
)); ));
return ( return (
<Container> <Container>
<Grid className={classes.section}> <Grid className={classes.section}>
<Grid.Col order={imageOnLeft ? 2:1} span={{xs: 6, md: 8}} > <Grid.Col order={imageOnLeft ? 2 : 1} span={{ xs: 6, md: 8 }}>
<Stack> <Stack>
<Title>{title}</Title> <Title>{title}</Title>
<Text size="lg">{content}</Text> <Text size="lg">{content}</Text>
@@ -36,8 +40,16 @@ const ExpertiseSection: React.FC<ExpertiseSectionProps> = ({
</Button> */} </Button> */}
</Stack> </Stack>
</Grid.Col> </Grid.Col>
<Grid.Col order={imageOnLeft ? 1:2} span={{xs: 6, md: 4}}> <Grid.Col order={imageOnLeft ? 1 : 2} span={{ xs: 6, md: 4 }}>
<Carousel slideSize={300} align="start" slideGap="md" controlsOffset="xl" controlSize={28} loop withIndicators> <Carousel
slideSize={300}
align="start"
slideGap="md"
controlsOffset="xl"
controlSize={28}
loop
withIndicators
>
{slides} {slides}
</Carousel> </Carousel>
</Grid.Col> </Grid.Col>
@@ -46,4 +58,4 @@ const ExpertiseSection: React.FC<ExpertiseSectionProps> = ({
); );
}; };
export default ExpertiseSection; export default ExpertiseSection;

View File

@@ -2,8 +2,8 @@ import { useState } from 'react';
import { Burger, Container, Group } from '@mantine/core'; import { Burger, Container, Group } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { MantineLogo } from '@mantinex/mantine-logo'; import { MantineLogo } from '@mantinex/mantine-logo';
import classes from './HeaderSimple.module.css';
import { ColorSchemeToggle } from '../ColorSchemeToggle/ColorSchemeToggle'; import { ColorSchemeToggle } from '../ColorSchemeToggle/ColorSchemeToggle';
import classes from './HeaderSimple.module.css';
const links = [ const links = [
{ link: '/about', label: 'Features' }, { link: '/about', label: 'Features' },
@@ -44,4 +44,4 @@ export function HeaderSimple() {
</Container> </Container>
</header> </header>
); );
} }

View File

@@ -9,6 +9,7 @@
padding-top: 200px; padding-top: 200px;
padding-bottom: 120px; padding-bottom: 120px;
width: 80%; width: 80%;
@media (max-width: $mantine-breakpoint-sm) { @media (max-width: $mantine-breakpoint-sm) {
padding-bottom: 80px; padding-bottom: 80px;
padding-top: 80px; padding-top: 80px;
@@ -17,7 +18,7 @@
.title { .title {
font-family: font-family:
Greycliff CF, "Greycliff CF",
var(--mantine-font-family); var(--mantine-font-family);
font-size: 62px; font-size: 62px;
font-weight: 900; font-weight: 900;

View File

@@ -5,7 +5,7 @@ import classes from './HeroTitle.module.css';
export function HeroTitle() { export function HeroTitle() {
return ( return (
<div className={classes.wrapper}> <div className={classes.wrapper}>
<Container fluid={true} size={700} className={classes.inner}> <Container fluid size={700} className={classes.inner}>
<h1 className={classes.title}> <h1 className={classes.title}>
{' '} {' '}
<Text component="span" variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} inherit> <Text component="span" variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} inherit>
@@ -14,8 +14,9 @@ export function HeroTitle() {
</h1> </h1>
<Text className={classes.description} c="dimmed"> <Text className={classes.description} c="dimmed">
Data Scientist with experience in consulting and research. Expertise in Computer Vision and Large Language Models. Take a look at what Data Scientist with experience in consulting and research. Expertise in Computer Vision
I can do for your business and get in touch if you have any questions or you'd like to work together! and Large Language Models. Take a look at what I can do for your business and get in touch
if you have any questions or you'd like to work together!
</Text> </Text>
<Group className={classes.controls}> <Group className={classes.controls}>
@@ -42,4 +43,4 @@ export function HeroTitle() {
</Container> </Container>
</div> </div>
); );
} }

View File

@@ -1,60 +1,58 @@
import { Carousel } from '@mantine/carousel';
import { Card, Text, Avatar, Group, Stack, Title } from '@mantine/core';
import Autoplay from 'embla-carousel-autoplay'; import Autoplay from 'embla-carousel-autoplay';
import { Carousel } from '@mantine/carousel';
import { Avatar, Card, Group, Stack, Text, Title } from '@mantine/core';
import styles from './Testimonial.module.css'; import styles from './Testimonial.module.css';
const testimonials = [ const testimonials = [
{ {
text: "What set Craig apart was his ability to understand our business challenges and deliver a solution that worked for us. He didn't just build a model, he delivered a system our team could actually use.", text: "What set Craig apart was his ability to understand our business challenges and deliver a solution that worked for us. He didn't just build a model, he delivered a system our team could actually use.",
clientName: "Steven Adair", clientName: 'Steven Adair',
role: "Director", role: 'Director',
business: "Managing Utilities Limited", business: 'Managing Utilities Limited',
}, },
]; ];
function TestimonialsCarousel() { function TestimonialsCarousel() {
const autoplay = Autoplay(); const autoplay = Autoplay();
return ( return (
<> <>
<Title align="center" mb="xl"> <Title ta="center" mb="xl">
Testimonials Testimonials
</Title> </Title>
<Carousel <Carousel
slideSize={'100%'} slideSize="100%"
slideGap="md" slideGap="md"
loop loop
align="start" align="start"
plugins={[autoplay]} plugins={[autoplay]}
onMouseEnter={autoplay.stop} onMouseEnter={autoplay.stop}
onMouseLeave={autoplay.reset} onMouseLeave={autoplay.reset}
withControls withControls
> >
{testimonials.map((testimonial, index) => ( {testimonials.map((testimonial, index) => (
<Carousel.Slide key={index}> <Carousel.Slide key={index}>
<Card shadow="sm" padding="lg" radius="md" withBorder className={styles.card}> <Card shadow="sm" padding="lg" radius="md" withBorder className={styles.card}>
<Stack gap="sm"> <Stack gap="sm">
<Text size="lg">{testimonial.text}</Text> <Text size="lg">{testimonial.text}</Text>
<Group gap="sm"> <Group gap="sm">
<Avatar radius="xl"> <Avatar radius="xl">{testimonial.clientName.charAt(0)}</Avatar>
{testimonial.clientName.charAt(0)} <div>
</Avatar> <Text size="lg" fw={700}>
<div> {testimonial.clientName}
<Text size="lg" fw={700}>{testimonial.clientName}</Text> </Text>
<Text size="sm" c="dimmed"> <Text size="sm" c="dimmed">
{testimonial.role}, {testimonial.business} {testimonial.role}, {testimonial.business}
</Text> </Text>
</div> </div>
</Group> </Group>
</Stack> </Stack>
</Card> </Card>
</Carousel.Slide> </Carousel.Slide>
))} ))}
</Carousel> </Carousel>
</> </>
); );
} }
export default TestimonialsCarousel; export default TestimonialsCarousel;

View File

@@ -1,4 +1,6 @@
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import App from './App'; import App from './App';
import '@mantine/carousel/styles.css'; import '@mantine/carousel/styles.css';
ReactDOM.createRoot(document.getElementById('root')!).render(<App />); ReactDOM.createRoot(document.getElementById('root')!).render(<App />);

View File

@@ -1,13 +1,11 @@
import { HeroTitle } from '@/components/HeroTitle/HeroTitle';
import { Welcome } from '../components/Welcome/Welcome';
import { HeaderSimple } from '@/components/HeaderSimple/HeaderSimple';
import ExpertiseSection from '@/components/ExpertiseSection/ExpertiseSection';
import outputGif from '@/assets/output.gif';
import openWebUIGif from '@/assets/open-webui.gif';
import Testimonial from '@/components/Testimonial/Testimonial';
import { Container } from '@mantine/core'; import { Container } from '@mantine/core';
import { ContactUs } from '@/components/ContactUs/ContactUs'; import openWebUIGif from '@/assets/open-webui.gif';
import outputGif from '@/assets/output.gif';
import { Contact } from '@/components/ContactUs/Contact'; import { Contact } from '@/components/ContactUs/Contact';
import ExpertiseSection from '@/components/ExpertiseSection/ExpertiseSection';
import { HeaderSimple } from '@/components/HeaderSimple/HeaderSimple';
import { HeroTitle } from '@/components/HeroTitle/HeroTitle';
import Testimonial from '@/components/Testimonial/Testimonial';
export function HomePage() { export function HomePage() {
return ( return (
@@ -15,27 +13,23 @@ export function HomePage() {
<HeaderSimple /> <HeaderSimple />
<HeroTitle /> <HeroTitle />
<ExpertiseSection <ExpertiseSection
title="Computer Vision" title="Computer Vision"
content="Expertise in developing and deploying computer vision models for various applications, including object detection, image classification, and more. Ask me about state-of-the-art real-time models for your business." content="Expertise in developing and deploying computer vision models for various applications, including object detection, image classification, and more. Ask me about state-of-the-art real-time models for your business."
images={[ images={[{ url: outputGif, alt: 'Video Segmentation with SAM2.' }]}
{ url: outputGif, alt: "Video Segmentation with SAM2." }, viewMoreLink="/computer-vision"
]} imageOnLeft
viewMoreLink="/computer-vision" />
imageOnLeft={true}
/>
<ExpertiseSection <ExpertiseSection
title="Natural Language Processing" title="Natural Language Processing"
content="Building NLP models for tasks such as sentiment analysis, text classification, and language generation. If you need a chatbot for your business and care about privacy, or a simple text classification algorithm to understand your data, I can help." content="Building NLP models for tasks such as sentiment analysis, text classification, and language generation. If you need a chatbot for your business and care about privacy, or a simple text classification algorithm to understand your data, I can help."
images={[ images={[{ url: openWebUIGif, alt: 'Locally hosted LLM using oLLama and OpenWebUI.' }]}
{ url: openWebUIGif, alt: "Locally hosted LLM using oLLama and OpenWebUI." }, viewMoreLink="/nlp"
]} imageOnLeft={false}
viewMoreLink="/nlp" />
imageOnLeft={false} <Container mt={20}>
/> <Testimonial />
<Container mt={20}> </Container>
<Testimonial /> <Contact />
</Container>
<Contact />
</> </>
); );
} }