├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── github_banner.png └── loka_chips.svg ├── src ├── App.jsx ├── assets │ ├── GillSansUltraBold.ttf │ ├── Working.svg │ ├── balado_stand.png │ ├── balado_tilt.png │ ├── character1.png │ ├── character2.png │ ├── character3.png │ ├── character4.png │ ├── character5.png │ ├── chocolate_stand.png │ ├── chocolate_tilt.png │ ├── discussion.svg │ ├── gallery2.png │ ├── gallery3.png │ ├── gallery4.mp4 │ ├── gallery5.png │ ├── gallery6.png │ ├── gallery7.png │ ├── gallery8.png │ ├── hero_img.png │ ├── index.js │ ├── loka_chips_logo.gif │ ├── not_found.svg │ ├── original_stand.png │ ├── original_tilt.png │ ├── studying.svg │ ├── thumb&gallery1.png │ ├── thumb2.png │ ├── thumb3.png │ ├── thumb4.png │ ├── thumb5.png │ ├── thumb6.png │ ├── thumb7.png │ ├── thumb8.png │ └── uin_img.png ├── components │ ├── banner.jsx │ ├── contact.jsx │ ├── cta.jsx │ ├── footer.jsx │ ├── gallery.jsx │ ├── home.jsx │ ├── index.js │ ├── navbar.jsx │ ├── not_found.jsx │ ├── product │ │ ├── DetailProduct.jsx │ │ ├── MainProduct.jsx │ │ └── index.js │ ├── products.jsx │ ├── testimonials.jsx │ └── tips.jsx ├── constants │ └── index.js ├── custonhooks │ └── useScrollBlock.js ├── index.css ├── main.jsx └── styles.js ├── tailwind.config.js └── vite.config.js /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react-refresh/only-export-components': [ 16 | 'warn', 17 | { allowConstantExport: true }, 18 | ], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .env 27 | *.bat 28 | *.ps1 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Loka Chips Banner](./public/github_banner.png) 2 | 3 | 4 | # Loka Chips 5 | 6 | This website is an online sales platform for Loka Chips, a distinctive banana chips product from Kep. Selayar, managed by a local micro, small, and medium enterprise (MSME) in Gowa, Indonesia. 7 | 8 | Visit Site: [lokachips.netlify.app](https://lokachips.netlify.app) 9 | 10 | 11 | 12 | ## Features 13 | 14 | Not as complex as others. This website simply provides information about Loka Chips products and offers a way for customers to purchase the products through WhatsApp chat. 15 | 16 | The business owner values simplicity and accessibility. The contact form section doesn't rely on a database but is connected to Google Sheets instead. This way, the business owner can easily manage the inquiries there. 17 | 18 | 19 | ## Tech Stack 20 | 21 | The technologies used here include [React JS](https://react.dev/) as the main framework, [Tailwind](https://tailwindcss.com/) for styling, and [GSAP](https://gsap.com) for animations. ["Click to chat"](https://faq.whatsapp.com/5913398998672934) is employed for WhatsApp redirection, and [Google Apps](https://www.google.com/script/start/) Script is integrated for form submission and Google Sheets interaction. 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Loka Chips 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loka_chips", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "gsap": "^3.12.2", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-icons": "^4.10.1", 17 | "react-multi-carousel": "^2.8.4", 18 | "react-router-dom": "^5.3.4", 19 | "split-type": "^0.3.3" 20 | }, 21 | "devDependencies": { 22 | "@types/react": "^18.2.15", 23 | "@types/react-dom": "^18.2.7", 24 | "@vitejs/plugin-react": "^4.0.3", 25 | "autoprefixer": "^10.4.14", 26 | "eslint": "^8.45.0", 27 | "eslint-plugin-react": "^7.32.2", 28 | "eslint-plugin-react-hooks": "^4.6.0", 29 | "eslint-plugin-react-refresh": "^0.4.3", 30 | "postcss": "^8.4.27", 31 | "tailwindcss": "^3.3.3", 32 | "vite": "^4.4.5" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/github_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/public/github_banner.png -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom/cjs/react-router-dom" 2 | import { Navbar, Home, Product, Tips, Banner, Testimonials, Gallery, CTA, Contact, Footer, NotFound } from "./components" 3 | 4 | function App() { 5 | 6 | return ( 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
27 |
28 | ) 29 | } 30 | 31 | export default App 32 | -------------------------------------------------------------------------------- /src/assets/GillSansUltraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/GillSansUltraBold.ttf -------------------------------------------------------------------------------- /src/assets/balado_stand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/balado_stand.png -------------------------------------------------------------------------------- /src/assets/balado_tilt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/balado_tilt.png -------------------------------------------------------------------------------- /src/assets/character1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/character1.png -------------------------------------------------------------------------------- /src/assets/character2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/character2.png -------------------------------------------------------------------------------- /src/assets/character3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/character3.png -------------------------------------------------------------------------------- /src/assets/character4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/character4.png -------------------------------------------------------------------------------- /src/assets/character5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/character5.png -------------------------------------------------------------------------------- /src/assets/chocolate_stand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/chocolate_stand.png -------------------------------------------------------------------------------- /src/assets/chocolate_tilt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/chocolate_tilt.png -------------------------------------------------------------------------------- /src/assets/gallery2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/gallery2.png -------------------------------------------------------------------------------- /src/assets/gallery3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/gallery3.png -------------------------------------------------------------------------------- /src/assets/gallery4.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/gallery4.mp4 -------------------------------------------------------------------------------- /src/assets/gallery5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/gallery5.png -------------------------------------------------------------------------------- /src/assets/gallery6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/gallery6.png -------------------------------------------------------------------------------- /src/assets/gallery7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/gallery7.png -------------------------------------------------------------------------------- /src/assets/gallery8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/gallery8.png -------------------------------------------------------------------------------- /src/assets/hero_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/hero_img.png -------------------------------------------------------------------------------- /src/assets/index.js: -------------------------------------------------------------------------------- 1 | import logo from "./loka_chips_logo.gif"; 2 | import heroimg from "./hero_img.png"; 3 | import original1 from "./original_tilt.png"; 4 | import original2 from "./original_stand.png"; 5 | import Chocolate1 from "./chocolate_tilt.png"; 6 | import Chocolate2 from "./chocolate_stand.png"; 7 | import balado1 from "./balado_tilt.png"; 8 | import balado2 from "./balado_stand.png"; 9 | import enjoy1 from "./studying.svg"; 10 | import enjoy2 from './discussion.svg'; 11 | import enjoy3 from './Working.svg'; 12 | import uinimg from "./uin_img.png"; 13 | import character1 from "./character1.png" 14 | import character2 from "./character2.png" 15 | import character3 from "./character3.png" 16 | import character4 from "./character4.png" 17 | import character5 from "./character5.png" 18 | import thumbNgallery from "./thumb&gallery1.png"; 19 | import thumb2 from "./thumb2.png" 20 | import thumb3 from "./thumb3.png" 21 | import thumb4 from "./thumb4.png" 22 | import thumb5 from "./thumb5.png" 23 | import thumb6 from "./thumb6.png" 24 | import thumb7 from "./thumb7.png" 25 | import thumb8 from "./thumb8.png" 26 | import gallery2 from "./gallery2.png"; 27 | import gallery3 from "./gallery3.png"; 28 | import gallery4 from "./gallery4.mp4" 29 | import gallery5 from "./gallery5.png"; 30 | import gallery6 from "./gallery6.png"; 31 | import gallery7 from "./gallery7.png"; 32 | import gallery8 from "./gallery8.png"; 33 | import not_found from "./not_found.svg" 34 | 35 | export { 36 | logo, 37 | heroimg, 38 | original1, 39 | original2, 40 | Chocolate1, 41 | Chocolate2, 42 | balado1, 43 | balado2, 44 | enjoy1, 45 | enjoy2, 46 | enjoy3, 47 | uinimg, 48 | character1, 49 | character2, 50 | character3, 51 | character4, 52 | character5, 53 | thumbNgallery, 54 | thumb2, 55 | thumb3, 56 | thumb4, 57 | thumb5, 58 | thumb6, 59 | thumb7, 60 | thumb8, 61 | gallery2, 62 | gallery3, 63 | gallery4, 64 | gallery5, 65 | gallery6, 66 | gallery7, 67 | gallery8, 68 | not_found 69 | } -------------------------------------------------------------------------------- /src/assets/loka_chips_logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/loka_chips_logo.gif -------------------------------------------------------------------------------- /src/assets/original_stand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/original_stand.png -------------------------------------------------------------------------------- /src/assets/original_tilt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/original_tilt.png -------------------------------------------------------------------------------- /src/assets/thumb&gallery1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/thumb&gallery1.png -------------------------------------------------------------------------------- /src/assets/thumb2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/thumb2.png -------------------------------------------------------------------------------- /src/assets/thumb3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/thumb3.png -------------------------------------------------------------------------------- /src/assets/thumb4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/thumb4.png -------------------------------------------------------------------------------- /src/assets/thumb5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/thumb5.png -------------------------------------------------------------------------------- /src/assets/thumb6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/thumb6.png -------------------------------------------------------------------------------- /src/assets/thumb7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/thumb7.png -------------------------------------------------------------------------------- /src/assets/thumb8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/thumb8.png -------------------------------------------------------------------------------- /src/assets/uin_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zumaku/loka_chips/39a473bc17f4c0433c696c1caca352490537bc1c/src/assets/uin_img.png -------------------------------------------------------------------------------- /src/components/banner.jsx: -------------------------------------------------------------------------------- 1 | import { FaLocationDot } from 'react-icons/fa6' 2 | import style from "../styles" 3 | import { uinimg } from "../assets" 4 | 5 | const Banner = () => { 6 | 7 | return ( 8 |
9 |
10 |

Gratis Ongkir

11 |

12 | Nikmati Loka Chips dalam berbagai keadaan. Pengiriman 13 | gratis 14 | untuk daerah 15 | Samata 16 | dan sekitarnya. Dapatkan Loka Chips di kantin-kantin kampus UIN Alauddin Makassar. 17 |

18 |
19 | 20 |

Samata, Gowa. Indonesia

21 |
22 |
23 |
24 | ); 25 | } 26 | 27 | export default Banner; 28 | -------------------------------------------------------------------------------- /src/components/contact.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useRef } from "react" 2 | import gsap, { Power4 } from "gsap/all" 3 | 4 | import style from "../styles" 5 | 6 | const Contact = () => { 7 | const desc = useRef(null) 8 | const form = useRef(null) 9 | 10 | const [namaValue, setNamaValue] = useState("") 11 | const [emailValue, setEmailValue] = useState("") 12 | const [pesanValue, setPesanValue] = useState("") 13 | const adjustTextareaHeight = () => { 14 | const textarea = document.getElementById("pesan") 15 | textarea.style.height = "auto" 16 | textarea.style.height = textarea.scrollHeight + "px" 17 | } 18 | 19 | useEffect(() => { 20 | adjustTextareaHeight() 21 | }, [pesanValue]) 22 | 23 | useEffect(() => { 24 | gsap.to(desc.current, { 25 | y: 0, 26 | opacity: 1, 27 | ease: Power4.easeInOut, 28 | duration: 0.5, 29 | scrollTrigger: { 30 | trigger: desc.current, 31 | start: "-180px 80%", 32 | }, 33 | }) 34 | 35 | const tl = gsap.timeline({ 36 | defaults: { 37 | ease: Power4.easeIn, 38 | duration: 0.3, 39 | }, 40 | scrollTrigger: { 41 | trigger: desc.current, 42 | start: "top 90%", 43 | }, 44 | }) 45 | tl.to(form.current.querySelector("#nama"), { opacity: 1, y: 0 }) 46 | .to(form.current.querySelector("#nama"), { width: "100%" }) 47 | .to( 48 | form.current.querySelector("#email"), 49 | { opacity: 1, y: 0 }, 50 | "-=.2" 51 | ) 52 | .to(form.current.querySelector("#email"), { width: "100%" }) 53 | .to( 54 | form.current.querySelector("#pesan"), 55 | { opacity: 1, y: 0 }, 56 | "-=.4" 57 | ) 58 | .to(form.current.querySelector("#pesan"), { width: "100%" }) 59 | .to(".label", { 60 | duration: 0.8, 61 | ease: Power4.easeOut, 62 | opacity: 1, 63 | x: 0, 64 | }) 65 | }, []) 66 | 67 | const [isSending, setIsSending] = useState(false) 68 | const [isSendSuccess, setIsSendSuccess] = useState(null) 69 | const scriptURL = import.meta.env.VITE_CONTACT_FORM_SHEEDS 70 | 71 | const handleSubmit = async (e) => { 72 | e.preventDefault() 73 | setIsSending(true) 74 | 75 | try { 76 | const form = e.target 77 | const formData = new FormData(form) 78 | 79 | const response = await fetch(scriptURL, { 80 | method: "POST", 81 | body: formData, 82 | }) 83 | 84 | setIsSendSuccess(true) 85 | console.log("Success!", response) 86 | } catch (error) { 87 | setIsSendSuccess(false) 88 | console.error("Error!", error.message) 89 | } 90 | 91 | setNamaValue("") 92 | setEmailValue("") 93 | setPesanValue("") 94 | setIsSending(false) 95 | } 96 | 97 | return ( 98 |
99 |

Kontak Kami

100 |

104 | Jika Anda memiliki saran atau masukan untuk meningkatkan 105 | pengalaman dengan Loka Chips, jangan ragu untuk mengirimkan 106 | pesan. Kami sangat antusias untuk mendengar pandangan Anda dan 107 | terus mengembangkan produk kami untuk kepuasan Anda. 108 |

109 | 110 | {isSendSuccess !== null ? ( 111 |
118 |
119 |

120 | {isSendSuccess ? "Yoos!" : "Oh Noo!"} 121 |

122 |

123 | {isSendSuccess 124 | ? "Pesan Berhasil Dikirim" 125 | : "Pesan Tidak Dapat Dikirim"} 126 |

127 |
128 |
setIsSendSuccess(null)}> 129 | 135 | 136 | 137 |
138 |
139 | ) : null} 140 | 141 |
147 | 153 | setNamaValue(e.target.value)} 164 | disabled={isSending === true} 165 | required 166 | /> 167 | 168 | 174 | setEmailValue(e.target.value)} 185 | disabled={isSending === true} 186 | required 187 | /> 188 | 189 | 195 | 210 | {isSending === true ? ( 211 | 235 | ) : ( 236 | 242 | )} 243 |
244 |
245 | ) 246 | } 247 | 248 | export default Contact 249 | -------------------------------------------------------------------------------- /src/components/cta.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | import gsap from "gsap" 3 | import SplitType from 'split-type' 4 | 5 | import style from "../styles" 6 | import { getWaApi } from "../constants" 7 | 8 | const CTA = () => { 9 | 10 | useEffect(() => { 11 | const title = new SplitType("#titleCTA") 12 | gsap.to(title.chars, { 13 | duration:.1, 14 | stagger: .05, 15 | y:-5, 16 | scrollTrigger:{ 17 | trigger:"#titleCTA .char", 18 | start:"top 90%" 19 | } 20 | }) 21 | }, []) 22 | 23 | return ( 24 |
25 |

TUNGGU APA LAGI?

26 |

Jangan Tunda Nikmatnya, Pesan Loka Chips Sekarang dan Rasakan Kelezatan Luar Biasa!

27 | Klik Disini 32 |
33 | ); 34 | } 35 | 36 | export default CTA; -------------------------------------------------------------------------------- /src/components/footer.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | import { FaLocationDot } from 'react-icons/fa6' 4 | import { BiLogoGmail } from 'react-icons/bi' 5 | import { FaGlobe } from 'react-icons/fa' 6 | import { AiFillInstagram, AiOutlineWhatsApp } from 'react-icons/ai' 7 | 8 | import style from "../styles" 9 | import { logo } from "../assets" 10 | import { infos } from "../constants" 11 | import { getWaApi } from "../constants" 12 | 13 | const Footer = () => { 14 | 15 | const [currentYear] = useState(new Date().getFullYear()); 16 | const [isCopy, setIsCopy] = useState(false); 17 | 18 | const phoneNumber = '082216774837'; // Ganti dengan nomor telepon yang ingin disalin 19 | 20 | const copyToClipboard = () => { 21 | navigator.clipboard.writeText(phoneNumber) 22 | .then(() => { 23 | console.log('Nomor telepon berhasil disalin ke clipboard'); 24 | setIsCopy(true) 25 | setTimeout(() => setIsCopy(false), 2000) 26 | }) 27 | .catch(err => { 28 | console.error('Gagal menyalin nomor telepon: ', err); 29 | }); 30 | }; 31 | 32 | return ( 33 |
34 |
35 |
36 |
37 | 58 |
59 |

Hubungi Kimi di Whatsapp

60 | {isCopy && ( 61 |

Nomer tercopy!

62 | )} 63 |

67 |

68 | {setIcon(infos[infos.length - 1].id)} 69 |
70 |

71 | {infos[infos.length - 1].content} 72 |

73 |

74 | Hubungi Sekarang 79 |
80 |
81 |
82 | 83 | 84 | 85 |

© {currentYear} Loka Chips. All rights reserved.

86 |
87 |
88 |
89 | ); 90 | 91 | function setIcon(id){ 92 | switch (id) { 93 | case 'location': 94 | return ( ) 95 | case 'email': 96 | return( ) 97 | case 'website': 98 | return( ) 99 | case 'instagram': 100 | return( ) 101 | case "whatsapp": 102 | return( ) 103 | default: 104 | return false 105 | } 106 | } 107 | } 108 | 109 | export default Footer; -------------------------------------------------------------------------------- /src/components/gallery.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useRef } from 'react' 2 | import gsap, {ScrollTrigger, Power4} from 'gsap/all' 3 | import useScrollBlock from '../custonhooks/useScrollBlock' 4 | 5 | import style from '../styles' 6 | import { gallerys } from '../constants' 7 | 8 | gsap.registerPlugin(ScrollTrigger) 9 | 10 | const Gallery = () => { 11 | 12 | const [selectedImage, setSelectedImage] = useState(null) 13 | const [blockScroll, allowScroll] = useScrollBlock() 14 | 15 | const trigRef = useRef(null) 16 | 17 | 18 | useEffect(() => { 19 | const tl = gsap.timeline({ 20 | duration: .3, 21 | ease: Power4.easeOut, 22 | scrollTrigger:{ 23 | trigger: trigRef.current, 24 | start: "10px 50%" 25 | } 26 | }) 27 | 28 | gallerys.map((gallery) => { 29 | tl.to(".coverFront" + gallery.id, {x: 0}, "-=.3") 30 | }) 31 | tl.to(".coverBack", {opacity: 0}) 32 | tl.to(".coverFrontAll", {x: "100%"}, "-=.4") 33 | }, []) 34 | 35 | const openImage = (galleryId) => { 36 | setSelectedImage(galleryId) 37 | blockScroll() 38 | } 39 | 40 | const closeImage = () => { 41 | setSelectedImage(null) 42 | allowScroll() 43 | } 44 | 45 | return ( 46 |
47 |
51 |

Galeri

52 |
53 | {gallerys.map((gallery) => ( 54 |
openImage(gallery.id)} 57 | className={`${ 58 | gallery.id === 'gallery1' ? 'col-span-2 sm:col-span-2 sm:row-span-2' : '' 59 | } ${ 60 | gallery.id === 'gallery4' ? 'col-span-2 relative' : '' 61 | } group hover:cursor-pointer overflow-hidden relative`} 62 | > 63 | {gallery.id === 'gallery4' ? playIcon() : ''} 64 | {gallery.alt} 71 |
72 |
73 |
74 | ))} 75 |
76 |
77 | 78 | {/* img preview */} 79 | {selectedImage && ( 80 |
81 | {selectedImage !== "gallery4" ? 82 | gallery.id === selectedImage).preview} alt="" className="max-h-full max-w-full" /> : 83 | <> 84 | 88 | 89 | } 90 |
94 | 95 | 96 | 97 |
98 |
99 | )} 100 |
101 | ) 102 | 103 | function playIcon() { 104 | return ( 105 |
106 | 107 | 108 | 109 | 110 |
111 | ) 112 | } 113 | } 114 | 115 | export default Gallery 116 | -------------------------------------------------------------------------------- /src/components/home.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react" 2 | import gsap, {Power4, Back} from "gsap" 3 | 4 | import style from "../styles" 5 | import {heroimg} from "../assets" 6 | import {getWaApi} from "../constants" 7 | 8 | const Home = () => { 9 | 10 | const [lebarWin, setLebarWin] = useState(window.innerWidth); 11 | const scrollTl = gsap.timeline({ease: Power4.easeIn, repeat: -1, yoyo: true, delay:.3}) 12 | 13 | const handleResize = () => { 14 | setLebarWin(window.innerWidth); 15 | }; 16 | 17 | useEffect(() => { 18 | scrollTl.to(".circle", {y:35}) 19 | 20 | const homeTl = gsap.timeline({defaults:{duration: .8,ease:Power4.easeOut}}) 21 | const heroImgTl = gsap.timeline({defaults:{duration:1.5, delay:.2}}) 22 | homeTl.fromTo(".imgHero", {scale:.5, opacity:0}, {scale:1, opacity:1}) 23 | .fromTo(".homeHeading", {x:-10, opacity:0}, {x:0, opacity:1}) 24 | .fromTo(".homeParagraph", {x:-10, opacity:0}, {x:0, opacity:1}, "-=.5") 25 | .fromTo(".buttons", {y:30, opacity:0, ease:Back.easeOut}, {y:0, opacity:1}, "-=.3") 26 | heroImgTl.fromTo(".imgHero", {scale:1, y:0, duration:10, delay:5}, {scale:"1.02", y:-5, repeat:-1, yoyo:true}) 27 | 28 | // Event Listener untuk ukuran window 29 | window.addEventListener("resize", handleResize); 30 | lebarWin <= 930 ? homeTl.to(".scrollAnim", {opacity:0}) : homeTl.fromTo(".scrollAnim", {duration:.3, y:10, opacity:0}, {y:0, opacity:1}) 31 | 32 | return () => { 33 | window.removeEventListener("resize", handleResize); 34 | }; 35 | }, []) 36 | 37 | return ( 38 |
39 |
40 | 2 Loka Chips Products 41 |
42 |

