themehmi commited on
Commit
31a4ac5
·
verified ·
1 Parent(s): 1cb75c9

Upload 3 files

Browse files
Files changed (2) hide show
  1. app.py +30 -11
  2. templates/index.html +401 -169
app.py CHANGED
@@ -22,7 +22,7 @@ watchlist_col = db["watchlist"]
22
  # STEAM SEARCH BY NAME HELPER
23
  def get_app_id_from_name(game_name):
24
  """Searches Steam for a game name and returns its App ID"""
25
- search_url = f"https://store.steampowered.com/api/storesearch/?term={game_name}&l=english&cc=US"
26
  try:
27
  response = requests.get(search_url, timeout=5)
28
  data = response.json()
@@ -35,7 +35,7 @@ def get_app_id_from_name(game_name):
35
 
36
  # STEAM SCRAPER FUNCTION
37
  def scrape_steam_price(app_id):
38
- url = f"https://store.steampowered.com/app/{app_id}/"
39
  cookies = {'birthtime': '283993201', 'lastagecheckage': '1-January-1979'}
40
  headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
41
 
@@ -52,9 +52,15 @@ def scrape_steam_price(app_id):
52
 
53
  if price_element:
54
  raw_price = price_element.text.strip()
55
- numeric_parts = re.findall(r'\d+\.?\d*', raw_price.replace(',', '.'))
 
 
56
  if numeric_parts:
57
- return {"title": game_title, "price_str": raw_price, "price_float": float(numeric_parts[-1])}
 
 
 
 
58
  elif "free" in raw_price.lower():
59
  return {"title": game_title, "price_str": "Free to Play", "price_float": 0.0}
60
  return None
@@ -70,7 +76,7 @@ def background_price_checker():
70
  for game in games:
71
  app_id = game["_id"]
72
  alert_type = game.get("alert_type")
73
- threshold = game.get("threshold", 0.0)
74
  triggered = game.get("triggered", 0)
75
 
76
  data = scrape_steam_price(app_id)
@@ -88,8 +94,12 @@ def background_price_checker():
88
  "current_price": current_price,
89
  "price_str": price_str
90
  }
91
- if is_triggered == 1 and triggered == 0:
92
- update_fields["triggered"] = 1
 
 
 
 
93
 
94
  watchlist_col.update_one({"_id": app_id}, {"$set": update_fields})
95
  except Exception as e:
@@ -106,9 +116,9 @@ def index():
106
  user_input = request.form.get('app_id', '').strip()
107
  alert_type = request.form.get('alert_type')
108
  try:
109
- threshold = float(request.form.get('threshold', 0))
110
  except ValueError:
111
- threshold = 0.0
112
 
113
  if user_input:
114
  # Check if input is a Name or an ID
@@ -122,6 +132,15 @@ def index():
122
  else:
123
  data = scrape_steam_price(app_id)
124
  if data:
 
 
 
 
 
 
 
 
 
125
  try:
126
  watchlist_col.replace_one(
127
  {"_id": app_id},
@@ -135,7 +154,7 @@ def index():
135
  "price_str": data['price_str'],
136
  "alert_type": alert_type,
137
  "threshold": threshold,
138
- "triggered": 0
139
  },
140
  upsert=True
141
  )
@@ -160,7 +179,7 @@ def index():
160
  doc.get("initial_price_str", ""),
161
  doc.get("price_str", ""),
162
  doc.get("alert_type", ""),
163
- doc.get("threshold", 0.0),
164
  doc.get("triggered", 0),
165
  doc.get("initial_price", 0.0),
166
  doc.get("current_price", 0.0)
 
22
  # STEAM SEARCH BY NAME HELPER
23
  def get_app_id_from_name(game_name):
24
  """Searches Steam for a game name and returns its App ID"""
25
+ search_url = f"https://store.steampowered.com/api/storesearch/?term={game_name}&l=english&cc=IN"
26
  try:
27
  response = requests.get(search_url, timeout=5)
28
  data = response.json()
 
35
 
36
  # STEAM SCRAPER FUNCTION
37
  def scrape_steam_price(app_id):
38
+ url = f"https://store.steampowered.com/app/{app_id}/?cc=in"
39
  cookies = {'birthtime': '283993201', 'lastagecheckage': '1-January-1979'}
40
  headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
41
 
 
52
 
53
  if price_element:
