Aryanshh commited on
Commit
3358e03
·
1 Parent(s): 4ceb41c

feat: Official production release with overhauled Command Console and Interactive Tour

Browse files
Files changed (3) hide show
  1. dashboard/app.js +94 -90
  2. dashboard/index.html +94 -89
  3. dashboard/style.css +291 -217
dashboard/app.js CHANGED
@@ -1,40 +1,43 @@
1
  const API_BASE = window.location.origin;
2
 
3
- // Simulation Specs (Logistics trade-offs)
4
  const SPECS = {
5
- sea: { cost: 1.0, carbon: 0.1, time: 10 },
6
- air: { cost: 5.0, carbon: 2.0, time: 2 },
7
- rail: { cost: 2.5, carbon: 0.5, time: 5 }
8
  };
9
 
10
- const RECIPES = {
11
- "EcoPhone": { chips: 1, sensors: 2 },
12
- "GreenTab": { chips: 2, sensors: 1 }
13
- };
14
-
15
- // Global State
16
- let latestInventory = { chips: 0, sensors: 0 };
17
- let currentTutStep = 0;
18
-
19
  const TUTORIAL_STEPS = [
20
  {
21
- title: "Mission Briefing",
22
- text: "Welcome, Logistics Operator. Your goal is to keep NetZero-Nav profitable while minimizing our carbon footprint. Balance is everything."
 
 
 
 
 
 
23
  },
24
  {
25
- title: "The Command Side",
26
- text: "On the left, you'll find your Command Console. This is where you order raw materials and start manufacturing runs."
 
27
  },
28
  {
29
- title: "Logistics Strategy",
30
- text: "Pay attention to the Delivery Times (ETAs) in the Logistics section. Sea is green but takes 10 full days. Air is fast but high emissions."
 
31
  },
32
  {
33
- title: "Crisis Resilience",
34
- text: "Scenario triggering (like the Suez Jam) will test your ability to pivot. Use Rail or Air when Sea routes are blocked!"
 
35
  }
36
  ];
37
 
 
 
38
  // State Management