Jelajahi Rasanya!

43 |

Loka Chips, Cemilan Wajib Mahasiswa

44 |

Kembangkan cita rasa dan semangatmu dengan setiap gigitan Loka Chips. Kami hadir untuk mengiringi perjalanan inspirasimu, menjadi pilihan cemil wajib bagi mahasiswa yang berani mengeksplorasi rasa.

45 |
46 | Order Sekarang 47 | Kontak Kami 48 |
49 |
50 | 51 |
52 |

Scroll

53 |
54 |
55 |
56 |
57 | ); 58 | } 59 | 60 | export default Home; -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Navbar from "./Navbar" 2 | import Home from "./Home" 3 | import Product from "./Products" 4 | import Tips from "./Tips" 5 | import Banner from "./Banner" 6 | import Testimonials from "./Testimonials" 7 | import Gallery from "./Gallery" 8 | import CTA from "./CTA" 9 | import Contact from "./Contact" 10 | import Footer from "./Footer" 11 | import NotFound from "./not_found" 12 | 13 | export { 14 | Navbar, 15 | Home, 16 | Product, 17 | Tips, 18 | Banner, 19 | Testimonials, 20 | Gallery, 21 | CTA, 22 | Contact, 23 | Footer, 24 | NotFound 25 | } -------------------------------------------------------------------------------- /src/components/navbar.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react" 2 | import gsap, {Power4} from "gsap" 3 | import { ScrollTrigger } from "gsap/ScrollTrigger" 4 | import useScrollBlock from "../custonhooks/useScrollBlock" 5 | 6 | import {logo} from "../assets" 7 | import {navLinks} from "../constants" 8 | import style from "../styles"; 9 | 10 | gsap.registerPlugin(ScrollTrigger) 11 | 12 | const Navbar = () => { 13 | 14 | const [toggle, setToggle] = useState(false) 15 | const [blockScroll, allowScroll] = useScrollBlock() 16 | const tl = gsap.timeline({ease: Power4.easeOut}) 17 | 18 | useEffect(()=>{ 19 | const showNav = gsap.to('.navContainer', { 20 | y:-100, 21 | duration:.5, 22 | ease:Power4.easeInOut 23 | }) 24 | 25 | showNav.pause() 26 | 27 | ScrollTrigger.create({ 28 | start: "top top", 29 | onUpdate: (self) => { 30 | self.direction === +1 ? showNav.play() : showNav.reverse() 31 | } 32 | }); 33 | }, []) 34 | 35 | const handleToggle = () => { 36 | if(toggle){ // Saat Navbar ditutup 37 | // X close 38 | gsap.to('.lclose1', {duration:.7, width:0}) 39 | gsap.to('.lclose2', {duration:.7, width:0}) 40 | 41 | // Humb open 42 | gsap.to('.lmenu1', {duration:1, x:0}) 43 | gsap.to('.lmenu2', {duration:.8, x:0}) 44 | gsap.to('.lmenu3', {duration:1.4, x:0}) 45 | 46 | // Nav items 47 | tl.to(".nav-item", {opacity:0, y:'20px', duration:.3}) 48 | .to('.mobileMenu', {y:'-100%', duration:.5}) 49 | 50 | allowScroll() 51 | } else{ // Saat Navbar dibuka 52 | // X open 53 | gsap.to('.lclose1', {duration:.8, width:'30.8492'}) 54 | gsap.to('.lclose2', {duration:.8, width:'30.8492'}) 55 | 56 | // humb close 57 | gsap.to('.lmenu1', {duration:.8, x:'100%'}) 58 | gsap.to('.lmenu2', {duration:.8, x:'120%'}) 59 | gsap.to('.lmenu3', {duration:.4, x:'100%'}) 60 | 61 | // Background blur 62 | tl.fromTo('.mobileMenu', {ease: Power4.easeIn}, {y:0, duration:.5}) 63 | 64 | // Nav items 65 | navLinks.map((nav)=>{ 66 | tl.fromTo("#nav-" + nav.id, {opacity:0, y:'20px'}, {duration:.3, opacity:1, y:0}) 67 | }) 68 | 69 | blockScroll() 70 | } 71 | setToggle(() => !toggle) 72 | } 73 | 74 | return ( 75 | 147 | ); 148 | 149 | } 150 | 151 | export default Navbar; -------------------------------------------------------------------------------- /src/components/not_found.jsx: -------------------------------------------------------------------------------- 1 | import style from "../styles" 2 | import { not_found } from "../assets" 3 | 4 | const NotFound = () => { 5 | return ( 6 |
7 |

