Javedalam commited on
Commit
27be395
·
verified ·
1 Parent(s): beca790

Upload 6 files

Browse files
Files changed (6) hide show
  1. index.html +210 -0
  2. project.md +39 -0
  3. script.js +288 -0
  4. server.js +47 -0
  5. style.css +352 -28
  6. test_solver.js +78 -0
index.html ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Interactive SDOF Vibration Simulator</title>
8
+ <meta name="description"
9
+ content="Interactive simulation of a damped single-degree-of-freedom (SDOF) vibration system. Visualize displacement and velocity in real-time.">
10
+ <link rel="stylesheet" href="style.css">
11
+ <link rel="preconnect" href="https://fonts.googleapis.com">
12
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13
+ <link
14
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Fira+Code:wght@500&display=swap"
15
+ rel="stylesheet">
16
+ </head>
17
+
18
+ <body>
19
+ <div class="container">
20
+ <header>
21
+ <h1>SDOF Vibration Simulator</h1>
22
+ <p>Explore the dynamics of a Single-Degree-of-Freedom system. Adjust parameters to see real-time response.
23
+ </p>
24
+ </header>
25
+
26
+ <!-- Visual Panel -->
27
+ <section class="visual-panel">
28
+ <div class="diagram-container">
29
+ <!-- SVG Diagram -->
30
+ <svg viewBox="0 0 480 180" width="100%" role="img" aria-label="SDOF System Diagram">
31
+ <pattern id="hatch" patternUnits="userSpaceOnUse" width="6" height="6">
32
+ <path d="M-1,1 l2,-2 M0,6 l6,-6 M5,7 l2,-2" stroke="#4b5563" stroke-width="1" />
33
+ </pattern>
34
+ <rect x="0" y="150" width="480" height="20" fill="url(#hatch)" />
35
+ <rect x="10" y="40" width="20" height="110" fill="#111827" stroke="#111827" stroke-width="2" />
36
+ <g transform="translate(30,90)">
37
+ <line x1="0" y1="0" x2="20" y2="0" stroke="#111827" stroke-width="2" />
38
+ <polyline points="20,0 30,-10 40,10 50,-10 60,10 70,-10 80,10 90,-10 100,0" fill="none"
39
+ stroke="#111827" stroke-width="2" />
40
+ <line x1="100" y1="0" x2="120" y2="0" stroke="#111827" stroke-width="2" />
41
+ </g>
42
+ <g transform="translate(30,120)">
43
+ <line x1="0" y1="0" x2="20" y2="0" stroke="#111827" stroke-width="2" />
44
+ <rect x="20" y="-8" width="25" height="16" fill="none" stroke="#111827" stroke-width="2" />
45
+ <line x1="45" y1="0" x2="70" y2="0" stroke="#111827" stroke-width="2" />
46
+ <line x1="70" y1="-8" x2="70" y2="8" stroke="#111827" stroke-width="2" />
47
+ <line x1="70" y1="-8" x2="80" y2="-8" stroke="#111827" stroke-width="2" />
48
+ <line x1="70" y1="8" x2="80" y2="8" stroke="#111827" stroke-width="2" />
49
+ <line x1="80" y1="-8" x2="80" y2="8" stroke="#111827" stroke-width="2" />
50
+ <line x1="80" y1="0" x2="120" y2="0" stroke="#111827" stroke-width="2" />
51
+ </g>
52
+ <rect x="150" y="70" width="110" height="70" fill="#ffffff" stroke="#111827" stroke-width="2" />
53
+ <circle cx="175" cy="145" r="8" fill="#ffffff" stroke="#111827" stroke-width="2" />
54
+ <circle cx="235" cy="145" r="8" fill="#ffffff" stroke="#111827" stroke-width="2" />
55
+ <defs>
56
+ <marker id="arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
57
+ <path d="M0,0 L8,4 L0,8 z" fill="#111827" />
58
+ </marker>
59
+ </defs>
60
+ <line x1="150" y1="55" x2="230" y2="55" stroke="#111827" stroke-width="2"
61
+ marker-end="url(#arrow)" />
62
+ <text x="232" y="59" font-size="14" font-family="sans-serif">u(t)</text>
63
+ <line x1="260" y1="105" x2="330" y2="105" stroke="#111827" stroke-width="2"
64
+ marker-end="url(#arrow)" />
65
+ <text x="332" y="93" font-size="14" font-family="sans-serif">F(t)</text>
66
+ </svg>
67
+ </div>
68
+ </section>
69
+
70
+ <!-- Theory Panel -->
71
+ <section class="theory-panel">
72
+ <h2>Theory & Guide</h2>
73
+ <div class="theory-content">
74
+ <div class="theory-block">
75
+ <h3>System Parameters</h3>
76
+ <ul>
77
+ <li><strong>m (Mass):</strong> Inertia of the system (kg).</li>
78
+ <li><strong>k (Stiffness):</strong> Resistance to deformation (N/m).</li>
79
+ <li><strong>c (Damping):</strong> Energy dissipation coefficient (N·s/m).</li>
80
+ </ul>
81
+ </div>
82
+ <div class="theory-block">
83
+ <h3>Forcing</h3>
84
+ <ul>
85
+ <li><strong>F₀ (Amplitude):</strong> Magnitude of external force (N).</li>
86
+ <li><strong>p (Frequency):</strong> Frequency of external force (rad/s).</li>
87
+ </ul>
88
+ </div>
89
+ <div class="theory-block">
90
+ <h3>Fundamental Equations</h3>
91
+ <p>Natural Frequency: <span class="math">ωₙ = √(k/m)</span></p>
92
+ <p>Damping Ratio: <span class="math">ζ = c / (2√(km))</span></p>
93
+ </div>
94
+ <div class="theory-block">
95
+ <h3>Damping Regimes</h3>
96
+ <ul>
97
+ <li><strong>Underdamped (ζ < 1):</strong> Oscillates with decaying amplitude.</li>
98
+ <li><strong>Critically Damped (ζ = 1):</strong> Fastest return to equilibrium without
99
+ oscillation.</li>
100
+ <li><strong>Overdamped (ζ > 1):</strong> Slow return to equilibrium without oscillation.</li>
101
+ </ul>
102
+ </div>
103
+ </div>
104
+ <div class="theory-footer">
105
+ <strong>Simulation Tip:</strong> For <em>Free Vibration</em>, set <strong>F₀ = 0</strong> and adjust
106
+ Initial Displacement (<strong>u₀</strong>) or Velocity (<strong>v₀</strong>).
107
+ </div>
108
+ </section>
109
+
110
+ <main class="dashboard">
111
+ <!-- Controls Panel (Left Column) -->
112
+ <section class="controls-panel">
113
+ <div class="control-group">
114
+ <label for="mass-slider">Mass (m)</label>
115
+ <div class="slider-wrapper">
116
+ <input type="range" id="mass-slider" min="0.5" max="5" step="0.1" value="1">
117
+ <span class="slider-value" id="mass-val">1.0</span>
118
+ </div>
119
+ </div>
120
+ <div class="control-group">
121
+ <label for="stiffness-slider">Stiffness (k)</label>
122
+ <div class="slider-wrapper">
123
+ <input type="range" id="stiffness-slider" min="20" max="800" step="10" value="100">
124
+ <span class="slider-value" id="stiffness-val">100</span>
125
+ </div>
126
+ </div>
127
+ <div class="control-group">
128
+ <label for="damping-slider">Damping (c)</label>
129
+ <div class="slider-wrapper">
130
+ <input type="range" id="damping-slider" min="0" max="60" step="0.5" value="5">
131
+ <span class="slider-value" id="damping-val">5.0</span>
132
+ </div>
133
+ </div>
134
+ <div class="control-group">
135
+ <label for="force-slider">Forcing Amp (F0)</label>
136
+ <div class="slider-wrapper">
137
+ <input type="range" id="force-slider" min="0" max="80" step="1" value="10">
138
+ <span class="slider-value" id="force-val">10</span>
139
+ </div>
140
+ </div>
141
+ <div class="control-group">
142
+ <label for="p-slider">Forcing Freq (p)</label>
143
+ <div class="slider-wrapper">
144
+ <input type="range" id="p-slider" min="0.5" max="20" step="0.5" value="5">
145
+ <span class="slider-value" id="p-val">5.0</span>
146
+ </div>
147
+ </div>
148
+ <div class="control-group">
149
+ <label for="u0-slider">Initial Disp. (u0)</label>
150
+ <div class="slider-wrapper">
151
+ <input type="range" id="u0-slider" min="-5" max="5" step="0.1" value="0">
152
+ <span class="slider-value" id="u0-val">0.0</span>
153
+ </div>
154
+ </div>
155
+ <div class="control-group">
156
+ <label for="v0-slider">Initial Vel. (v0)</label>
157
+ <div class="slider-wrapper">
158
+ <input type="range" id="v0-slider" min="-10" max="10" step="0.1" value="0">
159
+ <span class="slider-value" id="v0-val">0.0</span>
160
+ </div>
161
+ </div>
162
+
163
+ <!-- Calculated Stats (Moved inside Controls Panel) -->
164
+ <div class="stats-panel"
165
+ style="margin-top: 1.5rem; border-top: 1px solid var(--border); padding-top: 1.5rem;">
166
+ <div class="stat-card">
167
+ <span class="stat-label">Natural Frequency (ωn)</span>
168
+ <span class="stat-value" id="omega-n-val">--</span>
169
+ </div>
170
+ <div class="stat-card">
171
+ <span class="stat-label">Damping Ratio (ζ)</span>
172
+ <span class="stat-value" id="zeta-val">--</span>
173
+ </div>
174
+ <div class="stat-card">
175
+ <span class="stat-label">Regime</span>
176
+ <span class="stat-value highlight" id="regime-val">--</span>
177
+ </div>
178
+ <div class="stat-card">
179
+ <span class="stat-label">Peak Disp.</span>
180
+ <span class="stat-value" id="peak-disp-val">--</span>
181
+ </div>
182
+ <div class="stat-card">
183
+ <span class="stat-label">Peak Vel.</span>
184
+ <span class="stat-value" id="peak-vel-val">--</span>
185
+ </div>
186
+ </div>
187
+ </section>
188
+
189
+ <!-- Charts Panel (Bottom Right) -->
190
+ <section class="charts-panel">
191
+ <div class="equation-container">
192
+ <h3>System Equation</h3>
193
+ <div id="math-equation"></div>
194
+ <div id="math-solution"></div>
195
+ </div>
196
+ <div class="chart-container">
197
+ <h3>Displacement u(t)</h3>
198
+ <canvas id="disp-chart"></canvas>
199
+ </div>
200
+ <div class="chart-container">
201
+ <h3>Velocity u'(t)</h3>
202
+ <canvas id="vel-chart"></canvas>
203
+ </div>
204
+ </section>
205
+ </main>
206
+ </div>
207
+ <script src="script.js"></script>
208
+ </body>
209
+
210
+ </html>
project.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Interactive SDOF Vibration Simulator
2
+
3
+ ## Overview
4
+ The **Interactive SDOF Vibration Simulator** is a web-based educational tool designed to help students and engineers visualize the dynamics of a **Single-Degree-of-Freedom (SDOF)** system. It provides real-time simulation and visualization of how a mass-spring-damper system responds to external forces and initial conditions.
5
+
6
+ ## Key Features
7
+
8
+ ### 1. Real-Time Simulation
9
+ - Powered by a custom **Runge-Kutta 4th Order (RK4)** solver for high accuracy.
10
+ - Solves the differential equation: $m\ddot{u} + c\dot{u} + ku = F(t)$.
11
+ - Updates continuously as you adjust parameters.
12
+
13
+ ### 2. Interactive Controls
14
+ Adjust system parameters on the fly with responsive sliders:
15
+ - **System Properties**: Mass ($m$), Stiffness ($k$), Damping Coefficient ($c$).
16
+ - **Forcing**: Force Amplitude ($F_0$), Forcing Frequency ($p$).
17
+ - **Initial Conditions**: Initial Displacement ($u_0$), Initial Velocity ($v_0$).
18
+
19
+ ### 3. Dynamic Visualization
20
+ - **Animated Diagram**: A physics-based SVG representation of the mass, spring, and damper that moves in sync with the simulation.
21
+ - **Live Plots**: Real-time time-history graphs for **Displacement ($u$)** and **Velocity ($\dot{u}$)**.
22
+
23
+ ### 4. Instant Analysis
24
+ Automatically calculates and displays key system characteristics:
25
+ - **Natural Frequency ($\omega_n$)**
26
+ - **Damping Ratio ($\zeta$)**
27
+ - **Damping Regime**: Automatically classifies the system as **Underdamped**, **Critically Damped**, or **Overdamped**.
28
+
29
+ ## Technical Details
30
+ - **Frontend**: Pure HTML5, CSS3, and Vanilla JavaScript.
31
+ - **Styling**: Modern, responsive dark-mode design using CSS Variables and Flexbox/Grid.
32
+ - **Performance**: Optimized canvas rendering for smooth 60fps plotting.
33
+ - **No Dependencies**: Runs entirely in the browser without external libraries.
34
+
35
+ ## Usage
36
+ 1. Open `index.html` in a modern web browser.
37
+ 2. Use the sliders on the left to modify the system.
38
+ 3. Observe the diagram and plots update instantly.
39
+ 4. To simulate **Free Vibration**, set the **Forcing Amp ($F_0$)** to `0` and adjust the **Initial Displacement** or **Velocity**.
script.js ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Interactive SDOF Vibration Simulator
3
+ * Implements RK4 solver and real-time plotting
4
+ */
5
+
6
+ // --- Constants & State ---
7
+ const state = {
8
+ m: 1.0,
9
+ k: 100,
10
+ c: 5.0,
11
+ F0: 10,
12
+ p: 5.0, // Forcing frequency (rad/s)
13
+ u0: 0.0,
14
+ v0: 0.0,
15
+ tMax: 10.0,
16
+ dt: 0.001
17
+ };
18
+
19
+ // --- DOM Elements ---
20
+ const elements = {
21
+ sliders: {
22
+ m: document.getElementById('mass-slider'),
23
+ k: document.getElementById('stiffness-slider'),
24
+ c: document.getElementById('damping-slider'),
25
+ F0: document.getElementById('force-slider'),
26
+ p: document.getElementById('p-slider'),
27
+ u0: document.getElementById('u0-slider'),
28
+ v0: document.getElementById('v0-slider')
29
+ },
30
+ values: {
31
+ m: document.getElementById('mass-val'),
32
+ k: document.getElementById('stiffness-val'),
33
+ c: document.getElementById('damping-val'),
34
+ F0: document.getElementById('force-val'),
35
+ p: document.getElementById('p-val'),
36
+ u0: document.getElementById('u0-val'),
37
+ v0: document.getElementById('v0-val')
38
+ },
39
+ stats: {
40
+ omegaN: document.getElementById('omega-n-val'),
41
+ zeta: document.getElementById('zeta-val'),
42
+ regime: document.getElementById('regime-val'),
43
+ peakDisp: document.getElementById('peak-disp-val'),
44
+ peakVel: document.getElementById('peak-vel-val')
45
+ },
46
+ canvas: {
47
+ disp: document.getElementById('disp-chart'),
48
+ vel: document.getElementById('vel-chart')
49
+ },
50
+ equation: {
51
+ math: document.getElementById('math-equation'),
52
+ sol: document.getElementById('math-solution')
53
+ }
54
+ };
55
+
56
+ // --- Physics Engine ---
57
+
58
+ /**
59
+ * Differential equations for SDOF system
60
+ * u' = v
61
+ * v' = (F0 * sin(p * t) - c * v - k * u) / m
62
+ */
63
+ function derivatives(t, u, v) {
64
+ const u_prime = v;
65
+ const v_prime = (state.F0 * Math.sin(state.p * t) - state.c * v - state.k * u) / state.m;
66
+ return { u_prime, v_prime };
67
+ }
68
+
69
+ function solve() {
70
+ let t = 0;
71
+ let u = state.u0; // Initial displacement
72
+ let v = state.v0; // Initial velocity
73
+
74
+ const results = {
75
+ t: [],
76
+ u: [],
77
+ v: []
78
+ };
79
+
80
+ const steps = Math.ceil(state.tMax / state.dt);
81
+
82
+ for (let i = 0; i <= steps; i++) {
83
+ // Store current state
84
+ if (i % 10 === 0) { // Downsample for plotting (store every 10th point)
85
+ results.t.push(t);
86
+ results.u.push(u);
87
+ results.v.push(v);
88
+ }
89
+
90
+ // RK4 Steps
91
+ const k1 = derivatives(t, u, v);
92
+
93
+ const t2 = t + state.dt / 2;
94
+ const u2 = u + k1.u_prime * state.dt / 2;
95
+ const v2 = v + k1.v_prime * state.dt / 2;
96
+ const k2 = derivatives(t2, u2, v2);
97
+
98
+ const t3 = t + state.dt / 2;
99
+ const u3 = u + k2.u_prime * state.dt / 2;
100
+ const v3 = v + k2.v_prime * state.dt / 2;
101
+ const k3 = derivatives(t3, u3, v3);
102
+
103
+ const t4 = t + state.dt;
104
+ const u4 = u + k3.u_prime * state.dt;
105
+ const v4 = v + k3.v_prime * state.dt;
106
+ const k4 = derivatives(t4, u4, v4);
107
+
108
+ // Update state
109
+ u += (state.dt / 6) * (k1.u_prime + 2 * k2.u_prime + 2 * k3.u_prime + k4.u_prime);
110
+ v += (state.dt / 6) * (k1.v_prime + 2 * k2.v_prime + 2 * k3.v_prime + k4.v_prime);
111
+ t += state.dt;
112
+ }
113
+
114
+ return results;
115
+ }
116
+
117
+ /**
118
+ * Calculate system properties
119
+ */
120
+ function calculateProperties() {
121
+ const omegaN = Math.sqrt(state.k / state.m);
122
+ const zeta = state.c / (2 * Math.sqrt(state.k * state.m));
123
+
124
+ let regime = '';
125
+ if (zeta < 0) regime = 'Unstable';
126
+ else if (zeta >= 0 && zeta < 1) regime = 'Underdamped';
127
+ else if (zeta === 1) regime = 'Critically Damped';
128
+ else regime = 'Overdamped';
129
+
130
+ return { omegaN, zeta, regime };
131
+ }
132
+
133
+ function updateEquationDisplay(props) {
134
+ // 1. Governing Equation
135
+ // mu'' + cu' + ku = F0sin(pt)
136
+ // mu'' + cu' + ku = F0sin(pt)
137
+ const eq = `${state.m.toFixed(1)}u'' + ${state.c.toFixed(1)}u' + ${state.k.toFixed(0)}u = ${state.F0 > 0 ? `${state.F0}sin(${state.p.toFixed(1)}t)` : '0'}`;
138
+ elements.equation.math.textContent = eq;
139
+
140
+ // 2. Solution Form
141
+ let solText = "";
142
+ if (state.F0 === 0) {
143
+ // Free Vibration
144
+ if (props.zeta < 1) {
145
+ solText = `Free Underdamped: u(t) = e^(-${(props.zeta * props.omegaN).toFixed(2)}t) * (A cos(${(props.omegaN * Math.sqrt(1 - props.zeta ** 2)).toFixed(2)}t) + B sin(...))`;
146
+ } else if (props.zeta === 1) {
147
+ solText = `Free Critically Damped: u(t) = (A + Bt) * e^(-${props.omegaN.toFixed(2)}t)`;
148
+ } else {
149
+ solText = `Free Overdamped: u(t) = A * e^(s1*t) + B * e^(s2*t)`;
150
+ }
151
+ } else {
152
+ // Forced Vibration
153
+ solText = `Forced Response: u(t) = u_h(t) [Transient] + u_p(t) [Steady State]`;
154
+ }
155
+ elements.equation.sol.textContent = solText;
156
+ }
157
+
158
+ // --- Rendering ---
159
+
160
+ function drawChart(canvas, timeData, valueData, color, label) {
161
+ const ctx = canvas.getContext('2d');
162
+ const width = canvas.width;
163
+ const height = canvas.height;
164
+
165
+ // Clear canvas
166
+ ctx.clearRect(0, 0, width, height);
167
+
168
+ // Setup scaling
169
+ const padding = 40;
170
+ const plotWidth = width - 2 * padding;
171
+ const plotHeight = height - 2 * padding;
172
+
173
+ const minVal = Math.min(...valueData);
174
+ const maxVal = Math.max(...valueData);
175
+ const range = maxVal - minVal || 1; // Avoid division by zero
176
+
177
+ // Helper to map coordinates
178
+ const mapX = (t) => padding + (t / state.tMax) * plotWidth;
179
+ const mapY = (val) => height - padding - ((val - minVal) / range) * plotHeight;
180
+
181
+ // Draw Grid
182
+ ctx.strokeStyle = '#334155';
183
+ ctx.lineWidth = 1;
184
+ ctx.beginPath();
185
+ // Zero line
186
+ if (minVal < 0 && maxVal > 0) {
187
+ const yZero = mapY(0);
188
+ ctx.moveTo(padding, yZero);
189
+ ctx.lineTo(width - padding, yZero);
190
+ }
191
+ ctx.stroke();
192
+
193
+ // Draw Curve
194
+ ctx.strokeStyle = color;
195
+ ctx.lineWidth = 2;
196
+ ctx.beginPath();
197
+ ctx.moveTo(mapX(timeData[0]), mapY(valueData[0]));
198
+
199
+ for (let i = 1; i < timeData.length; i++) {
200
+ ctx.lineTo(mapX(timeData[i]), mapY(valueData[i]));
201
+ }
202
+ ctx.stroke();
203
+
204
+ // Draw Axes Labels (Simple)
205
+ ctx.fillStyle = '#94a3b8';
206
+ ctx.font = '12px Inter';
207
+ ctx.fillText('0', padding, height - padding + 15);
208
+ ctx.fillText(state.tMax + 's', width - padding - 20, height - padding + 15);
209
+
210
+ ctx.fillText(maxVal.toFixed(2), 5, padding + 5);
211
+ ctx.fillText(minVal.toFixed(2), 5, height - padding + 5);
212
+ }
213
+
214
+
215
+ let isUpdating = false;
216
+
217
+ function updateUI() {
218
+ if (isUpdating) return;
219
+ isUpdating = true;
220
+
221
+ requestAnimationFrame(() => {
222
+ // 1. Solve
223
+ console.log("Solving with state:", JSON.stringify(state));
224
+ const data = solve();
225
+ const props = calculateProperties();
226
+ console.log("Properties:", props);
227
+
228
+ updateEquationDisplay(props);
229
+
230
+ // 2. Update Stats
231
+ elements.stats.omegaN.textContent = props.omegaN.toFixed(2) + ' rad/s';
232
+ elements.stats.zeta.textContent = props.zeta.toFixed(3);
233
+ elements.stats.regime.textContent = props.regime;
234
+
235
+ // Peak values
236
+ const maxDisp = Math.max(...data.u.map(Math.abs));
237
+ const maxVel = Math.max(...data.v.map(Math.abs));
238
+ elements.stats.peakDisp.textContent = maxDisp.toFixed(3);
239
+ elements.stats.peakVel.textContent = maxVel.toFixed(3);
240
+
241
+ // 3. Draw Charts
242
+ // Resize canvas to match display size
243
+ [elements.canvas.disp, elements.canvas.vel].forEach(canvas => {
244
+ const rect = canvas.getBoundingClientRect();
245
+ if (rect.width > 0 && rect.height > 0) {
246
+ canvas.width = rect.width;
247
+ canvas.height = rect.height;
248
+ }
249
+ });
250
+
251
+ drawChart(elements.canvas.disp, data.t, data.u, '#38bdf8', 'Displacement');
252
+ drawChart(elements.canvas.vel, data.t, data.v, '#4ade80', 'Velocity');
253
+
254
+ isUpdating = false;
255
+ });
256
+ }
257
+
258
+ // --- Event Listeners ---
259
+
260
+ function handleSliderChange(key) {
261
+ return (e) => {
262
+ const val = parseFloat(e.target.value);
263
+ state[key] = val;
264
+ elements.values[key].textContent = val;
265
+ updateUI();
266
+ };
267
+ }
268
+
269
+ function init() {
270
+ console.log("Initializing SDOF Simulator v2");
271
+ // Attach listeners
272
+ elements.sliders.m.addEventListener('input', handleSliderChange('m'));
273
+ elements.sliders.k.addEventListener('input', handleSliderChange('k'));
274
+ elements.sliders.c.addEventListener('input', handleSliderChange('c'));
275
+ elements.sliders.F0.addEventListener('input', handleSliderChange('F0'));
276
+ elements.sliders.p.addEventListener('input', handleSliderChange('p'));
277
+ elements.sliders.u0.addEventListener('input', handleSliderChange('u0'));
278
+ elements.sliders.v0.addEventListener('input', handleSliderChange('v0'));
279
+
280
+ // Initial render
281
+ updateUI();
282
+
283
+ // Handle resize
284
+ window.addEventListener('resize', updateUI);
285
+ }
286
+
287
+ // Start
288
+ init();
server.js ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const http = require('http');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const PORT = 8080;
6
+
7
+ const mimeTypes = {
8
+ '.html': 'text/html',
9
+ '.js': 'text/javascript',
10
+ '.css': 'text/css',
11
+ '.json': 'application/json',
12
+ '.png': 'image/png',
13
+ '.jpg': 'image/jpg',
14
+ '.gif': 'image/gif',
15
+ '.svg': 'image/svg+xml',
16
+ };
17
+
18
+ const server = http.createServer((req, res) => {
19
+ console.log(`request ${req.url}`);
20
+
21
+ let filePath = '.' + req.url;
22
+ if (filePath === './') {
23
+ filePath = './index.html';
24
+ }
25
+
26
+ const extname = String(path.extname(filePath)).toLowerCase();
27
+ const contentType = mimeTypes[extname] || 'application/octet-stream';
28
+
29
+ fs.readFile(filePath, (error, content) => {
30
+ if (error) {
31
+ if (error.code == 'ENOENT') {
32
+ res.writeHead(404);
33
+ res.end('404 Not Found');
34
+ } else {
35
+ res.writeHead(500);
36
+ res.end('Sorry, check with the site admin for error: ' + error.code + ' ..\n');
37
+ }
38
+ } else {
39
+ res.writeHead(200, { 'Content-Type': contentType });
40
+ res.end(content, 'utf-8');
41
+ }
42
+ });
43
+ });
44
+
45
+ server.listen(PORT, () => {
46
+ console.log(`Server running at http://localhost:${PORT}/`);
47
+ });
style.css CHANGED
@@ -1,28 +1,352 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
- }
5
-
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
- }
10
-
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
- }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --bg-primary: #0f172a;
3
+ --bg-secondary: #1e293b;
4
+ --bg-tertiary: #334155;
5
+ --text-primary: #f8fafc;
6
+ --text-secondary: #94a3b8;
7
+ --accent-primary: #38bdf8;
8
+ --accent-secondary: #818cf8;
9
+ --success: #4ade80;
10
+ --warning: #fbbf24;
11
+ --danger: #f87171;
12
+ --border: #334155;
13
+ --radius: 12px;
14
+ --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
15
+ }
16
+
17
+ * {
18
+ box-sizing: border-box;
19
+ margin: 0;
20
+ padding: 0;
21
+ }
22
+
23
+ body {
24
+ font-family: 'Inter', sans-serif;
25
+ background-color: var(--bg-primary);
26
+ color: var(--text-primary);
27
+ line-height: 1.5;
28
+ min-height: 100vh;
29
+ display: flex;
30
+ flex-direction: column;
31
+ }
32
+
33
+ .container {
34
+ max-width: 1000px;
35
+ /* Reduced max-width for vertical layout readability */
36
+ margin: 0 auto;
37
+ padding: 2rem;
38
+ width: 100%;
39
+ display: flex;
40
+ flex-direction: column;
41
+ gap: 2rem;
42
+ }
43
+
44
+ header {
45
+ text-align: center;
46
+ /* margin-bottom handled by container gap */
47
+ }
48
+
49
+ header h1 {
50
+ font-size: 2.5rem;
51
+ font-weight: 700;
52
+ background: linear-gradient(to right, var(--accent-primary), var(--accent-secondary));
53
+ -webkit-background-clip: text;
54
+ background-clip: text;
55
+ -webkit-text-fill-color: transparent;
56
+ margin-bottom: 0.5rem;
57
+ }
58
+
59
+ header p {
60
+ color: var(--text-secondary);
61
+ font-size: 1.1rem;
62
+ }
63
+
64
+ /* Dashboard Grid Layout (Controls + Charts) */
65
+ .dashboard {
66
+ display: grid;
67
+ gap: 1.5rem;
68
+ grid-template-columns: 1fr;
69
+ }
70
+
71
+ @media (min-width: 1024px) {
72
+ .dashboard {
73
+ grid-template-columns: 350px 1fr;
74
+ /* Fixed width controls, flexible charts */
75
+ grid-template-areas: "controls charts";
76
+ align-items: start;
77
+ }
78
+
79
+ .controls-panel {
80
+ grid-area: controls;
81
+ }
82
+
83
+ .charts-panel {
84
+ grid-area: charts;
85
+ }
86
+ }
87
+
88
+ /* Panels Common Styles */
89
+ .visual-panel,
90
+ .controls-panel,
91
+ .charts-panel,
92
+ .theory-panel {
93
+ background-color: var(--bg-secondary);
94
+ border-radius: var(--radius);
95
+ padding: 1.5rem;
96
+ box-shadow: var(--shadow);
97
+ border: 1px solid var(--border);
98
+ }
99
+
100
+ /* Visual Panel */
101
+ .diagram-container {
102
+ background-color: #ffffff;
103
+ border-radius: 8px;
104
+ padding: 1rem;
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ overflow: hidden;
109
+ height: 250px;
110
+ /* Fixed height */
111
+ }
112
+
113
+ .diagram-container svg {
114
+ max-height: 100%;
115
+ max-width: 100%;
116
+ }
117
+
118
+ /* Theory Panel */
119
+ .theory-panel {
120
+ display: flex;
121
+ flex-direction: column;
122
+ gap: 1rem;
123
+ }
124
+
125
+ .theory-panel h2 {
126
+ font-size: 1.25rem;
127
+ color: var(--text-primary);
128
+ margin-bottom: 0.5rem;
129
+ border-bottom: 1px solid var(--border);
130
+ padding-bottom: 0.5rem;
131
+ }
132
+
133
+ .theory-content {
134
+ display: grid;
135
+ grid-template-columns: 1fr;
136
+ gap: 1rem;
137
+ }
138
+
139
+ .theory-block h3 {
140
+ font-size: 1rem;
141
+ color: var(--accent-secondary);
142
+ margin-bottom: 0.25rem;
143
+ }
144
+
145
+ .theory-block ul {
146
+ list-style: none;
147
+ padding: 0;
148
+ }
149
+
150
+ .theory-block li {
151
+ font-size: 0.9rem;
152
+ color: var(--text-secondary);
153
+ margin-bottom: 0.25rem;
154
+ }
155
+
156
+ .theory-block li strong {
157
+ color: var(--text-primary);
158
+ }
159
+
160
+ .theory-block p {
161
+ font-size: 0.9rem;
162
+ color: var(--text-secondary);
163
+ margin-bottom: 0.25rem;
164
+ }
165
+
166
+ .math {
167
+ font-family: 'Times New Roman', serif;
168
+ font-style: italic;
169
+ color: var(--accent-primary);
170
+ font-size: 1.1rem;
171
+ }
172
+
173
+ .theory-footer {
174
+ margin-top: auto;
175
+ padding: 0.75rem;
176
+ background-color: var(--bg-tertiary);
177
+ border-radius: 6px;
178
+ font-size: 0.9rem;
179
+ color: var(--text-secondary);
180
+ border-left: 4px solid var(--success);
181
+ }
182
+
183
+ .theory-footer strong {
184
+ color: var(--success);
185
+ }
186
+
187
+ /* Controls Panel */
188
+ .controls-panel {
189
+ display: flex;
190
+ flex-direction: column;
191
+ gap: 1.25rem;
192
+ }
193
+
194
+ .control-group {
195
+ display: flex;
196
+ flex-direction: column;
197
+ gap: 0.5rem;
198
+ }
199
+
200
+ .control-group label {
201
+ font-weight: 500;
202
+ color: var(--text-primary);
203
+ display: flex;
204
+ justify-content: space-between;
205
+ font-size: 0.95rem;
206
+ }
207
+
208
+ .slider-wrapper {
209
+ display: flex;
210
+ align-items: center;
211
+ gap: 1rem;
212
+ }
213
+
214
+ input[type="range"] {
215
+ flex: 1;
216
+ height: 6px;
217
+ background: var(--bg-tertiary);
218
+ border-radius: 3px;
219
+ appearance: none;
220
+ cursor: pointer;
221
+ }
222
+
223
+ input[type="range"]::-webkit-slider-thumb {
224
+ appearance: none;
225
+ width: 18px;
226
+ height: 18px;
227
+ background: var(--accent-primary);
228
+ border-radius: 50%;
229
+ cursor: pointer;
230
+ transition: transform 0.1s, background-color 0.2s;
231
+ }
232
+
233
+ input[type="range"]::-webkit-slider-thumb:hover {
234
+ transform: scale(1.1);
235
+ background-color: var(--accent-secondary);
236
+ }
237
+
238
+ .slider-value {
239
+ font-family: 'Fira Code', monospace;
240
+ min-width: 3.5rem;
241
+ text-align: right;
242
+ color: var(--accent-primary);
243
+ font-weight: 600;
244
+ font-size: 0.9rem;
245
+ }
246
+
247
+ /* Stats Panel (Inside Controls) */
248
+ .stats-panel {
249
+ display: grid;
250
+ grid-template-columns: 1fr 1fr;
251
+ gap: 0.75rem;
252
+ }
253
+
254
+ .stat-card {
255
+ background-color: var(--bg-tertiary);
256
+ padding: 0.75rem;
257
+ border-radius: 8px;
258
+ display: flex;
259
+ flex-direction: column;
260
+ gap: 0.25rem;
261
+ }
262
+
263
+ .stat-label {
264
+ font-size: 0.8rem;
265
+ color: var(--text-secondary);
266
+ }
267
+
268
+ .stat-value {
269
+ font-size: 1.1rem;
270
+ font-weight: 600;
271
+ color: var(--text-primary);
272
+ }
273
+
274
+ .stat-value.highlight {
275
+ color: var(--accent-primary);
276
+ }
277
+
278
+ /* Charts Panel */
279
+ .charts-panel {
280
+ display: flex;
281
+ flex-direction: column;
282
+ gap: 1.5rem;
283
+ }
284
+
285
+ .equation-container {
286
+ padding: 1rem;
287
+ background: var(--bg-tertiary);
288
+ border-radius: 8px;
289
+ border-left: 4px solid var(--accent-primary);
290
+ }
291
+
292
+ .equation-container h3 {
293
+ margin-bottom: 0.5rem;
294
+ color: var(--text-secondary);
295
+ font-size: 0.9rem;
296
+ text-transform: uppercase;
297
+ letter-spacing: 0.05em;
298
+ }
299
+
300
+ #math-equation {
301
+ font-family: 'Times New Roman', serif;
302
+ font-style: italic;
303
+ font-size: 1.3rem;
304
+ margin-bottom: 0.5rem;
305
+ color: var(--text-primary);
306
+ }
307
+
308
+ #math-solution {
309
+ font-family: 'Times New Roman', serif;
310
+ font-size: 1.1rem;
311
+ color: var(--accent-secondary);
312
+ }
313
+
314
+ .chart-container {
315
+ position: relative;
316
+ width: 100%;
317
+ height: 300px;
318
+ /* Taller charts */
319
+ background: var(--bg-primary);
320
+ border-radius: 8px;
321
+ border: 1px solid var(--border);
322
+ padding: 10px;
323
+ }
324
+
325
+ .chart-container h3 {
326
+ position: absolute;
327
+ top: 10px;
328
+ left: 10px;
329
+ margin: 0;
330
+ font-size: 0.9rem;
331
+ color: var(--text-secondary);
332
+ background: var(--bg-primary);
333
+ padding: 2px 6px;
334
+ border-radius: 4px;
335
+ z-index: 10;
336
+ }
337
+
338
+ canvas {
339
+ width: 100%;
340
+ height: 100%;
341
+ }
342
+
343
+ /* Responsive adjustments */
344
+ @media (max-width: 768px) {
345
+ .container {
346
+ padding: 1rem;
347
+ }
348
+
349
+ header h1 {
350
+ font-size: 1.75rem;
351
+ }
352
+ }
test_solver.js ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ const state = {
3
+ m: 1.0,
4
+ k: 100,
5
+ c: 5.0,
6
+ F0: 10,
7
+ p: 5.0,
8
+ u0: 0.0,
9
+ v0: 0.0,
10
+ tMax: 10.0,
11
+ dt: 0.001
12
+ };
13
+
14
+ function derivatives(t, u, v) {
15
+ const u_prime = v;
16
+ const v_prime = (state.F0 * Math.sin(state.p * t) - state.c * v - state.k * u) / state.m;
17
+ return { u_prime, v_prime };
18
+ }
19
+
20
+ function solve() {
21
+ let t = 0;
22
+ let u = state.u0;
23
+ let v = state.v0;
24
+ let maxU = 0;
25
+
26
+ const steps = Math.ceil(state.tMax / state.dt);
27
+ for (let i = 0; i <= steps; i++) {
28
+ const k1 = derivatives(t, u, v);
29
+ const t2 = t + state.dt / 2;
30
+ const u2 = u + k1.u_prime * state.dt / 2;
31
+ const v2 = v + k1.v_prime * state.dt / 2;
32
+ const k2 = derivatives(t2, u2, v2);
33
+ const t3 = t + state.dt / 2;
34
+ const u3 = u + k2.u_prime * state.dt / 2;
35
+ const v3 = v + k2.v_prime * state.dt / 2;
36
+ const k3 = derivatives(t3, u3, v3);
37
+ const t4 = t + state.dt;
38
+ const u4 = u + k3.u_prime * state.dt;
39
+ const v4 = v + k3.v_prime * state.dt;
40
+ const k4 = derivatives(t4, u4, v4);
41
+
42
+ u += (state.dt / 6) * (k1.u_prime + 2 * k2.u_prime + 2 * k3.u_prime + k4.u_prime);
43
+ v += (state.dt / 6) * (k1.v_prime + 2 * k2.v_prime + 2 * k3.v_prime + k4.v_prime);
44
+ t += state.dt;
45
+
46
+ if (Math.abs(u) > maxU) maxU = Math.abs(u);
47
+ }
48
+ return { finalU: u, maxU: maxU };
49
+ }
50
+
51
+ function runTest(name, params) {
52
+ Object.assign(state, params);
53
+ const res = solve();
54
+ console.log(`Test: ${name}`);
55
+ console.log(` Params: ${JSON.stringify(params)}`);
56
+ console.log(` Result: Max |u| = ${res.maxU.toFixed(5)}, Final u = ${res.finalU.toFixed(5)}`);
57
+ return res;
58
+ }
59
+
60
+ console.log("--- Verification Suite ---");
61
+
62
+ // 1. Zero Values
63
+ runTest("Zero Values", { m: 1, k: 100, c: 5, F0: 0, p: 0, u0: 0, v0: 0 });
64
+
65
+ // 2. Resonance (omega_n = 10)
66
+ // Low damping to see large amplitude
67
+ runTest("Resonance", { m: 1, k: 100, c: 1, F0: 10, p: 10, u0: 0, v0: 0 });
68
+
69
+ // 3. Underdamped Free Vibration (zeta = 0.25)
70
+ runTest("Underdamped Free", { m: 1, k: 100, c: 5, F0: 0, p: 0, u0: 1, v0: 0 });
71
+
72
+ // 4. Critically Damped Free Vibration (zeta = 1.0)
73
+ // c = 2 * sqrt(1 * 100) = 20
74
+ runTest("Critically Damped Free", { m: 1, k: 100, c: 20, F0: 0, p: 0, u0: 1, v0: 0 });
75
+
76
+ // 5. Overdamped Free Vibration (zeta = 2.0)
77
+ // c = 40
78
+ runTest("Overdamped Free", { m: 1, k: 100, c: 40, F0: 0, p: 0, u0: 1, v0: 0 });