39
  async function updateState() {
40
  try {
@@ -42,12 +45,6 @@ async function updateState() {
42
  if (!response.ok) throw new Error('API Unreachable');
43
  const data = await response.json();
44
 
45
- // Pulse animation on inventory change
46
- if (data.inventory.chips !== latestInventory.chips) pulse('chips-count');
47
- if (data.inventory.sensors !== latestInventory.sensors) pulse('sensors-count');
48
-
49
- latestInventory = data.inventory;
50
-
51
  const status = document.getElementById('connection-status');
52
  status.textContent = 'ONLINE';
53
  status.className = 'status-badge status-online';
@@ -57,8 +54,6 @@ async function updateState() {
57
  document.getElementById('chips-count').textContent = data.inventory.chips;
58
  document.getElementById('sensors-count').textContent = data.inventory.sensors;
59
 
60
- updateProducePreview();
61
-
62
  const newsAlert = document.getElementById('news-alert');
63
  if (data.news) {
64
  document.getElementById('news-text').textContent = data.news;
@@ -72,80 +67,49 @@ async function updateState() {
72
  container.innerHTML = data.orders.map(order => `
73
  <div class="shipment-item">
74
  <span>${order.product} (Required: ${order.quantity})</span>
75
- <small>ETA: Day ${order.due_date}</small>
76
  </div>
77
  `).join('');
78
  } else {
79
- container.innerHTML = '<p class="placeholder">All regional orders fulfilled.</p>';
80
  }
81
 
82
  } catch (error) {
83
- console.error('Telemetry lost:', error);
84
- document.getElementById('connection-status').textContent = 'RECONNECTING...';
85
- document.getElementById('connection-status').className = 'status-badge';
 
86
  }
87
  }
88
 
89
- function pulse(id) {
90
- const el = document.getElementById(id);
91
- el.style.transform = 'scale(1.2)';
92
- el.style.color = '#7A9B7A';
93
- setTimeout(() => {
94
- el.style.transform = 'scale(1)';
95
- el.style.color = 'inherit';
96
- }, 300);
97
- }
98
-
99
- function updateProducePreview() {
100
- const product = document.getElementById('product-select').value;
101
- const req = RECIPES[product];
102
- const preview = document.getElementById('req-preview');
103
- if (!preview) return;
104
-
105
- const chipClass = latestInventory.chips >= req.chips ? "sufficient" : "shortage";
106
- const sensorClass = latestInventory.sensors >= req.sensors ? "sufficient" : "shortage";
107
-
108
- preview.innerHTML = `
109
- <div class="req-item">
110
- <span>Chips: ${req.chips} req.</span>
111
- <span class="${chipClass}">In Stock: ${latestInventory.chips} (Min: ${req.chips})</span>
112
- </div>
113
- <div class="req-item">
114
- <span>Sensors: ${req.sensors} req.</span>
115
- <span class="${sensorClass}">In Stock: ${latestInventory.sensors} (Min: ${req.sensors})</span>
116
- </div>
117
- `;
118
- }
119
 
120
- function updateImpactPreview() {
121
- const mode = document.getElementById('mode-select').value;
122
  const s = SPECS[mode];
123
  const cost = 10 * 5 * s.cost;
124
  const carbon = 5 * s.carbon;
125
 
126
  document.getElementById('preview-cost').textContent = `$${cost.toFixed(0)}`;
127
  document.getElementById('preview-carbon').textContent = `+${carbon.toFixed(1)}kg CO2`;
128
-
129
- // Add visual feedback to impact based on carbon
130
- const carbonLabel = document.getElementById('preview-carbon');
131
- if (s.carbon > 1.0) carbonLabel.style.color = '#E76F51';
132
- else carbonLabel.style.color = '#2D5F2D';
133
  }
134
 
135
- // Global Execute
136
- window.execute = async function(type) {
137
- if (type === 'produce') {
138
- const product = document.getElementById('product-select').value;
139
- const req = RECIPES[product];
140
- if (latestInventory.chips < req.chips || latestInventory.sensors < req.sensors) {
141
- log(`STALLED: Insufficient materials for ${product}.`, 'error');
142
- return;
143
- }
144
- }
145
 
 
 
 
 
 
 
 
 
 
 
146
  document.querySelector('main').classList.add('transitioning');
147
 
148
- const actionObj = { action_type: type };
149
  if (type === 'order_parts') {
150
  actionObj.part_type = document.getElementById('part-select').value;
151
  actionObj.mode = document.getElementById('mode-select').value;
@@ -167,28 +131,41 @@ window.execute = async function(type) {
167
  const result = await response.json();
168
 
169
  if (result.info && result.info.error) {
170
- log(`DENIED: ${result.info.error}`, 'error');
171
  } else {
172
- log(`EXECUTED: ${type === 'skip' ? 'Day Advanced' : type.replace('_', ' ')}`, 'action');
173
  }
174
 
175
  await updateState();
176
  } catch (e) {
177
- log(`CRITICAL: Comm-link offline`, 'error');
178
  } finally {
179
  setTimeout(() => {
180
  document.querySelector('main').classList.remove('transitioning');
181
- }, 400);
182
  }
183
  }
184
 
185
  // Tutorial Logic
186
  function showStep(step) {
187
  const content = document.getElementById('tutorial-step');
 
188
  const data = TUTORIAL_STEPS[step];
 
189
  content.innerHTML = `<h2>${data.title}</h2><p>${data.text}</p>`;
190
  document.getElementById('tut-prev').classList.toggle('hidden', step === 0);
191
- document.getElementById('tut-next').textContent = step === TUTORIAL_STEPS.length - 1 ? "Initialize HUD" : "Next";
 
 
 
 
 
 
 
 
 
 
 
192
  }
193
 
194
  document.getElementById('tut-next').addEventListener('click', () => {
@@ -218,11 +195,38 @@ document.getElementById('info-btn').addEventListener('click', () => {
218
  });
219
 
220
  // Initialization
221
- document.getElementById('mode-select').addEventListener('change', updateImpactPreview);
222
- document.getElementById('product-select').addEventListener('change', updateProducePreview);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
 
 
224
  updateState();
225
  setInterval(updateState, 3000);
226
- updateImpactPreview();
227
  showStep(0);
228
  document.getElementById('tutorial-modal').classList.remove('hidden');
 
1
  const API_BASE = window.location.origin;
2
 
3
+ // Simulation Specs (Mirrors env.py)
4
  const SPECS = {
5
+ sea: { cost: 1.0, carbon: 0.1 },
6
+ air: { cost: 5.0, carbon: 2.0 },
7
+ rail: { cost: 2.5, carbon: 0.5 }
8
  };
9
 
10
+ // Tutorial Content
 
 
 
 
 
 
 
 
11
  const TUTORIAL_STEPS = [
12
  {
13
+ title: "Welcome to NetZero-Nav",
14
+ text: "You are now in command of a global eco-resilient logistics network. Your mission: Deliver products profitably while staying within carbon limits.",
15
+ target: "header"
16
+ },
17
+ {
18
+ title: "Supply Logistics",
19
+ text: "Use this section to order parts. Remember: Sea is green but slow, Air is fast but high emissions.",
20
+ target: "#section-order"
21
  },
22
  {
23
+ title: "Manufacturing",
24
+ text: "Once parts arrive in your inventory, start your production runs here to fulfill orders.",
25
+ target: "#section-produce"
26
  },
27
  {
28
+ title: "ESG Offsetting",
29
+ text: "Strategically buy carbon offsets here if you are nearing your quota limits.",
30
+ target: "#section-offset"
31
  },
32
  {
33
+ title: "Mission Control",
34
+ text: "Advance time or trigger crisis scenarios here. Good luck, Operator.",
35
+ target: ".time-controls"
36
  }
37
  ];
38
 
39
+ let currentTutStep = 0;
40
+
41
  // State Management
42
  async function updateState() {
43
  try {
 
45
  if (!response.ok) throw new Error('API Unreachable');
46
  const data = await response.json();
47
 
 
 
 
 
 
 
48
  const status = document.getElementById('connection-status');
49
  status.textContent = 'ONLINE';
50
  status.className = 'status-badge status-online';
 
54
  document.getElementById('chips-count').textContent = data.inventory.chips;
55
  document.getElementById('sensors-count').textContent = data.inventory.sensors;
56
 
 
 
57
  const newsAlert = document.getElementById('news-alert');
58
  if (data.news) {
59
  document.getElementById('news-text').textContent = data.news;
 
67
  container.innerHTML = data.orders.map(order => `
68
  <div class="shipment-item">
69
  <span>${order.product} (Required: ${order.quantity})</span>
70
+ <small>Deadline: Day ${order.due_date}</small>
71
  </div>
72
  `).join('');
73
  } else {
74
+ container.innerHTML = '<p class="placeholder">All orders fulfilled. Environment reset recommended.</p>';
75
  }
76
 
77
  } catch (error) {
78
+ console.error('Update failed:', error);
79
+ const status = document.getElementById('connection-status');
80
+ status.textContent = 'RECONNECTING...';
81
+ status.className = 'status-badge';
82
  }
83
  }
84
 
85
+ // UI Controls
86
+ const modeSelect = document.getElementById('mode-select');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
+ function updatePreview() {
89
+ const mode = modeSelect.value;
90
  const s = SPECS[mode];
91
  const cost = 10 * 5 * s.cost;
92
  const carbon = 5 * s.carbon;
93
 
94
  document.getElementById('preview-cost').textContent = `$${cost.toFixed(0)}`;
95
  document.getElementById('preview-carbon').textContent = `+${carbon.toFixed(1)}kg CO2`;
 
 
 
 
 
96
  }
97
 
98
+ modeSelect.addEventListener('change', updatePreview);
 
 
 
 
 
 
 
 
 
99
 
100
+ function log(message, type = 'system') {
101
+ const box = document.getElementById('activity-log');
102
+ const entry = document.createElement('p');
103
+ entry.className = `log-entry ${type}`;
104
+ entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
105
+ box.prepend(entry);
106
+ }
107
+
108
+ // Global Execute Function (exposed to onclick in HTML)
109
+ window.execute = async function(type) {
110
  document.querySelector('main').classList.add('transitioning');
111
 
112
+ let actionObj = { action_type: type };
113
  if (type === 'order_parts') {
114
  actionObj.part_type = document.getElementById('part-select').value;
115
  actionObj.mode = document.getElementById('mode-select').value;
 
131
  const result = await response.json();
132
 
133
  if (result.info && result.info.error) {
134
+ log(`ERROR: ${result.info.error}`, 'error');
135
  } else {
136
+ log(`SUCCESS: ${type === 'skip' ? 'Skipped Day' : 'Executed ' + type}`, 'action');
137
  }
138
 
139
  await updateState();
140
  } catch (e) {
141
+ log(`CRITICAL: Connection lost`, 'error');
142
  } finally {
143
  setTimeout(() => {
144
  document.querySelector('main').classList.remove('transitioning');
145
+ }, 300);
146
  }
147
  }
148
 
149
  // Tutorial Logic
150
  function showStep(step) {
151
  const content = document.getElementById('tutorial-step');
152
+ const modal = document.querySelector('.modal-card');
153
  const data = TUTORIAL_STEPS[step];
154
+
155
  content.innerHTML = `<h2>${data.title}</h2><p>${data.text}</p>`;
156
  document.getElementById('tut-prev').classList.toggle('hidden', step === 0);
157
+ document.getElementById('tut-next').textContent = step === TUTORIAL_STEPS.length - 1 ? "Start Simulation" : "Next";
158
+
159
+ // Position modal near target
160
+ const targetEl = document.querySelector(data.target);
161
+ if (targetEl && step > 0) {
162
+ const rect = targetEl.getBoundingClientRect();
163
+ document.getElementById('tutorial-modal').style.alignItems = 'flex-start';
164
+ document.getElementById('tutorial-modal').style.paddingTop = `${Math.max(20, rect.top - 100)}px`;
165
+ } else {
166
+ document.getElementById('tutorial-modal').style.alignItems = 'center';
167
+ document.getElementById('tutorial-modal').style.paddingTop = '0';
168
+ }
169
  }
170
 
171
  document.getElementById('tut-next').addEventListener('click', () => {
 
195
  });
196
 
197
  // Initialization
198
+ async function manualReset() {
199
+ if (!confirm("Are you sure you want to reset the simulation?")) return;
200
+ try {
201
+ await fetch(`${API_BASE}/reset`, {
202
+ method: 'POST',
203
+ headers: { 'Content-Type': 'application/json' },
204
+ body: JSON.stringify({ task: 'easy' })
205
+ });
206
+ log("Environment Reset to Day 0", "system");
207
+ await updateState();
208
+ } catch (e) {
209
+ log("Reset Failed", "error");
210
+ }
211
+ }
212
+
213
+ async function triggerSuezJam() {
214
+ try {
215
+ await fetch(`${API_BASE}/trigger`, { method: 'POST' });
216
+ log(`CRISIS: Suez Jam Triggered!`, 'error');
217
+ await updateState();
218
+ } catch (e) {
219
+ log(`Trigger failed`, 'error');
220
+ }
221
+ }
222
+
223
+ document.getElementById('skip-btn').addEventListener('click', () => window.execute('skip'));
224
+ document.getElementById('trigger-btn').addEventListener('click', triggerSuezJam);
225
+ document.getElementById('reset-btn').addEventListener('click', manualReset);
226
 
227
+ // Start
228
  updateState();
229
  setInterval(updateState, 3000);
230
+ updatePreview();
231
  showStep(0);
232
  document.getElementById('tutorial-modal').classList.remove('hidden');
dashboard/index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>NetZero-Nav | Logistics Command</title>
7
  <link rel="stylesheet" href="style.css">
8
  <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
9
  </head>
@@ -15,7 +15,7 @@
15
  <h1>NETZERO-NAV</h1>
16
  <button id="info-btn" class="help-circle">?</button>
17
  </div>
18
- <div id="connection-status" class="status-badge">TELEMETRY OFFLINE</div>
19
  </header>
20
 
21
  <!-- Interactive Walkthrough -->
@@ -25,153 +25,158 @@
25
  <!-- Dynamic Content -->
26
  </div>
27
  <div class="modal-footer">
28
- <button id="tut-prev" class="btn btn-outline hidden">Back</button>
29
- <button id="tut-next" class="btn btn-primary">Next</button>
30
- <button id="tut-close" class="btn btn-ghost">Skip Tour</button>
31
  </div>
32
  </div>
33
  </div>
34
 
35
  <main>
36
- <!-- Fixed Left Sidebar Console -->
37
- <section id="command-console" class="control-panel card">
38
- <h3><span class="i-icon">🕹️</span> Command Console</h3>
39
 
40
  <div class="operation-sections">
41
- <!-- Supply -->
42
  <div id="section-order" class="op-section">
43
  <h4>📦 Supply Logistics</h4>
44
  <div class="op-row">
45
  <select id="part-select">
46
- <option value="chips">Microchips (Procurement)</option>
47
- <option value="sensors">Sensors (Procurement)</option>
48
  </select>
49
  <select id="mode-select">
50
- <option value="sea">SEA (Ultra-Low CO2 | ETA: 10d)</option>
51
- <option value="rail">RAIL (Low CO2 | ETA: 5d)</option>
52
- <option value="air">AIR (High CO2 | ETA: 2d)</option>
53
  </select>
54
- <button onclick="execute('order_parts')" class="btn btn-primary">Process Order</button>
55
  </div>
56
  </div>
57
 
58
  <hr class="divider">
59
 
60
- <!-- Production -->
61
  <div id="section-produce" class="op-section">
62
  <h4>🏭 Manufacturing</h4>
63
  <div class="op-row">
64
  <select id="product-select">
65
- <option value="EcoPhone">Assemble EcoPhone</option>
66
- <option value="GreenTab">Assemble GreenTab</option>
67
  </select>
68
- <button onclick="execute('produce')" class="btn btn-primary">Initiate Run</button>
69
  </div>
70
- <div id="req-preview" class="req-preview"></div>
71
  </div>
72
 
73
  <hr class="divider">
74
 
75
- <!-- Offsetting -->
76
  <div id="section-offset" class="op-section">
77
- <h4>🏢 ESG ESG Strategy</h4>
78
  <div class="op-row">
79
- <button onclick="execute('offset')" class="btn btn-secondary">Purchase Carbon Offset (100u)</button>
 
80
  </div>
81
  </div>
82
  </div>
83
 
84
- <!-- Impact Preview -->
85
- <div id="impact-preview" class="impact-preview card" style="box-shadow: none; border: 1px dashed var(--sage-green); margin-top: 1rem; padding: 1rem;">
86
- <div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.5rem;">Estimated Shipment Impact:</div>
87
- <div style="display: flex; justify-content: space-between; font-weight: 600;">
88
- <span id="preview-cost" style="color: var(--deep-forest)">$0</span>
89
- <span id="preview-carbon" style="color: var(--sage-green)">0kg CO2</span>
90
- </div>
91
  </div>
92
 
93
- <div class="time-controls" style="margin-top: 2rem; display: flex; flex-direction: column; gap: 0.75rem;">
94
- <button id="skip-btn" onclick="execute('skip')" class="btn btn-outline" style="width: 100%;">Advance Single Day</button>
95
- <button id="trigger-btn" class="btn btn-danger">Manual Crisis: Suez Jam</button>
 
96
  </div>
97
-
98
  <div id="news-alert" class="news-alert hidden">
 
99
  <span id="news-text"></span>
100
  </div>
101
  </section>
102
 
103
- <!-- Fluid Right Content Column -->
104
- <div class="metrics-column">
105
- <section class="metrics-grid">
106
- <div class="card metric-card">
107
- <h3>Carbon Footprint</h3>
108
- <div class="gauge-container">
109
- <div class="gauge">
110
- <span id="carbon-value">0</span><small>kg</small>
111
- </div>
112
  </div>
113
- <div style="font-size: 0.75rem; color: var(--text-muted);">Regional Quota: 1000kg</div>
114
  </div>
 
 
115
 
116
- <div class="card metric-card">
117
- <h3>Operating Capital</h3>
118
- <div class="value-large">$<span id="cash-value">0</span></div>
119
- <div style="font-size: 0.75rem; color: var(--text-muted);">Current Liquid Assets</div>
120
- </div>
121
 
122
- <div class="card metric-card">
123
- <h3>Raw Inventory</h3>
124
- <div class="inventory-grid" style="margin-top: 1rem;">
125
- <div class="inv-item">
126
- <span class="label">Chips</span>
127
- <span id="chips-count" class="count">0</span>
128
- </div>
129
- <div class="inv-item">
130
- <span class="label">Sensors</span>
131
- <span id="sensors-count" class="count">0</span>
132
- </div>
133
  </div>
134
  </div>
135
- </section>
 
136
 
137
- <section class="log-section">
138
- <div class="card full-width">
139
- <h3>📠 Live Operations Log</h3>
140
- <div id="activity-log" class="log-box">
141
- <p class="log-entry system">[SYSTEM] Encrypted link established.</p>
142
- </div>
143
  </div>
144
- </section>
 
145
 
146
- <section class="details-section">
147
- <div class="card full-width" style="border-style: dashed;">
148
- <h3>🚚 Incoming Supply Stream</h3>
149
- <div id="shipments-container" class="shipment-list">
150
- <p class="placeholder">No active shipments in transit.</p>
151
- </div>
152
  </div>
153
- </section>
 
154
 
155
- <section class="guide-section">
156
- <div class="card full-width" style="background: rgba(45, 95, 45, 0.02);">
157
- <h3>📁 ESG Master Guidelines</h3>
158
- <div class="guide-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-top: 1rem;">
159
- <div>
160
- <h4 style="font-size: 0.8rem; color: var(--deep-forest);">MISSION PHILOSOPHY</h4>
161
- <p style="font-size: 0.85rem; color: var(--text-muted);">The NetZero-Nav initiative prioritizes 'Green-First' logistics. Always verify the Carbon Impact Preview before committing to high-speed transit.</p>
 
 
 
 
 
 
 
 
 
162
  </div>
163
- <div>
164
- <h4 style="font-size: 0.8rem; color: var(--deep-forest);">CRISIS ADAPTATION</h4>
165
- <p style="font-size: 0.85rem; color: var(--text-muted);">Regional blockades (Suez Jam) occur without warning. Divert all sea procurement to Rail or Air networks immediately upon alert.</p>
166
  </div>
167
  </div>
168
  </div>
169
- </section>
170
- </div>
171
  </main>
172
 
173
- <footer style="margin-top: 4rem; padding-top: 2rem; border-top: 1px solid var(--border-soft); text-align: center; color: var(--text-muted); font-size: 0.8rem;">
174
- &copy; 2026 NetZero-Nav | Enterprise Supply Chain Resilience
175
  </footer>
176
  </div>
177
  <script src="app.js"></script>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NetZero-Nav Dashboard</title>
7
  <link rel="stylesheet" href="style.css">
8
  <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
9
  </head>
 
15
  <h1>NETZERO-NAV</h1>
16
  <button id="info-btn" class="help-circle">?</button>
17
  </div>
18
+ <div id="connection-status" class="status-badge">Connecting...</div>
19
  </header>
20
 
21
  <!-- Interactive Walkthrough -->
 
25
  <!-- Dynamic Content -->
26
  </div>
27
  <div class="modal-footer">
28
+ <button id="tut-prev" class="btn btn-outline small hidden">Back</button>
29
+ <button id="tut-next" class="btn btn-primary small">Next</button>
30
+ <button id="tut-close" class="btn btn-ghost small">Skip Tour</button>
31
  </div>
32
  </div>
33
  </div>
34
 
35
  <main>
36
+ <section class="control-panel card">
37
+ <h3><span class="i-icon">ℹ️</span> Command Console</h3>
 
38
 
39
  <div class="operation-sections">
40
+ <!-- Section 1: Logistics -->
41
  <div id="section-order" class="op-section">
42
  <h4>📦 Supply Logistics</h4>
43
  <div class="op-row">
44
  <select id="part-select">
45
+ <option value="chips">Order Microchips</option>
46
+ <option value="sensors">Order Sensors</option>
47
  </select>
48
  <select id="mode-select">
49
+ <option value="sea">SEA ($50, 10d, 0.5kg)</option>
50
+ <option value="rail">RAIL ($125, 5d, 2.5kg)</option>
51
+ <option value="air">AIR ($250, 2d, 10kg)</option>
52
  </select>
53
+ <button onclick="execute('order_parts')" class="btn btn-primary small">Order</button>
54
  </div>
55
  </div>
56
 
57
  <hr class="divider">
58
 
59
+ <!-- Section 2: Manufacturing -->
60
  <div id="section-produce" class="op-section">
61
  <h4>🏭 Manufacturing</h4>
62
  <div class="op-row">
63
  <select id="product-select">
64
+ <option value="EcoPhone">Produce EcoPhone</option>
65
+ <option value="GreenTab">Produce GreenTab</option>
66
  </select>
67
+ <button onclick="execute('produce')" class="btn btn-primary small">Start Run</button>
68
  </div>
 
69
  </div>
70
 
71
  <hr class="divider">
72
 
73
+ <!-- Section 3: ESG / Offsetting -->
74
  <div id="section-offset" class="op-section">
75
+ <h4>🌳 ESG Strategy</h4>
76
  <div class="op-row">
77
+ <span class="op-text">Carbon Offset Purchase (100 units)</span>
78
+ <button onclick="execute('offset')" class="btn btn-primary small">Buy Offset</button>
79
  </div>
80
  </div>
81
  </div>
82
 
83
+ <div id="impact-preview" class="impact-preview">
84
+ <span class="preview-label">Live Estimate:</span>
85
+ <span id="preview-cost" class="val">$0</span>
86
+ <span class="sep">|</span>
87
+ <span id="preview-carbon" class="val">0kg CO2</span>
 
 
88
  </div>
89
 
90
+ <div class="time-controls">
91
+ <button id="skip-btn" class="btn btn-secondary">Advance to Next Day</button>
92
+ <button id="trigger-btn" class="btn btn-danger">Trigger Suez Jam</button>
93
+ <button id="reset-btn" class="btn btn-outline">Reset Sim</button>
94
  </div>
 
95
  <div id="news-alert" class="news-alert hidden">
96
+ <span class="warning-icon">⚠️</span>
97
  <span id="news-text"></span>
98
  </div>
99
  </section>
100
 
101
+ <section class="metrics-grid">
102
+ <div class="card metric-card">
103
+ <h3>Carbon Footprint</h3>
104
+ <div class="gauge-container">
105
+ <div id="carbon-gauge" class="gauge">
106
+ <span id="carbon-value">0</span><small>kg</small>
 
 
 
107
  </div>
 
108
  </div>
109
+ <p class="limit">Limit: <span id="carbon-limit">1000</span>kg</p>
110
+ </div>
111
 
112
+ <div class="card metric-card">
113
+ <h3>Capital Balance</h3>
114
+ <div class="value-large">$<span id="cash-value">0</span></div>
115
+ <div class="trend" id="cash-trend">Fulfillment: <span id="orders-done">0</span></div>
116
+ </div>
117
 
118
+ <div class="card metric-card">
119
+ <h3>Raw Inventory</h3>
120
+ <div class="inventory-grid">
121
+ <div class="inv-item">
122
+ <span class="label">Chips</span>
123
+ <span id="chips-count" class="count">0</span>
124
+ </div>
125
+ <div class="inv-item">
126
+ <span class="label">Sensors</span>
127
+ <span id="sensors-count" class="count">0</span>
 
128
  </div>
129
  </div>
130
+ </div>
131
+ </section>
132
 
133
+ <section class="details-section">
134
+ <div class="card full-width">
135
+ <h3>Active Logistics Stream</h3>
136
+ <div id="shipments-container" class="shipment-list">
137
+ <p class="placeholder">Awaiting shipment data...</p>
 
138
  </div>
139
+ </div>
140
+ </section>
141
 
142
+ <section class="log-section">
143
+ <div class="card full-width">
144
+ <h3>Simulation Activity Log</h3>
145
+ <div id="activity-log" class="log-box">
146
+ <p class="log-entry system">[SYSTEM] Dashboard initialized. Awaiting user action...</p>
 
147
  </div>
148
+ </div>
149
+ </section>
150
 
151
+ <section class="guide-section">
152
+ <div class="card full-width mission-card">
153
+ <h3>📁 Mission Briefing (Operator's Manual)</h3>
154
+ <div class="guide-content">
155
+ <div class="guide-grid">
156
+ <div class="guide-item">
157
+ <h4>🎯 Objective</h4>
158
+ <p>Fulfill "EcoPhone" and "GreenTab" orders by managing a global supply chain. You must keep <strong>Carbon Footprint</strong> below the quota while maximizing <strong>Capital Balance</strong>.</p>
159
+ </div>
160
+ <div class="guide-item">
161
+ <h4>🚢 Logistics Trade-offs</h4>
162
+ <ul class="guide-list">
163
+ <li><strong>SEA:</strong> Cheapest ($50), cleanest (0.5kg CO2), but very slow (10 days).</li>
164
+ <li><strong>RAIL:</strong> Balanced cost ($125), low CO2 (2.5kg), medium speed (5 days).</li>
165
+ <li><strong>AIR:</strong> Expensive ($250), high CO2 (10kg), but ultra-fast (2 days).</li>
166
+ </ul>
167
  </div>
168
+ <div class="guide-item">
169
+ <h4>☢️ Crisis Handling</h4>
170
+ <p>Clicking <strong>Trigger Suez Jam</strong> simulates a real-world blockade. Sea routes become unavailable for 7 days, forcing you to pivot to expensive Rail/Air routes to meet your deadlines.</p>
171
  </div>
172
  </div>
173
  </div>
174
+ </div>
175
+ </section>
176
  </main>
177
 
178
+ <footer>
179
+ <p>&copy; 2026 NetZero-Nav | Eco-Resilient Supply Chain Intelligence</p>
180
  </footer>
181
  </div>
182
  <script src="app.js"></script>
dashboard/style.css CHANGED
@@ -1,13 +1,13 @@
1
  :root {
2
- --cream-bg: #FDFCFB;
3
- --deep-forest: #2D5F2D;
4
- --sage-green: #7A9B7A;
5
- --accent-gold: #D4B483;
6
- --glass-white: rgba(255, 255, 255, 0.7);
7
- --border-soft: rgba(45, 95, 45, 0.1);
8
- --text-main: #2C3E2C;
9
- --text-muted: #5C6E5C;
10
- --danger: #E76F51;
11
  }
12
 
13
  * {
@@ -17,345 +17,419 @@
17
  }
18
 
19
  body {
20
- background-color: var(--cream-bg);
21
  color: var(--text-main);
22
  font-family: 'Outfit', sans-serif;
23
  line-height: 1.6;
24
- padding: 2rem;
25
- background-image:
26
- radial-gradient(circle at 10% 20%, rgba(122, 155, 122, 0.05) 0%, transparent 40%),
27
- radial-gradient(circle at 90% 80%, rgba(212, 180, 131, 0.05) 0%, transparent 40%);
28
- min-height: 100vh;
29
  }
30
 
31
  .container {
32
- max-width: 1400px;
33
  margin: 0 auto;
 
34
  }
35
 
36
- /* Header */
37
  header {
38
  display: flex;
39
  justify-content: space-between;
40
  align-items: center;
41
  margin-bottom: 3rem;
42
- padding-bottom: 1.5rem;
43
- border-bottom: 1px solid var(--border-soft);
44
  }
45
 
46
  .logo {
47
  display: flex;
48
  align-items: center;
49
- gap: 1rem;
50
  }
51
 
52
- h1 {
53
  font-family: 'Playfair Display', serif;
54
  font-size: 1.8rem;
 
55
  color: var(--deep-forest);
56
- letter-spacing: 0.05em;
57
  }
58
 
59
- .help-circle {
60
- width: 28px;
61
- height: 28px;
62
- border-radius: 50%;
63
- border: 1px solid var(--sage-green);
64
- background: transparent;
65
- color: var(--sage-green);
 
 
 
 
66
  cursor: pointer;
67
- font-size: 14px;
68
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
69
  }
70
 
71
- .help-circle:hover {
72
- background: var(--deep-forest);
73
  color: white;
74
- transform: rotate(360deg);
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
76
 
77
  .status-badge {
 
78
  padding: 0.5rem 1rem;
79
- border-radius: 30px;
80
- font-size: 0.75rem;
81
  font-weight: 600;
82
- background: #F0F0F0;
83
- color: #888;
84
- backdrop-filter: blur(5px);
85
- border: 1px solid var(--border-soft);
86
  }
87
 
88
- .status-online {
89
- background: rgba(45, 95, 45, 0.1);
90
- color: var(--deep-forest);
91
- border: 1px solid rgba(45, 95, 45, 0.2);
92
- }
93
 
94
- /* Main Layout */
95
- main {
96
  display: grid;
97
- grid-template-columns: 420px 1fr;
98
- gap: 3rem;
99
- align-items: start;
100
  }
101
 
102
- /* Cards */
103
  .card {
104
- background: var(--glass-white);
105
- backdrop-filter: blur(10px);
106
- border: 1px solid var(--glass-white);
107
- border-radius: 20px;
108
  padding: 2rem;
109
- box-shadow: 0 10px 30px rgba(0,0,0,0.03);
110
- margin-bottom: 2rem;
111
  }
112
 
113
  .card h3 {
114
- font-family: 'Playfair Display', serif;
115
- font-size: 1.25rem;
 
 
116
  margin-bottom: 1.5rem;
117
- color: var(--deep-forest);
 
 
 
118
  display: flex;
 
119
  align-items: center;
120
- gap: 0.5rem;
121
  }
122
 
123
- /* Left Sidebar: Command Console */
124
- .control-panel {
125
- position: sticky;
126
- top: 2rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  border-left: 4px solid var(--sage-green);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  }
129
 
130
  .op-section {
131
- margin-bottom: 2rem;
132
  }
133
 
134
  .op-section h4 {
135
- font-size: 0.75rem;
136
- text-transform: uppercase;
137
  color: var(--text-muted);
138
- letter-spacing: 0.1em;
139
- margin-bottom: 1rem;
140
  }
141
 
142
  .op-row {
143
  display: flex;
144
- flex-direction: column;
145
- gap: 0.75rem;
146
  }
147
 
148
- /* Custom Styled Selects */
149
- select {
150
- width: 100%;
151
- padding: 1rem;
152
- border-radius: 12px;
153
- border: 1px solid var(--border-soft);
154
- background: white;
155
- font-family: inherit;
156
  font-size: 0.9rem;
157
  color: var(--text-main);
158
- appearance: none;
159
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%232D5F2D' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
160
- background-repeat: no-repeat;
161
- background-position: right 1rem center;
162
- background-size: 1em;
163
- cursor: pointer;
164
- transition: all 0.2s;
165
  }
166
 
167
- select:hover {
168
- border-color: var(--sage-green);
169
- background-color: #fcfdfc;
170
  }
171
 
172
- /* Buttons */
173
- .btn {
174
- padding: 1rem 1.5rem;
175
- border-radius: 12px;
176
- border: none;
177
- font-family: inherit;
178
- font-weight: 600;
179
- font-size: 0.9rem;
180
- cursor: pointer;
181
- transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
182
- display: inline-flex;
183
- align-items: center;
184
- justify-content: center;
185
- gap: 0.5rem;
186
  }
187
 
188
- .btn-primary {
189
- background: var(--deep-forest);
190
- color: white;
191
- width: 100%;
 
192
  }
193
 
194
- .btn-primary:hover {
195
- transform: translateY(-2px);
196
- box-shadow: 0 5px 15px rgba(45, 95, 45, 0.3);
 
 
 
 
 
 
197
  }
198
 
199
- .btn-secondary {
200
- background: #F0F4EF;
201
- color: var(--deep-forest);
 
 
 
 
 
 
202
  }
203
 
204
- .btn-danger {
205
- background: #FFF1F0;
206
- color: var(--danger);
207
- width: 100%;
 
208
  }
209
 
210
- .btn-outline {
211
- background: transparent;
212
- border: 1px solid var(--border-soft);
213
  color: var(--text-muted);
214
  }
215
 
216
- /* Inventory Preview */
217
- .req-preview {
218
- margin-top: 1rem;
219
- padding: 1rem;
220
- background: rgba(45, 95, 45, 0.03);
221
- border-radius: 12px;
222
- font-size: 0.8rem;
223
  }
224
 
225
- .req-item {
226
- display: flex;
227
- justify-content: space-between;
228
- margin-bottom: 0.5rem;
229
  }
230
 
231
- .shortage { color: var(--danger); font-weight: 700; }
232
- .sufficient { color: var(--deep-forest); font-weight: 600; opacity: 0.8; }
 
233
 
234
- /* Right Column: Stats */
235
- .metrics-grid {
236
- display: grid;
237
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
238
- gap: 2rem;
239
- margin-bottom: 3rem;
 
 
 
 
 
240
  }
241
 
242
- .metric-card {
243
- text-align: center;
 
 
244
  }
245
 
246
- .metric-card h3 {
247
- justify-content: center;
248
- font-size: 1rem;
249
- text-transform: uppercase;
250
- letter-spacing: 0.05em;
 
 
 
 
 
 
 
 
 
251
  color: var(--text-muted);
252
  }
253
 
254
- .value-large {
255
- font-family: 'Playfair Display', serif;
256
- font-size: 2.5rem;
257
  color: var(--deep-forest);
258
- margin: 1rem 0;
259
  }
260
 
261
- .gauge-container {
262
- width: 120px;
263
- height: 120px;
264
- margin: 0 auto 1rem;
265
- position: relative;
266
- border: 8px solid rgba(45, 95, 45, 0.05);
267
- border-radius: 50%;
268
- display: flex;
269
- align-items: center;
270
- justify-content: center;
271
  }
272
 
273
- .gauge {
274
- font-family: 'Playfair Display', serif;
275
- font-size: 1.5rem;
276
- color: var(--deep-forest);
277
  }
278
 
279
- .inventory-grid {
280
  display: grid;
281
- grid-template-columns: 1fr 1fr;
282
- gap: 1rem;
 
283
  }
284
 
285
- .inv-item {
286
- padding: 1rem;
287
- background: white;
288
- border-radius: 15px;
289
- border: 1px solid var(--border-soft);
290
  }
291
 
292
- .inv-item .label {
293
- display: block;
294
- font-size: 0.7rem;
295
- text-transform: uppercase;
296
  color: var(--text-muted);
297
- margin-bottom: 0.25rem;
298
  }
299
 
300
- .inv-item .count {
301
- font-size: 1.5rem;
302
- font-weight: 600;
303
  }
304
 
305
- /* Activity Log */
306
- .log-box {
307
- background: #1A1D1A;
308
- color: #A0C0A0;
309
- padding: 1.5rem;
310
- border-radius: 15px;
311
- height: 250px;
312
- overflow-y: auto;
313
- font-family: 'Courier New', Courier, monospace;
314
- font-size: 0.85rem;
315
  }
316
 
317
- .log-entry { margin-bottom: 0.5rem; }
318
- .log-entry.error { color: #E76F51; }
319
- .log-entry.action { color: #88B04B; }
320
-
321
- /* Modal Overlay */
322
  .modal-overlay {
323
  position: fixed;
324
  top: 0;
325
  left: 0;
326
  width: 100%;
327
  height: 100%;
328
- background: rgba(45, 95, 45, 0.4);
329
- backdrop-filter: blur(8px);
330
  display: flex;
331
  justify-content: center;
332
  align-items: center;
333
- z-index: 2000;
 
334
  }
335
 
336
  .modal-card {
337
  background: white;
338
- padding: 3rem;
339
- border-radius: 30px;
340
- max-width: 550px;
341
- box-shadow: 0 30px 60px rgba(0,0,0,0.15);
 
 
 
342
  }
343
 
344
- .news-alert {
345
- background: #FFF5F2;
346
- padding: 1rem;
347
- border-radius: 12px;
348
- border: 1px solid rgba(231, 111, 81, 0.2);
349
- color: var(--danger);
350
- font-weight: 600;
351
- font-size: 0.9rem;
352
- margin-top: 1.5rem;
353
  }
354
 
355
- /* Utility */
356
- .hidden { display: none !important; }
 
 
 
357
 
358
- @media (max-width: 1100px) {
359
- main { grid-template-columns: 1fr; }
360
- .control-panel { position: static; }
 
361
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  :root {
2
+ --bg-cream: #FDFBF7;
3
+ --card-white: #FFFFFF;
4
+ --primary-green: #2C5F2D;
5
+ --sage-green: #97BC62;
6
+ --deep-forest: #004225;
7
+ --border-tan: #E9E1D4;
8
+ --text-main: #333333;
9
+ --text-muted: #666666;
10
+ --accent-gold: #D4AF37;
11
  }
12
 
13
  * {
 
17
  }
18
 
19
  body {
20
+ background-color: var(--bg-cream);
21
  color: var(--text-main);
22
  font-family: 'Outfit', sans-serif;
23
  line-height: 1.6;
 
 
 
 
 
24
  }
25
 
26
  .container {
27
+ max-width: 1200px;
28
  margin: 0 auto;
29
+ padding: 2rem;
30
  }
31
 
 
32
  header {
33
  display: flex;
34
  justify-content: space-between;
35
  align-items: center;
36
  margin-bottom: 3rem;
37
+ border-bottom: 2px solid var(--border-tan);
38
+ padding-bottom: 1rem;
39
  }
40
 
41
  .logo {
42
  display: flex;
43
  align-items: center;
44
+ gap: 0.75rem;
45
  }
46
 
47
+ .logo h1 {
48
  font-family: 'Playfair Display', serif;
49
  font-size: 1.8rem;
50
+ letter-spacing: 2px;
51
  color: var(--deep-forest);
 
52
  }
53
 
54
+ .controls {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: 1rem;
58
+ }
59
+
60
+ .btn {
61
+ padding: 0.6rem 1.2rem;
62
+ border-radius: 8px;
63
+ font-size: 0.85rem;
64
+ font-weight: 600;
65
  cursor: pointer;
66
+ transition: all 0.2s ease;
67
+ border: none;
68
  }
69
 
70
+ .btn-primary {
71
+ background-color: var(--primary-green);
72
  color: white;
73
+ }
74
+
75
+ .btn-primary:hover {
76
+ background-color: var(--deep-forest);
77
+ }
78
+
79
+ .btn-outline {
80
+ background-color: transparent;
81
+ border: 1.5px solid var(--primary-green);
82
+ color: var(--primary-green);
83
+ }
84
+
85
+ .btn-outline:hover {
86
+ background-color: #E1F5E1;
87
  }
88
 
89
  .status-badge {
90
+ background: var(--border-tan);
91
  padding: 0.5rem 1rem;
92
+ border-radius: 20px;
93
+ font-size: 0.8rem;
94
  font-weight: 600;
 
 
 
 
95
  }
96
 
97
+ .status-online { background: #E1F5E1; color: #2C5F2D; }
 
 
 
 
98
 
99
+ .metrics-grid {
 
100
  display: grid;
101
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
102
+ gap: 2rem;
103
+ margin-bottom: 2rem;
104
  }
105
 
 
106
  .card {
107
+ background: var(--card-white);
108
+ border: 1px solid var(--border-tan);
109
+ border-radius: 12px;
 
110
  padding: 2rem;
111
+ box-shadow: 0 4px 15px rgba(0,0,0,0.02);
112
+ transition: transform 0.3s ease;
113
  }
114
 
115
  .card h3 {
116
+ font-size: 0.9rem;
117
+ text-transform: uppercase;
118
+ letter-spacing: 1px;
119
+ color: var(--text-muted);
120
  margin-bottom: 1.5rem;
121
+ }
122
+
123
+ .gauge-container {
124
+ height: 100px;
125
  display: flex;
126
+ justify-content: center;
127
  align-items: center;
 
128
  }
129
 
130
+ .gauge {
131
+ font-size: 2.5rem;
132
+ font-weight: 700;
133
+ color: var(--primary-green);
134
+ }
135
+
136
+ .gauge small {
137
+ font-size: 1rem;
138
+ margin-left: 0.2rem;
139
+ }
140
+
141
+ .value-large {
142
+ font-size: 3rem;
143
+ font-weight: 600;
144
+ color: var(--deep-forest);
145
+ }
146
+
147
+ .shipment-list {
148
+ display: flex;
149
+ flex-direction: column;
150
+ gap: 0.75rem;
151
+ }
152
+
153
+ .shipment-item {
154
+ background: var(--bg-cream);
155
+ padding: 1rem;
156
+ border-radius: 8px;
157
  border-left: 4px solid var(--sage-green);
158
+ display: flex;
159
+ justify-content: space-between;
160
+ }
161
+
162
+ .ticker-box {
163
+ background: var(--bg-cream);
164
+ padding: 1rem;
165
+ border-radius: 8px;
166
+ font-style: italic;
167
+ color: var(--deep-forest);
168
+ }
169
+
170
+ .operation-sections {
171
+ display: flex;
172
+ flex-direction: column;
173
+ gap: 1.5rem;
174
  }
175
 
176
  .op-section {
177
+ padding: 0.5rem 0;
178
  }
179
 
180
  .op-section h4 {
181
+ font-size: 0.8rem;
 
182
  color: var(--text-muted);
183
+ text-transform: uppercase;
184
+ margin-bottom: 0.75rem;
185
  }
186
 
187
  .op-row {
188
  display: flex;
189
+ gap: 1rem;
190
+ align-items: center;
191
  }
192
 
193
+ .op-text {
 
 
 
 
 
 
 
194
  font-size: 0.9rem;
195
  color: var(--text-main);
196
+ flex-grow: 1;
 
 
 
 
 
 
197
  }
198
 
199
+ .divider {
200
+ border: none;
201
+ border-top: 1px solid var(--border-tan);
202
  }
203
 
204
+ .time-controls {
205
+ margin-top: 2rem;
206
+ display: flex;
207
+ gap: 1rem;
208
+ padding-top: 1.5rem;
209
+ border-top: 2px solid var(--border-tan);
 
 
 
 
 
 
 
 
210
  }
211
 
212
+ .hidden { display: none !important; }
213
+
214
+ .sub-inputs {
215
+ display: flex;
216
+ gap: 1rem;
217
  }
218
 
219
+ .log-box {
220
+ height: 150px;
221
+ overflow-y: auto;
222
+ background: #1A1A1A;
223
+ color: #00FF41;
224
+ font-family: 'Courier New', Courier, monospace;
225
+ padding: 1rem;
226
+ border-radius: 8px;
227
+ font-size: 0.85rem;
228
  }
229
 
230
+ .log-entry { margin-bottom: 0.4rem; }
231
+ .log-entry.system { color: #AAAAAA; }
232
+ .log-entry.action { color: #00FF41; }
233
+ .log-entry.error { color: #FF4136; }
234
+
235
+ .inventory-grid {
236
+ display: flex;
237
+ justify-content: space-around;
238
+ padding-top: 0.5rem;
239
  }
240
 
241
+ .inv-item {
242
+ display: flex;
243
+ flex-direction: column;
244
+ align-items: center;
245
+ gap: 0.5rem;
246
  }
247
 
248
+ .inv-item .label {
249
+ font-size: 0.7rem;
 
250
  color: var(--text-muted);
251
  }
252
 
253
+ .inv-item .count {
254
+ font-size: 2rem;
255
+ font-weight: 600;
256
+ color: var(--primary-green);
 
 
 
257
  }
258
 
259
+ .btn-danger {
260
+ background-color: #FF4136;
261
+ color: white;
 
262
  }
263
 
264
+ .btn-danger:hover {
265
+ background-color: #B22222;
266
+ }
267
 
268
+ .news-alert {
269
+ margin-top: 1.5rem;
270
+ padding: 1rem;
271
+ background: #FFF5F5;
272
+ border: 1px solid #FFC1C1;
273
+ border-radius: 8px;
274
+ color: #FF4136;
275
+ font-weight: 600;
276
+ display: flex;
277
+ gap: 1rem;
278
+ animation: pulse-border 2s infinite;
279
  }
280
 
281
+ @keyframes pulse-border {
282
+ 0% { border-color: #FFC1C1; }
283
+ 50% { border-color: #FF4136; }
284
+ 100% { border-color: #FFC1C1; }
285
  }
286
 
287
+ .impact-preview {
288
+ margin: 1.5rem 0;
289
+ padding: 1rem;
290
+ background: #F0F4F0;
291
+ border-radius: 8px;
292
+ border-left: 4px solid var(--primary-green);
293
+ font-size: 0.9rem;
294
+ display: flex;
295
+ gap: 0.75rem;
296
+ align-items: center;
297
+ }
298
+
299
+ .impact-preview .preview-label {
300
+ font-weight: 600;
301
  color: var(--text-muted);
302
  }
303
 
304
+ .impact-preview .val {
305
+ font-weight: 600;
 
306
  color: var(--deep-forest);
 
307
  }
308
 
309
+ .impact-preview .sep {
310
+ color: var(--border-tan);
 
 
 
 
 
 
 
 
311
  }
312
 
313
+ .mission-card {
314
+ background: #F4F7F9;
315
+ border: 1px dashed var(--sage-green);
 
316
  }
317
 
318
+ .guide-grid {
319
  display: grid;
320
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
321
+ gap: 2rem;
322
+ margin-top: 1rem;
323
  }
324
 
325
+ .guide-item h4 {
326
+ color: var(--primary-green);
327
+ margin-bottom: 0.5rem;
328
+ font-size: 0.9rem;
329
+ text-transform: uppercase;
330
  }
331
 
332
+ .guide-item p, .guide-item li {
333
+ font-size: 0.85rem;
 
 
334
  color: var(--text-muted);
 
335
  }
336
 
337
+ .guide-list {
338
+ padding-left: 1.2rem;
 
339
  }
340
 
341
+ .guide-list li {
342
+ margin-bottom: 0.3rem;
 
 
 
 
 
 
 
 
343
  }
344
 
345
+ /* Modal & Tutorial Styles */
 
 
 
 
346
  .modal-overlay {
347
  position: fixed;
348
  top: 0;
349
  left: 0;
350
  width: 100%;
351
  height: 100%;
352
+ background: rgba(0, 0, 0, 0.4);
353
+ backdrop-filter: blur(4px);
354
  display: flex;
355
  justify-content: center;
356
  align-items: center;
357
+ z-index: 1000;
358
+ transition: all 0.3s ease;
359
  }
360
 
361
  .modal-card {
362
  background: white;
363
+ padding: 2.5rem;
364
+ border-radius: 20px;
365
+ max-width: 500px;
366
+ width: 90%;
367
+ box-shadow: 0 20px 50px rgba(0,0,0,0.1);
368
+ border: 1px solid var(--border-tan);
369
+ animation: slideUp 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
370
  }
371
 
372
+ @keyframes slideUp {
373
+ from { opacity: 0; transform: translateY(30px); }
374
+ to { opacity: 1; transform: translateY(0); }
 
 
 
 
 
 
375
  }
376
 
377
+ .tutorial-content h2 {
378
+ font-family: 'Playfair Display', serif;
379
+ color: var(--deep-forest);
380
+ margin-bottom: 1rem;
381
+ }
382
 
383
+ .tutorial-content p {
384
+ color: var(--text-muted);
385
+ font-size: 1rem;
386
+ margin-bottom: 2rem;
387
  }
388
+
389
+ .modal-footer {
390
+ display: flex;
391
+ justify-content: space-between;
392
+ align-items: center;
393
+ }
394
+
395
+ .help-circle {
396
+ width: 24px;
397
+ height: 24px;
398
+ border-radius: 50%;
399
+ border: 1px solid var(--sage-green);
400
+ background: white;
401
+ color: var(--primary-green);
402
+ cursor: pointer;
403
+ font-weight: bold;
404
+ font-size: 12px;
405
+ transition: all 0.2s;
406
+ }
407
+
408
+ .help-circle:hover { background: var(--sage-green); color: white; }
409
+
410
+ .small { padding: 0.4rem 0.8rem; font-size: 0.75rem; }
411
+ .btn-ghost { background: transparent; color: var(--text-muted); }
412
+
413
+ /* Day Transition Blur */
414
+ .transitioning {
415
+ filter: blur(2px);
416
+ pointer-events: none;
417
+ transition: filter 0.3s ease;
418
+ }
419
+
420
+ footer {
421
+ text-align: center;
422
+ margin-top: 4rem;
423
+ padding-top: 1rem;
424
+ border-top: 1px solid var(--border-tan);
425
+ color: var(--text-muted);
426
+ font-size: 0.8rem;
427
+ }
428
+
429
+ @media (max-width: 768px) {
430
+ .container { padding: 1rem; }
431
+ }
432
+
433
+ .action-buttons { display: flex; gap: 0.75rem; }
434
+ .btn-secondary { background-color: var(--sage-green); color: var(--deep-forest); }
435
+ .btn-secondary:hover { background-color: #7AA346; }