54
  raw_price = price_element.text.strip()
55
+ # Clean commas (Indian thousands separator)
56
+ clean_price = raw_price.replace(',', '')
57
+ numeric_parts = re.findall(r'\d+\.?\d*', clean_price)
58
  if numeric_parts:
59
+ price_float = float(numeric_parts[0])
60
+ price_str = raw_price
61
+ if '₹' not in price_str and 'rs' not in price_str.lower():
62
+ price_str = f"₹ {price_float:,.2f}"
63
+ return {"title": game_title, "price_str": price_str, "price_float": price_float}
64
  elif "free" in raw_price.lower():
65
  return {"title": game_title, "price_str": "Free to Play", "price_float": 0.0}
66
  return None
 
76
  for game in games:
77
  app_id = game["_id"]
78
  alert_type = game.get("alert_type")
79
+ threshold = int(game.get("threshold", 0))
80
  triggered = game.get("triggered", 0)
81
 
82
  data = scrape_steam_price(app_id)
 
94
  "current_price": current_price,
95
  "price_str": price_str
96
  }
97
+ if is_triggered == 1:
98
+ if triggered == 0:
99
+ update_fields["triggered"] = 1
100
+ else:
101
+ if triggered != 0:
102
+ update_fields["triggered"] = 0
103
 
104
  watchlist_col.update_one({"_id": app_id}, {"$set": update_fields})
105
  except Exception as e:
 
116
  user_input = request.form.get('app_id', '').strip()
117
  alert_type = request.form.get('alert_type')
118
  try:
119
+ threshold = int(float(request.form.get('threshold', 0)))
120
  except ValueError:
121
+ threshold = 0
122
 
123
  if user_input:
124
  # Check if input is a Name or an ID
 
132
  else:
133
  data = scrape_steam_price(app_id)
134
  if data:
135
+ # Determine initial triggered state based on the current scraped price
136
+ current_price = data['price_float']
137
+ price_str = data['price_str']
138
+ is_triggered = 0
139
+ if alert_type == "target_price" and current_price <= threshold:
140
+ is_triggered = 1
141
+ elif alert_type == "discount_drop" and "discount" in price_str.lower():
142
+ is_triggered = 1
143
+
144
  try:
145
  watchlist_col.replace_one(
146
  {"_id": app_id},
 
154
  "price_str": data['price_str'],
155
  "alert_type": alert_type,
156
  "threshold": threshold,
157
+ "triggered": is_triggered
158
  },
159
  upsert=True
160
  )
 
179
  doc.get("initial_price_str", ""),
180
  doc.get("price_str", ""),
181
  doc.get("alert_type", ""),
182
+ int(doc.get("threshold", 0)),
183
  doc.get("triggered", 0),
184
  doc.get("initial_price", 0.0),
185
  doc.get("current_price", 0.0)
templates/index.html CHANGED
@@ -1,173 +1,401 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <!-- This meta tag is crucial for mobile responsiveness -->
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
7
  <title>Live Steam Price Monitor</title>
8
  <style>
9
- * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', sans-serif; }
10
-
 
 
 
 
 
11
  body {
12
  /* Authentic Steam dark gradient overlay with gaming poster background */
13
- background: linear-gradient(rgba(27, 40, 56, 0.88), rgba(21, 25, 31, 0.95)),
14
- url('https://www.pcworld.com/wp-content/uploads/2025/06/Steam-logo-on-top-of-a-background-full-of-Steam-library-games.jpg?quality=50&strip=all') no-repeat center center fixed;
15
- background-size: cover; color: #e0e0e0; padding: 40px 20px;
16
- display: flex; flex-direction: column; align-items: center; min-height: 100vh;
 
 
 
 
 
17
  }
18
 
19
- .container, .watchlist-container {
20
- background: rgba(35, 35, 35, 0.65); backdrop-filter: blur(12px);
21
- border: 1px solid rgba(255, 255, 255, 0.08); padding: 30px;
22
- border-radius: 12px; width: 100%; max-width: 650px;
23
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6); margin-bottom: 20px;
 
 
 
 
 
 
24
  }
25
 
26
  /* Flexbox header to align logo and text */
