feat(web): rebrand to FOOD MARKET green (#00B207) per mobile app logo

- Extract brand colors from food-market-app/.../AppIcon/appicon.svg
  (background #00B207, white FOOD, pale-green E8F5E9 MARKET)
- Add @theme custom colors in index.css:
  --color-brand #00B207, --color-brand-hover #009305, --color-brand-dark #007605,
  --color-brand-light #E8F5E9, --color-brand-tint #D7F2D9, --color-brand-foreground #FFFFFF
- Replace all violet-* Tailwind classes with var(--color-brand*) in:
  LoginPage, Button, Field (input+checkbox), SearchBar, AppLayout (nav active state)
- New Logo component: FM square badge + "FOOD" + "MARKET" typography in brand colors
- Put Logo in sidebar header and on LoginPage
- Replace Vite default favicon with branded SVG (green square + FOOD MARKET)
- Page title "FOOD MARKET", theme-color meta tag for mobile browsers

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nurdotnet 2026-04-21 20:53:42 +05:00
parent 1b2b5393fa
commit 5af8f74b5e
11 changed files with 65 additions and 16 deletions

View file

@ -4,7 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>food-market.web</title> <title>FOOD MARKET</title>
<meta name="theme-color" content="#00B207" />
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 387 B

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="456" height="456" viewBox="0 0 456 456">
<rect width="456" height="456" fill="#00B207"/>
</svg>

After

Width:  |  Height:  |  Size: 186 B

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="456" height="456" viewBox="0 0 456 456">
<g font-family="Arial, Helvetica, sans-serif" font-weight="900" fill="#FFFFFF" text-anchor="middle">
<text x="228" y="220" font-size="92" letter-spacing="4">FOOD</text>
<text x="228" y="306" font-size="64" letter-spacing="6" fill="#E8F5E9">MARKET</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 417 B

View file

@ -7,6 +7,7 @@ import {
LayoutDashboard, Package, FolderTree, Ruler, Percent, Tag, LayoutDashboard, Package, FolderTree, Ruler, Percent, Tag,
Users, Warehouse, Store as StoreIcon, Globe, Coins, LogOut, Users, Warehouse, Store as StoreIcon, Globe, Coins, LogOut,
} from 'lucide-react' } from 'lucide-react'
import { Logo } from './Logo'
interface MeResponse { interface MeResponse {
sub: string sub: string
@ -51,7 +52,7 @@ export function AppLayout() {
<div className="min-h-screen flex bg-slate-50 dark:bg-slate-950"> <div className="min-h-screen flex bg-slate-50 dark:bg-slate-950">
<aside className="w-60 flex-shrink-0 bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800 flex flex-col"> <aside className="w-60 flex-shrink-0 bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800 flex flex-col">
<div className="h-14 flex items-center px-5 border-b border-slate-200 dark:border-slate-800"> <div className="h-14 flex items-center px-5 border-b border-slate-200 dark:border-slate-800">
<span className="font-semibold text-slate-900 dark:text-slate-100">food-market</span> <Logo size={28} />
</div> </div>
<nav className="flex-1 overflow-y-auto py-3"> <nav className="flex-1 overflow-y-auto py-3">
@ -66,7 +67,7 @@ export function AppLayout() {
className={({ isActive }) => cn( className={({ isActive }) => cn(
'flex items-center gap-2.5 px-5 py-1.5 text-sm transition-colors', 'flex items-center gap-2.5 px-5 py-1.5 text-sm transition-colors',
isActive isActive
? 'bg-violet-50 dark:bg-violet-950/30 text-violet-700 dark:text-violet-300 font-medium border-r-2 border-violet-600' ? 'bg-[var(--color-brand-light)] dark:bg-[var(--color-brand-dark)]/20 text-[var(--color-brand-dark)] dark:text-[var(--color-brand-light)] font-medium border-r-2 border-[var(--color-brand)]'
: 'text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800/50' : 'text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800/50'
)} )}
> >

View file

@ -11,7 +11,7 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
} }
const variants: Record<Variant, string> = { const variants: Record<Variant, string> = {
primary: 'bg-violet-600 hover:bg-violet-700 text-white', primary: 'bg-[var(--color-brand)] hover:bg-[var(--color-brand-hover)] text-white',
secondary: 'bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 border border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-700', secondary: 'bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 border border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-700',
ghost: 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800', ghost: 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800',
danger: 'bg-red-600 hover:bg-red-700 text-white', danger: 'bg-red-600 hover:bg-red-700 text-white',

View file

@ -18,7 +18,7 @@ export function Field({ label, error, children, className }: FieldProps) {
) )
} }
const inputClass = 'w-full rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-violet-500 disabled:opacity-60' const inputClass = 'w-full rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-brand)] disabled:opacity-60'
export function TextInput(props: InputHTMLAttributes<HTMLInputElement>) { export function TextInput(props: InputHTMLAttributes<HTMLInputElement>) {
return <input {...props} className={cn(inputClass, props.className)} /> return <input {...props} className={cn(inputClass, props.className)} />
@ -50,7 +50,7 @@ export function Checkbox({
checked={checked} checked={checked}
disabled={disabled} disabled={disabled}
onChange={(e) => onChange(e.target.checked)} onChange={(e) => onChange(e.target.checked)}
className="w-4 h-4 rounded border-slate-300 text-violet-600 focus:ring-violet-500" className="w-4 h-4 rounded border-slate-300 text-[var(--color-brand)] focus:ring-[var(--color-brand)] accent-[var(--color-brand)]"
/> />
{label} {label}
</label> </label>

View file

@ -0,0 +1,25 @@
import { cn } from '@/lib/utils'
export function Logo({ size = 28, showText = true, className }: { size?: number; showText?: boolean; className?: string }) {
return (
<div className={cn('flex items-center gap-2.5', className)}>
<div
className="flex items-center justify-center rounded-md font-black text-white leading-none"
style={{
backgroundColor: 'var(--color-brand)',
width: size,
height: size,
fontSize: Math.floor(size * 0.38),
}}
>
FM
</div>
{showText && (
<div className="leading-tight">
<div className="font-black text-slate-900 dark:text-slate-100 tracking-wide">FOOD</div>
<div className="font-black text-xs tracking-[0.2em]" style={{ color: 'var(--color-brand)' }}>MARKET</div>
</div>
)}
</div>
)
}

View file

@ -15,7 +15,7 @@ export function SearchBar({ value, onChange, placeholder = 'Поиск…' }: Se
value={value} value={value}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
placeholder={placeholder} placeholder={placeholder}
className="pl-8 pr-3 py-1.5 w-64 rounded-md border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 text-sm focus:outline-none focus:ring-2 focus:ring-violet-500" className="pl-8 pr-3 py-1.5 w-64 rounded-md border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-brand)]"
/> />
</div> </div>
) )

View file

@ -1,8 +1,12 @@
@import "tailwindcss"; @import "tailwindcss";
@theme { @theme {
--color-brand: #6d28d9; --color-brand: #00B207;
--color-brand-foreground: #ffffff; --color-brand-hover: #009305;
--color-brand-dark: #007605;
--color-brand-light: #E8F5E9;
--color-brand-tint: #D7F2D9;
--color-brand-foreground: #FFFFFF;
} }
:root { :root {

View file

@ -1,6 +1,7 @@
import { useState, type FormEvent } from 'react' import { useState, type FormEvent } from 'react'
import { useNavigate, useLocation } from 'react-router-dom' import { useNavigate, useLocation } from 'react-router-dom'
import { login } from '@/lib/auth' import { login } from '@/lib/auth'
import { Logo } from '@/components/Logo'
export function LoginPage() { export function LoginPage() {
const navigate = useNavigate() const navigate = useNavigate()
@ -32,9 +33,9 @@ export function LoginPage() {
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="w-full max-w-md bg-white dark:bg-slate-800 rounded-xl shadow-lg p-8 space-y-5" className="w-full max-w-md bg-white dark:bg-slate-800 rounded-xl shadow-lg p-8 space-y-5"
> >
<div> <div className="space-y-3">
<h1 className="text-2xl font-semibold text-slate-900 dark:text-slate-100">food-market</h1> <Logo size={44} />
<p className="text-sm text-slate-500 mt-1">Вход в систему</p> <p className="text-sm text-slate-500">Вход в систему</p>
</div> </div>
<label className="block space-y-1.5"> <label className="block space-y-1.5">
@ -44,7 +45,7 @@ export function LoginPage() {
autoComplete="username" autoComplete="username"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
className="w-full rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-violet-500" className="w-full rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-brand)]"
required required
/> />
</label> </label>
@ -56,7 +57,7 @@ export function LoginPage() {
autoComplete="current-password" autoComplete="current-password"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
className="w-full rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-violet-500" className="w-full rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-brand)]"
required required
/> />
</label> </label>
@ -70,7 +71,7 @@ export function LoginPage() {
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full rounded-md bg-violet-600 hover:bg-violet-700 disabled:opacity-60 text-white font-medium py-2.5 text-sm transition-colors" className="w-full rounded-md bg-[var(--color-brand)] hover:bg-[var(--color-brand-hover)] disabled:opacity-60 text-white font-medium py-2.5 text-sm transition-colors"
> >
{loading ? 'Выполняется вход…' : 'Войти'} {loading ? 'Выполняется вход…' : 'Войти'}
</button> </button>