Mohsin Khan commited on
Commit
5a58b2b
·
0 Parent(s):

Initial commit for Hugging Face Docker Spaces

Browse files
.dockerignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .venv
2
+ __pycache__
3
+ .git
4
+ .gitignore
5
+ .gitattributes
6
+ Dockerfile
7
+ .dockerignore
8
+ README.md
9
+ *.pkl
10
+ *.pyc
11
+ *.pyo
12
+ *.pyd
13
+ .db
14
+ .DS_Store
.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ app/models/heart_disease_model.pkl filter=lfs diff=lfs merge=lfs -text
2
+ *.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ *.egg
6
+ *.egg-info
7
+ dist/
8
+ build/
9
+ eggs/
10
+ .eggs/
11
+ lib/
12
+ lib64/
13
+ parts/
14
+ sdist/
15
+ var/
16
+ wheels/
17
+ pip-wheel-metadata/
18
+ share/python-wheels/
19
+ *.manifest
20
+ *.spec
21
+
22
+ # Virtual Environments
23
+ .venv/
24
+ venv/
25
+ ENV/
26
+ env/
27
+ .virtualenv/
28
+
29
+ # PyCharm
30
+ .idea/
31
+ *.iml
32
+ .idea/**/workspace.xml
33
+ .idea/**/tasks.xml
34
+ .idea/**/dictionaries
35
+ .idea/**/shelf
36
+
37
+ # VS Code
38
+ .vscode/
39
+ *.code-workspace
40
+ .history/
41
+
42
+ # Jupyter Notebook
43
+ .ipynb_checkpoints
44
+ *.ipynb
45
+
46
+ # Flask
47
+ instance/
48
+ .webassets-cache
49
+ .env
50
+ .flaskenv
51
+
52
+ # Testing
53
+ .pytest_cache/
54
+ .coverage
55
+ htmlcov/
56
+ .tox/
57
+ .nox/
58
+
59
+ # macOS
60
+ .DS_Store
61
+ .AppleDouble
62
+ .LSOverride
63
+
64
+ # Windows
65
+ Thumbs.db
66
+ ehthumbs.db
67
+ Desktop.ini
68
+ $RECYCLE.BIN/
69
+ *.cab
70
+ *.msi
71
+ *.msix
72
+ *.msm
73
+ *.msp
74
+ *.lnk
75
+
76
+ # Linux
77
+ *~
78
+
79
+ # Logs
80
+ *.log
81
+ logs/
82
+ *.log.*
83
+
84
+ # Database
85
+ *.db
86
+ *.sqlite
87
+ *.sqlite3
88
+
89
+ # Environment variables
90
+ .env
91
+ .env.local
92
+ .env.*.local
93
+
94
+ # IDEs
95
+ *.swp
96
+ *.swo
97
+ *.swn
98
+ .project
99
+ .classpath
100
+ .settings/
101
+
102
+ # Backup files
103
+ *.bak
104
+ *.tmp
105
+ *.temp
106
+
107
+ # Cache
108
+ __pycache__/
109
+ *.pyc
110
+ .cache/
111
+
112
+ # Model files (uncomment if you don't want to commit large model files)
113
+ *.pkl
114
+ *.h5
115
+ *.pb
116
+ *.pth
117
+ *.onnx
Dockerfile ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.10-slim
3
+
4
+ # Set environment variables
5
+ ENV PYTHONDONTWRITEBYTECODE=1
6
+ ENV PYTHONUNBUFFERED=1
7
+ ENV MODEL_PATH=app/models/heart_disease_model.pkl
8
+
9
+ # Set work directory
10
+ WORKDIR /code
11
+
12
+ # Install system dependencies
13
+ RUN apt-get update && apt-get install -y --no-install-recommends \
14
+ build-essential \
15
+ libgomp1 \
16
+ && rm -rf /var/lib/apt/lists/*
17
+
18
+ # Install Python dependencies
19
+ COPY requirements.txt .
20
+ RUN pip install --no-cache-dir -r requirements.txt
21
+
22
+ # Copy project files
23
+ COPY . .
24
+
25
+ # Create a non-root user and switch to it for Hugging Face Spaces security
26
+ RUN useradd -m -u 1000 user
27
+ USER user
28
+ ENV HOME=/home/user \
29
+ PATH=/home/user/.local/bin:$PATH
30
+
31
+ # Expose the port FastAPI will run on (Hugging Face default)
32
+ EXPOSE 7860
33
+
34
+ # Command to run the application using uvicorn
35
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: HeartCare Risk Prediction System
3
+ emoji: 🌡️
4
+ colorFrom: red
5
+ colorTo: pink
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ ---
10
+
11
+ # HeartCare Risk Prediction System
12
+
13
+ ![Python](https://img.shields.io/badge/Python-3.9+-3776AB?style=for-the-badge&logo=python&logoColor=white)
14
+ ![FastAPI](https://img.shields.io/badge/FastAPI-0.100.0+-009688?style=for-the-badge&logo=fastapi&logoColor=white)
15
+ ![Scikit-Learn](https://img.shields.io/badge/Scikit--Learn-1.3+-F7931E?style=for-the-badge&logo=scikitlearn&logoColor=white)
16
+ ![License](https://img.shields.io/badge/License-MIT-green?style=for-the-badge)
17
+
18
+ An end-to-end machine learning–based web application for predicting cardiovascular disease risk using lifestyle and health attributes. This system provides a production-ready API and a modern web interface to help users assess their heart health through a series of clinically relevant questions.
19
+
20
+ ---
21
+
22
+ ## 📋 Table of Contents
23
+ - [Project Overview](#-project-overview)
24
+ - [Key Features](#-key-features)
25
+ - [Technology Stack](#-technology-stack)
26
+ - [Installation Instructions](#-installation-instructions)
27
+ - [Usage Guide](#-usage-guide)
28
+ - [Project Structure](#-project-structure)
29
+ - [Contribution Guidelines](#-contribution-guidelines)
30
+ - [License](#-license)
31
+
32
+ ---
33
+
34
+ ## 🌟 Project Overview
35
+
36
+ The **HeartCare Risk Prediction System** is designed to provide quick and accessible heart disease risk assessments. By processing variables such as age, lifestyle habits (smoking, alcohol, diet), and medical history (diabetes, checkups), the underlying machine learning model predicts the probability of heart-related complications.
37
+
38
+ ### Key Capabilities
39
+ - **Real-time Prediction**: Immediate risk assessment using a trained Random Forest model.
40
+ - **Data Validation**: Strict input checking via Pydantic schemas to ensure diagnostic accuracy.
41
+ - **User-Friendly UI**: Modern, glassmorphism-inspired design for a premium user experience.
42
+ - **RESTful API**: Fully documented API endpoints for integration with other healthcare systems.
43
+
44
+ ---
45
+
46
+ ## 🛠 Technology Stack
47
+
48
+ | Component | Technology |
49
+ | :--- | :--- |
50
+ | **Backend Framework** | [FastAPI](https://fastapi.tiangolo.com/) |
51
+ | **Machine Learning** | [Scikit-learn](https://scikit-learn.org/) (Random Forest) |
52
+ | **Data Validation** | [Pydantic](https://docs.pydantic.dev/) |
53
+ | **Frontend** | HTML5, Vanilla CSS, JavaScript |
54
+ | **Server** | Uvicorn (ASGI) |
55
+ | **Large File Storage** | [Git LFS](https://git-lfs.com/) |
56
+
57
+ ---
58
+
59
+ ## 📥 Installation Instructions
60
+
61
+ ### System Requirements
62
+ - **OS**: Windows 10/11, macOS, or Linux
63
+ - **Python**: 3.9 or higher
64
+ - **Disk Space**: ~500MB (including the ML model)
65
+ - **RAM**: 2GB minimum
66
+
67
+ ### Step-by-Step Setup
68
+
69
+ 1. **Clone the Repository** (Ensure Git LFS is installed):
70
+ ```bash
71
+ git clone https://github.com/mohsinkp02/HeartCare-Risk-Prediction-System.git
72
+ cd HeartCare-Risk-Prediction-System
73
+ ```
74
+
75
+ 2. **Initialize Git LFS and Pull Model**:
76
+ ```bash
77
+ git lfs install
78
+ git lfs pull
79
+ ```
80
+
81
+ 3. **Create a Virtual Environment**:
82
+ ```bash
83
+ # Windows
84
+ python -m venv .venv
85
+ .venv\Scripts\activate
86
+
87
+ # macOS/Linux
88
+ python3 -m venv .venv
89
+ source .venv/bin/activate
90
+ ```
91
+
92
+ 4. **Install Dependencies**:
93
+ ```bash
94
+ pip install -r requirements.txt
95
+ ```
96
+
97
+ ---
98
+
99
+ ## 🚀 Usage Guide
100
+
101
+ ### Running the Application
102
+
103
+ To launch the web server and application, execute the runner script:
104
+
105
+ ```bash
106
+ python run_app.py
107
+ ```
108
+
109
+ - **Website**: Access at [http://localhost:8000](http://localhost:8000)
110
+ - **API Documentation**: Interactive Swagger docs at [http://localhost:8000/docs](http://localhost:8000/docs)
111
+
112
+ ### Configuration Options
113
+
114
+ | Environment Variable | Default | Description |
115
+ | :--- | :--- | :--- |
116
+ | `APP_NAME` | HeartCare | The display name of the application |
117
+ | `API_V1_STR` | /api/v1 | The prefix for all API endpoints |
118
+ | `HOST` | 0.0.0.0 | Network interface to bind to |
119
+ | `PORT` | 8000 | Port number for the web server |
120
+
121
+ ---
122
+
123
+ ## 📂 Project Structure
124
+
125
+ ```mermaid
126
+ graph TD
127
+ Root[HeartCare-Risk-Prediction-System/] --> App[app/]
128
+ Root --> Config[.gitattributes, .gitignore, README.md, requirements.txt]
129
+ Root --> Runner[run_app.py]
130
+
131
+ App --> API[api/ - Routes & Endpoints]
132
+ App --> Core[core/ - App Config]
133
+ App --> Models[models/ - Trained .pkl Model]
134
+ App --> Schemas[schemas/ - Pydantic Validation]
135
+ App --> Services[services/ - Business Logic]
136
+ App --> Static[static/ - CSS, JS, Images]
137
+ App --> Templates[templates/ - HTML Files]
138
+ App --> Main[main.py - Entry Point]
139
+ ```
140
+
141
+ ---
142
+
143
+ ## 🤝 Contribution Guidelines
144
+
145
+ We welcome contributions to HeartCare! To contribute, follow these steps:
146
+
147
+ ### Reporting Issues
148
+ - Use the **GitHub Issues** tab to report bugs or request features.
149
+ - Provide a clear description and steps to reproduce the issue.
150
+
151
+ ### Pull Request Process
152
+ 1. Fork the repository.
153
+ 2. Create a new branch (`git checkout -b feature/AmazingFeature`).
154
+ 3. Commit your changes following our **Coding Standards** (PEP 8 for Python).
155
+ 4. Push to the branch.
156
+ 5. Open a Pull Request for review.
157
+
158
+ ### Coding Standards
159
+ - Follow [PEP 8](https://peps.python.org/pep-0008/) for style.
160
+ - Include docstrings for all new functions and classes.
161
+ - Ensure all tests pass before submitting.
162
+
163
+ ---
164
+
165
+ ## ⚖️ License
166
+
167
+ Copyright © 2024 Mohsin Khan.
168
+
169
+ Distributed under the **MIT License**. See `LICENSE` for more information.
170
+
171
+ ---
172
+
173
+ > [!NOTE]
174
+ > This system is intended for informational purposes and should not replace professional medical advice.
app/api/routes.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, Depends
2
+ from app.schemas.prediction import PredictionInput, PredictionOutput
3
+ from app.services.preprocessing import PreprocessingService
4
+ from app.services.model_service import ModelService
5
+ import logging
6
+
7
+ router = APIRouter()
8
+ logger = logging.getLogger(__name__)
9
+
10
+ @router.post("/predict", response_model=PredictionOutput)
11
+ async def predict(input_data: PredictionInput):
12
+ """
13
+ Predict heart disease risk based on patient data.
14
+ """
15
+ try:
16
+ # 1. Convert Pydantic model to dict (using alias for keys to match model features)
17
+ data_dict = input_data.model_dump(by_alias=True)
18
+
19
+ # 2. Preprocess
20
+ input_vector = PreprocessingService.process_input(data_dict)
21
+
22
+ # 3. Predict
23
+ result = ModelService.predict(input_vector)
24
+
25
+ return result
26
+ except Exception as e:
27
+ logger.error(f"Prediction error: {str(e)}")
28
+ raise HTTPException(status_code=500, detail=f"Internal Server Error: {str(e)}")
29
+
30
+ @router.get("/health")
31
+ async def health_check():
32
+ return {"status": "healthy", "service": "heart-disease-prediction"}
app/core/config.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pydantic_settings import BaseSettings
3
+ from functools import lru_cache
4
+
5
+ class Settings(BaseSettings):
6
+ APP_NAME: str = "Heart Disease Prediction API"
7
+ VERSION: str = "1.0.0"
8
+ API_V1_STR: str = "/api/v1"
9
+
10
+ # Model Configuration
11
+ MODEL_PATH: str = os.getenv("MODEL_PATH", "app/models/heart_disease_model.pkl")
12
+
13
+ # Logging
14
+ LOG_LEVEL: str = "INFO"
15
+
16
+ class Config:
17
+ env_file = ".env"
18
+
19
+ @lru_cache()
20
+ def get_settings():
21
+ return Settings()
app/main.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request
2
+ from fastapi.staticfiles import StaticFiles
3
+ from fastapi.templating import Jinja2Templates
4
+ from fastapi.responses import HTMLResponse
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ from prometheus_fastapi_instrumentator import Instrumentator
7
+ from app.api.routes import router as api_router
8
+ from app.core.config import get_settings
9
+ from app.services.model_service import ModelService
10
+ from contextlib import asynccontextmanager
11
+ import os
12
+
13
+ settings = get_settings()
14
+
15
+ @asynccontextmanager
16
+ async def lifespan(app: FastAPI):
17
+ # Load model on startup
18
+ ModelService.load_model()
19
+ yield
20
+ # Clean up resources if needed
21
+
22
+ app = FastAPI(
23
+ title=settings.APP_NAME,
24
+ version=settings.VERSION,
25
+ lifespan=lifespan,
26
+ docs_url="/docs",
27
+ redoc_url="/redoc"
28
+ )
29
+
30
+ # CORS
31
+ app.add_middleware(
32
+ CORSMiddleware,
33
+ allow_origins=["*"], # In production, restrict this to specific domains
34
+ allow_credentials=True,
35
+ allow_methods=["*"],
36
+ allow_headers=["*"],
37
+ )
38
+
39
+ # Get absolute path to app directory
40
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
41
+ STATIC_DIR = os.path.join(BASE_DIR, "static")
42
+ TEMPLATES_DIR = os.path.join(BASE_DIR, "templates")
43
+
44
+ # Mount Static Files
45
+ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
46
+
47
+ # Templates
48
+ if os.path.exists(TEMPLATES_DIR):
49
+ templates = Jinja2Templates(directory=TEMPLATES_DIR)
50
+ else:
51
+ print(f"Warning: Templates directory not found at {TEMPLATES_DIR}")
52
+ # Fallback to avoid crash on import, but routes will fail
53
+ templates = Jinja2Templates(directory=".")
54
+
55
+ # Frontend Routes
56
+ @app.get("/", response_class=HTMLResponse)
57
+ async def read_root(request: Request):
58
+ return templates.TemplateResponse("index.html", {"request": request})
59
+
60
+ @app.get("/calculate", response_class=HTMLResponse)
61
+ async def read_calculate(request: Request):
62
+ return templates.TemplateResponse("calculate.html", {"request": request})
63
+
64
+ @app.get("/recommendation/{risk_level}", response_class=HTMLResponse)
65
+ async def read_recommendation(request: Request, risk_level: int):
66
+ return templates.TemplateResponse("recommendation.html", {"request": request, "risk_level": risk_level})
67
+
68
+ # API Routes
69
+ app.include_router(api_router, prefix=settings.API_V1_STR)
70
+
71
+ # Prometheus Metrics
72
+ Instrumentator().instrument(app).expose(app)
73
+
74
+ if __name__ == "__main__":
75
+ import uvicorn
76
+ uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
app/models/heart_disease_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:706b4f8b470fee5ab80108e4e42381d9bedcd2b88a918722430673f5805defeb
3
+ size 375819353
app/schemas/prediction.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import Literal
3
+
4
+ class PredictionInput(BaseModel):
5
+ general_health: Literal['Excellent', 'Very_Good', 'Good', 'Fair', 'Poor'] = Field(..., alias="General_Health")
6
+ checkup: Literal['Within 1 year', '1-2 years', '2-5 years', '5+ years', 'Never'] = Field(..., alias="Checkup")
7
+ exercise: Literal['0', '1'] = Field(..., alias="Exercise")
8
+ skin_cancer: Literal['0', '1'] = Field(..., alias="Skin_Cancer")
9
+ other_cancer: Literal['0', '1'] = Field(..., alias="Other_Cancer")
10
+ depression: Literal['0', '1'] = Field(..., alias="Depression")
11
+ diabetes: Literal['No', 'Borderline', 'During Pregnancy', 'Yes'] = Field(..., alias="Diabetes")
12
+ arthritis: Literal['0', '1'] = Field(..., alias="Arthritis")
13
+ sex: Literal['0', '1'] = Field(..., alias="Sex")
14
+ age: float = Field(..., ge=18, le=100, alias="Age")
15
+ height: float = Field(..., ge=50, le=300, alias="Height")
16
+ weight: float = Field(..., ge=10, le=500, alias="Weight")
17
+ bmi: float = Field(..., ge=5, le=100, alias="BMI")
18
+ smoking: Literal['0', '1'] = Field(..., alias="Smoking")
19
+ alcohol: float = Field(..., ge=0, le=30, alias="Alcohol") # Assuming days/month? Logic in original app was weird (0-30)
20
+ fruit: float = Field(..., ge=0, le=100, alias="Fruit")
21
+ green_vegetables: float = Field(..., ge=0, le=100, alias="Green_Vegetables")
22
+ fried_potato: float = Field(..., ge=0, le=100, alias="Fried_Potato")
23
+
24
+ class Config:
25
+ populate_by_name = True
26
+ json_schema_extra = {
27
+ "example": {
28
+ "General_Health": "Good",
29
+ "Checkup": "Within 1 year",
30
+ "Exercise": "1",
31
+ "Skin_Cancer": "0",
32
+ "Other_Cancer": "0",
33
+ "Depression": "0",
34
+ "Diabetes": "No",
35
+ "Arthritis": "0",
36
+ "Sex": "0",
37
+ "Age": 30,
38
+ "Height": 175,
39
+ "Weight": 70,
40
+ "BMI": 22.5,
41
+ "Smoking": "0",
42
+ "Alcohol": 0,
43
+ "Fruit": 10,
44
+ "Green_Vegetables": 10,
45
+ "Fried_Potato": 0
46
+ }
47
+ }
48
+
49
+ class PredictionOutput(BaseModel):
50
+ probability: float
51
+ class_label: int = Field(..., alias="class")
52
+ risk_level: int
53
+ risk_label: str
54
+
55
+ class Config:
56
+ populate_by_name = True
app/services/model_service.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import joblib
2
+ import os
3
+ import logging
4
+ import numpy as np
5
+ from app.core.config import get_settings
6
+
7
+ logger = logging.getLogger(__name__)
8
+ settings = get_settings()
9
+
10
+ class ModelService:
11
+ _model = None
12
+
13
+ @classmethod
14
+ def load_model(cls):
15
+ if cls._model is not None:
16
+ return
17
+
18
+ try:
19
+ if os.path.exists(settings.MODEL_PATH):
20
+ logger.info(f"Loading model from {settings.MODEL_PATH}...")
21
+ cls._model = joblib.load(settings.MODEL_PATH)
22
+ logger.info("Model loaded successfully!")
23
+ else:
24
+ logger.error(f"Model file {settings.MODEL_PATH} not found! Using Mock Model for demonstration.")
25
+ cls._model = MockModel()
26
+ except Exception as e:
27
+ logger.error(f"Failed to load model: {e}")
28
+ cls._model = MockModel()
29
+
30
+ @classmethod
31
+ def predict(cls, input_vector: list) -> dict:
32
+ if cls._model is None:
33
+ cls.load_model()
34
+
35
+ input_array = [input_vector]
36
+
37
+ # Predict
38
+ try:
39
+ prediction_prob = cls._model.predict_proba(input_array)[0][1]
40
+ prediction_class = int(cls._model.predict(input_array)[0])
41
+ except AttributeError:
42
+ # Fallback for mock model or weird scikit versions
43
+ prediction_prob = 0.5
44
+ prediction_class = 0
45
+
46
+ risk_level, risk_label = cls._calculate_risk_level(prediction_prob)
47
+
48
+ return {
49
+ "probability": float(prediction_prob),
50
+ "class": prediction_class,
51
+ "risk_level": risk_level,
52
+ "risk_label": risk_label
53
+ }
54
+
55
+ @staticmethod
56
+ def _calculate_risk_level(prob: float):
57
+ if prob <= 0.20:
58
+ return 1, "Very Low"
59
+ elif prob <= 0.40:
60
+ return 2, "Low"
61
+ elif prob <= 0.60:
62
+ return 3, "Moderate"
63
+ elif prob <= 0.80:
64
+ return 4, "High"
65
+ else:
66
+ return 5, "Very High"
67
+
68
+ class MockModel:
69
+ """Mock model for when the real model is missing or fails to load."""
70
+ def predict_proba(self, X):
71
+ return np.array([[0.5, 0.5]]) # 50% chance
72
+
73
+ def predict(self, X):
74
+ return np.array([0])
app/services/preprocessing.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict, List
2
+ import logging
3
+
4
+ logger = logging.getLogger(__name__)
5
+
6
+ # Feature Configuration (18 Features) - EXACT ORDER
7
+ MODEL_FEATURES = [
8
+ 'General_Health', 'Checkup', 'Exercise', 'Skin_Cancer', 'Other_Cancer',
9
+ 'Depression', 'Diabetes', 'Arthritis', 'Sex', 'Age', 'Height', 'Weight',
10
+ 'BMI', 'Smoking', 'Alcohol', 'Fruit', 'Green_Vegetables', 'Fried_Potato'
11
+ ]
12
+
13
+ SCALERS = {
14
+ 'Weight': {'min': 29.94, 'max': 136.0},
15
+ 'Height': {'min': 142.0, 'max': 200.0},
16
+ 'BMI': {'min': 12.87, 'max': 43.28},
17
+ 'Age': {'min': 21, 'max': 82},
18
+ 'Fruit': {'min': 0, 'max': 56.0},
19
+ 'Green_Vegetables': {'min': 0, 'max': 44.0},
20
+ 'Fried_Potato': {'min': 0, 'max': 17.0},
21
+ 'Alcohol': {'min': 0, 'max': 15.0},
22
+ 'General_Health': {
23
+ 'Excellent': 1.0, 'Very_Good': 0.75, 'Good': 0.5, 'Fair': 0.25, 'Poor': 0.0
24
+ },
25
+ 'Checkup': {
26
+ 'Within 1 year': 1.0, '1-2 years': 0.75, '2-5 years': 0.5, '5+ years': 0.25, 'Never': 0.0
27
+ },
28
+ 'Diabetes': {
29
+ 'No': 0.0, 'Borderline': 0.33, 'During Pregnancy': 0.66, 'Yes': 1.0
30
+ },
31
+ 'Sex': {'Female': 0.0, 'Male': 1.0, '0': 0.0, '1': 1.0},
32
+ 'Exercise': {'0': 0.0, '1': 1.0},
33
+ 'Smoking': {'0': 0.0, '1': 1.0},
34
+ 'Skin_Cancer': {'0': 0.0, '1': 1.0},
35
+ 'Other_Cancer': {'0': 0.0, '1': 1.0},
36
+ 'Depression': {'0': 0.0, '1': 1.0},
37
+ 'Arthritis': {'0': 0.0, '1': 1.0}
38
+ }
39
+
40
+ class PreprocessingService:
41
+ @staticmethod
42
+ def process_input(data: Dict[str, Any]) -> List[float]:
43
+ input_vector = []
44
+ for feature in MODEL_FEATURES:
45
+ raw_val = data.get(feature)
46
+ processed_val = PreprocessingService._process_single_value(raw_val, feature)
47
+ input_vector.append(processed_val)
48
+ return input_vector
49
+
50
+ @staticmethod
51
+ def _process_single_value(value: Any, feature_name: str) -> float:
52
+ val_str = str(value).lower()
53
+
54
+ if feature_name in SCALERS:
55
+ config = SCALERS[feature_name]
56
+
57
+ # Numeric Range Scaling
58
+ if isinstance(config, dict) and 'min' in config:
59
+ try:
60
+ val = float(value)
61
+ norm_val = (val - config['min']) / (config['max'] - config['min'])
62
+ return max(0.0, min(1.0, norm_val))
63
+ except (ValueError, TypeError):
64
+ logger.warning(f"Failed to convert numeric value '{value}' for feature '{feature_name}'. Defaulting to 0.0.")
65
+ return 0.0
66
+
67
+ # Categorical Logic
68
+ elif isinstance(config, dict):
69
+ # Exact match
70
+ if str(value) in config:
71
+ return config[str(value)]
72
+
73
+ # Check normalized key (e.g. for "Very_Good" vs "Very Good" if needed, though Pydantic handles validation)
74
+ # But let's keep the logic from original app just in case
75
+ if val_str in ['on', 'true', 'yes']: return 1.0
76
+ if val_str in ['off', 'false', 'no']: return 0.0
77
+
78
+ logger.warning(f"Unknown categorical value '{value}' for feature '{feature_name}'. Defaulting to 0.0.")
79
+ return 0.0
80
+
81
+ # Default fallback
82
+ try:
83
+ return float(value)
84
+ except:
85
+ return 0.0
app/static/script.js ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let predictedRiskLevel = 1;
2
+
3
+ document.addEventListener('DOMContentLoaded', () => {
4
+ const form = document.getElementById('predictionForm');
5
+ const heightInput = document.getElementById('Height');
6
+ const weightInput = document.getElementById('Weight');
7
+ const bmiInput = document.getElementById('BMI');
8
+ const modal = document.getElementById('resultModal');
9
+ const closeModalBtn = document.querySelector('.close-modal');
10
+
11
+ // Live Preview Elements
12
+ const livePreview = document.getElementById('livePreview');
13
+ const liveRiskScore = document.getElementById('liveRiskScore');
14
+ const liveRiskLabel = document.getElementById('liveRiskLabel');
15
+
16
+ // Auto-calculate BMI
17
+ function calculateBMI() {
18
+ const heightCm = parseFloat(heightInput.value);
19
+ const weightKg = parseFloat(weightInput.value);
20
+
21
+ if (heightCm > 0 && weightKg > 0) {
22
+ const heightM = heightCm / 100;
23
+ const bmi = (weightKg / (heightM * heightM)).toFixed(2);
24
+ bmiInput.value = bmi;
25
+ } else {
26
+ bmiInput.value = '';
27
+ }
28
+ }
29
+
30
+ heightInput.addEventListener('input', calculateBMI);
31
+ weightInput.addEventListener('input', calculateBMI);
32
+
33
+ // Live Preview Logic (Debounced)
34
+ let debounceTimer;
35
+ function updateLivePreview() {
36
+ clearTimeout(debounceTimer);
37
+ debounceTimer = setTimeout(async () => {
38
+ // Check if required fields are filled (basic check)
39
+ if (!heightInput.value || !weightInput.value || !document.getElementById('Age').value) return;
40
+
41
+ const formData = new FormData(form);
42
+ const data = {};
43
+
44
+ formData.forEach((value, key) => {
45
+ if (form.querySelector(`input[name="${key}"][type="checkbox"]`)) {
46
+ data[key] = "1";
47
+ } else {
48
+ data[key] = value;
49
+ }
50
+ });
51
+
52
+ const checkboxes = form.querySelectorAll('input[type="checkbox"]');
53
+ checkboxes.forEach(cb => {
54
+ if (!data.hasOwnProperty(cb.name)) data[cb.name] = "0";
55
+ });
56
+
57
+ if (!data['BMI']) return;
58
+
59
+ try {
60
+ const response = await fetch('/api/v1/predict', {
61
+ method: 'POST',
62
+ headers: { 'Content-Type': 'application/json' },
63
+ body: JSON.stringify(data)
64
+ });
65
+
66
+ if (response.ok) {
67
+ const result = await response.json();
68
+ liveRiskScore.textContent = (result.probability * 100).toFixed(1) + '%';
69
+ liveRiskLabel.textContent = result.risk_label;
70
+ livePreview.classList.add('visible');
71
+
72
+ if (result.risk_level <= 2) liveRiskScore.style.color = '#10B981';
73
+ else if (result.risk_level === 3) liveRiskScore.style.color = '#F59E0B';
74
+ else liveRiskScore.style.color = '#EF4444';
75
+ }
76
+ } catch (e) {
77
+ console.error("Live preview failed", e);
78
+ }
79
+ }, 500);
80
+ }
81
+
82
+ form.addEventListener('change', updateLivePreview);
83
+
84
+ // Form Submission
85
+ form.addEventListener('submit', async (e) => {
86
+ e.preventDefault();
87
+
88
+ const submitBtn = form.querySelector('button[type="submit"]');
89
+ const originalBtnText = submitBtn.innerHTML;
90
+ submitBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Analyzing...';
91
+ submitBtn.disabled = true;
92
+
93
+ const formData = new FormData(form);
94
+ const data = {};
95
+
96
+ formData.forEach((value, key) => {
97
+ if (form.querySelector(`input[name="${key}"][type="checkbox"]`)) {
98
+ data[key] = "1";
99
+ } else {
100
+ data[key] = value;
101
+ }
102
+ });
103
+
104
+ const checkboxes = form.querySelectorAll('input[type="checkbox"]');
105
+ checkboxes.forEach(cb => {
106
+ if (!data.hasOwnProperty(cb.name)) {
107
+ data[cb.name] = "0";
108
+ }
109
+ });
110
+
111
+ try {
112
+ const response = await fetch('/api/v1/predict', {
113
+ method: 'POST',
114
+ headers: {
115
+ 'Content-Type': 'application/json'
116
+ },
117
+ body: JSON.stringify(data)
118
+ });
119
+
120
+ if (!response.ok) {
121
+ throw new Error('Network response was not ok');
122
+ }
123
+
124
+ const result = await response.json();
125
+ window.lastResult = result;
126
+ showResult(result);
127
+
128
+ } catch (error) {
129
+ console.error('Error:', error);
130
+ alert('An error occurred during analysis. Please try again.');
131
+ } finally {
132
+ submitBtn.innerHTML = originalBtnText;
133
+ submitBtn.disabled = false;
134
+ }
135
+ });
136
+
137
+ function showResult(result) {
138
+ const riskLevelEl = document.getElementById('riskLevel');
139
+ const riskIconEl = document.getElementById('riskIcon');
140
+ const probFill = document.getElementById('probFill');
141
+ const probValue = document.getElementById('probValue');
142
+
143
+ predictedRiskLevel = result.risk_level;
144
+
145
+ const probability = result.probability;
146
+ const percentage = (probability * 100).toFixed(1) + '%';
147
+
148
+ let colorStart, colorEnd, iconClass, text;
149
+
150
+ if (predictedRiskLevel <= 2) {
151
+ text = 'Low Risk';
152
+ iconClass = 'fa-heart-circle-check risk-low';
153
+ colorStart = '#34D399';
154
+ colorEnd = '#10B981';
155
+ riskLevelEl.style.color = '#10B981';
156
+ } else if (predictedRiskLevel === 3) {
157
+ text = 'Moderate Risk';
158
+ iconClass = 'fa-heart-circle-exclamation';
159
+ colorStart = '#FBBF24';
160
+ colorEnd = '#F59E0B';
161
+ riskLevelEl.style.color = '#F59E0B';
162
+ riskIconEl.innerHTML = `<i class="fa-solid ${iconClass}" style="color: #F59E0B"></i>`;
163
+ } else {
164
+ text = 'High Risk';
165
+ iconClass = 'fa-heart-crack risk-high';
166
+ colorStart = '#F87171';
167
+ colorEnd = '#EF4444';
168
+ riskLevelEl.style.color = '#EF4444';
169
+ }
170
+
171
+ riskLevelEl.textContent = text;
172
+ if (predictedRiskLevel !== 3) {
173
+ riskLevelEl.className = predictedRiskLevel <= 2 ? 'risk-low' : 'risk-high';
174
+ riskIconEl.innerHTML = `<i class="fa-solid ${iconClass}"></i>`;
175
+ }
176
+
177
+ probFill.style.background = `linear-gradient(90deg, ${colorStart}, ${colorEnd})`;
178
+ probFill.style.width = percentage;
179
+ probValue.textContent = percentage;
180
+
181
+ modal.classList.add('visible');
182
+ }
183
+
184
+ function closeModal() {
185
+ modal.classList.remove('visible');
186
+ }
187
+
188
+ closeModalBtn.addEventListener('click', closeModal);
189
+ window.onclick = function (event) {
190
+ if (event.target == modal) {
191
+ closeModal();
192
+ }
193
+ }
194
+
195
+ // smooth scroll to sections
196
+ document.querySelectorAll('a[href^="#"]').forEach(anchor => {
197
+ anchor.addEventListener('click', function (e) {
198
+ e.preventDefault();
199
+ const targetId = this.getAttribute('href');
200
+ if (targetId === '#') return;
201
+ const targetElement = document.querySelector(targetId);
202
+ if (targetElement) {
203
+ targetElement.scrollIntoView({
204
+ behavior: 'smooth'
205
+ });
206
+ }
207
+ });
208
+ });
209
+
210
+ window.closeModal = closeModal;
211
+ });
212
+
213
+ function goToRecommendations() {
214
+ // Gather key parameters for recommendation personalization
215
+ const params = new URLSearchParams();
216
+
217
+ // 1. Pass the exact calculated probability
218
+ if (window.lastResult && window.lastResult.probability) {
219
+ params.append('risk', (window.lastResult.probability * 100).toFixed(1));
220
+ }
221
+
222
+ // 2. Selects and Inputs
223
+ const fields = [
224
+ 'Age', 'BMI', 'Sex', 'Smoking', 'Diabetes',
225
+ 'Alcohol', 'Fried_Potato', 'Fruit', 'Green_Vegetables', 'Exercise', 'General_Health'
226
+ ];
227
+
228
+ fields.forEach(id => {
229
+ const el = document.getElementById(id);
230
+ if (el) params.append(id, el.value);
231
+ });
232
+
233
+ // 3. Checkboxes (Special handling)
234
+ const checkBoxes = ['Skin_Cancer', 'Other_Cancer', 'Depression', 'Arthritis'];
235
+ checkBoxes.forEach(id => {
236
+ const el = document.getElementById(id);
237
+ if (el) params.append(id, el.checked ? "1" : "0");
238
+ });
239
+
240
+ window.location.href = `/recommendation/${predictedRiskLevel}?${params.toString()}`;
241
+ }
app/static/style.css ADDED
@@ -0,0 +1,1790 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* =============================================================================
2
+ 1. GLOBAL STYLES - Used across ALL pages (index.html, calculate.html, recommendation.html)
3
+ ============================================================================= */
4
+
5
+ /* -----------------------------------------------------------------------------
6
+ 1.1 CSS Variables & Design Tokens
7
+ ----------------------------------------------------------------------------- */
8
+ :root {
9
+ /* Color System - Human Centric */
10
+ --primary-color: #3B82F6;
11
+ --primary-hover: #2563EB;
12
+ --secondary-color: #64748B;
13
+ --accent-color: #8B5CF6;
14
+ --success: #10B981;
15
+ --warning: #F59E0B;
16
+ --danger: #EF4444;
17
+
18
+ /* Backgrounds */
19
+ --bg-body: #F8FAFC;
20
+ --bg-surface: #FFFFFF;
21
+ --bg-subtle: #F1F5F9;
22
+ --glass-bg: rgba(255, 255, 255, 0.95);
23
+
24
+ /* Typography */
25
+ --text-main: #1E293B;
26
+ --text-muted: #64748B;
27
+ --text-light: #94A3B8;
28
+
29
+ /* Spacing & Layout */
30
+ --space-xs: 0.25rem;
31
+ --space-sm: 0.5rem;
32
+ --space-md: 1rem;
33
+ --space-lg: 1.5rem;
34
+ --space-xl: 2rem;
35
+
36
+ /* Borders & Radius */
37
+ --radius-sm: 0.375rem;
38
+ --radius-md: 0.5rem;
39
+ --radius-lg: 0.75rem;
40
+ --radius-xl: 1rem;
41
+ --border-color: #E2E8F0;
42
+ --border-subtle: #F1F5F9;
43
+
44
+ /* Shadows */
45
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
46
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
47
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
48
+ --shadow-glow: 0 0 20px rgba(59, 130, 246, 0.15);
49
+ }
50
+
51
+ /* -----------------------------------------------------------------------------
52
+ 1.2 Base Reset & Typography (All Pages)
53
+ ----------------------------------------------------------------------------- */
54
+ * {
55
+ margin: 0;
56
+ padding: 0;
57
+ box-sizing: border-box;
58
+ }
59
+
60
+ body {
61
+ font-family: 'Plus Jakarta Sans', 'Outfit', sans-serif;
62
+ background-color: var(--bg-body);
63
+ color: var(--text-main);
64
+ line-height: 1.6;
65
+ overflow-x: hidden;
66
+ width: 100%;
67
+ }
68
+
69
+ a {
70
+ text-decoration: none;
71
+ color: inherit;
72
+ transition: color 0.2s ease;
73
+ }
74
+
75
+ ul {
76
+ list-style: none;
77
+ }
78
+
79
+ img {
80
+ max-width: 100%;
81
+ height: auto;
82
+ }
83
+
84
+ /* -----------------------------------------------------------------------------
85
+ 1.3 Background Effect (All Pages)
86
+ ----------------------------------------------------------------------------- */
87
+ .background-glow {
88
+ position: fixed;
89
+ top: 0;
90
+ left: 0;
91
+ width: 100vw;
92
+ height: 100vh;
93
+ z-index: -1;
94
+ background:
95
+ radial-gradient(circle at 10% 20%, rgba(59, 130, 246, 0.05) 0%, transparent 40%),
96
+ radial-gradient(circle at 90% 80%, rgba(139, 92, 246, 0.05) 0%, transparent 40%);
97
+ }
98
+
99
+ /* -----------------------------------------------------------------------------
100
+ 1.4 Container (All Pages)
101
+ ----------------------------------------------------------------------------- */
102
+ .container {
103
+ max-width: 1200px;
104
+ margin: 0 auto;
105
+ padding: 0 var(--space-md);
106
+ }
107
+
108
+ /* -----------------------------------------------------------------------------
109
+ 1.5 Header & Navigation (All Pages)
110
+ ----------------------------------------------------------------------------- */
111
+ .site-header {
112
+ background: var(--glass-bg);
113
+ backdrop-filter: blur(10px);
114
+ position: sticky;
115
+ top: 0;
116
+ z-index: 1000;
117
+ border-bottom: 1px solid var(--border-subtle);
118
+ padding: var(--space-md) 0;
119
+ }
120
+
121
+ .header-container {
122
+ max-width: 1200px;
123
+ margin: 0 auto;
124
+ padding: 0 var(--space-md);
125
+ display: flex;
126
+ justify-content: space-between;
127
+ align-items: center;
128
+ }
129
+
130
+ .brand {
131
+ display: flex;
132
+ align-items: center;
133
+ gap: var(--space-sm);
134
+ font-weight: 700;
135
+ font-size: 1.5rem;
136
+ color: var(--text-main);
137
+ }
138
+
139
+ .heart-icon {
140
+ animation: heartbeat 1.5s infinite;
141
+ }
142
+
143
+ .nav-links {
144
+ display: flex;
145
+ align-items: center;
146
+ gap: 2rem;
147
+ }
148
+
149
+ .nav-links a {
150
+ font-weight: 500;
151
+ color: var(--text-muted);
152
+ }
153
+
154
+ .nav-links a:hover,
155
+ .nav-links a.active {
156
+ color: var(--primary-color);
157
+ }
158
+
159
+ .btn-start {
160
+ background: var(--primary-color);
161
+ color: white !important;
162
+ padding: 0.5rem 1.25rem;
163
+ border-radius: 100px;
164
+ transition: all 0.3s ease;
165
+ }
166
+
167
+ .btn-start:hover {
168
+ background: var(--primary-hover);
169
+ transform: translateY(-2px);
170
+ box-shadow: var(--shadow-md);
171
+ }
172
+
173
+ /* =============================================================================
174
+ 2. HOME PAGE STYLES (index.html)
175
+ ============================================================================= */
176
+
177
+ /* -----------------------------------------------------------------------------
178
+ 2.1 Hero Section (index.html)
179
+ ----------------------------------------------------------------------------- */
180
+ .hero {
181
+ display: grid;
182
+ grid-template-columns: 1fr 1fr;
183
+ gap: 4rem;
184
+ align-items: center;
185
+ padding: 6rem 0;
186
+ min-height: 80vh;
187
+ }
188
+
189
+ .hero h1 {
190
+ font-size: 3.5rem;
191
+ line-height: 1.2;
192
+ margin-bottom: 1.5rem;
193
+ font-weight: 800;
194
+ letter-spacing: -0.02em;
195
+ }
196
+
197
+ .hero p {
198
+ font-size: 1.25rem;
199
+ color: var(--text-muted);
200
+ margin-bottom: 2.5rem;
201
+ max-width: 540px;
202
+ }
203
+
204
+ .hero-btn {
205
+ display: inline-flex;
206
+ align-items: center;
207
+ gap: 0.75rem;
208
+ padding: 1rem 2rem;
209
+ border-radius: 100px;
210
+ font-weight: 600;
211
+ transition: all 0.3s ease;
212
+ }
213
+
214
+ .hero-image {
215
+ position: relative;
216
+ display: flex;
217
+ justify-content: center;
218
+ }
219
+
220
+ /* -----------------------------------------------------------------------------
221
+ 2.2 Trust Section (index.html)
222
+ ----------------------------------------------------------------------------- */
223
+ .trust-section {
224
+ background: white;
225
+ padding: 3rem 0;
226
+ border-top: 1px solid var(--border-subtle);
227
+ border-bottom: 1px solid var(--border-subtle);
228
+ }
229
+
230
+ .trust-container {
231
+ max-width: 1200px;
232
+ margin: 0 auto;
233
+ display: grid;
234
+ grid-template-columns: repeat(4, 1fr);
235
+ gap: 2rem;
236
+ text-align: center;
237
+ }
238
+
239
+ .stat-number {
240
+ font-size: 2.5rem;
241
+ font-weight: 700;
242
+ color: var(--primary-color);
243
+ margin-bottom: 0.5rem;
244
+ }
245
+
246
+ .stat-label {
247
+ color: var(--text-muted);
248
+ font-weight: 500;
249
+ }
250
+
251
+ /* -----------------------------------------------------------------------------
252
+ 2.3 How It Works Section (index.html)
253
+ ----------------------------------------------------------------------------- */
254
+ .how-it-works {
255
+ padding: 6rem 0;
256
+ }
257
+
258
+ .section-title {
259
+ text-align: center;
260
+ max-width: 700px;
261
+ margin: 0 auto 4rem;
262
+ }
263
+
264
+ .section-title h2 {
265
+ font-size: 2.5rem;
266
+ font-weight: 700;
267
+ margin-bottom: 1rem;
268
+ }
269
+
270
+ .section-title p {
271
+ color: var(--text-muted);
272
+ font-size: 1.125rem;
273
+ }
274
+
275
+ .steps-grid {
276
+ display: grid;
277
+ grid-template-columns: repeat(3, 1fr);
278
+ gap: 2rem;
279
+ }
280
+
281
+ .step-card {
282
+ background: white;
283
+ padding: 2.5rem;
284
+ border-radius: var(--radius-xl);
285
+ border: 1px solid var(--border-subtle);
286
+ position: relative;
287
+ transition: all 0.3s ease;
288
+ }
289
+
290
+ .step-card:hover {
291
+ transform: translateY(-5px);
292
+ box-shadow: var(--shadow-lg);
293
+ border-color: rgba(59, 130, 246, 0.3);
294
+ }
295
+
296
+ .step-number {
297
+ position: absolute;
298
+ top: -15px;
299
+ right: 15px;
300
+ /* Changed from -15px to 15px to avoid overflow */
301
+ width: 40px;
302
+ height: 40px;
303
+ background: var(--primary-color);
304
+ color: white;
305
+ border-radius: 50%;
306
+ display: flex;
307
+ align-items: center;
308
+ justify-content: center;
309
+ font-weight: 700;
310
+ box-shadow: var(--shadow-md);
311
+ }
312
+
313
+ .step-icon {
314
+ width: 60px;
315
+ height: 60px;
316
+ background: rgba(59, 130, 246, 0.1);
317
+ color: var(--primary-color);
318
+ border-radius: var(--radius-lg);
319
+ display: flex;
320
+ align-items: center;
321
+ justify-content: center;
322
+ font-size: 1.5rem;
323
+ margin-bottom: 1.5rem;
324
+ }
325
+
326
+ .step-card h3 {
327
+ font-size: 1.25rem;
328
+ margin-bottom: 0.75rem;
329
+ }
330
+
331
+ .step-card p {
332
+ color: var(--text-muted);
333
+ }
334
+
335
+ /* -----------------------------------------------------------------------------
336
+ 2.4 About Section (index.html)
337
+ ----------------------------------------------------------------------------- */
338
+ .about-grid {
339
+ display: grid;
340
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
341
+ gap: 2rem;
342
+ }
343
+
344
+ .about-card {
345
+ background: white;
346
+ padding: 2rem;
347
+ border-radius: var(--radius-xl);
348
+ box-shadow: var(--shadow-sm);
349
+ transition: all 0.3s ease;
350
+ text-align: center;
351
+ }
352
+
353
+ .about-card:hover {
354
+ transform: translateY(-5px);
355
+ box-shadow: var(--shadow-lg);
356
+ }
357
+
358
+ .about-card i {
359
+ font-size: 2.5rem;
360
+ color: var(--primary-color);
361
+ margin-bottom: 1.5rem;
362
+ }
363
+
364
+ .about-card h3 {
365
+ margin-bottom: 1rem;
366
+ font-size: 1.25rem;
367
+ }
368
+
369
+ .about-card p {
370
+ color: var(--text-muted);
371
+ }
372
+
373
+ /* -----------------------------------------------------------------------------
374
+ 2.5 Testimonials Section (index.html)
375
+ ----------------------------------------------------------------------------- */
376
+ .testimonials {
377
+ padding: 6rem 0;
378
+ background: white;
379
+ }
380
+
381
+ .testimonials-grid {
382
+ display: grid;
383
+ grid-template-columns: repeat(3, 1fr);
384
+ gap: 2rem;
385
+ }
386
+
387
+ .testimonial-card {
388
+ background: var(--bg-body);
389
+ padding: 2rem;
390
+ border-radius: var(--radius-xl);
391
+ position: relative;
392
+ }
393
+
394
+ .quote-icon {
395
+ color: var(--primary-color);
396
+ opacity: 0.2;
397
+ font-size: 2rem;
398
+ margin-bottom: 1rem;
399
+ }
400
+
401
+ .testimonial-text {
402
+ font-style: italic;
403
+ color: var(--text-main);
404
+ margin-bottom: 1.5rem;
405
+ }
406
+
407
+ .testimonial-author {
408
+ display: flex;
409
+ align-items: center;
410
+ gap: 1rem;
411
+ }
412
+
413
+ .author-avatar {
414
+ width: 48px;
415
+ height: 48px;
416
+ border-radius: 50%;
417
+ background: var(--primary-color);
418
+ color: white;
419
+ display: flex;
420
+ align-items: center;
421
+ justify-content: center;
422
+ font-weight: 700;
423
+ }
424
+
425
+ .author-name {
426
+ font-weight: 600;
427
+ }
428
+
429
+ .author-role {
430
+ font-size: 0.875rem;
431
+ color: var(--text-muted);
432
+ }
433
+
434
+ /* -----------------------------------------------------------------------------
435
+ 2.6 Contact Section (index.html)
436
+ ----------------------------------------------------------------------------- */
437
+ .contact-grid {
438
+ display: grid;
439
+ grid-template-columns: repeat(4, 1fr);
440
+ gap: 2rem;
441
+ }
442
+
443
+ .contact-card {
444
+ background: white;
445
+ padding: 2rem;
446
+ border-radius: var(--radius-xl);
447
+ text-align: center;
448
+ transition: all 0.3s ease;
449
+ }
450
+
451
+ .contact-card:hover {
452
+ transform: translateY(-5px);
453
+ box-shadow: var(--shadow-md);
454
+ }
455
+
456
+ .contact-card i {
457
+ font-size: 2rem;
458
+ color: var(--primary-color);
459
+ margin-bottom: 1rem;
460
+ }
461
+
462
+ .contact-card h3 {
463
+ margin-bottom: 0.5rem;
464
+ font-size: 1.125rem;
465
+ }
466
+
467
+ .contact-card p {
468
+ color: var(--text-muted);
469
+ }
470
+
471
+ .contact-card .small {
472
+ font-size: 0.875rem;
473
+ opacity: 0.8;
474
+ margin-top: 0.25rem;
475
+ }
476
+
477
+ /* -----------------------------------------------------------------------------
478
+ 1.6 Footer (All Pages)
479
+ ----------------------------------------------------------------------------- */
480
+ .footer {
481
+ background: #0F172A;
482
+ color: white;
483
+ padding: 4rem 0 0;
484
+ }
485
+
486
+ .footer-container {
487
+ max-width: 1200px;
488
+ margin: 0 auto;
489
+ padding: 0 var(--space-md);
490
+ display: grid;
491
+ grid-template-columns: 2fr 1fr 1fr;
492
+ gap: 4rem;
493
+ margin-bottom: 3rem;
494
+ }
495
+
496
+ .footer-about h3 {
497
+ font-size: 1.5rem;
498
+ margin-bottom: 1rem;
499
+ }
500
+
501
+ .footer-about p {
502
+ color: #94A3B8;
503
+ line-height: 1.8;
504
+ }
505
+
506
+ .footer-links h4,
507
+ .footer-contact h4 {
508
+ margin-bottom: 1.5rem;
509
+ color: white;
510
+ }
511
+
512
+ .footer-links a {
513
+ display: block;
514
+ color: #94A3B8;
515
+ margin-bottom: 0.75rem;
516
+ }
517
+
518
+ .footer-links a:hover {
519
+ color: white;
520
+ }
521
+
522
+ .footer-contact p {
523
+ color: #94A3B8;
524
+ margin-bottom: 0.75rem;
525
+ }
526
+
527
+ .footer-bottom {
528
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
529
+ padding: 2rem 0;
530
+ text-align: center;
531
+ color: #64748B;
532
+ }
533
+
534
+ /* =============================================================================
535
+ 3. CALCULATE PAGE STYLES (calculate.html)
536
+ ============================================================================= */
537
+
538
+ /* -----------------------------------------------------------------------------
539
+ 3.1 Calculator Container (calculate.html)
540
+ ----------------------------------------------------------------------------- */
541
+ .calculator-section {
542
+ max-width: 1100px;
543
+ margin: 50px auto;
544
+ text-align: center;
545
+ padding: 0 20px;
546
+ }
547
+
548
+ .calculator-section h1 {
549
+ font-size: 2rem;
550
+ margin-bottom: 0.5rem;
551
+ }
552
+
553
+ .subtitle {
554
+ color: #6b7280;
555
+ margin-bottom: 30px;
556
+ }
557
+
558
+ /* -----------------------------------------------------------------------------
559
+ 3.2 Card & Form Layout (calculate.html)
560
+ ----------------------------------------------------------------------------- */
561
+ .card {
562
+ background: white;
563
+ border-radius: 14px;
564
+ padding: 30px;
565
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
566
+ text-align: left;
567
+ }
568
+
569
+ .grid {
570
+ display: grid;
571
+ grid-template-columns: 1fr 1fr;
572
+ gap: 30px;
573
+ }
574
+
575
+ .box {
576
+ background: #f9fafb;
577
+ border-radius: 12px;
578
+ padding: 20px;
579
+ text-align: left;
580
+ }
581
+
582
+ .box h3 {
583
+ margin-bottom: 15px;
584
+ font-size: 1.1rem;
585
+ font-weight: 600;
586
+ }
587
+
588
+ .box.full {
589
+ margin-top: 30px;
590
+ }
591
+
592
+ /* -----------------------------------------------------------------------------
593
+ 3.3 Form Inputs (calculate.html)
594
+ ----------------------------------------------------------------------------- */
595
+ .calculator-section label {
596
+ display: block;
597
+ font-size: 16px;
598
+ margin: 10px 0 5px;
599
+ color: #374151;
600
+ text-align: left;
601
+ }
602
+
603
+ .calculator-section input,
604
+ .calculator-section select {
605
+ width: 100%;
606
+ padding: 10px;
607
+ border-radius: 8px;
608
+ border: none;
609
+ background: #eef2f7;
610
+ font-family: inherit;
611
+ font-size: 1.2rem;
612
+ color: #1f2937;
613
+ }
614
+
615
+ .calculator-section input:focus,
616
+ .calculator-section select:focus {
617
+ outline: 2px solid #2563eb;
618
+ outline-offset: 1px;
619
+ }
620
+
621
+ .calculator-section input[disabled] {
622
+ background: #e5e7eb;
623
+ color: #9ca3af;
624
+ cursor: not-allowed;
625
+ }
626
+
627
+ /* -----------------------------------------------------------------------------
628
+ 3.4 Checkboxes (calculate.html)
629
+ ----------------------------------------------------------------------------- */
630
+ .checkbox-group {
631
+ display: grid;
632
+ grid-template-columns: 1fr 1fr;
633
+ margin-top: 10px;
634
+ gap: 8px;
635
+ }
636
+
637
+ .checkbox-group label {
638
+ display: flex;
639
+ align-items: center;
640
+ gap: 8px;
641
+ font-size: 14px;
642
+ cursor: pointer;
643
+ }
644
+
645
+ .checkbox-group input[type="checkbox"] {
646
+ width: 20px;
647
+ height: 18px;
648
+ accent-color: #2563eb;
649
+ }
650
+
651
+ /* -----------------------------------------------------------------------------
652
+ 3.5 Lifestyle Grid (calculate.html)
653
+ ----------------------------------------------------------------------------- */
654
+ .grid-4 {
655
+ display: grid;
656
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
657
+ gap: 20px;
658
+ }
659
+
660
+ /* -----------------------------------------------------------------------------
661
+ 3.6 Analyze Button (calculate.html)
662
+ ----------------------------------------------------------------------------- */
663
+ .analyze-btn {
664
+ margin-top: 30px;
665
+ background: #2563eb;
666
+ color: white;
667
+ border: none;
668
+ padding: 15px 30px;
669
+ border-radius: 30px;
670
+ font-size: 16px;
671
+ font-weight: 600;
672
+ cursor: pointer;
673
+ transition: all 0.2s ease;
674
+ }
675
+
676
+ .analyze-btn:hover {
677
+ background: #1d4ed8;
678
+ transform: translateY(-2px);
679
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
680
+ }
681
+
682
+ /* -----------------------------------------------------------------------------
683
+ 3.7 Result Modal (calculate.html)
684
+ ----------------------------------------------------------------------------- */
685
+ .modal {
686
+ position: fixed;
687
+ top: 0;
688
+ left: 0;
689
+ width: 100%;
690
+ height: 100%;
691
+ background: rgba(15, 23, 42, 0.7);
692
+ backdrop-filter: blur(4px);
693
+ display: none;
694
+ justify-content: center;
695
+ align-items: center;
696
+ z-index: 10000;
697
+ }
698
+
699
+ .modal.visible {
700
+ display: flex;
701
+ }
702
+
703
+ .modal-content {
704
+ background: white;
705
+ width: 95%;
706
+ max-width: 500px;
707
+ padding: 3rem 2.5rem;
708
+ border-radius: 20px;
709
+ text-align: center;
710
+ position: relative;
711
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
712
+ }
713
+
714
+ .close-modal {
715
+ position: absolute;
716
+ top: 1.25rem;
717
+ right: 1.25rem;
718
+ width: 28px;
719
+ height: 28px;
720
+ border-radius: 50%;
721
+ background: #f1f5f9;
722
+ color: #94a3b8;
723
+ display: flex;
724
+ align-items: center;
725
+ justify-content: center;
726
+ cursor: pointer;
727
+ border: none;
728
+ font-size: 1rem;
729
+ transition: all 0.2s;
730
+ }
731
+
732
+ .close-modal:hover {
733
+ background: #e2e8f0;
734
+ color: #1e293b;
735
+ }
736
+
737
+ .risk-icon {
738
+ font-size: 4.5rem;
739
+ margin-bottom: 1.5rem;
740
+ }
741
+
742
+ .risk-high {
743
+ color: #EF4444;
744
+ }
745
+
746
+ .risk-low {
747
+ color: #10B981;
748
+ }
749
+
750
+ .result-header h2 {
751
+ font-size: 2rem;
752
+ font-weight: 800;
753
+ margin-bottom: 0.75rem;
754
+ }
755
+
756
+ .result-body p {
757
+ color: #64748b;
758
+ font-size: 0.9375rem;
759
+ line-height: 1.6;
760
+ margin-bottom: 1.5rem;
761
+ padding: 0 1rem;
762
+ }
763
+
764
+ .probability-meter {
765
+ height: 28px;
766
+ background: #f1f5f9;
767
+ border-radius: 100px;
768
+ margin: 1.5rem 0.5rem;
769
+ position: relative;
770
+ overflow: hidden;
771
+ }
772
+
773
+ .prob-fill {
774
+ height: 100%;
775
+ background: #10B981;
776
+ width: 0%;
777
+ transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1);
778
+ border-radius: 100px;
779
+ }
780
+
781
+ #probValue {
782
+ position: absolute;
783
+ top: 50%;
784
+ left: 50%;
785
+ transform: translate(-50%, -50%);
786
+ font-size: 0.875rem;
787
+ font-weight: 700;
788
+ color: #1e293b;
789
+ }
790
+
791
+ .disclaimer {
792
+ font-size: 0.75rem;
793
+ color: #94a3b8;
794
+ line-height: 1.5;
795
+ margin-top: 2rem;
796
+ margin-bottom: 2.5rem;
797
+ padding-top: 1.25rem;
798
+ border-top: 1px solid #f1f5f9;
799
+ }
800
+
801
+ .modal-actions {
802
+ display: flex;
803
+ gap: 1rem;
804
+ justify-content: center;
805
+ }
806
+
807
+ .view-btn,
808
+ .btn-cancel {
809
+ padding: 0.875rem 1.5rem;
810
+ border-radius: 100px;
811
+ font-size: 0.9375rem;
812
+ font-weight: 600;
813
+ cursor: pointer;
814
+ transition: all 0.2s;
815
+ white-space: nowrap;
816
+ min-width: 160px;
817
+ }
818
+
819
+ .view-btn {
820
+ background: #3b82f6;
821
+ color: white;
822
+ border: none;
823
+ box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.2);
824
+ }
825
+
826
+ .view-btn:hover {
827
+ background: #2563eb;
828
+ transform: translateY(-1px);
829
+ box-shadow: 0 6px 10px -1px rgba(59, 130, 246, 0.3);
830
+ }
831
+
832
+ .btn-cancel {
833
+ background: white;
834
+ color: #1e293b;
835
+ border: 1px solid #e2e8f0;
836
+ }
837
+
838
+ .btn-cancel:hover {
839
+ background: #f8fafc;
840
+ border-color: #cbd5e1;
841
+ }
842
+
843
+ /* Animations */
844
+ @keyframes fadeInUp {
845
+ from {
846
+ opacity: 0;
847
+ transform: translateY(20px);
848
+ }
849
+
850
+ to {
851
+ opacity: 1;
852
+ transform: translateY(0);
853
+ }
854
+ }
855
+
856
+ @keyframes fadeInDown {
857
+ from {
858
+ opacity: 0;
859
+ transform: translateY(-20px);
860
+ }
861
+
862
+ to {
863
+ opacity: 1;
864
+ transform: translateY(0);
865
+ }
866
+ }
867
+
868
+ @keyframes fadeIn {
869
+ from {
870
+ opacity: 0;
871
+ }
872
+
873
+ to {
874
+ opacity: 1;
875
+ }
876
+ }
877
+
878
+ @keyframes zoomIn {
879
+ from {
880
+ opacity: 0;
881
+ transform: scale(0.95);
882
+ }
883
+
884
+ to {
885
+ opacity: 1;
886
+ transform: scale(1);
887
+ }
888
+ }
889
+
890
+ @keyframes heartbeat {
891
+ 0% {
892
+ transform: scale(1);
893
+ }
894
+
895
+ 15% {
896
+ transform: scale(1.15);
897
+ }
898
+
899
+ 30% {
900
+ transform: scale(1);
901
+ }
902
+
903
+ 45% {
904
+ transform: scale(1.15);
905
+ }
906
+
907
+ 60% {
908
+ transform: scale(1);
909
+ }
910
+
911
+ 100% {
912
+ transform: scale(1);
913
+ }
914
+ }
915
+
916
+ @keyframes float {
917
+ 0% {
918
+ transform: translateY(0px);
919
+ }
920
+
921
+ 50% {
922
+ transform: translateY(-15px);
923
+ }
924
+
925
+ 100% {
926
+ transform: translateY(0px);
927
+ }
928
+ }
929
+
930
+ @keyframes pulse-soft {
931
+ 0% {
932
+ box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
933
+ }
934
+
935
+ 70% {
936
+ box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
937
+ }
938
+
939
+ 100% {
940
+ box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
941
+ }
942
+ }
943
+
944
+ .pulse {
945
+ animation: pulse-soft 2s infinite;
946
+ }
947
+
948
+ /* Responsive */
949
+ @media (max-width: 768px) {
950
+ .hero {
951
+ grid-template-columns: 1fr;
952
+ padding: 4rem 0;
953
+ text-align: center;
954
+ }
955
+
956
+ .hero h1 {
957
+ font-size: 2.5rem;
958
+ }
959
+
960
+ .hero-content {
961
+ order: 2;
962
+ }
963
+
964
+ .hero-image {
965
+ order: 1;
966
+ margin-bottom: 2rem;
967
+ }
968
+
969
+ .hero-buttons {
970
+ justify-content: center;
971
+ }
972
+
973
+ .trust-container {
974
+ grid-template-columns: repeat(2, 1fr);
975
+ }
976
+
977
+ .steps-grid,
978
+ .testimonials-grid,
979
+ .contact-grid {
980
+ grid-template-columns: 1fr;
981
+ }
982
+
983
+ .footer-container {
984
+ grid-template-columns: 1fr;
985
+ gap: 2rem;
986
+ }
987
+
988
+ .form-grid,
989
+ .lifestyle-grid,
990
+ .checkbox-group {
991
+ grid-template-columns: 1fr;
992
+ }
993
+ }
994
+
995
+ /* Utilities */
996
+ .text-muted {
997
+ color: var(--text-muted) !important;
998
+ }
999
+
1000
+ /* New Hero Enhancements (Preserved) */
1001
+ .badge-new {
1002
+ display: inline-flex;
1003
+ align-items: center;
1004
+ gap: 8px;
1005
+ background: rgba(59, 130, 246, 0.1);
1006
+ color: var(--primary-color);
1007
+ padding: 6px 12px;
1008
+ border-radius: 100px;
1009
+ font-size: 0.875rem;
1010
+ font-weight: 600;
1011
+ margin-bottom: var(--space-lg);
1012
+ border: 1px solid rgba(59, 130, 246, 0.2);
1013
+ }
1014
+
1015
+ .text-gradient {
1016
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--accent-color) 100%);
1017
+ -webkit-background-clip: text;
1018
+ -webkit-text-fill-color: transparent;
1019
+ background-clip: text;
1020
+ }
1021
+
1022
+ .hero-buttons {
1023
+ display: flex;
1024
+ gap: 1rem;
1025
+ margin-bottom: 2rem;
1026
+ flex-wrap: wrap;
1027
+ }
1028
+
1029
+ .primary-btn {
1030
+ background: var(--primary-color);
1031
+ color: white;
1032
+ }
1033
+
1034
+ .secondary-btn {
1035
+ background: white;
1036
+ color: var(--text-main);
1037
+ border: 1px solid var(--border-subtle);
1038
+ }
1039
+
1040
+ .secondary-btn:hover {
1041
+ background: var(--bg-subtle);
1042
+ color: var(--primary-color);
1043
+ }
1044
+
1045
+ .hero-trust {
1046
+ display: flex;
1047
+ gap: 1.5rem;
1048
+ font-size: 0.875rem;
1049
+ color: var(--text-muted);
1050
+ font-weight: 500;
1051
+ }
1052
+
1053
+ .hero-trust span {
1054
+ display: flex;
1055
+ align-items: center;
1056
+ gap: 6px;
1057
+ }
1058
+
1059
+ .hero-trust i {
1060
+ color: var(--success);
1061
+ }
1062
+
1063
+ .floating-heart {
1064
+ animation: float 6s ease-in-out infinite;
1065
+ }
1066
+
1067
+ .count-up {
1068
+ font-variant-numeric: tabular-nums;
1069
+ }
1070
+
1071
+ /* =============================================================================
1072
+ 4. RECOMMENDATION PAGE STYLES (recommendation.html)
1073
+ Note: Additional styles are embedded in recommendation.html <style> tag
1074
+ ============================================================================= */
1075
+
1076
+ /* -----------------------------------------------------------------------------
1077
+ 4.1 Page Layout (recommendation.html)
1078
+ ----------------------------------------------------------------------------- */
1079
+ .recommendation-page {
1080
+ padding: 2rem 0;
1081
+ }
1082
+
1083
+ .page-header {
1084
+ text-align: center;
1085
+ margin-bottom: 3rem;
1086
+ animation: fadeInDown 0.8s ease-out;
1087
+ }
1088
+
1089
+ .page-header h1 {
1090
+ font-size: 2.5rem;
1091
+ font-weight: 800;
1092
+ color: var(--text-main);
1093
+ margin-bottom: 0.5rem;
1094
+ }
1095
+
1096
+ .page-header p {
1097
+ color: var(--text-muted);
1098
+ font-size: 1.125rem;
1099
+ }
1100
+
1101
+ .report-grid {
1102
+ display: grid;
1103
+ grid-template-columns: 1fr 420px;
1104
+ gap: 2rem;
1105
+ margin-bottom: 2rem;
1106
+ }
1107
+
1108
+ @media (max-width: 980px) {
1109
+ .report-grid {
1110
+ grid-template-columns: 1fr;
1111
+ }
1112
+
1113
+ .sidebar {
1114
+ order: -1;
1115
+ }
1116
+ }
1117
+
1118
+ /* Risk Level Selector */
1119
+ .risk-selector {
1120
+ background: white;
1121
+ padding: 2rem;
1122
+ border-radius: var(--radius-xl);
1123
+ box-shadow: var(--shadow-md);
1124
+ margin-bottom: 2rem;
1125
+ }
1126
+
1127
+ .risk-selector h3 {
1128
+ font-size: 1.25rem;
1129
+ font-weight: 700;
1130
+ margin-bottom: 1rem;
1131
+ color: var(--text-main);
1132
+ }
1133
+
1134
+ .risk-pills {
1135
+ display: grid;
1136
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
1137
+ gap: 0.75rem;
1138
+ }
1139
+
1140
+ .risk-pill {
1141
+ padding: 1rem;
1142
+ border-radius: var(--radius-lg);
1143
+ color: white;
1144
+ text-align: center;
1145
+ cursor: pointer;
1146
+ font-weight: 600;
1147
+ transition: all 0.3s ease;
1148
+ border: 2px solid transparent;
1149
+ }
1150
+
1151
+ .risk-pill:hover {
1152
+ transform: translateY(-2px);
1153
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
1154
+ }
1155
+
1156
+ .risk-pill.active {
1157
+ border-color: var(--text-main);
1158
+ box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.1);
1159
+ }
1160
+
1161
+ .risk-pill.vlow {
1162
+ background: linear-gradient(135deg, #10B981, #059669);
1163
+ }
1164
+
1165
+ .risk-pill.low {
1166
+ background: linear-gradient(135deg, #34D399, #10B981);
1167
+ }
1168
+
1169
+ .risk-pill.mod {
1170
+ background: linear-gradient(135deg, #FBBF24, #F59E0B);
1171
+ }
1172
+
1173
+ .risk-pill.high {
1174
+ background: linear-gradient(135deg, #FB923C, #F97316);
1175
+ }
1176
+
1177
+ .risk-pill.vh {
1178
+ background: linear-gradient(135deg, #EF4444, #DC2626);
1179
+ }
1180
+
1181
+ .risk-pill small {
1182
+ display: block;
1183
+ font-size: 0.75rem;
1184
+ opacity: 0.9;
1185
+ margin-top: 0.25rem;
1186
+ }
1187
+
1188
+ /* Main Report Card */
1189
+ .report-card {
1190
+ background: white;
1191
+ padding: 2.5rem;
1192
+ border-radius: var(--radius-xl);
1193
+ box-shadow: var(--shadow-lg);
1194
+ border-left: 6px solid var(--primary-color);
1195
+ animation: fadeInUp 0.8s ease-out;
1196
+ }
1197
+
1198
+ .report-card.highlight {
1199
+ box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.1), var(--shadow-lg);
1200
+ }
1201
+
1202
+ .report-title {
1203
+ font-size: 1.75rem;
1204
+ font-weight: 800;
1205
+ margin-bottom: 0.5rem;
1206
+ color: var(--text-main);
1207
+ }
1208
+
1209
+ .report-subtitle {
1210
+ color: var(--text-muted);
1211
+ margin-bottom: 2rem;
1212
+ font-size: 1rem;
1213
+ }
1214
+
1215
+ .recommendations-grid {
1216
+ display: grid;
1217
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
1218
+ gap: 1.5rem;
1219
+ margin-top: 2rem;
1220
+ }
1221
+
1222
+ .recommendation-card {
1223
+ background: linear-gradient(135deg, #F8FAFC 0%, #F1F5F9 100%);
1224
+ padding: 1.5rem;
1225
+ border-radius: var(--radius-lg);
1226
+ border: 1px solid var(--border-color);
1227
+ transition: all 0.3s ease;
1228
+ }
1229
+
1230
+ .recommendation-card:hover {
1231
+ transform: translateY(-4px);
1232
+ box-shadow: var(--shadow-md);
1233
+ }
1234
+
1235
+ .recommendation-card h4 {
1236
+ font-size: 1.125rem;
1237
+ font-weight: 700;
1238
+ margin-bottom: 1rem;
1239
+ color: var(--primary-color);
1240
+ display: flex;
1241
+ align-items: center;
1242
+ gap: 0.5rem;
1243
+ }
1244
+
1245
+ .recommendation-card ul {
1246
+ list-style: none;
1247
+ padding: 0;
1248
+ margin: 0;
1249
+ }
1250
+
1251
+ .recommendation-card li {
1252
+ padding: 0.5rem 0;
1253
+ color: var(--text-main);
1254
+ display: flex;
1255
+ align-items: flex-start;
1256
+ gap: 0.5rem;
1257
+ }
1258
+
1259
+ .recommendation-card li::before {
1260
+ content: "✓";
1261
+ color: var(--primary-color);
1262
+ font-weight: 700;
1263
+ flex-shrink: 0;
1264
+ }
1265
+
1266
+ /* Sidebar */
1267
+ .sidebar .card {
1268
+ background: white;
1269
+ padding: 1.5rem;
1270
+ border-radius: var(--radius-xl);
1271
+ box-shadow: var(--shadow-md);
1272
+ margin-bottom: 1.5rem;
1273
+ }
1274
+
1275
+ .sidebar h3 {
1276
+ font-size: 1.125rem;
1277
+ font-weight: 700;
1278
+ margin-bottom: 1rem;
1279
+ color: var(--text-main);
1280
+ }
1281
+
1282
+ /* Severity Meter */
1283
+ .meter-container {
1284
+ display: flex;
1285
+ flex-direction: column;
1286
+ align-items: center;
1287
+ padding: 1.5rem 0;
1288
+ }
1289
+
1290
+ .meter {
1291
+ width: 160px;
1292
+ height: 160px;
1293
+ position: relative;
1294
+ }
1295
+
1296
+ .meter svg {
1297
+ transform: rotate(-90deg);
1298
+ }
1299
+
1300
+ .meter .center {
1301
+ position: absolute;
1302
+ inset: 0;
1303
+ display: flex;
1304
+ align-items: center;
1305
+ justify-content: center;
1306
+ flex-direction: column;
1307
+ }
1308
+
1309
+ .meter .percent {
1310
+ font-size: 2rem;
1311
+ font-weight: 800;
1312
+ color: var(--primary-color);
1313
+ }
1314
+
1315
+ .meter .label {
1316
+ font-size: 0.875rem;
1317
+ color: var(--text-muted);
1318
+ margin-top: 0.25rem;
1319
+ }
1320
+
1321
+ /* Chart */
1322
+ .chart-container {
1323
+ height: 240px;
1324
+ margin-top: 1rem;
1325
+ }
1326
+
1327
+ /* Doctor Cards */
1328
+ .doctor-list {
1329
+ display: flex;
1330
+ flex-direction: column;
1331
+ gap: 1rem;
1332
+ }
1333
+
1334
+ .doctor-card {
1335
+ padding: 1.25rem;
1336
+ border-radius: var(--radius-lg);
1337
+ background: linear-gradient(135deg, #F8FAFC 0%, #F1F5F9 100%);
1338
+ border: 1px solid var(--border-color);
1339
+ transition: all 0.3s ease;
1340
+ }
1341
+
1342
+ .doctor-card:hover {
1343
+ transform: translateX(4px);
1344
+ box-shadow: var(--shadow-sm);
1345
+ }
1346
+
1347
+ .doctor-name {
1348
+ font-weight: 700;
1349
+ color: var(--text-main);
1350
+ margin-bottom: 0.5rem;
1351
+ }
1352
+
1353
+ .doctor-info {
1354
+ font-size: 0.875rem;
1355
+ color: var(--text-muted);
1356
+ margin-bottom: 0.75rem;
1357
+ }
1358
+
1359
+ .doctor-actions {
1360
+ display: flex;
1361
+ gap: 0.5rem;
1362
+ margin-top: 0.75rem;
1363
+ }
1364
+
1365
+ .btn-small {
1366
+ padding: 0.5rem 1rem;
1367
+ border-radius: 0.5rem;
1368
+ font-size: 0.875rem;
1369
+ font-weight: 500;
1370
+ border: none;
1371
+ cursor: pointer;
1372
+ transition: all 0.2s ease;
1373
+ }
1374
+
1375
+ .btn-outline {
1376
+ background: white;
1377
+ border: 1px solid var(--border-color);
1378
+ color: var(--text-main);
1379
+ }
1380
+
1381
+ .btn-outline:hover {
1382
+ background: var(--bg-body);
1383
+ }
1384
+
1385
+ .btn-primary-small {
1386
+ background: var(--primary-color);
1387
+ color: white;
1388
+ }
1389
+
1390
+ .btn-primary-small:hover {
1391
+ background: var(--primary-hover);
1392
+ }
1393
+
1394
+ /* Map */
1395
+ #map {
1396
+ height: 280px;
1397
+ border-radius: var(--radius-lg);
1398
+ overflow: hidden;
1399
+ margin: 1rem 0;
1400
+ }
1401
+
1402
+ /* Action Buttons */
1403
+ .action-section {
1404
+ background: white;
1405
+ padding: 2rem;
1406
+ border-radius: var(--radius-xl);
1407
+ box-shadow: var(--shadow-md);
1408
+ margin-top: 2rem;
1409
+ }
1410
+
1411
+ .action-section h3 {
1412
+ font-size: 1.25rem;
1413
+ font-weight: 700;
1414
+ margin-bottom: 1.5rem;
1415
+ color: var(--text-main);
1416
+ }
1417
+
1418
+ .action-buttons {
1419
+ display: flex;
1420
+ flex-wrap: wrap;
1421
+ gap: 1rem;
1422
+ }
1423
+
1424
+ .action-btn {
1425
+ padding: 0.875rem 1.75rem;
1426
+ border-radius: 100px;
1427
+ font-weight: 600;
1428
+ border: none;
1429
+ cursor: pointer;
1430
+ transition: all 0.3s ease;
1431
+ display: inline-flex;
1432
+ align-items: center;
1433
+ gap: 0.5rem;
1434
+ }
1435
+
1436
+ .action-btn-primary {
1437
+ background: var(--primary-color);
1438
+ color: white;
1439
+ box-shadow: 0 4px 6px rgba(79, 70, 229, 0.2);
1440
+ }
1441
+
1442
+ .action-btn-primary:hover {
1443
+ background: var(--primary-hover);
1444
+ transform: translateY(-2px);
1445
+ box-shadow: 0 8px 12px rgba(79, 70, 229, 0.3);
1446
+ }
1447
+
1448
+ .action-btn-secondary {
1449
+ background: white;
1450
+ color: var(--text-main);
1451
+ border: 1px solid var(--border-color);
1452
+ }
1453
+
1454
+ .action-btn-secondary:hover {
1455
+ background: var(--bg-body);
1456
+ }
1457
+
1458
+ /* -----------------------------------------------------------------------------
1459
+ 3.6 Dark Header Override (calculate.html only)
1460
+ Note: Uses :has() selector to apply only when calculator-section is present
1461
+ ----------------------------------------------------------------------------- */
1462
+
1463
+ /* Dark Header Override section removed to match home page header */
1464
+
1465
+
1466
+ /* Calculator Section Override */
1467
+ .calculator-section {
1468
+ padding: 3rem 0 4rem;
1469
+ max-width: 900px;
1470
+ }
1471
+
1472
+ .calculator-section .section-title {
1473
+ margin-bottom: 2.5rem;
1474
+ }
1475
+
1476
+ .calculator-section .section-title h2 {
1477
+ font-size: 2.25rem;
1478
+ font-weight: 700;
1479
+ color: var(--text-main);
1480
+ }
1481
+
1482
+ .calculator-section .section-title p {
1483
+ color: var(--text-muted);
1484
+ font-size: 1rem;
1485
+ }
1486
+
1487
+ /* Glass Panel Form */
1488
+ .calculator-section .glass-panel {
1489
+ background: white;
1490
+ border: 1px solid var(--border-color);
1491
+ border-radius: 16px;
1492
+ padding: 2.5rem;
1493
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
1494
+ }
1495
+
1496
+ /* Form Grid - Two Columns */
1497
+ .calculator-section .form-grid {
1498
+ display: grid;
1499
+ grid-template-columns: 1fr 1fr;
1500
+ gap: 2rem 3rem;
1501
+ margin-bottom: 0;
1502
+ }
1503
+
1504
+ /* Form Section Styling */
1505
+ .calculator-section .form-section {
1506
+ display: flex;
1507
+ flex-direction: column;
1508
+ gap: 1.25rem;
1509
+ }
1510
+
1511
+ .calculator-section .form-section.full-width {
1512
+ grid-column: 1 / -1;
1513
+ border-top: 1px solid var(--border-subtle);
1514
+ padding-top: 2rem;
1515
+ margin-top: 0.5rem;
1516
+ }
1517
+
1518
+ .calculator-section .form-section h3 {
1519
+ font-size: 1rem;
1520
+ font-weight: 600;
1521
+ color: var(--text-main);
1522
+ display: flex;
1523
+ align-items: center;
1524
+ gap: 0.5rem;
1525
+ margin-bottom: 0.5rem;
1526
+ }
1527
+
1528
+ .calculator-section .form-section h3 i {
1529
+ color: var(--primary-color);
1530
+ font-size: 0.9rem;
1531
+ }
1532
+
1533
+ /* Input Groups */
1534
+ .calculator-section .input-group {
1535
+ display: flex;
1536
+ flex-direction: column;
1537
+ gap: 0.375rem;
1538
+ }
1539
+
1540
+ .calculator-section .input-group label {
1541
+ font-size: 0.875rem;
1542
+ font-weight: 500;
1543
+ color: var(--text-main);
1544
+ }
1545
+
1546
+ .calculator-section input,
1547
+ .calculator-section select {
1548
+ padding: 0.75rem 1rem;
1549
+ border: 1px solid var(--border-color);
1550
+ border-radius: 8px;
1551
+ font-size: 0.9375rem;
1552
+ background: white;
1553
+ transition: all 0.2s ease;
1554
+ }
1555
+
1556
+ .calculator-section input:focus,
1557
+ .calculator-section select:focus {
1558
+ outline: none;
1559
+ border-color: var(--primary-color);
1560
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
1561
+ }
1562
+
1563
+ .calculator-section input::placeholder {
1564
+ color: var(--text-light);
1565
+ }
1566
+
1567
+ .calculator-section input[readonly] {
1568
+ background: var(--bg-subtle);
1569
+ color: var(--text-muted);
1570
+ }
1571
+
1572
+ /* Lifestyle Grid - 3x2 layout */
1573
+ .calculator-section .lifestyle-grid {
1574
+ display: grid;
1575
+ grid-template-columns: repeat(4, 1fr);
1576
+ gap: 1.25rem;
1577
+ }
1578
+
1579
+ /* Checkbox Group Inline */
1580
+ .calculator-section .checkbox-group {
1581
+ display: grid;
1582
+ grid-template-columns: repeat(2, 1fr);
1583
+ gap: 0.75rem;
1584
+ background: transparent;
1585
+ padding: 0;
1586
+ margin-top: 0.5rem;
1587
+ }
1588
+
1589
+ .calculator-section .checkbox-container {
1590
+ display: flex;
1591
+ align-items: center;
1592
+ gap: 0.5rem;
1593
+ font-size: 0.875rem;
1594
+ font-weight: 400;
1595
+ color: var(--text-main);
1596
+ cursor: pointer;
1597
+ }
1598
+
1599
+ .calculator-section .checkbox-container input[type="checkbox"] {
1600
+ width: 1rem;
1601
+ height: 1rem;
1602
+ accent-color: var(--primary-color);
1603
+ cursor: pointer;
1604
+ }
1605
+
1606
+ /* Form Actions */
1607
+ .calculator-section .form-actions {
1608
+ display: flex;
1609
+ justify-content: center;
1610
+ margin-top: 2rem;
1611
+ padding-top: 1.5rem;
1612
+ border-top: 1px solid var(--border-subtle);
1613
+ }
1614
+
1615
+ .calculator-section .btn-primary {
1616
+ background: #3B82F6;
1617
+ color: white;
1618
+ border: none;
1619
+ padding: 0.875rem 2rem;
1620
+ border-radius: 50px;
1621
+ font-size: 1rem;
1622
+ font-weight: 600;
1623
+ cursor: pointer;
1624
+ display: inline-flex;
1625
+ align-items: center;
1626
+ gap: 0.5rem;
1627
+ transition: all 0.3s ease;
1628
+ }
1629
+
1630
+ .calculator-section .btn-primary:hover {
1631
+ background: #2563EB;
1632
+ transform: translateY(-2px);
1633
+ box-shadow: 0 6px 20px rgba(59, 130, 246, 0.35);
1634
+ }
1635
+
1636
+ /* Responsive */
1637
+ @media (max-width: 992px) {
1638
+ .hero {
1639
+ grid-template-columns: 1fr;
1640
+ text-align: center;
1641
+ gap: 2rem;
1642
+ }
1643
+
1644
+ .hero-content {
1645
+ order: 2;
1646
+ }
1647
+
1648
+ .hero-image {
1649
+ order: 1;
1650
+ }
1651
+
1652
+ .hero p {
1653
+ margin: 0 auto 2.5rem;
1654
+ }
1655
+
1656
+ .steps-grid {
1657
+ grid-template-columns: repeat(2, 1fr);
1658
+ }
1659
+
1660
+ .testimonials-grid {
1661
+ grid-template-columns: repeat(2, 1fr);
1662
+ }
1663
+
1664
+ .contact-grid {
1665
+ grid-template-columns: repeat(2, 1fr);
1666
+ }
1667
+ }
1668
+
1669
+ @media (max-width: 768px) {
1670
+ .header-container {
1671
+ flex-direction: column;
1672
+ gap: 1.25rem;
1673
+ padding-bottom: 0.5rem;
1674
+ }
1675
+
1676
+ .nav-links {
1677
+ gap: 1rem;
1678
+ font-size: 0.9rem;
1679
+ }
1680
+
1681
+ .calculator-section .form-grid {
1682
+ grid-template-columns: 1fr;
1683
+ }
1684
+
1685
+ .calculator-section .lifestyle-grid {
1686
+ grid-template-columns: repeat(2, 1fr);
1687
+ }
1688
+
1689
+ .calculator-section .glass-panel {
1690
+ padding: 1.5rem;
1691
+ }
1692
+
1693
+ .trust-container {
1694
+ grid-template-columns: repeat(2, 1fr);
1695
+ }
1696
+
1697
+ .footer-container {
1698
+ grid-template-columns: 1fr;
1699
+ gap: 2rem;
1700
+ text-align: center;
1701
+ }
1702
+
1703
+ .hero h1 {
1704
+ font-size: 2.5rem;
1705
+ }
1706
+ }
1707
+
1708
+ @media (max-width: 480px) {
1709
+ .steps-grid {
1710
+ grid-template-columns: 1fr;
1711
+ }
1712
+
1713
+ .testimonials-grid {
1714
+ grid-template-columns: 1fr;
1715
+ }
1716
+
1717
+ .contact-grid {
1718
+ grid-template-columns: 1fr;
1719
+ }
1720
+
1721
+ .nav-links {
1722
+ flex-wrap: wrap;
1723
+ justify-content: center;
1724
+ }
1725
+
1726
+ .calculator-section .lifestyle-grid {
1727
+ grid-template-columns: 1fr;
1728
+ }
1729
+
1730
+ .calculator-section .glass-panel {
1731
+ padding: 1rem;
1732
+ }
1733
+
1734
+ .step-number {
1735
+ top: 10px;
1736
+ right: 10px;
1737
+ }
1738
+ }
1739
+
1740
+ /* Live Preview Styles */
1741
+ .live-preview-card {
1742
+ position: fixed;
1743
+ bottom: 2rem;
1744
+ right: 2rem;
1745
+ background: white;
1746
+ padding: 1.25rem;
1747
+ border-radius: 16px;
1748
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
1749
+ z-index: 100;
1750
+ width: 220px;
1751
+ transform: translateY(20px);
1752
+ opacity: 0;
1753
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1754
+ border: 1px solid #E2E8F0;
1755
+ pointer-events: none;
1756
+ }
1757
+
1758
+ .live-preview-card.visible {
1759
+ transform: translateY(0);
1760
+ opacity: 1;
1761
+ pointer-events: auto;
1762
+ }
1763
+
1764
+ .live-preview-header {
1765
+ display: flex;
1766
+ justify-content: space-between;
1767
+ align-items: center;
1768
+ font-size: 0.75rem;
1769
+ color: #64748B;
1770
+ margin-bottom: 0.5rem;
1771
+ font-weight: 600;
1772
+ text-transform: uppercase;
1773
+ letter-spacing: 0.05em;
1774
+ }
1775
+
1776
+ .live-risk-score {
1777
+ font-size: 2.25rem;
1778
+ font-weight: 800;
1779
+ color: #1E293B;
1780
+ line-height: 1;
1781
+ margin-bottom: 0.25rem;
1782
+ }
1783
+
1784
+ .live-risk-label {
1785
+ font-size: 0.875rem;
1786
+ font-weight: 500;
1787
+ color: #64748B;
1788
+ }
1789
+
1790
+ /* Range Sliders Removed - Logic and UI cleaned up as requested */
app/templates/calculate.html ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Risk Assessment | HeartCare</title>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap"
11
+ rel="stylesheet">
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
13
+ <link rel="stylesheet" href="/static/style.css">
14
+ <link rel="icon"
15
+ href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>❤️</text></svg>">
16
+ </head>
17
+
18
+ <body>
19
+ <!-- Background Effect -->
20
+ <div class="background-glow"></div>
21
+
22
+ <!-- HEADER -->
23
+ <header class="site-header">
24
+ <div class="header-container">
25
+ <!-- Logo -->
26
+ <div class="brand">
27
+ <span class="heart-icon">❤️</span>
28
+ <span class="brand-name">HeartCare</span>
29
+ </div>
30
+
31
+ <!-- Navigation -->
32
+ <nav class="nav-links">
33
+ <a href="/">Home</a>
34
+ <a href="/#about">About</a>
35
+ <a href="/#contact">Contact</a>
36
+ <a href="/calculate" class="btn-start active">Start Assessment</a>
37
+ </nav>
38
+ </div>
39
+ </header>
40
+
41
+ <main class="container">
42
+
43
+ <!-- CALCULATOR SECTION -->
44
+ <section id="calculate" class="calculator-section">
45
+ <div class="section-title">
46
+ <h2>Assess Your Risk</h2>
47
+ <p>Fill in the details below for an instant Artificial Intelligence analysis.</p>
48
+ </div>
49
+
50
+ <form id="predictionForm" class="glass-panel">
51
+ <div class="form-grid">
52
+ <!-- Personal Details -->
53
+ <div class="form-section">
54
+ <h3><i class="fa-regular fa-id-card"></i> Personal Profile</h3>
55
+
56
+ <div class="input-group">
57
+ <label for="Age">Age</label>
58
+ <input type="number" id="Age" name="Age" min="18" max="100" required placeholder="e.g. 45">
59
+ </div>
60
+
61
+ <div class="input-group">
62
+ <label for="Sex">Biological Sex</label>
63
+ <select id="Sex" name="Sex" required>
64
+ <option value="" disabled selected>Select...</option>
65
+ <option value="0">Female</option>
66
+ <option value="1">Male</option>
67
+ </select>
68
+ </div>
69
+
70
+ <div class="input-group">
71
+ <label for="Height">Height (cm)</label>
72
+ <input type="number" id="Height" name="Height" min="100" max="250" required
73
+ placeholder="e.g. 175">
74
+ </div>
75
+
76
+ <div class="input-group">
77
+ <label for="Weight">Weight (kg)</label>
78
+ <input type="number" id="Weight" name="Weight" min="30" max="200" required
79
+ placeholder="e.g. 70">
80
+ </div>
81
+
82
+ <div class="input-group">
83
+ <label for="BMI">BMI (Auto-calculated)</label>
84
+ <input type="number" id="BMI" name="BMI" readonly placeholder="---">
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Health History -->
89
+ <div class="form-section">
90
+ <h3><i class="fa-solid fa-notes-medical"></i> Medical History</h3>
91
+
92
+ <div class="input-group">
93
+ <label for="General_Health">Self-Rated Health</label>
94
+ <select id="General_Health" name="General_Health" required>
95
+ <option value="Excellent">Excellent</option>
96
+ <option value="Very_Good">Very Good</option>
97
+ <option value="Good">Good</option>
98
+ <option value="Fair">Fair</option>
99
+ <option value="Poor">Poor</option>
100
+ </select>
101
+ </div>
102
+
103
+ <div class="input-group">
104
+ <label for="Checkup">Last Checkup</label>
105
+ <select id="Checkup" name="Checkup" required>
106
+ <option value="Within 1 year">Within 1 year</option>
107
+ <option value="1-2 years">1-2 years</option>
108
+ <option value="2-5 years">2-5 years</option>
109
+ <option value="5+ years">5+ years</option>
110
+ <option value="Never">Never</option>
111
+ </select>
112
+ </div>
113
+
114
+ <div class="input-group">
115
+ <label for="Diabetes">Diabetes Status</label>
116
+ <select id="Diabetes" name="Diabetes" required>
117
+ <option value="No">No</option>
118
+ <option value="Borderline">Borderline</option>
119
+ <option value="During Pregnancy">During Pregnancy</option>
120
+ <option value="Yes">Yes</option>
121
+ </select>
122
+ </div>
123
+
124
+ <div class="checkbox-group">
125
+ <label class="checkbox-container">
126
+ <input type="checkbox" id="Skin_Cancer" name="Skin_Cancer">
127
+ <span class="checkmark"></span>
128
+ Skin Cancer
129
+ </label>
130
+ <label class="checkbox-container">
131
+ <input type="checkbox" id="Other_Cancer" name="Other_Cancer">
132
+ <span class="checkmark"></span>
133
+ Other Cancer
134
+ </label>
135
+ <label class="checkbox-container">
136
+ <input type="checkbox" id="Depression" name="Depression">
137
+ <span class="checkmark"></span>
138
+ Depression
139
+ </label>
140
+ <label class="checkbox-container">
141
+ <input type="checkbox" id="Arthritis" name="Arthritis">
142
+ <span class="checkmark"></span>
143
+ Arthritis
144
+ </label>
145
+ </div>
146
+ </div>
147
+
148
+ <!-- Lifestyle -->
149
+ <div class="form-section full-width">
150
+ <h3><i class="fa-solid fa-person-running"></i> Lifestyle Factors</h3>
151
+
152
+ <div class="lifestyle-grid">
153
+ <div class="input-group">
154
+ <label for="Exercise">Regular Exercise</label>
155
+ <select id="Exercise" name="Exercise" required>
156
+ <option value="0">No</option>
157
+ <option value="1">Yes</option>
158
+ </select>
159
+ </div>
160
+
161
+ <div class="input-group">
162
+ <label for="Smoking">Smoking History</label>
163
+ <select id="Smoking" name="Smoking" required>
164
+ <option value="0">No</option>
165
+ <option value="1">Yes</option>
166
+ </select>
167
+ </div>
168
+
169
+ <div class="input-group">
170
+ <label for="Alcohol">Alcohol (Days/Month)</label>
171
+ <input type="number" id="Alcohol" name="Alcohol" min="0" max="30" value="0">
172
+ </div>
173
+
174
+ <div class="input-group">
175
+ <label for="Fruit">Fruit (Servings/Month)</label>
176
+ <input type="number" id="Fruit" name="Fruit" min="0" max="100" value="0">
177
+ </div>
178
+
179
+ <div class="input-group">
180
+ <label for="Green_Vegetables">Green Veggies (Servings/Month)</label>
181
+ <input type="number" id="Green_Vegetables" name="Green_Vegetables" min="0" max="100"
182
+ value="0">
183
+ </div>
184
+
185
+ <div class="input-group">
186
+ <label for="Fried_Potato">Fried Food (Servings/Month)</label>
187
+ <input type="number" id="Fried_Potato" name="Fried_Potato" min="0" max="100" value="0">
188
+ </div>
189
+ </div>
190
+ </div>
191
+ </div>
192
+
193
+ <div class="form-actions">
194
+ <button type="submit" class="btn-primary">
195
+ <span>Analyze Risk Profile</span>
196
+ <i class="fa-solid fa-arrow-right"></i>
197
+ </button>
198
+ </div>
199
+ </form>
200
+ </section>
201
+
202
+ </main>
203
+
204
+ <!-- FOOTER -->
205
+ <footer class="footer">
206
+ <div class="footer-container">
207
+ <div class="footer-about">
208
+ <h3>HeartCare</h3>
209
+ <p>
210
+ HeartCare is a heart disease risk prediction platform created to promote early
211
+ detection, awareness, and prevention of cardiovascular diseases through simple
212
+ online assessment.
213
+ </p>
214
+ </div>
215
+
216
+ <div class="footer-links">
217
+ <h4>Quick Links</h4>
218
+ <a href="/">Home</a>
219
+ <a href="/#about">About</a>
220
+ <a href="/#contact">Contact</a>
221
+ <a href="/calculate">Start Calculation</a>
222
+ </div>
223
+
224
+ <div class="footer-contact">
225
+ <h4>Contact Info</h4>
226
+ <p><strong>Email:</strong> support@heartcare.com</p>
227
+ <p><strong>Phone:</strong> +91 9876543210</p>
228
+ <p><strong>Location:</strong> Nanded, Maharashtra, India</p>
229
+ </div>
230
+ </div>
231
+
232
+ <div class="footer-bottom">
233
+ <p>&copy; 2025 HeartCare | AI Heart Disease Risk Prediction System | All Rights Reserved</p>
234
+ </div>
235
+ </footer>
236
+
237
+ <!-- Live Risk Preview -->
238
+ <div id="livePreview" class="live-preview-card">
239
+ <div class="live-preview-header">
240
+ <span>Live Risk Preview</span>
241
+ <i class="fa-solid fa-heart-pulse"></i>
242
+ </div>
243
+ <div class="live-risk-score" id="liveRiskScore">--%</div>
244
+ <div class="live-risk-label" id="liveRiskLabel">Enter details...</div>
245
+ </div>
246
+
247
+ <!-- Modals -->
248
+ <div id="resultModal" class="modal">
249
+ <div class="modal-content">
250
+ <span class="close-modal" onclick="closeModal()">&times;</span>
251
+ <div class="result-header">
252
+ <div id="riskIcon" class="risk-icon"></div>
253
+ <h2 id="riskLevel">High Risk</h2>
254
+ </div>
255
+ <div class="result-body">
256
+ <p>Based on your health indicators, our AI model estimates your cardiovascular disease risk
257
+ probability as:</p>
258
+ <div class="probability-meter">
259
+ <div id="probFill" class="prob-fill"></div>
260
+ <span id="probValue">0%</span>
261
+ </div>
262
+ <p class="disclaimer"><strong>Disclaimer:</strong> This tool provides an estimate based on
263
+ statistical models. It is not a medical diagnosis. Please consult a cardiologist for a proper
264
+ evaluation.</p>
265
+ </div>
266
+ <div class="modal-actions">
267
+ <button class="btn-cancel" onclick="closeModal()">Close Result</button>
268
+ <button type="button" onclick="goToRecommendations()" class="view-btn">
269
+ View Recommendations
270
+ </button>
271
+ </div>
272
+ </div>
273
+ </div>
274
+
275
+ <!-- SCRIPTS -->
276
+ <script src="/static/script.js"></script>
277
+ </body>
278
+
279
+ </html>
app/templates/index.html ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>HeartCare | Advanced Heart Health Assessment</title>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link
11
+ href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Outfit:wght@300;400;500;600;700;800&display=swap"
12
+ rel="stylesheet">
13
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
14
+ <link rel="stylesheet" href="/static/style.css">
15
+ <link rel="icon"
16
+ href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>❤️</text></svg>">
17
+ <script>
18
+ (function () { if (!window.chatbase || window.chatbase("getState") !== "initialized") { window.chatbase = (...arguments) => { if (!window.chatbase.q) { window.chatbase.q = [] } window.chatbase.q.push(arguments) }; window.chatbase = new Proxy(window.chatbase, { get(target, prop) { if (prop === "q") { return target.q } return (...args) => target(prop, ...args) } }) } const onLoad = function () { const script = document.createElement("script"); script.src = "https://www.chatbase.co/embed.min.js"; script.id = "AS3pcxeyHl1Kl0m0BEkAf"; script.domain = "www.chatbase.co"; document.body.appendChild(script) }; if (document.readyState === "complete") { onLoad() } else { window.addEventListener("load", onLoad) } })();
19
+ </script>
20
+ </head>
21
+
22
+ <body>
23
+ <!-- Background Effect -->
24
+ <div class="background-glow"></div>
25
+
26
+ <!-- HEADER -->
27
+ <header class="site-header">
28
+ <div class="header-container">
29
+ <div class="brand">
30
+ <a href="/" class="brand-link" style="text-decoration: none; color: var(--text-main);">
31
+ <span class="heart-icon">❤️</span>
32
+ <span class="brand-name">HeartCare</span>
33
+ </a>
34
+ </div>
35
+ <nav class="nav-links">
36
+ <a href="#home">Home</a>
37
+ <a href="#about">About</a>
38
+ <a href="#contact">Contact</a>
39
+ <a href="/calculate" class="btn-start">Start Assessment</a>
40
+ </nav>
41
+ </div>
42
+ </header>
43
+
44
+ <main class="container">
45
+
46
+ <!-- HERO SECTION -->
47
+ <section id="home" class="hero">
48
+ <div class="hero-content" style="animation: fadeInUp 0.8s ease-out;">
49
+ <div class="badge-new">
50
+ <i class="fa-solid fa-sparkles"></i> New AI-Powered Analysis
51
+ </div>
52
+ <h1>Your Heart Health, <br> <span class="text-gradient">Understood in Minutes</span></h1>
53
+ <p>
54
+ Experience the future of preventive cardiology. Our advanced AI assesses your risk with clinical
55
+ precision and provides personalized, actionable insights—all from the comfort of your home.
56
+ </p>
57
+ <div class="hero-buttons">
58
+ <a href="/calculate" class="hero-btn primary-btn">
59
+ Check Your Risk Now <i class="fa-solid fa-arrow-right"></i>
60
+ </a>
61
+ <a href="#how-it-works" class="hero-btn secondary-btn">
62
+ How It Works <i class="fa-solid fa-circle-play"></i>
63
+ </a>
64
+ </div>
65
+ <div class="hero-trust">
66
+ <span><i class="fa-solid fa-user-shield"></i> 100% Private & Secure</span>
67
+ <span><i class="fa-solid fa-check-circle"></i> Clinically Validated Model</span>
68
+ </div>
69
+ </div>
70
+ <div class="hero-image" style="animation: fadeInUp 1s ease-out;">
71
+ <img src="/static/images/heart.png" alt="3D Heart Illustration" class="floating-heart">
72
+ </div>
73
+ </section>
74
+
75
+ <!-- TRUST INDICATORS -->
76
+ <section class="trust-section">
77
+ <div class="trust-container">
78
+ <div class="trust-stat">
79
+ <div class="stat-number count-up" data-target="10000">10,000+</div>
80
+ <div class="stat-label">Assessments Completed</div>
81
+ </div>
82
+ <div class="trust-stat">
83
+ <div class="stat-number">98%</div>
84
+ <div class="stat-label">User Satisfaction</div>
85
+ </div>
86
+ <div class="trust-stat">
87
+ <div class="stat-number">24/7</div>
88
+ <div class="stat-label">AI Availability</div>
89
+ </div>
90
+ <div class="trust-stat">
91
+ <div class="stat-number">100%</div>
92
+ <div class="stat-label">Data Privacy</div>
93
+ </div>
94
+ </div>
95
+ </section>
96
+
97
+ <!-- HOW IT WORKS -->
98
+ <section id="how-it-works" class="how-it-works">
99
+ <div class="section-title">
100
+ <div class="badge-new" style="margin-bottom: 1rem;">Simple Process</div>
101
+ <h2>How It Works</h2>
102
+ <p>Get your personalized heart health assessment in three simple steps</p>
103
+ </div>
104
+ <div class="steps-grid">
105
+ <div class="step-card">
106
+ <div class="step-number">1</div>
107
+ <div class="step-icon"><i class="fa-solid fa-clipboard-list"></i></div>
108
+ <h3>Fill Health Profile</h3>
109
+ <p>Answer a few questions about your age, lifestyle, medical history, and current health status.
110
+ Takes only 2-3 minutes.</p>
111
+ </div>
112
+ <div class="step-card">
113
+ <div class="step-number">2</div>
114
+ <div class="step-icon"><i class="fa-solid fa-brain"></i></div>
115
+ <h3>AI Analysis</h3>
116
+ <p>Our advanced machine learning model analyzes your data against thousands of clinical patterns to
117
+ assess your risk.</p>
118
+ </div>
119
+ <div class="step-card">
120
+ <div class="step-number">3</div>
121
+ <div class="step-icon"><i class="fa-solid fa-file-medical"></i></div>
122
+ <h3>Get Recommendations</h3>
123
+ <p>Receive a detailed report with your risk level, personalized diet plans, exercise tips, and
124
+ nearby cardiologist suggestions.</p>
125
+ </div>
126
+ </div>
127
+ </section>
128
+
129
+ <!-- ABOUT SECTION -->
130
+ <section id="about"
131
+ style="background: linear-gradient(180deg, var(--bg-subtle) 0%, white 100%); padding: 6rem 0;">
132
+ <div class="section-title">
133
+ <div class="badge-new" style="margin-bottom: 1rem;">Our Mission</div>
134
+ <h2>Why Choose HeartCare?</h2>
135
+ <p>Empowering you with early detection and preventive knowledge through advanced technology.</p>
136
+ </div>
137
+
138
+ <div class="about-grid">
139
+ <div class="about-card">
140
+ <i class="fa-solid fa-heart-pulse"></i>
141
+ <h3>AI Risk Analysis</h3>
142
+ <p>Advanced machine learning algorithms assess multiple health parameters to estimate your risk
143
+ profile with high accuracy.</p>
144
+ </div>
145
+
146
+ <div class="about-card">
147
+ <i class="fa-solid fa-chart-line"></i>
148
+ <h3>Data-Driven Accuracy</h3>
149
+ <p>Trained on thousands of clinical records to ensure reliable and statistical predictions aligned
150
+ with medical research.</p>
151
+ </div>
152
+
153
+ <div class="about-card">
154
+ <i class="fa-solid fa-user-shield"></i>
155
+ <h3>Privacy First</h3>
156
+ <p>Your health data is processed securely and is never stored permanently on our servers. Your
157
+ privacy is our priority.</p>
158
+ </div>
159
+
160
+ <div class="about-card">
161
+ <i class="fa-solid fa-user-doctor"></i>
162
+ <h3>Preventive Care</h3>
163
+ <p>Get actionable recommendations and alerts to consult specialists when necessary, helping you stay
164
+ ahead of health issues.</p>
165
+ </div>
166
+ </div>
167
+ </section>
168
+
169
+ <!-- TESTIMONIALS -->
170
+ <section class="testimonials">
171
+ <div class="section-title">
172
+ <div class="badge-new"
173
+ style="background: rgba(16, 185, 129, 0.1); color: var(--success); border-color: rgba(16, 185, 129, 0.2); margin-bottom: 1rem;">
174
+ <i class="fa-solid fa-star"></i> Success Stories
175
+ </div>
176
+ <h2>What Our Users Say</h2>
177
+ <p>Real stories from people who took control of their heart health</p>
178
+ </div>
179
+ <div class="testimonials-grid">
180
+ <div class="testimonial-card">
181
+ <div class="quote-icon"><i class="fa-solid fa-quote-left"></i></div>
182
+ <p class="testimonial-text">"HeartCare's assessment helped me identify risk factors I wasn't aware
183
+ of. I made lifestyle changes and my doctor confirmed significant improvement in my health
184
+ markers."</p>
185
+ <div class="testimonial-author">
186
+ <div class="author-avatar" style="background: #e0e7ff; color: #4338ca;">RS</div>
187
+ <div>
188
+ <div class="author-name">Rajesh Sharma <i class="fa-solid fa-circle-check"
189
+ style="color: var(--success); font-size: 0.8em;" title="Verified User"></i></div>
190
+ <div class="author-role">Software Engineer, 45</div>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ <div class="testimonial-card">
195
+ <div class="quote-icon"><i class="fa-solid fa-quote-left"></i></div>
196
+ <p class="testimonial-text">"As someone with a family history of heart disease, this tool gave me
197
+ peace of mind. The recommendations were practical and the doctor suggestions were very helpful."
198
+ </p>
199
+ <div class="testimonial-author">
200
+ <div class="author-avatar" style="background: #d1fae5; color: #047857;">PD</div>
201
+ <div>
202
+ <div class="author-name">Priya Deshmukh <i class="fa-solid fa-circle-check"
203
+ style="color: var(--success); font-size: 0.8em;" title="Verified User"></i></div>
204
+ <div class="author-role">Teacher, 38</div>
205
+ </div>
206
+ </div>
207
+ </div>
208
+ <div class="testimonial-card">
209
+ <div class="quote-icon"><i class="fa-solid fa-quote-left"></i></div>
210
+ <p class="testimonial-text">"The detailed report with diet and exercise plans was exactly what I
211
+ needed. It's like having a personal health advisor available 24/7."</p>
212
+ <div class="testimonial-author">
213
+ <div class="author-avatar" style="background: #fee2e2; color: #b91c1c;">AK</div>
214
+ <div>
215
+ <div class="author-name">Amit Kulkarni <i class="fa-solid fa-circle-check"
216
+ style="color: var(--success); font-size: 0.8em;" title="Verified User"></i></div>
217
+ <div class="author-role">Business Owner, 52</div>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ </div>
222
+ </section>
223
+
224
+ <!-- CONTACT SECTION -->
225
+ <section id="contact" style="background: var(--bg-subtle); padding: 6rem 0;">
226
+ <div class="section-title">
227
+ <div class="badge-new" style="margin-bottom: 1rem;">Support</div>
228
+ <h2>Get in Touch</h2>
229
+ <p>We are always available to help you with your queries and support.</p>
230
+ </div>
231
+
232
+ <div class="contact-grid">
233
+ <div class="contact-card">
234
+ <i class="fa-solid fa-envelope"></i>
235
+ <h3>Email Us</h3>
236
+ <p>support@heartcare.com</p>
237
+ <p class="small text-muted">Response within 24 hours</p>
238
+ </div>
239
+
240
+ <div class="contact-card">
241
+ <i class="fa-solid fa-phone"></i>
242
+ <h3>Call Us</h3>
243
+ <p>+91 9876543210</p>
244
+ <p class="small text-muted">Mon-Fri, 9am - 6pm</p>
245
+ </div>
246
+
247
+ <div class="contact-card">
248
+ <i class="fa-solid fa-location-dot"></i>
249
+ <h3>Visit Us</h3>
250
+ <p>Nanded, Maharashtra, India</p>
251
+ <p class="small text-muted">Main Office</p>
252
+ </div>
253
+
254
+ <div class="contact-card">
255
+ <i class="fa-solid fa-headset"></i>
256
+ <h3>Live Support</h3>
257
+ <p>Chat with our AI Agent</p>
258
+ <p class="small text-muted">Available 24/7</p>
259
+ </div>
260
+ </div>
261
+ </section>
262
+
263
+ </main>
264
+
265
+ <!-- FOOTER -->
266
+ <footer class="footer">
267
+ <div class="footer-container">
268
+ <div class="footer-about">
269
+ <h3>HeartCare</h3>
270
+ <p>
271
+ HeartCare is a heart disease risk prediction platform created to promote early
272
+ detection, awareness, and prevention of cardiovascular diseases through simple
273
+ online assessment.
274
+ </p>
275
+ </div>
276
+
277
+ <div class="footer-links">
278
+ <h4>Quick Links</h4>
279
+ <a href="#home">Home</a>
280
+ <a href="#about">About</a>
281
+ <a href="#contact">Contact</a>
282
+ <a href="/calculate">Start Calculation</a>
283
+ </div>
284
+
285
+ <div class="footer-contact">
286
+ <h4>Contact Info</h4>
287
+ <p><i class="fa-solid fa-envelope" style="margin-right: 8px; color: var(--primary-color);"></i>
288
+ <strong>Email:</strong> support@heartcare.com
289
+ </p>
290
+ <p><i class="fa-solid fa-phone" style="margin-right: 8px; color: var(--primary-color);"></i>
291
+ <strong>Phone:</strong> +91 9876543210
292
+ </p>
293
+ <p><i class="fa-solid fa-location-dot" style="margin-right: 8px; color: var(--primary-color);"></i>
294
+ <strong>Location:</strong> Nanded, Maharashtra, India
295
+ </p>
296
+ </div>
297
+ </div>
298
+
299
+ <div class="footer-bottom">
300
+ <p>&copy; 2025 HeartCare | AI Heart Disease Risk Prediction System | All Rights Reserved</p>
301
+ </div>
302
+ </footer>
303
+
304
+ <script src="/static/script.js"></script>
305
+ </body>
306
+
307
+ </html>
app/templates/recommendation.html ADDED
@@ -0,0 +1,1105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
7
+ <title>Your Health Report | HeartCare</title>
8
+
9
+ <!-- Fonts -->
10
+ <link rel="preconnect" href="https://fonts.googleapis.com">
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap"
13
+ rel="stylesheet">
14
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
15
+
16
+ <!-- Main Styles -->
17
+ <link rel="stylesheet" href="/static/style.css">
18
+
19
+ <!-- Libraries -->
20
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
21
+ <script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
22
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
23
+ <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
24
+
25
+ <style>
26
+ /* Recommendation-specific styles */
27
+ .recommendation-page {
28
+ padding: 2rem 0;
29
+ }
30
+
31
+ .page-header {
32
+ text-align: center;
33
+ margin-bottom: 3rem;
34
+ animation: fadeInDown 0.8s ease-out;
35
+ }
36
+
37
+ .page-header h1 {
38
+ font-size: 2.5rem;
39
+ font-weight: 800;
40
+ color: var(--text-main);
41
+ margin-bottom: 0.5rem;
42
+ }
43
+
44
+ .page-header p {
45
+ color: var(--text-muted);
46
+ font-size: 1.125rem;
47
+ }
48
+
49
+ .report-grid {
50
+ display: grid;
51
+ grid-template-columns: 1fr 420px;
52
+ gap: 2rem;
53
+ margin-bottom: 2rem;
54
+ }
55
+
56
+ @media (max-width: 980px) {
57
+ .report-grid {
58
+ grid-template-columns: 1fr;
59
+ }
60
+
61
+ .sidebar {
62
+ order: -1;
63
+ }
64
+ }
65
+
66
+ /* Risk Level Selector */
67
+ .risk-selector {
68
+ background: white;
69
+ padding: 2rem;
70
+ border-radius: var(--radius-xl);
71
+ box-shadow: var(--shadow-md);
72
+ margin-bottom: 2rem;
73
+ }
74
+
75
+ .risk-selector h3 {
76
+ font-size: 1.25rem;
77
+ font-weight: 700;
78
+ margin-bottom: 1rem;
79
+ color: var(--text-main);
80
+ }
81
+
82
+ .risk-pills {
83
+ display: grid;
84
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
85
+ gap: 0.75rem;
86
+ }
87
+
88
+ .risk-pill {
89
+ padding: 1rem;
90
+ border-radius: var(--radius-lg);
91
+ color: white;
92
+ text-align: center;
93
+ cursor: pointer;
94
+ font-weight: 600;
95
+ transition: all 0.3s ease;
96
+ border: 2px solid transparent;
97
+ }
98
+
99
+ .risk-pill:hover {
100
+ transform: translateY(-2px);
101
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
102
+ }
103
+
104
+ .risk-pill.active {
105
+ border-color: var(--text-main);
106
+ box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.1);
107
+ }
108
+
109
+ .risk-pill.vlow {
110
+ background: linear-gradient(135deg, #10B981, #059669);
111
+ }
112
+
113
+ .risk-pill.low {
114
+ background: linear-gradient(135deg, #34D399, #10B981);
115
+ }
116
+
117
+ .risk-pill.mod {
118
+ background: linear-gradient(135deg, #FBBF24, #F59E0B);
119
+ }
120
+
121
+ .risk-pill.high {
122
+ background: linear-gradient(135deg, #FB923C, #F97316);
123
+ }
124
+
125
+ .risk-pill.vh {
126
+ background: linear-gradient(135deg, #EF4444, #DC2626);
127
+ }
128
+
129
+ .risk-pill small {
130
+ display: block;
131
+ font-size: 0.75rem;
132
+ opacity: 0.9;
133
+ margin-top: 0.25rem;
134
+ }
135
+
136
+ /* Main Report Card */
137
+ .report-card {
138
+ background: white;
139
+ padding: 2.5rem;
140
+ border-radius: var(--radius-xl);
141
+ box-shadow: var(--shadow-lg);
142
+ border-left: 6px solid var(--primary-color);
143
+ animation: fadeInUp 0.8s ease-out;
144
+ }
145
+
146
+ .report-card.highlight {
147
+ box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.1), var(--shadow-lg);
148
+ }
149
+
150
+ .report-title {
151
+ font-size: 1.75rem;
152
+ font-weight: 800;
153
+ margin-bottom: 0.5rem;
154
+ color: var(--text-main);
155
+ }
156
+
157
+ .report-subtitle {
158
+ color: var(--text-muted);
159
+ margin-bottom: 2rem;
160
+ font-size: 1rem;
161
+ }
162
+
163
+ .recommendations-grid {
164
+ display: grid;
165
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
166
+ gap: 1.5rem;
167
+ margin-top: 2rem;
168
+ }
169
+
170
+ .recommendation-card {
171
+ background: linear-gradient(135deg, #F8FAFC 0%, #F1F5F9 100%);
172
+ padding: 1.5rem;
173
+ border-radius: var(--radius-lg);
174
+ border: 1px solid var(--border-color);
175
+ transition: all 0.3s ease;
176
+ }
177
+
178
+ .recommendation-card:hover {
179
+ transform: translateY(-4px);
180
+ box-shadow: var(--shadow-md);
181
+ }
182
+
183
+ .recommendation-card h4 {
184
+ font-size: 1.125rem;
185
+ font-weight: 700;
186
+ margin-bottom: 1rem;
187
+ color: var(--primary-color);
188
+ display: flex;
189
+ align-items: center;
190
+ gap: 0.5rem;
191
+ }
192
+
193
+ .recommendation-card ul {
194
+ list-style: none;
195
+ padding: 0;
196
+ margin: 0;
197
+ }
198
+
199
+ .recommendation-card li {
200
+ padding: 0.5rem 0;
201
+ color: var(--text-main);
202
+ display: flex;
203
+ align-items: flex-start;
204
+ gap: 0.5rem;
205
+ }
206
+
207
+ .recommendation-card li::before {
208
+ content: "✓";
209
+ color: var(--primary-color);
210
+ font-weight: 700;
211
+ flex-shrink: 0;
212
+ }
213
+
214
+ /* Sidebar */
215
+ .sidebar .card {
216
+ background: white;
217
+ padding: 1.5rem;
218
+ border-radius: var(--radius-xl);
219
+ box-shadow: var(--shadow-md);
220
+ margin-bottom: 1.5rem;
221
+ }
222
+
223
+ .sidebar h3 {
224
+ font-size: 1.125rem;
225
+ font-weight: 700;
226
+ margin-bottom: 1rem;
227
+ color: var(--text-main);
228
+ }
229
+
230
+ /* Severity Meter */
231
+ .meter-container {
232
+ display: flex;
233
+ flex-direction: column;
234
+ align-items: center;
235
+ padding: 1.5rem 0;
236
+ }
237
+
238
+ .meter {
239
+ width: 160px;
240
+ height: 160px;
241
+ position: relative;
242
+ }
243
+
244
+ .meter svg {
245
+ transform: rotate(-90deg);
246
+ }
247
+
248
+ .meter .center {
249
+ position: absolute;
250
+ inset: 0;
251
+ display: flex;
252
+ align-items: center;
253
+ justify-content: center;
254
+ flex-direction: column;
255
+ }
256
+
257
+ .meter .percent {
258
+ font-size: 2rem;
259
+ font-weight: 800;
260
+ color: var(--primary-color);
261
+ }
262
+
263
+ .meter .label {
264
+ font-size: 0.875rem;
265
+ color: var(--text-muted);
266
+ margin-top: 0.25rem;
267
+ }
268
+
269
+ /* Chart */
270
+ .chart-container {
271
+ height: 240px;
272
+ margin-top: 1rem;
273
+ }
274
+
275
+ /* Doctor Cards */
276
+ .doctor-list {
277
+ display: flex;
278
+ flex-direction: column;
279
+ gap: 1rem;
280
+ }
281
+
282
+ .doctor-card {
283
+ padding: 1.25rem;
284
+ border-radius: var(--radius-lg);
285
+ background: linear-gradient(135deg, #F8FAFC 0%, #F1F5F9 100%);
286
+ border: 1px solid var(--border-color);
287
+ transition: all 0.3s ease;
288
+ }
289
+
290
+ .doctor-card:hover {
291
+ transform: translateX(4px);
292
+ box-shadow: var(--shadow-sm);
293
+ }
294
+
295
+ .doctor-name {
296
+ font-weight: 700;
297
+ color: var(--text-main);
298
+ margin-bottom: 0.5rem;
299
+ }
300
+
301
+ .doctor-info {
302
+ font-size: 0.875rem;
303
+ color: var(--text-muted);
304
+ margin-bottom: 0.75rem;
305
+ }
306
+
307
+ .doctor-actions {
308
+ display: flex;
309
+ gap: 0.5rem;
310
+ margin-top: 0.75rem;
311
+ }
312
+
313
+ .btn-small {
314
+ padding: 0.5rem 1rem;
315
+ border-radius: 0.5rem;
316
+ font-size: 0.875rem;
317
+ font-weight: 500;
318
+ border: none;
319
+ cursor: pointer;
320
+ transition: all 0.2s ease;
321
+ }
322
+
323
+ .btn-outline {
324
+ background: white;
325
+ border: 1px solid var(--border-color);
326
+ color: var(--text-main);
327
+ }
328
+
329
+ .btn-outline:hover {
330
+ background: var(--bg-body);
331
+ }
332
+
333
+ .btn-primary-small {
334
+ background: var(--primary-color);
335
+ color: white;
336
+ }
337
+
338
+ .btn-primary-small:hover {
339
+ background: var(--primary-hover);
340
+ }
341
+
342
+ /* Map */
343
+ #map {
344
+ height: 280px;
345
+ border-radius: var(--radius-lg);
346
+ overflow: hidden;
347
+ margin: 1rem 0;
348
+ }
349
+
350
+ /* Action Buttons */
351
+ .action-section {
352
+ background: white;
353
+ padding: 2rem;
354
+ border-radius: var(--radius-xl);
355
+ box-shadow: var(--shadow-md);
356
+ margin-top: 2rem;
357
+ }
358
+
359
+ .action-section h3 {
360
+ font-size: 1.25rem;
361
+ font-weight: 700;
362
+ margin-bottom: 1.5rem;
363
+ color: var(--text-main);
364
+ }
365
+
366
+ .action-buttons {
367
+ display: flex;
368
+ flex-wrap: wrap;
369
+ gap: 1rem;
370
+ }
371
+
372
+ .action-btn {
373
+ padding: 0.875rem 1.75rem;
374
+ border-radius: 100px;
375
+ font-weight: 600;
376
+ border: none;
377
+ cursor: pointer;
378
+ transition: all 0.3s ease;
379
+ display: inline-flex;
380
+ align-items: center;
381
+ gap: 0.5rem;
382
+ }
383
+
384
+ .action-btn-primary {
385
+ background: var(--primary-color);
386
+ color: white;
387
+ box-shadow: 0 4px 6px rgba(79, 70, 229, 0.2);
388
+ }
389
+
390
+ .action-btn-primary:hover {
391
+ background: var(--primary-hover);
392
+ transform: translateY(-2px);
393
+ box-shadow: 0 8px 12px rgba(79, 70, 229, 0.3);
394
+ }
395
+
396
+ /* Priority Factors */
397
+ .priority-section {
398
+ margin-bottom: 2rem;
399
+ animation: fadeInUp 0.9s ease-out;
400
+ }
401
+
402
+ .priority-grid {
403
+ display: grid;
404
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
405
+ gap: 1rem;
406
+ }
407
+
408
+ .priority-card {
409
+ background: #FEF2F2;
410
+ border: 1px solid #FCA5A5;
411
+ padding: 1.25rem;
412
+ border-radius: var(--radius-lg);
413
+ position: relative;
414
+ }
415
+
416
+ .priority-card.warning {
417
+ background: #FFFBEB;
418
+ border-color: #FCD34D;
419
+ }
420
+
421
+ .priority-header {
422
+ display: flex;
423
+ justify-content: space-between;
424
+ align-items: center;
425
+ margin-bottom: 0.5rem;
426
+ }
427
+
428
+ .priority-title {
429
+ font-weight: 700;
430
+ color: #991B1B;
431
+ display: flex;
432
+ align-items: center;
433
+ gap: 0.5rem;
434
+ }
435
+
436
+ .priority-card.warning .priority-title {
437
+ color: #92400E;
438
+ }
439
+
440
+ .priority-value {
441
+ font-weight: 800;
442
+ font-size: 1.1rem;
443
+ }
444
+
445
+ .priority-desc {
446
+ font-size: 0.9rem;
447
+ color: var(--text-main);
448
+ margin-bottom: 0.5rem;
449
+ }
450
+
451
+ .info-tooltip {
452
+ display: inline-block;
453
+ cursor: pointer;
454
+ color: var(--primary-color);
455
+ margin-left: 0.5rem;
456
+ }
457
+
458
+ .explanation-box {
459
+ background: white;
460
+ padding: 0.75rem;
461
+ border-radius: var(--radius-md);
462
+ font-size: 0.85rem;
463
+ margin-top: 0.5rem;
464
+ border-left: 3px solid var(--primary-color);
465
+ display: none;
466
+ }
467
+
468
+ .explanation-box.visible {
469
+ display: block;
470
+ animation: fadeInDown 0.3s ease-out;
471
+ }
472
+
473
+ .action-btn-secondary {
474
+ background: white;
475
+ color: var(--text-main);
476
+ border: 1px solid var(--border-color);
477
+ }
478
+
479
+ .action-btn-secondary:hover {
480
+ background: var(--bg-body);
481
+ }
482
+
483
+ /* Animations */
484
+ @keyframes fadeInDown {
485
+ from {
486
+ opacity: 0;
487
+ transform: translateY(-20px);
488
+ }
489
+
490
+ to {
491
+ opacity: 1;
492
+ transform: translateY(0);
493
+ }
494
+ }
495
+
496
+ @keyframes fadeInUp {
497
+ from {
498
+ opacity: 0;
499
+ transform: translateY(20px);
500
+ }
501
+
502
+ to {
503
+ opacity: 1;
504
+ transform: translateY(0);
505
+ }
506
+ }
507
+ </style>
508
+ </head>
509
+
510
+ <body>
511
+ <!-- Background Effect -->
512
+ <div class="background-glow"></div>
513
+
514
+ <!-- HEADER -->
515
+ <header class="site-header">
516
+ <div class="header-container">
517
+ <div class="brand">
518
+ <span class="heart-icon">❤️</span>
519
+ <span class="brand-name">HeartCare</span>
520
+ </div>
521
+ <nav class="nav-links">
522
+ <a href="/">Home</a>
523
+ <a href="/#about">About</a>
524
+ <a href="/#contact">Contact</a>
525
+ <a href="/calculate" class="btn-start">Start Assessment</a>
526
+ </nav>
527
+ </div>
528
+ </header>
529
+
530
+ <main class="container recommendation-page">
531
+ <!-- Page Header -->
532
+ <div class="page-header">
533
+ <h1>Your Personalized Health Report</h1>
534
+ <p>Comprehensive cardiovascular risk assessment with tailored recommendations</p>
535
+ </div>
536
+
537
+ <!-- Risk Level Selector -->
538
+ <div class="risk-selector">
539
+ <h3>Select Risk Level to View Details</h3>
540
+ <p class="small" style="margin-bottom: 1rem;">Click on a risk level below or the system will auto-select based on
541
+ your assessment</p>
542
+ <div class="risk-pills">
543
+ <div class="risk-pill vlow" onclick="show(1)">
544
+ Very Low<br><small>0–20%</small>
545
+ </div>
546
+ <div class="risk-pill low" onclick="show(2)">
547
+ Low<br><small>21–40%</small>
548
+ </div>
549
+ <div class="risk-pill mod" onclick="show(3)">
550
+ Moderate<br><small>41–60%</small>
551
+ </div>
552
+ <div class="risk-pill high" onclick="show(4)">
553
+ High<br><small>61–80%</small>
554
+ </div>
555
+ <div class="risk-pill vh" onclick="show(5)">
556
+ Very High<br><small>81–100%</small>
557
+ </div>
558
+ </div>
559
+ </div>
560
+
561
+ <!-- Main Content Grid -->
562
+ <div class="report-grid">
563
+ <!-- Left Column: Report Details -->
564
+ <div class="main-content">
565
+ <div id="detailsArea"></div>
566
+
567
+ <!-- Action Buttons -->
568
+ <div class="action-section">
569
+ <h3><i class="fa-solid fa-download"></i> Export & Download</h3>
570
+ <div class="action-buttons">
571
+ <button class="action-btn action-btn-primary" onclick="downloadDietPDF()">
572
+ <i class="fa-solid fa-file-pdf"></i> Download Diet Plan (PDF)
573
+ </button>
574
+ <button class="action-btn action-btn-secondary" onclick="downloadDietCSV()">
575
+ <i class="fa-solid fa-file-csv"></i> Download Diet Plan (CSV)
576
+ </button>
577
+ <button class="action-btn action-btn-primary" onclick="generatePDF()">
578
+ <i class="fa-solid fa-file-medical"></i> Download Full Report (PDF)
579
+ </button>
580
+ <button class="action-btn action-btn-secondary" onclick="printFriendly()">
581
+ <i class="fa-solid fa-print"></i> Print Report
582
+ </button>
583
+ </div>
584
+ </div>
585
+ </div>
586
+
587
+ <!-- Right Column: Sidebar -->
588
+ <div class="sidebar">
589
+ <!-- Severity Meter -->
590
+ <div class="card">
591
+ <h3><i class="fa-solid fa-gauge-high"></i> Risk Severity</h3>
592
+ <div class="meter-container">
593
+ <div class="meter">
594
+ <svg width="160" height="160" viewBox="0 0 100 100">
595
+ <defs>
596
+ <linearGradient id="meterGradient" x1="0" x2="1">
597
+ <stop offset="0%" stop-color="#10B981" />
598
+ <stop offset="50%" stop-color="#FBBF24" />
599
+ <stop offset="100%" stop-color="#EF4444" />
600
+ </linearGradient>
601
+ </defs>
602
+ <circle cx="50" cy="50" r="40" stroke="#E5E7EB" stroke-width="12" fill="none"></circle>
603
+ <circle id="meterArc" cx="50" cy="50" r="40" stroke="url(#meterGradient)" stroke-width="12"
604
+ stroke-linecap="round" fill="none" stroke-dasharray="0 251.2"></circle>
605
+ </svg>
606
+ <div class="center">
607
+ <div class="percent" id="meterPercent">0%</div>
608
+ <div class="label" id="meterLabel">Risk Level</div>
609
+ </div>
610
+ </div>
611
+ </div>
612
+ </div>
613
+
614
+ <!-- Risk Factors Chart -->
615
+ <div class="card">
616
+ <h3><i class="fa-solid fa-chart-column"></i> Risk Factor Analysis</h3>
617
+ <div class="chart-container">
618
+ <canvas id="factorChart"></canvas>
619
+ </div>
620
+ </div>
621
+
622
+ <!-- Nearby Doctors -->
623
+ <div class="card">
624
+ <h3><i class="fa-solid fa-user-doctor"></i> Nearby Cardiologists</h3>
625
+ <p class="small" style="margin-bottom: 1rem;">Recommended specialists in Nanded, Maharashtra</p>
626
+ <div id="map"></div>
627
+ <div class="doctor-list" id="doctorList"></div>
628
+ </div>
629
+ </div>
630
+ </div>
631
+ </main>
632
+
633
+ <!-- Footer -->
634
+ <footer class="footer">
635
+ <div class="footer-container">
636
+ <div class="footer-about">
637
+ <h3>HeartCare</h3>
638
+ <p>HeartCare is a heart disease risk prediction platform created to promote early detection, awareness, and
639
+ prevention of cardiovascular diseases.</p>
640
+ </div>
641
+ <div class="footer-links">
642
+ <h4>Quick Links</h4>
643
+ <a href="/">Home</a>
644
+ <a href="/#about">About</a>
645
+ <a href="/#contact">Contact</a>
646
+ <a href="/calculate">Start Calculation</a>
647
+ </div>
648
+ <div class="footer-contact">
649
+ <h4>Contact Info</h4>
650
+ <p><strong>Email:</strong> support@heartcare.com</p>
651
+ <p><strong>Phone:</strong> +91 9876543210</p>
652
+ <p><strong>Location:</strong> Nanded, Maharashtra, India</p>
653
+ </div>
654
+ </div>
655
+ <div class="footer-bottom">
656
+ <p>&copy; 2025 HeartCare | AI Heart Disease Risk Prediction System | All Rights Reserved</p>
657
+ </div>
658
+ </footer>
659
+
660
+ <script>
661
+ /* Sample data — Cardiologists in Nanded, MH */
662
+ const doctors = [
663
+ { name: "Dr. Shrikant Bhoskar", address: "Sunrise Global Hospital, Nanded", phone: "+91 70454 45363", email: "shrikant@example.com", lat: 19.1683962, lon: 77.3063096 },
664
+ { name: "Dr. Sunil Kamble", address: "Rhythm Heart Care Clinic, Nanded", phone: "+91 92267 30273", email: "sunil@example.com", lat: 19.1574799, lon: 77.3081876 },
665
+ { name: "Dattakrupa Heart Care", address: "Doctor Lane, Khadakpura, Nanded", phone: "+91 93071 27289", email: "dattakrupa@example.com", lat: 19.159009, lon: 77.307444 }
666
+ ];
667
+
668
+ const NANDED_LAT = 19.1601;
669
+ const NANDED_LON = 77.3040;
670
+
671
+ /* Recommendation templates */
672
+ const PLANS = {
673
+ 1: {
674
+ title: "✅ Very Low Risk (0–20%)",
675
+ desc: "Excellent health status! Continue your healthy lifestyle with routine checkups once per year.",
676
+ factors: [2, 2, 1, 2, 2],
677
+ diet: ["Daily fruits & vegetables", "Whole grains", "Limit processed sugar", "Stay hydrated"],
678
+ exercise: ["30 min walk daily", "Yoga/Stretching 3× week", "Light cardio activities"]
679
+ },
680
+ 2: {
681
+ title: "🟢 Low Risk (21–40%)",
682
+ desc: "Good health with room for improvement. Small lifestyle changes recommended.",
683
+ factors: [3, 3, 2, 3, 3],
684
+ diet: ["Reduce salt intake", "Increase fiber", "Include omega-3 foods", "Limit saturated fats"],
685
+ exercise: ["Brisk walk 30–40 mins daily", "Light resistance training 2× week", "Swimming or cycling"]
686
+ },
687
+ 3: {
688
+ title: "🟡 Moderate Risk (41–60%)",
689
+ desc: "Preventive action advised. Please consult your doctor for personalized guidance.",
690
+ factors: [5, 6, 5, 5, 4],
691
+ diet: ["Mediterranean diet", "Avoid fried foods", "Limit red meat", "Increase vegetables"],
692
+ exercise: ["Brisk walking 30–45 mins", "Swimming or cycling", "Yoga for stress management"]
693
+ },
694
+ 4: {
695
+ title: "🟠 High Risk (61–80%)",
696
+ desc: "Cardiologist consultation strongly recommended. Take immediate preventive measures.",
697
+ factors: [7, 8, 7, 6, 5],
698
+ diet: ["Low sodium & low saturated fat", "Small frequent meals", "Consult dietician", "Avoid processed foods"],
699
+ exercise: ["Doctor-approved light exercise only", "Avoid heavy exertion", "Gentle walking"]
700
+ },
701
+ 5: {
702
+ title: "🔴 Very High Risk (81–100%)",
703
+ desc: "URGENT: Immediate medical attention required. Please visit ER or cardiology specialist now.",
704
+ factors: [9, 10, 9, 8, 7],
705
+ diet: ["Strict medical diet", "Monitor blood sugar", "Avoid processed foods", "Follow doctor's plan"],
706
+ exercise: ["Only doctor-approved minimal activity", "Complete rest if advised"]
707
+ }
708
+ };
709
+
710
+ /* Read parameters */
711
+ let currentRiskLevel = {{ risk_level }};
712
+ const params = new URLSearchParams(location.search);
713
+ const riskPercentParam = parseFloat(params.get("risk")) || (currentRiskLevel * 20 - 10);
714
+
715
+ let factorChartInstance = null;
716
+
717
+ /* Initialize map */
718
+ let map = L.map('map', { scrollWheelZoom: false }).setView([NANDED_LAT, NANDED_LON], 13);
719
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map);
720
+
721
+ doctors.forEach((d) => {
722
+ const marker = L.marker([d.lat, d.lon]).addTo(map);
723
+ marker.bindPopup(`<strong>${d.name}</strong><br>${d.address}<br>Phone: ${d.phone}`);
724
+ });
725
+
726
+ /* Populate doctor list */
727
+ function refreshDoctorList() {
728
+ const div = document.getElementById('doctorList');
729
+ div.innerHTML = '';
730
+ doctors.forEach(d => {
731
+ const el = document.createElement('div');
732
+ el.className = 'doctor-card';
733
+ el.innerHTML = `
734
+ <div class="doctor-name">${d.name}</div>
735
+ <div class="doctor-info">${d.address}</div>
736
+ <div class="doctor-info">📞 ${d.phone}</div>
737
+ <div class="doctor-actions">
738
+ <button class="btn-small btn-outline" onclick="emailDoctor('${d.email}','${d.name}')">
739
+ <i class="fa-solid fa-envelope"></i> Email
740
+ </button>
741
+ <button class="btn-small btn-primary-small" onclick="alertDoctor('${d.email}','${d.name}')">
742
+ <i class="fa-solid fa-bell"></i> Send Alert
743
+ </button>
744
+ </div>
745
+ `;
746
+ div.appendChild(el);
747
+ });
748
+ }
749
+ refreshDoctorList();
750
+
751
+ /* Show risk details */
752
+ function show(level) {
753
+ currentRiskLevel = level;
754
+ const data = PLANS[level];
755
+ const details = document.getElementById('detailsArea');
756
+
757
+ // Update active pill
758
+ document.querySelectorAll('.risk-pill').forEach(pill => pill.classList.remove('active'));
759
+ document.querySelectorAll('.risk-pill')[level - 1].classList.add('active');
760
+
761
+ const priorityHTML = getPriorityFactorsHTML();
762
+
763
+ details.innerHTML = `
764
+ <div class="report-card highlight" id="detailSection">
765
+ <div class="report-title">${data.title}</div>
766
+ <div class="report-subtitle">${data.desc}</div>
767
+
768
+ <div style="background: linear-gradient(135deg, #EEF2FF 0%, #E0E7FF 100%); padding: 1.5rem; border-radius: var(--radius-lg); margin-bottom: 2rem;">
769
+ <h4 style="margin: 0 0 0.5rem 0; color: var(--primary-color);"><i class="fa-solid fa-chart-line"></i> Your Risk Score</h4>
770
+ <p style="margin: 0; font-size: 2rem; font-weight: 800; color: var(--text-main);" id="predRiskText">--</p>
771
+ </div>
772
+
773
+ ${priorityHTML}
774
+
775
+ <h3 style="margin-bottom: 1.5rem; font-size: 1.5rem;"><i class="fa-solid fa-clipboard-list"></i> Personalized Recommendations</h3>
776
+ <div class="recommendations-grid">
777
+ <div class="recommendation-card">
778
+ <h4><i class="fa-solid fa-utensils"></i> Diet Plan</h4>
779
+ <ul>${data.diet.map(i => `<li>${i}</li>`).join('')}</ul>
780
+ </div>
781
+ <div class="recommendation-card">
782
+ <h4><i class="fa-solid fa-dumbbell"></i> Exercise</h4>
783
+ <ul>${data.exercise.map(i => `<li>${i}</li>`).join('')}</ul>
784
+ </div>
785
+ <div class="recommendation-card">
786
+ <h4><i class="fa-solid fa-stethoscope"></i> Medical Care</h4>
787
+ <ul>
788
+ <li>${data.desc}</li>
789
+ <li>Recommended tests: BP, Lipid profile, ECG</li>
790
+ ${level >= 4 ? '<li style="color: #EF4444; font-weight: 600;">Consult cardiologist immediately</li>' : ''}
791
+ </ul>
792
+ </div>
793
+ </div>
794
+ </div>
795
+ `;
796
+
797
+ const risk = (typeof riskPercentParam === 'number' && !Number.isNaN(riskPercentParam)) ? riskPercentParam : Math.min(100, Math.max(5, level * 20 - 5));
798
+ animateMeter(risk);
799
+ document.getElementById('predRiskText').innerText = risk + '%';
800
+ renderChart(data.factors);
801
+
802
+ setTimeout(() => document.getElementById('detailSection').scrollIntoView({ behavior: 'smooth' }), 150);
803
+ }
804
+
805
+ function getPriorityFactorsHTML() {
806
+ const factors = [];
807
+
808
+ // BMI
809
+ const bmi = parseFloat(params.get('BMI'));
810
+ if (bmi >= 30) {
811
+ factors.push({
812
+ title: 'High BMI (Obesity)',
813
+ value: bmi,
814
+ desc: 'A BMI above 30 significantly increases heart disease risk.',
815
+ critical: true,
816
+ tip: 'Aim for a BMI between 18.5 and 24.9 through calorie deficit and regular exercise.'
817
+ });
818
+ } else if (bmi >= 25) {
819
+ factors.push({
820
+ title: 'Overweight',
821
+ value: bmi,
822
+ desc: 'Being overweight is a modifiable risk factor.',
823
+ critical: false,
824
+ tip: 'Slight weight loss (5-10%) can produce significant health benefits.'
825
+ });
826
+ }
827
+
828
+ // Smoking
829
+ if (params.get('Smoking') === '1') {
830
+ factors.push({
831
+ title: 'Smoking History',
832
+ value: 'Yes',
833
+ desc: 'Smoking damages blood vessels and reduces oxygen delivery.',
834
+ critical: true,
835
+ tip: 'Quitting smoking is the most impactful step for heart health. Ask a doctor about cessation aids.'
836
+ });
837
+ }
838
+
839
+ // Diabetes
840
+ const diabetes = params.get('Diabetes');
841
+ if (diabetes === 'Yes' || diabetes === 'During Pregnancy') {
842
+ factors.push({
843
+ title: 'Diabetes Indicator',
844
+ value: diabetes,
845
+ desc: 'High blood sugar damages nerves and blood vessels controlling the heart.',
846
+ critical: true,
847
+ tip: 'Monitor blood sugar daily and adhere to a low-glycemic diet.'
848
+ });
849
+ }
850
+
851
+ // Alcohol
852
+ const alcohol = parseInt(params.get('Alcohol'));
853
+ if (alcohol > 14) {
854
+ factors.push({
855
+ title: 'High Alcohol Intake',
856
+ value: `${alcohol} days/mo`,
857
+ desc: 'Excessive alcohol can lead to high blood pressure and heart failure.',
858
+ critical: false,
859
+ tip: 'Limit alcohol to max 1-2 drinks per occasion or consider abstinence.'
860
+ });
861
+ }
862
+
863
+ // Age
864
+ const age = parseInt(params.get('Age'));
865
+ if (age > 65) {
866
+ factors.push({
867
+ title: 'Age Factor',
868
+ value: `${age} yrs`,
869
+ desc: 'Cardiovascular risk naturally increases with age.',
870
+ critical: false,
871
+ tip: 'More frequent checkups (every 6 months) are recommended for your age group.'
872
+ });
873
+ }
874
+
875
+ // Poor Health Perception
876
+ const genHealth = params.get('General_Health');
877
+ if (genHealth === 'Poor' || genHealth === 'Fair') {
878
+ factors.push({
879
+ title: 'Self-Reported Health',
880
+ value: genHealth,
881
+ desc: 'Your self-perception often correlates with underlying issues.',
882
+ critical: true,
883
+ tip: 'Discuss your specific symptoms (fatigue, pain) with a GP.'
884
+ });
885
+ }
886
+
887
+ // Depression
888
+ if (params.get('Depression') === '1') {
889
+ factors.push({
890
+ title: 'Emotional Well-being',
891
+ value: 'Depression Indicator',
892
+ desc: 'Mental health significantly impacts physical heart health.',
893
+ critical: false,
894
+ tip: 'Regular exercise and therapy can improve both mood and heart health.'
895
+ });
896
+ }
897
+
898
+ // Arthritis
899
+ if (params.get('Arthritis') === '1') {
900
+ factors.push({
901
+ title: 'Chronic Condition',
902
+ value: 'Arthritis',
903
+ desc: 'Inflammation from arthritis can affect cardiovascular health.',
904
+ critical: false,
905
+ tip: 'Focus on low-impact activities like swimming to keep the heart healthy without straining joints.'
906
+ });
907
+ }
908
+
909
+ // Cancer History
910
+ if (params.get('Skin_Cancer') === '1' || params.get('Other_Cancer') === '1') {
911
+ factors.push({
912
+ title: 'Medical History',
913
+ value: 'Cancer Survivor',
914
+ desc: 'Prior health challenges require more careful cardiovascular monitoring.',
915
+ critical: false,
916
+ tip: 'Maintain regular follow-ups with your oncology and cardiology team.'
917
+ });
918
+ }
919
+
920
+ if (factors.length === 0) return '';
921
+
922
+ return `
923
+ <div class="priority-section">
924
+ <h3 style="color: #991B1B; margin-bottom: 1rem;"><i class="fa-solid fa-triangle-exclamation"></i> Priority Risk Factors</h3>
925
+ <div class="priority-grid">
926
+ ${factors.map((f, i) => `
927
+ <div class="priority-card ${f.critical ? '' : 'warning'}">
928
+ <div class="priority-header">
929
+ <div class="priority-title">
930
+ ${f.critical ? '<i class="fa-solid fa-circle-exclamation"></i>' : '<i class="fa-solid fa-circle-info"></i>'}
931
+ ${f.title}
932
+ </div>
933
+ <div class="priority-value">${f.value}</div>
934
+ </div>
935
+ <div class="priority-desc">${f.desc}</div>
936
+ <div style="font-size: 0.85rem; color: var(--primary-color); font-weight: 600; cursor: pointer;" onclick="toggleExp(${i})">
937
+ <i class="fa-regular fa-lightbulb"></i> View Advice
938
+ </div>
939
+ <div id="exp-${i}" class="explanation-box">
940
+ <strong><i class="fa-solid fa-user-doctor"></i> Recommendation:</strong> ${f.tip}
941
+ </div>
942
+ </div>
943
+ `).join('')}
944
+ </div>
945
+ </div>
946
+ `;
947
+ }
948
+
949
+ function toggleExp(i) {
950
+ const el = document.getElementById(`exp-${i}`);
951
+ el.classList.toggle('visible');
952
+ }
953
+
954
+ /* Animate meter */
955
+ function animateMeter(percent) {
956
+ const arc = document.getElementById('meterArc');
957
+ const meterPercent = document.getElementById('meterPercent');
958
+ const circumference = 2 * Math.PI * 40;
959
+ const target = Math.max(0, Math.min(100, percent));
960
+ const length = (target / 100) * circumference;
961
+
962
+ let current = 0;
963
+ const steps = 50;
964
+ const stepVal = length / steps;
965
+
966
+ if (window.meterAnim) clearInterval(window.meterAnim);
967
+ arc.style.strokeDasharray = `0 ${circumference}`;
968
+
969
+ window.meterAnim = setInterval(() => {
970
+ current += stepVal;
971
+ const filled = current;
972
+ const remain = Math.max(0, circumference - filled);
973
+ arc.style.strokeDasharray = `${filled} ${remain}`;
974
+ meterPercent.innerText = Math.round((filled / circumference) * 100) + '%';
975
+ if (filled >= length - 0.1) {
976
+ clearInterval(window.meterAnim);
977
+ meterPercent.innerText = Math.round(target) + '%';
978
+ }
979
+ }, 10);
980
+ }
981
+
982
+ /* Chart.js for factors */
983
+ function renderChart(values) {
984
+ const ctx = document.getElementById('factorChart').getContext('2d');
985
+ if (factorChartInstance) factorChartInstance.destroy();
986
+ factorChartInstance = new Chart(ctx, {
987
+ type: 'bar',
988
+ data: {
989
+ labels: ['BMI', 'BP', 'Cholesterol', 'Lifestyle', 'Diet'],
990
+ datasets: [{
991
+ label: 'Factor Level',
992
+ data: values,
993
+ backgroundColor: ['#10B981', '#34D399', '#FBBF24', '#FB923C', '#EF4444']
994
+ }]
995
+ },
996
+ options: {
997
+ responsive: true,
998
+ maintainAspectRatio: false,
999
+ plugins: { legend: { display: false } },
1000
+ scales: { y: { beginAtZero: true, max: 10 } }
1001
+ }
1002
+ });
1003
+ }
1004
+
1005
+ /* Download functions */
1006
+ function downloadDietCSV() {
1007
+ const lev = currentRiskLevel;
1008
+ const plan = PLANS[lev];
1009
+ const rows = [['Type', 'Item'], ...plan.diet.map(i => ['Diet', i]), ...plan.exercise.map(i => ['Exercise', i])];
1010
+ const csv = rows.map(r => r.map(c => `"${String(c).replace(/"/g, '""')}"`).join(',')).join('\n');
1011
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
1012
+ const url = URL.createObjectURL(blob);
1013
+ const a = document.createElement('a');
1014
+ a.href = url;
1015
+ a.download = `heartcare_diet_plan_level_${lev}.csv`;
1016
+ document.body.appendChild(a);
1017
+ a.click();
1018
+ a.remove();
1019
+ URL.revokeObjectURL(url);
1020
+ }
1021
+
1022
+ async function downloadDietPDF() {
1023
+ const { jsPDF } = window.jspdf;
1024
+ const doc = new jsPDF();
1025
+ const lev = currentRiskLevel;
1026
+ const plan = PLANS[lev];
1027
+ doc.setFontSize(16);
1028
+ doc.text(`HeartCare - Diet & Exercise Plan`, 14, 20);
1029
+ doc.setFontSize(14);
1030
+ doc.text(`${plan.title}`, 14, 30);
1031
+ doc.setFontSize(12);
1032
+ let y = 44;
1033
+ doc.text('Diet Recommendations:', 14, y);
1034
+ y += 6;
1035
+ plan.diet.forEach(i => { doc.text(`• ${i}`, 18, y); y += 6; });
1036
+ y += 4;
1037
+ doc.text('Exercise Recommendations:', 14, y);
1038
+ y += 6;
1039
+ plan.exercise.forEach(i => { doc.text(`• ${i}`, 18, y); y += 6; });
1040
+ doc.save(`heartcare_diet_plan_level_${lev}.pdf`);
1041
+ }
1042
+
1043
+ function printFriendly() {
1044
+ window.print();
1045
+ }
1046
+
1047
+ async function generatePDF() {
1048
+ const { jsPDF } = window.jspdf;
1049
+ const doc = new jsPDF({ unit: 'px', format: 'a4' });
1050
+ const lev = currentRiskLevel;
1051
+ const plan = PLANS[lev];
1052
+ const risk = riskPercentParam || (lev * 20 - 10);
1053
+
1054
+ doc.setFontSize(20);
1055
+ doc.text('HeartCare — Health Report', 20, 30);
1056
+ doc.setFontSize(12);
1057
+ doc.text(`${plan.title} — Risk: ${risk}%`, 20, 52);
1058
+ doc.text(`Generated: ${new Date().toLocaleString()}`, 20, 68);
1059
+
1060
+ const chartCanvas = document.getElementById('factorChart');
1061
+ const chartDataUrl = chartCanvas.toDataURL('image/png', 1.0);
1062
+ doc.addImage(chartDataUrl, 'PNG', 20, 90, 320, 160);
1063
+
1064
+ let y = 260;
1065
+ doc.setFontSize(13);
1066
+ doc.text('Diet Recommendations:', 20, y);
1067
+ y += 16;
1068
+ plan.diet.forEach(i => { doc.setFontSize(11); doc.text('• ' + i, 24, y); y += 12; });
1069
+ y += 8;
1070
+ doc.setFontSize(13);
1071
+ doc.text('Exercise Recommendations:', 20, y);
1072
+ y += 16;
1073
+ plan.exercise.forEach(i => { doc.setFontSize(11); doc.text('• ' + i, 24, y); y += 12; });
1074
+ y += 14;
1075
+ doc.setFontSize(13);
1076
+ doc.text('Nearby Doctors:', 20, y);
1077
+ y += 16;
1078
+ doctors.forEach(d => {
1079
+ doc.setFontSize(11);
1080
+ doc.text(`${d.name} — ${d.address} — ${d.phone}`, 24, y);
1081
+ y += 12;
1082
+ });
1083
+ doc.save(`heartcare_report_level_${lev}.pdf`);
1084
+ }
1085
+
1086
+ function emailDoctor(email, name) {
1087
+ const lev = currentRiskLevel;
1088
+ const risk = riskPercentParam || (lev * 20 - 10);
1089
+ const subject = encodeURIComponent(`HeartCare: Consultation Request — Risk ${risk}%`);
1090
+ const body = encodeURIComponent(`Dear ${name},\n\nI am contacting you regarding a cardiovascular risk assessment showing ${risk}% risk. Please let me know your available consultation slots.\n\nRegards,\nHeartCare User`);
1091
+ window.location.href = `mailto:${email}?subject=${subject}&body=${body}`;
1092
+ }
1093
+
1094
+ function alertDoctor(email, name) {
1095
+ emailDoctor(email, name);
1096
+ }
1097
+
1098
+ /* Initialize */
1099
+ window.addEventListener('load', () => {
1100
+ show(currentRiskLevel);
1101
+ });
1102
+ </script>
1103
+ </body>
1104
+
1105
+ </html>
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.109.0
2
+ uvicorn==0.27.0
3
+ pydantic==2.6.0
4
+ pydantic-settings==2.1.0
5
+ joblib==1.3.2
6
+ scikit-learn==1.4.0
7
+ pandas==2.2.0
8
+ numpy==1.26.3
9
+ python-multipart==0.0.6
10
+ prometheus-fastapi-instrumentator==6.1.0
11
+ pytest==8.0.0
12
+ httpx==0.26.0
13
+ jinja2
14
+ aiofiles
run_app.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import subprocess
4
+ import webbrowser
5
+ import threading
6
+ import time
7
+
8
+ def install_dependencies():
9
+ """Install dependencies from requirements.txt."""
10
+ print("Checking and installing dependencies...")
11
+ try:
12
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
13
+ print("Dependencies installed successfully.")
14
+ except subprocess.CalledProcessError as e:
15
+ print(f"Error installing dependencies: {e}")
16
+ sys.exit(1)
17
+
18
+ def run_app():
19
+ """Run the FastAPI application."""
20
+ print("Starting Heart Disease Prediction App...")
21
+
22
+ # Set Environment Variable for Model Path if not set
23
+ if "MODEL_PATH" not in os.environ:
24
+ # Default to the correct path relative to this script
25
+ os.environ["MODEL_PATH"] = "app/models/heart_disease_model.pkl"
26
+
27
+ # Add current directory to sys.path
28
+ sys.path.append(os.getcwd())
29
+
30
+ # Open browser in a separate thread
31
+ def open_browser():
32
+ time.sleep(3) # Wait for server to start
33
+ print("Opening browser at http://localhost:8000")
34
+ webbrowser.open("http://localhost:8000")
35
+
36
+ threading.Thread(target=open_browser, daemon=True).start()
37
+
38
+ # Run Uvicorn
39
+ try:
40
+ import uvicorn
41
+ uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=False)
42
+ except ImportError:
43
+ print("Error: uvicorn not found. Please run the script again after installation.")
44
+ except Exception as e:
45
+ print(f"Error running application: {e}")
46
+
47
+ if __name__ == "__main__":
48
+ # Ensure we are in the script's directory
49
+ os.chdir(os.path.dirname(os.path.abspath(__file__)))
50
+
51
+ # Check if dependencies are installed
52
+ try:
53
+ import fastapi
54
+ import uvicorn
55
+ import joblib
56
+ import jinja2
57
+ except ImportError:
58
+ install_dependencies()
59
+
60
+ run_app()