27
  .main-title {
28
- display: flex; justify-content: center; align-items: center; gap: 12px;
29
- margin-bottom: 10px; color: #fff; font-size: 2em; font-weight: bold;
30
- }
31
-
32
- .subtitle { text-align: center; font-size: 0.9em; color: #aaa; margin-bottom: 25px; }
33
- .form-group { margin-bottom: 15px; }
34
- label { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; font-size: 0.9em; color: #ccc; }
35
-
36
- input, select, button {
37
- width: 100%; padding: 12px; background: rgba(0, 0, 0, 0.4);
38
- border: 1px solid #444; border-radius: 6px; color: #fff; font-size: 1em; outline: none;
39
- }
40
- input:focus, select:focus { border-color: #66c0f4; }
41
-
42
- .btn-main { background-color: #1b2838; border-color: #66c0f4; color: #66c0f4; font-weight: bold; cursor: pointer; transition: 0.3s; margin-top: 10px; }
43
- .btn-main:hover { background-color: #66c0f4; color: #1b2838; }
44
-
45
- .search-bar { display: flex; gap: 10px; margin-bottom: 20px; }
46
- .search-bar input { flex-grow: 1; }
47
- .search-bar button { width: auto; margin-top: 0; padding: 12px 25px; cursor: pointer; background: #333; border: 1px solid #555; color: white;}
48
- .search-bar button:hover { background: #555; }
49
-
50
- .game-item { display: flex; flex-direction: column; padding: 15px; background: rgba(0,0,0,0.3); border-radius: 6px; margin-bottom: 12px; border-left: 4px solid #444; }
51
- .game-item.alert-active { border-left-color: #5cb85c; background: rgba(92, 184, 92, 0.1); }
52
- .game-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #444; padding-bottom: 10px; margin-bottom: 10px; }
53
-
54
- .btn-delete { background: rgba(255, 74, 74, 0.1); color: #ff4a4a; border: 1px solid #ff4a4a; padding: 6px 12px; border-radius: 4px; font-size: 0.85em; cursor: pointer; transition: 0.2s; margin-top: 0; width: auto; }
55
- .btn-delete:hover { background: #ff4a4a; color: white; }
56
- .header-actions { display: flex; gap: 15px; align-items: center; }
57
-
58
- .price-compare { display: flex; justify-content: space-between; font-size: 0.95em; gap: 10px; }
59
- .price-box { text-align: center; width: 48%; background: rgba(255,255,255,0.05); padding: 12px 8px; border-radius: 4px; }
60
- .price-dropped { color: #5cb85c; font-weight: bold; font-size: 1.1em;}
61
- .price-same { color: #ccc; font-size: 1.1em; }
62
-
63
- .error { background: rgba(255, 74, 74, 0.2); border-left: 4px solid #ff4a4a; padding: 15px; margin-bottom: 20px; border-radius: 4px; }
64
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  .popup-notification {
66
- position: fixed; bottom: 20px; right: 20px; background: rgba(30, 45, 60, 0.95);
67
- border: 2px solid #5cb85c; padding: 20px; border-radius: 8px; width: 320px;
68
- display: none; z-index: 1000; animation: slideIn 0.5s ease-out;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  }
70
- @keyframes slideIn { from { transform: translateX(120%); } to { transform: translateX(0); } }
71
- .popup-close { float: right; cursor: pointer; color: #aaa; font-weight: bold; }
72
 
73
  /* --- MOBILE RESPONSIVENESS (Triggers on screens smaller than 650px) --- */
74
  @media (max-width: 650px) {
75
- body { padding: 20px 10px; }
76
- .container, .watchlist-container { padding: 20px; }
77
- .main-title { font-size: 1.5em; } /* Shrink header text */
78
- .main-title img { width: 32px; height: 32px; } /* Shrink logo */
79
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  /* Stack search bar */
81
- .search-bar { flex-direction: column; }
82
- .search-bar button { width: 100%; }
83
-
 
 
 
 
 
84
  /* Stack game header elements */
85
- .game-header { flex-direction: column; align-items: flex-start; gap: 10px; }
86
- .header-actions { width: 100%; justify-content: space-between; margin-top: 5px; }
87
-
 
 
 
 
 
 
 
 
 
88
  /* Stack price boxes */
89
- .price-compare { flex-direction: column; }
90
- .price-box { width: 100%; }
91
-
 
 
 
 
 
92
  /* Center mobile popup */
93
- .popup-notification {
94
- bottom: 20px; left: 50%; transform: translateX(-50%);
95
- width: 90%; right: auto;
 
 
 
96
  animation: slideUp 0.5s ease-out;
97
  }
98
- @keyframes slideUp { from { transform: translate(-50%, 120%); } to { transform: translate(-50%, 0); } }
 
 
 
 
 
 
 
 
 
99
  }
100
  </style>
101
  </head>
 
102
  <body>
103
 
104
- <div class="container">
105
- <div class="main-title">
106
- <!-- Official Steam Vector Logo -->
107
- <img src="https://upload.wikimedia.org/wikipedia/commons/8/83/Steam_icon_logo.svg" alt="Steam Logo" width="45" height="45">
108
- Steam Games Price Monitor
109
- </div>
110
- <div class="subtitle">Track prices. Get alerted. Save money.</div>
 
111
 
112
- {% if error %}
113
  <div class="error">{{ error }}</div>
114
- {% endif %}
115
-
116
- <form method="POST">
117
- <div class="form-group">
118
- <label>
119
- <span>Steam App ID OR Game Name</span>
120
- <a href="https://steamdb.info/" target="_blank" style="color: #66c0f4; text-decoration: none; font-size: 0.85em;">(get App ID from here)</a>
121
- </label>
122
- <input type="text" name="app_id" placeholder="e.g. 292030 OR The Witcher 3" required>
123
- </div>
124
- <div class="form-group">
125
- <label>Condition</label>
126
- <select name="alert_type">
127
- <option value="target_price">Drop at or below Target Price</option>
128
- <option value="discount_drop">Any Active Sale Discount</option>
129
- </select>
130
- </div>
131
- <div class="form-group">
132
- <label>Target Price Threshold (if applicable)</label>
133
- <input type="number" name="threshold" step="0.01" value="15.00">
134
- </div>
135
- <button type="submit" class="btn-main">Add Game to Watchlist</button>
136
- </form>
137
- </div>
138
-
139
- <div class="watchlist-container">
140
- <h2 style="margin-bottom: 15px;">📋 Tracked Watchlist</h2>
141
-
142
- <form method="GET" class="search-bar">
143
- <input type="text" name="search" placeholder="Filter your list..." value="{{ search_query }}">
144
- <button type="submit">Search</button>
145
- {% if search_query %}
146
- <a href="/" style="display:block; text-align:center; margin-top:10px; color:#aaa; text-decoration:none;">Clear Filter</a>
147
  {% endif %}
148
- </form>
149
 
150
- <div style="margin-top:15px;">
151
- {% if not watchlist %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  <p style="text-align:center; color:#888; padding: 20px 0;">No games found.</p>
153
- {% endif %}
154
-
155
- {% for app_id, title, initial_price_str, price_str, alert_type, threshold, triggered, initial_price, current_price in watchlist %}
 
156
  <div class="game-item {% if triggered == 1 %}alert-active{% endif %}">
157
-
158
  <div class="game-header">
159
  <div>
160
- <a href="https://store.steampowered.com/app/{{ app_id }}/" target="_blank" style="color: #fff; text-decoration: none; font-size: 1.1em; font-weight: bold;">
 
161
  {{ title }} 🔗
162
- </a>
163
- <br><small style="color:#888;">Alert: {{ alert_type.replace('_',' ') }} (Goal: {{ threshold }})</small>
164
  </div>
165
-
166
  <div class="header-actions">
167
- <a href="https://store.steampowered.com/app/{{ app_id }}/" target="_blank" style="color: #66c0f4; font-size: 0.85em; text-decoration: none;">View Store</a>
168
-
 
169
  <form action="/delete/{{ app_id }}" method="POST" style="margin: 0; padding: 0;">
170
- <button type="submit" class="btn-delete" onclick="return confirm('Stop tracking {{ title }}?');">
 
171
  ✖ Remove
172
  </button>
173
  </form>
@@ -176,60 +404,64 @@
176
 
177
  <div class="price-compare">
178
  <div class="price-box">
179
- <span style="color:#888; font-size:0.8em; display:block; margin-bottom: 4px;">PRICE BEFORE</span>
 
180
  <span class="price-same">{{ initial_price_str }}</span>
181
  </div>
182
  <div class="price-box">
183
  <span style="color:#888; font-size:0.8em; display:block; margin-bottom: 4px;">PRICE AFTER</span>
184
- <span class="{% if current_price < initial_price %}price-dropped{% else %}price-same{% endif %}">
 
185
  {{ price_str }}
186
  </span>
187
  </div>
188
  </div>
189
 
190
  </div>
191
- {% endfor %}
 
192
  </div>
193
- </div>
194
-
195
- <div id="alertPopup" class="popup-notification">
196
- <span class="popup-close" onclick="closePopup()">&times;</span>
197
- <h4 style="color: #5cb85c; margin-bottom: 5px;">🔥 PRICE DROP DETECTED!</h4>
198
- <p id="alertMessage" style="font-size: 0.95em; color: #fff;"></p>
199
- <a id="alertLink" href="#" target="_blank" style="color: #66c0f4; display:block; margin-top:10px; font-size:0.85em;">View on Steam Store</a>
200
- </div>
201
-
202
- <script>
203
- let activeAlertAppId = null;
204
-
205
- setInterval(function() {
206
- fetch('/api/alerts')
207
- .then(response => response.json())
208
- .then(alerts => {
209
- if(alerts.length > 0) {
210
- let topAlert = alerts[0];
211
- activeAlertAppId = topAlert.app_id;
212
-
213
- document.getElementById("alertMessage").innerHTML = `<strong>${topAlert.title}</strong> has hit your threshold! Current Price: <b>${topAlert.price}</b>`;
214
- document.getElementById("alertLink").href = `https://store.steampowered.com/app/${topAlert.app_id}/`;
215
- document.getElementById("alertPopup").style.display = "block";
216
- }
217
- });
218
- }, 4000);
219
-
220
- function closePopup() {
221
- document.getElementById("alertPopup").style.display = "none";
222
- if (activeAlertAppId) {
223
- fetch('/api/dismiss', {
224
- method: 'POST',
225
- headers: { 'Content-Type': 'application/json' },
226
- body: JSON.stringify({ app_id: activeAlertAppId })
227
- }).then(() => {
228
- window.location.reload();
229
- });
230
- }
231
- }
232
- </script>
233
 
234
  </body>
 
235
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
+
4
  <head>
5
  <meta charset="UTF-8">
6
  <!-- This meta tag is crucial for mobile responsiveness -->
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
8
  <title>Live Steam Price Monitor</title>
9
  <style>
10
+ * {
11
+ box-sizing: border-box;
12
+ margin: 0;
13
+ padding: 0;
14
+ font-family: 'Segoe UI', sans-serif;
15
+ }
16
+
17
  body {
18
  /* Authentic Steam dark gradient overlay with gaming poster background */
19
+ background: linear-gradient(rgba(27, 40, 56, 0.88), rgba(21, 25, 31, 0.95)),
20
+ url('https://www.pcworld.com/wp-content/uploads/2025/06/Steam-logo-on-top-of-a-background-full-of-Steam-library-games.jpg?quality=50&strip=all') no-repeat center center fixed;
21
+ background-size: cover;
22
+ color: #e0e0e0;
23
+ padding: 40px 20px;
24
+ display: flex;
25
+ flex-direction: column;
26
+ align-items: center;
27
+ min-height: 100vh;
28
  }
29
 
30
+ .container,
31
+ .watchlist-container {
32
+ background: rgba(35, 35, 35, 0.65);
33
+ backdrop-filter: blur(12px);
34
+ border: 1px solid rgba(255, 255, 255, 0.08);
35
+ padding: 30px;
36
+ border-radius: 12px;
37
+ width: 100%;
38
+ max-width: 650px;
39
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
40
+ margin-bottom: 20px;
41
  }
42
 
43
  /* Flexbox header to align logo and text */
44
  .main-title {
45
+ display: flex;
46
+ justify-content: center;
47
+ align-items: center;
48
+ gap: 12px;
49
+ margin-bottom: 10px;
50
+ color: #fff;
51
+ font-size: 2em;
52
+ font-weight: bold;
53
+ }
54
+
55
+ .subtitle {
56
+ text-align: center;
57
+ font-size: 0.9em;
58
+ color: #aaa;
59
+ margin-bottom: 25px;
60
+ }
61
+
62
+ .form-group {
63
+ margin-bottom: 15px;
64
+ }
65
+
66
+ label {
67
+ display: flex;
68
+ justify-content: space-between;
69
+ align-items: baseline;
70
+ margin-bottom: 6px;
71
+ font-size: 0.9em;
72
+ color: #ccc;
73
+ }
74
+
75
+ input,
76
+ select,
77
+ button {
78
+ width: 100%;
79
+ padding: 12px;
80
+ background: rgba(0, 0, 0, 0.4);
81
+ border: 1px solid #444;
82
+ border-radius: 6px;
83
+ color: #fff;
84
+ font-size: 1em;
85
+ outline: none;
86
+ }
87
+
88
+ input:focus,
89
+ select:focus {
90
+ border-color: #66c0f4;
91
+ }
92
+
93
+ .btn-main {
94
+ background-color: #1b2838;
95
+ border-color: #66c0f4;
96
+ color: #66c0f4;
97
+ font-weight: bold;
98
+ cursor: pointer;
99
+ transition: 0.3s;
100
+ margin-top: 10px;
101
+ }
102
+
103
+ .btn-main:hover {
104
+ background-color: #66c0f4;
105
+ color: #1b2838;
106
+ }
107
+
108
+ .search-bar {
109
+ display: flex;
110
+ gap: 10px;
111
+ margin-bottom: 20px;
112
+ }
113
+
114
+ .search-bar input {
115
+ flex-grow: 1;
116
+ }
117
+
118
+ .search-bar button {
119
+ width: auto;
120
+ margin-top: 0;
121
+ padding: 12px 25px;
122
+ cursor: pointer;
123
+ background: #333;
124
+ border: 1px solid #555;
125
+ color: white;
126
+ }
127
+
128
+ .search-bar button:hover {
129
+ background: #555;
130
+ }
131
+
132
+ .game-item {
133
+ display: flex;
134
+ flex-direction: column;
135
+ padding: 15px;
136
+ background: rgba(0, 0, 0, 0.3);
137
+ border-radius: 6px;
138
+ margin-bottom: 12px;
139
+ border-left: 4px solid #444;
140
+ }
141
+
142
+ .game-item.alert-active {
143
+ border-left-color: #5cb85c;
144
+ background: rgba(92, 184, 92, 0.1);
145
+ }
146
+
147
+ .game-header {
148
+ display: flex;
149
+ justify-content: space-between;
150
+ align-items: center;
151
+ border-bottom: 1px solid #444;
152
+ padding-bottom: 10px;
153
+ margin-bottom: 10px;
154
+ }
155
+
156
+ .btn-delete {
157
+ background: rgba(255, 74, 74, 0.1);
158
+ color: #ff4a4a;
159
+ border: 1px solid #ff4a4a;
160
+ padding: 6px 12px;
161
+ border-radius: 4px;
162
+ font-size: 0.85em;
163
+ cursor: pointer;
164
+ transition: 0.2s;
165
+ margin-top: 0;
166
+ width: auto;
167
+ }
168
+
169
+ .btn-delete:hover {
170
+ background: #ff4a4a;
171
+ color: white;
172
+ }
173
+
174
+ .header-actions {
175
+ display: flex;
176
+ gap: 15px;
177
+ align-items: center;
178
+ }
179
+
180
+ .price-compare {
181
+ display: flex;
182
+ justify-content: space-between;
183
+ font-size: 0.95em;
184
+ gap: 10px;
185
+ }
186
+
187
+ .price-box {
188
+ text-align: center;
189
+ width: 48%;
190
+ background: rgba(255, 255, 255, 0.05);
191
+ padding: 12px 8px;
192
+ border-radius: 4px;
193
+ }
194
+
195
+ .price-dropped {
196
+ color: #5cb85c;
197
+ font-weight: bold;
198
+ font-size: 1.1em;
199
+ }
200
+
201
+ .price-same {
202
+ color: #ccc;
203
+ font-size: 1.1em;
204
+ }
205
+
206
+ .error {
207
+ background: rgba(255, 74, 74, 0.2);
208
+ border-left: 4px solid #ff4a4a;
209
+ padding: 15px;
210
+ margin-bottom: 20px;
211
+ border-radius: 4px;
212
+ }
213
+
214
  .popup-notification {
215
+ position: fixed;
216
+ bottom: 20px;
217
+ right: 20px;
218
+ background: rgba(30, 45, 60, 0.95);
219
+ border: 2px solid #5cb85c;
220
+ padding: 20px;
221
+ border-radius: 8px;
222
+ width: 320px;
223
+ display: none;
224
+ z-index: 1000;
225
+ animation: slideIn 0.5s ease-out;
226
+ }
227
+
228
+ @keyframes slideIn {
229
+ from {
230
+ transform: translateX(120%);
231
+ }
232
+
233
+ to {
234
+ transform: translateX(0);
235
+ }
236
+ }
237
+
238
+ .popup-close {
239
+ float: right;
240
+ cursor: pointer;
241
+ color: #aaa;
242
+ font-weight: bold;
243
  }
 
 
244
 
245
  /* --- MOBILE RESPONSIVENESS (Triggers on screens smaller than 650px) --- */
246
  @media (max-width: 650px) {
247
+ body {
248
+ padding: 20px 10px;
249
+ }
250
+
251
+ .container,
252
+ .watchlist-container {
253
+ padding: 20px;
254
+ }
255
+
256
+ .main-title {
257
+ font-size: 1.5em;
258
+ }
259
+
260
+ /* Shrink header text */
261
+ .main-title img {
262
+ width: 32px;
263
+ height: 32px;
264
+ }
265
+
266
+ /* Shrink logo */
267
+
268
  /* Stack search bar */
269
+ .search-bar {
270
+ flex-direction: column;
271
+ }
272
+
273
+ .search-bar button {
274
+ width: 100%;
275
+ }
276
+
277
  /* Stack game header elements */
278
+ .game-header {
279
+ flex-direction: column;
280
+ align-items: flex-start;
281
+ gap: 10px;
282
+ }
283
+
284
+ .header-actions {
285
+ width: 100%;
286
+ justify-content: space-between;
287
+ margin-top: 5px;
288
+ }
289
+
290
  /* Stack price boxes */
291
+ .price-compare {
292
+ flex-direction: column;
293
+ }
294
+
295
+ .price-box {
296
+ width: 100%;
297
+ }
298
+
299
  /* Center mobile popup */
300
+ .popup-notification {
301
+ bottom: 20px;
302
+ left: 50%;
303
+ transform: translateX(-50%);
304
+ width: 90%;
305
+ right: auto;
306
  animation: slideUp 0.5s ease-out;
307
  }
308
+
309
+ @keyframes slideUp {
310
+ from {
311
+ transform: translate(-50%, 120%);
312
+ }
313
+
314
+ to {
315
+ transform: translate(-50%, 0);
316
+ }
317
+ }
318
  }
319
  </style>
320
  </head>
321
+
322
  <body>
323
 
324
+ <div class="container">
325
+ <div class="main-title">
326
+ <!-- Official Steam Vector Logo -->
327
+ <img src="https://upload.wikimedia.org/wikipedia/commons/8/83/Steam_icon_logo.svg" alt="Steam Logo"
328
+ width="45" height="45">
329
+ Steam Games Price Monitor
330
+ </div>
331
+ <div class="subtitle">Track prices. Get alerted. Save money.</div>
332
 
333
+ {% if error %}
334
  <div class="error">{{ error }}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  {% endif %}
 
336
 
337
+ <form method="POST">
338
+ <div class="form-group">
339
+ <label>
340
+ <span>Steam App ID OR Game Name</span>
341
+ <a href="https://steamdb.info/" target="_blank"
342
+ style="color: #66c0f4; text-decoration: none; font-size: 0.85em;">(get App ID from here)</a>
343
+ </label>
344
+ <input type="text" name="app_id" placeholder="e.g. 292030 OR The Witcher 3" required>
345
+ </div>
346
+ <div class="form-group">
347
+ <label>Condition</label>
348
+ <select name="alert_type">
349
+ <option value="target_price">Drop at or below Target Price</option>
350
+ <option value="discount_drop">Any Active Sale Discount</option>
351
+ </select>
352
+ </div>
353
+ <div class="form-group">
354
+ <label>Target Price Threshold (in ₹ Rupees)</label>
355
+ <input type="number" name="threshold" step="1" value="1000">
356
+ </div>
357
+ <button type="submit" class="btn-main">Add Game to Watchlist</button>
358
+ </form>
359
+ </div>
360
+
361
+ <div class="watchlist-container">
362
+ <h2 style="margin-bottom: 15px;">📋 Tracked Watchlist</h2>
363
+
364
+ <form method="GET" class="search-bar">
365
+ <input type="text" name="search" placeholder="Filter your list..." value="{{ search_query }}">
366
+ <button type="submit">Search</button>
367
+ {% if search_query %}
368
+ <a href="/"
369
+ style="display:block; text-align:center; margin-top:10px; color:#aaa; text-decoration:none;">Clear
370
+ Filter</a>
371
+ {% endif %}
372
+ </form>
373
+
374
+ <div style="margin-top:15px;">
375
+ {% if not watchlist %}
376
  <p style="text-align:center; color:#888; padding: 20px 0;">No games found.</p>
377
+ {% endif %}
378
+
379
+ {% for app_id, title, initial_price_str, price_str, alert_type, threshold, triggered, initial_price,
380
+ current_price in watchlist %}
381
  <div class="game-item {% if triggered == 1 %}alert-active{% endif %}">
382
+
383
  <div class="game-header">
384
  <div>
385
+ <a href="https://store.steampowered.com/app/{{ app_id }}/" target="_blank"
386
+ style="color: #fff; text-decoration: none; font-size: 1.1em; font-weight: bold;">
387
  {{ title }} 🔗
388
+ </a>
389
+ <br><small style="color:#888;">Alert: {{ alert_type.replace('_',' ') }} (Goal: {{ threshold }})</small>
390
  </div>
391
+
392
  <div class="header-actions">
393
+ <a href="https://store.steampowered.com/app/{{ app_id }}/" target="_blank"
394
+ style="color: #66c0f4; font-size: 0.85em; text-decoration: none;">View Store</a>
395
+
396
  <form action="/delete/{{ app_id }}" method="POST" style="margin: 0; padding: 0;">
397
+ <button type="submit" class="btn-delete"
398
+ onclick="return confirm('Stop tracking {{ title }}?');">
399
  ✖ Remove
400
  </button>
401
  </form>
 
404
 
405
  <div class="price-compare">
406
  <div class="price-box">
407
+ <span style="color:#888; font-size:0.8em; display:block; margin-bottom: 4px;">PRICE
408
+ BEFORE</span>
409
  <span class="price-same">{{ initial_price_str }}</span>
410
  </div>
411
  <div class="price-box">
412
  <span style="color:#888; font-size:0.8em; display:block; margin-bottom: 4px;">PRICE AFTER</span>
413
+ <span
414
+ class="{% if current_price < initial_price %}price-dropped{% else %}price-same{% endif %}">
415
  {{ price_str }}
416
  </span>
417
  </div>
418
  </div>
419
 
420
  </div>
421
+ {% endfor %}
422
+ </div>
423
  </div>
424
+
425
+ <div id="alertPopup" class="popup-notification">
426
+ <span class="popup-close" onclick="closePopup()">&times;</span>
427
+ <h4 style="color: #5cb85c; margin-bottom: 5px;">🔥 PRICE DROP DETECTED!</h4>
428
+ <p id="alertMessage" style="font-size: 0.95em; color: #fff;"></p>
429
+ <a id="alertLink" href="#" target="_blank"
430
+ style="color: #66c0f4; display:block; margin-top:10px; font-size:0.85em;">View on Steam Store</a>
431
+ </div>
432
+
433
+ <script>
434
+ let activeAlertAppId = null;
435
+
436
+ setInterval(function () {
437
+ fetch('/api/alerts')
438
+ .then(response => response.json())
439
+ .then(alerts => {
440
+ if (alerts.length > 0) {
441
+ let topAlert = alerts[0];
442
+ activeAlertAppId = topAlert.app_id;
443
+
444
+ document.getElementById("alertMessage").innerHTML = `<strong>${topAlert.title}</strong> has hit your threshold! Current Price: <b>${topAlert.price}</b>`;
445
+ document.getElementById("alertLink").href = `https://store.steampowered.com/app/${topAlert.app_id}/`;
446
+ document.getElementById("alertPopup").style.display = "block";
447
+ }
448
+ });
449
+ }, 4000);
450
+
451
+ function closePopup() {
452
+ document.getElementById("alertPopup").style.display = "none";
453
+ if (activeAlertAppId) {
454
+ fetch('/api/dismiss', {
455
+ method: 'POST',
456
+ headers: { 'Content-Type': 'application/json' },
457
+ body: JSON.stringify({ app_id: activeAlertAppId })
458
+ }).then(() => {
459
+ window.location.reload();
460
+ });
461
+ }
462
+ }
463
+ </script>
464
 
465
  </body>
466
+
467
  </html>