updated
							
								
								
									
										25
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,25 @@ | ||||
|  | ||||
| FROM node:18-alpine | ||||
|  | ||||
| RUN mkdir -p /app | ||||
|  | ||||
| WORKDIR /app | ||||
|  | ||||
| COPY package.json ./ | ||||
|  | ||||
| RUN npm install | ||||
|  | ||||
| RUN npm install --save-dev | ||||
|  | ||||
|  | ||||
| COPY . /app | ||||
|  | ||||
| # Build the Next.js app   | ||||
| RUN npm run build   | ||||
|  | ||||
| # Expose the port the app will run on   | ||||
| EXPOSE 3000   | ||||
|  | ||||
| COPY . . | ||||
|  | ||||
| CMD ["npm", "run", "dev"] | ||||
							
								
								
									
										20
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,20 @@ | ||||
| services: | ||||
|   app: | ||||
|     build: | ||||
|       context: . | ||||
|       dockerfile: Dockerfile | ||||
|     container_name: healthy-oil | ||||
|  | ||||
|     env_file: | ||||
|       - .env | ||||
|     ports: | ||||
|       - "3005:3000" | ||||
|     volumes: | ||||
|       - .:/app | ||||
|       - /app/node_modules | ||||
|  | ||||
|      | ||||
|  | ||||
|     networks: | ||||
|       - gitea_network | ||||
|  | ||||
| @@ -2,9 +2,9 @@ | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="UTF-8" /> | ||||
|     <link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||||
|     <link rel="icon" type="image/svg+xml" href="/logo_.svg" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|     <title>Vite + React + TS</title> | ||||
|     <title>Healthy Oil</title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <div id="root"></div> | ||||
|   | ||||
							
								
								
									
										64
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						| @@ -19,7 +19,9 @@ | ||||
|         "next-themes": "^0.4.6", | ||||
|         "react": "^19.0.0", | ||||
|         "react-dom": "^19.0.0", | ||||
|         "react-facebook": "^9.0.12", | ||||
|         "react-icons": "^5.5.0", | ||||
|         "react-social-plugins": "^2.1.0", | ||||
|         "snippet": "^0.1.0" | ||||
|       }, | ||||
|       "devDependencies": { | ||||
| @@ -4897,6 +4899,18 @@ | ||||
|       "integrity": "sha512-nMoGWW2HurtuJf6XAL56FWTDCWLOTSsanrgwOyaR5Y4e3zfG5N/0cU5xWZSEU3tBxhQugRbV1xL9jb+ug7yZww==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/loose-envify": { | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", | ||||
|       "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "js-tokens": "^3.0.0 || ^4.0.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "loose-envify": "cli.js" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/lru-cache": { | ||||
|       "version": "5.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", | ||||
| @@ -5472,6 +5486,17 @@ | ||||
|         "url": "https://github.com/prettier/prettier?sponsor=1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/prop-types": { | ||||
|       "version": "15.8.1", | ||||
|       "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", | ||||
|       "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "loose-envify": "^1.4.0", | ||||
|         "object-assign": "^4.1.1", | ||||
|         "react-is": "^16.13.1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/proxy-compare": { | ||||
|       "version": "3.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz", | ||||
| @@ -5538,6 +5563,21 @@ | ||||
|         "react": "^19.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-facebook": { | ||||
|       "version": "9.0.12", | ||||
|       "resolved": "https://registry.npmjs.org/react-facebook/-/react-facebook-9.0.12.tgz", | ||||
|       "integrity": "sha512-u7L9wWEG8/UR1kj3JRvHR5SR/cBvZkC4ZCnIt4ZmXMpg4LGVJkihcYIb2/L3FOHUsNt6p9k6jpA+7Cz3zD34iA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@babel/runtime": "^7.20.6" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 6.0.0" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "react": ">= 16" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-icons": { | ||||
|       "version": "5.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", | ||||
| @@ -5563,6 +5603,30 @@ | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-social-plugins": { | ||||
|       "version": "2.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/react-social-plugins/-/react-social-plugins-2.1.0.tgz", | ||||
|       "integrity": "sha512-TiCJI8DGtMn+NjkjgH0QoGrAl3Mplg//c0/r8nF13nl8fgBzEP0QTvBR3pJnc+Klb4N5oBEeWim6jWntjW/eSg==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "prop-types": "^15.7.2", | ||||
|         "react": "^16.13.1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/react-social-plugins/node_modules/react": { | ||||
|       "version": "16.14.0", | ||||
|       "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", | ||||
|       "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "loose-envify": "^1.1.0", | ||||
|         "object-assign": "^4.1.1", | ||||
|         "prop-types": "^15.6.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/readdirp": { | ||||
|       "version": "3.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", | ||||
|   | ||||
| @@ -21,7 +21,9 @@ | ||||
|     "next-themes": "^0.4.6", | ||||
|     "react": "^19.0.0", | ||||
|     "react-dom": "^19.0.0", | ||||
|     "react-facebook": "^9.0.12", | ||||
|     "react-icons": "^5.5.0", | ||||
|     "react-social-plugins": "^2.1.0", | ||||
|     "snippet": "^0.1.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								public/images/background.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 241 KiB | 
| Before Width: | Height: | Size: 735 KiB After Width: | Height: | Size: 735 KiB | 
| Before Width: | Height: | Size: 241 KiB After Width: | Height: | Size: 241 KiB | 
| Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 253 KiB | 
| Before Width: | Height: | Size: 664 KiB After Width: | Height: | Size: 664 KiB | 
| Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 333 KiB | 
| Before Width: | Height: | Size: 339 KiB After Width: | Height: | Size: 339 KiB | 
| Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 222 KiB | 
| Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB | 
| Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB | 
| Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/images/hero2title.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 375 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/images/hk.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 74 KiB | 
| Before Width: | Height: | Size: 356 KiB After Width: | Height: | Size: 356 KiB | 
| Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB | 
| Before Width: | Height: | Size: 324 KiB After Width: | Height: | Size: 324 KiB | 
| Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 247 KiB | 
| Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB | 
| Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB | 
| Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB | 
| Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB | 
| Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB | 
| Before Width: | Height: | Size: 289 KiB After Width: | Height: | Size: 289 KiB | 
| Before Width: | Height: | Size: 774 KiB After Width: | Height: | Size: 774 KiB | 
| Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 169 KiB | 
| Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB | 
							
								
								
									
										
											BIN
										
									
								
								public/images/pp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 37 KiB | 
| Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB | 
| Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB | 
| Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB | 
| Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB | 
| Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB | 
| Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB | 
| Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB | 
| Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/images/we.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 545 KiB After Width: | Height: | Size: 545 KiB | 
							
								
								
									
										9
									
								
								public/logo_.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="1200pt" height="1200pt" version="1.1" viewBox="0 0 1200 1200"> | ||||
|  <g> | ||||
|   <path d="m780 588c0 26.508-21.492 48-48 48s-48-21.492-48-48 21.492-48 48-48 48 21.492 48 48" fill="#fd8b00"/> | ||||
|   <path d="m540 588c0 26.508-21.492 48-48 48s-48-21.492-48-48 21.492-48 48-48 48 21.492 48 48" fill="#fd8b00"/> | ||||
|   <path d="m636 780h-108c-17.148 0-32.996 9.1484-41.57 24-8.5742 14.852-8.5742 33.148 0 48 8.5742 14.852 24.422 24 41.57 24h108c17.148 0 32.996-9.1484 41.57-24 8.5742-14.852 8.5742-33.148 0-48-8.5742-14.852-24.422-24-41.57-24z" fill="#fd8b00"/> | ||||
|   <path d="m224.4 963.36c8.1055 11.711 20.992 19.219 35.176 20.496 14.184 1.2734 28.203-3.8164 38.266-13.895l87.48-87.48c14.441-14.43 22.559-34.004 22.559-54.422 0-20.414-8.1172-39.992-22.559-54.418l-87.48-87.48c-10.242-10.27-24.578-15.359-39.004-13.848-14.426 1.5156-27.391 9.4727-35.277 21.648-5.9727 9.4453-8.5156 20.656-7.207 31.754 1.3125 11.102 6.3984 21.41 14.41 29.207l73.199 73.199-72 72c-8.4297 8.0078-13.797 18.711-15.176 30.258-1.3789 11.543 1.3125 23.211 7.6133 32.98z" fill="#fd8b00"/> | ||||
|   <path d="m904.8 254.4c-13.496-18.059-32.914-30.793-54.852-35.969-21.941-5.1797-45.004-2.4688-65.148 7.6484-31.434 16.168-60.207 37.051-85.32 61.922-57.164-16.711-117.92-16.711-175.08 0-25.152-25.062-54.008-46.109-85.559-62.398-20.145-10.121-43.207-12.832-65.148-7.6523-21.938 5.1797-41.359 17.91-54.852 35.973-29.52 39.48-62.641 109.2-74.52 294.48v-0.003906c-1.0938 17.148 7.0469 33.578 21.352 43.102 14.301 9.5195 32.602 10.688 48 3.0586 15.395-7.6289 25.555-22.891 26.648-40.039 12-181.32 43.68-226.8 55.68-242.52 0 0 72 36 96 96 35.617-23.477 77.34-35.992 120-35.992s84.383 12.516 120 35.992c24-60 96-96 96-96 14.52 19.441 60 96 60 408 0 15.914-6.3203 31.176-17.574 42.426-11.25 11.254-26.512 17.574-42.426 17.574h-24c-13.586-0.066406-26.559 5.6289-35.711 15.672-9.1484 10.039-13.613 23.488-12.289 37.008 1.5117 12.129 7.4609 23.27 16.703 31.27 9.2383 8 21.117 12.293 33.336 12.051h21.961c41.375 0 81.055-16.438 110.31-45.691s45.691-68.934 45.691-110.31c0-308.52-42.359-416.4-79.199-465.6z" fill="#fd8b00"/> | ||||
|  </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										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" }} | ||||
|             > | ||||
|                 <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' | ||||
|                     width={'500px'} /> | ||||
|                         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' | ||||
|                     width={'500px'} /> | ||||
|  | ||||
|                         w={{ base: "80%", sm: "90%", md: '350px', lg: '500px' }} | ||||
|                     /> | ||||
|                 </MotionBox> | ||||
|             </Flex> | ||||
|  | ||||
|             <Stack | ||||
|                 w='100%' | ||||
|                 align={"center"} | ||||
|  | ||||
|             > | ||||
|                 <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: "100%", sm: "100%", md: '600px' }} | ||||
|                         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
									
								
							
							
						
						| @@ -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} | ||||
|   | ||||
							
								
								
									
										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
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | ||||
| interface Window { | ||||
|     FB?: { | ||||
|       XFBML: { | ||||
|         parse: (element?: Element) => void; | ||||
|       }; | ||||
|     }; | ||||
|   } | ||||