/* global React */
const MENU_DATA = {
Burgers: [
{ name: 'Classic', price: '95K' },
{ name: 'Cheese', price: '99K' },
{ name: 'BBQ', price: '105K' },
{ name: 'Bacon', price: '110K' },
{ name: 'Truffle Parm', price: '135K' },
{ name: 'Dbl. Fried Onion', price: '155K' },
{ name: 'Dbl. Chicken', price: '110K' },
{ name: 'Vegetarian', price: '110K' },
],
Sides: [
{ name: 'Handcut Fries', price: '40K' },
{ name: 'Onion Rings', price: '45K' },
{ name: 'Nuggets', price: '50K' },
{ name: 'BBQ Wings', price: '55K' },
],
Shakes: [
{ name: 'Nutella', price: '52K' },
{ name: 'Oreo', price: '52K' },
{ name: 'Lotus', price: '52K' },
{ name: 'Vanilla', price: '52K' },
],
Sauces: [
{ name: 'Hot Mayo ๐ถ', price: '10K' },
{ name: 'Vang Mayo', price: '15K' },
{ name: 'BBQ', price: '15K' },
{ name: 'Truffle Mayo', price: '20K' },
{ name: 'Parmesan', price: '25K' },
],
Drinks: [
{ name: 'Water', price: '20K' },
{ name: 'Orange Fanta', price: '25K' },
{ name: 'Coke / Zero', price: '25K' },
{ name: 'Beer (Bintang)', price: '35K' },
],
};
const LINKS = {
instagram: 'https://www.instagram.com/crave.indonesia/',
linktree: 'https://linktr.ee/craveindonesia',
};
const LOCATIONS = {
Ungasan: {
name: 'Crave Ungasan',
addr: 'Jalan Toyanin II, Pecatu, Bali 80362',
coords: '-8.832,115.137',
maps: 'https://maps.app.goo.gl/8ZN9KNUWxH6qwM5a6',
gofood: 'https://gofood.co.id/bali/restaurant/crave-burger-ungasan-4739ec58-a72c-4753-8837-a40ad3175687',
grab: 'https://www.grab.com/id/download/?pid=inappsharing&c=6-C6VZA8CTGLCUA6',
embedQ: 'Crave+Burger+Ungasan,+Jalan+Toyanin+II,+Pecatu,+Bali',
hours: [
['Monday', '12:00 PM โ 2:45 AM'],
['Tuesday', '6:00 PM โ 1:45 AM'],
['Wednesday', '12:00 PM โ 1:45 AM'],
['Thursday', '12:00 PM โ 1:45 AM'],
['Friday', '12:00 PM โ 2:45 AM'],
['Saturday', '12:00 PM โ 2:45 AM'],
['Sunday', '12:00 PM โ 2:45 AM'],
],
blurb: "Tucked behind the cliffs of Pecatu โ close to Single Fin and Suluban. Late-night surfer fuel.",
},
Berawa: {
name: 'Crave Berawa',
addr: 'Jalan Pantai Berawa 34, Badung, Bali 80361',
coords: '-8.660,115.140',
maps: 'https://maps.app.goo.gl/wjYUBwn5gmti5vT69',
gofood: 'https://gofood.co.id/bali/restaurant/crave-burger-berawa-kuta-utara-badung-e7dfeb3d-0b19-4c44-9794-42389c7457f2',
grab: 'https://food.grab.com/id/en/restaurant/crave-burger-berawa-delivery/6-C72KRXETAY3WUE?sourceID=20260311_102453_5A74EE991A2847D9A64A81368161159E_MEXMPS',
embedQ: 'Crave+Burger+Berawa,+Jalan+Pantai+Berawa+34,+Badung,+Bali',
hours: [
['Monday', '12:00 PM โ 2:00 AM'],
['Tuesday', '6:00 PM โ 2:00 AM'],
['Wednesday', '12:00 PM โ 2:00 AM'],
['Thursday', '12:00 PM โ 2:00 AM'],
['Friday', '12:00 PM โ 3:00 AM'],
['Saturday', '12:00 PM โ 3:00 AM'],
['Sunday', '12:00 PM โ 3:00 AM'],
],
blurb: "Right off Berawa beach โ between Finns and Atlas. Where Canggu eats after dark.",
},
};
function MenuSection({ onOpenFull }) {
const [tab, setTab] = React.useState('Burgers');
const items = MENU_DATA[tab];
return (
);
}
function LocationsSection() {
const [active, setActive] = React.useState('Ungasan');
const loc = LOCATIONS[active];
// Dark-themed Google Maps embed via CSS filter. Use coordinates with a label
// so the pin always lands at the right place (text-only queries can fail to
// resolve for less-indexed venues).
const embedSrc = `https://maps.google.com/maps?q=${loc.coords}(${encodeURIComponent(loc.name)})&t=&z=16&ie=UTF8&iwloc=&output=embed`;
return (
โ
Two Outposts โ
FIND CRAVE.
{Object.keys(LOCATIONS).map(k=>(
setActive(k)}>
{k}
))}
โ Open Now
{active==='Ungasan'?'No.001':'No.002'}
{loc.name.split(' ')[0]} {loc.name.split(' ')[1]}
{loc.addr}
{loc.blurb}
{loc.hours.map(([d,t],i)=>(
{d}
{t}
))}
);
}
function GallerySection() {
return (
Bite no mercy
House Trays
Oreo Shake
Late Night
All ages welcome
First bite
Made fresh
);
}
function StorySection() {
return (
Our Story
BORN IN BALI. BUILT TO SMASH.
Crave started with one obsession: a burger that hits like a wave at Uluwatu โ bold,
hot, impossible to ignore. So we sourced 100% imported Australian Angus, learned to
smash it on a screaming-hot grill, and built every sauce in-house from scratch.
No fluff. No pretense. Just quality smashed loud and served fast.
);
}
function OrderSection() {
return (
Get it delivered
STILL HUNGRY?
We deliver across Uluwatu and Canggu. Pick your location, then pick your app.
);
}
function Footer() {
return (
);
}
function FullMenuModal({ open, onClose }) {
if (!open) return null;
return (
e.stopPropagation()}>
โ
โ
The Full Menu โ
SMASH BURGER
{MENU_DATA.Burgers.map((it,i)=>(
{it.name}
{it.price}
))}
CUSTOM IT
{[['Fried Egg','10K'],['Crunchy Bacon','20K'],['Cheddar','15K'],['Beef Smash','75K'],['Chicken Smash','35K']].map(([n,p],i)=>(
{n}
{p}
))}
All beef burgers served with 100% imported Australian Angus.
{['Sides','Shakes','Sauces'].map(cat=>(
{cat.toUpperCase()}
{MENU_DATA[cat].map((it,i)=>(
{it.name}
{it.price}
))}
))}
Gov tax 11% not included in prices
);
}
function ContactSection() {
const [status, setStatus] = React.useState('idle'); // idle | sending | success | error
const [errorMsg, setErrorMsg] = React.useState('');
const startedAt = React.useRef(Date.now());
async function handleSubmit(e) {
e.preventDefault();
setStatus('sending');
setErrorMsg('');
const form = e.target;
const fd = new FormData(form);
const data = Object.fromEntries(fd.entries());
const p = new URLSearchParams(window.location.search);
Object.assign(data, {
utm_source: p.get('utm_source') || '',
utm_medium: p.get('utm_medium') || '',
utm_campaign: p.get('utm_campaign') || '',
utm_content: p.get('utm_content') || '',
utm_term: p.get('utm_term') || '',
_t: String(Date.now() - startedAt.current),
});
try {
const res = await fetch(window.location.origin + '/__api/contact/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const json = await res.json();
if (json.ok) { form.reset(); setStatus('success'); }
else { setStatus('error'); setErrorMsg(json.error || 'Something went wrong.'); }
} catch (err) {
setStatus('error');
setErrorMsg('Network error. Please try again.');
}
}
return (
);
}
Object.assign(window, { MenuSection, LocationsSection, GallerySection, StorySection, OrderSection, ContactSection, Footer, FullMenuModal });