diff --git a/frontend/.gitignore b/frontend/.gitignore index 5ef6a520..a547bf36 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,41 +1,24 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug +# Logs +logs +*.log npm-debug.log* yarn-debug.log* yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md index e215bc4c..a36934d8 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,36 +1,16 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# React + Vite -## Getting Started +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. -First, run the development server: +Currently, two official plugins are available: -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +## React Compiler -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +## Expanding the ESLint configuration -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 00000000..ea36dd3d --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,21 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + parserOptions: { ecmaFeatures: { jsx: true } }, + }, + }, +]) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 00000000..e7006852 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Daily Sushi + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json index 72b917d9..f3287994 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,27 +1,33 @@ { "name": "frontend", - "version": "0.1.0", "private": true, + "version": "0.0.0", + "type": "module", "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" + "dev": "vite --host 0.0.0.0", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" }, "dependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0", - "next": "15.2.4" + "gsap": "^3.15.0", + "lucide-react": "^1.17.0", + "react": "^19.2.6", + "react-dom": "^19.2.6" }, "devDependencies": { - "typescript": "^5", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "@tailwindcss/postcss": "^4", - "tailwindcss": "^4", - "eslint": "^9", - "eslint-config-next": "15.2.4", - "@eslint/eslintrc": "^3" + "@eslint/js": "^10.0.1", + "@tailwindcss/vite": "^4.3.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "autoprefixer": "^10.5.0", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "postcss": "^8.5.15", + "tailwindcss": "^4.3.0", + "vite": "^8.0.12" } } diff --git a/frontend/postcss.config.mjs b/frontend/postcss.config.mjs index c7bcb4b1..7738160a 100644 --- a/frontend/postcss.config.mjs +++ b/frontend/postcss.config.mjs @@ -1,5 +1,5 @@ -const config = { - plugins: ["@tailwindcss/postcss"], +export default { + plugins: { + autoprefixer: {}, + }, }; - -export default config; diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 00000000..6893eb13 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg new file mode 100644 index 00000000..e9522193 --- /dev/null +++ b/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 00000000..ac89c08c --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,44 @@ +import { useEffect, useState } from "react"; +import Landing from "./landing/Landing.jsx"; +import Login from "./login/Login.jsx"; + +function getRouteFromHash() { + return window.location.hash.replace("#", "") || "/"; +} + +function App() { + const [route, setRoute] = useState(getRouteFromHash); + + useEffect(() => { + function handleHashChange() { + setRoute(getRouteFromHash()); + } + + window.addEventListener("hashchange", handleHashChange); + return () => window.removeEventListener("hashchange", handleHashChange); + }, []); + + function handleLoginClick() { + window.location.hash = "/login"; + } + + function handleHomeClick() { + window.location.hash = "/"; + } + + if (route === "/login") { + return ( + + ); + } + + return ( + + ); +} + +export default App; diff --git a/frontend/src/app/favicon.ico b/frontend/src/app/favicon.ico deleted file mode 100644 index 718d6fea..00000000 Binary files a/frontend/src/app/favicon.ico and /dev/null differ diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css deleted file mode 100644 index a2dc41ec..00000000 --- a/frontend/src/app/globals.css +++ /dev/null @@ -1,26 +0,0 @@ -@import "tailwindcss"; - -:root { - --background: #ffffff; - --foreground: #171717; -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; -} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx deleted file mode 100644 index f7fa87eb..00000000 --- a/frontend/src/app/layout.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - {children} - - - ); -} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx deleted file mode 100644 index e68abe6b..00000000 --- a/frontend/src/app/page.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import Image from "next/image"; - -export default function Home() { - return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - src/app/page.tsx - - . -
  2. -
  3. - Save and see your changes instantly. -
  4. -
