Files
web_template/web/dist/food1/index.html
Geliebte 10979351f8 feat: 默认语言改为英语 + 导航页添加i18n三语支持
- 导航页(index.html)添加完整i18n引擎,支持zh-CN/zh-TW/en
- 导航页添加语言切换按钮(EN/中/繁)
- 所有20个前端模板默认语言从zh-CN改为en
- 导航页SEO meta标签改为英文
2026-09-09 19:48:45 +08:00

1362 lines
75 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title data-i18n="site_title">美味达 · 外卖平台</title>
<style>
:root{
--orange:#FF6B35;
--orange-dark:#F0521B;
--orange-light:#FF8455;
--yellow:#FFD23F;
--yellow-light:#FFE57A;
--orange-soft:#FFF1EA;
--bg:#F7F8FA;
--white:#FFFFFF;
--text:#1F2329;
--text-sub:#646A73;
--text-light:#8F959E;
--border:#EFF0F1;
--shadow-sm:0 2px 8px rgba(255,107,53,.06);
--shadow:0 6px 20px rgba(255,107,53,.10);
--shadow-lg:0 12px 40px rgba(255,107,53,.16);
--radius:16px;
--radius-sm:10px;
--radius-lg:24px;
--maxw:1200px;
--grad-orange:linear-gradient(135deg,#FF6B35 0%,#FF9A3D 100%);
--grad-yellow:linear-gradient(135deg,#FFD23F 0%,#FFB323 100%);
--grad-hero:linear-gradient(135deg,#FF6B35 0%,#FF9A3D 45%,#FFD23F 100%);
--grad-cart:linear-gradient(135deg,#FF6B35 0%,#FF8455 100%);
}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei","Helvetica Neue",Arial,sans-serif;background:var(--bg);color:var(--text);font-size:14px;line-height:1.6;-webkit-font-smoothing:antialiased}
img{display:block;max-width:100%}
button{font-family:inherit;cursor:pointer;border:none;background:none;outline:none}
input{font-family:inherit;outline:none;border:none}
a{color:inherit;text-decoration:none}
ul{list-style:none}
.container{max-width:var(--maxw);margin:0 auto;padding:0 20px}
.hidden{display:none!important}
::-webkit-scrollbar{width:6px;height:6px}
::-webkit-scrollbar-thumb{background:#ddd;border-radius:3px}
::-webkit-scrollbar-thumb:hover{background:#ccc}
.navbar{position:sticky;top:0;z-index:100;background:var(--white);box-shadow:0 1px 0 rgba(0,0,0,.04)}
.navbar-inner{display:flex;align-items:center;gap:20px;height:64px}
.logo{display:flex;align-items:center;gap:8px;flex-shrink:0;cursor:pointer}
.logo-icon{width:38px;height:38px;border-radius:11px;background:var(--grad-orange);display:flex;align-items:center;justify-content:center;box-shadow:0 4px 10px rgba(255,107,53,.35)}
.logo-icon svg{width:22px;height:22px;fill:#fff}
.logo-text{font-size:20px;font-weight:800;color:var(--orange);letter-spacing:.5px}
.nav-search{flex:1;max-width:480px;position:relative}
.nav-search input{width:100%;height:40px;padding:0 44px 0 18px;border-radius:20px;background:var(--orange-soft);font-size:14px;color:var(--text);transition:.2s}
.nav-search input::placeholder{color:#C9A99A}
.nav-search input:focus{background:#FFE9DF;box-shadow:0 0 0 3px rgba(255,107,53,.12)}
.nav-search .s-icon{position:absolute;right:14px;top:50%;transform:translateY(-50%);width:18px;height:18px;color:var(--orange);pointer-events:none}
.nav-right{display:flex;align-items:center;gap:18px;margin-left:auto}
.nav-loc{display:flex;align-items:center;gap:5px;color:var(--text-sub);font-size:13px;font-weight:600}
.nav-loc svg{width:15px;height:15px;color:var(--orange);fill:var(--orange)}
.nav-cart{position:relative;display:flex;align-items:center;justify-content:center;width:42px;height:42px;border-radius:12px;background:var(--orange-soft);cursor:pointer;transition:.2s}
.nav-cart:hover{background:#FFE0D2}
.nav-cart svg{width:22px;height:22px;color:var(--orange)}
.cart-badge{position:absolute;top:-4px;right:-4px;min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:var(--orange);color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;border:2px solid #fff;line-height:1}
.cart-badge.show{display:flex}
.cart-badge:not(.show){display:none}
.lang-switcher{position:relative;margin-left:8px}
.lang-btn{
display:flex;align-items:center;gap:6px;padding:8px 14px;border-radius:10px;
font-size:13px;font-weight:600;color:var(--text-sub);cursor:pointer;
background:var(--orange-soft);border:1px solid var(--border);
transition:all .25s ease;
}
.lang-btn:hover{color:var(--orange);background:#FFE0D2;border-color:var(--orange)}
.lang-btn svg{width:16px;height:16px}
.lang-dropdown{
position:absolute;top:calc(100% + 8px);right:0;min-width:160px;
background:var(--white);border:1px solid var(--border);border-radius:var(--radius-sm);
box-shadow:0 12px 40px rgba(0,0,0,.15);overflow:hidden;
opacity:0;transform:translateY(-8px);pointer-events:none;
transition:all .25s ease;z-index:1001;
}
.lang-dropdown.open{opacity:1;transform:none;pointer-events:auto}
.lang-option{
display:flex;align-items:center;gap:10px;padding:12px 16px;
font-size:14px;color:var(--text-sub);cursor:pointer;transition:all .2s;
}
.lang-option:hover{background:var(--orange-soft);color:var(--orange)}
.lang-option.active{color:var(--orange);font-weight:600}
.lang-option .check{width:16px;height:16px;opacity:0;transition:opacity .2s}
.lang-option.active .check{opacity:1}
.hero{background:var(--grad-hero);position:relative;overflow:hidden;padding:48px 0 56px}
.hero::before{content:"";position:absolute;top:-60px;right:-40px;width:280px;height:280px;border-radius:50%;background:rgba(255,255,255,.12)}
.hero::after{content:"";position:absolute;bottom:-80px;left:-50px;width:220px;height:220px;border-radius:50%;background:rgba(255,255,255,.10)}
.hero-inner{position:relative;z-index:2;text-align:center}
.hero h1{color:#fff;font-size:38px;font-weight:800;letter-spacing:1px;text-shadow:0 2px 8px rgba(0,0,0,.12);margin-bottom:8px}
.hero p{color:rgba(255,255,255,.92);font-size:16px;margin-bottom:24px;font-weight:500}
.hero-search{max-width:560px;margin:0 auto;display:flex;background:#fff;border-radius:28px;padding:6px;box-shadow:0 10px 30px rgba(0,0,0,.15)}
.hero-search input{flex:1;height:44px;padding:0 20px;font-size:15px;color:var(--text)}
.hero-search input::placeholder{color:#bbb}
.hero-search button{height:44px;padding:0 30px;border-radius:24px;background:var(--grad-orange);color:#fff;font-size:15px;font-weight:700;transition:.2s;box-shadow:0 4px 12px rgba(255,107,53,.4)}
.hero-search button:hover{transform:translateY(-1px);box-shadow:0 6px 16px rgba(255,107,53,.5)}
.hero-tags{display:flex;justify-content:center;gap:10px;margin-top:18px;flex-wrap:wrap}
.hero-tag{padding:5px 14px;border-radius:14px;background:rgba(255,255,255,.22);color:#fff;font-size:12px;font-weight:600;backdrop-filter:blur(4px);cursor:pointer;transition:.2s}
.hero-tag:hover{background:rgba(255,255,255,.35)}
.section{padding:36px 0}
.section-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px}
.section-title{font-size:22px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:8px}
.section-title::before{content:"";width:5px;height:22px;border-radius:3px;background:var(--grad-orange)}
.section-sub{font-size:13px;color:var(--text-light)}
.cat-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:16px}
.cat-item{display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:var(--radius);cursor:pointer;transition:.25s;box-shadow:var(--shadow-sm);border:2px solid transparent}
.cat-item:hover{transform:translateY(-4px);box-shadow:var(--shadow);border-color:var(--orange-soft)}
.cat-item.active{border-color:var(--orange);background:var(--orange-soft)}
.cat-icon{width:56px;height:56px;border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 6px 14px rgba(0,0,0,.12);transition:.25s}
.cat-item:hover .cat-icon{transform:scale(1.08) rotate(-5deg)}
.cat-icon svg{width:30px;height:30px;fill:#fff}
.cat-name{font-size:14px;font-weight:700;color:var(--text)}
.cat-count{font-size:11px;color:var(--text-light)}
.featured-scroll{display:flex;gap:16px;overflow-x:auto;padding-bottom:8px;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch}
.featured-scroll::-webkit-scrollbar{height:4px}
.feature-card{min-width:280px;max-width:280px;background:#fff;border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow-sm);cursor:pointer;transition:.25s;scroll-snap-align:start;display:flex;flex-direction:column}
.feature-card:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
.feature-cover{height:130px;position:relative;display:flex;align-items:center;justify-content:center}
.feature-cover svg{width:54px;height:54px;fill:rgba(255,255,255,.9);filter:drop-shadow(0 2px 6px rgba(0,0,0,.15))}
.feature-rank{position:absolute;top:10px;left:10px;width:26px;height:26px;border-radius:8px;background:rgba(0,0,0,.45);color:#fff;font-size:12px;font-weight:800;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px)}
.feature-rank.top1{background:var(--grad-yellow);color:#8B5A00}
.feature-rank.top2{background:linear-gradient(135deg,#C0C0C0,#E8E8E8);color:#555}
.feature-rank.top3{background:linear-gradient(135deg,#CD7F32,#E8A87C);color:#fff}
.feature-body{padding:14px;flex:1;display:flex;flex-direction:column}
.feature-name{font-size:15px;font-weight:700;color:var(--text);margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.feature-tags{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:8px;min-height:20px}
.feature-tag{font-size:10px;padding:2px 7px;border-radius:6px;background:var(--orange-soft);color:var(--orange);font-weight:600}
.feature-meta{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-sub);margin-top:auto}
.feature-meta .star{color:var(--yellow);font-weight:700}
.feature-meta .dot{color:#ddd}
.list-toolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px;flex-wrap:wrap;gap:12px}
.sort-tabs{display:flex;gap:6px;background:#fff;padding:5px;border-radius:12px;box-shadow:var(--shadow-sm)}
.sort-tab{padding:7px 16px;border-radius:8px;font-size:13px;font-weight:600;color:var(--text-sub);transition:.2s;white-space:nowrap}
.sort-tab.active{background:var(--grad-orange);color:#fff;box-shadow:0 3px 8px rgba(255,107,53,.35)}
.sort-tab:not(.active):hover{background:var(--orange-soft);color:var(--orange)}
.result-info{font-size:13px;color:var(--text-light)}
.result-info b{color:var(--orange)}
.rest-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}
.rest-card{display:flex;gap:14px;background:#fff;border-radius:var(--radius);padding:14px;box-shadow:var(--shadow-sm);cursor:pointer;transition:.25s;position:relative;overflow:hidden}
.rest-card::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--grad-orange);transform:scaleY(0);transform-origin:top;transition:.25s}
.rest-card:hover{transform:translateY(-3px);box-shadow:var(--shadow-lg)}
.rest-card:hover::before{transform:scaleY(1)}
.rest-logo{width:92px;height:92px;border-radius:14px;flex-shrink:0;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}
.rest-logo svg{width:44px;height:44px;fill:rgba(255,255,255,.92);filter:drop-shadow(0 2px 4px rgba(0,0,0,.15))}
.rest-logo .status-badge{position:absolute;bottom:0;left:0;right:0;text-align:center;font-size:10px;color:#fff;padding:2px;background:rgba(0,0,0,.35);font-weight:600}
.rest-logo .status-badge.closed{background:rgba(120,120,120,.7)}
.rest-info{flex:1;min-width:0;display:flex;flex-direction:column}
.rest-name{font-size:16px;font-weight:700;color:var(--text);margin-bottom:4px;display:flex;align-items:center;gap:6px}
.rest-name .status-dot{width:7px;height:7px;border-radius:50%;background:#34C759;flex-shrink:0}
.rest-name .status-dot.closed{background:#999}
.rest-tags{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:6px}
.rest-tag{font-size:10px;padding:2px 7px;border-radius:6px;background:var(--orange-soft);color:var(--orange);font-weight:600}
.rest-rate{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-sub);margin-bottom:6px}
.rest-rate .star{color:var(--yellow);font-weight:700;font-size:13px}
.rest-rate .sales{color:var(--text-light)}
.rest-meta{display:flex;align-items:center;gap:0;font-size:11px;color:var(--text-sub);margin-top:auto;flex-wrap:wrap}
.rest-meta span{display:flex;align-items:center;gap:3px;padding-right:8px;margin-right:8px;border-right:1px solid var(--border)}
.rest-meta span:last-child{border-right:none}
.rest-meta .free{color:var(--orange);font-weight:700}
.skeleton-card{display:flex;gap:14px;background:#fff;border-radius:var(--radius);padding:14px;box-shadow:var(--shadow-sm)}
.sk-block{background:linear-gradient(90deg,#f0f0f0 25%,#e6e6e6 37%,#f0f0f0 63%);background-size:400% 100%;animation:sk 1.4s ease infinite;border-radius:8px}
@keyframes sk{0%{background-position:100% 50%}100%{background-position:0 50%}}
.sk-logo{width:92px;height:92px;border-radius:14px;flex-shrink:0}
.sk-info{flex:1;display:flex;flex-direction:column;gap:8px;padding-top:4px}
.sk-line{height:14px;border-radius:7px}
.sk-line.w60{width:60%}
.sk-line.w40{width:40%}
.sk-line.w80{width:80%}
.sk-cat{width:56px;height:56px;border-radius:50%}
.cat-grid.skeleton{display:grid;grid-template-columns:repeat(6,1fr);gap:16px}
.cat-sk{display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:var(--radius);box-shadow:var(--shadow-sm)}
.featured-sk{display:flex;gap:16px;overflow:hidden}
.featured-sk .sk-block{min-width:280px;height:230px;border-radius:var(--radius)}
.detail-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:200;opacity:0;pointer-events:none;transition:.3s;backdrop-filter:blur(2px)}
.detail-overlay.show{opacity:1;pointer-events:auto}
.detail-panel{position:fixed;top:0;right:0;bottom:0;width:100%;max-width:960px;background:var(--bg);z-index:201;transform:translateX(100%);transition:.35s cubic-bezier(.4,0,.2,1);display:flex;flex-direction:column;box-shadow:-8px 0 40px rgba(0,0,0,.2)}
.detail-panel.show{transform:translateX(0)}
.detail-header{position:relative;height:200px;display:flex;align-items:flex-end;padding:20px;color:#fff;overflow:hidden;flex-shrink:0}
.detail-header-bg{position:absolute;inset:0;z-index:0}
.detail-header-bg::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,.1) 0%,rgba(0,0,0,.45) 100%)}
.detail-header-content{position:relative;z-index:2;width:100%}
.detail-close{position:absolute;top:16px;right:16px;z-index:5;width:36px;height:36px;border-radius:50%;background:rgba(0,0,0,.35);color:#fff;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(6px);transition:.2s}
.detail-close:hover{background:rgba(0,0,0,.55);transform:rotate(90deg)}
.detail-close svg{width:18px;height:18px}
.detail-name{font-size:24px;font-weight:800;margin-bottom:6px;text-shadow:0 2px 6px rgba(0,0,0,.3)}
.detail-tags{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}
.detail-tag{font-size:11px;padding:3px 9px;border-radius:6px;background:rgba(255,255,255,.25);backdrop-filter:blur(4px);font-weight:600}
.detail-notice{font-size:12px;color:rgba(255,255,255,.9);display:flex;align-items:center;gap:5px;margin-top:4px}
.detail-notice svg{width:14px;height:14px;fill:#fff;flex-shrink:0}
.detail-info-row{display:flex;gap:0;background:#fff;padding:14px 20px;border-bottom:1px solid var(--border);flex-shrink:0}
.detail-info-item{flex:1;text-align:center;border-right:1px solid var(--border)}
.detail-info-item:last-child{border-right:none}
.detail-info-item .v{font-size:18px;font-weight:800;color:var(--orange)}
.detail-info-item .v.yellow{color:#F5A623}
.detail-info-item .l{font-size:11px;color:var(--text-light);margin-top:2px}
.detail-body{flex:1;display:flex;overflow:hidden;min-height:0}
.menu-sidebar{width:100px;background:#F2F3F5;overflow-y:auto;flex-shrink:0;-webkit-overflow-scrolling:touch}
.menu-cat-item{padding:16px 8px;text-align:center;font-size:13px;color:var(--text-sub);cursor:pointer;position:relative;transition:.2s;font-weight:600;line-height:1.4}
.menu-cat-item.active{background:#fff;color:var(--orange);font-weight:700}
.menu-cat-item.active::before{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);width:3px;height:18px;border-radius:0 2px 2px 0;background:var(--orange)}
.menu-cat-item:not(.active):hover{background:rgba(255,255,255,.4)}
.menu-list{flex:1;overflow-y:auto;padding:12px 16px 100px;-webkit-overflow-scrolling:touch}
.menu-section{margin-bottom:8px}
.menu-section-title{font-size:15px;font-weight:800;color:var(--text);padding:12px 4px 10px;position:sticky;top:0;background:var(--bg);z-index:2}
.menu-item{display:flex;gap:12px;background:#fff;border-radius:var(--radius);padding:12px;margin-bottom:10px;box-shadow:var(--shadow-sm);transition:.2s}
.menu-item:hover{box-shadow:var(--shadow)}
.menu-item-img{width:88px;height:88px;border-radius:12px;flex-shrink:0;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}
.menu-item-img svg{width:40px;height:40px;fill:rgba(255,255,255,.92);filter:drop-shadow(0 2px 4px rgba(0,0,0,.15))}
.menu-item-img .discount{position:absolute;top:0;left:0;background:var(--orange);color:#fff;font-size:9px;padding:2px 6px;font-weight:700;border-radius:0 0 8px 0}
.menu-item-info{flex:1;min-width:0;display:flex;flex-direction:column}
.menu-item-name{font-size:15px;font-weight:700;color:var(--text);margin-bottom:3px}
.menu-item-desc{font-size:12px;color:var(--text-light);margin-bottom:5px;line-height:1.5;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.menu-item-tags{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}
.menu-item-tag{font-size:10px;padding:1px 6px;border-radius:5px;background:var(--orange-soft);color:var(--orange);font-weight:600}
.menu-item-bottom{display:flex;align-items:center;justify-content:space-between;margin-top:auto}
.menu-item-meta{display:flex;flex-direction:column;gap:2px}
.menu-item-price{display:flex;align-items:baseline;gap:5px}
.menu-item-price .now{font-size:18px;font-weight:800;color:var(--orange)}
.menu-item-price .now small{font-size:12px}
.menu-item-price .old{font-size:12px;color:var(--text-light);text-decoration:line-through}
.menu-item-stat{font-size:11px;color:var(--text-light)}
.menu-item-stat .star{color:var(--yellow);font-weight:700}
.add-btn{width:32px;height:32px;border-radius:50%;background:var(--grad-orange);color:#fff;display:flex;align-items:center;justify-content:center;box-shadow:0 3px 8px rgba(255,107,53,.4);transition:.2s;flex-shrink:0}
.add-btn:hover{transform:scale(1.1) rotate(90deg)}
.add-btn svg{width:18px;height:18px;stroke:#fff;stroke-width:2.5;fill:none}
.qty-control{display:flex;align-items:center;gap:8px;flex-shrink:0}
.qty-control button{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;transition:.2s}
.qty-control .minus{background:#fff;border:1.5px solid var(--orange);color:var(--orange)}
.qty-control .minus:hover{background:var(--orange-soft)}
.qty-control .plus{background:var(--grad-orange);color:#fff;box-shadow:0 2px 6px rgba(255,107,53,.4)}
.qty-control .num{font-size:15px;font-weight:700;color:var(--text);min-width:18px;text-align:center}
.cart-bar{position:fixed;left:0;right:0;bottom:0;z-index:150;pointer-events:none}
.cart-bar-inner{max-width:var(--maxw);margin:0 auto;padding:0 20px;pointer-events:none}
.cart-bar-box{background:#2A2A2E;border-radius:30px;display:flex;align-items:center;height:60px;box-shadow:0 -4px 24px rgba(0,0,0,.2);pointer-events:auto;overflow:hidden;transition:.3s;position:relative}
.cart-bar-box.empty{background:#3A3A3E}
.cart-icon-wrap{width:60px;height:60px;flex-shrink:0;display:flex;align-items:center;justify-content:center;position:relative;margin-left:8px}
.cart-icon-circle{width:48px;height:48px;border-radius:50%;background:#5A5A5E;display:flex;align-items:center;justify-content:center;transition:.25s;cursor:pointer}
.cart-icon-circle.active{background:var(--grad-orange);box-shadow:0 4px 14px rgba(255,107,53,.5)}
.cart-icon-circle svg{width:24px;height:24px;color:#fff}
.cart-icon-circle.active svg{animation:bounce .4s}
@keyframes bounce{0%,100%{transform:scale(1)}50%{transform:scale(1.25)}}
.cart-total{flex:1;color:#fff;padding-left:14px;display:flex;flex-direction:column;justify-content:center}
.cart-total.empty .t{color:#999}
.cart-total .t{font-size:17px;font-weight:800}
.cart-total .t small{font-size:12px;font-weight:500}
.cart-total .s{font-size:11px;color:#bbb}
.cart-checkout{height:60px;padding:0 28px;background:var(--grad-yellow);color:#8B5A00;font-size:16px;font-weight:800;display:flex;align-items:center;cursor:pointer;transition:.2s;flex-shrink:0}
.cart-checkout:hover{filter:brightness(1.05)}
.cart-checkout.disabled{background:#5A5A5E;color:#888;cursor:not-allowed}
.cart-bar .badge{position:absolute;top:2px;right:2px;min-width:20px;height:20px;padding:0 5px;border-radius:10px;background:var(--orange);color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;border:2px solid #2A2A2E;line-height:1}
.cart-bar .badge:not(.show){display:none}
.cart-detail{position:fixed;left:0;right:0;bottom:60px;z-index:149;background:#fff;border-radius:20px 20px 0 0;box-shadow:0 -8px 30px rgba(0,0,0,.15);transform:translateY(120%);transition:.3s;max-height:60vh;display:flex;flex-direction:column}
.cart-detail.show{transform:translateY(0)}
.cart-detail-mask{position:fixed;inset:0;top:-60vh;background:rgba(0,0,0,.4);z-index:148;opacity:0;pointer-events:none;transition:.3s}
.cart-detail-mask.show{opacity:1;pointer-events:auto}
.cart-detail-head{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border);flex-shrink:0}
.cart-detail-title{font-size:15px;font-weight:700}
.cart-clear{font-size:13px;color:var(--text-light);display:flex;align-items:center;gap:4px;cursor:pointer}
.cart-clear svg{width:14px;height:14px}
.cart-detail-list{overflow-y:auto;padding:8px 20px 16px;flex:1}
.cart-detail-item{display:flex;align-items:center;gap:12px;padding:12px 0;border-bottom:1px solid var(--border)}
.cart-detail-item:last-child{border-bottom:none}
.cart-detail-item .name{flex:1;font-size:14px;font-weight:600}
.cart-detail-item .price{font-size:14px;font-weight:800;color:var(--orange);margin-right:8px}
.cart-detail-empty{text-align:center;padding:40px 0;color:var(--text-light)}
.cart-detail-empty svg{width:60px;height:60px;fill:#ddd;margin-bottom:10px}
.toast-wrap{position:fixed;top:80px;left:50%;transform:translateX(-50%);z-index:999;display:flex;flex-direction:column;gap:8px;pointer-events:none}
.toast{background:rgba(0,0,0,.82);color:#fff;padding:10px 20px;border-radius:24px;font-size:14px;font-weight:500;box-shadow:0 6px 20px rgba(0,0,0,.25);display:flex;align-items:center;gap:8px;animation:toastIn .3s;backdrop-filter:blur(6px)}
.toast.out{animation:toastOut .3s forwards}
.toast svg{width:16px;height:16px}
.toast.success svg{fill:#4CAF50}
.toast.warn svg{fill:var(--yellow)}
@keyframes toastIn{from{opacity:0;transform:translateY(-12px)}to{opacity:1;transform:translateY(0)}}
@keyframes toastOut{to{opacity:0;transform:translateY(-12px)}}
.footer{background:linear-gradient(135deg,#FF6B35 0%,#F0521B 100%);color:#fff;padding:40px 0 24px;margin-top:40px}
.footer-inner{text-align:center}
.footer-logo{font-size:22px;font-weight:800;margin-bottom:8px;display:inline-flex;align-items:center;gap:8px}
.footer-logo svg{width:24px;height:24px;fill:#fff}
.footer-links{display:flex;justify-content:center;gap:24px;margin:16px 0;font-size:13px;opacity:.92}
.footer-links a{cursor:pointer;transition:.2s}
.footer-links a:hover{opacity:.7;text-decoration:underline}
.footer-copy{font-size:12px;opacity:.75;border-top:1px solid rgba(255,255,255,.2);padding-top:16px;margin-top:8px}
.empty-state{text-align:center;padding:60px 20px;color:var(--text-light)}
.empty-state svg{width:80px;height:80px;fill:#E0E0E0;margin-bottom:14px}
.empty-state p{font-size:15px;margin-bottom:4px}
.empty-state .sub{font-size:13px;color:var(--text-light)}
.load-more{text-align:center;padding:20px}
.load-more button{padding:10px 32px;border-radius:24px;background:#fff;color:var(--orange);font-size:14px;font-weight:600;box-shadow:var(--shadow-sm);transition:.2s;border:1px solid var(--orange-soft)}
.load-more button:hover{background:var(--orange-soft)}
.load-more button:disabled{color:var(--text-light);cursor:default}
@media(max-width:900px){
.rest-grid{grid-template-columns:1fr}
.cat-grid{grid-template-columns:repeat(4,1fr)}
}
@media(max-width:768px){
.hero h1{font-size:28px}
.hero p{font-size:14px}
.navbar-inner{gap:10px}
.nav-loc{display:none}
.logo-text{font-size:18px}
.section{padding:28px 0}
.section-title{font-size:19px}
.container{padding:0 14px}
.detail-panel{max-width:100%}
.menu-sidebar{width:84px}
.menu-item-img{width:76px;height:76px}
.rest-logo{width:80px;height:80px}
.rest-logo svg{width:38px;height:38px}
.lang-switcher{position:static;margin-left:0}
}
@media(max-width:560px){
.cat-grid{grid-template-columns:repeat(3,1fr);gap:10px}
.hero{padding:32px 0 40px}
.hero-search button{padding:0 20px;font-size:14px}
.feature-card{min-width:240px;max-width:240px}
.rest-card{padding:12px;gap:10px}
.rest-name{font-size:15px}
.detail-header{height:170px}
.detail-name{font-size:20px}
.nav-search{max-width:none}
.sort-tabs{overflow-x:auto;-webkit-overflow-scrolling:touch}
}
</style>
</head>
<body>
<header class="navbar">
<div class="container navbar-inner">
<div class="logo" onclick="goHome()">
<div class="logo-icon">
<svg viewBox="0 0 24 24"><path d="M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7zm5-3v8h2.5v8H21V2c-2.76 0-5 2.24-5 4z"/></svg>
</div>
<span class="logo-text" data-i18n="logo">美味达</span>
</div>
<div class="nav-search">
<input id="navSearchInput" type="text" data-i18n-placeholder="search_placeholder" placeholder="搜索餐厅或美食" onkeydown="if(event.key==='Enter')doSearch(this.value)">
<svg class="s-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
</div>
<div class="nav-right">
<div class="nav-loc">
<svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 0 1 0-5 2.5 2.5 0 0 1 0 5z"/></svg>
<span data-i18n="current_location">当前位置</span>
</div>
<div class="lang-switcher" id="langSwitcher">
<div class="lang-btn" onclick="toggleLangDropdown()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20z"/></svg>
<span id="langLabel">中文</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="width:12px;height:12px"><path d="m6 9 6 6 6-6"/></svg>
</div>
<div class="lang-dropdown" id="langDropdown">
<div class="lang-option" data-lang="zh-CN" onclick="switchLang('zh-CN')">
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M5 12l5 5L20 7"/></svg>
中文
</div>
<div class="lang-option" data-lang="zh-TW" onclick="switchLang('zh-TW')">
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M5 12l5 5L20 7"/></svg>
繁體中文
</div>
<div class="lang-option" data-lang="en" onclick="switchLang('en')">
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M5 12l5 5L20 7"/></svg>
English
</div>
</div>
</div>
<div class="nav-cart" onclick="scrollToCart()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>
<span id="navCartBadge" class="cart-badge">0</span>
</div>
</div>
</div>
</header>
<section class="hero">
<div class="container hero-inner">
<h1 data-i18n="hero_title">美味触手可及</h1>
<p data-i18n="hero_desc">周边好店 · 极速配送 · 优惠不停</p>
<div class="hero-search">
<input id="heroSearchInput" type="text" data-i18n-placeholder="hero_search_placeholder" placeholder="想吃什么?搜搜看吧" onkeydown="if(event.key==='Enter')doSearch(this.value)">
<button onclick="doSearch(document.getElementById('heroSearchInput').value)" data-i18n="search_btn">搜索</button>
</div>
<div class="hero-tags" id="heroTags"></div>
</div>
</section>
<section class="section">
<div class="container">
<div class="section-head">
<div class="section-title" data-i18n="cat_title">餐厅分类</div>
<div class="section-sub" data-i18n="cat_sub">选择分类,发现好店</div>
</div>
<div id="catGrid" class="cat-grid">
<div class="cat-grid skeleton">
<div class="cat-sk" style="display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:16px;box-shadow:0 2px 8px rgba(255,107,53,.06)"><div class="sk-block sk-cat"></div><div class="sk-block" style="width:40px;height:12px"></div></div>
<div class="cat-sk" style="display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:16px;box-shadow:0 2px 8px rgba(255,107,53,.06)"><div class="sk-block sk-cat"></div><div class="sk-block" style="width:40px;height:12px"></div></div>
<div class="cat-sk" style="display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:16px;box-shadow:0 2px 8px rgba(255,107,53,.06)"><div class="sk-block sk-cat"></div><div class="sk-block" style="width:40px;height:12px"></div></div>
<div class="cat-sk" style="display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:16px;box-shadow:0 2px 8px rgba(255,107,53,.06)"><div class="sk-block sk-cat"></div><div class="sk-block" style="width:40px;height:12px"></div></div>
<div class="cat-sk" style="display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:16px;box-shadow:0 2px 8px rgba(255,107,53,.06)"><div class="sk-block sk-cat"></div><div class="sk-block" style="width:40px;height:12px"></div></div>
<div class="cat-sk" style="display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:16px;box-shadow:0 2px 8px rgba(255,107,53,.06)"><div class="sk-block sk-cat"></div><div class="sk-block" style="width:40px;height:12px"></div></div>
</div>
</div>
</div>
</section>
<section class="section" style="padding-top:0">
<div class="container">
<div class="section-head">
<div class="section-title" data-i18n="featured_title">为你推荐</div>
<div class="section-sub" data-i18n="featured_sub">人气好店,不容错过</div>
</div>
<div id="featuredScroll" class="featured-scroll">
<div class="featured-sk">
<div class="sk-block"></div><div class="sk-block"></div><div class="sk-block"></div><div class="sk-block"></div>
</div>
</div>
</div>
</section>
<section class="section" style="padding-top:0" id="restSection">
<div class="container">
<div class="section-head">
<div class="section-title" id="listTitle" data-i18n="all_restaurants">全部餐厅</div>
</div>
<div class="list-toolbar">
<div class="sort-tabs" id="sortTabs">
<div class="sort-tab active" data-sort="" data-i18n="sort_default">默认排序</div>
<div class="sort-tab" data-sort="rating" data-i18n="sort_rating">评分最高</div>
<div class="sort-tab" data-sort="sales" data-i18n="sort_sales">销量最高</div>
<div class="sort-tab" data-sort="delivery_time" data-i18n="sort_delivery">配送最快</div>
</div>
<div class="result-info"><span data-i18n="result_prefix">共 </span><b id="resultTotal">0</b><span data-i18n="result_suffix"> 家餐厅</span></div>
</div>
<div id="restGrid" class="rest-grid"></div>
<div id="loadMore" class="load-more hidden">
<button id="loadMoreBtn" onclick="loadMoreRestaurants()" data-i18n="load_more">加载更多</button>
</div>
</div>
</section>
<div class="detail-overlay" id="detailOverlay" onclick="closeDetail()"></div>
<div class="detail-panel" id="detailPanel">
<div class="detail-header" id="detailHeader">
<div class="detail-header-bg" id="detailHeaderBg"></div>
<button class="detail-close" onclick="closeDetail()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6 6 18M6 6l12 12"/></svg>
</button>
<div class="detail-header-content" id="detailHeaderContent"></div>
</div>
<div class="detail-info-row" id="detailInfoRow"></div>
<div class="detail-body" id="detailBody">
<div class="menu-sidebar" id="menuSidebar"></div>
<div class="menu-list" id="menuList"></div>
</div>
</div>
<div class="cart-detail-mask" id="cartDetailMask" onclick="toggleCartDetail(false)"></div>
<div class="cart-detail" id="cartDetail">
<div class="cart-detail-head">
<span class="cart-detail-title" id="cartDetailTitle" data-i18n="cart">购物车</span>
<span class="cart-clear" onclick="clearCart()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
<span data-i18n="clear_cart">清空</span>
</span>
</div>
<div class="cart-detail-list" id="cartDetailList"></div>
</div>
<div class="cart-bar" id="cartBar" style="display:none">
<div class="cart-bar-inner">
<div class="cart-bar-box empty" id="cartBarBox">
<div class="cart-icon-wrap">
<div class="cart-icon-circle" id="cartIconCircle" onclick="toggleCartDetail()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>
</div>
<span class="badge" id="cartBadge">0</span>
</div>
<div class="cart-total empty" id="cartTotal">
<span class="t">&yen;0</span>
<span class="s" id="cartSub"></span>
</div>
<div class="cart-checkout disabled" id="checkoutBtn" onclick="checkout()"></div>
</div>
</div>
</div>
<div class="toast-wrap" id="toastWrap"></div>
<footer class="footer">
<div class="container footer-inner">
<div class="footer-logo">
<svg viewBox="0 0 24 24"><path d="M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7zm5-3v8h2.5v8H21V2c-2.76 0-5 2.24-5 4z"/></svg>
<span data-i18n="logo">美味达</span>
</div>
<div class="footer-links">
<a onclick="goHome()" data-i18n="footer_home">首页</a>
<a onclick="toast(t('order_demo'),'warn')" data-i18n="footer_orders">我的订单</a>
<a onclick="toast(t('merchant_demo'),'warn')" data-i18n="footer_merchant">商家入驻</a>
<a onclick="toast(t('service_demo'),'warn')" data-i18n="footer_service">联系客服</a>
</div>
<div class="footer-copy" data-i18n="footer_copy">&copy; 2026 美味达外卖平台 · 美味触手可及 · 本页面为演示模板</div>
</div>
</footer>
<script>
var I18N_LOCALES = {
'zh-CN': {
site_title: '\u7f8e\u5473\u8fbe \u00b7 \u5916\u5356\u5e73\u53f0',
logo: '\u7f8e\u5473\u8fbe',
search_placeholder: '\u641c\u7d22\u9910\u5385\u6216\u7f8e\u98df',
current_location: '\u5f53\u524d\u4f4d\u7f6e',
hero_title: '\u7f8e\u5473\u89e6\u624b\u53ef\u53ca',
hero_desc: '\u5468\u8fb9\u597d\u5e97 \u00b7 \u6781\u901f\u914d\u9001 \u00b7 \u4f18\u60e0\u4e0d\u505c',
hero_search_placeholder: '\u60f3\u5403\u4ec0\u4e48\uff1f\u641c\u641c\u770b\u5427',
search_btn: '\u641c\u7d22',
cat_title: '\u9910\u5385\u5206\u7c7b',
cat_sub: '\u9009\u62e9\u5206\u7c7b\uff0c\u53d1\u73b0\u597d\u5e97',
featured_title: '\u4e3a\u4f60\u63a8\u8350',
featured_sub: '\u4eba\u6c14\u597d\u5e97\uff0c\u4e0d\u5bb9\u9519\u8fc7',
all_restaurants: '\u5168\u90e8\u9910\u5385',
sort_default: '\u9ed8\u8ba4\u6392\u5e8f',
sort_rating: '\u8bc4\u5206\u6700\u9ad8',
sort_sales: '\u9500\u91cf\u6700\u9ad8',
sort_delivery: '\u914d\u9001\u6700\u5feb',
result_prefix: '\u5171 ',
result_suffix: ' \u5bb6\u9910\u5385',
load_more: '\u52a0\u8f7d\u66f4\u591a',
cart: '\u8d2d\u7269\u8f66',
clear_cart: '\u6e05\u7a7a',
checkout: '\u53bb\u7ed3\u7b97',
footer_home: '\u9996\u9875',
footer_orders: '\u6211\u7684\u8ba2\u5355',
footer_merchant: '\u5546\u5bb6\u5165\u9a7b',
footer_service: '\u8054\u7cfb\u5ba2\u670d',
footer_copy: '\u00a9 2026 \u7f8e\u5473\u8fbe\u5916\u5356\u5e73\u53f0 \u00b7 \u7f8e\u5473\u89e6\u624b\u53ef\u53ca \u00b7 \u672c\u9875\u9762\u4e3a\u6f14\u793a\u6a21\u677f',
order_demo: '\u8ba2\u5355\u529f\u80fd\u6f14\u793a\u4e2d',
merchant_demo: '\u5546\u5bb6\u5165\u9a7b\u529f\u80fd\u6f14\u793a\u4e2d',
service_demo: '\u5ba2\u670d\u4e2d\u5fc3\u6f14\u793a\u4e2d',
load_data_fail: '\u6570\u636e\u52a0\u8f7d\u5931\u8d25\uff0c\u8bf7\u68c0\u67e5\u670d\u52a1',
load_error_data: '\u6570\u636e\u52a0\u8f7d\u5931\u8d25',
load_error_service: '\u8bf7\u786e\u4fdd\u540e\u7aef\u670d\u52a1\u5df2\u542f\u52a8',
load_restaurant_fail: '\u52a0\u8f7d\u9910\u5385\u5931\u8d25',
retry_later: '\u8bf7\u7a0d\u540e\u91cd\u8bd5',
no_restaurant: '\u6ca1\u6709\u627e\u5230\u76f8\u5173\u9910\u5385',
try_other: '\u6362\u4e2a\u5173\u952e\u8bcd\u6216\u5206\u7c7b\u8bd5\u8bd5',
open: '\u8425\u4e1a\u4e2d',
closed: '\u4f11\u606f\u4e2d',
monthly_sales: '\u6708\u552e ',
mins: '\u5206\u949f',
min_order_prefix: '\u8d77\u9001 \u00a5',
free_delivery: '\u514d\u914d\u9001\u8d39',
delivery_fee_prefix: '\u914d\u9001 \u00a5',
detail_rating: '\u8bc4\u5206',
detail_monthly: '\u6708\u552e',
detail_delivery_time: '\u914d\u9001\u65f6\u95f4',
detail_distance: '\u8ddd\u79bb',
detail_min_order: '\u8d77\u9001',
detail_delivery_fee: '\u914d\u9001\u8d39',
detail_free: '\u514d',
no_menu: '\u6682\u65e0\u83dc\u54c1',
loading: '\u52a0\u8f7d\u4e2d...',
load_fail: '\u52a0\u8f7d\u5931\u8d25',
detail_load_fail: '\u8be6\u60c5\u52a0\u8f7d\u5931\u8d25',
cross_store_confirm: '\u8d2d\u7269\u8f66\u5df2\u6709\u300c{name}\u300d\u7684\u83dc\u54c1\uff0c\u662f\u5426\u6e05\u7a7a\u5e76\u52a0\u5165\u65b0\u5e97\u83dc\u54c1\uff1f',
added_item: '\u5df2\u52a0\u5165\u300c{name}\u300d',
clear_cart_confirm: '\u786e\u5b9a\u6e05\u7a7a\u8d2d\u7269\u8f66\u5417\uff1f',
cart_cleared: '\u8d2d\u7269\u8f66\u5df2\u6e05\u7a7a',
cart_empty: '\u8d2d\u7269\u8f66\u662f\u7a7a\u7684',
cart_empty_hint: '\u5feb\u53bb\u9009\u4e9b\u597d\u5403\u7684\u5427',
cart_with_name: '\u8d2d\u7269\u8f66 \u00b7 {name}',
not_reach_min: '\u672a\u8fbe\u5230\u8d77\u9001\u4ef7',
order_success: '\u8ba2\u5355\u63d0\u4ea4\u6210\u529f\uff08\u6f14\u793a\uff09\u00a5{total}',
delivery_fee_text: '\u914d\u9001\u8d39\u00a5{fee}',
need_more: '\u8fd8\u5dee\u00a5{diff}\u8d77\u9001',
checkout_disabled: '\u5dee\u00a5{diff}\u8d77\u9001',
hot_tags: ['\u9ebb\u8fa3','\u5ddd\u83dc','\u5976\u8336','\u6c49\u5821','\u70e4\u8089','\u6c99\u62c9']
},
'en': {
site_title: 'MeiWeiDa \u00b7 Food Delivery',
logo: 'MeiWeiDa',
search_placeholder: 'Search restaurants or food',
current_location: 'Current Location',
hero_title: 'Delicious Food at Your Doorstep',
hero_desc: 'Top local restaurants \u00b7 Fast delivery \u00b7 Non-stop deals',
hero_search_placeholder: 'What are you craving? Search here',
search_btn: 'Search',
cat_title: 'Categories',
cat_sub: 'Pick a category to explore',
featured_title: 'Recommended',
featured_sub: 'Popular restaurants you can\'t miss',
all_restaurants: 'All Restaurants',
sort_default: 'Default',
sort_rating: 'Top Rated',
sort_sales: 'Best Sellers',
sort_delivery: 'Fastest Delivery',
result_prefix: '',
result_suffix: ' restaurants found',
load_more: 'Load More',
cart: 'Cart',
clear_cart: 'Clear',
checkout: 'Checkout',
footer_home: 'Home',
footer_orders: 'My Orders',
footer_merchant: 'Merchant Signup',
footer_service: 'Contact Support',
footer_copy: '\u00a9 2026 MeiWeiDa \u00b7 Delicious food at your doorstep \u00b7 Demo template',
order_demo: 'Orders feature (demo)',
merchant_demo: 'Merchant signup (demo)',
service_demo: 'Customer support (demo)',
load_data_fail: 'Failed to load data, please check the server',
load_error_data: 'Failed to load data',
load_error_service: 'Please make sure the backend is running',
load_restaurant_fail: 'Failed to load restaurants',
retry_later: 'Please try again later',
no_restaurant: 'No restaurants found',
try_other: 'Try a different keyword or category',
open: 'Open',
closed: 'Closed',
monthly_sales: 'Monthly ',
mins: 'min',
min_order_prefix: 'Min \u00a5',
free_delivery: 'Free Delivery',
delivery_fee_prefix: 'Delivery \u00a5',
detail_rating: 'Rating',
detail_monthly: 'Monthly Sales',
detail_delivery_time: 'Delivery Time',
detail_distance: 'Distance',
detail_min_order: 'Min Order',
detail_delivery_fee: 'Delivery Fee',
detail_free: 'Free',
no_menu: 'No menu items',
loading: 'Loading...',
load_fail: 'Load Failed',
detail_load_fail: 'Failed to load details',
cross_store_confirm: 'Your cart has items from "{name}". Clear cart and add items from this store?',
added_item: 'Added "{name}"',
clear_cart_confirm: 'Clear the entire cart?',
cart_cleared: 'Cart cleared',
cart_empty: 'Your cart is empty',
cart_empty_hint: 'Pick something delicious!',
cart_with_name: 'Cart \u00b7 {name}',
not_reach_min: 'Minimum order not reached',
order_success: 'Order placed (demo) \u00a5{total}',
delivery_fee_text: 'Delivery \u00a5{fee}',
need_more: '\u00a5{diff} more to order',
checkout_disabled: '\u00a5{diff} more to checkout',
hot_tags: ['Spicy','Sichuan','Bubble Tea','Burger','BBQ','Salad']
},
'zh-TW': {
site_title: '\u7f8e\u5473\u9054 \u00b7 \u5916\u8ca9\u5e73\u53f0',
logo: '\u7f8e\u5473\u9054',
search_placeholder: '\u641c\u5bfb\u9910\u5385\u6216\u7f8e\u98df',
current_location: '\u76ee\u524d\u4f4d\u7f6e',
hero_title: '\u7f8e\u5473\u89f8\u624b\u53ef\u53ca',
hero_desc: '\u5468\u908a\u597d\u5e97 \u00b7 \u6975\u901f\u914d\u9001 \u00b7 \u512a\u60e0\u4e0d\u505c',
hero_search_placeholder: '\u60f3\u5403\u4ec0\u9ebc\uff1f\u641c\u641c\u770b\u5427',
search_btn: '\u641c\u5bfb',
cat_title: '\u9910\u5385\u5206\u985e',
cat_sub: '\u9078\u64c7\u5206\u985e\uff0c\u767c\u73b0\u597d\u5e97',
featured_title: '\u70ba\u4f60\u63a8\u85a6',
featured_sub: '\u4eba\u6c23\u597d\u5e97\uff0c\u4e0d\u5bb9\u932f\u904e',
all_restaurants: '\u5168\u90e8\u9910\u5385',
sort_default: '\u9810\u8a2d\u6392\u5e8f',
sort_rating: '\u8a55\u5206\u6700\u9ad8',
sort_sales: '\u92b7\u91cf\u6700\u9ad8',
sort_delivery: '\u914d\u9001\u6700\u5feb',
result_prefix: '\u5171 ',
result_suffix: ' \u5bb6\u9910\u5385',
load_more: '\u8f09\u5165\u66f4\u591a',
cart: '\u8cfc\u7269\u8eca',
clear_cart: '\u6e05\u7a7a',
checkout: '\u53bb\u7d50\u5e33',
footer_home: '\u9996\u9801',
footer_orders: '\u6211\u7684\u8a02\u55ae',
footer_merchant: '\u5546\u5bb6\u5165\u9a7b',
footer_service: '\u806f\u7e6c\u5ba2\u670d',
footer_copy: '\u00a9 2026 \u7f8e\u5473\u9054\u5916\u8ca9\u5e73\u53f0 \u00b7 \u7f8e\u5473\u89f8\u624b\u53ef\u53ca \u00b7 \u672c\u9801\u9762\u70ba\u6f14\u793a\u6a21\u677f',
order_demo: '\u8a02\u55ae\u529f\u80fd\u6f14\u793a\u4e2d',
merchant_demo: '\u5546\u5bb6\u5165\u9a7b\u529f\u80fd\u6f14\u793a\u4e2d',
service_demo: '\u5ba2\u670d\u4e2d\u5fc3\u6f14\u793a\u4e2d',
load_data_fail: '\u8cc7\u6599\u8f09\u5165\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u670d\u52d9',
load_error_data: '\u8cc7\u6599\u8f09\u5165\u5931\u6557',
load_error_service: '\u8acb\u78ba\u4fdd\u5f8c\u7aef\u670d\u52d9\u5df2\u555f\u52d5',
load_restaurant_fail: '\u8f09\u5165\u9910\u5385\u5931\u6557',
retry_later: '\u8acb\u7a0d\u5f8c\u91cd\u8a66',
no_restaurant: '\u6ca1\u6709\u627e\u5230\u76f8\u95dc\u9910\u5385',
try_other: '\u63db\u500b\u95dc\u9375\u8a5e\u6216\u5206\u985e\u8a66\u8a66',
open: '\u71df\u696d\u4e2d',
closed: '\u4f11\u606f\u4e2d',
monthly_sales: '\u6708\u552e ',
mins: '\u5206\u9418',
min_order_prefix: '\u8d77\u9001 \u00a5',
free_delivery: '\u514d\u914d\u9001\u8cbb',
delivery_fee_prefix: '\u914d\u9001 \u00a5',
detail_rating: '\u8a55\u5206',
detail_monthly: '\u6708\u552e',
detail_delivery_time: '\u914d\u9001\u6642\u9593',
detail_distance: '\u8ddd\u96e2',
detail_min_order: '\u8d77\u9001',
detail_delivery_fee: '\u914d\u9001\u8cbb',
detail_free: '\u514d',
no_menu: '\u6682\u7121\u83dc\u54c1',
loading: '\u8f09\u5165\u4e2d...',
load_fail: '\u8f09\u5165\u5931\u6557',
detail_load_fail: '\u8a73\u60c5\u8f09\u5165\u5931\u6557',
cross_store_confirm: '\u8cfc\u7269\u8eca\u5df2\u6709\u300c{name}\u300d\u7684\u83dc\u54c1\uff0c\u662f\u5426\u6e05\u7a7a\u4e26\u52a0\u5165\u65b0\u5e97\u83dc\u54c1\uff1f',
added_item: '\u5df2\u52a0\u5165\u300c{name}\u300d',
clear_cart_confirm: '\u78ba\u5b9a\u6e05\u7a7a\u8cfc\u7269\u8eca\u55ce\uff1f',
cart_cleared: '\u8cfc\u7269\u8eca\u5df2\u6e05\u7a7a',
cart_empty: '\u8cfc\u7269\u8eca\u662f\u7a7a\u7684',
cart_empty_hint: '\u5feb\u53bb\u9078\u4e9b\u597d\u5403\u7684\u5427',
cart_with_name: '\u8cfc\u7269\u8eca \u00b7 {name}',
not_reach_min: '\u672a\u9054\u5230\u8d77\u9001\u50f9',
order_success: '\u8a02\u55ae\u63d0\u4ea4\u6210\u529f\uff08\u6f14\u793a\uff09\u00a5{total}',
delivery_fee_text: '\u914d\u9001\u8cbb\u00a5{fee}',
need_more: '\u9084\u5dee\u00a5{diff}\u8d77\u9001',
checkout_disabled: '\u5dee\u00a5{diff}\u8d77\u9001',
hot_tags: ['\u9ebb\u8fa3','\u5ddd\u83dc','\u5976\u8336','\u6f22\u5821','\u70e4\u8089','\u6c99\u62c9']
}
};
var LANG_LABELS = {'zh-CN':'\u4e2d\u6587','en':'EN','zh-TW':'\u7e41\u4e2d'};
var currentLang = (function(){
try{
var u=new URLSearchParams(location.search).get('lang');
if(u&&I18N_LOCALES[u]) return u;
}catch(e){}
try{var s=localStorage.getItem('lang');if(s&&I18N_LOCALES[s]) return s;}catch(e){}
var b=navigator.language||navigator.userLanguage||'';
if(b.startsWith('zh-TW')||b.startsWith('zh-Hant')) return 'zh-TW';
if(b.startsWith('zh')) return 'en';
return 'en';
})();
function t(key){
var v=I18N_LOCALES[currentLang]&&I18N_LOCALES[currentLang][key];
if(v===undefined) v=I18N_LOCALES['zh-CN'][key];
return v!==undefined?v:key;
}
function tParam(key,params){
var s=t(key);
if(params){Object.keys(params).forEach(function(k){s=s.replace(new RegExp('\\{'+k+'\\}','g'),params[k]);});}
return s;
}
function applyI18n(){
document.querySelectorAll('[data-i18n]').forEach(function(el){
var key=el.getAttribute('data-i18n');
el.textContent=t(key);
});
document.querySelectorAll('[data-i18n-placeholder]').forEach(function(el){
var key=el.getAttribute('data-i18n-placeholder');
el.placeholder=t(key);
});
document.documentElement.lang=currentLang==='zh-TW'?'zh-TW':currentLang==='zh-CN'?'zh-CN':'en';
document.title=t('site_title');
document.getElementById('langLabel').textContent=LANG_LABELS[currentLang]||currentLang;
document.querySelectorAll('.lang-option').forEach(function(opt){
opt.classList.toggle('active',opt.getAttribute('data-lang')===currentLang);
});
}
function switchLang(lang){
if(!I18N_LOCALES[lang]) return;
currentLang=lang;
try{localStorage.setItem('lang',lang);}catch(e){}
applyI18n();
if(typeof onLangChange==='function') onLangChange(lang);
document.getElementById('langDropdown').classList.remove('open');
}
function toggleLangDropdown(){
document.getElementById('langDropdown').classList.toggle('open');
}
document.addEventListener('click',function(e){
var sw=document.getElementById('langSwitcher');
if(sw&&!sw.contains(e.target)) document.getElementById('langDropdown').classList.remove('open');
});
/* ==========================================================
Data & State
========================================================== */
var API = '/api/v1/food';
var state = {
categories: [],
featured: [],
restaurants: [],
total: 0,
page: 1,
pageSize: 12,
currentCategory: 0,
keyword: '',
sort: '',
loading: false,
currentRestaurant: null,
menuData: null,
cart: null,
};
var CAT_STYLES = {
chinese: {grad:'linear-gradient(135deg,#FF6B35,#FF9A3D)', svg:'<path d="M3 3v18h18V3H3zm9 14a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"/>'},
japanese: {grad:'linear-gradient(135deg,#FF8FA3,#FF6B9D)', svg:'<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/><path d="M12 6v12"/>'},
western: {grad:'linear-gradient(135deg,#FFD23F,#FFB323)', svg:'<path d="M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7z"/>'},
hotpot: {grad:'linear-gradient(135deg,#FF4757,#FF6B35)', svg:'<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/>'},
dessert: {grad:'linear-gradient(135deg,#FF6B9D,#FFD23F)', svg:'<path d="M12 3 10.7 8.3 5.3 9.6l4 3.9-.9 5.4 4.6-2.6 4.6 2.6-.9-5.4 4-3.9-5.4-1.3z"/>'},
healthy: {grad:'linear-gradient(135deg,#5BB318,#7CC576)', svg:'<path d="M17 8C8 10 5.9 16.17 3.82 21.34l1.89.66.95-2.3c.48.17.98.3 1.34.3C19 20 22 3 22 3c-1 2-8 2.25-13 3.25S2 11.5 2 13.5s1.75 3.75 1.75 3.75C4 8 17 8 17 8z"/>'},
};
var DEFAULT_CAT_STYLE = {grad:'linear-gradient(135deg,#FF6B35,#FFD23F)', svg:'<circle cx="12" cy="12" r="9"/>'};
var REST_GRADIENTS = [
'linear-gradient(135deg,#FF6B35,#FF9A3D)',
'linear-gradient(135deg,#FF4757,#FF6B35)',
'linear-gradient(135deg,#FFD23F,#FF9A3D)',
'linear-gradient(135deg,#FF8FA3,#FF6B9D)',
'linear-gradient(135deg,#5BB318,#7CC576)',
'linear-gradient(135deg,#FF9A3D,#FFD23F)',
'linear-gradient(135deg,#FF6B9D,#FFD23F)',
'linear-gradient(135deg,#F0521B,#FF8455)',
];
var REST_SVGS = [
'<path d="M3 3v18h18V3H3zm9 14a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"/>',
'<path d="M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7zm5-3v8h2.5v8H21V2c-2.76 0-5 2.24-5 4z"/>',
'<path d="M12 3 10.7 8.3 5.3 9.6l4 3.9-.9 5.4 4.6-2.6 4.6 2.6-.9-5.4 4-3.9-5.4-1.3z"/>',
'<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 0 1 0-5 2.5 2.5 0 0 1 0 5z"/>',
'<path d="M17 8C8 10 5.9 16.17 3.82 21.34l1.89.66.95-2.3c.48.17.98.3 1.34.3C19 20 22 3 22 3c-1 2-8 2.25-13 3.25S2 11.5 2 13.5s1.75 3.75 1.75 3.75C4 8 17 8 17 8z"/>',
'<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/><path d="M12 6v12"/>',
'<path d="M21 11H3v8h18v-8zm-2-7H5v5h14V4z"/>',
'<path d="M20 8h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.2 2.2A5.99 5.99 0 0 0 12 5c-.47 0-.93.06-1.39.15L8.41 3 7 4.41l1.62 1.63A5.99 5.99 0 0 0 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z"/>',
];
var ITEM_GRADIENTS = [
'linear-gradient(135deg,#FF6B35,#FF9A3D)',
'linear-gradient(135deg,#FFD23F,#FFB323)',
'linear-gradient(135deg,#FF8FA3,#FF6B9D)',
'linear-gradient(135deg,#5BB318,#7CC576)',
'linear-gradient(135deg,#FF4757,#FF6B35)',
'linear-gradient(135deg,#FF9A3D,#FFD23F)',
];
var ITEM_SVGS = [
'<path d="M3 3v18h18V3H3zm9 14a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"/>',
'<path d="M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7z"/>',
'<path d="M12 3 10.7 8.3 5.3 9.6l4 3.9-.9 5.4 4.6-2.6 4.6 2.6-.9-5.4 4-3.9-5.4-1.3z"/>',
'<path d="M17 8C8 10 5.9 16.17 3.82 21.34l1.89.66.95-2.3c.48.17.98.3 1.34.3C19 20 22 3 22 3c-1 2-8 2.25-13 3.25S2 11.5 2 13.5s1.75 3.75 1.75 3.75C4 8 17 8 17 8z"/>',
'<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 0 1 0-5 2.5 2.5 0 0 1 0 5z"/>',
'<path d="M20 8h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.2 2.2A5.99 5.99 0 0 0 12 5c-.47 0-.93.06-1.39.15L8.41 3 7 4.41l1.62 1.63A5.99 5.99 0 0 0 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z"/>',
];
/* ==========================================================
Utilities
========================================================== */
function $(id){return document.getElementById(id)}
function esc(s){return (s==null?'':String(s)).replace(/[&<>"']/g,function(c){return({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c]})}
function parseTags(t){try{return JSON.parse(t||'[]')}catch(e){return []}}
function safeNum(n,d){n=Number(n);return isNaN(n)?(d||0):n}
function money(n){return safeNum(n).toFixed(1).replace(/\.0$/,'')}
function catStyle(icon){return CAT_STYLES[icon]||DEFAULT_CAT_STYLE}
function restGrad(id){return REST_GRADIENTS[(id-1)%REST_GRADIENTS.length]}
function restSvg(id){return REST_SVGS[(id-1)%REST_SVGS.length]}
function itemGrad(id){return ITEM_GRADIENTS[(id-1)%ITEM_GRADIENTS.length]}
function itemSvg(id){return ITEM_SVGS[(id-1)%ITEM_SVGS.length]}
function toast(msg,type){
var wrap=$('toastWrap');
var el=document.createElement('div');
el.className='toast '+(type||'');
var icons={success:'<svg viewBox="0 0 24 24"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>',warn:'<svg viewBox="0 0 24 24"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>'};
el.innerHTML=(icons[type]||'')+'<span>'+esc(msg)+'</span>';
wrap.appendChild(el);
setTimeout(function(){el.classList.add('out');setTimeout(function(){el.remove()},300)},2000);
}
/* ==========================================================
API
========================================================== */
async function api(path){
try{
var res=await fetch(API+path);
if(!res.ok) throw new Error('HTTP '+res.status);
var json=await res.json();
if(json.code!==0) throw new Error(json.message||'API error');
return json.data;
}catch(e){
console.error('API error:',path,e);
throw e;
}
}
/* ==========================================================
Init
========================================================== */
async function init(){
try{
var home=await api('/home');
state.categories=home.categories||[];
state.featured=home.featured||[];
renderHeroTags();
renderCategories();
renderFeatured();
await loadRestaurants(true);
}catch(e){
toast(t('load_data_fail'),'warn');
$('catGrid').innerHTML='<div class="empty-state" style="grid-column:1/-1"><p>'+t('load_error_data')+'</p><p class="sub">'+t('load_error_service')+'</p></div>';
}
}
function renderHeroTags(){
var hot=t('hot_tags');
if(!Array.isArray(hot)) hot=['Spicy','Sichuan','Bubble Tea','Burger','BBQ','Salad'];
$('heroTags').innerHTML=hot.map(function(tag){return '<span class="hero-tag" onclick="doSearch(\''+esc(tag)+'\')">'+esc(tag)+'</span>'}).join('');
}
function renderCategories(){
var html=state.categories.map(function(c){
var s=catStyle(c.icon);
return '<div class="cat-item '+(state.currentCategory===c.id?'active':'')+'" onclick="selectCategory('+c.id+')">'+
'<div class="cat-icon" style="background:'+s.grad+'"><svg viewBox="0 0 24 24">'+s.svg+'</svg></div>'+
'<span class="cat-name">'+esc(c.name)+'</span>'+
'</div>';
}).join('');
$('catGrid').innerHTML=html;
}
function renderFeatured(){
if(!state.featured.length){$('featuredScroll').innerHTML='';return}
var html=state.featured.map(function(r,i){
var tags=parseTags(r.tags).slice(0,2);
return '<div class="feature-card" onclick="openDetail('+r.id+')">'+
'<div class="feature-cover" style="background:'+restGrad(r.id)+'">'+
(r.cover?'<img src="'+esc(r.cover)+'" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" onerror="this.style.display=\'none\';this.parentNode.querySelector(\'svg\').style.display=\'\'">':'')+
'<div class="feature-rank '+(i<3?'top'+(i+1):'')+'">'+(i+1)+'</div>'+
'<svg viewBox="0 0 24 24" style="'+(r.cover?'display:none':'')+'">'+restSvg(r.id)+'</svg>'+
'</div>'+
'<div class="feature-body">'+
'<div class="feature-name">'+esc(r.name)+'</div>'+
'<div class="feature-tags">'+tags.map(function(tag){return '<span class="feature-tag">'+esc(tag)+'</span>'}).join('')+'</div>'+
'<div class="feature-meta">'+
'<span class="star">\u2605 '+money(r.rating)+'</span>'+
'<span class="dot">\u00b7</span>'+
'<span>'+t('monthly_sales')+r.sales+'</span>'+
'<span class="dot">\u00b7</span>'+
'<span>'+r.delivery_time+t('mins')+'</span>'+
'</div>'+
'</div>'+
'</div>';
}).join('');
$('featuredScroll').innerHTML=html;
}
/* ==========================================================
Restaurant List
========================================================== */
function buildQuery(){
var p=new URLSearchParams();
if(state.currentCategory) p.set('category_id',state.currentCategory);
if(state.keyword) p.set('keyword',state.keyword);
if(state.sort) p.set('sort',state.sort);
p.set('page',state.page);
p.set('page_size',state.pageSize);
return p.toString();
}
async function loadRestaurants(reset){
if(state.loading) return;
state.loading=true;
if(reset){
state.page=1;
state.restaurants=[];
$('restGrid').innerHTML=renderSkeleton(4);
}
try{
var data=await api('/restaurants?'+buildQuery());
var list=data.list||[];
if(reset){state.restaurants=list}else{state.restaurants=state.restaurants.concat(list)}
state.total=safeNum(data.total,0);
renderRestaurantList();
}catch(e){
$('restGrid').innerHTML='<div class="empty-state" style="grid-column:1/-1"><p>'+t('load_restaurant_fail')+'</p><p class="sub">'+t('retry_later')+'</p></div>';
}
state.loading=false;
}
function loadMoreRestaurants(){
state.page++;
loadRestaurants(false);
}
function renderSkeleton(n){
var html='';
for(var i=0;i<n;i++){
html+='<div class="skeleton-card"><div class="sk-block sk-logo"></div><div class="sk-info"><div class="sk-block sk-line w60"></div><div class="sk-block sk-line w40"></div><div class="sk-block sk-line w80"></div><div class="sk-block sk-line w40"></div></div></div>';
}
return html;
}
function renderRestaurantList(){
$('resultTotal').textContent=state.total;
var grid=$('restGrid');
if(!state.restaurants.length){
grid.innerHTML='<div class="empty-state" style="grid-column:1/-1"><svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15.93A7.001 7.001 0 0 1 5 12c0-.39.04-.77.1-1.14L10 15.74v.19c0 .74.21 1.43.57 2.02.14-.02.28-.02.43-.02zm6.9-2.54c-.26-1.05-1.2-1.84-2.35-1.84-.16 0-.31.02-.46.05L12.5 11.5V9c1.1 0 2-.9 2-2V5c0-.55-.45-1-1-1h-1c-.55 0-1 .45-1 1v2c0 1.1.9 2 2 2v2.5l-3.5 1.05c-.6.18-1 .7-1 1.3v.39l-3.5 3.5C4.42 16.45 4 15.27 4 14c0-.29.02-.58.05-.86L8 9.19V9c0-1.1.9-2 2-2V5c0-.55-.45-1-1-1H8c-.55 0-1 .45-1 1v2c0 1.1.9 2 2 2v.19l-3.05 3.05C6.84 12.5 8.27 12 10 12c1.73 0 3.16.5 4.05 1.24L8 11.5V11c0-1.1-.9-2-2-2V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v2c0 1.1.9 2 2 2v.5L8 13.05V13c0-.6.4-1.12 1-1.3l3.5-1.05v-.15c0-.6.4-1.12 1-1.3L17 8.5V8c0-1.1.9-2 2-2V4c0-.55-.45-1-1-1h-1c-.55 0-1 .45-1 1z"/></svg><p>'+t('no_restaurant')+'</p><p class="sub">'+t('try_other')+'</p></div>';
$('loadMore').classList.add('hidden');
return;
}
grid.innerHTML=state.restaurants.map(function(r){
var tags=parseTags(r.tags).slice(0,3);
var open=r.status===1;
return '<div class="rest-card" onclick="openDetail('+r.id+')">'+
'<div class="rest-logo" style="background:'+restGrad(r.id)+'">'+
(r.logo?'<img src="'+esc(r.logo)+'" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" onerror="this.style.display=\'none\';this.parentNode.querySelector(\'svg\').style.display=\'\'">':'')+
'<svg viewBox="0 0 24 24" style="'+(r.logo?'display:none':'')+'">'+restSvg(r.id)+'</svg>'+
'<div class="status-badge '+(open?'':'closed')+'">'+(open?t('open'):t('closed'))+'</div>'+
'</div>'+
'<div class="rest-info">'+
'<div class="rest-name"><span class="status-dot '+(open?'':'closed')+'"></span>'+esc(r.name)+'</div>'+
'<div class="rest-tags">'+tags.map(function(tag){return '<span class="rest-tag">'+esc(tag)+'</span>'}).join('')+'</div>'+
'<div class="rest-rate"><span class="star">\u2605 '+money(r.rating)+'</span><span class="sales">'+t('monthly_sales')+r.sales+'</span></div>'+
'<div class="rest-meta">'+
'<span>'+t('min_order_prefix')+money(r.min_order)+'</span>'+
'<span>'+(r.delivery_fee==0?'<span class="free">'+t('free_delivery')+'</span>':t('delivery_fee_prefix')+money(r.delivery_fee))+'</span>'+
'<span>'+r.delivery_time+t('mins')+'</span>'+
'<span>'+esc(r.distance)+'</span>'+
'</div>'+
'</div>'+
'</div>';
}).join('');
var hasMore=state.restaurants.length<state.total;
$('loadMore').classList.toggle('hidden',!hasMore);
}
function selectCategory(id){
state.currentCategory = state.currentCategory===id ? 0 : id;
document.querySelectorAll('.cat-item').forEach(function(el,i){
el.classList.toggle('active', state.categories[i]&&state.categories[i].id===state.currentCategory);
});
var cat=state.categories.find(function(c){return c.id===state.currentCategory});
$('listTitle').textContent = cat ? cat.name+(currentLang==='en'?' ':'') : t('all_restaurants');
loadRestaurants(true);
$('restSection').scrollIntoView({behavior:'smooth',block:'start'});
}
document.querySelectorAll('.sort-tab').forEach(function(tab){
tab.addEventListener('click',function(){
document.querySelectorAll('.sort-tab').forEach(function(t){t.classList.remove('active')});
tab.classList.add('active');
state.sort=tab.dataset.sort;
loadRestaurants(true);
});
});
function doSearch(kw){
state.keyword=(kw||'').trim();
$('navSearchInput').value=state.keyword;
$('heroSearchInput').value=state.keyword;
loadRestaurants(true);
$('restSection').scrollIntoView({behavior:'smooth',block:'start'});
}
function goHome(){
state.currentCategory=0;
state.keyword='';
state.sort='';
$('navSearchInput').value='';
$('heroSearchInput').value='';
$('listTitle').textContent=t('all_restaurants');
document.querySelectorAll('.sort-tab').forEach(function(t,i){t.classList.toggle('active',i===0)});
document.querySelectorAll('.cat-item').forEach(function(el){el.classList.remove('active')});
loadRestaurants(true);
window.scrollTo({top:0,behavior:'smooth'});
}
/* ==========================================================
Restaurant Detail
========================================================== */
async function openDetail(id){
$('detailOverlay').classList.add('show');
$('detailPanel').classList.add('show');
document.body.style.overflow='hidden';
$('detailHeaderBg').style.background=restGrad(id);
$('detailHeaderBg').innerHTML='';
$('detailHeaderContent').innerHTML='<div style="color:#fff;font-size:16px">'+t('loading')+'</div>';
$('detailInfoRow').innerHTML='';
$('menuSidebar').innerHTML='<div style="padding:20px;color:#999;text-align:center">'+t('loading')+'</div>';
$('menuList').innerHTML='';
try{
var data=await api('/restaurants/'+id+'/menu');
state.currentRestaurant=data.restaurant||{};
state.menuData=data;
renderDetail();
}catch(e){
$('detailHeaderContent').innerHTML='<div style="color:#fff;font-size:16px">'+t('load_fail')+'</div>';
toast(t('detail_load_fail'),'warn');
}
}
function closeDetail(){
$('detailOverlay').classList.remove('show');
$('detailPanel').classList.remove('show');
document.body.style.overflow='';
}
function renderDetail(){
var r=state.currentRestaurant;
var tags=parseTags(r.tags);
$('detailHeaderBg').style.background=restGrad(r.id);
$('detailHeaderBg').innerHTML=r.cover?'<img src="'+esc(r.cover)+'" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" onerror="this.style.display=\'none\'">':'';
$('detailHeaderContent').innerHTML=
'<div class="detail-name">'+esc(r.name)+'</div>'+
'<div class="detail-tags">'+tags.map(function(tag){return '<span class="detail-tag">'+esc(tag)+'</span>'}).join('')+'<span class="detail-tag">'+esc(r.category_name)+'</span></div>'+
(r.notice?'<div class="detail-notice"><svg viewBox="0 0 24 24"><path d="M18 11v2h4v-2h-4zm-2 6.59 3.29-3.29-1.41-1.41L14.59 16 16 17.59zM12 4C7.03 4 3 8.03 3 13s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm0 16c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7zm-1-11h2v6h-2zm0 8h2v2h-2z"/></svg>'+esc(r.notice)+'</div>':'');
$('detailInfoRow').innerHTML=
'<div class="detail-info-item"><div class="v yellow">\u2605 '+money(r.rating)+'</div><div class="l">'+t('detail_rating')+'</div></div>'+
'<div class="detail-info-item"><div class="v">'+r.sales+'</div><div class="l">'+t('detail_monthly')+'</div></div>'+
'<div class="detail-info-item"><div class="v">'+r.delivery_time+'<small style="font-size:11px">'+t('mins')+'</small></div><div class="l">'+t('detail_delivery_time')+'</div></div>'+
'<div class="detail-info-item"><div class="v">'+esc(r.distance)+'</div><div class="l">'+t('detail_distance')+'</div></div>'+
'<div class="detail-info-item"><div class="v">\u00a5'+money(r.min_order)+'</div><div class="l">'+t('detail_min_order')+'</div></div>'+
'<div class="detail-info-item"><div class="v">'+(r.delivery_fee==0?t('detail_free'):('\u00a5'+money(r.delivery_fee)))+'</div><div class="l">'+t('detail_delivery_fee')+'</div></div>';
renderMenu();
}
function renderMenu(){
var cats=state.menuData.categories||[];
var items=state.menuData.menu_items||[];
$('menuSidebar').innerHTML=cats.map(function(c,i){return '<div class="menu-cat-item '+(i===0?'active':'')+'" data-cat="'+c.id+'" onclick="scrollToCat('+c.id+')">'+esc(c.name)+'</div>'}).join('');
var html='';
cats.forEach(function(cat){
var catItems=items.filter(function(it){return it.category_id===cat.id});
if(!catItems.length) return;
html+='<div class="menu-section" id="catsec-'+cat.id+'">'+
'<div class="menu-section-title">'+esc(cat.name)+'</div>';
catItems.forEach(function(it){
var tags=parseTags(it.tags).slice(0,2);
var hasOld=it.original_price>0 && it.original_price>it.price;
var qty=getQty(it.id);
html+='<div class="menu-item">'+
'<div class="menu-item-img" style="background:'+itemGrad(it.id)+'">'+
(it.image?'<img src="'+esc(it.image)+'" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" onerror="this.style.display=\'none\';this.parentNode.querySelector(\'svg\').style.display=\'\'">':'')+
(hasOld?'<span class="discount">%</span>':'')+
'<svg viewBox="0 0 24 24" style="'+(it.image?'display:none':'')+'">'+itemSvg(it.id)+'</svg>'+
'</div>'+
'<div class="menu-item-info">'+
'<div class="menu-item-name">'+esc(it.name)+'</div>'+
(it.description?'<div class="menu-item-desc">'+esc(it.description)+'</div>':'')+
'<div class="menu-item-tags">'+tags.map(function(tag){return '<span class="menu-item-tag">'+esc(tag)+'</span>'}).join('')+'</div>'+
'<div class="menu-item-bottom">'+
'<div class="menu-item-meta">'+
'<div class="menu-item-price">'+
'<span class="now">\u00a5<small></small>'+money(it.price)+'</span>'+
(hasOld?'<span class="old">\u00a5'+money(it.original_price)+'</span>':'')+
'</div>'+
'<div class="menu-item-stat"><span class="star">\u2605 '+money(it.rating)+'</span> \u00b7 '+t('monthly_sales')+it.sales+'</div>'+
'</div>'+
(qty>0?'<div class="qty-control">'+
'<button class="minus" onclick="changeQty('+it.id+',-1,event)">\u2212</button>'+
'<span class="num">'+qty+'</span>'+
'<button class="plus" onclick="changeQty('+it.id+',1,event)">+</button>'+
'</div>':'<button class="add-btn" onclick="addToCart('+it.id+',event)">'+
'<svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>'+
'</button>')+
'</div>'+
'</div>'+
'</div>';
});
html+='</div>';
});
$('menuList').innerHTML=html || '<div class="empty-state"><p>'+t('no_menu')+'</p></div>';
setupScrollSync();
}
var scrollSyncOn=true;
function setupScrollSync(){
var list=$('menuList');
list.onscroll=function(){
if(!scrollSyncOn) return;
var cats=state.menuData.categories||[];
var activeId=cats.length?cats[0].id:0;
for(var ci=0;ci<cats.length;ci++){
var c=cats[ci];
var sec=$('catsec-'+c.id);
if(sec && sec.offsetTop-60<=list.scrollTop){activeId=c.id}
}
document.querySelectorAll('.menu-cat-item').forEach(function(el){
el.classList.toggle('active',Number(el.dataset.cat)===activeId);
});
};
}
function scrollToCat(catId){
var sec=$('catsec-'+catId);
var list=$('menuList');
if(sec){
scrollSyncOn=false;
list.scrollTo({top:sec.offsetTop-10,behavior:'smooth'});
document.querySelectorAll('.menu-cat-item').forEach(function(el){
el.classList.toggle('active',Number(el.dataset.cat)===catId);
});
setTimeout(function(){scrollSyncOn=true},400);
}
}
/* ==========================================================
Cart
========================================================== */
function getQty(itemId){
if(!state.cart||!state.cart.items[itemId]) return 0;
return state.cart.items[itemId].qty;
}
function addToCart(itemId,e){
if(e){e.stopPropagation()}
var items=state.menuData.menu_items||[];
var item=items.find(function(it){return it.id===itemId});
if(!item) return;
var r=state.currentRestaurant;
if(state.cart && state.cart.restaurantId!==r.id && Object.keys(state.cart.items).length){
if(!confirm(tParam('cross_store_confirm',{name:state.cart.restaurantName}))) return;
state.cart=null;
}
if(!state.cart){
state.cart={restaurantId:r.id,restaurantName:r.name,deliveryFee:r.delivery_fee,minOrder:r.min_order,items:{}};
}
if(state.cart.items[itemId]){
state.cart.items[itemId].qty++;
}else{
state.cart.items[itemId]={item:item,qty:1};
}
toast(tParam('added_item',{name:item.name}),'success');
renderMenu();
renderCart();
}
function changeQty(itemId,delta,e){
if(e){e.stopPropagation()}
if(!state.cart||!state.cart.items[itemId]) return;
state.cart.items[itemId].qty+=delta;
if(state.cart.items[itemId].qty<=0){
delete state.cart.items[itemId];
}
renderMenu();
renderCart();
if(state.cart&&!Object.keys(state.cart.items).length){
toggleCartDetail(false);
}
}
function clearCart(){
if(!state.cart||!Object.keys(state.cart.items).length) return;
if(!confirm(t('clear_cart_confirm'))) return;
state.cart=null;
renderMenu();
renderCart();
toggleCartDetail(false);
toast(t('cart_cleared'),'warn');
}
function cartCount(){
if(!state.cart) return 0;
return Object.values(state.cart.items).reduce(function(s,v){return s+v.qty},0);
}
function cartTotal(){
if(!state.cart) return 0;
return Object.values(state.cart.items).reduce(function(s,v){return s+v.item.price*v.qty},0);
}
function renderCart(){
var count=cartCount();
var total=cartTotal();
var bar=$('cartBar');
if(count>0){
bar.style.display='block';
$('cartBarBox').classList.remove('empty');
$('cartIconCircle').classList.add('active');
$('cartTotal').classList.remove('empty');
$('cartBadge').classList.add('show');
$('navCartBadge').classList.add('show');
$('cartBadge').textContent=count;
$('navCartBadge').textContent=count;
$('cartTotal').querySelector('.t').innerHTML='\u00a5<small></small>'+money(total);
var fee=state.cart?state.cart.deliveryFee:0;
var min=state.cart?state.cart.minOrder:0;
var canCheckout=total>=min;
if(canCheckout){
$('cartSub').textContent=tParam('delivery_fee_text',{fee:money(fee)});
$('checkoutBtn').textContent=t('checkout');
}else{
$('cartSub').textContent=tParam('need_more',{diff:money(min-total)});
$('checkoutBtn').textContent=tParam('checkout_disabled',{diff:money(min-total)});
}
$('cartSub').style.display='';
$('checkoutBtn').classList.toggle('disabled',!canCheckout);
}else{
bar.style.display='none';
$('navCartBadge').classList.remove('show');
}
renderCartDetail();
}
function renderCartDetail(){
if(!state.cart||!Object.keys(state.cart.items).length){
$('cartDetailList').innerHTML='<div class="cart-detail-empty"><svg viewBox="0 0 24 24"><path d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49A1 1 0 0 0 20 4H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"/></svg><p>'+t('cart_empty')+'</p><p class="sub">'+t('cart_empty_hint')+'</p></div>';
return;
}
$('cartDetailTitle').textContent=tParam('cart_with_name',{name:state.cart.restaurantName});
var html=Object.values(state.cart.items).map(function(v){
var it=v.item;
return '<div class="cart-detail-item">'+
'<div style="width:44px;height:44px;border-radius:10px;background:'+itemGrad(it.id)+';display:flex;align-items:center;justify-content:center;flex-shrink:0;position:relative;overflow:hidden">'+
(it.image?'<img src="'+esc(it.image)+'" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" onerror="this.style.display=\'none\';this.parentNode.querySelector(\'svg\').style.display=\'\'">':'')+
'<svg viewBox="0 0 24 24" style="width:22px;height:22px;fill:rgba(255,255,255,.92);'+(it.image?'display:none':'')+'">'+itemSvg(it.id)+'</svg>'+
'</div>'+
'<span class="name">'+esc(it.name)+'</span>'+
'<span class="price">\u00a5'+money(it.price)+'</span>'+
'<div class="qty-control">'+
'<button class="minus" onclick="changeQty('+it.id+',-1)">\u2212</button>'+
'<span class="num">'+v.qty+'</span>'+
'<button class="plus" onclick="changeQty('+it.id+',1)">+</button>'+
'</div>'+
'</div>';
}).join('');
$('cartDetailList').innerHTML=html;
}
function toggleCartDetail(force){
var show = force===undefined ? !$('cartDetail').classList.contains('show') : force;
if(show && (!state.cart||!Object.keys(state.cart.items).length)){toast(t('cart_empty'),'warn');return}
$('cartDetail').classList.toggle('show',show);
$('cartDetailMask').classList.toggle('show',show);
}
function scrollToCart(){
if(!state.cart||!Object.keys(state.cart.items).length){toast(t('cart_empty'),'warn');return}
toggleCartDetail(true);
}
function checkout(){
if(!state.cart||!Object.keys(state.cart.items).length) return;
var total=cartTotal();
if(total<(state.cart?state.cart.minOrder:0)){toast(t('not_reach_min'),'warn');return}
toast(tParam('order_success',{total:money(total)}),'success');
state.cart=null;
renderMenu();
renderCart();
toggleCartDetail(false);
}
/* ==========================================================
onLangChange: re-render all dynamic content
========================================================== */
function onLangChange(lang){
renderHeroTags();
renderCategories();
renderFeatured();
renderRestaurantList();
renderCart();
if(state.currentRestaurant){
renderDetail();
}
}
/* ==========================================================
Bootstrap
========================================================== */
document.addEventListener('keydown',function(e){
if(e.key==='Escape') closeDetail();
});
applyI18n();
init();
</script>
</body>
</html>