updated
This commit is contained in:
14
src/App.tsx
14
src/App.tsx
@@ -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>
|
||||
</>
|
||||
)
|
||||
|
@@ -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
|
||||
|
@@ -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
43
src/components/footer.tsx
Normal 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;
|
@@ -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"
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
146
src/components/salespoint.tsx
Normal file
146
src/components/salespoint.tsx
Normal 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
7
src/types/global.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
interface Window {
|
||||
FB?: {
|
||||
XFBML: {
|
||||
parse: (element?: Element) => void;
|
||||
};
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user