Not Found!

8 |

Sorry.. Halaman ini tidak ada. Seperti halnya cintanya padamu hehe..

9 | Not Found 10 | Designed by stories / Freepik 11 | Kembali ke Home 12 |
13 | ); 14 | } 15 | 16 | export default NotFound; -------------------------------------------------------------------------------- /src/components/product/DetailProduct.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react" 2 | import gsap, {Back, Power4} from "gsap" 3 | import useScrollBlock from "../../custonhooks/useScrollBlock" 4 | 5 | import style from "../../styles" 6 | import { getWaApi } from "../../constants" 7 | 8 | const DetailProduct = (props) => { 9 | 10 | const [blockScroll, allowScroll] = useScrollBlock() 11 | 12 | const container = useRef(null) 13 | const title = useRef(null) 14 | const subTitle = useRef(null) 15 | const paragraph1 = useRef(null) 16 | const paragraph2 = useRef(null) 17 | const btn = useRef(null) 18 | const img = useRef(null) 19 | const circleTransRef = useRef(null) 20 | 21 | const detailTl = gsap.timeline({defaults:{duration:1, ease:Back.easeOut}}) 22 | 23 | useEffect(()=>{ 24 | if(props.isDetailActive){ 25 | detailTl.to(container.current, {opacity: 1, y: 0, ease: Power4.easeOut}) 26 | .to(circleTransRef.current, {scale: 1, y: 0}, "-.5") 27 | .to(circleTransRef.current, {scale: 18, duration: 1.5}) 28 | .to(container.current, {backgroundColor: "#fff"}, "-=1") 29 | .to(title.current, {opacity: 1, x: 0, ease: Power4.easeOut}, "-=1") 30 | .to(subTitle.current, {opacity: 1, ease: Power4.easeOut}) 31 | .to(img.current, {scale: 1, opacity: 1}, "-=2") 32 | .to(paragraph1.current, {y: 0, opacity: 1}, "-=1") 33 | .to(paragraph2.current, {y: 0, opacity: 1}, "-=.8") 34 | .to(btn.current, {opacity: 1, ease: Back.easeIn}) 35 | // gsap.to(container.current, {opacity: 1, duration: 1, ease: Power4.easeOut}) 36 | blockScroll() 37 | } else{ 38 | detailTl.to(container.current, {y: "100%"}) 39 | // gsap.to(container.current, {opacity: 0, duration: 1, ease: Power4.easeOut}) 40 | allowScroll() 41 | } 42 | }, [props.isDetailActive]) 43 | 44 | const setTitleColor = () => { 45 | if (props.product.id === "Chocolate") return "text-chocolate" 46 | else if (props.product.id === "balado") return "text-balado" 47 | else return "text-primary" 48 | } 49 | 50 | return( 51 |
55 |
56 |
57 |

