├── .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 | 
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 |
21 |
22 |
23 |
24 |
25 |
26 |
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 |
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 |
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 |
85 | gallery.id === selectedImage).preview} type="video/mp4" />
86 | Your browser does not support the video tag.
87 |
88 | >
89 | }
90 |
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 |
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 |
49 |
50 |
51 |
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 |
76 |
77 |
78 |
79 |
80 |
81 | {/* Desktop Menu */}
82 | {
83 | navLinks.map((nav, index)=>(
84 |
91 | {nav.title}
92 |
93 |
94 | ))
95 | }
96 |
97 | {/* Hamburger Menu */}
98 |
99 | {/* Toggle */}
100 |
101 |
102 | {/* Close Icon */}
103 |
104 |
105 |
106 |
107 |
108 | {/* Humb Icon */}
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | {/* Mobile Menu */}
117 | {/*Aku pikir untuk menggunakan layout ini untuk mobile navbarnya,
118 | tapi aku tidak yakin dengan user experiens nya.
119 | Soalnya, saat user mengklik humb menunya, itu di pojok kanan atas,
120 | sedangkan nav itemnya akan muncul di sudut kiri bawah.
121 | */}
122 |
145 |
146 |
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 |
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 |
props.setIsDetailActive(false)}
87 | >Kembali
88 |
89 |
90 |
91 |
92 |
93 |
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 |
props.setIsDetailActive(true)}
130 | >Lihat Detail
131 |
132 |
133 | {/* Mid */}
134 |
135 |
139 |
140 |
141 |
160 |
161 |
162 | {/* Right */}
163 |
164 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------