JanithDeshan24 commited on
Commit
3815023
·
verified ·
1 Parent(s): 313061f

feat: Initial project setup

Browse files

feat: Initial project setup
This commit establishes the complete initial structure for the AquaTest project.
It includes:
-Backend: app.py (Flask server and prediction API)
-Frontend: index.html, style.css, main.js (The complete user interface)
-AI Models: The trained water_quality_model.h5 and water_quality_model_ALT.h5
-Preprocessors: The saved scaler.joblib and scaler_ALT.joblib
-Training Scripts: Two complete Python scripts for model training and EDA, demonstrating the effect of hyperparameter tuning.
-Dataset: The water_potability.csv file used for training.

Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use a standard Python 3.9 image
2
+ FROM python:3.9-slim
3
+
4
+ # Set the working directory inside the container
5
+ WORKDIR /app
6
+
7
+ # Copy the requirements file first and install dependencies
8
+ COPY requirements.txt requirements.txt
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ # Copy the rest of your project files into the container
12
+ COPY . .
13
+
14
+ # Tell Hugging Face to expose port 7860
15
+ EXPOSE 7860
16
+
17
+ # The command to run your Flask app
18
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ import numpy as np
3
+ import tensorflow as tf
4
+ import joblib
5
+
6
+ app = Flask(__name__)
7
+
8
+ # --- MODEL LOADING ---
9
+ # Load the model and scaler you want to use.
10
+ # By default, this loads the FIRST model you trained.
11
+ try:
12
+ model = tf.keras.models.load_model('water_quality_model.h5')
13
+ scaler = joblib.load('scaler.joblib')
14
+ print("✅ Model 'water_quality_model.h5' and 'scaler.joblib' loaded successfully.")
15
+
16
+ except Exception as e:
17
+ print(f"❌ Error loading 'water_quality_model.h5': {e}")
18
+ print("---")
19
+ # --- IF YOU WANT TO USE THE ALTERNATIVE MODEL, UNCOMMENT THE 3 LINES BELOW ---
20
+ # try:
21
+ # model = tf.keras.models.load_model('water_quality_model_ALT.h5')
22
+ # scaler = joblib.load('scaler_ALT.joblib')
23
+ # print("✅ Alternative Model 'water_quality_model_ALT.h5' loaded.")
24
+ # except Exception as e2:
25
+ # print(f"❌ Error loading all models: {e2}")
26
+ # model = None
27
+ # scaler = None
28
+ model = None
29
+ scaler = None
30
+ # ---------------------
31
+
32
+
33
+ @app.route('/')
34
+ def home():
35
+ """Renders the main HTML page."""
36
+ return render_template('index.html')
37
+
38
+
39
+ @app.route('/predict', methods=['POST'])
40
+ def predict():
41
+ """Receives data from the form and returns a prediction."""
42
+ if not model or not scaler:
43
+ return jsonify({'error': 'Model is not loaded properly. Check server logs.'}), 500
44
+
45
+ try:
46
+ # Get data from the JSON request sent by the JavaScript
47
+ data = request.get_json()
48
+
49
+ # 1. Create a numpy array in the correct order (must match training order)
50
+ features = [
51
+ float(data['ph']),
52
+ float(data['Hardness']),
53
+ float(data['Solids']),
54
+ float(data['Chloramines']),
55
+ float(data['Sulfate']),
56
+ float(data['Conductivity']),
57
+ float(data['Organic_carbon']),
58
+ float(data['Trihalomethanes']),
59
+ float(data['Turbidity'])
60
+ ]
61
+
62
+ # 2. Put data into a 2D numpy array (model expects a batch)
63
+ input_data = np.array([features])
64
+
65
+ # 3. Scale the data using the *loaded* scaler
66
+ input_scaled = scaler.transform(input_data)
67
+
68
+ # 4. Make the prediction (will be a probability between 0 and 1)
69
+ prediction_prob = model.predict(input_scaled)[0][0]
70
+
71
+ # 5. Classify the prediction
72
+ prediction = int(prediction_prob > 0.5) # 1 if > 0.5, else 0
73
+
74
+ # 6. Return the result as JSON
75
+ return jsonify({
76
+ 'potability': prediction, # 0 or 1
77
+ 'probability': float(prediction_prob) # The raw probability
78
+ })
79
+
80
+ except Exception as e:
81
+ return jsonify({'error': str(e)}), 400
82
+
83
+
84
+ if __name__ == '__main__':
85
+ app.run(host="0.0.0.0", port=7860)
code/correct_water_qulity_01.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
code/correct_water_qulity_01.py ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """correct water qulity 01
3
+
4
+ Automatically generated by Colab.
5
+
6
+ Original file is located at
7
+ https://colab.research.google.com/drive/1P_fudbhG4Zu0c7jfo1ohnHoQLyG5yjyo
8
+ """
9
+
10
+ # --- 1. SETUP AND IMPORTS ---
11
+ import tensorflow as tf
12
+ from tensorflow.keras.models import Sequential, load_model
13
+ from tensorflow.keras.layers import Dense, Dropout
14
+ from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
15
+ from sklearn.model_selection import train_test_split
16
+ from sklearn.preprocessing import StandardScaler
17
+ from sklearn.impute import KNNImputer
18
+ from sklearn.metrics import confusion_matrix, classification_report, precision_score
19
+ from imblearn.over_sampling import SMOTE
20
+ import pandas as pd
21
+ import numpy as np
22
+ import matplotlib.pyplot as plt
23
+ import seaborn as sns
24
+ import joblib
25
+
26
+ print("TensorFlow Version:", tf.__version__)
27
+
28
+ # --- 2. DATA LOADING ---
29
+ try:
30
+ data = pd.read_csv('water_potability.csv')
31
+ print("Dataset loaded successfully.")
32
+ except FileNotFoundError:
33
+ print("Error: 'water_potability.csv' not found.")
34
+ print("Please download the dataset from Kaggle and place it in the same directory.")
35
+ exit()
36
+
37
+ # --- 3. TASK 1: PREPROCESSING TECHNIQUES & EDA ---
38
+ # Each subsection represents a specific technique with its own EDA.
39
+
40
+ # --------------------------------------------------------------------------
41
+ # Technique 1 (Member 1): Handling Missing Values
42
+ # --------------------------------------------------------------------------
43
+ print("\n--- EDA for Technique 1: Missing Values ---")
44
+ missing_percent = (data.isnull().sum() / len(data)) * 100
45
+ plt.figure(figsize=(10, 6))
46
+ sns.barplot(x=missing_percent.index, y=missing_percent.values)
47
+ plt.title('Percentage of Missing Values per Feature', fontsize=16)
48
+ plt.ylabel('Percentage Missing (%)')
49
+ plt.xlabel('Features')
50
+ plt.xticks(rotation=45)
51
+ plt.show()
52
+
53
+ print("EDA Conclusion: 'ph', 'Sulfate', and 'Trihalomethanes' have significant missing data.")
54
+ print("Preprocessing Step: We will use KNNImputer to fill these, as it's more accurate than a simple mean.")
55
+
56
+ # --------------------------------------------------------------------------
57
+ # Technique 2 (Member 2): Handling Class Imbalance
58
+ # --------------------------------------------------------------------------
59
+ print("\n--- EDA for Technique 2: Class Imbalance ---")
60
+ plt.figure(figsize=(7, 5))
61
+ sns.countplot(x='Potability', data=data)
62
+ plt.title('Class Distribution (0 = Not Potable, 1 = Potable)', fontsize=16)
63
+ plt.xlabel('Potability')
64
+ plt.ylabel('Count')
65
+ plt.show()
66
+
67
+ print(f"Distribution:\n{data['Potability'].value_counts(normalize=True)}")
68
+ print("EDA Conclusion: The dataset is imbalanced. There are more 'Not Potable' (0) samples.")
69
+ print("Preprocessing Step: We will use SMOTE (Synthetic Minority Over-sampling Technique) on the training data to create a balanced dataset for the model to learn from.")
70
+
71
+ # --------------------------------------------------------------------------
72
+ # Technique 3 (Member 3): Exploring Feature Distributions & Outliers
73
+ # --------------------------------------------------------------------------
74
+ print("\n--- EDA for Technique 3: Feature Distributions (Outliers) ---")
75
+ # Melt the dataframe for easier plotting with Seaborn
76
+ data_melted = pd.melt(data, id_vars=['Potability'], var_name='Feature', value_name='Value')
77
+
78
+ plt.figure(figsize=(15, 8))
79
+ sns.boxplot(x='Feature', y='Value', data=data_melted, showfliers=True) # showfliers=True to show outliers
80
+ plt.title('Boxplots for Each Feature (Showing Outliers)', fontsize=16)
81
+ plt.xticks(rotation=45)
82
+ plt.yscale('log') # Use log scale for better visibility of distributions
83
+ plt.show()
84
+
85
+ print("EDA Conclusion: Features have vastly different scales and ranges (e.g., 'Solids' is in 10,000s, 'pH' is 0-14).")
86
+ print("Many features also have significant outliers.")
87
+ print("Preprocessing Step: Feature Scaling is mandatory for neural networks.")
88
+
89
+ # --------------------------------------------------------------------------
90
+ # Technique 4 (Member 4): Feature Scaling
91
+ # --------------------------------------------------------------------------
92
+ print("\n--- EDA for Technique 4: Feature Scaling (Before/After) ---")
93
+ # We'll simulate the scaling on 'Solids' (a high-value feature) to visualize the effect.
94
+ # Note: We only use non-null values for this specific plot.
95
+ scaler_demo = StandardScaler()
96
+ solids_data = data[['Solids']].dropna()
97
+ solids_scaled = scaler_demo.fit_transform(solids_data)
98
+
99
+ plt.figure(figsize=(12, 5))
100
+ plt.subplot(1, 2, 1)
101
+ sns.kdeplot(solids_data['Solids'], fill=True)
102
+ plt.title('Before Scaling (Solids)')
103
+ plt.xlabel('TDS (ppm)')
104
+
105
+ plt.subplot(1, 2, 2)
106
+ sns.kdeplot(solids_scaled.flatten(), fill=True, color='green')
107
+ plt.title('After Scaling (Solids)')
108
+ plt.xlabel('Standardized Value')
109
+ plt.suptitle('Technique 4: Effect of StandardScaler', fontsize=16)
110
+ plt.show()
111
+
112
+ print("EDA Conclusion: Scaling centers the data around 0 and squashes it to a standard range.")
113
+ print("Preprocessing Step: We will apply StandardScaler to all 9 features after splitting the data.")
114
+
115
+ # --------------------------------------------------------------------------
116
+ # Technique 5 (Member 5): Correlation Analysis
117
+ # --------------------------------------------------------------------------
118
+ print("\n--- EDA for Technique 5: Feature Correlation ---")
119
+ # Use the imputed data just for this visualization (otherwise NaNs mess up the heatmap)
120
+ imputer_demo = KNNImputer(n_neighbors=5)
121
+ data_imputed_demo = pd.DataFrame(imputer_demo.fit_transform(data), columns=data.columns)
122
+ corr = data_imputed_demo.corr()
123
+
124
+ plt.figure(figsize=(12, 10))
125
+ sns.heatmap(corr, annot=True, cmap='coolwarm', fmt='.2f', linewidths=0.5)
126
+ plt.title('Feature Correlation Heatmap', fontsize=16)
127
+ plt.show()
128
+
129
+ print("EDA Conclusion: No features are extremely highly correlated (e.g., > 0.9 or < -0.9).")
130
+ print("This suggests that all 9 features provide unique information and should be kept for the model.")
131
+
132
+ # --------------------------------------------------------------------------
133
+ # Final Combined Preprocessing Pipeline (The "How-To")
134
+ # --------------------------------------------------------------------------
135
+ print("\n--- Final Preprocessing Pipeline (Code) ---")
136
+ print("Combining all techniques to prepare data for the model...")
137
+
138
+ # 1. Impute Missing Values
139
+ print("Step 1: Imputing missing values with KNNImputer...")
140
+ imputer = KNNImputer(n_neighbors=5)
141
+ data_imputed = pd.DataFrame(imputer.fit_transform(data), columns=data.columns)
142
+
143
+ # 2. Feature / Target Split
144
+ print("Step 2: Separating features (X) and target (y)...")
145
+ X = data_imputed.drop('Potability', axis=1)
146
+ y = data_imputed['Potability']
147
+
148
+ # 3. Data Splitting (Train/Test)
149
+ print("Step 3: Splitting data into training and test sets...")
150
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
151
+ print(f"Original training samples: {X_train.shape[0]}, Test samples: {X_test.shape[0]}")
152
+
153
+ # 4. Handle Class Imbalance (SMOTE)
154
+ print("Step 4: Balancing training data with SMOTE...")
155
+ smote = SMOTE(random_state=42)
156
+ X_train_resampled, y_train_resampled = smote.fit_resample(X_train, y_train)
157
+ print(f"Resampled training samples: {X_train_resampled.shape[0]}")
158
+
159
+ # 5. Feature Scaling
160
+ print("Step 5: Applying StandardScaler...")
161
+ scaler = StandardScaler()
162
+ # Fit the scaler ONLY on the training data
163
+ X_train_scaled = scaler.fit_transform(X_train_resampled)
164
+ # Apply the same scaler to the test data
165
+ X_test_scaled = scaler.transform(X_test)
166
+
167
+ print("\n✅ Final data pipelines are built and ready for model training.")
168
+ print("The 'scaler' object is saved to apply to new user input in the app.")
169
+
170
+ # --- 4. TASK 2: ALGORITHM SELECTION, IMPLEMENTATION & HYPERPARAMETER TUNING ---
171
+
172
+ """
173
+ ### Task 2.1: Algorithm Selection
174
+ For this tabular, binary classification task, we will use a **Deep Neural Network (DNN)**,
175
+ also known as a Multi-Layer Perceptron (MLP). This is a powerful and flexible
176
+ choice that can learn complex, non-linear relationships between the 9 features.
177
+ """
178
+
179
+ # --- Task 2.2: Model Implementation ---
180
+ def build_model(input_shape):
181
+ model = Sequential([
182
+ # Input layer: 9 features
183
+ Dense(64, activation='relu', input_shape=[input_shape]),
184
+ Dropout(0.3), # Dropout layer to prevent overfitting
185
+ Dense(128, activation='relu'),
186
+ Dropout(0.3),
187
+ Dense(64, activation='relu'),
188
+ Dropout(0.3),
189
+ # Output layer: 1 neuron with sigmoid activation
190
+ # for binary classification (0 or 1)
191
+ Dense(1, activation='sigmoid')
192
+ ])
193
+ return model
194
+
195
+ model = build_model(X_train_scaled.shape[1])
196
+ model.summary()
197
+
198
+ """
199
+ ### Task 2.3: Hyperparameter Tuning Strategy
200
+ * **Optimizer:** Adam (an efficient and popular choice).
201
+ * **Loss Function:** `binary_crossentropy` (This is REQUIRED for a two-class, 0/1 problem).
202
+ * **Metrics:** We will monitor `accuracy`.
203
+ * **Callbacks:**
204
+ * `EarlyStopping`: Stops training when validation accuracy stops improving.
205
+ * `ReduceLROnPlateau`: Lowers the learning rate if training plateaus.
206
+ """
207
+
208
+ # --- Model Training ---
209
+ print("\n--- Model Training ---")
210
+
211
+ model.compile(
212
+ optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
213
+ loss='binary_crossentropy',
214
+ metrics=['accuracy']
215
+ )
216
+
217
+ callbacks = [
218
+ EarlyStopping(monitor='val_accuracy', patience=20, verbose=1, restore_best_weights=True),
219
+ ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=10, min_lr=1e-6, verbose=1)
220
+ ]
221
+
222
+ # Train on the RESAMPLED and SCALED data
223
+ history = model.fit(
224
+ X_train_scaled,
225
+ y_train_resampled, # Use the balanced target
226
+ epochs=200, # Set high, EarlyStopping will handle it
227
+ validation_data=(X_test_scaled, y_test), # Validate on the original, unbalanced test set
228
+ callbacks=callbacks,
229
+ batch_size=32
230
+ )
231
+
232
+ # --- 5. TASK 3: EVALUATION METRICS ---
233
+ """
234
+ ### Task 3.1: Evaluation Metrics
235
+ For this problem, **Accuracy is misleading**. We MUST focus on the
236
+ **Confusion Matrix** and **Precision for Class 1**.
237
+
238
+ * **DANGER:** A **False Positive** (model says 'Potable' when it's 'Not Potable')
239
+ is the worst possible error.
240
+ * **Our Goal:** Minimize False Positives.
241
+ * **Key Metric:** **Precision (Class 1)** tells us: "Of all the times the
242
+ model said 'Potable', what percentage was it correct?"
243
+ """
244
+ print("\n--- Final Model Evaluation ---")
245
+ final_loss, final_accuracy = model.evaluate(X_test_scaled, y_test)
246
+ print(f"\nFinal Test Loss: {final_loss:.4f}")
247
+ print(f"Final Test Accuracy: {final_accuracy * 100:.2f}% (Can be misleading!)")
248
+
249
+ y_pred_probs = model.predict(X_test_scaled)
250
+ y_pred = (y_pred_probs > 0.5).astype(int)
251
+
252
+ # --- CRITICAL EVALUATION ---
253
+ cm = confusion_matrix(y_test, y_pred)
254
+ precision_class_1 = precision_score(y_test, y_pred, pos_label=1, zero_division=0)
255
+ false_positives = cm[0][1]
256
+
257
+ print("\n--- Detailed Classification Report ---")
258
+ print(classification_report(y_test, y_pred, target_names=['Not Potable (0)', 'Potable (1)'], zero_division=0))
259
+
260
+ print("\n--- CRITICAL METRIC ANALYSIS ---")
261
+ print(f"Precision (Class 1 - Potable): {precision_class_1 * 100:.2f}%")
262
+ print(" > This means when the model says water IS 'Potable', it is correct this % of the time.")
263
+ print(f"\nTotal DANGEROUS Predictions (False Positives): {false_positives}")
264
+ print(f" > The model incorrectly labeled {false_positives} unsafe samples as 'safe'.")
265
+ print("-----------------------------------")
266
+
267
+
268
+ plt.figure(figsize=(8, 6))
269
+ sns.heatmap(
270
+ cm,
271
+ annot=True, fmt='d', cmap='Reds', # Use 'Reds' to highlight danger
272
+ xticklabels=['Predicted Not Potable (0)', 'Predicted Potable (1)'],
273
+ yticklabels=['Actual Not Potable (0)', 'Actual Potable (1)']
274
+ )
275
+ plt.title(f'Confusion Matrix\n{false_positives} False Positives (DANGEROUS)', fontsize=14, color='red')
276
+ plt.xlabel('Predicted Label')
277
+ plt.ylabel('True Label')
278
+ plt.show()
279
+
280
+ # --- 6. TASK 4: ETHICAL AND BIAS ANALYSIS ---
281
+ """
282
+ ### Task 4.1: Ethical and Bias Analysis
283
+
284
+ * **CRITICAL RISK: False Positives.**
285
+ As shown in the evaluation, a False Positive (predicting 'Potable' when
286
+ water is 'Not Potable') is a severe health risk. The model's Precision
287
+ for the 'Potable' class must be as high as possible.
288
+
289
+ * **Dataset Bias:**
290
+ The dataset's origin is not specified. It may represent water from a
291
+ specific region or type of source (e.g., municipal vs. well). The
292
+ model may not generalize well to water with different chemical profiles
293
+ from other parts of the world.
294
+
295
+ * **Conclusion & Disclaimer:**
296
+ This application **MUST** be deployed with a very strong
297
+ disclaimer. It should be labeled: "For educational and
298
+ informational purposes ONLY. This is NOT a substitute
299
+ for a professional, laboratory-based water quality test."
300
+ The developer has a responsibility to make this clear to all users.
301
+ """
302
+
303
+ # --- 7. SAVE THE FINAL MODEL AND SCALER ---
304
+ # We must save TWO files:
305
+ # 1. The trained Keras model (.h5)
306
+ # 2. The StandardScaler object (.joblib)
307
+ model.save('water_quality_model.h5')
308
+ joblib.dump(scaler, 'scaler.joblib')
309
+
310
+ print("\n✅ Final model saved as 'water_quality_model.h5'")
311
+ print("✅ Scaler saved as 'scaler.joblib'")
312
+ print("\nProject setup complete. You are ready to build the Flask app.")
scaler.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0331c9f8377c4d3666b57610c6b16782eab623a8bb99e8e31a2f16aab8e18e72
3
+ size 1183
scaler_ALT.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0331c9f8377c4d3666b57610c6b16782eab623a8bb99e8e31a2f16aab8e18e72
3
+ size 1183
static/css/style.css ADDED
@@ -0,0 +1,666 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ====== Base Styles ====== */
2
+ :root {
3
+ --primary: #0891B2;
4
+ --primary-dark: #155E75;
5
+ --primary-light: #22D3EE;
6
+ --secondary: #155E75;
7
+ --dark: #082F49;
8
+ --light: #F0F9FF;
9
+ --gray: #64748B;
10
+ --danger: #DC2626;
11
+ --success: #059669;
12
+ --warning: #D97706;
13
+ --transition: all 0.3s ease;
14
+ --shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
15
+ --shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.15);
16
+ }
17
+
18
+ body {
19
+ font-family: 'Open Sans', sans-serif;
20
+ color: #333;
21
+ overflow-x: hidden;
22
+ background-color: #fff;
23
+ }
24
+
25
+ h1, h2, h3, h4, h5, h6 {
26
+ font-family: 'Poppins', sans-serif;
27
+ font-weight: 600;
28
+ }
29
+
30
+ section {
31
+ position: relative;
32
+ padding: 80px 0;
33
+ }
34
+
35
+ .section-title {
36
+ font-weight: 700;
37
+ position: relative;
38
+ margin-bottom: 15px;
39
+ color: var(--dark);
40
+ }
41
+
42
+ .section-divider {
43
+ height: 4px;
44
+ width: 70px;
45
+ background: var(--primary);
46
+ margin: 0 auto 40px;
47
+ border-radius: 2px;
48
+ }
49
+
50
+ .btn {
51
+ border-radius: 50px;
52
+ padding: 12px 25px;
53
+ font-weight: 500;
54
+ transition: var(--transition);
55
+ text-transform: uppercase;
56
+ letter-spacing: 0.5px;
57
+ font-size: 14px;
58
+ }
59
+
60
+ .btn-primary {
61
+ background-color: var(--primary);
62
+ border-color: var(--primary);
63
+ box-shadow: 0 4px 15px rgba(8, 145, 178, 0.3);
64
+ }
65
+
66
+ .btn-primary:hover, .btn-primary:focus {
67
+ background-color: var(--primary-dark);
68
+ border-color: var(--primary-dark);
69
+ transform: translateY(-2px);
70
+ box-shadow: 0 6px 20px rgba(8, 145, 178, 0.4);
71
+ }
72
+
73
+ .btn-lg {
74
+ padding: 14px 30px;
75
+ font-size: 16px;
76
+ }
77
+
78
+ /* ====== Particles Background ====== */
79
+ #particles-js {
80
+ position: fixed;
81
+ width: 100%;
82
+ height: 100%;
83
+ top: 0;
84
+ left: 0;
85
+ z-index: -1;
86
+ background: linear-gradient(135deg, #0C4A6E, #082F49);
87
+ }
88
+
89
+ /* ====== Navbar ====== */
90
+ .navbar {
91
+ padding: 20px 0;
92
+ transition: var(--transition);
93
+ z-index: 1000;
94
+ }
95
+
96
+ .navbar.scrolled {
97
+ background-color: rgba(8, 47, 73, 0.9);
98
+ padding: 10px 0;
99
+ box-shadow: var(--shadow);
100
+ }
101
+
102
+ .navbar-brand {
103
+ font-family: 'Poppins', sans-serif;
104
+ font-weight: 700;
105
+ font-size: 24px;
106
+ color: var(--light) !important;
107
+ }
108
+
109
+ .wave-icon {
110
+ animation: waveAnimation 2s infinite;
111
+ display: inline-block;
112
+ }
113
+
114
+ @keyframes waveAnimation {
115
+ 0% { transform: rotate(0deg); }
116
+ 25% { transform: rotate(-10deg); }
117
+ 50% { transform: rotate(0deg); }
118
+ 75% { transform: rotate(10deg); }
119
+ 100% { transform: rotate(0deg); }
120
+ }
121
+
122
+ /* Unique Nav styling */
123
+ .custom-nav {
124
+ display: flex;
125
+ gap: 15px;
126
+ }
127
+
128
+ .custom-nav .nav-item {
129
+ position: relative;
130
+ }
131
+
132
+ .custom-nav .nav-link {
133
+ display: flex;
134
+ flex-direction: column;
135
+ align-items: center;
136
+ padding: 8px 15px;
137
+ color: var(--light) !important;
138
+ background: rgba(255, 255, 255, 0.05);
139
+ border-radius: 12px;
140
+ overflow: hidden;
141
+ transition: all 0.3s ease;
142
+ }
143
+
144
+ .custom-nav .nav-icon {
145
+ font-size: 1.2rem;
146
+ margin-bottom: 3px;
147
+ transition: all 0.3s ease;
148
+ }
149
+
150
+ .custom-nav .nav-text {
151
+ font-size: 0.85rem;
152
+ font-weight: 500;
153
+ opacity: 0.8;
154
+ }
155
+
156
+ .custom-nav .nav-link:hover,
157
+ .custom-nav .nav-link.active {
158
+ background: rgba(255, 255, 255, 0.15);
159
+ transform: translateY(-3px);
160
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
161
+ }
162
+
163
+ .custom-nav .nav-link:hover .nav-icon,
164
+ .custom-nav .nav-link.active .nav-icon {
165
+ color: var(--primary-light);
166
+ transform: scale(1.2);
167
+ }
168
+
169
+ .custom-nav .nav-link::after {
170
+ content: '';
171
+ position: absolute;
172
+ bottom: 5px;
173
+ left: 50%;
174
+ width: 0;
175
+ height: 2px;
176
+ background: var(--primary-light);
177
+ transform: translateX(-50%);
178
+ transition: width 0.3s ease;
179
+ }
180
+
181
+ .custom-nav .nav-link:hover::after,
182
+ .custom-nav .nav-link.active::after {
183
+ width: 50%;
184
+ }
185
+
186
+ /* ====== Hero Section ====== */
187
+ .hero {
188
+ height: 100vh;
189
+ min-height: 700px;
190
+ display: flex;
191
+ align-items: center;
192
+ position: relative;
193
+ overflow: hidden;
194
+ background-color: transparent;
195
+ padding-bottom: 100px;
196
+ }
197
+
198
+ .hero h1 {
199
+ font-size: 56px;
200
+ font-weight: 700;
201
+ margin-bottom: 20px;
202
+ text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
203
+ }
204
+
205
+ .hero .lead {
206
+ font-size: 18px;
207
+ margin-bottom: 30px;
208
+ }
209
+
210
+ .hero-image {
211
+ position: relative;
212
+ z-index: 1;
213
+ text-align: center;
214
+ }
215
+
216
+ .hero-img {
217
+ max-width: 90%;
218
+ box-shadow: var(--shadow-lg);
219
+ border: 5px solid rgba(255, 255, 255, 0.2);
220
+ animation: float 6s ease-in-out infinite;
221
+ }
222
+
223
+ @keyframes float {
224
+ 0% { transform: translateY(0px); }
225
+ 50% { transform: translateY(-20px); }
226
+ 100% { transform: translateY(0px); }
227
+ }
228
+
229
+ .wave {
230
+ position: absolute;
231
+ bottom: -10px;
232
+ left: 0;
233
+ width: 100%;
234
+ z-index: 2;
235
+ }
236
+
237
+ /* ====== About Section ====== */
238
+ .about {
239
+ background-color: #fff;
240
+ }
241
+
242
+ .feature-card {
243
+ background: #fff;
244
+ border-radius: 15px;
245
+ padding: 30px;
246
+ text-align: center;
247
+ box-shadow: var(--shadow);
248
+ height: 100%;
249
+ transition: var(--transition);
250
+ }
251
+
252
+ .feature-card:hover {
253
+ transform: translateY(-10px);
254
+ box-shadow: var(--shadow-lg);
255
+ }
256
+
257
+ .icon-wrapper {
258
+ width: 80px;
259
+ height: 80px;
260
+ margin: 0 auto 20px;
261
+ display: flex;
262
+ align-items: center;
263
+ justify-content: center;
264
+ background-color: rgba(8, 145, 178, 0.1);
265
+ border-radius: 50%;
266
+ }
267
+
268
+ .icon-wrapper i {
269
+ font-size: 36px;
270
+ }
271
+
272
+ .feature-card h4 {
273
+ margin-bottom: 15px;
274
+ color: var(--dark);
275
+ }
276
+
277
+ .img-wrapper {
278
+ border-radius: 15px;
279
+ overflow: hidden;
280
+ }
281
+
282
+ .floating-badge {
283
+ position: absolute;
284
+ top: 20px;
285
+ right: -15px;
286
+ background: var(--primary);
287
+ color: white;
288
+ padding: 10px 20px;
289
+ border-radius: 30px;
290
+ box-shadow: var(--shadow);
291
+ font-weight: 600;
292
+ }
293
+
294
+ .parameters-grid {
295
+ display: grid;
296
+ grid-template-columns: repeat(3, 1fr);
297
+ gap: 10px;
298
+ margin-top: 20px;
299
+ }
300
+
301
+ .parameter {
302
+ background-color: rgba(8, 145, 178, 0.1);
303
+ padding: 10px;
304
+ border-radius: 5px;
305
+ text-align: center;
306
+ font-weight: 500;
307
+ transition: var(--transition);
308
+ }
309
+
310
+ .parameter:hover {
311
+ background-color: var(--primary);
312
+ color: white;
313
+ transform: scale(1.05);
314
+ }
315
+
316
+ /* ====== Water Test Section ====== */
317
+ .water-test {
318
+ background-color: #f9f9f9;
319
+ position: relative;
320
+ overflow: hidden;
321
+ }
322
+
323
+ .test-bg {
324
+ position: absolute;
325
+ top: 0;
326
+ left: 0;
327
+ width: 100%;
328
+ height: 100%;
329
+ background: url('https://source.unsplash.com/1600x900/?water,pattern') no-repeat center center;
330
+ background-size: cover;
331
+ opacity: 0.05;
332
+ z-index: 0;
333
+ }
334
+
335
+ .test-card {
336
+ background: white;
337
+ border-radius: 20px;
338
+ box-shadow: var(--shadow-lg);
339
+ padding: 40px;
340
+ position: relative;
341
+ z-index: 1;
342
+ }
343
+
344
+ .form-floating .form-control {
345
+ border-radius: 10px;
346
+ height: 60px;
347
+ border: 2px solid #e1e1e1;
348
+ }
349
+
350
+ .form-floating .form-control:focus {
351
+ border-color: var(--primary);
352
+ box-shadow: 0 0 0 0.25rem rgba(8, 145, 178, 0.25);
353
+ }
354
+
355
+ .result-box {
356
+ background: white;
357
+ border-radius: 15px;
358
+ box-shadow: var(--shadow);
359
+ overflow: hidden;
360
+ border: 1px solid #e9e9e9;
361
+ }
362
+
363
+ .result-header {
364
+ padding: 15px 25px;
365
+ background: var(--primary);
366
+ color: white;
367
+ }
368
+
369
+ .result-body {
370
+ padding: 25px;
371
+ }
372
+
373
+ .gauge-container {
374
+ padding: 20px 0;
375
+ }
376
+
377
+ .gauge {
378
+ width: 150px;
379
+ height: 75px;
380
+ border-radius: 75px 75px 0 0;
381
+ background-color: #e0e0e0;
382
+ position: relative;
383
+ overflow: hidden;
384
+ margin: 0 auto;
385
+ }
386
+
387
+ .gauge-fill {
388
+ position: absolute;
389
+ bottom: 0;
390
+ left: 0;
391
+ width: 100%;
392
+ height: 0%;
393
+ background: linear-gradient(to top, var(--danger), var(--warning), var(--success));
394
+ transition: height 1s cubic-bezier(0.34, 1.56, 0.64, 1);
395
+ }
396
+
397
+ .gauge-center {
398
+ position: absolute;
399
+ bottom: 0;
400
+ left: 0;
401
+ width: 100%;
402
+ height: 100%;
403
+ display: flex;
404
+ align-items: center;
405
+ justify-content: center;
406
+ font-weight: bold;
407
+ font-size: 24px;
408
+ color: #333;
409
+ }
410
+
411
+ .gauge-label {
412
+ margin-top: 10px;
413
+ font-weight: 600;
414
+ color: var(--gray);
415
+ }
416
+
417
+ .result-info {
418
+ padding: 0 20px;
419
+ }
420
+
421
+ #result-status {
422
+ margin-bottom: 15px;
423
+ font-weight: 700;
424
+ }
425
+
426
+ #result-status.safe {
427
+ color: var(--success);
428
+ }
429
+
430
+ #result-status.unsafe {
431
+ color: var(--danger);
432
+ }
433
+
434
+ #recommendations-list li {
435
+ margin-bottom: 8px;
436
+ }
437
+
438
+ /* ====== Contact Section ====== */
439
+ .contact {
440
+ background-color: white;
441
+ }
442
+
443
+ .contact-info {
444
+ text-align: center;
445
+ padding: 40px;
446
+ border-radius: 20px;
447
+ box-shadow: var(--shadow-lg);
448
+ height: 100%;
449
+ background-color: #fff;
450
+ position: relative;
451
+ z-index: 1;
452
+ overflow: hidden;
453
+ }
454
+
455
+ .contact-info::before {
456
+ content: '';
457
+ position: absolute;
458
+ top: 0;
459
+ left: 0;
460
+ width: 100%;
461
+ height: 100%;
462
+ background: linear-gradient(135deg, rgba(8, 145, 178, 0.05), rgba(21, 94, 117, 0.1));
463
+ z-index: -1;
464
+ }
465
+
466
+ .profile-img {
467
+ width: 180px;
468
+ height: 180px;
469
+ margin: 0 auto 20px;
470
+ border-radius: 50%;
471
+ overflow: hidden;
472
+ border: 5px solid rgba(8, 145, 178, 0.1);
473
+ position: relative;
474
+ }
475
+
476
+ .profile-img::after {
477
+ content: '';
478
+ position: absolute;
479
+ top: -10px;
480
+ left: -10px;
481
+ right: -10px;
482
+ bottom: -10px;
483
+ border-radius: 50%;
484
+ border: 2px solid var(--primary-light);
485
+ animation: pulseRing 2s infinite;
486
+ }
487
+
488
+ @keyframes pulseRing {
489
+ 0% {
490
+ transform: scale(0.95);
491
+ opacity: 1;
492
+ }
493
+ 70% {
494
+ transform: scale(1.1);
495
+ opacity: 0.7;
496
+ }
497
+ 100% {
498
+ transform: scale(0.95);
499
+ opacity: 1;
500
+ }
501
+ }
502
+
503
+ .profile-img img {
504
+ width: 100%;
505
+ height: 100%;
506
+ object-fit: cover;
507
+ border-radius: 50%;
508
+ }
509
+
510
+ .social-links {
511
+ display: flex;
512
+ justify-content: center;
513
+ gap: 15px;
514
+ margin-top: 25px;
515
+ }
516
+
517
+ .social-icon {
518
+ display: flex;
519
+ align-items: center;
520
+ justify-content: center;
521
+ width: 45px;
522
+ height: 45px;
523
+ border-radius: 50%;
524
+ color: white;
525
+ transition: var(--transition);
526
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
527
+ }
528
+
529
+ .social-icon:hover {
530
+ transform: translateY(-5px) rotate(10deg);
531
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
532
+ }
533
+
534
+ .facebook {
535
+ background-color: #3b5998;
536
+ }
537
+
538
+ .linkedin {
539
+ background-color: #0077b5;
540
+ }
541
+
542
+ .github {
543
+ background-color: #333;
544
+ }
545
+
546
+ .instagram {
547
+ background: linear-gradient(45deg, #f09433, #e6683c, #dc2743, #cc2366, #bc1888);
548
+ }
549
+
550
+ .student-id {
551
+ background-color: var(--primary-light);
552
+ color: var(--dark);
553
+ padding: 5px 15px;
554
+ border-radius: 20px;
555
+ font-weight: 600;
556
+ display: inline-block;
557
+ margin-top: -5px;
558
+ margin-bottom: 10px;
559
+ }
560
+
561
+ /* ====== Footer ====== */
562
+ .footer {
563
+ background-color: var(--dark);
564
+ color: var(--light);
565
+ }
566
+
567
+ /* ====== Responsive Styles ====== */
568
+ @media (max-width: 991px) {
569
+ .hero h1 {
570
+ font-size: 42px;
571
+ }
572
+
573
+ .hero-image {
574
+ margin-top: 50px;
575
+ }
576
+
577
+ .test-card {
578
+ padding: 30px 20px;
579
+ }
580
+
581
+ .custom-nav {
582
+ gap: 5px;
583
+ }
584
+
585
+ .custom-nav .nav-link {
586
+ padding: 8px 10px;
587
+ }
588
+ }
589
+
590
+ @media (max-width: 767px) {
591
+ section {
592
+ padding: 60px 0;
593
+ }
594
+
595
+ .hero h1 {
596
+ font-size: 36px;
597
+ text-align: center;
598
+ }
599
+
600
+ .hero .lead {
601
+ text-align: center;
602
+ }
603
+
604
+ .hero .btn {
605
+ display: block;
606
+ margin: 0 auto;
607
+ }
608
+
609
+ .feature-card {
610
+ margin-bottom: 30px;
611
+ }
612
+
613
+ .test-card {
614
+ padding: 25px 15px;
615
+ }
616
+
617
+ .custom-nav {
618
+ flex-wrap: wrap;
619
+ justify-content: center;
620
+ }
621
+
622
+ .custom-nav .nav-item {
623
+ width: calc(50% - 10px);
624
+ }
625
+
626
+ .custom-nav .nav-link {
627
+ margin-bottom: 10px;
628
+ }
629
+ }
630
+
631
+ /* ====== Animation Classes ====== */
632
+ .fade-in {
633
+ animation: fadeIn 1s ease forwards;
634
+ }
635
+
636
+ .slide-up {
637
+ animation: slideUp 0.8s ease forwards;
638
+ }
639
+
640
+ @keyframes fadeIn {
641
+ from { opacity: 0; }
642
+ to { opacity: 1; }
643
+ }
644
+
645
+ @keyframes slideUp {
646
+ from {
647
+ opacity: 0;
648
+ transform: translateY(50px);
649
+ }
650
+ to {
651
+ opacity: 1;
652
+ transform: translateY(0);
653
+ }
654
+ }
655
+
656
+ @keyframes waterRipple {
657
+ 0% {
658
+ box-shadow: 0 0 0 0 rgba(34, 211, 238, 0.5);
659
+ }
660
+ 70% {
661
+ box-shadow: 0 0 0 20px rgba(34, 211, 238, 0);
662
+ }
663
+ 100% {
664
+ box-shadow: 0 0 0 0 rgba(34, 211, 238, 0);
665
+ }
666
+ }
static/js/main.js ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', function() {
2
+ // Initialize AOS Animation Library
3
+ AOS.init({
4
+ duration: 800,
5
+ easing: 'ease',
6
+ once: true,
7
+ });
8
+
9
+ // Initialize Particles.js for background animation
10
+ particlesJS('particles-js', {
11
+ "particles": {
12
+ "number": {
13
+ "value": 80,
14
+ "density": {
15
+ "enable": true,
16
+ "value_area": 800
17
+ }
18
+ },
19
+ "color": {
20
+ "value": "#ffffff"
21
+ },
22
+ "shape": {
23
+ "type": "circle",
24
+ "stroke": {
25
+ "width": 0,
26
+ "color": "#000000"
27
+ },
28
+ "polygon": {
29
+ "nb_sides": 5
30
+ }
31
+ },
32
+ "opacity": {
33
+ "value": 0.5,
34
+ "random": false,
35
+ "anim": {
36
+ "enable": false,
37
+ "speed": 1,
38
+ "opacity_min": 0.1,
39
+ "sync": false
40
+ }
41
+ },
42
+ "size": {
43
+ "value": 3,
44
+ "random": true,
45
+ "anim": {
46
+ "enable": false,
47
+ "speed": 40,
48
+ "size_min": 0.1,
49
+ "sync": false
50
+ }
51
+ },
52
+ "line_linked": {
53
+ "enable": true,
54
+ "distance": 150,
55
+ "color": "#ffffff",
56
+ "opacity": 0.4,
57
+ "width": 1
58
+ },
59
+ "move": {
60
+ "enable": true,
61
+ "speed": 2,
62
+ "direction": "none",
63
+ "random": false,
64
+ "straight": false,
65
+ "out_mode": "out",
66
+ "bounce": false,
67
+ "attract": {
68
+ "enable": false,
69
+ "rotateX": 600,
70
+ "rotateY": 1200
71
+ }
72
+ }
73
+ },
74
+ "interactivity": {
75
+ "detect_on": "canvas",
76
+ "events": {
77
+ "onhover": {
78
+ "enable": true,
79
+ "mode": "grab"
80
+ },
81
+ "onclick": {
82
+ "enable": true,
83
+ "mode": "push"
84
+ },
85
+ "resize": true
86
+ },
87
+ "modes": {
88
+ "grab": {
89
+ "distance": 140,
90
+ "line_linked": {
91
+ "opacity": 1
92
+ }
93
+ },
94
+ "bubble": {
95
+ "distance": 400,
96
+ "size": 40,
97
+ "duration": 2,
98
+ "opacity": 8,
99
+ "speed": 3
100
+ },
101
+ "repulse": {
102
+ "distance": 200,
103
+ "duration": 0.4
104
+ },
105
+ "push": {
106
+ "particles_nb": 4
107
+ },
108
+ "remove": {
109
+ "particles_nb": 2
110
+ }
111
+ }
112
+ },
113
+ "retina_detect": true
114
+ });
115
+
116
+ // Navbar scroll effect
117
+ window.addEventListener('scroll', function() {
118
+ const navbar = document.querySelector('.navbar');
119
+ if (window.scrollY > 100) {
120
+ navbar.classList.add('scrolled');
121
+ } else {
122
+ navbar.classList.remove('scrolled');
123
+ }
124
+ });
125
+
126
+ // Active menu item highlighting
127
+ const sections = document.querySelectorAll('section');
128
+ const navLinks = document.querySelectorAll('.navbar-nav .nav-link');
129
+
130
+ window.addEventListener('scroll', () => {
131
+ let current = '';
132
+
133
+ sections.forEach(section => {
134
+ const sectionTop = section.offsetTop;
135
+ const sectionHeight = section.clientHeight;
136
+ if (pageYOffset >= (sectionTop - 150)) {
137
+ current = section.getAttribute('id');
138
+ }
139
+ });
140
+
141
+ navLinks.forEach(link => {
142
+ link.classList.remove('active');
143
+ if (link.getAttribute('href').substring(1) === current) {
144
+ link.classList.add('active');
145
+ }
146
+ });
147
+ });
148
+
149
+ // Smooth scrolling for anchor links
150
+ document.querySelectorAll('a[href^="#"]').forEach(anchor => {
151
+ anchor.addEventListener('click', function(e) {
152
+ e.preventDefault();
153
+ const target = document.querySelector(this.getAttribute('href'));
154
+ if (target) {
155
+ window.scrollTo({
156
+ top: target.offsetTop - 80,
157
+ behavior: 'smooth'
158
+ });
159
+ }
160
+ });
161
+ });
162
+
163
+ // ==========================================================
164
+ // === UPDATED WATER QUALITY PREDICTION FORM SUBMISSION ===
165
+ // ==========================================================
166
+ const waterForm = document.getElementById('water-form');
167
+ const resultContainer = document.getElementById('result-container');
168
+
169
+ // All the result elements
170
+ const resultStatus = document.getElementById('result-status');
171
+ const resultMessage = document.getElementById('result-message');
172
+ const resultProbability = document.getElementById('result-probability');
173
+ const gaugeFill = document.getElementById('gauge-fill');
174
+ const recommendationsList = document.getElementById('recommendations-list');
175
+
176
+ if (waterForm) {
177
+ waterForm.addEventListener('submit', function(e) {
178
+ e.preventDefault(); // Stop the form from reloading
179
+
180
+ // Show loading state (using your existing IDs)
181
+ resultContainer.style.display = 'block';
182
+ resultStatus.textContent = 'Analyzing water parameters...';
183
+ resultStatus.className = ''; // Reset color
184
+ resultMessage.textContent = 'Please wait while we process your results.';
185
+ recommendationsList.innerHTML = '';
186
+ gaugeFill.style.height = '0%';
187
+ gaugeFill.style.background = '#e0e0e0'; // Reset gauge color
188
+ resultProbability.textContent = '...';
189
+
190
+ // 1. Collect form data into a JavaScript object
191
+ const formData = new FormData(waterForm);
192
+ const data = {};
193
+ formData.forEach((value, key) => {
194
+ data[key] = value;
195
+ });
196
+
197
+ // 2. Send data as JSON using jQuery.ajax (as in your original file)
198
+ $.ajax({
199
+ type: 'POST',
200
+ url: '/predict', // The endpoint in our Flask app
201
+ data: JSON.stringify(data), // Send as a JSON string
202
+ contentType: 'application/json', // Tell the server we are sending JSON
203
+ success: function(response) {
204
+ // Simulate processing delay for better UX
205
+ setTimeout(() => {
206
+ displayResults(response);
207
+ }, 1000); // 1-second delay
208
+ },
209
+ error: function(error) {
210
+ console.log(error);
211
+ resultContainer.style.display = 'block';
212
+ resultStatus.textContent = 'Prediction Error';
213
+ resultStatus.className = 'unsafe'; // Use 'unsafe' style for errors
214
+ resultMessage.textContent = 'An error occurred. Please check your input values and try again.';
215
+ }
216
+ });
217
+ });
218
+ }
219
+
220
+ /**
221
+ * This function takes the JSON response from Flask and updates the HTML.
222
+ */
223
+ function displayResults(response) {
224
+ if (response.error) {
225
+ resultStatus.textContent = 'Error';
226
+ resultStatus.className = 'unsafe';
227
+ resultMessage.textContent = response.error;
228
+ return;
229
+ }
230
+
231
+ const probability = response.probability;
232
+ const potability = response.potability;
233
+ let confidence = 0;
234
+ let recommendationsHTML = '';
235
+
236
+ if (potability === 1) {
237
+ // --- SAFE (POTABLE) ---
238
+ confidence = probability * 100;
239
+ resultStatus.textContent = 'Result: Potable (Safe)';
240
+ resultStatus.className = 'safe'; // Uses your --success color
241
+ resultMessage.textContent = 'The model predicts this water is safe for consumption.';
242
+ gaugeFill.style.background = 'var(--success)';
243
+ recommendationsHTML = `
244
+ <li>Confidence in this result is high.</li>
245
+ <li>Maintain regular filtering and testing schedules.</li>
246
+ <li><strong>Disclaimer:</strong> This is an AI prediction and not a substitute for a professional lab test.</li>
247
+ `;
248
+
249
+ } else {
250
+ // --- UNSAFE (NOT POTABLE) ---
251
+ confidence = (1 - probability) * 100; // Confidence in the "unsafe" prediction
252
+ resultStatus.textContent = 'Result: Not Potable (Unsafe)';
253
+ resultStatus.className = 'unsafe'; // Uses your --danger color
254
+ resultMessage.textContent = 'The model predicts this water is unsafe for consumption.';
255
+ gaugeFill.style.background = 'var(--danger)';
256
+ recommendationsHTML = `
257
+ <li>It is strongly advised **not** to drink this water.</li>
258
+ <li>Boil water before use or use a certified water filter.</li>
259
+ <li>Consult a local health authority for professional testing.</li>
260
+ <li><strong>Disclaimer:</strong> This is an AI prediction. Prioritize safety.</li>
261
+ `;
262
+ }
263
+
264
+ // Update gauge
265
+ resultProbability.textContent = `${confidence.toFixed(0)}%`;
266
+ gaugeFill.style.height = `${confidence.toFixed(0)}%`;
267
+
268
+ // Update recommendations
269
+ recommendationsList.innerHTML = recommendationsHTML;
270
+
271
+ // Scroll to the result
272
+ resultContainer.scrollIntoView({ behavior: 'smooth', block: 'center' });
273
+ }
274
+
275
+ });
templates/index.html ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Water Quality Prediction | Janith Deshan</title>
7
+
8
+ <!-- Favicon -->
9
+ <link rel="icon" href="https://img.icons8.com/color/48/000000/water-drop.png" type="image/png">
10
+
11
+ <!-- Bootstrap CSS -->
12
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
13
+
14
+ <!-- Font Awesome -->
15
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
16
+
17
+ <!-- Google Fonts -->
18
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
19
+
20
+ <!-- AOS Animation Library -->
21
+ <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
22
+
23
+ <!-- Custom CSS -->
24
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
25
+ </head>
26
+ <body>
27
+ <!-- Particles Background -->
28
+ <div id="particles-js"></div>
29
+
30
+ <!-- Header Section -->
31
+ <header>
32
+ <nav class="navbar navbar-expand-lg navbar-dark">
33
+ <div class="container">
34
+ <a class="navbar-brand" href="#">
35
+ <i class="fas fa-water me-2 text-primary wave-icon"></i>
36
+ <span>AquaTest</span>
37
+ </a>
38
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
39
+ <span class="navbar-toggler-icon"></span>
40
+ </button>
41
+ <div class="collapse navbar-collapse" id="navbarNav">
42
+ <ul class="navbar-nav ms-auto custom-nav">
43
+ <li class="nav-item">
44
+ <a class="nav-link active" href="#home">
45
+ <span class="nav-icon"><i class="fas fa-home"></i></span>
46
+ <span class="nav-text">Home</span>
47
+ </a>
48
+ </li>
49
+ <li class="nav-item">
50
+ <a class="nav-link" href="#about">
51
+ <span class="nav-icon"><i class="fas fa-info-circle"></i></span>
52
+ <span class="nav-text">About</span>
53
+ </a>
54
+ </li>
55
+ <li class="nav-item">
56
+ <a class="nav-link" href="#test">
57
+ <span class="nav-icon"><i class="fas fa-flask"></i></span>
58
+ <span class="nav-text">Test Water</span>
59
+ </a>
60
+ </li>
61
+ <li class="nav-item">
62
+ <a class="nav-link" href="#contact">
63
+ <span class="nav-icon"><i class="fas fa-user"></i></span>
64
+ <span class="nav-text">Profile</span>
65
+ </a>
66
+ </li>
67
+ </ul>
68
+ </div>
69
+ </div>
70
+ </nav>
71
+ </header>
72
+
73
+ <!-- Hero Section -->
74
+ <section id="home" class="hero">
75
+ <div class="container h-100">
76
+ <div class="row h-100 align-items-center">
77
+ <div class="col-lg-6" data-aos="fade-right" data-aos-duration="1000">
78
+ <h1 class="display-4 fw-bold text-white">Water Quality <span class="text-primary">Prediction</span></h1>
79
+ <p class="lead my-4 text-white-50">
80
+ Analyze water parameters and determine potability using machine learning. Get instant results and ensure your water is safe for consumption.
81
+ </p>
82
+ <a href="#test" class="btn btn-primary btn-lg">Test Your Water <i class="fas fa-arrow-right ms-2"></i></a>
83
+ </div>
84
+
85
+ </div>
86
+ </div>
87
+ <div class="wave">
88
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
89
+ <path fill="#ffffff" fill-opacity="1" d="M0,128L60,138.7C120,149,240,171,360,176C480,181,600,171,720,149.3C840,128,960,96,1080,90.7C1200,85,1320,107,1380,117.3L1440,128L1440,320L1380,320C1320,320,1200,320,1080,320C960,320,840,320,720,320C600,320,480,320,360,320C240,320,120,320,60,320L0,320Z"></path>
90
+ </svg>
91
+ </div>
92
+ </section>
93
+
94
+ <!-- About Section -->
95
+ <section id="about" class="about py-5">
96
+ <div class="container">
97
+ <div class="row">
98
+ <div class="col-12 text-center mb-5" data-aos="fade-up">
99
+ <h2 class="section-title">About This Project</h2>
100
+ <div class="section-divider"></div>
101
+ </div>
102
+ </div>
103
+ <div class="row g-4">
104
+ <div class="col-md-4" data-aos="fade-up" data-aos-delay="100">
105
+ <div class="feature-card">
106
+ <div class="icon-wrapper">
107
+ <i class="fas fa-flask text-primary"></i>
108
+ </div>
109
+ <h4>Water Analysis</h4>
110
+ <p>Input water quality parameters to analyze potability using advanced machine learning algorithms.</p>
111
+ </div>
112
+ </div>
113
+ <div class="col-md-4" data-aos="fade-up" data-aos-delay="200">
114
+ <div class="feature-card">
115
+ <div class="icon-wrapper">
116
+ <i class="fas fa-brain text-primary"></i>
117
+ </div>
118
+ <h4>ML Powered</h4>
119
+ <p>Backed by machine learning models trained on thousands of water samples for accurate predictions.</p>
120
+ </div>
121
+ </div>
122
+ <div class="col-md-4" data-aos="fade-up" data-aos-delay="300">
123
+ <div class="feature-card">
124
+ <div class="icon-wrapper">
125
+ <i class="fas fa-tachometer-alt text-primary"></i>
126
+ </div>
127
+ <h4>Instant Results</h4>
128
+ <p>Get immediate assessment of water potability with confidence percentage and recommendations.</p>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ <div class="row mt-5">
133
+ <div class="col-md-6" data-aos="fade-right">
134
+ <div class="img-wrapper position-relative">
135
+ <img src="https://images.unsplash.com/photo-1617155093730-a8bf47be792d?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="Water Testing" class="img-fluid rounded shadow">
136
+ <div class="floating-badge">
137
+ <span>9 Water Parameters</span>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ <div class="col-md-6" data-aos="fade-left">
142
+ <h3 class="mb-4">The Science Behind Water Potability</h3>
143
+ <p>This application analyzes critical water parameters to determine if water is safe for human consumption. The model considers factors like pH level, hardness, TDS, turbidity and more to make an informed prediction.</p>
144
+ <div class="parameters-grid">
145
+ <div class="parameter">pH</div>
146
+ <div class="parameter">Hardness</div>
147
+ <div class="parameter">Solids</div>
148
+ <div class="parameter">Chloramines</div>
149
+ <div class="parameter">Sulfate</div>
150
+ <div class="parameter">Conductivity</div>
151
+ <div class="parameter">Organic Carbon</div>
152
+ <div class="parameter">Trihalomethanes</div>
153
+ <div class="parameter">Turbidity</div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </section>
159
+
160
+ <!-- Water Test Section -->
161
+ <section id="test" class="water-test py-5">
162
+ <div class="test-bg"></div>
163
+ <div class="container">
164
+ <div class="row justify-content-center">
165
+ <div class="col-lg-10">
166
+ <div class="test-card" data-aos="zoom-in">
167
+ <div class="row">
168
+ <div class="col-12 text-center mb-4">
169
+ <h2 class="section-title">Water Quality Test</h2>
170
+ <p class="text-muted">Enter the water parameters to predict potability</p>
171
+ </div>
172
+ </div>
173
+
174
+ <form id="water-form">
175
+ <div class="row g-3">
176
+ <div class="col-md-4">
177
+ <div class="form-floating">
178
+ <input type="number" step="0.01" class="form-control" id="ph" name="ph" placeholder="pH Level" required>
179
+ <label for="ph"><i class="fas fa-vial me-2"></i> pH Level (0-14)</label>
180
+ </div>
181
+ </div>
182
+ <div class="col-md-4">
183
+ <div class="form-floating">
184
+ <input type="number" step="0.01" class="form-control" id="Hardness" name="Hardness" placeholder="Hardness" required>
185
+ <label for="Hardness"><i class="fas fa-gem me-2"></i> Hardness (mg/L)</label>
186
+ </div>
187
+ </div>
188
+ <div class="col-md-4">
189
+ <div class="form-floating">
190
+ <input type="number" step="0.01" class="form-control" id="Solids" name="Solids" placeholder="TDS" required>
191
+ <label for="Solids"><i class="fas fa-microscope me-2"></i> TDS (ppm)</label>
192
+ </div>
193
+ </div>
194
+
195
+ <div class="col-md-4">
196
+ <div class="form-floating">
197
+ <input type="number" step="0.01" class="form-control" id="Chloramines" name="Chloramines" placeholder="Chloramines" required>
198
+ <label for="Chloramines"><i class="fas fa-prescription-bottle me-2"></i> Chloramines (ppm)</label>
199
+ </div>
200
+ </div>
201
+ <div class="col-md-4">
202
+ <div class="form-floating">
203
+ <input type="number" step="0.01" class="form-control" id="Sulfate" name="Sulfate" placeholder="Sulfate" required>
204
+ <label for="Sulfate"><i class="fas fa-atom me-2"></i> Sulfate (mg/L)</label>
205
+ </div>
206
+ </div>
207
+ <div class="col-md-4">
208
+ <div class="form-floating">
209
+ <input type="number" step="0.01" class="form-control" id="Conductivity" name="Conductivity" placeholder="Conductivity" required>
210
+ <label for="Conductivity"><i class="fas fa-bolt me-2"></i> Conductivity (μS/cm)</label>
211
+ </div>
212
+ </div>
213
+
214
+ <div class="col-md-4">
215
+ <div class="form-floating">
216
+ <input type="number" step="0.01" class="form-control" id="Organic_carbon" name="Organic_carbon" placeholder="Organic Carbon" required>
217
+ <label for="Organic_carbon"><i class="fas fa-leaf me-2"></i> Organic Carbon (ppm)</label>
218
+ </div>
219
+ </div>
220
+ <div class="col-md-4">
221
+ <div class="form-floating">
222
+ <input type="number" step="0.01" class="form-control" id="Trihalomethanes" name="Trihalomethanes" placeholder="Trihalomethanes" required>
223
+ <label for="Trihalomethanes"><i class="fas fa-biohazard me-2"></i> Trihalomethanes (μg/L)</label>
224
+ </div>
225
+ </div>
226
+ <div class="col-md-4">
227
+ <div class="form-floating">
228
+ <input type="number" step="0.01" class="form-control" id="Turbidity" name="Turbidity" placeholder="Turbidity" required>
229
+ <label for="Turbidity"><i class="fas fa-cloud me-2"></i> Turbidity (NTU)</label>
230
+ </div>
231
+ </div>
232
+
233
+ <div class="col-12 text-center mt-4">
234
+ <button type="submit" class="btn btn-primary btn-lg px-5">
235
+ <i class="fas fa-flask-vial me-2"></i> Analyze Water
236
+ </button>
237
+ </div>
238
+ </div>
239
+ </form>
240
+
241
+ <div class="mt-4" id="result-container" style="display: none;">
242
+ <div class="result-box">
243
+ <div class="result-header">
244
+ <h4 id="result-title">Prediction Result</h4>
245
+ </div>
246
+ <div class="result-body">
247
+ <div class="row">
248
+ <div class="col-md-4 text-center">
249
+ <div class="gauge-container">
250
+ <div class="gauge">
251
+ <div class="gauge-fill" id="gauge-fill"></div>
252
+ <div class="gauge-center">
253
+ <span id="result-probability">0%</span>
254
+ </div>
255
+ </div>
256
+ <p class="gauge-label">Confidence</p>
257
+ </div>
258
+ </div>
259
+ <div class="col-md-8">
260
+ <div class="result-info">
261
+ <h5 id="result-status">Testing...</h5>
262
+ <p id="result-message" class="lead"></p>
263
+ <div id="result-recommendations" class="mt-3">
264
+ <h6><i class="fas fa-info-circle me-2"></i> Recommendations</h6>
265
+ <ul id="recommendations-list">
266
+ <!-- Recommendations will be added dynamically -->
267
+ </ul>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ </div>
273
+ </div>
274
+ </div>
275
+ </div>
276
+ </div>
277
+ </div>
278
+ </div>
279
+ </section>
280
+
281
+ <!-- Contact/Profile Section -->
282
+ <section id="contact" class="contact py-5">
283
+ <div class="container">
284
+ <div class="row text-center mb-5">
285
+ <div class="col-12" data-aos="fade-up">
286
+ <h2 class="section-title">Developer Profile</h2>
287
+ <div class="section-divider"></div>
288
+ </div>
289
+ </div>
290
+ <div class="row justify-content-center">
291
+ <div class="col-md-8" data-aos="fade-up">
292
+ <div class="contact-info">
293
+ <div class="profile-img">
294
+ <img src="https://avatars.githubusercontent.com/u/138566861?v=4" alt="Janith Deshan" class="img-fluid rounded-circle">
295
+ </div>
296
+ <h3 class="mt-4 mb-3">Janith Deshan</h3>
297
+ <p class="lead">Water Quality Researcher & ML Engineer</p>
298
+ <p class="student-id">IT24102137</p>
299
+ <p><i class="fas fa-envelope me-2"></i> janithmihijaya123@gmail.com</p>
300
+
301
+ <div class="social-links mt-4">
302
+ <a href="https://www.facebook.com/janith.deshan.186" target="_blank" class="social-icon facebook">
303
+ <i class="fab fa-facebook-f"></i>
304
+ </a>
305
+ <a href="https://www.linkedin.com/in/janithdeshan/" target="_blank" class="social-icon linkedin">
306
+ <i class="fab fa-linkedin-in"></i>
307
+ </a>
308
+ <a href="https://github.com/janiyax35" target="_blank" class="social-icon github">
309
+ <i class="fab fa-github"></i>
310
+ </a>
311
+ <a href="https://www.instagram.com/janith_deshan11/" target="_blank" class="social-icon instagram">
312
+ <i class="fab fa-instagram"></i>
313
+ </a>
314
+ </div>
315
+ </div>
316
+ </div>
317
+ </div>
318
+ </div>
319
+ </section>
320
+
321
+ <!-- Footer -->
322
+ <footer class="footer py-4">
323
+ <div class="container">
324
+ <div class="row align-items-center">
325
+ <div class="col-md-6 text-center text-md-start">
326
+ <p class="mb-0">&copy; 2025 Water Quality Prediction | Developed by <span class="fw-bold">Janith Deshan</span></p>
327
+ </div>
328
+ <div class="col-md-6 text-center text-md-end">
329
+ <p class="mb-0">IT24102137</p>
330
+ </div>
331
+ </div>
332
+ </div>
333
+ </footer>
334
+
335
+ <!-- Bootstrap JS -->
336
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
337
+
338
+ <!-- jQuery -->
339
+ <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
340
+
341
+ <!-- AOS Animation Library -->
342
+ <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
343
+
344
+ <!-- Particles.js -->
345
+ <script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
346
+
347
+ <!-- Custom JS -->
348
+ <script src="{{ url_for('static', filename='js/main.js') }}"></script>
349
+ </body>
350
+ </html>
water_quality_model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:04ca7ec3bda533dd4039f67b43001cb687c956a1eea4a8b10f05b580b2bae5fd
3
+ size 246744
water_quality_model_ALT.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9f99b44eaf30caaa94fbd517c8bb480e0378fa7c35f9dcdcab5facd0607c4f26
3
+ size 50008