61 | Varian 62 | {props.product.title} 66 |

67 |

{props.product.detail.desc1}

71 |

{props.product.detail.desc2}

75 |
79 | Order Sekarang 84 | 88 |
89 |
90 | 91 |
92 | 93 |
94 |
98 |
99 |
100 | ) 101 | } 102 | 103 | export default DetailProduct -------------------------------------------------------------------------------- /src/components/product/MainProduct.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from "react" 2 | import gsap, {Back, Power4} from "gsap" 3 | import { ScrollTrigger } from "gsap/ScrollTrigger" 4 | import SplitType from 'split-type' 5 | 6 | import style from "../../styles" 7 | 8 | gsap.registerPlugin(ScrollTrigger) 9 | 10 | const MainProduct = (props) => { 11 | 12 | const imgPdtRef = useRef(null) 13 | const listRef = useRef(null) 14 | const ropeRef = useRef(null) 15 | const taglineRef1 = useRef(null) 16 | const taglineRef2 = useRef(null) 17 | 18 | useEffect(() => { 19 | 20 | const title = new SplitType("#titleProduct") 21 | gsap.to(title.chars, { 22 | duration:.1, 23 | stagger: .05, 24 | y:-5, 25 | scrollTrigger:{ 26 | trigger:".char", 27 | start:"top 80%" 28 | } 29 | }) 30 | 31 | // Img1 Products annimation 32 | gsap.to(imgPdtRef.current, { 33 | duration:.5, 34 | opacity:1, 35 | y:0, 36 | ease:Back.easeOut, 37 | scrollTrigger: { 38 | trigger:imgPdtRef.current, 39 | start:"top 80%", 40 | } 41 | }) 42 | 43 | // List on the right section animation 44 | gsap.to(listRef.current, { 45 | y:0, 46 | scrollTrigger: { 47 | trigger:imgPdtRef.current, 48 | start:"120px center", 49 | } 50 | }) 51 | 52 | // Tagline Animation 53 | const tglnTl = gsap.timeline({ 54 | duration:.3, 55 | ease:Power4.easeOut, 56 | scrollTrigger:{ 57 | trigger:taglineRef1.current, 58 | start:"top 90%", 59 | } 60 | }) 61 | tglnTl.to(taglineRef1.current, {width:"100%"}) 62 | .to(taglineRef1.current, {x:"100%"}) 63 | .to(taglineRef2.current, {opacity:1}) 64 | 65 | // Rope shape animation 66 | const animateSVG = () => { 67 | const path = ropeRef.current.querySelector("path"); 68 | 69 | const length = path.getTotalLength(); 70 | path.style.strokeDasharray = length; 71 | path.style.strokeDashoffset = length; 72 | 73 | gsap.to(path, { 74 | strokeDashoffset: 0, 75 | duration: 3, 76 | ease: "power1.out", 77 | scrollTrigger: { 78 | trigger: path, 79 | start: "top 80%", 80 | end: "bottom center", 81 | scrub: true, 82 | }, 83 | }); 84 | }; 85 | 86 | animateSVG(); 87 | }, []) 88 | 89 | const prevImg = () => { 90 | props.setCurIndex((prevIndex) => (prevIndex === 0 ? 3 - 1 : prevIndex - 1)) 91 | handleShake(imgPdtRef) 92 | }; 93 | 94 | const nextImg = () => { 95 | props.setCurIndex((prevIndex) => (prevIndex === 3 - 1 ? 0 : prevIndex + 1)) 96 | handleShake(imgPdtRef) 97 | }; 98 | 99 | const handleShake = (e) => { 100 | gsap.to(e.current, { 101 | x: -15, 102 | duration: 0.05, 103 | repeat: 5, 104 | yoyo: true, 105 | onComplete: () => { 106 | gsap.set(e.current, { x: 0 }); // Reset the position after shaking 107 | }, 108 | }) 109 | } 110 | 111 | return( 112 |
113 | 114 |
115 |

Varian Produk

116 |

{props.product.title}

117 |
118 | 119 |
120 | 121 | {/* Left */} 122 |
123 |
124 |
125 |

{props.product.tagline}

126 |
127 | 131 |
132 | 133 | {/* Mid */} 134 |
135 |
139 | Loka Chips Rasa original 140 |
141 |
142 |
146 | 147 | 148 | 149 |
150 |
151 |
155 | 156 | 157 | 158 |
159 |
160 |
161 | 162 | {/* Right */} 163 |
164 |
165 | 166 | 167 | 168 |
169 |
170 |

Autentik

171 |

Renyah

172 |

Kriuk

173 |

Pedas

174 |
175 |
176 |
177 |
178 | ) 179 | } 180 | 181 | export default MainProduct -------------------------------------------------------------------------------- /src/components/product/index.js: -------------------------------------------------------------------------------- 1 | import MainProduct from "./MainProduct" 2 | import DetailProduct from "./DetailProduct" 3 | 4 | export{ 5 | MainProduct, 6 | DetailProduct 7 | } -------------------------------------------------------------------------------- /src/components/products.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, useState } from "react" 2 | import gsap, {Back} from "gsap" 3 | import { ScrollTrigger } from "gsap/ScrollTrigger" 4 | import { MainProduct, DetailProduct } from "./product/" 5 | import style from "../styles" 6 | import { produks } from "../constants" 7 | 8 | gsap.registerPlugin(ScrollTrigger) 9 | 10 | const Product = () => { 11 | const [curIndex, setCurIndex] = useState(0) 12 | const product = produks[curIndex] 13 | const [isDetailActive, setIsDetailActive] = useState(false) 14 | 15 | const halfCrRef = useRef(null) 16 | 17 | useEffect(() => { 18 | // half circle bg animation 19 | gsap.to(halfCrRef.current, { 20 | y:0, 21 | duration:1, 22 | ease:Back.easeOut, 23 | scrollTrigger:{ 24 | trigger:halfCrRef.current, 25 | start:"-80% 100%", 26 | } 27 | }) 28 | gsap.to(halfCrRef.current, { 29 | scale:1.2, 30 | scrollTrigger: { 31 | trigger: halfCrRef.current, 32 | start: "-50% 100%", 33 | scrub: true, 34 | }, 35 | }) 36 | }, []) 37 | 38 | return ( 39 |
40 |
41 |
42 | 43 |
44 | 45 |
46 | ); 47 | } 48 | 49 | export default Product; 50 | -------------------------------------------------------------------------------- /src/components/testimonials.jsx: -------------------------------------------------------------------------------- 1 | import Carousel from "react-multi-carousel" 2 | import "react-multi-carousel/lib/styles.css" 3 | 4 | import style from "../styles" 5 | import { testimonials } from "../constants" 6 | 7 | const Testimonials = () => { 8 | 9 | const responsive = { 10 | superLargeDesktop: { 11 | // the naming can be any, depends on you. 12 | breakpoint: { max: 1700, min: 1060 }, 13 | items: 1 14 | }, 15 | desktop: { 16 | breakpoint: { max: 1060, min: 930 }, 17 | items: 1 18 | }, 19 | tablet: { 20 | breakpoint: { max: 930, min: 620 }, 21 | items: 2 22 | }, 23 | mobile: { 24 | breakpoint: { max: 620, min: 0 }, 25 | items: 1 26 | } 27 | }; 28 | 29 | return ( 30 |
31 |

Apa Kata Mereka

32 | 39 | {testimonials.map((testimoni) => { 40 | return ( 41 |
45 |
46 |

{testimoni.content}

47 |

{testimoni.by}

48 |

{testimoni.an}

49 |
50 |
51 | Image Testimonials 52 |
53 |
54 | ); 55 | })} 56 |
57 |
58 | ); 59 | } 60 | 61 | export default Testimonials; -------------------------------------------------------------------------------- /src/components/tips.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react" 2 | import gsap, { Back } from "gsap" 3 | import { ScrollTrigger } from "gsap/ScrollTrigger" 4 | import style from "../styles" 5 | import { enjoy } from "../constants" 6 | 7 | gsap.registerPlugin(ScrollTrigger) 8 | 9 | const Tips = () => { 10 | 11 | const startAnimating = useRef(null) 12 | 13 | useEffect(() => { 14 | const tl = gsap.timeline({ 15 | duration: .5, 16 | ease: Back.easeOut, 17 | scrollTrigger: { 18 | trigger: startAnimating.current, 19 | start: "top 50%" 20 | } 21 | }) 22 | 23 | enjoy.map((e, index) => { 24 | tl.to("#" + e.id, {y: "0", opacity: 1}, index === 0 ? "" : "-=.3") 25 | }) 26 | }, []) 27 | 28 | return ( 29 |
30 |
31 |

Enjoy Loka Chips

35 |
36 | {enjoy.map((tips) => ( 37 |
44 | Enjoy Loka Chips 45 |

{tips.title}

46 |

{tips.content}

47 |
48 | ))} 49 |
50 |
51 |
52 | ); 53 | } 54 | 55 | export default Tips; -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | original1, original2, Chocolate1, Chocolate2, balado1, balado2, 3 | enjoy1, enjoy2, enjoy3, 4 | character1, character2, character3, character4, character5, 5 | thumbNgallery, thumb2, thumb3, thumb4, thumb5, thumb6, thumb7, thumb8, 6 | gallery2, gallery3, gallery4, gallery5, gallery6, gallery7, gallery8 7 | } from "../assets"; 8 | 9 | export const navLinks = [ 10 | { 11 | id: "home", 12 | title: "Home", 13 | link: "#" 14 | }, 15 | { 16 | id: "produk", 17 | title: "Produk", 18 | link: "#produk" 19 | }, 20 | { 21 | id: "testimoni", 22 | title: "Testimoni", 23 | link: "#testimoni" 24 | }, 25 | { 26 | id: "galeri", 27 | title: "Galeri", 28 | link: "#galeri" 29 | }, 30 | { 31 | id: "kontak", 32 | title: "Kontak", 33 | link: "#kontak" 34 | }, 35 | ]; 36 | 37 | export const produks = [ 38 | { 39 | id: "original", 40 | title: "Original", 41 | tagline: "Rasa Asli dan Autentik, Kerenyahan Pisang Sejati", 42 | img: original1, 43 | detail: { 44 | desc1: 45 | "Loka Chips Rasa Original adalah pilihan utama bagi pencinta kerupuk yang menghargai kesederhanaan dalam cita rasa. Terbuat dari pisang pilihan Selayar yang diolah dengan hati-hati, setiap kerupuk menghadirkan rasa autentik pisang yang segar dan kenyal yang sulit ditolak.", 46 | desc2: 47 | "Tanpa campuran apa pun, Loka Chips Rasa Original menghadirkan pengalaman kerupuk pisang yang murni. Nikmaati kelezatan pisang Selayar dalam bentuk yang sederhana namun memikat. Cocok untuk dinikmati di berbagai kesempatan, menjadi teman setia bagi mereka yang menginginkan cita rasa pisang yang otentik dan khas dalam setiap gigitannya.", 48 | img: original2 49 | } 50 | }, 51 | { 52 | id: "Chocolate", 53 | title: "Chocolate", 54 | tagline: "Manisnya Kelezatan dengan Sentuhan Coklat", 55 | img: Chocolate1, 56 | detail: { 57 | desc1: 58 | "Loka Chips Rasa Coklat mengajakmu untuk merasakan kelezatan manis yang memikat, menggabungkan kerupuk renyah dengan sentuhan coklat yang menggoda. Setiap gigitan adalah perpaduan sempurna antara kekenyalan pisang Selayar dan sensasi coklat lezat yang melumer di mulutmu.", 59 | desc2: 60 | "Rasakan petualangan rasa dengan Loka Chips Rasa Coklat, di mana coklat cair memeluk setiap kerupuk, menciptakan harmoni manis yang luar biasa bersama kelembutan pisang. Cocok untuk dinikmati sendiri atau dibagikan, Loka Chips Rasa Coklat membawa manisnya kelezatan dalam setiap momen. Biarkan dirimu terhanyut dalam kelezatan manis dan renyah yang tak tertandingi, dengan setiap gigitan membawamu pada perjalanan rasa yang memikat dan menggugah selera.", 61 | img: Chocolate2 62 | } 63 | }, 64 | { 65 | id: "balado", 66 | title: "Balado", 67 | tagline: "Gelora Pedas Berpadu dengan Kenikmatan Pisang", 68 | img: balado1, 69 | detail: { 70 | desc1: 71 | "Loka Chips Rasa Balado memikat dengan sensasi pedas dalam setiap kerupuk, mengajakmu pada petualangan rasa yang menggairahkan. Dibuat dengan cermat menggunakan bubuk balado pilihan, produk ini menggabungkan keunikan pisang Selayar dengan sentuhan pedas yang khas. Setiap gigitan kerupuk renyah dihiasi dengan lapisan rasa balado yang membara, menghadirkan perpaduan tak terlupakan antara kelezatan pisang dan kegilaan pedas.", 72 | desc2: 73 | "Apakah kamu pecinta pedas atau ingin mencoba yang baru, Loka Chips Rasa Balado adalah pilihan tepat. Rasakan kenikmatan pisang yang berpadu dengan gelora pedas dalam setiap gigitan, dan biarkan rasa balado ini memanjakan lidahmu dalam sebuah petualangan rasa yang menggetarkan.", 74 | img: balado2 75 | } 76 | }, 77 | ]; 78 | 79 | export const enjoy = [ 80 | { 81 | id: "enjoy1", 82 | img: enjoy1, 83 | title: "Teman Belajar", 84 | content: 85 | "Tingkatkan fokus dan kreativitasmu bersama Loka Chips. Cemilan yang cocok menemani saat belajar dan mengerjakan tugas." 86 | }, 87 | { 88 | id: "enjoy2", 89 | img: enjoy2, 90 | title: "Kumpul Bersama", 91 | content: 92 | "Jadikan saat-saat berbagi dengan orang terkasih lebih istimewa bersama Loka Chips. Nikmati kelezatan yang menggoda saat tertawa dan berbagi cerita bersama." 93 | }, 94 | { 95 | id: "enjoy3", 96 | img: enjoy3, 97 | title: "Saat Bekerja", 98 | content: 99 | "Bawa semangat pada pekerjaanmu bersama Loka Chips. Kerenyahannya membuat waktu produktifmu semakin berharga." 100 | }, 101 | ]; 102 | 103 | export const testimonials = [ 104 | { 105 | id: "testi1", 106 | by: "Indra Syarifuddin", 107 | an: "Mahasiswa Teknik Informatika", 108 | img: character1, 109 | content: 110 | "Loka Chips adalah teman setia saat sesi belajarku. Rasanya yang autentik dan renyah membantu menghilangkan kejenuhan. Dengan Loka Chips, aku merasa lebih termotivasi untuk menyelesaikan tugas-tugas kuliahku." 111 | }, 112 | { 113 | id: "testi2", 114 | by: "Prof. Anita Wardani", 115 | an: "Dosen Matematika", 116 | img: character2, 117 | content: 118 | "Sebagai dosen, saya mengapresiasi Loka Chips sebagai pilihan cemilan sehat dan lezat bagi mahasiswa. Saya sering melihat mahasiswa membawa Loka Chips saat kelas atau konsultasi, memberikan energi yang dibutuhkan." 119 | }, 120 | { 121 | id: "testi3", 122 | by: "Dara Mahdayati", 123 | an: "Mahasiswa Desain Grafis", 124 | img: character3, 125 | content: 126 | "Bersantai dengan teman-teman jadi lebih seru dengan Loka Chips. Rasanya yang unik dan praktisnya membuat kami jadi ketagihan. Bukan hanya cemilan biasa, tapi juga teman akrab di setiap momen berbagi." 127 | }, 128 | { 129 | id: "testi4", 130 | by: "Adi Kusumang", 131 | an: "Konsultan Keuangan", 132 | img: character4, 133 | content: 134 | "Loka Chips adalah pilihan sempurna untuk membantu saya tetap produktif di tengah jadwal yang padat. Rasanya yang autentik dan teksturnya yang renyah memberikan kepuasan luar biasa saat bekerja." 135 | }, 136 | { 137 | id: "testi5", 138 | by: "Rina Putri D.", 139 | an: "Pecinta Pedas", 140 | img: character5, 141 | content: 142 | "Saya seorang pecinta pedas sejati, dan Loka Chips Rasa Balado adalah jawaban atas keinginan saya. Pedasnya pas dan nikmat, dengan sentuhan pisang Selayar yang unik. Sungguh pilihan cemilan yang tepat!" 143 | }, 144 | ]; 145 | 146 | export const gallerys = [ 147 | { 148 | id: "gallery1", 149 | thumb: thumbNgallery, 150 | preview: thumbNgallery, 151 | alt: "Loka Chips Image", 152 | link: "https://www.instagram.com/keripikpisangslyr/" 153 | }, 154 | { 155 | id: "gallery2", 156 | thumb: thumb2, 157 | preview: gallery2, 158 | alt: "Loka Chips Image", 159 | link: "https://www.instagram.com/keripikpisangslyr/" 160 | }, 161 | { 162 | id: "gallery3", 163 | thumb: thumb3, 164 | preview: gallery3, 165 | alt: "Loka Chips Image", 166 | link: "https://www.instagram.com/keripikpisangslyr/" 167 | }, 168 | { 169 | id: "gallery4", 170 | thumb: thumb4, 171 | preview: gallery4, 172 | alt: "Loka Chips Image", 173 | link: "https://www.instagram.com/keripikpisangslyr/" 174 | }, 175 | { 176 | id: "gallery5", 177 | thumb: thumb5, 178 | preview: gallery5, 179 | alt: "Loka Chips Image", 180 | link: "https://www.instagram.com/keripikpisangslyr/" 181 | }, 182 | { 183 | id: "gallery6", 184 | thumb: thumb6, 185 | preview: gallery6, 186 | alt: "Loka Chips Image", 187 | link: "https://www.instagram.com/keripikpisangslyr/" 188 | }, 189 | { 190 | id: "gallery7", 191 | thumb: thumb7, 192 | preview: gallery7, 193 | alt: "Loka Chips Image", 194 | link: "https://www.instagram.com/keripikpisangslyr/" 195 | }, 196 | { 197 | id: "gallery8", 198 | thumb: thumb8, 199 | preview: gallery8, 200 | alt: "Loka Chips Image", 201 | link: "https://www.instagram.com/keripikpisangslyr/" 202 | } 203 | ]; 204 | 205 | export const infos = [ 206 | { 207 | id: "location", 208 | content: "Samata, Gowa. Indonesia.", 209 | link: "https://www.google.com/maps/place/Samata,+Somba+Opu,+Gowa+Regency,+South+Sulawesi/@-5.1962145,119.5008564,14z/data=!3m1!4b1!4m6!3m5!1s0x2dbee3f25e9a8bb9:0x139ad8e6521423fe!8m2!3d-5.1958966!4d119.5020698!16s%2Fg%2F1hc0hkl6r?entry=ttu" 210 | }, 211 | { 212 | id: "email", 213 | content: "lokachips@gmail.com", 214 | link: "", 215 | }, 216 | { 217 | id: "website", 218 | content: "lokachips.netlify.com", 219 | link: "https://lokachips.netlify.app/" 220 | }, 221 | { 222 | id: "instagram", 223 | content: "@keripikpisangskyr", 224 | link: "https://www.instagram.com/keripikpisangslyr/" 225 | }, 226 | { 227 | id: "whatsapp", 228 | content: "+62 822-1677-4837", 229 | link: "https://api.whatsapp.com/send/?phone=%2B6282216774837&text=" 230 | }, 231 | ]; 232 | 233 | const waApi = infos[infos.length - 1].link 234 | 235 | const questions = [ 236 | [ 237 | "Halo kak. Saya ingin pesan Loka Chipnya kak.", 238 | "Tabe kak. Loka Chipnya masih ada?", 239 | "Permisi kak. Saya mau pesan Loka Chips rasa ...", 240 | "Permisi kak. Aku mau order Loka Chipsnya kak", 241 | "Halo kak. Harga Loka Chipsnya berapa ya?", 242 | "Tabe kak. Pesan Loka Chips kak, mau tanya harga.", 243 | ], 244 | [ 245 | "Halo kak. Saya ingin pesan Loka Chipnya kak, yang rasa ", 246 | "Tabe kak. Loka Chipnya masih ada? Saya mau pesan yang ", 247 | "Halo kak. Harga Loka Chipsnya berapa ya? Yang rasa ", 248 | "Permisi kak. Mau pesan yang rasa ", 249 | ] 250 | ] 251 | 252 | export const getWaApi = (param = false) => { 253 | let question = ''; 254 | 255 | if (param === "original" || param === "Chocolate" || param === "balado"){ 256 | question = (questions[1][Math.floor(Math.random() * questions.length)] + param).replace(/ /g, '%20'); 257 | } else if (param === false){ 258 | question = questions[0][Math.floor(Math.random() * questions.length)].replace(/ /g, '%20'); 259 | } 260 | 261 | return waApi + question; 262 | } -------------------------------------------------------------------------------- /src/custonhooks/useScrollBlock.js: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | const safeDocument = typeof document !== 'undefined' ? document : {}; 4 | 5 | /** 6 | * Usage: 7 | * const [blockScroll, allowScroll] = useScrollBlock(); 8 | */ 9 | export default () => { 10 | const scrollBlocked = useRef(); 11 | const html = safeDocument.documentElement; 12 | const { body } = safeDocument; 13 | 14 | const blockScroll = () => { 15 | if (!body || !body.style || scrollBlocked.current) return; 16 | 17 | const scrollBarWidth = window.innerWidth - html.clientWidth; 18 | const bodyPaddingRight = 19 | parseInt(window.getComputedStyle(body).getPropertyValue("padding-right")) || 0; 20 | 21 | /** 22 | * 1. Fixes a bug in iOS and desktop Safari whereby setting 23 | * `overflow: hidden` on the html/body does not prevent scrolling. 24 | * 2. Fixes a bug in desktop Safari where `overflowY` does not prevent 25 | * scroll if an `overflow-x` style is also applied to the body. 26 | */ 27 | html.style.position = 'relative'; /* [1] */ 28 | html.style.overflow = 'hidden'; /* [2] */ 29 | body.style.position = 'relative'; /* [1] */ 30 | body.style.overflow = 'hidden'; /* [2] */ 31 | body.style.paddingRight = `${bodyPaddingRight + scrollBarWidth}px`; 32 | 33 | scrollBlocked.current = true; 34 | }; 35 | 36 | const allowScroll = () => { 37 | if (!body || !body.style || !scrollBlocked.current) return; 38 | 39 | html.style.position = ''; 40 | html.style.overflow = ''; 41 | body.style.position = ''; 42 | body.style.overflow = ''; 43 | body.style.paddingRight = ''; 44 | 45 | scrollBlocked.current = false; 46 | }; 47 | 48 | return [blockScroll, allowScroll]; 49 | }; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800;900&display=swap"); 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | @font-face { 8 | font-family: "gsub"; 9 | src: url("assets/GillSansUltraBold.ttf") format("truetype"); 10 | } 11 | 12 | @layer base{ 13 | :root { 14 | --color-primary: 255, 199, 39; 15 | --color-primary-half: 255, 199, 39, .5; 16 | --color-secondary: 52, 29, 18; 17 | --color-accent: 221, 154, 0; 18 | --color-chocolate: 128, 95, 15; 19 | --color-balado: 203, 39, 39; 20 | --color-balado-half: 203, 39, 39, .5; 21 | --color-txtcolor: 30, 30, 30; 22 | --color-bkg: 255, 255, 255; 23 | } 24 | 25 | @media (prefers-color-scheme:dark){ 26 | :root { 27 | --color-primary: 255, 199, 39; 28 | --color-primary-half: 255, 199, 39, .5; 29 | --color-secondary: 52, 29, 18; 30 | --color-accent: 221, 154, 0; 31 | --color-chocolate: 128, 95, 15; 32 | --color-balado: 203, 39, 39; 33 | --color-balado-half: 203, 39, 39, .5; 34 | --color-txtcolor: 30, 30, 30; 35 | --color-bkg: 255, 255, 255; 36 | } 37 | } 38 | } 39 | 40 | ::selection { 41 | background-color: #F0C91A; 42 | color: #1E1E1E; 43 | } 44 | 45 | .invertSelection::selection { 46 | background-color: white; 47 | color: #1E1E1E; 48 | } 49 | 50 | html { 51 | scroll-behavior: smooth; 52 | background-color: white; 53 | } 54 | 55 | .text-stroke-1 { 56 | -webkit-text-stroke: .5px #341D12; 57 | } 58 | 59 | .text-stroke-half { 60 | -webkit-text-stroke: .5px #341D12; 61 | } 62 | 63 | .text-stroke-4 { 64 | -webkit-text-stroke: 4px #341D12; 65 | } 66 | 67 | .char{ 68 | transform: translateY(100%); 69 | transition: all .5s; 70 | } 71 | 72 | .react-multiple-carousel__arrow{ 73 | background-color: #341d122c; 74 | z-index: 10; 75 | } -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /src/styles.js: -------------------------------------------------------------------------------- 1 | const style = { 2 | // Buttons 3 | btnYellow: "font-bold w-fit text-base sl:text-[18px] px-3 sl:px-8 py-3 sl:py-[18px] rounded-md bg-primary hover:drop-shadow-[0_4px_20px_rgba(240,201,26,.5)] hover:-translate-y-1 transition duration-150 ease-in", 4 | btnChocolate: "font-bold w-fit text-base sl:text-[18px] px-3 sl:px-8 py-3 sl:py-[18px] rounded-md bg-secondary text-white hover:-translate-y-1 transition duration-150 ease-in", 5 | btnWhite: "font-bold w-fit text-base sl:text-[18px] px-3 sl:px-8 py-3 sl:py-[18px] rounded-md bg-white text-txtcolor hover:-translate-y-1 transition duration-150 ease-in", 6 | btnTransparant: "font-bold w-fit text-base sl:text-[18px] px-3 sl:px-8 py-3 sl:py-[18px] rounded-md hover:bg-primary border-collapse text-secondary hover:-translate-y-1 transition duration-150 ease-in border-[3px] border-secondary", 7 | btnSending: "font-bold w-fit text-base sl:text-[18px] px-3 sl:px-8 py-3 sl:py-[18px] rounded-md bg-primary drop-shadow-[0_4px_20px_rgba(240,201,26,.5)] hover:cursor-not-allowed", 8 | 9 | heading1: "text-4xl xs:text-[55px] xs:leading-[60px] lg:text-5xl font-bold", 10 | heading2: "text-2xl sm:text-4xl font-bold", 11 | heading3: "text-lg font-bold", 12 | headingS: "font-gsub text-primary drop-shadow-[3px_3px_0_#341D12] text-stroke-4", 13 | paragraph: "font-poppins text-sm sm:text-base sl:text-lg font-normal", 14 | navbar: "text-primary font-bold drop-shadow-[1.5px_1px_0_#341D12] text-stroke-1 sm:text-stroke-half", 15 | 16 | flexCenter: "flex justify-center items-center", 17 | flexStart: "flex justify-center items-start", 18 | 19 | paddingX: "lg:px-20 px-5", 20 | 21 | marginX: "sm:mx-16 mx-6", 22 | marginY: "sm:my-16 my-6", 23 | 24 | arrow: "w-16 opacity-75 group-hover:w-[91px] group-hover:opacity-100 transition-all", 25 | 26 | input: "w-full mb-10 py-3 sm:py-5 px-5 sm:px-7 border-4 border-secondary rounded-xl focus:outline-none drop-shadow-[3px_3px_0_#341D12] focus:drop-shadow-[6px_6px_0_#341D12] transition-all", 27 | }; 28 | 29 | export default style; -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{js,jsx}"], 4 | mode: "jit", 5 | theme: { 6 | extend: { 7 | colors: { 8 | // primary: "#F0C91A", 9 | // primaryHalf: "rgba(255, 199, 39, .5)", 10 | // secondary: "#341D12", 11 | // accent: "#DD9A00", 12 | // chocolate: "#805F0F", 13 | // balado: "#CB2727", 14 | // baladoHalf: "rgb(203, 39, 39, .5)", 15 | // txtcolor: "#1E1E1E", 16 | 17 | primary: "rgb(var(--color-primary))", 18 | primaryHalf: "rgba(var(--color-primary-half))", 19 | secondary: "rgb(var(--color-secondary))", 20 | accent: "rgb(var(--color-accent)", 21 | chocolate: "rgb(var(--color-chocolate))", 22 | balado: "rgb(var(--color-balado))", 23 | baladoHalf: "rgb(var(--color-balado-half))", 24 | txtcolor: "rgb(var(--color-txtcolor))", 25 | bkg: "rgb(var(--color-bkg))", 26 | }, 27 | fontFamily: { 28 | poppins: ["Poppins", "sans-serif"], 29 | gsub: ["gsub", "sans-serif"], 30 | }, 31 | }, 32 | screens: { 33 | xs: "480px", 34 | ss: "620px", 35 | sm: "768px", 36 | sl: "930px", 37 | md: "1060px", 38 | lg: "1200px", 39 | xl: "1700px", 40 | }, 41 | }, 42 | plugins: [], 43 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | --------------------------------------------------------------------------------