This commit is contained in:
2025-03-16 13:42:53 +08:00
parent a909854e4c
commit e169585a99
52 changed files with 425 additions and 73 deletions

View File

@@ -1,7 +1,6 @@
import {
Box,
Container,
Text,
} from '@chakra-ui/react'
import Header from './components/header'
@@ -11,7 +10,8 @@ import Compare from './components/compare'
import Qa from './components/qa'
import Oil_info from './components/oil_info'
import Bestoil from './components/bestoil'
import Salespoint from './components/salespoint'
import Footer from './components/footer'
function App() {
return (
<>
@@ -34,17 +34,15 @@ function App() {
<Box>
<Bestoil />
</Box>
<Box my={8}>
<Salespoint />
</Box>
</Box>
{/* Info Section */}
{/* Footer */}
<Box bg="#7BC142" color="white" py={6} w="full">
<Box maxW="100%" px={0} mx={0} textAlign="center">
<Text mb={2}>29437810</Text>
<Box w="150px" h="40px" bg="gray.300" borderRadius="md" mx="auto" />
</Box>
</Box>
<Footer />
</Container>
</>
)

View File

@@ -1,13 +1,32 @@
import { Box, Stack, Image, Flex, useBreakpointValue, Text } from '@chakra-ui/react'
import { colors } from '../colors';
import { motion, useInView } from 'framer-motion'
import { useRef } from 'react'
// Create motion components from Chakra components
const MotionBox = motion(Box);
const MotionStack = motion(Stack);
function Bestoil() {
const cook = useBreakpointValue({
base: "/images/cook_mb.png",
sm: "/images/cook_mb.png",
md: "/images/cook_pc.png",
});
// Create refs for elements we want to animate
const bestOilRef = useRef(null);
const oilChartRef = useRef(null);
const cookRef = useRef(null);
const oilCubesRef = useRef(null);
// Check if elements are in view
const isBestOilInView = useInView(bestOilRef, { once: true });
const isOilChartInView = useInView(oilChartRef, { once: true });
const isCookInView = useInView(cookRef, { once: true });
const isOilCubesInView = useInView(oilCubesRef, { once: true });
const oilCube = [
{
bgColor: 'linear-gradient(to right,#00609E ,#008FD0,#00609E )',
@@ -35,53 +54,63 @@ function Bestoil() {
text: '低飽和脂肪,穩定性高,適合高溫烹調'
},
]
const formatText = (text: string) => {
return text.split('\n').map((line, i) => (
<Box key={i}
color={'white'}
fontSize='9px'
mt={-2}
className='font-noto-sans font-regular'
>
{line.split('\t').map((segment, j) => (
j === 0 ? segment : <Text as="span" ml={4} key={j}>{segment}</Text>
))}
{i < text.split('\n').length - 1 && <br />}
</Box>
));
};
return (
<Stack
position="relative"
w="100%"
overflow="hidden"
bgColor={colors.backgroundColor}
>
<Flex
w='100%'
justify={"center"}
align={"center"}
direction={{ base: "column", sm: "column", md: "row" }}
>
<Image src="/images/best5.png"
fit='contain'
width={'500px'} />
<Image src="/images/oilchart.png"
fit='contain'
width={'500px'} />
<MotionBox
ref={bestOilRef}
initial={{ opacity: 0, x: -50 }}
justifyItems={"center"}
animate={isBestOilInView ? { opacity: 1, x: 0 } : { opacity: 0, x: -50 }}
transition={{ duration: 0.8 }}
>
<Image src="/images/best5.png"
fit='contain'
w={{ base: "70%", sm: "80%", md: '350px', lg: '500px' }}
/>
</MotionBox>
<MotionBox
ref={oilChartRef}
initial={{ opacity: 0, x: 50 }}
animate={isOilChartInView ? { opacity: 1, x: 0 } : { opacity: 0, x: 50 }}
transition={{ duration: 0.8 }}
justifyItems={"center"}
>
<Image src="/images/oilchart.png"
fit='contain'
w={{ base: "80%", sm: "90%", md: '350px', lg: '500px' }}
/>
</MotionBox>
</Flex>
<Stack
w='100%'
align={"center"}
>
<Image src={cook}
w={{ base: "100%", sm: "100%", md: '600px' }}
fit='contain'
/>
<MotionBox
ref={cookRef}
initial={{ opacity: 0, y: 50 }}
animate={isCookInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 50 }}
transition={{ duration: 0.8 }}
justifyItems={"center"}
>
<Image src={cook}
w={{ base: "80%", sm: "80%", md: '600px' }}
fit='contain'
/>
</MotionBox>
<Text
w={{ base: "90%", sm: "90%", md: '600px' }}
@@ -92,14 +121,23 @@ function Bestoil() {
{"不同食油的脂肪酸組合各有優勢而長期使用單一油種則可能令營養失衡。營萃護心油融合5種優質食油發揮更全面的健康效益"}
</Text>
<Box width="100%" display="flex" flexDirection="column" alignItems="center" mt={5}>
<Box
width="100%"
display="flex"
flexDirection={{ base: "row", sm: "row", md: "column" }}
alignItems="center"
mt={5}
ref={oilCubesRef}
>
<Flex flexWrap="wrap" justifyContent="center" gap="15px" maxWidth="945px">
{oilCube.map((item, index) => (
<Stack
<MotionStack
key={index}
w='270px'
h='90px'
initial={{ opacity: 0, scale: 0.8 }}
animate={isOilCubesInView ? { opacity: 1, scale: 1 } : { opacity: 0, scale: 0.8 }}
transition={{ duration: 0.5, delay: index * 0.15 }}
w='300px'
h='110px'
bgImage={item.bgColor}
roundedTopLeft={'30px'}
roundedBottomRight={'30px'}
@@ -107,7 +145,6 @@ function Bestoil() {
justify="center"
>
<Text
color="white"
className='font-melle font-black'
fontSize="2xl"
@@ -116,28 +153,29 @@ function Bestoil() {
{item.title}
</Text>
<Text
color={colors.backgroundColor}
className='font-melle font-medium'
mt={-1}
>
{item.text}
</Text>
</Stack>
</MotionStack>
))}
</Flex>
</Box>
<Text
w={{ base: "90%", sm: "90%", md: '700px' }}
fontSize={'lg'}
my={5}
className='NotoSansCJKtc font-regular'
color={colors.textColor}
textAlign={"center"}>
{"選擇「營萃護心油」,助你和家人攝取理想脂肪酸比例,食得安心,護心更放心!"}
</Text>
</Stack>
</Stack>
)
}
export default Bestoil

View File

@@ -1,7 +1,6 @@
import { Box, Stack, Image, Text, Flex, SimpleGrid } from '@chakra-ui/react'
import { } from '../colors';
import { Box, Stack, Image, Flex, SimpleGrid } from '@chakra-ui/react'
import {useRef } from 'react';
import { motion, useInView } from 'framer-motion';
import { motion } from 'framer-motion';
// Create motion versions of Chakra components

43
src/components/footer.tsx Normal file
View File

@@ -0,0 +1,43 @@
import { Stack, Flex, Image, Text, } from '@chakra-ui/react';
import { colors } from '../colors';
function Footer() {
const currentYear = new Date().getFullYear();
return (
<Stack bg={colors.topBarColor} py={5} w="full"
align={'center'}
>
<Text
className='font-melle font-xbold'
fontSize={'4xl'}
color={'white'}
>
{"查詢29437810"}
</Text>
<Flex alignItems={'center'} justifyContent={'center'} direction="column">
<Image src="/images/headerlogo.png" alt="Logo" width="150px" />
<Text textAlign={'center'} marginLeft={3} marginTop={2} className="font-melle font-medium" color={'#075C39'}>
{"【積極求變 健康向前】"}
</Text>
</Flex>
<Flex
alignItems={'flex-end'}
w={{ base: '100%', sm: '100%', md: '60%', lg: '55%', xl: '55%' }}
h='60px'
>
<Text
className='font-noto-sans font-regular'
color={colors.textColor}
fontSize={{ base: 'xs', sm: 'sm', md: 'md', lg: 'md', xl: 'md' }}
mt={5}
ml={2}
>
{`Copyright © ${currentYear}合興食油(香港)有限公司 版權所有,不得轉載。`}
</Text>
</Flex>
</Stack>
);
}
export default Footer;

View File

@@ -48,7 +48,7 @@ function Hero1() {
overflow="hidden"
position="relative"
w="100%"
bgImage={"url('/images/background.png')"}
bgImage={"url('/images/background.jpg')"}
bgSize="cover"
backgroundPosition="center"
bgRepeat="no-repeat"

View File

@@ -1,6 +1,6 @@
import { Box, Stack, Image, Text, Flex, SimpleGrid } from '@chakra-ui/react'
import { colors } from '../colors';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useRef } from 'react';
import { motion, useInView, useAnimation } from 'framer-motion'
// Create motion components from Chakra UI components
@@ -12,7 +12,6 @@ const MotionText = motion(Text);
function Hero2() {
const textStackRef = useRef<HTMLDivElement>(null);
const imageStackRef = useRef<HTMLDivElement>(null);
const [textStackHeight, setTextStackHeight] = useState<number | null>(null);
// Create refs and animation controls for scroll-based animations
const containerRef = useRef<HTMLDivElement>(null);
@@ -35,8 +34,7 @@ function Hero2() {
useEffect(() => {
const updateHeight = () => {
if (textStackRef.current) {
const height = textStackRef.current.offsetHeight;
setTextStackHeight(height);
// const height = textStackRef.current.offsetHeight;
}
};
@@ -71,7 +69,7 @@ function Hero2() {
align={"center"}
>
<Image
src="/images/title1.png"
src="/images/hero2title.png"
w={{base:"420px",sm:"450px",md:"450px",lg:"350px",xl:"350px"}}
maxW={"95%"}
/>
@@ -192,5 +190,4 @@ function Hero2() {
</Box>
);
}
export default Hero2;

View File

@@ -1,7 +1,7 @@
import { Box, Stack, Image, Flex, useBreakpointValue, Text } from '@chakra-ui/react'
import { motion, useInView, useAnimation } from 'framer-motion'
import { useRef, useEffect } from 'react'
const MotionFlex = motion(Flex);
function Oil_info() {
const oilinfotitle = useBreakpointValue({
base: "/images/mboilinfotitle.png",
@@ -15,6 +15,7 @@ function Oil_info() {
md: "/images/pcoilinfogroup.png",
});
// Animation controls for the header section
const headerControls = useAnimation();
const headerRef = useRef(null);
@@ -70,11 +71,8 @@ function Oil_info() {
bgColor={'#4E8C34'}
>
<Stack>
<MotionFlex
as={motion.div}
ref={headerRef}
initial={{ opacity: 0, y: 50 }}
animate={headerControls}
<Flex
direction="column"
bgImage={"url('/images/oilinfobg.png')"}
bgSize="cover"
@@ -118,7 +116,7 @@ function Oil_info() {
</Text>
</Stack>
</Flex>
</MotionFlex>
</Flex>
</Stack>
{/* Rest of the component remains the same until the footer image */}
@@ -222,6 +220,12 @@ function Oil_info() {
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={footerControls}
style={{
display: 'flex',
justifyContent: 'center',
width: '100%'
}}
>
<Image
src={oilinfogroup}
@@ -229,8 +233,8 @@ function Oil_info() {
/>
</motion.div>
</Stack>
</Flex>
</Stack>
</Flex >
</Stack >
);
}
export default Oil_info;

View File

@@ -0,0 +1,146 @@
import { Box, Stack, Image, Text, Flex, Link } from '@chakra-ui/react'
import { colors } from '../colors';
import { useEffect, useRef } from 'react';
import { motion, useInView } from 'framer-motion'
function Salespoint() {
const shopImages = [
{
image: '/images/pp.png',
link: 'https://www.pns.hk/zh-hk/all-brands/b/112546/%E7%8D%85%E7%90%83%E5%98%9C',
},
{
image: '/images/we.png',
link: 'https://www.wellcome.com.hk/zh-hant/search?keyword=%E7%8D%85%E7%90%83%E5%98%9C&page=1',
},
{
image: '/images/hk.png',
link: 'https://www.hktvmall.com/hktv/zh/search_a?keyword=%E7%8D%85%E7%90%83%E5%98%9C',
},
]
const fbContainerRef = useRef<HTMLDivElement>(null);
const titleRef = useRef<HTMLDivElement>(null);
const isTitleInView = useInView(titleRef, { once: true });
// Load Facebook SDK and initialize the plugin
useEffect(() => {
// Add Facebook SDK if it doesn't exist
if (!document.getElementById('facebook-jssdk')) {
const script = document.createElement('script');
script.id = 'facebook-jssdk';
script.src = 'https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v18.0';
script.async = true;
script.defer = true;
document.body.appendChild(script);
}
// Parse XFBML when SDK is loaded
const handleSDKLoad = () => {
if ((window as any).FB && fbContainerRef.current) {
(window as any).FB.XFBML.parse(fbContainerRef.current);
}
};
// Check if FB is already loaded
if ((window as any).FB) {
handleSDKLoad();
} else {
// Listen for SDK load
(window as any).fbAsyncInit = handleSDKLoad;
}
return () => {
// Clean up
(window as any).fbAsyncInit = undefined;
};
}, []);
return (
<Stack
position="relative"
w="100%"
overflow="hidden"
align={'center'}
>
<motion.div
ref={titleRef}
initial={{ opacity: 0, y: 50 }}
animate={isTitleInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 50 }}
transition={{ duration: 0.7 }}
>
<Stack
w='100%'
justify={'center'}
align={'center'}
>
<Text
className='font-melle font-xbold'
color={colors.textColor}
fontSize={'4xl'}
>
</Text>
</Stack>
</motion.div>
<Flex
gap={12}
direction={{ base: 'column', sm: 'column', md: 'row' }}
justify="center"
>
{shopImages.map((image, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.2 }}
>
<Stack
w='250px'
justify={'flex-end'}
>
<Link href={image.link}>
<Image src={image.image} cursor="pointer" />
</Link>
</Stack>
</motion.div>
))}
</Flex>
{/* Facebook Page Plugin */}
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.7, delay: 0.3 }}
>
<Box
ref={fbContainerRef}
className="fb-page-container"
w="100%"
maxW="500px"
bgColor='red'
justifyItems={'center'}
my={4}
h="auto" // Add a minimum height so there's space for the plugin to render
>
<div id="fb-root"></div>
<div
className="fb-page"
data-href="https://www.facebook.com/hoehinwhiteflower/" // Use a valid Facebook page URL
data-width="600" // Increased from 500px to 600px
data-height="700" // Increased from 230px to 400px
data-hide-cover="false"
data-show-facepile="false"
data-tabs="timeline"
></div>
</Box>
</motion.div>
</Stack>
)
}
export default Salespoint;

7
src/types/global.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
interface Window {
FB?: {
XFBML: {
parse: (element?: Element) => void;
};
};
}