diff --git a/Planteer/Planteer/__init__.py b/Planteer/Planteer/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/Planteer/asgi.py b/Planteer/Planteer/asgi.py
new file mode 100644
index 0000000..7693f2d
--- /dev/null
+++ b/Planteer/Planteer/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for Planteer project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Planteer.settings')
+
+application = get_asgi_application()
diff --git a/Planteer/Planteer/settings.py b/Planteer/Planteer/settings.py
new file mode 100644
index 0000000..91cba2e
--- /dev/null
+++ b/Planteer/Planteer/settings.py
@@ -0,0 +1,132 @@
+"""
+Django settings for Planteer project.
+
+Generated by 'django-admin startproject' using Django 6.0.3.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/6.0/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/6.0/ref/settings/
+"""
+
+
+from pathlib import Path
+import os
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-%0cc1#2i4e^(nfrm&@y5#6tcqx@lnftl89^+@cj*l(9l9l44vx'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'main',
+ 'plants',
+ 'accounts',
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'Planteer.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [BASE_DIR / 'templates'],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'Planteer.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': BASE_DIR / 'db.sqlite3',
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/6.0/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/6.0/howto/static-files/
+
+STATIC_URL = '/static/'
+STATICFILES_DIRS = [BASE_DIR / 'static']
+
+MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+LOGIN_URL = 'accounts:signin'
+LOGIN_REDIRECT_URL = '/'
+LOGOUT_REDIRECT_URL = '/'
diff --git a/Planteer/Planteer/urls.py b/Planteer/Planteer/urls.py
new file mode 100644
index 0000000..ea8b7ac
--- /dev/null
+++ b/Planteer/Planteer/urls.py
@@ -0,0 +1,30 @@
+"""
+URL configuration for Planteer project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/6.0/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path, include
+from django.conf import settings
+from django.conf.urls.static import static
+
+urlpatterns = [
+ path('admin/', admin.site.urls),
+ path('', include('main.urls')),
+ path('plants/', include('plants.urls')),
+ path('accounts/', include('accounts.urls')),
+]
+
+if settings.DEBUG:
+ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
\ No newline at end of file
diff --git a/Planteer/Planteer/wsgi.py b/Planteer/Planteer/wsgi.py
new file mode 100644
index 0000000..f14ac6f
--- /dev/null
+++ b/Planteer/Planteer/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for Planteer project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Planteer.settings')
+
+application = get_wsgi_application()
diff --git a/Planteer/accounts/__init__.py b/Planteer/accounts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/accounts/admin.py b/Planteer/accounts/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/Planteer/accounts/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/Planteer/accounts/apps.py b/Planteer/accounts/apps.py
new file mode 100644
index 0000000..9b3fc5a
--- /dev/null
+++ b/Planteer/accounts/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class AccountsConfig(AppConfig):
+ name = 'accounts'
diff --git a/Planteer/accounts/forms.py b/Planteer/accounts/forms.py
new file mode 100644
index 0000000..c9c4fef
--- /dev/null
+++ b/Planteer/accounts/forms.py
@@ -0,0 +1,48 @@
+from django import forms
+from django.contrib.auth.models import User
+
+
+class SignUpForm(forms.Form):
+ username = forms.CharField(
+ widget=forms.TextInput(attrs={'placeholder': 'Username'})
+ )
+ email = forms.EmailField(
+ widget=forms.EmailInput(attrs={'placeholder': 'Email'})
+ )
+ password = forms.CharField(
+ widget=forms.PasswordInput(attrs={'placeholder': 'Password'})
+ )
+ confirm_password = forms.CharField(
+ widget=forms.PasswordInput(attrs={'placeholder': 'Confirm Password'})
+ )
+
+ def clean_username(self):
+ username = self.cleaned_data.get('username', '').strip()
+ if User.objects.filter(username=username).exists():
+ raise forms.ValidationError('This username is already taken.')
+ return username
+
+ def clean_email(self):
+ email = self.cleaned_data.get('email', '').strip()
+ if User.objects.filter(email=email).exists():
+ raise forms.ValidationError('This email is already registered.')
+ return email
+
+ def clean(self):
+ cleaned_data = super().clean()
+ password = cleaned_data.get('password')
+ confirm_password = cleaned_data.get('confirm_password')
+
+ if password and confirm_password and password != confirm_password:
+ raise forms.ValidationError('Passwords do not match.')
+
+ return cleaned_data
+
+
+class SignInForm(forms.Form):
+ username = forms.CharField(
+ widget=forms.TextInput(attrs={'placeholder': 'Username'})
+ )
+ password = forms.CharField(
+ widget=forms.PasswordInput(attrs={'placeholder': 'Password'})
+ )
diff --git a/Planteer/accounts/migrations/__init__.py b/Planteer/accounts/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/accounts/models.py b/Planteer/accounts/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/Planteer/accounts/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/Planteer/accounts/templates/accounts/signin.html b/Planteer/accounts/templates/accounts/signin.html
new file mode 100644
index 0000000..084f00b
--- /dev/null
+++ b/Planteer/accounts/templates/accounts/signin.html
@@ -0,0 +1,44 @@
+{% extends 'base.html' %}
+
+{% block title %}Sign In{% endblock %}
+
+{% block content %}
+
+
+
+
Sign In
+
Welcome back
+
+ {% if error_message %}
+
+ {% endif %}
+
+
+
+
Don't have an account? Sign Up
+
+
+
+{% endblock %}
diff --git a/Planteer/accounts/templates/accounts/signup.html b/Planteer/accounts/templates/accounts/signup.html
new file mode 100644
index 0000000..da6eac2
--- /dev/null
+++ b/Planteer/accounts/templates/accounts/signup.html
@@ -0,0 +1,62 @@
+{% extends 'base.html' %}
+
+{% block title %}Sign Up{% endblock %}
+
+{% block content %}
+
+
+
+
Sign Up
+
Create a new account
+
+ {% if form.non_field_errors %}
+
+ {% endif %}
+
+
+
+
Already have an account? Sign In
+
+
+
+{% endblock %}
diff --git a/Planteer/accounts/tests.py b/Planteer/accounts/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/Planteer/accounts/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/Planteer/accounts/urls.py b/Planteer/accounts/urls.py
new file mode 100644
index 0000000..f954140
--- /dev/null
+++ b/Planteer/accounts/urls.py
@@ -0,0 +1,10 @@
+from django.urls import path
+from . import views
+
+app_name = "accounts"
+
+urlpatterns = [
+ path('signup/', views.sign_up, name='signup'),
+ path('signin/', views.sign_in, name='signin'),
+ path('logout/', views.log_out, name='logout'),
+]
diff --git a/Planteer/accounts/views.py b/Planteer/accounts/views.py
new file mode 100644
index 0000000..167b858
--- /dev/null
+++ b/Planteer/accounts/views.py
@@ -0,0 +1,54 @@
+from django.contrib.auth import authenticate, login, logout
+from django.contrib.auth.models import User
+from django.shortcuts import render, redirect
+
+from .forms import SignUpForm, SignInForm
+
+
+def sign_up(request):
+ if request.user.is_authenticated:
+ return redirect('main:home')
+
+ if request.method == 'POST':
+ form = SignUpForm(request.POST)
+ if form.is_valid():
+ user = User.objects.create_user(
+ username=form.cleaned_data['username'],
+ email=form.cleaned_data['email'],
+ password=form.cleaned_data['password'],
+ )
+ login(request, user)
+ return redirect('main:home')
+ else:
+ form = SignUpForm()
+
+ return render(request, 'accounts/signup.html', {'form': form})
+
+
+def sign_in(request):
+ if request.user.is_authenticated:
+ return redirect('main:home')
+
+ form = SignInForm(request.POST or None)
+ error_message = None
+
+ if request.method == 'POST' and form.is_valid():
+ user = authenticate(
+ request,
+ username=form.cleaned_data['username'],
+ password=form.cleaned_data['password'],
+ )
+ if user is not None:
+ login(request, user)
+ return redirect('main:home')
+ error_message = 'Invalid username or password'
+
+ return render(request, 'accounts/signin.html', {
+ 'form': form,
+ 'error_message': error_message,
+ })
+
+
+def log_out(request):
+ logout(request)
+ return redirect('main:home')
diff --git a/Planteer/db.sqlite3 b/Planteer/db.sqlite3
new file mode 100644
index 0000000..237702b
Binary files /dev/null and b/Planteer/db.sqlite3 differ
diff --git a/Planteer/main/__init__.py b/Planteer/main/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/main/admin.py b/Planteer/main/admin.py
new file mode 100644
index 0000000..d407bfd
--- /dev/null
+++ b/Planteer/main/admin.py
@@ -0,0 +1,9 @@
+from django.contrib import admin
+from .models import ContactMessage
+
+
+@admin.register(ContactMessage)
+class ContactMessageAdmin(admin.ModelAdmin):
+ list_display = ['first_name', 'last_name', 'email', 'created_at']
+ list_filter = ['created_at']
+ search_fields = ['first_name', 'last_name', 'email']
\ No newline at end of file
diff --git a/Planteer/main/apps.py b/Planteer/main/apps.py
new file mode 100644
index 0000000..833bff6
--- /dev/null
+++ b/Planteer/main/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class MainConfig(AppConfig):
+ name = 'main'
diff --git a/Planteer/main/forms.py b/Planteer/main/forms.py
new file mode 100644
index 0000000..75b991b
--- /dev/null
+++ b/Planteer/main/forms.py
@@ -0,0 +1,27 @@
+from django import forms
+from .models import ContactMessage
+
+
+class ContactForm(forms.ModelForm):
+
+ class Meta:
+ model = ContactMessage
+ fields = ['first_name', 'last_name', 'email', 'message']
+ widgets = {
+ 'first_name': forms.TextInput(attrs={'placeholder': 'Jane', 'required': True}),
+ 'last_name': forms.TextInput(attrs={'placeholder': 'Smitherton', 'required': True}),
+ 'email': forms.EmailInput(attrs={'placeholder': 'email@fakedomain.net', 'required': True}),
+ 'message': forms.Textarea(attrs={'placeholder': 'Enter your question or message', 'rows': 5, 'required': True}),
+ }
+
+ def clean_first_name(self):
+ name = self.cleaned_data.get('first_name', '').strip()
+ if len(name) < 2:
+ raise forms.ValidationError("First name must be at least 2 characters.")
+ return name
+
+ def clean_message(self):
+ msg = self.cleaned_data.get('message', '').strip()
+ if len(msg) < 10:
+ raise forms.ValidationError("Message must be at least 10 characters.")
+ return msg
\ No newline at end of file
diff --git a/Planteer/main/migrations/0001_initial.py b/Planteer/main/migrations/0001_initial.py
new file mode 100644
index 0000000..b96653a
--- /dev/null
+++ b/Planteer/main/migrations/0001_initial.py
@@ -0,0 +1,29 @@
+# Generated by Django 6.0.3 on 2026-04-17 23:45
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ContactMessage',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100)),
+ ('email', models.EmailField(max_length=254)),
+ ('subject', models.CharField(max_length=200)),
+ ('message', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('is_read', models.BooleanField(default=False)),
+ ],
+ options={
+ 'ordering': ['-created_at'],
+ },
+ ),
+ ]
diff --git a/Planteer/main/migrations/0002_remove_contactmessage_is_read_and_more.py b/Planteer/main/migrations/0002_remove_contactmessage_is_read_and_more.py
new file mode 100644
index 0000000..8e927a0
--- /dev/null
+++ b/Planteer/main/migrations/0002_remove_contactmessage_is_read_and_more.py
@@ -0,0 +1,37 @@
+# Generated by Django 6.0.4 on 2026-04-19 16:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('main', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='contactmessage',
+ name='is_read',
+ ),
+ migrations.RemoveField(
+ model_name='contactmessage',
+ name='name',
+ ),
+ migrations.RemoveField(
+ model_name='contactmessage',
+ name='subject',
+ ),
+ migrations.AddField(
+ model_name='contactmessage',
+ name='first_name',
+ field=models.CharField(default='', max_length=100),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='contactmessage',
+ name='last_name',
+ field=models.CharField(default='', max_length=100),
+ preserve_default=False,
+ ),
+ ]
diff --git a/Planteer/main/migrations/__init__.py b/Planteer/main/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/main/models.py b/Planteer/main/models.py
new file mode 100644
index 0000000..e64730e
--- /dev/null
+++ b/Planteer/main/models.py
@@ -0,0 +1,15 @@
+from django.db import models
+
+
+class ContactMessage(models.Model):
+ first_name = models.CharField(max_length=100)
+ last_name = models.CharField(max_length=100)
+ email = models.EmailField()
+ message = models.TextField()
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ ordering = ['-created_at']
+
+ def __str__(self):
+ return f"{self.first_name} {self.last_name} - {self.email}"
\ No newline at end of file
diff --git a/Planteer/main/templates/main/contact.html b/Planteer/main/templates/main/contact.html
new file mode 100644
index 0000000..70dcdb3
--- /dev/null
+++ b/Planteer/main/templates/main/contact.html
@@ -0,0 +1,59 @@
+{% extends 'base.html' %}
+
+{% block title %}Contact Us{% endblock %}
+
+{% block content %}
+
+
+
Contact us
+
Subheading for description or instructions
+
+ {% if success %}
+
Your message has been sent successfully!
+ {% endif %}
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/main/templates/main/contact_messages.html b/Planteer/main/templates/main/contact_messages.html
new file mode 100644
index 0000000..0934f6e
--- /dev/null
+++ b/Planteer/main/templates/main/contact_messages.html
@@ -0,0 +1,29 @@
+{% extends 'base.html' %}
+
+{% block title %}Messages{% endblock %}
+
+{% block content %}
+
+
+
Messages from Users
+
+ {% if messages_list %}
+
+ {% for msg in messages_list %}
+
+
✉
+
{{ msg.first_name }} {{ msg.last_name }}
+
{{ msg.email }}
+
{{ msg.message }}
+
+ {% endfor %}
+
+ {% else %}
+
+
No messages yet
+
When someone contacts you, their messages will appear here.
+
+ {% endif %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/main/templates/main/home.html b/Planteer/main/templates/main/home.html
new file mode 100644
index 0000000..2020f98
--- /dev/null
+++ b/Planteer/main/templates/main/home.html
@@ -0,0 +1,53 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block title %}Home{% endblock %}
+
+{% block content %}
+
+
+
+
Planteer
+
Plant Database For Plants Lovers
+
+
+
+
+
+
+
+
+
+ {% if featured_plants %}
+
+
+ {% else %}
+
No plants yet. Add one!
+ {% endif %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/main/tests.py b/Planteer/main/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/Planteer/main/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/Planteer/main/urls.py b/Planteer/main/urls.py
new file mode 100644
index 0000000..2a6a03c
--- /dev/null
+++ b/Planteer/main/urls.py
@@ -0,0 +1,10 @@
+from django.urls import path
+from . import views
+
+app_name = "main"
+
+urlpatterns = [
+ path('', views.home, name='home'),
+ path('contact/', views.contact, name='contact'),
+ path('contact/messages/', views.contact_messages, name='contact_messages'),
+]
\ No newline at end of file
diff --git a/Planteer/main/views.py b/Planteer/main/views.py
new file mode 100644
index 0000000..255203e
--- /dev/null
+++ b/Planteer/main/views.py
@@ -0,0 +1,38 @@
+from django.shortcuts import render, redirect
+from django.http import HttpRequest, HttpResponse
+from plants.models import Plant, Category
+from .forms import ContactForm
+from .models import ContactMessage
+
+
+def home(request: HttpRequest):
+ featured_plants = Plant.objects.all()[:6]
+ categories = Category.objects.all()
+
+ return render(request, 'main/home.html', {
+ 'featured_plants': featured_plants,
+ 'categories': categories,
+ })
+
+
+def contact(request: HttpRequest):
+ if request.method == 'POST':
+ form = ContactForm(request.POST)
+ if form.is_valid():
+ form.save()
+ return render(request, 'main/contact.html', {
+ 'form': ContactForm(),
+ 'success': True,
+ })
+ else:
+ form = ContactForm()
+
+ return render(request, 'main/contact.html', {'form': form})
+
+
+def contact_messages(request: HttpRequest):
+ messages_list = ContactMessage.objects.all()
+
+ return render(request, 'main/contact_messages.html', {
+ 'messages_list': messages_list,
+ })
\ No newline at end of file
diff --git a/Planteer/manage.py b/Planteer/manage.py
new file mode 100644
index 0000000..bd0a64a
--- /dev/null
+++ b/Planteer/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+ """Run administrative tasks."""
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Planteer.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/Planteer/media/flags/Egypt.htm b/Planteer/media/flags/Egypt.htm
new file mode 100644
index 0000000..8935738
--- /dev/null
+++ b/Planteer/media/flags/Egypt.htm
@@ -0,0 +1,3043 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Flag of Egypt | History, Colors, Symbols | Britannica
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ At a Glance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Table of Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Table of Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ask Anything
+
+
+
+
flag of Egypt horizontally striped red-white-black national flag with a central coat of arms in the form of a gold eagle. The flag has a width-to-length ratio of 2 to 3. (more) Many flags have been flown over Egypt in its thousands of years of history, but its first true national flag was established only on February 16, 1915, after the British, who had effectively controlled the country since 1882, formally proclaimed a protectorate to deter restoration of Egypt’s nominal ties to the Ottoman Empire . The flag previously used by the khedive (the Ottoman viceroy in Egypt) became the national flag; it was red with three white crescents and stars. Participants in the revolt of 1919 hoisted a green flag with a white crescent and cross, indicating unity between Muslims and Christians in the struggle for independence. A similar flag with three white stars instead of the cross was adopted on December 10, 1923, following the proclamation of the Kingdom of Egypt.
The 1952 revolt established the Arab Liberation Flag , which had red-white-black horizontal stripes and a gold eagle. That flag was often flown beside the national flag but did not itself have official status; nevertheless, its design was reflected in the official 1958 national flag of the United Arab Republic , where the gold eagle was replaced by two green stars to symbolize the union of Egypt and Syria . It was anticipated that the number of stars would increase as other Arab states joined the union. In fact, Syria seceded from the union, although Egypt did not alter the flag to reflect this. On January 1, 1972, the Confederation of Arab Republics was established between Egypt, Syria, and Libya . The stars were replaced with the gold hawk of Quraysh, symbol of the tribe to which the Prophet Muhammad had belonged. Finally, on October 9, 1984, five years after the dissolution of the federation, the gold eagle of Saladin —12th-century ruler of Egypt, Syria, Yemen, and Palestine—was substituted for the hawk.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Planteer/media/flags/ronaldo.jpeg b/Planteer/media/flags/ronaldo.jpeg
new file mode 100644
index 0000000..500a5b8
Binary files /dev/null and b/Planteer/media/flags/ronaldo.jpeg differ
diff --git "a/Planteer/media/flags/\330\247\331\204\330\247\330\261\330\257\331\206.avif" "b/Planteer/media/flags/\330\247\331\204\330\247\330\261\330\257\331\206.avif"
new file mode 100644
index 0000000..3b4ea8d
Binary files /dev/null and "b/Planteer/media/flags/\330\247\331\204\330\247\330\261\330\257\331\206.avif" differ
diff --git "a/Planteer/media/flags/\330\247\331\204\330\263\330\271\331\210\330\257\331\212\330\251.jpg" "b/Planteer/media/flags/\330\247\331\204\330\263\330\271\331\210\330\257\331\212\330\251.jpg"
new file mode 100644
index 0000000..02223f8
Binary files /dev/null and "b/Planteer/media/flags/\330\247\331\204\330\263\330\271\331\210\330\257\331\212\330\251.jpg" differ
diff --git "a/Planteer/media/flags/\330\247\331\204\330\271\330\261\330\247\331\202.webp" "b/Planteer/media/flags/\330\247\331\204\330\271\330\261\330\247\331\202.webp"
new file mode 100644
index 0000000..4ccf4cf
Binary files /dev/null and "b/Planteer/media/flags/\330\247\331\204\330\271\330\261\330\247\331\202.webp" differ
diff --git "a/Planteer/media/flags/\330\271\331\205\330\247\331\206.png" "b/Planteer/media/flags/\330\271\331\205\330\247\331\206.png"
new file mode 100644
index 0000000..77f3fa6
Binary files /dev/null and "b/Planteer/media/flags/\330\271\331\205\330\247\331\206.png" differ
diff --git "a/Planteer/media/flags/\331\205\330\265\330\261.webp" "b/Planteer/media/flags/\331\205\330\265\330\261.webp"
new file mode 100644
index 0000000..df6cd9f
Binary files /dev/null and "b/Planteer/media/flags/\331\205\330\265\330\261.webp" differ
diff --git "a/Planteer/media/flags/\331\205\330\272\330\261\330\250.jpg" "b/Planteer/media/flags/\331\205\330\272\330\261\330\250.jpg"
new file mode 100644
index 0000000..16f93cf
Binary files /dev/null and "b/Planteer/media/flags/\331\205\330\272\330\261\330\250.jpg" differ
diff --git "a/Planteer/media/plants/\330\247\331\203\331\204\331\212\331\204_\330\247\331\204\330\254\330\250\331\204.webp" "b/Planteer/media/plants/\330\247\331\203\331\204\331\212\331\204_\330\247\331\204\330\254\330\250\331\204.webp"
new file mode 100644
index 0000000..0a1e90b
Binary files /dev/null and "b/Planteer/media/plants/\330\247\331\203\331\204\331\212\331\204_\330\247\331\204\330\254\330\250\331\204.webp" differ
diff --git "a/Planteer/media/plants/\330\247\331\204\330\250\331\202\330\257\331\210\331\206\330\263.jpg" "b/Planteer/media/plants/\330\247\331\204\330\250\331\202\330\257\331\210\331\206\330\263.jpg"
new file mode 100644
index 0000000..8f29c27
Binary files /dev/null and "b/Planteer/media/plants/\330\247\331\204\330\250\331\202\330\257\331\210\331\206\330\263.jpg" differ
diff --git "a/Planteer/media/plants/\330\247\331\204\330\250\331\202\331\204\330\251.webp" "b/Planteer/media/plants/\330\247\331\204\330\250\331\202\331\204\330\251.webp"
new file mode 100644
index 0000000..19bdccd
Binary files /dev/null and "b/Planteer/media/plants/\330\247\331\204\330\250\331\202\331\204\330\251.webp" differ
diff --git "a/Planteer/media/plants/\330\247\331\204\330\256\330\250\331\212\330\262\330\251.jpg" "b/Planteer/media/plants/\330\247\331\204\330\256\330\250\331\212\330\262\330\251.jpg"
new file mode 100644
index 0000000..ba3692c
Binary files /dev/null and "b/Planteer/media/plants/\330\247\331\204\330\256\330\250\331\212\330\262\330\251.jpg" differ
diff --git "a/Planteer/media/plants/\330\247\331\204\330\256\330\261\331\210\330\271.jpg" "b/Planteer/media/plants/\330\247\331\204\330\256\330\261\331\210\330\271.jpg"
new file mode 100644
index 0000000..99595b2
Binary files /dev/null and "b/Planteer/media/plants/\330\247\331\204\330\256\330\261\331\210\330\271.jpg" differ
diff --git "a/Planteer/media/plants/\330\247\331\204\330\264\331\210\331\203\330\261\330\247\331\206.jpg" "b/Planteer/media/plants/\330\247\331\204\330\264\331\210\331\203\330\261\330\247\331\206.jpg"
new file mode 100644
index 0000000..9236a27
Binary files /dev/null and "b/Planteer/media/plants/\330\247\331\204\330\264\331\210\331\203\330\261\330\247\331\206.jpg" differ
diff --git "a/Planteer/media/plants/\330\247\331\204\330\271\331\206\330\265\331\204.jpg" "b/Planteer/media/plants/\330\247\331\204\330\271\331\206\330\265\331\204.jpg"
new file mode 100644
index 0000000..20965c7
Binary files /dev/null and "b/Planteer/media/plants/\330\247\331\204\330\271\331\206\330\265\331\204.jpg" differ
diff --git "a/Planteer/media/plants/\330\247\331\204\331\207\331\206\330\257\330\250\330\247\330\241.webp" "b/Planteer/media/plants/\330\247\331\204\331\207\331\206\330\257\330\250\330\247\330\241.webp"
new file mode 100644
index 0000000..bf02764
Binary files /dev/null and "b/Planteer/media/plants/\330\247\331\204\331\207\331\206\330\257\330\250\330\247\330\241.webp" differ
diff --git "a/Planteer/media/plants/\330\252\331\201\330\247\330\255_\330\247\331\204\330\264\331\212\330\267\330\247\331\206.jpg" "b/Planteer/media/plants/\330\252\331\201\330\247\330\255_\330\247\331\204\330\264\331\212\330\267\330\247\331\206.jpg"
new file mode 100644
index 0000000..f4edcd9
Binary files /dev/null and "b/Planteer/media/plants/\330\252\331\201\330\247\330\255_\330\247\331\204\330\264\331\212\330\267\330\247\331\206.jpg" differ
diff --git "a/Planteer/media/plants/\330\261\331\212\330\255\330\247\331\206.jpg" "b/Planteer/media/plants/\330\261\331\212\330\255\330\247\331\206.jpg"
new file mode 100644
index 0000000..308e3da
Binary files /dev/null and "b/Planteer/media/plants/\330\261\331\212\330\255\330\247\331\206.jpg" differ
diff --git "a/Planteer/media/plants/\330\262\331\206\330\250\331\202_\330\247\331\204\331\210\330\247\330\257\331\212.jpg" "b/Planteer/media/plants/\330\262\331\206\330\250\331\202_\330\247\331\204\331\210\330\247\330\257\331\212.jpg"
new file mode 100644
index 0000000..8d8c5b8
Binary files /dev/null and "b/Planteer/media/plants/\330\262\331\206\330\250\331\202_\330\247\331\204\331\210\330\247\330\257\331\212.jpg" differ
diff --git "a/Planteer/media/plants/\330\263\330\252_\330\247\331\204\330\255\330\263\331\206.jpg" "b/Planteer/media/plants/\330\263\330\252_\330\247\331\204\330\255\330\263\331\206.jpg"
new file mode 100644
index 0000000..fae4f6f
Binary files /dev/null and "b/Planteer/media/plants/\330\263\330\252_\330\247\331\204\330\255\330\263\331\206.jpg" differ
diff --git "a/Planteer/media/plants/\331\202\331\201\330\247\330\262_\330\247\331\204\330\253\330\271\331\204\330\250.webp" "b/Planteer/media/plants/\331\202\331\201\330\247\330\262_\330\247\331\204\330\253\330\271\331\204\330\250.webp"
new file mode 100644
index 0000000..01e6b0e
Binary files /dev/null and "b/Planteer/media/plants/\331\202\331\201\330\247\330\262_\330\247\331\204\330\253\330\271\331\204\330\250.webp" differ
diff --git "a/Planteer/media/plants/\331\206\330\271\331\206\330\247\330\271.jpg" "b/Planteer/media/plants/\331\206\330\271\331\206\330\247\330\271.jpg"
new file mode 100644
index 0000000..5eeada4
Binary files /dev/null and "b/Planteer/media/plants/\331\206\330\271\331\206\330\247\330\271.jpg" differ
diff --git a/Planteer/plants/__init__.py b/Planteer/plants/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/plants/admin.py b/Planteer/plants/admin.py
new file mode 100644
index 0000000..6530482
--- /dev/null
+++ b/Planteer/plants/admin.py
@@ -0,0 +1,29 @@
+from django.contrib import admin
+from .models import Category, Plant, Country, Comment
+
+
+@admin.register(Category)
+class CategoryAdmin(admin.ModelAdmin):
+ list_display = ['name', 'description']
+ search_fields = ['name']
+
+
+@admin.register(Plant)
+class PlantAdmin(admin.ModelAdmin):
+ list_display = ['name', 'category', 'is_edible', 'created_at']
+ list_filter = ['category', 'is_edible']
+ search_fields = ['name', 'scientific_name']
+ filter_horizontal = ['countries']
+
+
+@admin.register(Country)
+class CountryAdmin(admin.ModelAdmin):
+ list_display = ['name']
+ search_fields = ['name']
+
+
+@admin.register(Comment)
+class CommentAdmin(admin.ModelAdmin):
+ list_display = ['user', 'plant', 'created_at']
+ list_filter = ['created_at']
+ search_fields = ['user__username', 'body']
\ No newline at end of file
diff --git a/Planteer/plants/apps.py b/Planteer/plants/apps.py
new file mode 100644
index 0000000..2dee018
--- /dev/null
+++ b/Planteer/plants/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class PlantConfig(AppConfig):
+ name = 'plants'
diff --git a/Planteer/plants/forms.py b/Planteer/plants/forms.py
new file mode 100644
index 0000000..74efea5
--- /dev/null
+++ b/Planteer/plants/forms.py
@@ -0,0 +1,65 @@
+from django import forms
+from .models import Plant, Category, Country, Comment
+
+
+class PlantForm(forms.ModelForm):
+
+ class Meta:
+ model = Plant
+ fields = ['name', 'category', 'description', 'image', 'is_edible', 'countries']
+ widgets = {
+ 'name': forms.TextInput(attrs={'placeholder': 'Plant name'}),
+ 'category': forms.Select(),
+ 'description': forms.Textarea(attrs={'placeholder': 'Description', 'rows': 4}),
+ 'image': forms.ClearableFileInput(attrs={'accept': 'image/*'}),
+ 'is_edible': forms.CheckboxInput(),
+ 'countries': forms.CheckboxSelectMultiple(),
+ }
+
+ def clean_name(self):
+ name = self.cleaned_data.get('name', '').strip()
+ if len(name) < 2:
+ raise forms.ValidationError("Plant name must be at least 2 characters.")
+ return name
+
+ def clean_description(self):
+ desc = self.cleaned_data.get('description', '').strip()
+ if not desc:
+ raise forms.ValidationError("Description is required.")
+ return desc
+
+
+class PlantFilterForm(forms.Form):
+ name = forms.CharField(
+ required=False,
+ widget=forms.TextInput(attrs={'placeholder': 'Search by plant name...'}),
+ )
+ is_edible = forms.NullBooleanField(
+ required=False,
+ widget=forms.Select(
+ choices=[('', 'All Plants'), ('true', 'Edible'), ('false', 'Non-Edible')],
+ )
+ )
+ country = forms.ModelChoiceField(
+ queryset=Country.objects.all(),
+ required=False,
+ empty_label='All Countries',
+ widget=forms.Select(),
+ )
+
+
+class CommentForm(forms.ModelForm):
+
+ class Meta:
+ model = Comment
+ fields = ['body']
+ labels = {
+ 'body': 'Comment',
+ }
+ widgets = {
+ 'body': forms.Textarea(attrs={
+ 'placeholder': 'Write your comment...',
+ 'rows': 3,
+ 'required': True,
+ }),
+ }
\ No newline at end of file
diff --git a/Planteer/plants/migrations/0001_initial.py b/Planteer/plants/migrations/0001_initial.py
new file mode 100644
index 0000000..d1575d0
--- /dev/null
+++ b/Planteer/plants/migrations/0001_initial.py
@@ -0,0 +1,49 @@
+# Generated by Django 6.0.3 on 2026-04-17 23:45
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Category',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100, unique=True)),
+ ('description', models.TextField(blank=True)),
+ ('icon', models.CharField(default='🌿', max_length=10)),
+ ],
+ options={
+ 'verbose_name_plural': 'Categories',
+ 'ordering': ['name'],
+ },
+ ),
+ migrations.CreateModel(
+ name='Plant',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200)),
+ ('scientific_name', models.CharField(blank=True, max_length=200)),
+ ('description', models.TextField()),
+ ('image', models.ImageField(blank=True, null=True, upload_to='plants/')),
+ ('image_url', models.URLField(blank=True, help_text='رابط صورة خارجي (بديل عن رفع صورة)')),
+ ('is_edible', models.BooleanField(default=False)),
+ ('sunlight', models.CharField(choices=[('full_sun', 'Full Sun ☀️'), ('partial', 'Partial Shade ⛅'), ('shade', 'Full Shade 🌥️')], default='full_sun', max_length=20)),
+ ('water_needs', models.CharField(choices=[('low', 'Low 💧'), ('medium', 'Medium 💧💧'), ('high', 'High 💧💧💧')], default='medium', max_length=20)),
+ ('difficulty', models.CharField(choices=[('easy', 'Easy 🟢'), ('medium', 'Medium 🟡'), ('hard', 'Hard 🔴')], default='easy', max_length=20)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='plants', to='plants.category')),
+ ],
+ options={
+ 'ordering': ['-created_at'],
+ },
+ ),
+ ]
diff --git a/Planteer/plants/migrations/0002_remove_category_icon_remove_plant_image_url_and_more.py b/Planteer/plants/migrations/0002_remove_category_icon_remove_plant_image_url_and_more.py
new file mode 100644
index 0000000..157d77f
--- /dev/null
+++ b/Planteer/plants/migrations/0002_remove_category_icon_remove_plant_image_url_and_more.py
@@ -0,0 +1,36 @@
+# Generated by Django 6.0.4 on 2026-04-19 16:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('plants', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='category',
+ name='icon',
+ ),
+ migrations.RemoveField(
+ model_name='plant',
+ name='image_url',
+ ),
+ migrations.AlterField(
+ model_name='plant',
+ name='difficulty',
+ field=models.CharField(choices=[('easy', 'Easy'), ('medium', 'Medium'), ('hard', 'Hard')], default='easy', max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='plant',
+ name='sunlight',
+ field=models.CharField(choices=[('full_sun', 'Full Sun'), ('partial', 'Partial Shade'), ('shade', 'Full Shade')], default='full_sun', max_length=20),
+ ),
+ migrations.AlterField(
+ model_name='plant',
+ name='water_needs',
+ field=models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='medium', max_length=20),
+ ),
+ ]
diff --git a/Planteer/plants/migrations/0003_alter_plant_category.py b/Planteer/plants/migrations/0003_alter_plant_category.py
new file mode 100644
index 0000000..f6073eb
--- /dev/null
+++ b/Planteer/plants/migrations/0003_alter_plant_category.py
@@ -0,0 +1,19 @@
+# Generated by Django 6.0.4 on 2026-04-19 19:33
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('plants', '0002_remove_category_icon_remove_plant_image_url_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='plant',
+ name='category',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='plants', to='plants.category'),
+ ),
+ ]
diff --git a/Planteer/plants/migrations/0004_remove_plant_difficulty_remove_plant_sunlight_and_more.py b/Planteer/plants/migrations/0004_remove_plant_difficulty_remove_plant_sunlight_and_more.py
new file mode 100644
index 0000000..b18cd4d
--- /dev/null
+++ b/Planteer/plants/migrations/0004_remove_plant_difficulty_remove_plant_sunlight_and_more.py
@@ -0,0 +1,25 @@
+# Generated by Django 6.0.4 on 2026-04-19 19:41
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('plants', '0003_alter_plant_category'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='plant',
+ name='difficulty',
+ ),
+ migrations.RemoveField(
+ model_name='plant',
+ name='sunlight',
+ ),
+ migrations.RemoveField(
+ model_name='plant',
+ name='water_needs',
+ ),
+ ]
diff --git a/Planteer/plants/migrations/0005_review.py b/Planteer/plants/migrations/0005_review.py
new file mode 100644
index 0000000..651204f
--- /dev/null
+++ b/Planteer/plants/migrations/0005_review.py
@@ -0,0 +1,27 @@
+# Generated by Django 6.0.4 on 2026-04-20 06:42
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('plants', '0004_remove_plant_difficulty_remove_plant_sunlight_and_more'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Review',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('rating', models.PositiveIntegerField()),
+ ('comment', models.TextField(blank=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('plant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='plants.plant')),
+ ],
+ options={
+ 'ordering': ['-created_at'],
+ },
+ ),
+ ]
diff --git a/Planteer/plants/migrations/0006_alter_review_options_remove_review_created_at_and_more.py b/Planteer/plants/migrations/0006_alter_review_options_remove_review_created_at_and_more.py
new file mode 100644
index 0000000..241a448
--- /dev/null
+++ b/Planteer/plants/migrations/0006_alter_review_options_remove_review_created_at_and_more.py
@@ -0,0 +1,28 @@
+# Generated by Django 6.0.4 on 2026-04-21 05:50
+
+import django.utils.timezone
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('plants', '0005_review'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='review',
+ options={'ordering': ['-created']},
+ ),
+ migrations.RemoveField(
+ model_name='review',
+ name='created_at',
+ ),
+ migrations.AddField(
+ model_name='review',
+ name='created',
+ field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
+ preserve_default=False,
+ ),
+ ]
diff --git a/Planteer/plants/migrations/0007_country_plant_countries.py b/Planteer/plants/migrations/0007_country_plant_countries.py
new file mode 100644
index 0000000..4a7ad09
--- /dev/null
+++ b/Planteer/plants/migrations/0007_country_plant_countries.py
@@ -0,0 +1,30 @@
+# Generated by Django 6.0.4 on 2026-04-21 19:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('plants', '0006_alter_review_options_remove_review_created_at_and_more'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Country',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100, unique=True)),
+ ('flag', models.ImageField(blank=True, null=True, upload_to='flags/')),
+ ],
+ options={
+ 'verbose_name_plural': 'Countries',
+ 'ordering': ['name'],
+ },
+ ),
+ migrations.AddField(
+ model_name='plant',
+ name='countries',
+ field=models.ManyToManyField(blank=True, related_name='plants', to='plants.country'),
+ ),
+ ]
diff --git a/Planteer/plants/migrations/0008_comment.py b/Planteer/plants/migrations/0008_comment.py
new file mode 100644
index 0000000..b539167
--- /dev/null
+++ b/Planteer/plants/migrations/0008_comment.py
@@ -0,0 +1,29 @@
+# Generated by Django 6.0.4 on 2026-04-26 16:44
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('plants', '0007_country_plant_countries'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Comment',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('body', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('plant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='plants.plant')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'ordering': ['-created_at'],
+ },
+ ),
+ ]
diff --git a/Planteer/plants/migrations/0009_delete_review.py b/Planteer/plants/migrations/0009_delete_review.py
new file mode 100644
index 0000000..dadad9e
--- /dev/null
+++ b/Planteer/plants/migrations/0009_delete_review.py
@@ -0,0 +1,16 @@
+# Generated by Django 6.0.4 on 2026-04-26 16:58
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('plants', '0008_comment'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='Review',
+ ),
+ ]
diff --git a/Planteer/plants/migrations/__init__.py b/Planteer/plants/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/Planteer/plants/models.py b/Planteer/plants/models.py
new file mode 100644
index 0000000..f4ab59b
--- /dev/null
+++ b/Planteer/plants/models.py
@@ -0,0 +1,70 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+
+class Category(models.Model):
+ name = models.CharField(max_length=100, unique=True)
+ description = models.TextField(blank=True)
+
+ class Meta:
+ verbose_name_plural = "Categories"
+ ordering = ['name']
+
+ def __str__(self):
+ return self.name
+
+
+class Country(models.Model):
+ name = models.CharField(max_length=100, unique=True)
+ flag = models.ImageField(upload_to='flags/', blank=True, null=True)
+
+ class Meta:
+ verbose_name_plural = "Countries"
+ ordering = ['name']
+
+ def __str__(self):
+ return self.name
+
+ def get_flag(self):
+ if self.flag:
+ return self.flag.url
+ return '/static/images/default-flag.svg'
+
+
+class Plant(models.Model):
+ name = models.CharField(max_length=200)
+ scientific_name = models.CharField(max_length=200, blank=True)
+ description = models.TextField()
+ category = models.ForeignKey(Category, on_delete=models.SET_NULL, related_name='plants', null=True, blank=True)
+ image = models.ImageField(upload_to='plants/', blank=True, null=True)
+ is_edible = models.BooleanField(default=False)
+ countries = models.ManyToManyField(Country, related_name='plants', blank=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ ordering = ['-created_at']
+
+ def __str__(self):
+ return self.name
+
+ def get_image(self):
+ if self.image:
+ return self.image.url
+ return '/static/images/default-plant.png'
+
+ def get_related_plants(self):
+ return Plant.objects.filter(category=self.category).exclude(pk=self.pk)[:4]
+
+
+class Comment(models.Model):
+ plant = models.ForeignKey(Plant, on_delete=models.CASCADE, related_name='comments')
+ user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='comments')
+ body = models.TextField()
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ ordering = ['-created_at']
+
+ def __str__(self):
+ return f"{self.user.username} on {self.plant.name}"
diff --git a/Planteer/plants/templates/plants/all_plants.html b/Planteer/plants/templates/plants/all_plants.html
new file mode 100644
index 0000000..a0cadd9
--- /dev/null
+++ b/Planteer/plants/templates/plants/all_plants.html
@@ -0,0 +1,42 @@
+{% extends 'base.html' %}
+
+{% block title %}All Plants{% endblock %}
+
+{% block content %}
+
+
+
All Plants
+
+
+
+
+ {% if plants %}
+
+ {% for plant in plants %}
+ {% include 'plants/includes/plant_card.html' %}
+ {% endfor %}
+
+ {% else %}
+
+ {% endif %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/plants/templates/plants/includes/plant_card.html b/Planteer/plants/templates/plants/includes/plant_card.html
new file mode 100644
index 0000000..514f2fc
--- /dev/null
+++ b/Planteer/plants/templates/plants/includes/plant_card.html
@@ -0,0 +1,22 @@
+{% load static %}
+
+
+
+
+
+
+
{{ plant.name }}
+
{{ plant.description|truncatewords:8 }}
+
{{ plant.category.name }}
+ {% if plant.countries.all %}
+
+ {% for c in plant.countries.all %}
+
+
+ {{ c.name }}
+
+ {% endfor %}
+
+ {% endif %}
+
+
diff --git a/Planteer/plants/templates/plants/plant_delete.html b/Planteer/plants/templates/plants/plant_delete.html
new file mode 100644
index 0000000..dd8fe10
--- /dev/null
+++ b/Planteer/plants/templates/plants/plant_delete.html
@@ -0,0 +1,29 @@
+{% extends 'base.html' %}
+
+{% block title %}Delete {{ plant.name }}{% endblock %}
+
+{% block content %}
+
+
+
+
Delete Plant
+
Are you sure you want to delete "{{ plant.name }}" ?
+
This action cannot be undone.
+
+
+
+
+
{{ plant.name }}
+
{{ plant.category.name }}
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/plants/templates/plants/plant_detail.html b/Planteer/plants/templates/plants/plant_detail.html
new file mode 100644
index 0000000..7a32483
--- /dev/null
+++ b/Planteer/plants/templates/plants/plant_detail.html
@@ -0,0 +1,98 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block title %}{{ plant.name }}{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
{{ plant.name }}
+
{{ plant.category.name }}
+
{{ plant.description }}
+
+
+
Is Edible: {% if plant.is_edible %}Yes{% else %}No{% endif %}
+ {% if plant.scientific_name %}
+
Scientific: {{ plant.scientific_name }}
+ {% endif %}
+
+
+
+
+
+
+
+
+ {% if user.is_authenticated %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+
+
+
+
+
+{% if related_plants %}
+
+
+
Related Plants
+
+ {% for rp in related_plants %}
+ {% include 'plants/includes/plant_card.html' with plant=rp %}
+ {% endfor %}
+
+
+
+{% endif %}
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/plants/templates/plants/plant_form.html b/Planteer/plants/templates/plants/plant_form.html
new file mode 100644
index 0000000..f86f976
--- /dev/null
+++ b/Planteer/plants/templates/plants/plant_form.html
@@ -0,0 +1,97 @@
+{% extends 'base.html' %}
+
+{% block title %}{{ action }} Plant{% endblock %}
+
+{% block content %}
+
+
+
{{ action }} Plant
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/plants/templates/plants/plants_by_country.html b/Planteer/plants/templates/plants/plants_by_country.html
new file mode 100644
index 0000000..d7add2d
--- /dev/null
+++ b/Planteer/plants/templates/plants/plants_by_country.html
@@ -0,0 +1,32 @@
+{% extends 'base.html' %}
+
+{% block title %}Plants from {{ country.name }}{% endblock %}
+
+{% block content %}
+
+
+
+
+ {% if plants %}
+
+ {% for plant in plants %}
+ {% include 'plants/includes/plant_card.html' %}
+ {% endfor %}
+
+ {% else %}
+
+
No plants found
+
No plants have been tagged with this country yet.
+
+ {% endif %}
+
+
+{% endblock %}
diff --git a/Planteer/plants/templates/plants/search.html b/Planteer/plants/templates/plants/search.html
new file mode 100644
index 0000000..77273a8
--- /dev/null
+++ b/Planteer/plants/templates/plants/search.html
@@ -0,0 +1,31 @@
+{% extends 'base.html' %}
+
+{% block title %}Search Results{% endblock %}
+
+{% block content %}
+
+
+
Search Results
+
+
+
+ {% if searched %}
+ {% if plants %}
+
+ {% for plant in plants %}
+ {% include 'plants/includes/plant_card.html' %}
+ {% endfor %}
+
+ {% else %}
+
+
No plants found
+
Try a different search term.
+
+ {% endif %}
+ {% endif %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Planteer/plants/tests.py b/Planteer/plants/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/Planteer/plants/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/Planteer/plants/urls.py b/Planteer/plants/urls.py
new file mode 100644
index 0000000..4059a02
--- /dev/null
+++ b/Planteer/plants/urls.py
@@ -0,0 +1,14 @@
+from django.urls import path
+from . import views
+
+app_name = "plants"
+
+urlpatterns = [
+ path('all/', views.all_plants, name='all_plants'),
+ path('search/', views.search_plants, name='search'),
+ path('new/', views.add_plant, name='add_plant'),
+ path('/detail/', views.plant_detail, name='plant_detail'),
+ path('/update/', views.update_plant, name='update_plant'),
+ path('/delete/', views.delete_plant, name='delete_plant'),
+ path('country//', views.plants_by_country, name='plants_by_country'),
+]
\ No newline at end of file
diff --git a/Planteer/plants/views.py b/Planteer/plants/views.py
new file mode 100644
index 0000000..aa454d0
--- /dev/null
+++ b/Planteer/plants/views.py
@@ -0,0 +1,123 @@
+from django.shortcuts import render, get_object_or_404, redirect
+from django.http import HttpRequest
+from django.contrib import messages
+from .models import Plant, Category, Country, Comment
+from .forms import PlantForm, PlantFilterForm, CommentForm
+
+
+def all_plants(request: HttpRequest):
+ plants = Plant.objects.all()
+ filter_form = PlantFilterForm(request.GET)
+
+ if filter_form.is_valid():
+ name = filter_form.cleaned_data.get('name')
+ is_edible = filter_form.cleaned_data.get('is_edible')
+ country = filter_form.cleaned_data.get('country')
+ if name:
+ plants = plants.filter(name__icontains=name)
+ if is_edible is not None:
+ plants = plants.filter(is_edible=is_edible)
+ if country:
+ plants = plants.filter(countries=country)
+
+ return render(request, 'plants/all_plants.html', {
+ 'plants': plants,
+ 'filter_form': filter_form,
+ 'total': plants.count(),
+ })
+
+
+def plant_detail(request: HttpRequest, plant_id):
+ plant = get_object_or_404(Plant, pk=plant_id)
+ comments = plant.comments.all()
+ comment_form = CommentForm()
+
+ if request.method == 'POST' and request.user.is_authenticated:
+ comment_form = CommentForm(request.POST)
+ if comment_form.is_valid():
+ comment = comment_form.save(commit=False)
+ comment.plant = plant
+ comment.user = request.user
+ comment.save()
+ return redirect('plants:plant_detail', plant_id=plant.pk)
+
+ related_plants = plant.get_related_plants()
+
+ return render(request, 'plants/plant_detail.html', {
+ 'plant': plant,
+ 'related_plants': related_plants,
+ 'comments': comments,
+ 'comment_form': comment_form,
+ })
+
+
+def add_plant(request: HttpRequest):
+ if request.method == 'POST':
+ form = PlantForm(request.POST, request.FILES)
+ if form.is_valid():
+ form.save()
+ messages.success(request, 'Plant added successfully!')
+ return redirect('plants:all_plants')
+ else:
+ form = PlantForm()
+
+ return render(request, 'plants/plant_form.html', {
+ 'form': form,
+ 'action': 'Add',
+ })
+
+
+def update_plant(request: HttpRequest, plant_id):
+ plant = get_object_or_404(Plant, pk=plant_id)
+
+ if request.method == 'POST':
+ form = PlantForm(request.POST, request.FILES, instance=plant)
+ if form.is_valid():
+ form.save()
+ messages.success(request, 'Plant updated successfully!')
+ return redirect('plants:plant_detail', plant_id=plant.pk)
+ else:
+ form = PlantForm(instance=plant)
+
+ return render(request, 'plants/plant_form.html', {
+ 'form': form,
+ 'action': 'Update',
+ 'plant': plant,
+ })
+
+
+def delete_plant(request: HttpRequest, plant_id):
+ plant = get_object_or_404(Plant, pk=plant_id)
+
+ if request.method == 'POST':
+ plant.delete()
+ return redirect('plants:all_plants')
+
+ return render(request, 'plants/plant_delete.html', {'plant': plant})
+
+
+def search_plants(request: HttpRequest):
+ plants = Plant.objects.none()
+ query = request.GET.get('q', '').strip()
+ searched = bool(request.GET)
+
+ if query:
+ plants = Plant.objects.filter(name__icontains=query) | Plant.objects.filter(description__icontains=query)
+
+ return render(request, 'plants/search.html', {
+ 'plants': plants,
+ 'query': query,
+ 'searched': searched,
+ 'result_count': plants.count(),
+ })
+
+
+def plants_by_country(request: HttpRequest, country_id):
+ country = get_object_or_404(Country, pk=country_id)
+ plants = country.plants.all()
+
+ return render(request, 'plants/plants_by_country.html', {
+ 'country': country,
+ 'plants': plants,
+ 'total': plants.count(),
+ })
\ No newline at end of file
diff --git a/Planteer/static/css/style.css b/Planteer/static/css/style.css
new file mode 100644
index 0000000..f7757d5
--- /dev/null
+++ b/Planteer/static/css/style.css
@@ -0,0 +1,1240 @@
+/* ============================================
+ PLANTEER - Clean Minimal Design
+ Matching wireframe: black, white, simple
+ ============================================ */
+
+/* ===== RESET ===== */
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+html { scroll-behavior: smooth; }
+
+body {
+ font-family: 'Inter', system-ui, sans-serif;
+ color: #1a1a1a;
+ background: #fff;
+ line-height: 1.6;
+}
+
+a { text-decoration: none; color: inherit; }
+img { max-width: 100%; height: auto; display: block; }
+button { cursor: pointer; font-family: inherit; }
+
+.container {
+ max-width: 1140px;
+ margin: 0 auto;
+ padding: 0 24px;
+}
+
+/* ===== NAVBAR ===== */
+.navbar {
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ background: rgba(255, 255, 255, 0.9);
+ border-bottom: 1px solid #e7e7e7;
+ backdrop-filter: blur(8px);
+}
+
+.navbar::after {
+ content: "";
+ display: block;
+ height: 2px;
+ background: linear-gradient(90deg, #1a1a1a 0%, #4f4f4f 50%, #1a1a1a 100%);
+ opacity: 0.14;
+}
+
+.navbar-inner {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ min-height: 70px;
+ gap: 20px;
+}
+
+.navbar-logo {
+ font-family: 'Playfair Display', serif;
+ font-size: 1.35rem;
+ font-weight: 700;
+ color: #1a1a1a;
+ letter-spacing: 0.2px;
+ flex-shrink: 0;
+}
+
+.navbar-links {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 20px;
+ width: 100%;
+}
+
+.navbar-primary,
+.navbar-auth {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+}
+
+.navbar-auth {
+ justify-content: flex-end;
+}
+
+.nav-link {
+ position: relative;
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #1a1a1a;
+ padding: 8px 10px;
+ border-radius: 8px;
+ transition: color 0.2s, background 0.2s;
+}
+
+.nav-link::after {
+ content: "";
+ position: absolute;
+ left: 10px;
+ right: 10px;
+ bottom: 6px;
+ height: 2px;
+ background: #1a1a1a;
+ transform: scaleX(0);
+ transform-origin: center;
+ transition: transform 0.2s;
+}
+
+.nav-link:hover {
+ background: #f5f5f5;
+}
+
+.nav-link:hover::after {
+ transform: scaleX(1);
+}
+
+.nav-btn {
+ display: inline-block;
+ padding: 9px 18px;
+ background: #1a1a1a;
+ color: #fff;
+ font-size: 0.85rem;
+ font-weight: 600;
+ border: 1px solid #1a1a1a;
+ border-radius: 999px;
+ transition: background 0.2s, color 0.2s, transform 0.2s;
+}
+
+.nav-btn:hover {
+ background: #333;
+ transform: translateY(-1px);
+}
+
+.nav-btn-outline {
+ background: #fff;
+ color: #1a1a1a;
+}
+
+.nav-btn-outline:hover {
+ background: #1a1a1a;
+ color: #fff;
+}
+
+.nav-user {
+ font-size: 0.82rem;
+ color: #444;
+ background: #f3f3f3;
+ border: 1px solid #e5e5e5;
+ border-radius: 999px;
+ padding: 7px 12px;
+}
+
+/* ===== BUTTONS ===== */
+.btn {
+ display: inline-block;
+ padding: 10px 24px;
+ font-size: 0.9rem;
+ font-weight: 500;
+ border-radius: 6px;
+ border: 1px solid transparent;
+ transition: all 0.2s;
+ text-align: center;
+}
+
+.btn-dark {
+ background: #1a1a1a;
+ color: #fff;
+ border-color: #1a1a1a;
+}
+.btn-dark:hover { background: #333; }
+
+.btn-outline {
+ background: #fff;
+ color: #1a1a1a;
+ border-color: #d0d0d0;
+}
+.btn-outline:hover { border-color: #1a1a1a; }
+
+.btn-danger {
+ background: #dc3545;
+ color: #fff;
+ border-color: #dc3545;
+}
+.btn-danger:hover { background: #c82333; }
+
+.btn-full { width: 100%; }
+
+/* ===== HERO ===== */
+.hero {
+ background: #f5f5f5;
+ padding: 80px 0;
+ text-align: center;
+}
+
+.hero-content { max-width: 600px; margin: 0 auto; }
+
+.hero-title {
+ font-family: 'Playfair Display', serif;
+ font-size: 3rem;
+ font-weight: 700;
+ color: #1a1a1a;
+ margin-bottom: 12px;
+}
+
+.hero-subtitle {
+ font-size: 1.1rem;
+ color: #555;
+ margin-bottom: 32px;
+}
+
+.hero-search {
+ display: flex;
+ gap: 0;
+ max-width: 440px;
+ margin: 0 auto;
+ border: 1px solid #d0d0d0;
+ border-radius: 8px;
+ overflow: hidden;
+ background: #fff;
+}
+
+.hero-search-input {
+ flex: 1;
+ padding: 12px 16px;
+ border: none;
+ font-size: 0.9rem;
+ font-family: inherit;
+ outline: none;
+}
+
+.hero-search-btn {
+ padding: 12px 24px;
+ background: #1a1a1a;
+ color: #fff;
+ border: none;
+ font-size: 0.85rem;
+ font-weight: 500;
+ font-family: inherit;
+}
+
+.hero-search-btn:hover { background: #333; }
+
+/* ===== SECTIONS ===== */
+.section { padding: 60px 0; }
+.section-gray { background: #f9f9f9; }
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+ margin-bottom: 32px;
+}
+
+.section-title {
+ font-family: 'Playfair Display', serif;
+ font-size: 1.75rem;
+ font-weight: 700;
+ color: #1a1a1a;
+ margin-bottom: 4px;
+}
+
+.section-subtitle {
+ font-size: 0.9rem;
+ color: #777;
+}
+
+.more-link {
+ font-size: 0.9rem;
+ font-weight: 500;
+ color: #1a1a1a;
+}
+
+.more-link:hover { text-decoration: underline; }
+
+.page-title {
+ font-family: 'Playfair Display', serif;
+ font-size: 2rem;
+ font-weight: 700;
+ color: #1a1a1a;
+ margin-bottom: 8px;
+}
+
+.page-subtitle {
+ font-size: 0.95rem;
+ color: #777;
+ margin-bottom: 32px;
+}
+
+/* ===== PLANT CARDS ===== */
+.plants-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 24px;
+}
+
+.plant-card {
+ background: #fff;
+ border: 1px solid #eee;
+ border-radius: 8px;
+ overflow: hidden;
+ transition: box-shadow 0.2s;
+ display: flex;
+ flex-direction: column;
+}
+
+.plant-card:hover {
+ box-shadow: 0 4px 20px rgba(0,0,0,0.08);
+}
+
+.plant-card-img {
+ height: 200px;
+ overflow: hidden;
+ background: #f5f5f5;
+}
+
+.plant-card-img img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.plant-card-body {
+ padding: 16px;
+}
+
+.plant-card-title {
+ font-size: 0.95rem;
+ font-weight: 600;
+ color: #1a1a1a;
+ margin-bottom: 4px;
+}
+
+.plant-card-desc {
+ font-size: 0.8rem;
+ color: #999;
+ margin-bottom: 4px;
+}
+
+.plant-card-category {
+ font-size: 0.8rem;
+ font-weight: 600;
+ color: #1a1a1a;
+}
+
+.plant-card-countries {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ margin-top: 6px;
+}
+
+/* ===== COUNTRY TAGS ===== */
+.country-tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 3px 8px;
+ background: #f0f0f0;
+ border-radius: 20px;
+ font-size: 0.72rem;
+ font-weight: 500;
+ color: #444;
+ white-space: nowrap;
+}
+
+.country-tag-link {
+ transition: background 0.2s, color 0.2s;
+ text-decoration: none;
+}
+
+.country-tag-link:hover {
+ background: #1a1a1a;
+ color: #fff;
+}
+
+.country-flag {
+ width: 18px;
+ height: 12px;
+ object-fit: contain;
+ background: #fff;
+ border: 1px solid #ddd;
+ border-radius: 2px;
+ display: inline-block;
+ flex-shrink: 0;
+}
+
+/* ===== DETAIL COUNTRIES ===== */
+.detail-countries {
+ margin-bottom: 20px;
+}
+
+.detail-countries-label {
+ font-size: 0.85rem;
+ font-weight: 500;
+ color: #555;
+ margin-bottom: 8px;
+}
+
+.country-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+/* ===== COUNTRY PAGE HEADER ===== */
+.country-header {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ margin-bottom: 32px;
+}
+
+.country-header-flag {
+ width: 64px;
+ height: 44px;
+ object-fit: contain;
+ background: #fff;
+ border-radius: 4px;
+ border: 1px solid #e5e5e5;
+}
+
+/* ===== COUNTRIES CHECKBOX LIST ===== */
+.countries-checkbox-list {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 8px 16px;
+ padding: 12px 16px;
+ border: 1px solid #d0d0d0;
+ border-radius: 8px;
+ background: #fafafa;
+ max-height: 220px;
+ overflow-y: auto;
+}
+
+.country-checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 0.85rem;
+ color: #333;
+ cursor: pointer;
+ margin-bottom: 0;
+}
+
+.country-checkbox-label input[type="checkbox"] {
+ width: 15px;
+ height: 15px;
+ cursor: pointer;
+ accent-color: #1a1a1a;
+ flex-shrink: 0;
+}
+
+/* ===== DETAIL PAGE ===== */
+.detail-layout {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 48px;
+ align-items: start;
+}
+
+.detail-image {
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.detail-image img {
+ width: 100%;
+ height: 400px;
+ object-fit: cover;
+}
+
+.detail-title {
+ font-family: 'Playfair Display', serif;
+ font-size: 2rem;
+ font-weight: 700;
+ margin-bottom: 8px;
+}
+
+.detail-category {
+ font-size: 0.9rem;
+ color: #777;
+ margin-bottom: 20px;
+}
+
+.detail-desc {
+ font-size: 0.95rem;
+ line-height: 1.8;
+ color: #444;
+ margin-bottom: 24px;
+}
+
+.detail-specs {
+ margin-bottom: 24px;
+}
+
+.detail-specs p {
+ font-size: 0.9rem;
+ margin-bottom: 6px;
+ color: #333;
+}
+
+.detail-specs strong {
+ color: #1a1a1a;
+}
+
+.detail-actions {
+ display: flex;
+ gap: 12px;
+}
+
+/* ===== REVIEWS ===== */
+.reviews-title {
+ font-size: 1.2rem;
+ font-weight: 600;
+ margin: 28px 0 14px;
+ color: #1a1a1a;
+ border-bottom: 2px solid #e8e8e8;
+ padding-bottom: 8px;
+}
+
+.comments-title {
+ font-size: 1.2rem;
+ font-weight: 600;
+ margin: 28px 0 14px;
+ color: #1a1a1a;
+ border-bottom: 2px solid #e8e8e8;
+ padding-bottom: 8px;
+}
+
+.review-form {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-bottom: 24px;
+}
+
+.review-textarea {
+ width: 100%;
+ padding: 10px 14px;
+ border: 1px solid #d0d0d0;
+ border-radius: 8px;
+ font-size: 0.9rem;
+ font-family: inherit;
+ color: #1a1a1a;
+ resize: vertical;
+ transition: border-color 0.2s;
+}
+
+.review-textarea:focus {
+ outline: none;
+ border-color: #1a1a1a;
+}
+
+.review-select {
+ width: 100%;
+ padding: 10px 14px;
+ border: 1px solid #d0d0d0;
+ border-radius: 8px;
+ font-size: 0.9rem;
+ font-family: inherit;
+ color: #1a1a1a;
+ background: #fff;
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23333' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 12px center;
+ padding-right: 36px;
+ cursor: pointer;
+ transition: border-color 0.2s;
+}
+
+.review-select:focus {
+ outline: none;
+ border-color: #1a1a1a;
+}
+
+.reviews-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ margin-top: 8px;
+}
+
+.review-item {
+ background: #f9f9f9;
+ border: 1px solid #ebebeb;
+ border-radius: 10px;
+ padding: 14px 16px;
+}
+
+.review-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+.review-stars {
+ font-size: 1.1rem;
+ color: #f5a623;
+ letter-spacing: 2px;
+}
+
+.review-date {
+ font-size: 0.78rem;
+ color: #888;
+}
+
+.review-text {
+ font-size: 0.9rem;
+ color: #444;
+ margin: 0;
+ line-height: 1.5;
+}
+
+.no-reviews {
+ color: #999;
+ font-size: 0.9rem;
+ font-style: italic;
+}
+.form-container {
+ max-width: 680px;
+}
+
+.auth-box {
+ max-width: 440px;
+ margin: 0 auto;
+}
+
+.auth-link {
+ text-align: center;
+ margin-top: 20px;
+ font-size: 0.9rem;
+}
+
+.auth-link a {
+ color: #1a1a1a;
+ font-weight: 600;
+ text-decoration: underline;
+}
+
+.comment-form-box {
+ background: #f9f9f9;
+ border: 1px solid #eee;
+ border-radius: 8px;
+ padding: 20px;
+ margin-bottom: 24px;
+}
+
+.login-prompt {
+ background: #f9f9f9;
+ border: 1px solid #eee;
+ border-radius: 8px;
+ padding: 24px;
+ text-align: center;
+ margin-bottom: 24px;
+}
+
+.login-prompt a {
+ color: #1a1a1a;
+ font-weight: 600;
+ text-decoration: underline;
+}
+
+.comments-list {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.comment-card {
+ border: 1px solid #eee;
+ border-radius: 8px;
+ padding: 16px;
+}
+
+.comment-header {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 8px;
+}
+
+.comment-date {
+ font-size: 0.8rem;
+ color: #999;
+}
+
+.comment-body {
+ font-size: 0.9rem;
+ color: #444;
+ line-height: 1.6;
+}
+
+.styled-form input[type="text"],
+.styled-form input[type="email"],
+.styled-form input[type="password"],
+.styled-form input[type="url"],
+.styled-form input[type="number"],
+.styled-form textarea,
+.styled-form select {
+ width: 100%;
+ padding: 10px 14px;
+ border: 1px solid #d0d0d0;
+ border-radius: 6px;
+ font-size: 0.9rem;
+ font-family: inherit;
+ color: #1a1a1a;
+ background: #fff;
+ transition: border-color 0.2s;
+}
+
+.styled-form input:focus,
+.styled-form textarea:focus,
+.styled-form select:focus {
+ outline: none;
+ border-color: #1a1a1a;
+}
+
+.styled-form textarea { resize: vertical; }
+
+.styled-form select {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23333' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 12px center;
+ padding-right: 36px;
+}
+
+.form-group {
+ margin-bottom: 20px;
+}
+
+.form-group label {
+ display: block;
+ font-size: 0.85rem;
+ font-weight: 500;
+ color: #1a1a1a;
+ margin-bottom: 6px;
+}
+
+.form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px;
+}
+
+.form-row-3 {
+ grid-template-columns: 1fr 1fr 1fr;
+}
+
+.form-checkbox-group {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.form-checkbox-group label {
+ margin-bottom: 0;
+ font-size: 0.9rem;
+}
+
+.form-actions {
+ display: flex;
+ gap: 12px;
+ margin-top: 8px;
+}
+
+.error-text {
+ display: block;
+ color: #dc3545;
+ font-size: 0.8rem;
+ margin-top: 4px;
+}
+
+.form-errors {
+ background: #fff0f0;
+ border: 1px solid #fcc;
+ border-radius: 6px;
+ padding: 12px 16px;
+ margin-bottom: 20px;
+}
+
+.success-msg {
+ background: #f0fff0;
+ border: 1px solid #b2e6b2;
+ border-radius: 6px;
+ padding: 12px 16px;
+ margin-bottom: 24px;
+ color: #2d7a2d;
+ font-size: 0.9rem;
+}
+
+.section-footer {
+ text-align: center;
+ margin-top: 56px;
+ margin-bottom: 16px;
+}
+
+/* ===== MESSAGES BANNER ===== */
+.messages-banner {
+ max-width: 1140px;
+ margin: 16px auto 0;
+ padding: 0 24px;
+}
+
+.message-alert {
+ padding: 14px 20px;
+ border-radius: 6px;
+ font-size: 0.9rem;
+ font-weight: 500;
+ margin-bottom: 8px;
+}
+
+.message-success {
+ background: #f0fff0;
+ border: 1px solid #b2e6b2;
+ color: #2d7a2d;
+}
+
+.message-error {
+ background: #fff0f0;
+ border: 1px solid #fcc;
+ color: #c0392b;
+}
+
+/* ===== UPLOAD BUTTON ===== */
+.upload-btn {
+ display: inline-block;
+ padding: 10px 20px;
+ background: #1a1a1a;
+ color: #fff;
+ font-size: 0.85rem;
+ font-weight: 500;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.upload-btn:hover { background: #333; }
+
+.upload-input-hidden input[type="file"] {
+ margin-top: 8px;
+}
+
+/* ===== FILTER BAR ===== */
+.filter-bar {
+ display: flex;
+ align-items: flex-end;
+ gap: 16px;
+ margin-bottom: 32px;
+ flex-wrap: wrap;
+}
+
+.filter-group { flex: 1; min-width: 150px; }
+
+.filter-group label {
+ display: block;
+ font-size: 0.8rem;
+ font-weight: 500;
+ margin-bottom: 4px;
+ color: #555;
+}
+
+.filter-group select {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid #d0d0d0;
+ border-radius: 6px;
+ font-size: 0.85rem;
+ font-family: inherit;
+ appearance: none;
+ background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23333' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E") no-repeat right 10px center;
+ padding-right: 32px;
+}
+
+.filter-group input[type="text"] {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid #d0d0d0;
+ border-radius: 6px;
+ font-size: 0.85rem;
+ font-family: inherit;
+ color: #1a1a1a;
+ background: #fff;
+ outline: none;
+}
+
+.filter-group input[type="text"]:focus {
+ border-color: #1a1a1a;
+}
+
+/* ===== SEARCH BAR ===== */
+.search-bar {
+ display: flex;
+ gap: 12px;
+ margin-bottom: 32px;
+}
+
+.search-input {
+ flex: 1;
+ padding: 10px 16px;
+ border: 1px solid #d0d0d0;
+ border-radius: 6px;
+ font-size: 0.9rem;
+ font-family: inherit;
+}
+
+.search-input:focus {
+ outline: none;
+ border-color: #1a1a1a;
+}
+
+/* ===== CONFIRM BOX ===== */
+.confirm-box {
+ max-width: 480px;
+ margin: 0 auto;
+ text-align: center;
+ border: 1px solid #eee;
+ border-radius: 8px;
+ padding: 40px;
+}
+
+.confirm-box h1 {
+ font-family: 'Playfair Display', serif;
+ font-size: 1.5rem;
+ margin-bottom: 12px;
+}
+
+.confirm-warning {
+ color: #dc3545;
+ font-size: 0.85rem;
+ margin-bottom: 24px;
+}
+
+.confirm-plant {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ background: #f9f9f9;
+ border-radius: 6px;
+ margin-bottom: 24px;
+ text-align: left;
+}
+
+.confirm-img {
+ width: 64px;
+ height: 64px;
+ object-fit: cover;
+ border-radius: 6px;
+}
+
+.confirm-actions {
+ display: flex;
+ gap: 12px;
+ justify-content: center;
+}
+
+/* ===== CONTACT PAGE ===== */
+.contact-layout {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 48px;
+ align-items: start;
+}
+
+.contact-img {
+ width: 100%;
+ height: 100%;
+ min-height: 400px;
+ object-fit: cover;
+ border-radius: 8px;
+}
+
+/* ===== MESSAGES PAGE ===== */
+.messages-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 24px;
+}
+
+.message-card {
+ border: 1px solid #eee;
+ border-radius: 8px;
+ padding: 24px;
+}
+
+.message-icon {
+ font-size: 1.2rem;
+ color: #999;
+ margin-bottom: 12px;
+}
+
+.message-name {
+ font-size: 1rem;
+ font-weight: 600;
+ color: #1a1a1a;
+ margin-bottom: 4px;
+}
+
+.message-email {
+ font-size: 0.85rem;
+ color: #2563eb;
+ display: block;
+ margin-bottom: 12px;
+}
+
+.message-body {
+ font-size: 0.85rem;
+ color: #555;
+ line-height: 1.6;
+}
+
+/* ===== EMPTY STATE ===== */
+.empty-state {
+ text-align: center;
+ padding: 60px 24px;
+}
+
+.empty-state h2 {
+ font-family: 'Playfair Display', serif;
+ font-size: 1.3rem;
+ margin-bottom: 8px;
+}
+
+.empty-state p {
+ color: #777;
+ font-size: 0.9rem;
+}
+
+.empty-state a { color: #1a1a1a; text-decoration: underline; }
+.empty-text { color: #777; font-size: 0.9rem; }
+.empty-text a { color: #1a1a1a; text-decoration: underline; }
+
+/* ===== FOOTER ===== */
+.footer {
+ background: #fff;
+ border-top: 1px solid #e5e5e5;
+ padding: 48px 0 32px;
+ margin-top: 40px;
+}
+
+.footer-grid {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: space-between;
+ flex-wrap: nowrap;
+ gap: 40px;
+ margin-bottom: 32px;
+}
+
+
+.footer-col h3 {
+ font-size: 1rem;
+ font-weight: 600;
+ color: #1a1a1a;
+ margin-bottom: 16px;
+}
+
+
+.footer-heading {
+ font-size: 0.85rem;
+ font-weight: 600;
+ color: #1a1a1a;
+ margin-bottom: 12px;
+}
+
+.footer-col a {
+ display: block;
+ font-size: 0.85rem;
+ color: #777;
+ padding: 3px 0;
+ transition: color 0.2s;
+}
+
+.footer-col a:hover { color: #1a1a1a; }
+
+.footer-bottom {
+ border-top: 1px solid #e5e5e5;
+ padding-top: 20px;
+}
+
+.footer-social {
+ display: flex;
+ gap: 12px;
+}
+
+.social-icon {
+ width: 24px;
+ height: 24px;
+ font-size: 8px;
+ color: #999;
+}
+
+/* ============================================
+ RESPONSIVE
+ ============================================ */
+
+/* ── Tablet (≤ 900px) ── */
+@media (max-width: 900px) {
+ .container { padding: 0 20px; }
+
+ /* Navbar */
+ .navbar-inner { min-height: 60px; }
+ .navbar-logo { font-size: 1.1rem; }
+ .navbar-links { gap: 12px; }
+ .navbar-primary,
+ .navbar-auth { gap: 8px; }
+ .nav-link { font-size: 0.85rem; }
+ .nav-btn { padding: 7px 14px; font-size: 0.8rem; }
+ .nav-user { font-size: 0.78rem; padding: 6px 10px; }
+
+ /* Hero */
+ .hero { padding: 60px 0; }
+ .hero-title { font-size: 2.4rem; }
+ .hero-subtitle { font-size: 1rem; }
+
+ /* Grids */
+ .plants-grid { grid-template-columns: repeat(2, 1fr); gap: 20px; }
+ .messages-grid { grid-template-columns: 1fr 1fr; gap: 20px; }
+
+ /* Detail */
+ .detail-layout { grid-template-columns: 1fr; gap: 28px; }
+ .detail-image img { height: 320px; }
+
+ /* Contact */
+ .contact-layout { grid-template-columns: 1fr; }
+ .contact-image-side { max-height: 300px; overflow: hidden; border-radius: 8px; }
+ .contact-img { height: 300px; }
+
+ /* Footer */
+ .footer-grid { grid-template-columns: 1fr 1fr; gap: 28px; }
+
+ /* Forms */
+ .form-row-3 { grid-template-columns: 1fr 1fr; }
+ .form-container { max-width: 100%; }
+}
+
+/* ── Mobile (≤ 640px) ── */
+@media (max-width: 640px) {
+ .container { padding: 0 16px; }
+
+ /* Navbar */
+ .navbar-inner {
+ min-height: 52px;
+ flex-wrap: wrap;
+ padding: 10px 0;
+ }
+ .navbar-links {
+ width: 100%;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 8px;
+ }
+ .navbar-primary,
+ .navbar-auth {
+ width: 100%;
+ justify-content: flex-start;
+ flex-wrap: wrap;
+ gap: 8px;
+ }
+ .nav-link { font-size: 0.8rem; padding: 7px 9px; }
+ .nav-btn { padding: 6px 12px; font-size: 0.78rem; }
+ .nav-user { font-size: 0.78rem; }
+ .navbar-logo { font-size: 1rem; }
+
+ /* Hero */
+ .hero { padding: 40px 0; }
+ .hero-title { font-size: 2rem; }
+ .hero-subtitle { font-size: 0.9rem; margin-bottom: 24px; }
+ .hero-search { max-width: 100%; }
+ .hero-search-input { font-size: 0.85rem; padding: 10px 12px; }
+ .hero-search-btn { padding: 10px 16px; font-size: 0.8rem; }
+
+ /* Sections */
+ .section { padding: 36px 0; }
+ .section-title { font-size: 1.4rem; }
+ .section-header { flex-direction: column; align-items: flex-start; gap: 6px; }
+ .section-footer { margin-top: 36px; }
+
+ /* Page titles */
+ .page-title { font-size: 1.5rem; }
+ .page-subtitle { font-size: 0.85rem; }
+
+ /* Grids */
+ .plants-grid { grid-template-columns: 1fr; gap: 16px; }
+ .messages-grid { grid-template-columns: 1fr; gap: 16px; }
+
+ /* Plant cards */
+ .plant-card-img { height: 180px; }
+
+ /* Detail */
+ .detail-layout { gap: 20px; }
+ .detail-image img { height: 240px; border-radius: 8px; }
+ .detail-title { font-size: 1.5rem; }
+ .detail-actions { flex-direction: column; gap: 10px; }
+ .detail-actions .btn { width: 100%; text-align: center; }
+
+ /* Filter */
+ .filter-bar { flex-direction: column; align-items: stretch; gap: 12px; }
+ .filter-group { min-width: unset; }
+
+ /* Search */
+ .search-bar { flex-direction: column; gap: 10px; }
+ .search-input { width: 100%; }
+
+ /* Forms */
+ .form-row { grid-template-columns: 1fr; }
+ .form-row-3 { grid-template-columns: 1fr; }
+ .form-actions { flex-direction: column; gap: 10px; }
+ .form-actions .btn { width: 100%; text-align: center; }
+ .form-container { max-width: 100%; }
+
+ /* Contact */
+ .contact-image-side { display: none; }
+ .contact-layout { grid-template-columns: 1fr; }
+
+ /* Confirm */
+ .confirm-box { padding: 24px 16px; }
+ .confirm-actions { flex-direction: column; gap: 10px; }
+ .confirm-actions .btn { width: 100%; }
+
+ /* Footer */
+ .footer { padding: 36px 0 24px; }
+ .footer-grid { grid-template-columns: 1fr; gap: 20px; }
+ .footer-social { justify-content: flex-start; }
+
+ /* Messages banner */
+ .messages-banner { padding: 0 16px; }
+}
+
+/* ── Small Mobile (≤ 400px) ── */
+@media (max-width: 400px) {
+ .hero-title { font-size: 1.7rem; }
+ .navbar-links { gap: 6px; }
+ .nav-btn { padding: 5px 10px; font-size: 0.75rem; }
+}
+
+.hero {
+ background-image: url('../images/نبتة خلفية.jpg');
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+}
\ No newline at end of file
diff --git a/Planteer/static/images/sa.svg b/Planteer/static/images/sa.svg
new file mode 100644
index 0000000..596cf48
--- /dev/null
+++ b/Planteer/static/images/sa.svg
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Planteer/static/images/saudi.svg b/Planteer/static/images/saudi.svg
new file mode 100644
index 0000000..596cf48
--- /dev/null
+++ b/Planteer/static/images/saudi.svg
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git "a/Planteer/static/images/\330\265\331\210\330\261\330\251 \331\204\331\204\330\256\331\204\331\201\331\212\330\251 \331\206\330\250\330\247\330\252\330\247\330\252.jpg" "b/Planteer/static/images/\330\265\331\210\330\261\330\251 \331\204\331\204\330\256\331\204\331\201\331\212\330\251 \331\206\330\250\330\247\330\252\330\247\330\252.jpg"
new file mode 100644
index 0000000..4e79e2d
Binary files /dev/null and "b/Planteer/static/images/\330\265\331\210\330\261\330\251 \331\204\331\204\330\256\331\204\331\201\331\212\330\251 \331\206\330\250\330\247\330\252\330\247\330\252.jpg" differ
diff --git "a/Planteer/static/images/\331\206\330\250\330\252\330\251 \330\256\331\204\331\201\331\212\330\251.jpg" "b/Planteer/static/images/\331\206\330\250\330\252\330\251 \330\256\331\204\331\201\331\212\330\251.jpg"
new file mode 100644
index 0000000..3b08671
Binary files /dev/null and "b/Planteer/static/images/\331\206\330\250\330\252\330\251 \330\256\331\204\331\201\331\212\330\251.jpg" differ
diff --git a/Planteer/templates/base.html b/Planteer/templates/base.html
new file mode 100644
index 0000000..a98c771
--- /dev/null
+++ b/Planteer/templates/base.html
@@ -0,0 +1,95 @@
+{% load static %}
+
+
+
+
+
+ {% block title %}Planteer{% endblock %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% if messages %}
+
+ {% for message in messages %}
+
{{ message }}
+ {% endfor %}
+
+ {% endif %}
+ {% block content %}{% endblock %}
+
+
+
+
+
+
+
+
\ No newline at end of file
Commenting as {{ user.username }}
+ +