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..2a9caf0 --- /dev/null +++ b/Planteer/Planteer/settings.py @@ -0,0 +1,126 @@ +""" +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 + +# 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-ys#o(!0#1*q4-6u6&_mj_%w2g(7x5e+j^d8ek196fn-_5nih#@' + +# 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', + 'core', + 'plants', +] + +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': [], + 'DIRS': ['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 = 'media' \ No newline at end of file diff --git a/Planteer/Planteer/urls.py b/Planteer/Planteer/urls.py new file mode 100644 index 0000000..28dc336 --- /dev/null +++ b/Planteer/Planteer/urls.py @@ -0,0 +1,12 @@ + +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('', include('core.urls')), + path('plants/', include('plants.urls')), + path('admin/', admin.site.urls), +] +urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 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/core/__init__.py b/Planteer/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Planteer/core/admin.py b/Planteer/core/admin.py new file mode 100644 index 0000000..b5d6b24 --- /dev/null +++ b/Planteer/core/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin +from .models import Contact + +# Register your models here. + + +class ContactAdmin(admin.ModelAdmin): + list_display = ('first_name', 'last_name', 'email', 'created_at') + list_filter = ('created_at',) + +admin.site.register(Contact, ContactAdmin) \ No newline at end of file diff --git a/Planteer/core/apps.py b/Planteer/core/apps.py new file mode 100644 index 0000000..26f78a8 --- /dev/null +++ b/Planteer/core/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + name = 'core' diff --git a/Planteer/core/migrations/0001_initial.py b/Planteer/core/migrations/0001_initial.py new file mode 100644 index 0000000..e5a2c74 --- /dev/null +++ b/Planteer/core/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 6.0.3 on 2026-04-20 17:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Contact', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('first_name', models.CharField(max_length=100)), + ('last_name', models.CharField(max_length=100)), + ('email', models.EmailField(max_length=254)), + ('message', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + ] diff --git a/Planteer/core/migrations/__init__.py b/Planteer/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Planteer/core/models.py b/Planteer/core/models.py new file mode 100644 index 0000000..cf8c5e8 --- /dev/null +++ b/Planteer/core/models.py @@ -0,0 +1,14 @@ +from django.db import models + +# Create your models here. + +class Contact(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) + + def __str__(self): + return f"{self.first_name} {self.last_name}" + \ No newline at end of file diff --git a/Planteer/core/templates/core/article.html b/Planteer/core/templates/core/article.html new file mode 100644 index 0000000..9212633 --- /dev/null +++ b/Planteer/core/templates/core/article.html @@ -0,0 +1,39 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + + +
+
+
+ {{ article.tag }} +

{{ article.title }}

+
+ {{ article.author }} + {{ article.read_time }} + {{ article.date }} +
+
+
+
+ + +
+
+
+ + + ← Back to Care Tips + + + {% for section in article.content %} +
+

{{ section.heading }}

+

{{ section.text }}

+
+ {% endfor %} + +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/core/templates/core/care_tips.html b/Planteer/core/templates/core/care_tips.html new file mode 100644 index 0000000..10b9e5b --- /dev/null +++ b/Planteer/core/templates/core/care_tips.html @@ -0,0 +1,295 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + + +
+
+ PLANT KNOWLEDGE HUB +

Plant Care Tips

+

+ Expert guides on watering, repotting, pest control, and more — + everything you need to keep your plants thriving. +

+
+ Watering Guide + Repotting + Pest Control + Lighting + Fertilizing +
+
+
+ + +
+
+
+ +
+
+ +

Check soil moisture before every watering — never water on a schedule blindly.

+
+
+ +
+
+ +

Rotate plants 90° every 2 weeks so all sides get equal light exposure.

+
+
+ +
+
+ +

Always use clean, sharp scissors for pruning — dull blades crush stems and invite disease.

+
+
+ +
+
+ +

Most houseplants prefer temperatures between 60–80°F (15–27°C). Avoid cold drafts.

+
+
+ +
+
+
+ + +
+
+ +

Care Guides

+ +
+ +
+
+
+ +
+
Watering Guide
+

Learn how to water your plants correctly. Overwatering is the #1 killer of houseplants.

+
    +
  • Check soil moisture with your finger
  • +
  • Water deeply but infrequently
  • +
  • Use room temperature water
  • +
  • Empty saucers after 30 minutes
  • +
+
+
+ +
+
+
+ +
+
Lighting Guide
+

Different plants need different light levels. Understanding light is key to plant success.

+
    +
  • Bright direct: succulents & cacti
  • +
  • Bright indirect: most tropicals
  • +
  • Low light: ferns & snake plants
  • +
  • Rotate for even growth
  • +
+
+
+ +
+
+
+ +
+
Repotting Guide
+

Know when and how to repot your plants to keep them healthy and growing strong.

+
    +
  • Repot every 1-2 years
  • +
  • Choose a pot 2" larger
  • +
  • Spring is the best time
  • +
  • Use fresh potting mix
  • +
+
+
+ +
+
+
+ +
+
Pest Control
+

Common pests can destroy your plants. Early detection and treatment is essential.

+
    +
  • Inspect leaves regularly
  • +
  • Use neem oil for prevention
  • +
  • Isolate infected plants
  • +
  • Wipe leaves with damp cloth
  • +
+
+
+ +
+
+
+ +
+
Fertilizing
+

Feed your plants the right nutrients to promote healthy growth and vibrant foliage.

+
    +
  • Fertilize in spring & summer
  • +
  • Use balanced NPK fertilizer
  • +
  • Never fertilize dry soil
  • +
  • Less is more — avoid overfeed
  • +
+
+
+ +
+
+
+ +
+
Humidity & Air
+

Many tropical plants need humidity. Learn how to create the perfect environment.

+
    +
  • Mist leaves in dry weather
  • +
  • Use a pebble tray with water
  • +
  • Group plants together
  • +
  • Avoid air vents & heaters
  • +
+
+
+ +
+
+
+ + +
+
+ +

Latest Articles

+ + +
+
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/core/templates/core/contact.html b/Planteer/core/templates/core/contact.html new file mode 100644 index 0000000..a1bbd2f --- /dev/null +++ b/Planteer/core/templates/core/contact.html @@ -0,0 +1,77 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + +
+
+
+ + +
+ GET IN TOUCH +

Contact Us

+

Have a question or suggestion? We'd love to hear from you.

+
+ + {% if request.GET.sent %} +
+ ✅ Message sent successfully! +
+ {% endif %} + + {% if errors %} +
+ {% for error in errors %} +

⚠️ {{ error }}

+ {% endfor %} +
+ {% endif %} + + +
+
+ {% csrf_token %} + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ + + +
+
+ +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/core/templates/core/favourites.html b/Planteer/core/templates/core/favourites.html new file mode 100644 index 0000000..56866a1 --- /dev/null +++ b/Planteer/core/templates/core/favourites.html @@ -0,0 +1,47 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + +
+ + +
+ Home + + Favourites +
+ +
+
+

My Favourites

+

Plants you have saved

+
+ Browse All Plants → +
+ + + {% if plants %} +
+ {% for plant in plants %} +
+ {% include "plants/partials/plant_card.html" with plant=plant %} +
+ {% endfor %} +
+ + {% else %} + +
+
+ +
+

No favourites yet

+

Press the heart icon on any plant to save it here

+ + Browse Plants + +
+ {% endif %} + +
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/core/templates/core/find_my_plant.html b/Planteer/core/templates/core/find_my_plant.html new file mode 100644 index 0000000..dfeed3d --- /dev/null +++ b/Planteer/core/templates/core/find_my_plant.html @@ -0,0 +1,186 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + + +
+
+
+
+ PERSONALIZED FOR YOU +

Find Your Perfect Plant

+

+ Answer 3 quick questions about your space, lifestyle, and experience + — we'll match you with the plants that will actually thrive with you. +

+ +
+
+
+
+ + +
+
+
+ +
+
+
+ +
+
3 Quick Questions
+

Light, watering habits, and experience level

+
+
+ +
+
+
+ +
+
Smart Matching
+

Our algorithm scores all 16 plants against your answers

+
+
+ +
+
+
+ +
+
4 Perfect Picks
+

Get your top matches with reasons why

+
+
+ +
+ + + Start Quiz Now + +
+
+ + +
+
+
+
+ +

Find My Plant 🌿

+ +
+ + +
+
1. How much natural light does your space get?
+
+ + + +
+
+ + +
+
2. How often do you want to water your plant?
+
+ + + +
+
+ + +
+
3. What is your experience with plants?
+
+ + + +
+
+ +
+ +
+ +
+
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/core/templates/core/find_my_plant_results.html b/Planteer/core/templates/core/find_my_plant_results.html new file mode 100644 index 0000000..a526581 --- /dev/null +++ b/Planteer/core/templates/core/find_my_plant_results.html @@ -0,0 +1,83 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + +
+ + +
+ YOUR MATCHES +

Your Perfect Plants

+

Based on your answers, here are the best plants for you

+
+ + + {% if plants %} +
+ {% for plant in plants %} +
+
+ +
+ {{ plant.get_category_display }} + {% if plant.is_edible %} + Edible + {% endif %} +
+ + {% if plant.image %} + {{ plant.name }} + {% endif %} + +
+
{{ plant.name }}
+

{{ plant.about|truncatechars:80 }}

+ +
+ {% if plant.water %} + {{ plant.get_water_display }} + {% endif %} + {% if plant.light %} + {{ plant.get_light_display }} + {% endif %} + {% if plant.care_level %} + {{ plant.get_care_level_display }} + {% endif %} +
+ + + View Details → + +
+ +
+
+ {% endfor %} +
+ + {% else %} + +
+
+ +
+

No exact matches found

+

Try different answers or browse all our plants

+ +
+ {% endif %} + + + {% if plants %} +
+ + ← Retake Quiz + +
+ {% endif %} + +
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/core/templates/core/home.html b/Planteer/core/templates/core/home.html new file mode 100644 index 0000000..3d985ab --- /dev/null +++ b/Planteer/core/templates/core/home.html @@ -0,0 +1,274 @@ +{% extends 'Planteer/base.html' %} + +{% block content %} + +
+
+
+ + + +
+ PLANT DATABASE +

Planteer

+

Plant Database For Plants Lovers

+

+ Discover hundreds of beautiful plants, learn how to care for them, + and explore their uses. Your ultimate botanical reference. +

+ + + + + +
+ Tropical + Herbs + Succulents + Flowering +
+
+ +
+ +
+
+ +
+
+

16+

+ Plant Species +
+
+ +
+
+ +
+
+

6

+ Categories +
+
+ +
+
+ +
+
+

100%

+ Expert Curated +
+
+ +
+ +
+ + + +
+
+ + +

Plant Categories

+ + + + + +
+ +
+
+

ALL Plants

+

Learn more about plants

+
+ More → +
+ +
+ {% for plant in plants %} +
+
+ {% if plant.image %} + + {% endif %} +
+
{{ plant.name }}
+

{{ plant.about|truncatechars:50 }}

+ {{ plant.get_category_display }} +
+
+
+ {% endfor %} +
+ +
+
+ + + +
+
+ + +
+
+ LATEST ADDITIONS +

Recently Added Plants

+
+ + View All Plants → + +
+ + +
+ {% for plant in recent_plants %} +
+ {% include "plants/partials/plant_card_simple.html" with plant=plant %} +
+ {% endfor %} +
+ +
+
+ + + + + + + +
+
+ +

WHY PLANTEER

+

More Than Just Plants

+ +
+ +
+
+
+
Curated Collection
+

Every plant in our catalog is carefully selected for quality, beauty, ease of care.

+
+
+ +
+
+
+
Expert Guidance
+

Detailed care instructions and tips from experienced botanists for every plant.

+
+
+ +
+
+
+
Easy Discovery
+

Filter by category, edibility, and more to find the perfect plant for your space.

+
+
+ +
+
+
+
Plant Community
+

Join thousands of plant lovers sharing their growing journeys and experiences.

+
+
+ +
+
+
+ + + +
+
+ +

WHAT PEOPLE SAY

+

Loved by Plant Lovers

+ + +
+
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/core/templates/core/login.html b/Planteer/core/templates/core/login.html new file mode 100644 index 0000000..29cd6da --- /dev/null +++ b/Planteer/core/templates/core/login.html @@ -0,0 +1,59 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + +
+
+
+ +
+ + +
+
+ +
+

Welcome Back

+

Sign in to your Planteer account

+
+ + + {% if error %} +
+ ⚠️ {{ error }} +
+ {% endif %} + + +
+ {% csrf_token %} + +
+ + +
+ +
+ + +
+ + + +
+ +

+ Don't have an account? + Create one +

+ +
+ +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/core/templates/core/messages.html b/Planteer/core/templates/core/messages.html new file mode 100644 index 0000000..445dbf3 --- /dev/null +++ b/Planteer/core/templates/core/messages.html @@ -0,0 +1,46 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + +
+ +

Messages

+ +
+ + {% for msg in contacts %} + +
+
+
+
+ {{ msg.first_name|first }}{{ msg.last_name|first }} +
+
+
{{ msg.first_name }} {{ msg.last_name }}
+

{{ msg.email }}

+
+ + + {% if request.user.is_staff %} + + + + {% endif %} + +
+

{{ msg.message }}

+ {{ msg.created_at|date:"d M Y" }} +
+
+ + {% empty %} +

No messages yet

+ {% endfor %} + +
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/core/templates/core/register.html b/Planteer/core/templates/core/register.html new file mode 100644 index 0000000..74d6004 --- /dev/null +++ b/Planteer/core/templates/core/register.html @@ -0,0 +1,65 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + +
+
+
+ +
+ + +
+
+ +
+

Create Account

+

Join Planteer and start exploring plants

+
+ + + {% if error %} +
+ ⚠️ {{ error }} +
+ {% endif %} + + +
+ {% csrf_token %} + +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +
+ +

+ Already have an account? + Sign in +

+ +
+ +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/core/tests.py b/Planteer/core/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/Planteer/core/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Planteer/core/urls.py b/Planteer/core/urls.py new file mode 100644 index 0000000..3ee911d --- /dev/null +++ b/Planteer/core/urls.py @@ -0,0 +1,31 @@ +from django.urls import path +from . import views + +app_name = "core" + +urlpatterns = [ + path('', views.home, name='home'), + + path('contact/', views.contact, name='contact'), + + path('messages/', views.messages_view, name='messages'), + + path('toggle-dark-mode/', views.toggle_dark_mode, name='toggle_dark_mode'), + + path('care-tips/', views.care_tips_view, name='care_tips'), + + path('care-tips//', views.article_view, name='article'), + + path('find-my-plant/', views.find_my_plant_view, name='find_my_plant'), + + path('find-my-plant/results/', views.find_my_plant_results, name='find_my_plant_results'), + + path('favourites/', views.favourites_view, name='favourites'), + + path('register/', views.register_view, name='register'), + + path('login/', views.login_view, name='login'), + + path('logout/', views.logout_view, name='logout'), + +] \ No newline at end of file diff --git a/Planteer/core/views.py b/Planteer/core/views.py new file mode 100644 index 0000000..c4d635b --- /dev/null +++ b/Planteer/core/views.py @@ -0,0 +1,241 @@ +from django.shortcuts import render +from plants.models import Plant +from django.shortcuts import render, redirect +from .models import Contact + + + +# Create your views here. + +def home_view(request): + return render(request, 'core/home.html') + +def contact_view(request): + return render(request, 'core/contact.html') + + +def home(request): + plants = Plant.objects.all()[:3] + recent_plants = Plant.objects.all().order_by('-created_at')[:3] + + return render(request, 'core/home.html', { + 'plants': plants, + 'recent_plants': recent_plants, + }) + + + +def contact(request): + errors = [] + form_data = {} + + if request.method == 'POST': + first_name = request.POST.get('first_name', '').strip() + last_name = request.POST.get('last_name', '').strip() + email = request.POST.get('email', '').strip() + message = request.POST.get('message', '').strip() + + form_data = { + 'first_name': first_name, + 'last_name': last_name, + 'email': email, + 'message': message, + } + + if not first_name: + errors.append('First name is required') + if not last_name: + errors.append('Last name is required') + if not email or '@' not in email: + errors.append('Valid email is required') + if not message: + errors.append('Message is required') + + if not errors: + Contact.objects.create(**form_data) + return redirect('/contact/?sent=1') + + return render(request, 'core/contact.html', { + 'errors': errors, + 'form_data': form_data, + }) + +def messages_view(request): + contacts = Contact.objects.all().order_by('-created_at') + + return render(request, 'core/messages.html', { + 'contacts': contacts + }) + + + + + +def toggle_dark_mode(request): + redirect_url = request.GET.get('next', '/') + response = redirect(redirect_url) + + current = request.COOKIES.get('dark_mode', 'off') + if current == 'on': + response.set_cookie('dark_mode', 'off', max_age=60*60*24*30) + else: + response.set_cookie('dark_mode', 'on', max_age=60*60*24*30) + + return response + + +def care_tips_view(request): + return render(request, 'core/care_tips.html') + + +ARTICLES = { + 'watering-guide': { + 'title': 'The Complete Watering Guide for Houseplants', + 'tag': 'Watering', + 'author': 'Lily', + 'read_time': '7 min read', + 'date': 'Mar 12', + 'image': 'https://images.unsplash.com/photo-1416879595882-3373a0480b5b?w=1200', + 'content': [ + {'heading': 'The Golden Rule', 'text': 'Never water on a schedule. Always check the soil moisture first by sticking your finger 2 inches into the soil. If it feels dry, water deeply. If still moist, wait another day or two.'}, + {'heading': 'How to Water Correctly', 'text': 'Water slowly and deeply until it drains from the bottom. This encourages roots to grow downward. Avoid shallow watering which leads to weak root systems.'}, + {'heading': 'Common Mistakes', 'text': 'Overwatering is the number one killer of houseplants. Signs include yellowing leaves, mushy stems, and soggy soil. Underwatering shows as dry, crispy leaf edges and wilting.'}, + {'heading': 'Water Quality', 'text': 'Most plants prefer room temperature water. Let tap water sit overnight to allow chlorine to evaporate. Rainwater or filtered water is ideal for sensitive plants like orchids.'}, + ] + }, + 'repotting-guide': { + 'title': 'When & How to Repot Your Plants the Right Way', + 'tag': 'Repotting', + 'author': 'Marcus', + 'read_time': '8 min read', + 'date': 'Mar 28', + 'image': 'https://images.unsplash.com/photo-1466692476868-aef1dfb1e735?w=1200', + 'content': [ + {'heading': 'When to Repot', 'text': 'Signs your plant needs repotting: roots growing out of drainage holes, plant drying out too quickly, or roots circling the bottom of the pot. Spring is the best time to repot.'}, + {'heading': 'Choosing the Right Pot', 'text': 'Always go up just one size — about 2 inches larger in diameter. Too large a pot holds excess moisture and can lead to root rot.'}, + {'heading': 'The Repotting Process', 'text': 'Water your plant the day before. Gently remove it, shake off old soil, trim any dead roots, and place in fresh potting mix. Water lightly after repotting.'}, + {'heading': 'After Care', 'text': 'Keep the plant in indirect light for 1-2 weeks after repotting. Avoid fertilizing for at least a month to let roots settle in the new soil.'}, + ] + }, + 'soil-guide': { + 'title': 'The Best Soil Mixes for Every Plant Type', + 'tag': 'Soil & Nutrients', + 'author': 'Lily', + 'read_time': '6 min read', + 'date': 'Apr 2', + 'image': 'https://images.unsplash.com/photo-1585320806297-9794b3e4eeae?w=1200', + 'content': [ + {'heading': 'Why Soil Matters', 'text': 'Soil is the foundation of plant health. It provides nutrients, support, and regulates moisture. Using the wrong soil type is one of the most common mistakes.'}, + {'heading': 'For Succulents & Cacti', 'text': 'Use a fast-draining mix with extra perlite or coarse sand. Standard potting soil retains too much moisture and will cause root rot in succulents.'}, + {'heading': 'For Tropical Plants', 'text': 'A rich, well-draining mix works best. Combine potting soil with perlite and orchid bark for good aeration and moisture retention.'}, + {'heading': 'For Herbs', 'text': 'Herbs prefer a light, fertile mix with good drainage. Add compost for extra nutrients. Avoid heavy clay soils that compact easily.'}, + ] + }, + 'propagation-guide': { + 'title': 'How to Propagate Any Plant at Home', + 'tag': 'Propagation', + 'author': 'Sara', + 'read_time': '5 min read', + 'date': 'Apr 8', + 'image': 'https://images.unsplash.com/photo-1518531933037-91b2f5f229cc?w=1200', + 'content': [ + {'heading': 'What is Propagation', 'text': 'Propagation is growing new plants from an existing one. It is free, fun, and a great way to expand your collection or share plants with friends.'}, + {'heading': 'Stem Cuttings', 'text': 'Cut a healthy stem just below a node. Remove lower leaves and place in water or moist soil. Keep in bright indirect light and change water every few days.'}, + {'heading': 'Leaf Cuttings', 'text': 'Perfect for succulents. Gently twist a healthy leaf off and lay it on top of dry soil. Mist lightly every few days until roots and a new plant emerge.'}, + {'heading': 'Division', 'text': 'For clumping plants, remove from pot and gently separate the root ball into sections. Each section should have roots and leaves. Pot up separately.'}, + ] + }, + 'seasonal-guide': { + 'title': 'Seasonal Plant Care: What to Do Each Month', + 'tag': 'Seasonal', + 'author': 'Marcus', + 'read_time': '9 min read', + 'date': 'Apr 15', + 'image': 'https://images.unsplash.com/photo-1534710961216-75c88202f43e?w=1200', + 'content': [ + {'heading': 'Spring (Mar-May)', 'text': 'This is the growing season. Start fertilizing, repot if needed, and increase watering as days get longer. Great time to take cuttings and propagate.'}, + {'heading': 'Summer (Jun-Aug)', 'text': 'Watch for heat stress and increase humidity. Water more frequently but check soil first. Move plants away from hot windows to avoid leaf scorch.'}, + {'heading': 'Autumn (Sep-Nov)', 'text': 'Gradually reduce watering and stop fertilizing. Bring outdoor plants inside before temperatures drop. Check for pests before bringing them indoors.'}, + {'heading': 'Winter (Dec-Feb)', 'text': 'Most plants go dormant. Water sparingly and avoid fertilizing. Keep plants away from cold drafts and heating vents. Dust leaves to maximize light absorption.'}, + ] + }, +} + +def article_view(request, slug): + article = ARTICLES.get(slug) + if not article: + return redirect('/care-tips/') + return render(request, 'core/article.html', {'article': article}) + +def find_my_plant_view(request): + return render(request, 'core/find_my_plant.html') + +def find_my_plant_results(request): + light = request.GET.get('light') + water = request.GET.get('water') + care = request.GET.get('care') + + from plants.models import Plant + plants = Plant.objects.all() + + if light: + plants = plants.filter(light=light) + if water: + plants = plants.filter(water=water) + if care: + plants = plants.filter(care_level=care) + + return render(request, 'core/find_my_plant_results.html', { + 'plants': plants, + 'light': light, + 'water': water, + 'care': care, + }) + + +def favourites_view(request): + from plants.models import Plant + favs = request.COOKIES.get('favs', '') + fav_ids = [f for f in favs.split(',') if f] + plants = Plant.objects.filter(id__in=fav_ids) + return render(request, 'core/favourites.html', {'plants': plants}) + + +from django.contrib.auth import authenticate, login, logout +from django.contrib.auth.models import User + +def register_view(request): + if request.method == 'POST': + username = request.POST.get('username') + email = request.POST.get('email') + password = request.POST.get('password') + + if User.objects.filter(username=username).exists(): + return render(request, 'core/register.html', {'error': 'Username already exists'}) + + user = User.objects.create_user(username=username, email=email, password=password) + user.save() + login(request, user) + return redirect('/') + + return render(request, 'core/register.html') + + +def login_view(request): + if request.method == 'POST': + username = request.POST.get('username') + password = request.POST.get('password') + + user = authenticate(request, username=username, password=password) + if user is not None: + login(request, user) + return redirect('/') + else: + return render(request, 'core/login.html', {'error': 'Invalid username or password'}) + + return render(request, 'core/login.html') + + +def logout_view(request): + logout(request) + return redirect('/') diff --git a/Planteer/db.sqlite3 b/Planteer/db.sqlite3 new file mode 100644 index 0000000..6f42b14 Binary files /dev/null and b/Planteer/db.sqlite3 differ 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/Flag_of_Brazil.svg.png b/Planteer/media/flags/Flag_of_Brazil.svg.png new file mode 100644 index 0000000..69801c3 Binary files /dev/null and b/Planteer/media/flags/Flag_of_Brazil.svg.png differ diff --git a/Planteer/media/flags/ae.webp b/Planteer/media/flags/ae.webp new file mode 100644 index 0000000..785ab09 Binary files /dev/null and b/Planteer/media/flags/ae.webp differ diff --git a/Planteer/media/flags/eg.webp b/Planteer/media/flags/eg.webp new file mode 100644 index 0000000..c4bf2a2 Binary files /dev/null and b/Planteer/media/flags/eg.webp differ diff --git a/Planteer/media/flags/india.jpg b/Planteer/media/flags/india.jpg new file mode 100644 index 0000000..a9d9af9 Binary files /dev/null and b/Planteer/media/flags/india.jpg differ diff --git a/Planteer/media/flags/iq.webp b/Planteer/media/flags/iq.webp new file mode 100644 index 0000000..69ca23e Binary files /dev/null and b/Planteer/media/flags/iq.webp differ diff --git a/Planteer/media/flags/kw.webp b/Planteer/media/flags/kw.webp new file mode 100644 index 0000000..1e64bef Binary files /dev/null and b/Planteer/media/flags/kw.webp differ diff --git a/Planteer/media/flags/mx.webp b/Planteer/media/flags/mx.webp new file mode 100644 index 0000000..aa40287 Binary files /dev/null and b/Planteer/media/flags/mx.webp differ diff --git a/Planteer/media/flags/saudi.jpg b/Planteer/media/flags/saudi.jpg new file mode 100644 index 0000000..6e5b49d Binary files /dev/null and b/Planteer/media/flags/saudi.jpg differ diff --git a/Planteer/media/flags/south_africa.png b/Planteer/media/flags/south_africa.png new file mode 100644 index 0000000..147ae6d Binary files /dev/null and b/Planteer/media/flags/south_africa.png differ diff --git a/Planteer/media/flags/usa.png b/Planteer/media/flags/usa.png new file mode 100644 index 0000000..9d7da1c Binary files /dev/null and b/Planteer/media/flags/usa.png differ diff --git a/Planteer/media/plants/Echeveria.jpg b/Planteer/media/plants/Echeveria.jpg new file mode 100644 index 0000000..01282ed Binary files /dev/null and b/Planteer/media/plants/Echeveria.jpg differ diff --git a/Planteer/media/plants/Fiddle_Leaf_Fig.webp b/Planteer/media/plants/Fiddle_Leaf_Fig.webp new file mode 100644 index 0000000..07cdafa Binary files /dev/null and b/Planteer/media/plants/Fiddle_Leaf_Fig.webp differ diff --git a/Planteer/media/plants/Jasmine.jpg b/Planteer/media/plants/Jasmine.jpg new file mode 100644 index 0000000..132c070 Binary files /dev/null and b/Planteer/media/plants/Jasmine.jpg differ diff --git a/Planteer/media/plants/aloe_vera.jpg b/Planteer/media/plants/aloe_vera.jpg new file mode 100644 index 0000000..1c9a0ba Binary files /dev/null and b/Planteer/media/plants/aloe_vera.jpg differ diff --git a/Planteer/media/plants/aloe_vera_1JvUJDd.jpg b/Planteer/media/plants/aloe_vera_1JvUJDd.jpg new file mode 100644 index 0000000..1c9a0ba Binary files /dev/null and b/Planteer/media/plants/aloe_vera_1JvUJDd.jpg differ diff --git a/Planteer/media/plants/basil.jpg b/Planteer/media/plants/basil.jpg new file mode 100644 index 0000000..549ff54 Binary files /dev/null and b/Planteer/media/plants/basil.jpg differ diff --git a/Planteer/media/plants/bird_of_paradias.jpg b/Planteer/media/plants/bird_of_paradias.jpg new file mode 100644 index 0000000..16c24d9 Binary files /dev/null and b/Planteer/media/plants/bird_of_paradias.jpg differ diff --git a/Planteer/media/plants/bird_of_paradias_G3MJGan.jpg b/Planteer/media/plants/bird_of_paradias_G3MJGan.jpg new file mode 100644 index 0000000..16c24d9 Binary files /dev/null and b/Planteer/media/plants/bird_of_paradias_G3MJGan.jpg differ diff --git a/Planteer/media/plants/boston_fern.webp b/Planteer/media/plants/boston_fern.webp new file mode 100644 index 0000000..2d59b03 Binary files /dev/null and b/Planteer/media/plants/boston_fern.webp differ diff --git a/Planteer/media/plants/jade-plant.jpg b/Planteer/media/plants/jade-plant.jpg new file mode 100644 index 0000000..e752446 Binary files /dev/null and b/Planteer/media/plants/jade-plant.jpg differ diff --git a/Planteer/media/plants/maidenhair-fern.webp b/Planteer/media/plants/maidenhair-fern.webp new file mode 100644 index 0000000..494e1fd Binary files /dev/null and b/Planteer/media/plants/maidenhair-fern.webp differ diff --git a/Planteer/media/plants/mint.jpg b/Planteer/media/plants/mint.jpg new file mode 100644 index 0000000..30a4909 Binary files /dev/null and b/Planteer/media/plants/mint.jpg differ diff --git a/Planteer/media/plants/monstera.jpg b/Planteer/media/plants/monstera.jpg new file mode 100644 index 0000000..b2a792b Binary files /dev/null and b/Planteer/media/plants/monstera.jpg differ diff --git a/Planteer/media/plants/monstera_Ie0wWYH.jpg b/Planteer/media/plants/monstera_Ie0wWYH.jpg new file mode 100644 index 0000000..b2a792b Binary files /dev/null and b/Planteer/media/plants/monstera_Ie0wWYH.jpg differ diff --git a/Planteer/media/plants/olive_tree.png b/Planteer/media/plants/olive_tree.png new file mode 100644 index 0000000..2b88b6f Binary files /dev/null and b/Planteer/media/plants/olive_tree.png differ diff --git a/Planteer/media/plants/palm_tree.webp b/Planteer/media/plants/palm_tree.webp new file mode 100644 index 0000000..3bab474 Binary files /dev/null and b/Planteer/media/plants/palm_tree.webp differ diff --git a/Planteer/media/plants/rose.avif b/Planteer/media/plants/rose.avif new file mode 100644 index 0000000..af9d054 Binary files /dev/null and b/Planteer/media/plants/rose.avif differ diff --git a/Planteer/media/plants/rosemary.webp b/Planteer/media/plants/rosemary.webp new file mode 100644 index 0000000..218e3e8 Binary files /dev/null and b/Planteer/media/plants/rosemary.webp differ diff --git a/Planteer/media/plants/rosemary_UOglqzN.webp b/Planteer/media/plants/rosemary_UOglqzN.webp new file mode 100644 index 0000000..02f2c0e Binary files /dev/null and b/Planteer/media/plants/rosemary_UOglqzN.webp differ diff --git a/Planteer/media/plants/rosemary_f4MYaBW.webp b/Planteer/media/plants/rosemary_f4MYaBW.webp new file mode 100644 index 0000000..fa3bf09 Binary files /dev/null and b/Planteer/media/plants/rosemary_f4MYaBW.webp differ diff --git a/Planteer/media/plants/sunflower.avif b/Planteer/media/plants/sunflower.avif new file mode 100644 index 0000000..68c25f6 Binary files /dev/null and b/Planteer/media/plants/sunflower.avif differ diff --git a/Planteer/media/plants/sunflower1.webp b/Planteer/media/plants/sunflower1.webp new file mode 100644 index 0000000..632b86b Binary files /dev/null and b/Planteer/media/plants/sunflower1.webp differ diff --git a/Planteer/media/plants/wjn.logo.png b/Planteer/media/plants/wjn.logo.png new file mode 100644 index 0000000..4339d25 Binary files /dev/null and b/Planteer/media/plants/wjn.logo.png differ diff --git a/Planteer/media/plants/wjn_d28Ytpf.logo.png b/Planteer/media/plants/wjn_d28Ytpf.logo.png new file mode 100644 index 0000000..4339d25 Binary files /dev/null and b/Planteer/media/plants/wjn_d28Ytpf.logo.png 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..cca2c50 --- /dev/null +++ b/Planteer/plants/admin.py @@ -0,0 +1,24 @@ +from django.contrib import admin +from .models import Plant, Comment, Country +# Register your models here. + + + + +class PlantAdmin(admin.ModelAdmin): + list_display = ('name', 'category', 'is_edible', 'created_at') + list_filter = ('category', 'is_edible') + +class CommentAdmin(admin.ModelAdmin): + list_display = ('name', 'plant', 'created_at') + list_filter = ('plant',) + +admin.site.register(Plant, PlantAdmin) +admin.site.register(Comment, CommentAdmin) + + + +class CountryAdmin(admin.ModelAdmin): + list_display = ('name',) + +admin.site.register(Country, CountryAdmin) \ No newline at end of file diff --git a/Planteer/plants/apps.py b/Planteer/plants/apps.py new file mode 100644 index 0000000..478c3ce --- /dev/null +++ b/Planteer/plants/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class PlantsConfig(AppConfig): + name = 'plants' diff --git a/Planteer/plants/forms.py b/Planteer/plants/forms.py new file mode 100644 index 0000000..22162c9 --- /dev/null +++ b/Planteer/plants/forms.py @@ -0,0 +1,7 @@ +from django import forms +from .models import Plant + +class PlantForm(forms.ModelForm): + class Meta: + model = Plant + fields = '__all__' \ 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..2e3f9d9 --- /dev/null +++ b/Planteer/plants/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 6.0.3 on 2026-04-19 05:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + 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)), + ('about', models.TextField()), + ('used_for', models.TextField()), + ('image', models.ImageField(upload_to='plants/')), + ('category', models.CharField(choices=[('tree', 'Tree'), ('fruit', 'Fruit'), ('vegetable', 'Vegetable'), ('herbs', 'Herbs'), ('flowering', 'Flowering')], max_length=20)), + ('is_edible', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + ] diff --git a/Planteer/plants/migrations/0002_alter_plant_image.py b/Planteer/plants/migrations/0002_alter_plant_image.py new file mode 100644 index 0000000..f25f6e4 --- /dev/null +++ b/Planteer/plants/migrations/0002_alter_plant_image.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.3 on 2026-04-19 17:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('plants', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='plant', + name='image', + field=models.ImageField(blank=True, null=True, upload_to='plants/'), + ), + ] diff --git a/Planteer/plants/migrations/0003_contact_alter_plant_category.py b/Planteer/plants/migrations/0003_contact_alter_plant_category.py new file mode 100644 index 0000000..a3bdc37 --- /dev/null +++ b/Planteer/plants/migrations/0003_contact_alter_plant_category.py @@ -0,0 +1,29 @@ +# Generated by Django 6.0.3 on 2026-04-19 19:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('plants', '0002_alter_plant_image'), + ] + + operations = [ + migrations.CreateModel( + name='Contact', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('first_name', models.CharField(max_length=100)), + ('last_name', models.CharField(max_length=100)), + ('email', models.EmailField(max_length=254)), + ('message', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.AlterField( + model_name='plant', + name='category', + field=models.CharField(choices=[('TR', 'Tropical'), ('SU', 'Succulents'), ('HE', 'Herbs'), ('FE', 'Ferns'), ('FL', 'Flowering'), ('TRS', 'Trees')], max_length=20), + ), + ] diff --git a/Planteer/plants/migrations/0004_comment_delete_contact.py b/Planteer/plants/migrations/0004_comment_delete_contact.py new file mode 100644 index 0000000..86af82f --- /dev/null +++ b/Planteer/plants/migrations/0004_comment_delete_contact.py @@ -0,0 +1,27 @@ +# Generated by Django 6.0.3 on 2026-04-20 17:42 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('plants', '0003_contact_alter_plant_category'), + ] + + operations = [ + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('content', 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')), + ], + ), + migrations.DeleteModel( + name='Contact', + ), + ] diff --git a/Planteer/plants/migrations/0005_country_plant_countries.py b/Planteer/plants/migrations/0005_country_plant_countries.py new file mode 100644 index 0000000..1b96e00 --- /dev/null +++ b/Planteer/plants/migrations/0005_country_plant_countries.py @@ -0,0 +1,26 @@ +# Generated by Django 6.0.3 on 2026-04-21 09:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('plants', '0004_comment_delete_contact'), + ] + + 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)), + ('flag', models.ImageField(blank=True, null=True, upload_to='flags/')), + ], + ), + migrations.AddField( + model_name='plant', + name='countries', + field=models.ManyToManyField(blank=True, related_name='plants', to='plants.country'), + ), + ] diff --git a/Planteer/plants/migrations/0006_plant_care_level_plant_light_plant_water.py b/Planteer/plants/migrations/0006_plant_care_level_plant_light_plant_water.py new file mode 100644 index 0000000..6f5d262 --- /dev/null +++ b/Planteer/plants/migrations/0006_plant_care_level_plant_light_plant_water.py @@ -0,0 +1,28 @@ +# Generated by Django 6.0.3 on 2026-04-22 21:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('plants', '0005_country_plant_countries'), + ] + + operations = [ + migrations.AddField( + model_name='plant', + name='care_level', + field=models.CharField(blank=True, choices=[('EASY', 'Easy'), ('MED', 'Medium'), ('HARD', 'Hard')], max_length=10, null=True), + ), + migrations.AddField( + model_name='plant', + name='light', + field=models.CharField(blank=True, choices=[('DIR', 'Direct'), ('IND', 'Indirect'), ('LOW', 'Low Light')], max_length=10, null=True), + ), + migrations.AddField( + model_name='plant', + name='water', + field=models.CharField(blank=True, choices=[('LOW', 'Low'), ('MO', 'Moderate'), ('HIGH', 'High')], max_length=10, null=True), + ), + ] diff --git a/Planteer/plants/migrations/0007_comment_user.py b/Planteer/plants/migrations/0007_comment_user.py new file mode 100644 index 0000000..f94b76d --- /dev/null +++ b/Planteer/plants/migrations/0007_comment_user.py @@ -0,0 +1,21 @@ +# Generated by Django 6.0.3 on 2026-04-26 18:07 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('plants', '0006_plant_care_level_plant_light_plant_water'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='comment', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] 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..780ad38 --- /dev/null +++ b/Planteer/plants/models.py @@ -0,0 +1,63 @@ +from django.db import models +from django.contrib.auth.models import User + +class Country(models.Model): + name = models.CharField(max_length=100) + flag = models.ImageField(upload_to='flags/', null=True, blank=True) + + def __str__(self): + return self.name + + +class Plant(models.Model): + + class Category(models.TextChoices): + TR = 'TR', 'Tropical' + SU = 'SU', 'Succulents' + HE = 'HE', 'Herbs' + FE = 'FE', 'Ferns' + FL = 'FL', 'Flowering' + TRS = 'TRS', 'Trees' + + class WaterLevel(models.TextChoices): + LOW = 'LOW', 'Low' + MODERATE = 'MO', 'Moderate' + HIGH = 'HIGH', 'High' + + class LightLevel(models.TextChoices): + DIRECT = 'DIR', 'Direct' + INDIRECT = 'IND', 'Indirect' + LOW_LIGHT = 'LOW', 'Low Light' + + class CareLevel(models.TextChoices): + EASY = 'EASY', 'Easy' + MEDIUM = 'MED', 'Medium' + HARD = 'HARD', 'Hard' + + name = models.CharField(max_length=200) + about = models.TextField() + used_for = models.TextField() + image = models.ImageField(upload_to='plants/', null=True, blank=True) + category = models.CharField(max_length=20, choices=Category.choices) + is_edible = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + countries = models.ManyToManyField(Country, blank=True, related_name='plants') + water = models.CharField(max_length=10, choices=WaterLevel.choices, null=True, blank=True) + light = models.CharField(max_length=10, choices=LightLevel.choices, null=True, blank=True) + care_level = models.CharField(max_length=10, choices=CareLevel.choices, null=True, blank=True) + + def __str__(self): + return self.name + + +class Comment(models.Model): + plant = models.ForeignKey(Plant, on_delete=models.CASCADE, related_name='comments') + user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True) + name = models.CharField(max_length=100) + content = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.name} - {self.plant.name}" + + diff --git a/Planteer/plants/templates/plants/all.html b/Planteer/plants/templates/plants/all.html new file mode 100644 index 0000000..381e158 --- /dev/null +++ b/Planteer/plants/templates/plants/all.html @@ -0,0 +1,116 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + + +
+ +
+ Home + + All Plants +
+ +
+
+

All Plants

+

Browse our complete plant database

+
+ + + + Add New Plant + +
+ +
+ + +
+
+ + +
+
+ +
+
Filters
+ Clear all +
+ + +

CATEGORY

+ + + All Categories + + + {% for value, label in categories %} + + {{ label }} + + {% endfor %} + +
+ + +

EDIBILITY

+ +
+ Edible Only + +
+ +
+ + +

COUNTRY

+
+ {% for country in countries %} + + {{ country.name }} + + {% endfor %} +
+ +
+
+ + +
+ +
+ +
+ +

+ Showing {{ plants|length }} plants +

+ +
+ {% for plant in plants %} +
+
+ {% include "plants/partials/plant_card.html" with plant=plant %} +
+
+ {% empty %} +

No plants found

+ {% endfor %} +
+ +
+ +
+
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/plants/templates/plants/country_plants.html b/Planteer/plants/templates/plants/country_plants.html new file mode 100644 index 0000000..6e463b5 --- /dev/null +++ b/Planteer/plants/templates/plants/country_plants.html @@ -0,0 +1,38 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + +
+ + +
+ Home + + All Plants + + {{ country.name }} +
+ +
+ {% if country.flag %} + + {% endif %} + +
+

Plants native to {{ country.name }}

+

Showing {{ plants|length }} plants

+
+ + +
+ {% for plant in plants %} +
+ {% include "plants/partials/plant_card.html" with plant=plant %} +
+ {% empty %} +

No plants found for this country.

+ {% endfor %} +
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/plants/templates/plants/detail.html b/Planteer/plants/templates/plants/detail.html new file mode 100644 index 0000000..70e882e --- /dev/null +++ b/Planteer/plants/templates/plants/detail.html @@ -0,0 +1,164 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + +
+ + +
+ Home + + All Plants + + {{ plant.name }} +
+ + +
+ + +
+ {% if plant.image %} + + {% endif %} +
+ + +
+
+ +

{{ plant.name }}

+ +
+ {{ plant.get_category_display }} + {% if plant.is_edible %} + Edible + {% endif %} +
+ +
+ +
+
About
+

{{ plant.about }}

+
+ +
+
Used For
+

{{ plant.used_for }}

+
+ + + {% if plant.countries.all %} +
+
Native To
+
+ {% for country in plant.countries.all %} + + {% if country.flag %} + + {% endif %} + {{ country.name }} + + {% endfor %} +
+
+ {% endif %} + + + {% if request.user.is_staff %} + + Edit Plant + + {% endif %} + +
+
+ +
+ +
+ + +
+ + +
+

Comments ({{ comments|length }})

+ + {% for comment in comments %} +
+
+
{{ comment.name|first }}
+
+
{{ comment.name }}
+

{{ comment.created_at|date:"d M Y" }}

+
+
+

{{ comment.content }}

+
+ {% empty %} +

No comments yet. Be the first! 🌿

+ {% endfor %} +
+ + +
+
+
Add a Comment
+ + {% if request.user.is_authenticated %} +
+ {% csrf_token %} +
+ + +
+ +
+ + {% else %} +
+
+ +
+

+ You need to be logged in to add a comment +

+ Login + Register +
+ {% endif %} + +
+
+ +
+ +
+ + +{% if related_plants %} +
+
+

Related Plants

+
+ {% for related in related_plants %} +
+
+ {% if related.image %} + + {% endif %} +
+
{{ related.name }}
+

{{ related.about|truncatechars:60 }}

+ View Details → +
+
+
+ {% endfor %} +
+
+{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/Planteer/plants/templates/plants/new.html b/Planteer/plants/templates/plants/new.html new file mode 100644 index 0000000..b6ef73b --- /dev/null +++ b/Planteer/plants/templates/plants/new.html @@ -0,0 +1,111 @@ +{% extends 'Planteer/base.html' %} +{% load static %} +{% block content %} + +
+ + +
+ Home + + All Plants + + Add Plant +
+ + +
+ NEW PLANT +

Add New Plant

+

Fill in the details below to add a new plant

+
+ + +
+
+
+ {% csrf_token %} + + +
+ + + {% if form.name.errors %} +
{{ form.name.errors.0 }}
+ {% endif %} +
+ + +
+ + + {% if form.about.errors %} +
{{ form.about.errors.0 }}
+ {% endif %} +
+ + +
+ + + {% if form.used_for.errors %} +
{{ form.used_for.errors.0 }}
+ {% endif %} +
+ + +
+ + + {% if form.image.errors %} +
{{ form.image.errors.0 }}
+ {% endif %} +
+ + +
+
+ + + {% if form.category.errors %} +
{{ form.category.errors.0 }}
+ {% endif %} +
+ +
+
+ + +
+
+
+ + +
+ + Cancel +
+ +
+
+
+ +
+ +{% endblock %} \ No newline at end of file diff --git a/Planteer/plants/templates/plants/partials/plant_card.html b/Planteer/plants/templates/plants/partials/plant_card.html new file mode 100644 index 0000000..476d13a --- /dev/null +++ b/Planteer/plants/templates/plants/partials/plant_card.html @@ -0,0 +1,63 @@ +
+ + +
+ {{ plant.get_category_display }} + {% if plant.is_edible %} + Edible + {% endif %} +
+ + + {% with favs=request.COOKIES.favs|default:'' %} + + + + {% endwith %} + + + {% if plant.image %} + {{ plant.name }} + {% endif %} + + +
+
{{ plant.name }}
+

{{ plant.about|truncatechars:70 }}

+ + + {% if plant.water or plant.light or plant.countries.first %} +
+ {% if plant.water %} + {{ plant.get_water_display }} + {% endif %} + {% if plant.light %} + {{ plant.get_light_display }} + {% endif %} + {% if plant.countries.first %} + {{ plant.countries.first.name }} + {% endif %} +
+ {% endif %} + + + + +
+
\ No newline at end of file diff --git a/Planteer/plants/templates/plants/partials/plant_card_simple.html b/Planteer/plants/templates/plants/partials/plant_card_simple.html new file mode 100644 index 0000000..514f1e5 --- /dev/null +++ b/Planteer/plants/templates/plants/partials/plant_card_simple.html @@ -0,0 +1,10 @@ +
+ {% if plant.image %} + + {% endif %} +
+
{{ plant.name }}
+

{{ plant.about|truncatechars:50 }}

+ {{ plant.get_category_display }} +
+
\ No newline at end of file diff --git a/Planteer/plants/templates/plants/search.html b/Planteer/plants/templates/plants/search.html new file mode 100644 index 0000000..e69de29 diff --git a/Planteer/plants/templates/plants/update.html b/Planteer/plants/templates/plants/update.html new file mode 100644 index 0000000..433ae4e --- /dev/null +++ b/Planteer/plants/templates/plants/update.html @@ -0,0 +1,45 @@ +{% extends 'Planteer/base.html' %} +{% block content %} + +
+ +

Edit Plant

+ +
+ {% csrf_token %} + + + + + + + + + +
+ + +
+ +
+ + + +
+ + +
+ +
+ +{% 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..cac0c7f --- /dev/null +++ b/Planteer/plants/urls.py @@ -0,0 +1,24 @@ +from django.urls import path +from . import views + +app_name = "plants" + +urlpatterns = [ + path('all/', views.all_plants_view, name='all_plants'), + + path('/detail/', views.plant_detail_view, name='plant_detail'), + + path('new/', views.create_plant_view, name='create_plant'), + + path('/update/', views.update_plant_view, name='update_plant'), + + path('/delete/', views.delete_plant_view, name='delete_plant'), + + path('search/', views.search_view, name='search'), + + path('/country/', views.country_plants_view, name='country_plants'), + + path('/favourite/', views.toggle_favourite, name='toggle_favourite'), + + +] \ No newline at end of file diff --git a/Planteer/plants/views.py b/Planteer/plants/views.py new file mode 100644 index 0000000..c3cf56e --- /dev/null +++ b/Planteer/plants/views.py @@ -0,0 +1,190 @@ +from django.shortcuts import render, redirect, get_object_or_404 +from .models import Plant, Comment, Country +from .forms import PlantForm + + +def all_plants_view(request): + plants = Plant.objects.all() + + category = request.GET.get('category') + edible = request.GET.get('edible') + country = request.GET.get('country') + + if category: + plants = plants.filter(category=category) + + if edible == 'true': + plants = plants.filter(is_edible=True) + + if country: + plants = plants.filter(countries__id=country) + + countries = Country.objects.all() + + return render(request, 'plants/all.html', { + 'plants': plants, + 'categories': Plant.Category.choices, + 'selected_category': category, + 'selected_edible': edible, + 'selected_country': country, + 'countries': countries, + }) + + +def plant_detail_view(request, id): + plant = get_object_or_404(Plant, id=id) + comments = plant.comments.all().order_by('-created_at') + related_plants = Plant.objects.filter(category=plant.category).exclude(id=plant.id)[:3] + + if request.method == 'POST': + if request.user.is_authenticated: + Comment.objects.create( + plant=plant, + user=request.user, + name=request.user.username, + content=request.POST.get('content'), + ) + return redirect(f'/plants/{id}/detail/') + + return render(request, 'plants/detail.html', { + 'plant': plant, + 'comments': comments, + 'related_plants': related_plants, + }) + + +def create_plant_view(request): + if not request.user.is_staff: + return redirect('/') + + if request.method == 'POST': + name = request.POST.get('name') + about = request.POST.get('about') + used_for = request.POST.get('used_for') + image = request.FILES.get('image') + category = request.POST.get('category') + is_edible = request.POST.get('is_edible') == 'on' + + Plant.objects.create( + name=name, + about=about, + used_for=used_for, + image=image, + category=category, + is_edible=is_edible + ) + + return redirect('/plants/all/') + + return render(request, 'plants/new.html') + + +def update_plant_view(request, id): + if not request.user.is_staff: + return redirect('/') + + plant = get_object_or_404(Plant, id=id) + + if request.method == 'POST': + plant.name = request.POST.get('name') + plant.about = request.POST.get('about') + plant.used_for = request.POST.get('used_for') + + image = request.FILES.get('image') + if image: + plant.image = image + + category = request.POST.get('category') + if not category: + category = plant.category + + plant.category = category + plant.is_edible = request.POST.get('is_edible') == 'on' + plant.save() + return redirect('/plants/all/') + + return render(request, 'plants/update.html', {'plant': plant}) + + +def delete_plant_view(request, id): + if not request.user.is_staff: + return redirect('/') + + plant = get_object_or_404(Plant, id=id) + plant.delete() + return redirect('/plants/all/') + + +def search_view(request): + query = request.GET.get('q', '').strip() + category = request.GET.get('category') + edible = request.GET.get('edible') + + plants = Plant.objects.all() + + category_match = None + for value, label in Plant.Category.choices: + if query.lower() == label.lower(): + category_match = value + break + + if category_match: + plants = plants.filter(category=category_match) + category = category_match + elif query: + plants = plants.filter(name__icontains=query) + + if category and not category_match: + plants = plants.filter(category=category) + + if edible == 'true': + plants = plants.filter(is_edible=True) + + return render(request, 'plants/all.html', { + 'plants': plants, + 'categories': Plant.Category.choices, + 'selected_category': category, + 'selected_edible': edible + }) + + +def add_plant(request): + if not request.user.is_staff: + return redirect('/') + + if request.method == 'POST': + form = PlantForm(request.POST, request.FILES) + if form.is_valid(): + form.save() + return redirect('all_plants') + else: + form = PlantForm() + + return render(request, 'plants/new.html', {'form': form}) + + +def country_plants_view(request, id): + country = get_object_or_404(Country, id=id) + plants = Plant.objects.filter(countries__id=id) + + return render(request, 'plants/country_plants.html', { + 'country': country, + 'plants': plants, + }) + + +def toggle_favourite(request, id): + get_object_or_404(Plant, id=id) + redirect_url = request.GET.get('next', '/plants/all/') + response = redirect(redirect_url) + + favs = request.COOKIES.get('favs', '') + fav_list = [f for f in favs.split(',') if f] + + if str(id) in fav_list: + fav_list.remove(str(id)) + else: + fav_list.append(str(id)) + + response.set_cookie('favs', ','.join(fav_list), max_age=60*60*24*30) + return response \ No newline at end of file diff --git a/Planteer/static/Planteer/css/style.css b/Planteer/static/Planteer/css/style.css new file mode 100644 index 0000000..318d9dc --- /dev/null +++ b/Planteer/static/Planteer/css/style.css @@ -0,0 +1,1841 @@ +/* ================= DARK MODE ================= */ +.dark-mode { + background-color: #0f1f0f !important; + color: #e0e0e0; +} + +.dark-mode .navbar { + background-color: #0f1f0f !important; +} + +.dark-mode .nav-logo-icon { + background: #1e5c1e; +} + +.dark-mode .nav-link, +.dark-mode .nav-linkc { + color: #a0c0a0 !important; +} + +.dark-mode .brand-color { + color: #4a9a4a !important; +} + +.dark-mode .btn-add { + background: #1e5c1e; + color: white; +} + +.dark-mode .plant-card { + background-color: #162816 !important; + border-color: #1e3d1e !important; + box-shadow: 0 2px 12px rgba(0,0,0,0.3); +} + +.dark-mode .card-body, +.dark-mode .plant-info { + background-color: #162816 !important; + border-top-color: #1e3d1e !important; + color: #e0e0e0 !important; +} + +.dark-mode .card-body h5, +.dark-mode .plant-info h5 { + color: #ffffff !important; +} + +.dark-mode .card-body p, +.dark-mode .plant-info p { + color: #8ab08a !important; +} + +.dark-mode .plant-meta { + color: #6a8a6a !important; + border-bottom-color: #1e3d1e !important; +} + +.dark-mode .filter-box { + background-color: #162816 !important; + border: 1px solid #1e3d1e; +} + +.dark-mode .filter-item { + color: #c0d8c0 !important; +} + +.dark-mode .filter-item:hover { + background: #1e3d1e !important; +} + +.dark-mode .filter-item.active { + background: #2e5d34 !important; + color: white !important; +} + +.dark-mode .plant-form, +.dark-mode .contact-form-box, +.dark-mode .comment-form-box, +.dark-mode .delete-box { + background-color: #162816 !important; + border-color: #1e3d1e !important; + color: #e0e0e0 !important; +} + +.dark-mode .form-control { + background-color: #1a3a1a !important; + border-color: #2a502a !important; + color: #e0e0e0 !important; +} + +.dark-mode .form-control::placeholder { + color: #6a8a6a !important; +} + +.dark-mode .msg-card { + background-color: #162816 !important; + border-color: #1e3d1e !important; +} + +.dark-mode .msg-name { + color: #ffffff !important; +} + +.dark-mode .msg-body { + color: #8ab08a !important; + border-top-color: #1e3d1e !important; +} + +.dark-mode .detail-info-box { + background-color: #162816 !important; + border-color: #1e3d1e !important; +} + +.dark-mode .detail-name, +.dark-mode .page-title { + color: #ffffff !important; +} + +.dark-mode .detail-text, +.dark-mode .page-subtitle, +.dark-mode .showing-text { + color: #8ab08a !important; +} + +.dark-mode .why-section { + background-color: #0a150a !important; +} + +.dark-mode .why-card { + background-color: #162816 !important; + color: #e0e0e0 !important; +} + +.dark-mode .categories { + background-color: #0f1f0f !important; + background-image: radial-gradient(#1e3d1e 1px, transparent 1px) !important; +} + +.dark-mode .hero-search { + background: #162816 !important; + border-color: #2e5d34 !important; +} + +.dark-mode .hero-search input { + background: #162816 !important; + color: #e0e0e0 !important; +} + +.dark-mode .hero-title { + color: #4a9a4a !important; +} + +.dark-mode .footer-pro { + background-color: #0a150a !important; + border-top: 1px solid #1a3a1a; +} + +.dark-mode .footer-text, +.dark-mode .footer-links a, +.dark-mode .footer-bottom { + color: #8ab08a !important; +} + +.dark-mode hr { + border-color: #1e3d1e !important; +} + +.dark-mode .recent-section { + background: #0a150a; +} + +.dark-mode .recent-title { + color: #ffffff; +} + +.dark-mode .btn-view-all { + background: #162816; + color: #ffffff; + border-color: #1e3d1e; +} + +.dark-mode .btn-view-all:hover { + background: #1e3d1e; +} + +.dark-mode .quick-tips-section { + background: #0a150a; +} + +.dark-mode .care-guides-section { + background: #0f1f0f; +} + +.dark-mode .care-card { + background: #162816; + border-color: #1e3d1e; +} + +.dark-mode .section-title, +.dark-mode .care-card h5 { + color: #ffffff; +} + +.dark-mode .care-card p, +.dark-mode .care-list, +.dark-mode .quick-tip p { + color: #8ab08a; +} + +.dark-mode .articles-section { + background: #0a150a; +} + +.dark-mode .article-card { + background: #162816; + border-color: #1e3d1e; +} + +.dark-mode .article-body h5 { + color: #ffffff; +} + +.dark-mode .article-body p { + color: #8ab08a; +} + +.dark-mode .article-meta { + color: #6a8a6a; +} + +.dark-mode .how-section, +.dark-mode .quiz-section { + background: #0f1f0f; +} + +.dark-mode .quiz-q { + color: #ffffff; +} + +.dark-mode .quiz-option-box { + border-color: #1e3d1e; + background: #162816; + color: #e0e0e0; +} + +.dark-mode .quiz-option input:checked + .quiz-option-box { + border-color: #4a9a4a; + background: #1e3d1e; +} + +.dark-mode .navbar.scrolled { + border-bottom: 1px solid #2a502a; +} + +/* ================= GLOBAL ================= */ +body { + padding-top: 80px; + font-family: sans-serif; +} + +a { + text-decoration: none; +} + +/* ================= NAVBAR ================= */ +.navbar { + background-color: #0f1f0f !important; + border-bottom: 1px solid transparent; + transition: border-bottom 0.3s ease; + padding: 14px 0; +} + +.navbar.scrolled { + border-bottom: 1px solid #1e3d1e; +} + +.nav-logo-icon { + width: 36px; + height: 36px; + background: #2e5d34; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 16px; +} + +.brand-color { + color: #ffffff !important; + font-size: 20px; +} + +.nav-link { + color: #ffffff !important; + font-size: 15px; + font-weight: 500; + transition: 0.2s; + padding: 0 !important; + line-height: normal !important; + display: flex !important; + align-items: center !important; +} + +.nav-divider { + width: 1px; + height: 24px; + background: rgba(255,255,255,0.15); + margin: 0 4px; +} + +.nav-link:hover { + color: #4a9a4a !important; +} + +.active-link { + color: #4a9a4a !important; + font-weight: 600; +} + +.nav-linkc { + color: #ffffff; + font-weight: 500; + text-decoration: none; + font-size: 15px; +} + +.nav-linkc:hover { + color: #4a9a4a; +} + +.btn-dark-toggle { + width: 40px; + height: 40px; + border-radius: 10px; + background: #2a3a2a; + display: flex; + align-items: center; + justify-content: center; + color: #ffffff; + font-size: 16px; + text-decoration: none; + transition: 0.2s; +} + +.btn-dark-toggle:hover { + background: #3a4a3a; + color: white; +} + +.dark-mode .btn-dark-toggle { + color: #a0c0a0; +} + +.btn-add { + background: #2e5d34; + color: white; + border: none; + padding: 10px 20px; + border-radius: 10px; + font-weight: 600; + font-size: 14px; + text-decoration: none; + transition: 0.2s; + margin-left: 8px; +} + +.btn-add:hover { + background: #2e7d2e; + color: white; +} + +/* ================= HERO ================= */ +.hero { + position: relative; + height: 100vh; + background: url("https://res.cloudinary.com/dd2nfo3ua/image/upload/v1776882537/bg_planterr_x5onai.avif") center/cover no-repeat; + display: flex; + align-items: center; + color: white; + overflow: hidden; +} + +.hero-overlay { + position: absolute; + inset: 0; + background: linear-gradient( + 90deg, + rgba(0, 20, 10, 0.95) 0%, + rgba(0, 20, 10, 0.75) 40%, + rgba(0, 0, 0, 0.2) 100% + ); +} + +.hero-content { + position: relative; + z-index: 2; + max-width: 600px; +} + +.hero-badge { + background: rgba(155,226,140,0.15); + border: 1px solid rgba(155,226,140,0.3); + padding: 6px 14px; + border-radius: 20px; + font-size: 13px; + display: inline-flex; + align-items: center; + gap: 6px; + margin-bottom: 15px; +} + +.hero-title { + font-size: 70px; + font-weight: 800; + line-height: 1.1; +} + +.hero-title span { + color: #9be28c; +} + +.hero-desc { + margin-top: 15px; + opacity: 0.8; + max-width: 500px; +} + +.hero-search { + display: flex; + align-items: center; + margin-top: 25px; + background: rgba(255,255,255,0.08); + border: 1px solid rgba(155,226,140,0.2); + border-radius: 12px; + overflow: hidden; + max-width: 420px; +} + +.hero-search i { + padding: 0 12px; + color: #9be28c; +} + +.hero-search input { + flex: 1; + padding: 12px; + background: transparent; + border: none; + color: white; + outline: none; +} + +.hero-search button { + background: #9be28c; + border: none; + padding: 12px 20px; + color: black; + font-weight: bold; + cursor: pointer; +} + +.hero-tags { + margin-top: 15px; +} + +.hero-tags span { + display: inline-flex; + align-items: center; + gap: 6px; + margin: 8px 6px 0 0; + padding: 6px 14px; + border-radius: 20px; + background: rgba(255,255,255,0.1); + font-size: 13px; +} + +.hero-stats { + margin-top: 50px; + display: flex; + justify-content: space-between; + background: rgba(0, 30, 10, 0.5); + border: 1px solid rgba(155,226,140,0.15); + border-radius: 16px; + backdrop-filter: blur(15px); + overflow: hidden; + transform: translateY(30px); +} + +.stat-item { + flex: 1; + display: flex; + align-items: center; + gap: 15px; + padding: 20px 25px; + position: relative; +} + +.stat-item:not(:last-child) { + border-right: 1px solid rgba(255,255,255,0.08); +} + +.stat-icon { + width: 45px; + height: 45px; + background: rgba(155,226,140,0.15); + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; +} + +.stat-icon i { + color: #9be28c; + font-size: 18px; +} + +.stat-item h4 { + margin: 0; + font-size: 20px; + font-weight: bold; +} + +.stat-item small { + display: block; + opacity: 0.7; + font-size: 13px; +} + +/* ================= CATEGORIES ================= */ +.categories { + margin-top: 80px; + padding: 60px 0; + background-color: #f3efe8; + background-image: radial-gradient(#c8b99a 1px, transparent 1px); + background-size: 20px 20px; +} + +.cat-subtitle { + color: #9aa89a; + font-size: 12px; + letter-spacing: 2px; +} + +.cat-title { + font-size: 36px; + font-weight: 700; + color: #2e5d34; +} + +.cat-card { + display: block; + position: relative; + border-radius: 20px; + overflow: hidden; + cursor: pointer; + text-decoration: none; + color: inherit; +} + +.cat-card img { + width: 100%; + height: 150px; + object-fit: cover; + transition: 0.3s; +} + +.cat-card span { + position: absolute; + bottom: 10px; + left: 15px; + color: white; + font-weight: 600; + font-size: 14px; + z-index: 3; +} + +.cat-card::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(to top, rgba(0,0,0,0.5), transparent); +} + +.cat-card:hover img { + transform: scale(1.05); +} + +/* ================= PLANTS SECTION ================= */ +.plants-section { + margin-top: 80px; + margin-bottom: 80px; +} + +.plants-title { + font-weight: 700; + color: #2e5d34; +} + +.plants-subtitle { + color: #777; + font-size: 14px; +} + +.more-link { + text-decoration: none; + color: #2e5d34; + font-weight: 600; +} + +/* ================= PLANT CARD ================= */ +.plant-card { + background: white; + border-radius: 20px; + overflow: hidden; + position: relative; + transition: all 0.3s ease; + border: 1px solid #f0f0f0; + box-shadow: 0 2px 12px rgba(0,0,0,0.06); + height: 100%; + display: flex; + flex-direction: column; +} + +.plant-card:hover { + transform: translateY(-6px); + box-shadow: 0 16px 40px rgba(0,0,0,0.12); +} + +.plant-card img { + width: 100%; + height: 220px; + object-fit: cover; + transition: 0.4s; + flex-shrink: 0; +} + +.plant-card:hover img { + transform: scale(1.04); +} + +.plant-category { + font-size: 13px; + color: #2e5d34; + font-weight: 600; +} + +/* ================= PLANT INFO (Home Cards) ================= */ +.plant-info { + padding: 16px; + background: white; + border-top: 1px solid #f0f0f0; +} + +.plant-info h5 { + font-weight: 600; + margin-bottom: 5px; +} + +.plant-info p { + color: #777; + font-size: 13px; + margin-bottom: 6px; +} + +/* ================= RECENT SECTION ================= */ +.recent-section { + padding: 80px 0; + background: #ade7a243; +} + +.recent-title { + font-size: 36px; + font-weight: 700; + color: #1c1c1c; +} + +.btn-view-all { + background: white; + color: #1c1c1c; + border: 1px solid #e0e0e0; + padding: 10px 20px; + border-radius: 10px; + font-weight: 600; + text-decoration: none; + transition: 0.2s; +} + +.btn-view-all:hover { + background: #f0f0f0; + color: #1c1c1c; +} + +/* ================= BADGES ================= */ +.card-badges { + position: absolute; + top: 12px; + left: 12px; + display: flex; + gap: 6px; + z-index: 2; +} + +.badge-category { + background: rgba(46, 93, 52, 0.9); + color: white; + padding: 4px 10px; + border-radius: 20px; + font-size: 11px; + backdrop-filter: blur(4px); +} + +.badge-edible { + background: rgba(243, 156, 18, 0.9); + color: white; + padding: 4px 10px; + border-radius: 20px; + font-size: 11px; + backdrop-filter: blur(4px); +} + +/* ================= CARD BODY ================= */ +.card-body { + padding: 16px; + background: white; + border-top: 1px solid #f0f0f0; + flex: 1; + display: flex; + flex-direction: column; +} + +.card-body h5 { + font-weight: 700; + margin-bottom: 6px; + color: #1c1c1c; +} + +.card-body p { + color: #777; + font-size: 13px; + margin-bottom: 10px; + line-height: 1.6; +} + +/* ================= PLANT META ================= */ +.plant-meta { + display: flex; + flex-wrap: wrap; + gap: 8px; + font-size: 12px; + color: #888; + margin-bottom: 12px; + padding-bottom: 12px; + border-bottom: 1px solid #f5f5f5; +} + +.plant-meta i { + color: #2e5d34; + margin-right: 3px; +} + +/* ================= PLANT FOOTER ================= */ +.plant-footer { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: auto; +} + +.details-link { + color: #2e5d34; + font-weight: 500; + text-decoration: none; +} + +.details-link:hover::after { + margin-left: 5px; +} + +.card-actions a { + margin-left: 13px; +} + +.card-actions i { + color: #ccc; + cursor: pointer; + transition: 0.3s; +} + +.card-actions i:hover { + color: #2e5d34; + transform: scale(1.1); +} + +/* ================= CARE BADGE ================= */ +.care-badge { + position: absolute; + bottom: 12px; + right: 12px; + padding: 4px 12px; + border-radius: 20px; + font-size: 12px; + font-weight: 600; + z-index: 2; +} + +.care-easy { + background: rgba(255,255,255,0.85); + color: #2e7d32; +} + +.care-med { + background: rgba(255,255,255,0.85); + color: #e65100; +} + +.care-hard { + background: rgba(255,255,255,0.85); + color: #c62828; +} + +/* ================= FAV BUTTON ================= */ +.fav-btn { + position: absolute; + top: 12px; + right: 12px; + width: 36px; + height: 36px; + border-radius: 50%; + background: rgba(255,255,255,0.9); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + z-index: 3; + transition: 0.2s; + font-size: 15px; + color: #aaa; + text-decoration: none; +} + +.fav-btn:hover { + background: white; + color: #e74c3c; +} + +.fav-btn.fav-active { + color: #e74c3c; +} + +/* ================= FAVOURITES ================= */ +.fav-empty-icon { + font-size: 70px; + color: #e74c3c; + opacity: 0.4; +} + +/* ================= WHY SECTION ================= */ +.why-section { + margin-top: 80px; + padding: 60px 0; + background: #f3efe8; +} + +.why-subtitle { + font-size: 12px; + letter-spacing: 2px; + color: #9aa89a; +} + +.why-title { + font-size: 36px; + font-weight: 700; + color: #2e5d34; +} + +.why-card { + background: white; + padding: 30px 20px; + border-radius: 20px; + text-align: center; + transition: 0.3s; +} + +.why-card:hover { + transform: translateY(-5px); +} + +.icon-box { + width: 60px; + height: 60px; + background: #e6f4ea; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 15px; + font-size: 20px; + color: #2e5d34; +} + +/* ================= TESTIMONIALS ================= */ +.testimonials { + background: #234d20; + padding: 80px 0; + color: white; + margin-top: 80px; +} + +.test-subtitle { + font-size: 12px; + letter-spacing: 2px; + opacity: 0.7; +} + +.test-title { + font-size: 36px; + font-weight: 700; +} + +.test-card { + background: rgba(255,255,255,0.1); + padding: 30px; + border-radius: 20px; +} + +.test-text { + margin-bottom: 20px; + line-height: 1.6; +} + +#testCarousel { + position: relative; +} + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 50%; + transform: translateY(-50%); + width: auto; + height: auto; +} + +.carousel-control-prev { + left: -60px; +} + +.carousel-control-next { + right: -60px; +} + +.user { + display: flex; + align-items: center; + gap: 12px; +} + +.user-icon { + width: 45px; + height: 45px; + border-radius: 50%; + background: rgba(255,255,255,0.2); + color: #234d20; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; +} + +.user h6 { + margin: 0; + font-size: 15px; +} + +.user small { + color: #ccc; +} + +/* ================= BREADCRUMB & PAGE HEADER ================= */ +.breadcrumb-link { + color: #9aa89a; + font-size: 17px; + text-decoration: none; +} + +.breadcrumb-link:hover { + color: #2e5d34; +} + +.breadcrumb-green { + color: #2e5d34; + font-size: 17px; + text-decoration: none; +} + +.breadcrumb-green:hover { + color: #24492a; +} + +.breadcrumb-current { + color: #2e5d34; + font-weight: 500; +} + +.page-title { + font-size: 40px; + font-weight: 700; + color: #1c1c1c; + letter-spacing: -0.5px; +} + +.page-subtitle { + color: #8c8c8c; + font-size: 15px; + margin-top: 5px; +} + +/* ================= BUTTONS ================= */ +.btn-main { + background: #2e5d34; + color: white; + border: none; + border-radius: 12px; + padding: 12px 20px; + font-weight: 600; + transition: 0.2s; +} + +.btn-main:hover { + background: #24492a; + color: white; +} + +.btn-light { + background: #f3efe8; + border: 1px solid #e8ddd0; + border-radius: 12px; + padding: 12px; + text-align: center; + color: #5a4a38; + transition: 0.2s; +} + +.btn-light:hover { + background: #e8ddd0; + color: #3d2e1e; +} + +/* ================= FILTER BOX ================= */ +.filter-box { + background: #f6f7f6; + padding: 20px; + border-radius: 20px; +} + +.filter-title { + font-size: 12px; + color: #9aa89a; + letter-spacing: 1px; + margin-bottom: 10px; +} + +.filter-item { + display: block; + padding: 10px 12px; + border-radius: 10px; + color: #333; + margin-bottom: 6px; + text-decoration: none; +} + +.filter-item:hover { + background: #e6f4ea; + color: #2e5d34; +} + +.filter-item.active { + background: #2e5d34; + color: white; +} + +.clear-link { + font-size: 12px; + color: #e74c3c; +} + +.showing-text { + color: #666; + margin-bottom: 15px; +} + +.country-list { + max-height: 180px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: #2e5d34 #f0f0f0; +} + +/* ================= FORM ================= */ +.plant-form { + background: #fdf8f3; + border: 1px solid #e8ddd0; + padding: 40px; + border-radius: 24px; +} + +.plant-form label { + font-weight: 600; + margin-bottom: 6px; + display: block; + color: #2e5d34; +} + +.form-control { + border-radius: 14px; + padding: 12px; + border: 1px solid #e0d8cc; + background: #ffffff; + transition: 0.2s; +} + +.form-control:focus { + border-color: #2e5d34; + box-shadow: 0 0 0 2px rgba(46, 93, 52, 0.1); +} + +textarea.form-control { + resize: none; +} + +select.form-control { + height: 45px; +} + +.form-check-input { + width: 45px; + height: 22px; + cursor: pointer; +} + +.form-check-input:checked { + background-color: #2e5d34; + border-color: #2e5d34; +} + +.d-flex.gap-3 { + margin-top: 20px; +} + +/* ================= DETAIL PAGE ================= */ +.detail-img { + width: 100%; + border-radius: 20px; + object-fit: cover; + max-height: 420px; +} + +.detail-info-box { + background: #fdf8f3; + border: 1px solid #e8ddd0; + border-radius: 20px; + padding: 30px; + height: 100%; +} + +.detail-name { + font-size: 32px; + font-weight: 700; + color: #1c1c1c; + margin-bottom: 12px; +} + +.detail-label { + font-weight: 700; + color: #2e5d34; + margin-bottom: 6px; +} + +.detail-text { + color: #5a4a38; + font-size: 15px; + line-height: 1.7; +} + +.detail-section { + margin-bottom: 20px; +} + +.comment-form-box { + background: #fdf8f3; + border: 1px solid #e8ddd0; + border-radius: 20px; + padding: 24px; +} + +.comment-form-box label { + font-weight: 600; + color: #2e5d34; + margin-bottom: 6px; + display: block; +} + +.country-badge { + background: #e6f4ea; + color: #2e5d34; + padding: 6px 14px; + border-radius: 20px; + font-size: 13px; + font-weight: 600; + text-decoration: none; + transition: 0.2s; +} + +.country-badge:hover { + background: #2e5d34; + color: white; +} + +/* ================= CONTACT ================= */ +.contact-form-box { + background: #fdf8f3; + border: 1px solid #e8ddd0; + border-radius: 24px; + padding: 40px; +} + +.contact-form-box label { + font-weight: 600; + color: #2e5d34; + margin-bottom: 6px; + display: block; +} + +.alert-success-custom { + background: #e6f4ea; + color: #2e5d34; + border-radius: 12px; + padding: 14px; + font-weight: 600; +} + +/* ================= MESSAGES CARD ================= */ +.msg-card { + background-color: #fdf8f3; + border: 1px solid #e8ddd0; + border-radius: 16px; + padding: 24px; + transition: box-shadow 0.3s ease; +} + +.msg-card:hover { + box-shadow: 0 6px 20px rgba(180, 150, 110, 0.15); +} + +.msg-card-header { + display: flex; + align-items: center; + gap: 14px; + margin-bottom: 16px; +} + +.msg-avatar { + width: 46px; + height: 46px; + border-radius: 50%; + background-color: #e8ddd0; + color: #7a6248; + font-weight: 700; + font-size: 15px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.msg-name { + margin: 0; + font-weight: 600; + color: #3d2e1e; + font-size: 15px; +} + +.msg-email { + margin: 0; + font-size: 13px; + color: #a08060; +} + +.msg-body { + color: #5a4a38; + font-size: 14px; + line-height: 1.7; + margin-bottom: 14px; + border-top: 1px solid #e8ddd0; + padding-top: 14px; +} + +.msg-date { + font-size: 12px; + color: #b0967a; +} + +/* ================= DELETE BOX ================= */ +.delete-box { + background: #fdf8f3; + border: 1px solid #e8ddd0; + border-radius: 24px; + padding: 40px; +} + +.delete-icon { + font-size: 50px; +} + +/* ================= CARE TIPS ================= */ +.care-hero { + background: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), + url('https://images.unsplash.com/photo-1545241047-6083a3684587?w=1600') center/cover; + padding: 120px 0; + color: white; +} + +.care-hero-title { + font-size: 52px; + font-weight: 700; + color: white; +} + +.care-hero-desc { + font-size: 18px; + opacity: 0.9; + max-width: 600px; + margin: 0 auto; +} + +.care-tags span { + display: inline-block; + border: 1px solid rgba(255,255,255,0.5); + color: white; + padding: 8px 18px; + border-radius: 30px; + margin: 4px; + font-size: 14px; +} + +.quick-tips-section { + padding: 60px 0; + background: #f9f9f7; +} + +.quick-tip { + display: flex; + align-items: flex-start; + gap: 14px; +} + +.quick-tip-icon { + font-size: 22px; + margin-top: 2px; + flex-shrink: 0; +} + +.quick-tip p { + color: #555; + font-size: 14px; + line-height: 1.6; + margin: 0; +} + +.care-guides-section { + padding: 80px 0; +} + +.section-title { + font-size: 32px; + font-weight: 700; + color: #1c1c1c; +} + +.care-card { + background: white; + border: 1px solid #f0f0f0; + border-radius: 20px; + padding: 28px; + height: 100%; + transition: 0.3s; +} + +.care-card:hover { + box-shadow: 0 8px 24px rgba(0,0,0,0.08); + transform: translateY(-4px); +} + +.care-card-icon { + width: 52px; + height: 52px; + border-radius: 14px; + display: flex; + align-items: center; + justify-content: center; + font-size: 22px; + margin-bottom: 16px; +} + +.care-card h5 { + font-weight: 700; + margin-bottom: 8px; +} + +.care-card p { + color: #777; + font-size: 14px; + margin-bottom: 16px; +} + +.care-list { + padding-left: 16px; + color: #555; + font-size: 14px; + line-height: 2; +} + +/* ================= ARTICLES ================= */ +.articles-section { + padding: 80px 0; + background: #f9f9f7; +} + +.article-card { + background: white; + border-radius: 20px; + overflow: hidden; + border: 1px solid #f0f0f0; + transition: 0.3s; + height: 100%; +} + +.article-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 30px rgba(0,0,0,0.08); +} + +.article-img-wrap { + position: relative; +} + +.article-img-wrap img { + width: 100%; + height: 200px; + object-fit: cover; +} + +.article-tag { + position: absolute; + top: 12px; + left: 12px; + padding: 5px 12px; + border-radius: 20px; + font-size: 12px; + font-weight: 600; +} + +.article-body { + padding: 20px; +} + +.article-body h5 { + font-weight: 700; + color: #1c1c1c; + margin-bottom: 8px; + line-height: 1.4; +} + +.article-body p { + color: #777; + font-size: 14px; + margin-bottom: 16px; +} + +.article-meta { + display: flex; + gap: 14px; + font-size: 12px; + color: #aaa; +} + +.article-card-link { + text-decoration: none; + color: inherit; + display: block; +} + +.article-card-link:hover .article-card { + transform: translateY(-5px); + box-shadow: 0 10px 30px rgba(0,0,0,0.08); +} + +.article-hero { + height: 500px; + background-size: cover; + background-position: center; + position: relative; +} + +.article-hero-overlay { + position: absolute; + inset: 0; + background: linear-gradient(to bottom, rgba(0,0,0,0.2), rgba(0,0,0,0.75)); + display: flex; + align-items: flex-end; + padding-bottom: 60px; + color: white; +} + +.article-hero-title { + font-size: 42px; + font-weight: 700; + color: white; + max-width: 800px; + line-height: 1.3; +} + +.article-hero-meta { + display: flex; + gap: 20px; + font-size: 14px; + opacity: 0.9; + margin-top: 12px; +} + +.article-heading { + font-size: 22px; + font-weight: 700; + color: #2e5d34; + margin-bottom: 10px; + margin-top: 10px; +} + +.article-text { + color: #444; + font-size: 16px; + line-height: 1.9; +} + +.article-section { + border-bottom: 1px solid #f0f0f0; + padding-bottom: 28px; + margin-bottom: 28px; +} + +.article-section:last-child { + border-bottom: none; +} + +/* ================= FIND MY PLANT ================= */ +.find-hero { + background: linear-gradient(rgba(0,0,0,0.45), rgba(0,0,0,0.45)), + url('https://res.cloudinary.com/dd2nfo3ua/image/upload/v1776895422/quiz_k7ieac.webp') center/cover; + padding: 80px 0; + color: white; +} + +.find-hero-title { + font-size: 52px; + font-weight: 700; + color: white; +} + +.find-hero-desc { + font-size: 18px; + opacity: 0.9; + max-width: 500px; +} + +.how-section { + padding: 60px 0; + background: #f9f9f7; +} + +.how-card { + padding: 20px; +} + +.how-icon { + width: 60px; + height: 60px; + border-radius: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 22px; + margin: 0 auto 14px; +} + +.how-card h6 { + font-weight: 700; + margin-bottom: 6px; +} + +.how-card p { + color: #777; + font-size: 14px; +} + +.quiz-section { + padding: 80px 0; +} + +.quiz-q { + font-weight: 700; + margin-bottom: 20px; + color: #1c1c1c; +} + +.quiz-options { + display: flex; + gap: 16px; + flex-wrap: wrap; +} + +.quiz-option { + flex: 1; + min-width: 140px; + cursor: pointer; +} + +.quiz-option input { + display: none; +} + +.quiz-option-box { + border: 2px solid #e0e0e0; + border-radius: 16px; + padding: 20px; + text-align: center; + transition: 0.2s; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.quiz-option-box i { + font-size: 24px; +} + +.quiz-option-box span { + font-weight: 600; + font-size: 15px; +} + +.quiz-option-box small { + color: #888; + font-size: 12px; +} + +.quiz-option input:checked + .quiz-option-box { + border-color: #2e5d34; + background: #f0f9f0; +} + +.quiz-option-box:hover { + border-color: #2e5d34; +} + +.no-results-icon { + font-size: 60px; + color: #2e5d34; +} + +/* ================= FOOTER ================= */ +.footer-pro { + background: #f3efe8; + padding: 50px 0; + margin-top: 80px; +} + +.footer-logo { + color: #2e5d34; + font-weight: bold; +} + +.footer-title { + color: #2e5d34; + font-weight: bold; + margin-bottom: 15px; +} + +.footer-text { + color: #6c757d; + font-size: 14px; +} + +.footer-links { + list-style: none; + padding: 0; +} + +.footer-links li { + margin-bottom: 8px; +} + +.footer-links a { + text-decoration: none; + color: #6c757d; +} + +.footer-links a:hover { + color: #198754; +} + +.social-icons a { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: #e6e2da; + border-radius: 50%; + margin-right: 10px; + color: #2e5d34; + text-decoration: none; + font-size: 16px; + transition: 0.3s; +} + +.social-icons a:hover { + background: #2e5d34; + color: white; +} + +.subscribe-box { + display: flex; + margin-top: 10px; +} + +.subscribe-box input { + flex: 1; + padding: 8px; + border-radius: 8px; + border: 1px solid #ddd; +} + +.subscribe-box button { + margin-left: 8px; + background: #2e5d34; + color: white; + border: none; + padding: 8px 15px; + border-radius: 8px; +} + +.footer-bottom { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 20px; + font-size: 14px; + color: #6c757d; +} + +.footer-bottom a { + margin-left: 15px; + text-decoration: none; + color: #6c757d; +} + +/* ================= AUTH ================= */ +.auth-box { + background: #fdf8f3; + border: 1px solid #e8ddd0; + border-radius: 24px; + padding: 40px; +} + +.auth-icon { + width: 60px; + height: 60px; + background: #e6f4ea; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto; + font-size: 24px; + color: #2e5d34; +} + +.auth-title { + font-size: 26px; + font-weight: 700; + color: #1c1c1c; +} + +.auth-subtitle { + color: #888; + font-size: 14px; +} + +.auth-label { + font-weight: 600; + color: #2e5d34; + margin-bottom: 6px; + display: block; +} + +.auth-link { + color: #2e5d34; + font-weight: 600; +} + +/* DARK MODE */ +.dark-mode .auth-box { + background: #162816; + border-color: #1e3d1e; +} + +.dark-mode .auth-title { + color: #ffffff; +} + +.dark-mode .auth-subtitle { + color: #8ab08a; +} + +/* ================= RESPONSIVE ================= */ +@media (max-width: 768px) { + .plant-form, + .contact-form-box { + padding: 20px; + } + + .hero-title { + font-size: 40px; + } + + .find-hero-title, + .care-hero-title { + font-size: 36px; + } +} \ No newline at end of file diff --git a/Planteer/templates/Planteer/base.html b/Planteer/templates/Planteer/base.html new file mode 100644 index 0000000..b5d5eea --- /dev/null +++ b/Planteer/templates/Planteer/base.html @@ -0,0 +1,179 @@ +{% load static %} + + + + Planteer + + + + + + + + + + +
+ {% block content %} + {% endblock %} +
+ +
+
+ +
+ +
+ + + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ +
+ +
+ + + +
+
+ + + + + + \ No newline at end of file