- -
- - Vercel logomark - Deploy now - - - Read our docs - -
-
- -
- ); -} diff --git a/frontend/src/assets/Logo.png b/frontend/src/assets/Logo.png new file mode 100644 index 00000000..7c8523ce Binary files /dev/null and b/frontend/src/assets/Logo.png differ diff --git a/frontend/src/assets/bg-1.jpg b/frontend/src/assets/bg-1.jpg new file mode 100644 index 00000000..4fd8e469 Binary files /dev/null and b/frontend/src/assets/bg-1.jpg differ diff --git a/frontend/src/assets/cheesy-baked-sushi.jpg b/frontend/src/assets/cheesy-baked-sushi.jpg new file mode 100644 index 00000000..3d4d22ef Binary files /dev/null and b/frontend/src/assets/cheesy-baked-sushi.jpg differ diff --git a/frontend/src/assets/hero-section.jpg b/frontend/src/assets/hero-section.jpg new file mode 100644 index 00000000..7a724bb0 Binary files /dev/null and b/frontend/src/assets/hero-section.jpg differ diff --git a/frontend/src/assets/icon.jpg b/frontend/src/assets/icon.jpg new file mode 100644 index 00000000..3160469c Binary files /dev/null and b/frontend/src/assets/icon.jpg differ diff --git a/frontend/src/assets/japanese-bg.jpg b/frontend/src/assets/japanese-bg.jpg new file mode 100644 index 00000000..fd8e31f4 Binary files /dev/null and b/frontend/src/assets/japanese-bg.jpg differ diff --git a/frontend/src/assets/product-1.jpg b/frontend/src/assets/product-1.jpg new file mode 100644 index 00000000..8a21cb8f Binary files /dev/null and b/frontend/src/assets/product-1.jpg differ diff --git a/frontend/src/assets/product-2.jpg b/frontend/src/assets/product-2.jpg new file mode 100644 index 00000000..7275885c Binary files /dev/null and b/frontend/src/assets/product-2.jpg differ diff --git a/frontend/src/assets/product-3.jpg b/frontend/src/assets/product-3.jpg new file mode 100644 index 00000000..61fd5d18 Binary files /dev/null and b/frontend/src/assets/product-3.jpg differ diff --git a/frontend/src/assets/review-1.jpg b/frontend/src/assets/review-1.jpg new file mode 100644 index 00000000..35c4d699 Binary files /dev/null and b/frontend/src/assets/review-1.jpg differ diff --git a/frontend/src/assets/review-2.jpg b/frontend/src/assets/review-2.jpg new file mode 100644 index 00000000..5a2e8912 Binary files /dev/null and b/frontend/src/assets/review-2.jpg differ diff --git a/frontend/src/assets/review-3.jpg b/frontend/src/assets/review-3.jpg new file mode 100644 index 00000000..ee4e9595 Binary files /dev/null and b/frontend/src/assets/review-3.jpg differ diff --git a/frontend/src/assets/review-4.jpg b/frontend/src/assets/review-4.jpg new file mode 100644 index 00000000..cda3e4a2 Binary files /dev/null and b/frontend/src/assets/review-4.jpg differ diff --git a/frontend/src/assets/review-5.jpg b/frontend/src/assets/review-5.jpg new file mode 100644 index 00000000..2212adf3 Binary files /dev/null and b/frontend/src/assets/review-5.jpg differ diff --git a/frontend/src/components/ThemeComponents.jsx b/frontend/src/components/ThemeComponents.jsx new file mode 100644 index 00000000..cb9ec38e --- /dev/null +++ b/frontend/src/components/ThemeComponents.jsx @@ -0,0 +1,189 @@ +const shared = { + layout: { + shell: "mx-auto w-full max-w-7xl px-5 sm:px-6 lg:px-10", + section: "py-16 sm:py-20 lg:py-24", + eyebrow: "text-xs font-bold uppercase tracking-[0.26em]", + navLink: "text-sm font-semibold transition", + rounded: "rounded-[1.75rem]", + }, +}; + +export const themeComponents = { + dailySushi: { + ...shared, + page: "bg-[#FFF8F0] text-[#1F2937]", + sections: { + cream: "bg-[#FFF8F0]", + white: "bg-white", + charcoal: "bg-[#1F2937] text-white", + beige: "bg-[#F5E6D3]", + red: "bg-[#E63946] text-white", + }, + text: { + primary: "text-[#1F2937]", + secondary: "text-[#1F2937]/68", + body: "text-[#1F2937]/78", + muted: "text-[#1F2937]/48", + accent: "text-[#E63946]", + gold: "text-[#D4A017]", + cream: "text-[#F5E6D3]", + inverted: "text-white", + invertedMuted: "text-white/60", + invertedSecondary: "text-white/70", + heroBody: "text-white/84", + footerMuted: "text-white/48", + hoverInverted: "hover:text-white", + online: "text-emerald-700", + }, + nav: { + bar: "border-slate-200 bg-[#FFF8F0]/88 backdrop-blur-xl", + brand: "text-[#1F2937]", + link: "text-[#1F2937]/70 hover:text-[#E63946]", + mobileButton: "border-[#1F2937]/10 bg-white text-[#1F2937]", + mobilePanel: "border-[#1F2937]/10 bg-[#FFF8F0]", + mobileLink: "text-[#1F2937]/75 hover:bg-white", + }, + buttons: { + primary: "bg-[#E63946] text-white shadow-lg shadow-[#E63946]/25 hover:bg-[#c92d39]", + primarySoftShadow: "bg-[#E63946] text-white shadow-xl shadow-[#E63946]/30 hover:bg-[#c92d39]", + primaryPlain: "bg-[#E63946] text-white hover:bg-[#c92d39]", + secondaryOnDark: "border-white/30 bg-white/12 text-white backdrop-blur-md hover:bg-white hover:text-[#1F2937]", + outlineOnDark: "border-white/20 text-white hover:bg-white hover:text-[#1F2937]", + lightOnRed: "bg-white text-[#E63946] shadow-xl shadow-[#1F2937]/15 hover:bg-[#FFF8F0]", + iconLight: "border-[#1F2937]/10 bg-white text-[#1F2937] shadow-sm hover:bg-[#E63946] hover:text-white disabled:hover:bg-white disabled:hover:text-[#1F2937]", + chatSocial: "border-white/15 transition hover:bg-white hover:text-[#1F2937]", + quickReply: "border-[#E63946]/20 bg-white text-[#E63946] hover:bg-[#E63946] hover:text-white", + }, + cards: { + menu: "border-[#F5E6D3] bg-white shadow-sm hover:border-[#D4A017]/55 hover:shadow-2xl hover:shadow-[#E63946]/10", + menuImage: "bg-[#F5E6D3]", + price: "bg-[#F5E6D3] text-[#E63946]", + reason: "border-[#F5E6D3] bg-[#FFF8F0]/90 hover:border-[#E63946]/35", + dark: "border-white/10 bg-white/[0.06] hover:border-[#D4A017]/50 hover:bg-white/[0.09]", + reviewShell: "border-[#F5E6D3] bg-white shadow-2xl shadow-[#D4A017]/10", + reviewDivider: "border-[#F5E6D3]", + chatIncoming: "bg-[#FFF8F0] text-[#1F2937] shadow-sm", + chatOutgoing: "bg-[#E63946] text-white shadow-lg shadow-[#E63946]/20", + contact: "bg-[#1F2937] text-white shadow-2xl shadow-[#1F2937]/18", + contactItem: "bg-white/8 hover:bg-white/14", + step: "bg-[#FFF8F0]", + chatbot: "border-[#F5E6D3] bg-[#FFF8F0] shadow-2xl shadow-[#1F2937]/25", + chatbotFooter: "border-[#F5E6D3] bg-white/70", + chatbotBot: "bg-white text-[#1F2937] shadow-sm", + logoDisc: "bg-white", + }, + accents: { + redFill: "bg-[#E63946] text-white", + goldFill: "bg-[#D4A017] text-[#1F2937]", + creamFill: "bg-[#F5E6D3] text-[#E63946]", + goldIcon: "text-[#D4A017]", + redIcon: "text-[#E63946]", + dotActive: "bg-[#E63946]", + dotIdle: "bg-[#D4A017]/35", + dotCream: "bg-[#F5E6D3]", + onlinePill: "bg-emerald-50 text-emerald-700", + decorCream: "border-[#D4A017]/35 bg-[#FFF8F0]/10", + decorRed: "border-white/25 bg-[#E63946]/20", + decorWhite: "border-white/25 bg-white/10", + socialFooter: "border-white/15 text-white/70 hover:border-[#D4A017] hover:bg-[#D4A017] hover:text-[#1F2937]", + }, + borders: { + whiteSubtle: "border-white/20", + whiteSoft: "border-white/15", + whiteMuted: "border-white/10", + darkSubtle: "border-[#1F2937]/10", + beige: "border-[#F5E6D3]", + redSoft: "border-[#E63946]/20", + }, + hero: { + background: "bg-[#1F2937] text-white", + overlay: "bg-[linear-gradient(90deg,rgba(31,41,55,0.88)_0%,rgba(31,41,55,0.66)_42%,rgba(31,41,55,0.18)_100%)]", + eyebrow: "border-white/20 bg-white/12 text-[#F5E6D3]", + }, + footer: "bg-[#1F2937] text-white", + input: + "border-[#F5E6D3] bg-white text-[#1F2937] placeholder:text-[#1F2937]/35 focus:border-[#E63946]", + surface: { + translucentWhite: "bg-white/15", + closeButton: "bg-white/10 hover:bg-white/20", + }, + }, + light: { + ...shared, + page: "bg-white text-slate-950", + heroBackdrop: + "bg-[radial-gradient(circle_at_top_right,rgba(124,58,237,0.16),transparent_34%),linear-gradient(180deg,#ffffff_0%,#f8fafc_100%)]", + nav: "border-slate-200/80 bg-white/85 text-slate-950 shadow-sm shadow-slate-200/70 backdrop-blur-xl", + navLinkHover: "hover:text-slate-950", + text: { + primary: "text-slate-950", + secondary: "text-slate-600", + muted: "text-slate-500", + accent: "text-violet-700", + positive: "text-emerald-600", + inverted: "text-white", + }, + border: "border-slate-200", + divider: "border-slate-200", + card: "border-slate-200 bg-white shadow-sm shadow-slate-200/70", + cardHover: "hover:border-violet-200 hover:shadow-lg hover:shadow-violet-100/80", + panel: "border-slate-200 bg-slate-50", + elevatedPanel: "border-slate-200 bg-white shadow-2xl shadow-slate-200/80", + glassPanel: "border-white/70 bg-white/85 shadow-2xl shadow-slate-950/10 backdrop-blur-xl", + metricPanel: "border-slate-200 bg-white", + input: + "border-slate-300 bg-white text-slate-950 placeholder:text-slate-400 focus:border-violet-600 focus:ring-violet-600/20", + primaryButton: + "bg-violet-700 text-white shadow-lg shadow-violet-700/25 hover:bg-violet-800", + secondaryButton: "border-slate-300 bg-white text-slate-950 hover:bg-slate-100", + ghostButton: "text-slate-700 hover:bg-slate-100", + tabTrack: "border-slate-200 bg-slate-100", + activeTab: "bg-violet-700 text-white shadow-lg shadow-violet-700/20", + inactiveTab: "text-slate-500 hover:text-slate-950", + badge: "bg-violet-100 text-violet-700", + iconBox: "border-violet-100 bg-violet-50 text-violet-700", + heroOverlay: "bg-slate-950/55", + cta: "border-slate-200 bg-[linear-gradient(135deg,#ffffff_0%,#f5f3ff_58%,#ede9fe_100%)]", + }, + dark: { + ...shared, + page: "bg-[#08080d] text-white", + heroBackdrop: + "bg-[radial-gradient(circle_at_72%_16%,rgba(124,58,237,0.28),transparent_30%),linear-gradient(180deg,#08080d_0%,#0b0b12_54%,#08080d_100%)]", + nav: "border-white/10 bg-[#08080d]/86 text-white shadow-lg shadow-black/20 backdrop-blur-xl", + navLinkHover: "hover:text-white", + text: { + primary: "text-white", + secondary: "text-zinc-300", + muted: "text-zinc-400", + accent: "text-violet-400", + positive: "text-emerald-400", + inverted: "text-[#08080d]", + }, + border: "border-white/10", + divider: "border-white/10", + card: "border-white/10 bg-white/[0.045] shadow-none", + cardHover: "hover:border-violet-400/35 hover:bg-white/[0.07]", + panel: "border-white/10 bg-white/[0.04]", + elevatedPanel: "border-white/10 bg-white/[0.065] shadow-2xl shadow-black/35", + glassPanel: "border-white/10 bg-[#101018]/86 shadow-2xl shadow-black/45 backdrop-blur-xl", + metricPanel: "border-white/10 bg-[#111119]", + input: + "border-white/15 bg-[#12121a] text-white placeholder:text-zinc-500 focus:border-violet-400 focus:ring-violet-400/20", + primaryButton: + "bg-violet-600 text-white shadow-lg shadow-violet-700/25 hover:bg-violet-500", + secondaryButton: "border-white/15 bg-white/[0.04] text-white hover:bg-white/[0.08]", + ghostButton: "text-zinc-200 hover:bg-white/[0.08]", + tabTrack: "border-white/10 bg-white/[0.045]", + activeTab: "bg-violet-600 text-white shadow-lg shadow-violet-700/25", + inactiveTab: "text-zinc-400 hover:text-white", + badge: "bg-violet-500/12 text-violet-300", + iconBox: "border-violet-400/20 bg-violet-500/10 text-violet-300", + heroOverlay: "bg-black/64", + cta: "border-white/10 bg-[linear-gradient(135deg,#14141d_0%,#15111f_54%,#3b1265_100%)]", + }, +}; + +export function getThemeComponents(theme) { + return themeComponents[theme] ?? themeComponents.dark; +} diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 00000000..3c0740ea --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,70 @@ +@import "tailwindcss"; + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + margin: 0; + min-width: 320px; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, + "Segoe UI", sans-serif; + background: #fff8f0; + color: #1f2937; +} + +a, +button { + -webkit-tap-highlight-color: transparent; +} + +@keyframes float { + 0%, + 100% { + transform: translateY(0) rotate(0deg); + } + + 50% { + transform: translateY(-16px) rotate(3deg); + } +} + +@keyframes hero-title-shimmer { + 0%, + 100% { + text-shadow: 0 0 0 rgba(245, 230, 211, 0); + } + + 50% { + text-shadow: 0 10px 36px rgba(245, 230, 211, 0.34); + } +} + +.hero-title-animated { + animation: hero-title-shimmer 3.8s ease-in-out infinite; +} + +@keyframes sushi-drift { + 0%, + 100% { + filter: drop-shadow(0 22px 28px rgba(0, 0, 0, 0.24)); + } + + 35% { + filter: drop-shadow(-18px 34px 34px rgba(0, 0, 0, 0.3)); + } + + 70% { + filter: drop-shadow(14px 18px 30px rgba(0, 0, 0, 0.28)); + } +} + +.floating-sushi { + animation: sushi-drift 3.2s ease-in-out infinite; + transform-origin: center; + will-change: transform, filter; +} diff --git a/frontend/src/landing/Landing.jsx b/frontend/src/landing/Landing.jsx new file mode 100644 index 00000000..ee89985b --- /dev/null +++ b/frontend/src/landing/Landing.jsx @@ -0,0 +1,67 @@ +import { useEffect, useRef, useState } from "react"; +import { gsap } from "gsap"; +import { ScrollTrigger } from "gsap/ScrollTrigger"; +import BestSellersSection from "./components/BestSellersSection.jsx"; +import Chatbot from "./components/Chatbot.jsx"; +import DeliverySection from "./components/DeliverySection.jsx"; +import FeaturedProductsSection from "./components/FeaturedProductsSection.jsx"; +import FinalCtaSection from "./components/FinalCtaSection.jsx"; +import Footer from "./components/Footer.jsx"; +import HeroSection from "./components/HeroSection.jsx"; +import Navbar from "./components/Navbar.jsx"; +import ReviewsSection from "./components/ReviewsSection.jsx"; +import WhyChooseSection from "./components/WhyChooseSection.jsx"; +import { getThemeComponents } from "../components/ThemeComponents.jsx"; + +gsap.registerPlugin(ScrollTrigger); + +function Landing() { + const rootRef = useRef(null); + const [menuOpen, setMenuOpen] = useState(false); + const [chatOpen, setChatOpen] = useState(false); + const ui = getThemeComponents("dailySushi"); + + useEffect(() => { + const ctx = gsap.context(() => { + gsap.utils.toArray("[data-reveal]").forEach((item) => { + gsap.from(item, { + y: 38, + opacity: 0, + duration: 0.85, + ease: "power3.out", + scrollTrigger: { + trigger: item, + start: "top 84%", + }, + }); + }); + }, rootRef); + + return () => ctx.revert(); + }, []); + + return ( +
+ setMenuOpen(false)} + onMenuToggle={() => setMenuOpen((value) => !value)} + /> + + + + + + setChatOpen(true)} /> + +
+ setChatOpen(false)} + onOpen={() => setChatOpen(true)} + /> +
+ ); +} + +export default Landing; diff --git a/frontend/src/landing/components/BestSellersSection.jsx b/frontend/src/landing/components/BestSellersSection.jsx new file mode 100644 index 00000000..5f227724 --- /dev/null +++ b/frontend/src/landing/components/BestSellersSection.jsx @@ -0,0 +1,69 @@ +import { Star } from "lucide-react"; +import bakedSushiImg from "../../assets/product-1.jpg"; +import jellyImg from "../../assets/product-2.jpg"; +import makiImg from "../../assets/product-3.jpg"; +import { getThemeComponents } from "../../components/ThemeComponents.jsx"; + +const bestSellers = [ + { + name: "Signature Baked Sushi Tray", + description: "The rich, creamy crowd favorite for parties, merienda, and weekend cravings.", + rating: "4.9", + orders: "1.8k orders", + image: bakedSushiImg, + }, + { + name: "California Maki Party Box", + description: "Fresh rolls with mango and kani, made for sharing and easy gifting.", + rating: "4.8", + orders: "1.2k orders", + image: makiImg, + }, + { + name: "Mango Jelly Cups", + description: "A cool, creamy dessert that turns every sushi order into a full treat.", + rating: "4.9", + orders: "980 orders", + image: jellyImg, + }, +]; + +function BestSellersSection() { + const ui = getThemeComponents("dailySushi"); + + return ( +
+
+
+
+

Best Sellers

+

The orders customers come back for

+
+
+ +
+ {bestSellers.map((item) => ( +
+
+ {item.name} +
+
+
+ {Array.from({ length: 5 }).map((_, index) => ( + + ))} + {item.rating} +
+

{item.name}

+

{item.description}

+

{item.orders}

+
+
+ ))} +
+
+
+ ); +} + +export default BestSellersSection; diff --git a/frontend/src/landing/components/Chatbot.jsx b/frontend/src/landing/components/Chatbot.jsx new file mode 100644 index 00000000..71a6e62b --- /dev/null +++ b/frontend/src/landing/components/Chatbot.jsx @@ -0,0 +1,144 @@ +import { useEffect, useRef, useState } from "react"; +import { MessageCircle, Send, X } from "lucide-react"; +import Logo from "../../assets/Logo.png"; +import { getThemeComponents } from "../../components/ThemeComponents.jsx"; + +const quickReplies = [ + "Menu and prices", + "How to order", + "Delivery areas", + "Store hours", +]; + +function getBotReply(message) { + const text = message.toLowerCase(); + + if (text.includes("menu") || text.includes("price")) { + return "Our favorites are Baked Sushi, California Maki, Salmon Maki, Crabstick California Roll, Mango Jelly, and Lychee Jelly. Send your preferred items and quantity so we can prepare your order."; + } + + if (text.includes("deliver") || text.includes("area") || text.includes("location")) { + return "We serve Baras, Tanay, and Pililia, Rizal. Share your exact location so we can confirm delivery availability."; + } + + if (text.includes("hour") || text.includes("open") || text.includes("time")) { + return "We are open Monday to Friday, 3:00 PM - 11:00 PM."; + } + + if (text.includes("order") || text.includes("how")) { + return "To order, send your name, chosen items, quantity, delivery location, and preferred delivery time. You can also call or text 09667889034."; + } + + return "Thanks for messaging Daily Sushi. Please send your order details, location, and preferred delivery time. We will confirm availability shortly."; +} + +function Chatbot({ isOpen, onClose, onOpen }) { + const ui = getThemeComponents("dailySushi"); + const [messages, setMessages] = useState([ + { + author: "bot", + text: "Hi! Welcome to Daily Sushi. What would you like to order today?", + }, + ]); + const [draft, setDraft] = useState(""); + const messagesEndRef = useRef(null); + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [messages, isOpen]); + + function sendMessage(text) { + const trimmed = text.trim(); + + if (!trimmed) { + return; + } + + setMessages((current) => [ + ...current, + { author: "user", text: trimmed }, + { author: "bot", text: getBotReply(trimmed) }, + ]); + setDraft(""); + } + + function handleSubmit(event) { + event.preventDefault(); + sendMessage(draft); + } + + if (!isOpen) { + return ( + + ); + } + + return ( + + ); +} + +export default Chatbot; diff --git a/frontend/src/landing/components/DeliverySection.jsx b/frontend/src/landing/components/DeliverySection.jsx new file mode 100644 index 00000000..70bc3ef4 --- /dev/null +++ b/frontend/src/landing/components/DeliverySection.jsx @@ -0,0 +1,59 @@ +import { Clock, MapPin, MessageCircle, Phone } from "lucide-react"; +import { getThemeComponents } from "../../components/ThemeComponents.jsx"; + +const orderingSteps = [ + "Choose your favorite sushi trays, rolls, and desserts.", + "Send your order through chat, call, or social media.", + "Receive it freshly packed at your doorstep.", +]; + +function DeliverySection({ onChatOpen }) { + const ui = getThemeComponents("dailySushi"); + + return ( +
+
+ ); +} + +export default DeliverySection; diff --git a/frontend/src/landing/components/FeaturedProductsSection.jsx b/frontend/src/landing/components/FeaturedProductsSection.jsx new file mode 100644 index 00000000..69b88b73 --- /dev/null +++ b/frontend/src/landing/components/FeaturedProductsSection.jsx @@ -0,0 +1,71 @@ +import { ChevronRight } from "lucide-react"; +import product1 from "../../assets/product-1.jpg"; +import product2 from "../../assets/product-2.jpg"; +import product3 from "../../assets/product-3.jpg"; +import { getThemeComponents } from "../../components/ThemeComponents.jsx"; + +const products = [ + { + name: "Baked Sushi", + description: "Creamy, savory baked sushi tray layered with rice, seafood, kani, and toasted toppings.", + price: "PHP 110", + image: product1, + }, + { + name: "California Maki", + description: "Classic maki rolls with kani, cucumber, mango, and a clean sesame finish.", + price: "PHP 180", + image: product3, + }, + { + name: "Mango Jelly", + description: "Refreshing mango jelly dessert with silky cream and juicy fruit notes.", + price: "PHP 95", + image: product2, + }, +]; + +function FeaturedProductsSection() { + const ui = getThemeComponents("dailySushi"); + + return ( + + ); +} + +export default FeaturedProductsSection; diff --git a/frontend/src/landing/components/FinalCtaSection.jsx b/frontend/src/landing/components/FinalCtaSection.jsx new file mode 100644 index 00000000..dec55d18 --- /dev/null +++ b/frontend/src/landing/components/FinalCtaSection.jsx @@ -0,0 +1,26 @@ +import { CheckCircle2, ShoppingBag } from "lucide-react"; +import japaneseBgImg from "../../assets/japanese-bg.jpg"; +import { getThemeComponents } from "../../components/ThemeComponents.jsx"; + +function FinalCtaSection() { + const ui = getThemeComponents("dailySushi"); + + return ( +
+
+
+

+ + Made fresh for you +

+

Ready for Your Sushi Craving?

+ + Order Your Favorite Sushi Today + + +
+
+ ); +} + +export default FinalCtaSection; diff --git a/frontend/src/landing/components/Footer.jsx b/frontend/src/landing/components/Footer.jsx new file mode 100644 index 00000000..dd44813f --- /dev/null +++ b/frontend/src/landing/components/Footer.jsx @@ -0,0 +1,93 @@ +import { Clock, MapPin, Phone } from "lucide-react"; +import Logo from "../../assets/Logo.png"; +import { getThemeComponents } from "../../components/ThemeComponents.jsx"; + +const navLinks = [ + { label: "Home", href: "#home" }, + { label: "Menu", href: "#menu" }, + { label: "Reviews", href: "#reviews" }, + { label: "Contacts", href: "#contacts" }, +]; + +function FacebookIcon() { + return ( + + ); +} + +function InstagramIcon() { + return ( + + ); +} + +function Footer() { + const ui = getThemeComponents("dailySushi"); + + return ( + + ); +} + +export default Footer; diff --git a/frontend/src/landing/components/HeroSection.jsx b/frontend/src/landing/components/HeroSection.jsx new file mode 100644 index 00000000..6f3e35c6 --- /dev/null +++ b/frontend/src/landing/components/HeroSection.jsx @@ -0,0 +1,154 @@ +import { useEffect, useRef } from "react"; +import { ShoppingBag, Sparkles } from "lucide-react"; +import { gsap } from "gsap"; +import { ScrollTrigger } from "gsap/ScrollTrigger"; +import heroImg from "../../assets/hero-section.jpg"; +import logoImg from "../../assets/Logo.png"; +import { getThemeComponents } from "../../components/ThemeComponents.jsx"; + +gsap.registerPlugin(ScrollTrigger); + +function HeroSection() { + const heroRef = useRef(null); + const ui = getThemeComponents("dailySushi"); + + useEffect(() => { + const ctx = gsap.context(() => { + const timeline = gsap.timeline({ defaults: { ease: "power3.out" } }); + + timeline + .from("[data-hero-media]", { + scale: 1.14, + opacity: 0.72, + duration: 1.45, + }) + .from("[data-hero-copy]", { + y: 44, + opacity: 0, + duration: 0.9, + stagger: 0.13, + }, "-=0.85") + .from("[data-hero-badge]", { + y: -18, + opacity: 0, + scale: 0.82, + duration: 0.7, + ease: "back.out(1.8)", + }, "-=0.35"); + + timeline.from("[data-floating-sushi]", { + x: 120, + y: 28, + opacity: 0, + rotate: 18, + scale: 0.78, + duration: 1, + ease: "back.out(1.6)", + }, "-=0.55"); + + gsap.to("[data-hero-media]", { + yPercent: 12, + scale: 1.08, + ease: "none", + scrollTrigger: { + trigger: heroRef.current, + start: "top top", + end: "bottom top", + scrub: true, + }, + }); + + gsap.to("[data-hero-deco]", { + y: "random(-18, 18)", + x: "random(-8, 8)", + rotate: "random(-8, 8)", + duration: "random(3.2, 5.4)", + ease: "sine.inOut", + repeat: -1, + yoyo: true, + stagger: 0.18, + }); + + gsap.to("[data-hero-badge]", { + y: -8, + duration: 1.6, + ease: "sine.inOut", + repeat: -1, + yoyo: true, + }); + + gsap.to("[data-floating-sushi]", { + y: -24, + x: -10, + rotate: -5, + duration: 2.8, + ease: "sine.inOut", + repeat: -1, + yoyo: true, + }); + + gsap.to("[data-floating-sushi-ring]", { + rotate: 360, + duration: 13, + ease: "none", + repeat: -1, + }); + + gsap.to("[data-hero-cta]", { + y: -5, + duration: 1.8, + ease: "sine.inOut", + repeat: -1, + yoyo: true, + }); + }, heroRef); + + return () => ctx.revert(); + }, []); + + return ( +
+
+ Beautifully arranged sushi platter with rolls and desserts +
+
+ +
+ ); +} + +export default HeroSection; diff --git a/frontend/src/landing/components/Navbar.jsx b/frontend/src/landing/components/Navbar.jsx new file mode 100644 index 00000000..7a79517a --- /dev/null +++ b/frontend/src/landing/components/Navbar.jsx @@ -0,0 +1,63 @@ +import { Menu, X } from "lucide-react"; +import Logo from "../../assets/Logo.png"; +import { getThemeComponents } from "../../components/ThemeComponents.jsx"; + +const navLinks = [ + { label: "Home", href: "#home" }, + { label: "Menu", href: "#menu" }, + { label: "Reviews", href: "#reviews" }, + { label: "Contacts", href: "#contacts" }, +]; + +function Navbar({ menuOpen, onMenuToggle, onMenuClose }) { + const ui = getThemeComponents("dailySushi"); + + return ( + + ); +} + +export default Navbar; diff --git a/frontend/src/landing/components/ReviewsSection.jsx b/frontend/src/landing/components/ReviewsSection.jsx new file mode 100644 index 00000000..44b44949 --- /dev/null +++ b/frontend/src/landing/components/ReviewsSection.jsx @@ -0,0 +1,79 @@ +import { Star } from "lucide-react"; +import review1 from "../../assets/review-1.jpg"; +import review2 from "../../assets/review-2.jpg"; +import review3 from "../../assets/review-3.jpg"; +import review4 from "../../assets/review-4.jpg"; +import review5 from "../../assets/review-5.jpg"; +import { getThemeComponents } from "../../components/ThemeComponents.jsx"; + +const reviews = [ + { + name: "Customer 1", + detail: "Ordered for family dinner", + image: review1, + }, + { + name: "Customer 2", + detail: "Repeat customer", + image: review2, + }, + { + name: "Customer 3", + detail: "Team snack order", + image: review3, + }, + { + name: "Customer 4", + detail: "Birthday spread", + image: review4, + }, + { + name: "Customer 5", + detail: "Weekend order", + image: review5, + }, +]; + +function ReviewsSection() { + const ui = getThemeComponents("dailySushi"); + + return ( +
+
+
+

Customer Reviews

+

Real messages from happy customers

+

+ We deliver happiness to your doorstep, and our customers can't help but share their joy. Read what they have to say about their experience with Daily Sushi. +

+
+ +
+ {reviews.map((review) => ( +
+
+ {`Review +
+
+
+ {Array.from({ length: 5 }).map((_, starIndex) => ( + + ))} + 5.0 +
+
+
+

{review.name}

+

{review.detail}

+
+
+
+
+ ))} +
+
+
+ ); +} + +export default ReviewsSection; diff --git a/frontend/src/landing/components/WhyChooseSection.jsx b/frontend/src/landing/components/WhyChooseSection.jsx new file mode 100644 index 00000000..6de338ea --- /dev/null +++ b/frontend/src/landing/components/WhyChooseSection.jsx @@ -0,0 +1,43 @@ +import { HeartHandshake, ShieldCheck, Sparkles, Truck, Wallet } from "lucide-react"; +import japaneseBgImg from "../../assets/japanese-bg.jpg"; +import { getThemeComponents } from "../../components/ThemeComponents.jsx"; + +const reasons = [ + { title: "Fresh Ingredients Daily", text: "Rice, seafood, fruit, and toppings are prepared in small batches.", icon: Sparkles }, + { title: "Handmade with Care", text: "Every tray and roll is assembled by hand for consistent flavor.", icon: HeartHandshake }, + { title: "Fast Delivery", text: "Packed neatly and sent out quickly while flavors stay fresh.", icon: Truck }, + { title: "Affordable Prices", text: "Premium comfort food with bundles that work for cravings and gatherings.", icon: Wallet }, + { title: "Premium Quality", text: "Clean presentation, balanced flavors, and reliable daily preparation.", icon: ShieldCheck }, +]; + +function WhyChooseSection() { + const ui = getThemeComponents("dailySushi"); + + return ( +
+
+
+
+

Why Choose Daily Sushi

+

Small-batch quality, polished for everyday delivery

+
+
+ {reasons.map((reason) => { + const Icon = reason.icon; + return ( +
+
+ +
+

{reason.title}

+

{reason.text}

+
+ ); + })} +
+
+
+ ); +} + +export default WhyChooseSection; diff --git a/frontend/src/login/Login.jsx b/frontend/src/login/Login.jsx new file mode 100644 index 00000000..e2c4f551 --- /dev/null +++ b/frontend/src/login/Login.jsx @@ -0,0 +1,84 @@ +import { getThemeComponents } from "../components/ThemeComponents.jsx"; + +function Login({ onBackHome }) { + const ui = getThemeComponents("light"); + + return ( +
+
+ + +
+
+
+ +
+ +
+

Portal access

+

Sign In

+

Codebility Portal

+ +
+ + + +
+ + +
+
+
+
+
+ ); +} + +export default Login; diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx new file mode 100644 index 00000000..b9a1a6de --- /dev/null +++ b/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 00000000..3ae59ba6 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from "vite" +import react from "@vitejs/plugin-react" +import tailwindcss from "@tailwindcss/vite" + +export default defineConfig({ + plugins: [ + react(), + tailwindcss(), + ], + server: { + host: "0.0.0.0", + port: 5173, + }, +})