{"id":100645,"date":"2026-02-26T11:00:05","date_gmt":"2026-02-26T11:00:05","guid":{"rendered":"https:\/\/vibromera.eu\/?p=100645"},"modified":"2026-03-18T11:40:45","modified_gmt":"2026-03-18T11:40:45","slug":"resonance-of-machine-elements-and-assemblies","status":"publish","type":"post","link":"https:\/\/vibromera.eu\/nb\/uncategorized\/resonance-of-machine-elements-and-assemblies\/","title":{"rendered":"Resonans av maskinelementer og sammenstillinger"},"content":{"rendered":"<div id=\"pl-100645\"  class=\"panel-layout\" ><div id=\"pg-100645-0\"  class=\"panel-grid panel-no-style\" ><div id=\"pgc-100645-0-0\"  class=\"panel-grid-cell\" ><div id=\"panel-100645-0-0-0\" class=\"widget_text so-panel widget widget_custom_html panel-first-child panel-last-child\" data-index=\"0\" ><div class=\"textwidget custom-html-widget\"><!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Resonance in Rotor Dynamics \u2014 Interactive Guide<\/title>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=JetBrains+Mono:wght@300;400;500;600&family=DM+Sans:wght@300;400;500;600;700&family=DM+Serif+Display&display=swap\" rel=\"stylesheet\">\n<style>\n\/* ============================================\n   RESONANCE APP \u2014 FULLY SCOPED STYLES\n   All selectors under .resonance-app\n   All sizes in px\n   ============================================ *\/\n\n.resonance-app {\n  --bg: #f8fafc;\n  --bg2: #f1f5f9;\n  --bg3: #e2e8f0;\n  --surface: #e8ecf1;\n  --surface2: #dde3eb;\n  --border: #cbd5e1;\n  --text: #1e293b;\n  --text2: #334155;\n  --text3: #64748b;\n  --accent: #d97706;\n  --accent2: #b45309;\n  --red: #dc2626;\n  --red2: #ef4444;\n  --blue: #2563eb;\n  --blue2: #3b82f6;\n  --green: #059669;\n  --cyan: #0891b2;\n  --purple: #7c3aed;\n\n  background: var(--bg);\n  color: var(--text);\n  font-family: 'DM Sans', sans-serif;\n  font-size: 16px;\n  line-height: 1.7;\n  overflow-x: hidden;\n}\n\n.resonance-app *,\n.resonance-app *::before,\n.resonance-app *::after {\n  margin: 0;\n  padding: 0;\n  box-sizing: border-box;\n}\n\n.resonance-app .noise-overlay {\n  position: fixed; top: 0; left: 0; width: 100%; height: 100%;\n  background: url(\"data:image\/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http:\/\/www.w3.org\/2000\/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'\/%3E%3C\/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.015'\/%3E%3C\/svg%3E\");\n  pointer-events: none; z-index: 9999;\n}\n\n.resonance-app .container {\n  max-width: 1200px;\n  margin: 0 auto;\n  padding: 0 32px;\n}\n\n.resonance-app header {\n  padding: 64px 0 48px;\n  border-bottom: 1px solid var(--border);\n  position: relative;\n}\n.resonance-app header::before {\n  content: '';\n  position: absolute;\n  top: 0; left: 0; right: 0;\n  height: 300px;\n  background: radial-gradient(ellipse at 30% 0%, rgba(217,119,6,0.06) 0%, transparent 60%),\n              radial-gradient(ellipse at 70% 0%, rgba(37,99,235,0.04) 0%, transparent 60%);\n  pointer-events: none;\n}\n\n.resonance-app h1 {\n  font-family: 'DM Serif Display', serif;\n  font-size: 48px;\n  font-weight: 400;\n  letter-spacing: -0.02em;\n  line-height: 1.15;\n  margin-bottom: 16px;\n  background: linear-gradient(135deg, #1e293b, #b45309);\n  -webkit-background-clip: text;\n  -webkit-text-fill-color: transparent;\n}\n\n.resonance-app .subtitle {\n  font-size: 18px;\n  color: var(--text2);\n  max-width: 960px;\n  font-weight: 300;\n}\n\n.resonance-app section {\n  padding: 64px 0;\n  border-bottom: 1px solid var(--border);\n  animation: resAppFadeUp 0.6s ease-out;\n}\n\n.resonance-app h2 {\n  font-family: 'DM Serif Display', serif;\n  font-size: 32px;\n  font-weight: 400;\n  margin-bottom: 16px;\n  color: var(--accent2);\n}\n\n.resonance-app h3 {\n  font-family: 'DM Sans', sans-serif;\n  font-size: 17px;\n  font-weight: 600;\n  text-transform: uppercase;\n  letter-spacing: 0.08em;\n  color: var(--text3);\n  margin-bottom: 8px;\n}\n\n.resonance-app p {\n  color: var(--text2);\n  max-width: 960px;\n  margin-bottom: 24px;\n  font-size: 16px;\n}\n\n.resonance-app .interactive-panel {\n  background: var(--bg2);\n  border: 1px solid var(--border);\n  border-radius: 16px;\n  overflow: hidden;\n  margin: 32px 0;\n}\n\n.resonance-app .panel-header {\n  padding: 20px 28px;\n  background: var(--surface);\n  border-bottom: 1px solid var(--border);\n  display: flex;\n  align-items: center;\n  gap: 12px;\n}\n\n.resonance-app .panel-dot {\n  width: 10px; height: 10px;\n  border-radius: 50%;\n}\n.resonance-app .panel-dot.r { background: var(--red); }\n.resonance-app .panel-dot.y { background: var(--accent); }\n.resonance-app .panel-dot.g { background: var(--green); }\n\n.resonance-app .panel-title {\n  font-family: 'JetBrains Mono', monospace;\n  font-size: 13px;\n  color: var(--text3);\n  margin-left: 8px;\n}\n\n.resonance-app .panel-body {\n  display: grid;\n  grid-template-columns: 320px 1fr;\n  min-height: 520px;\n}\n\n.resonance-app .controls {\n  padding: 24px;\n  background: var(--bg2);\n  border-right: 1px solid var(--border);\n  overflow-y: auto;\n  max-height: 620px;\n}\n\n.resonance-app .control-group {\n  margin-bottom: 20px;\n  padding-bottom: 20px;\n  border-bottom: 1px solid rgba(148,163,184,0.35);\n}\n.resonance-app .control-group:last-child { border-bottom: none; }\n\n.resonance-app .control-label {\n  font-family: 'JetBrains Mono', monospace;\n  font-size: 12px;\n  color: var(--text3);\n  text-transform: uppercase;\n  letter-spacing: 0.06em;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  margin-bottom: 8px;\n}\n\n.resonance-app .control-value {\n  color: var(--accent);\n  font-weight: 500;\n  font-size: 13px;\n}\n\n.resonance-app input[type=\"range\"] {\n  -webkit-appearance: none;\n  width: 100%;\n  height: 6px;\n  background: var(--surface2);\n  border-radius: 3px;\n  outline: none;\n  cursor: pointer;\n}\n.resonance-app input[type=\"range\"]::-webkit-slider-thumb {\n  -webkit-appearance: none;\n  width: 18px; height: 18px;\n  border-radius: 50%;\n  background: var(--accent);\n  border: 3px solid #fff;\n  box-shadow: 0 1px 4px rgba(0,0,0,0.2), 0 0 6px rgba(217,119,6,0.3);\n  cursor: grab;\n  transition: transform 0.15s;\n}\n.resonance-app input[type=\"range\"]::-webkit-slider-thumb:hover {\n  transform: scale(1.2);\n}\n.resonance-app input[type=\"range\"]::-webkit-slider-thumb:active {\n  cursor: grabbing;\n}\n\n.resonance-app .toggle-row {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  margin-bottom: 8px;\n}\n.resonance-app .toggle {\n  position: relative;\n  width: 40px; height: 22px;\n  cursor: pointer;\n}\n.resonance-app .toggle input { opacity: 0; width: 0; height: 0; }\n.resonance-app .toggle-track {\n  position: absolute;\n  top: 0; left: 0; right: 0; bottom: 0;\n  background: var(--surface2);\n  border-radius: 11px;\n  transition: background 0.3s;\n}\n.resonance-app .toggle input:checked + .toggle-track {\n  background: var(--accent);\n}\n.resonance-app .toggle-track::after {\n  content: '';\n  position: absolute;\n  width: 16px; height: 16px;\n  left: 3px; top: 3px;\n  background: white;\n  border-radius: 50%;\n  transition: transform 0.3s;\n}\n.resonance-app .toggle input:checked + .toggle-track::after {\n  transform: translateX(18px);\n}\n.resonance-app .toggle-label {\n  font-family: 'JetBrains Mono', monospace;\n  font-size: 11px;\n  color: var(--text2);\n  text-transform: uppercase;\n  letter-spacing: 0.04em;\n}\n\n.resonance-app .custom-select {\n  width: 100%;\n  padding: 8px 12px;\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  color: var(--text);\n  font-family: 'JetBrains Mono', monospace;\n  font-size: 13px;\n  cursor: pointer;\n  outline: none;\n}\n.resonance-app .custom-select:focus { border-color: var(--accent); }\n\n.resonance-app .canvas-wrap {\n  position: relative;\n  padding: 0;\n  display: flex;\n  align-items: stretch;\n  justify-content: stretch;\n  background: linear-gradient(135deg, var(--bg) 0%, var(--bg2) 100%);\n  min-height: 420px;\n  overflow: hidden;\n}\n\n.resonance-app canvas {\n  width: 100%;\n  height: 100%;\n  display: block;\n  border-radius: 8px;\n}\n\n.resonance-app .readouts {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n  gap: 16px;\n  padding: 20px 28px;\n  background: var(--surface);\n  border-top: 1px solid var(--border);\n}\n\n.resonance-app .readout { text-align: center; }\n.resonance-app .readout-label {\n  font-family: 'JetBrains Mono', monospace;\n  font-size: 10px;\n  color: var(--text3);\n  text-transform: uppercase;\n  letter-spacing: 0.1em;\n}\n.resonance-app .readout-val {\n  font-family: 'JetBrains Mono', monospace;\n  font-size: 20px;\n  font-weight: 600;\n  color: var(--accent2);\n}\n\n.resonance-app .schematics-grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 32px;\n  margin: 32px 0;\n}\n\n.resonance-app .schematic-card {\n  background: var(--bg2);\n  border: 1px solid var(--border);\n  border-radius: 16px;\n  overflow: hidden;\n  transition: border-color 0.3s;\n}\n.resonance-app .schematic-card:hover { border-color: var(--accent); }\n\n.resonance-app .schematic-canvas-wrap {\n  padding: 0;\n  display: block;\n  background: linear-gradient(180deg, var(--bg) 0%, var(--bg2) 100%);\n  height: 300px;\n  overflow: hidden;\n}\n.resonance-app .schematic-canvas-wrap canvas {\n  display: block;\n  width: 100%;\n  height: 100%;\n}\n\n.resonance-app .schematic-info {\n  padding: 20px 24px;\n  border-top: 1px solid var(--border);\n}\n.resonance-app .schematic-info h4 {\n  font-family: 'DM Sans', sans-serif;\n  font-size: 16px;\n  font-weight: 600;\n  margin-bottom: 6px;\n  color: var(--text);\n}\n.resonance-app .schematic-info p {\n  font-size: 14px;\n  color: var(--text3);\n  margin-bottom: 0;\n  max-width: none;\n}\n\n.resonance-app .impact-body {\n  display: grid;\n  grid-template-columns: 200px 1fr;\n  min-height: 400px;\n}\n\n.resonance-app .afch-body {\n  display: grid;\n  grid-template-columns: 280px 1fr;\n  min-height: 500px;\n}\n\n.resonance-app .preset-group {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 8px;\n  margin-bottom: 16px;\n}\n.resonance-app .preset-btn {\n  padding: 6px 14px;\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  color: var(--text2);\n  font-family: 'JetBrains Mono', monospace;\n  font-size: 11px;\n  cursor: pointer;\n  transition: all 0.2s;\n  text-transform: uppercase;\n  letter-spacing: 0.04em;\n}\n.resonance-app .preset-btn:hover {\n  border-color: var(--accent);\n  color: var(--accent);\n  background: rgba(217,119,6,0.08);\n}\n.resonance-app .preset-btn.active {\n  border-color: var(--accent);\n  color: #fff;\n  background: var(--accent);\n}\n\n.resonance-app .bell-section {\n  display: flex;\n  gap: 32px;\n  align-items: flex-start;\n  margin: 32px 0;\n}\n.resonance-app .bell-visual {\n  min-width: 300px;\n  background: var(--bg2);\n  border: 1px solid var(--border);\n  border-radius: 16px;\n  padding: 32px;\n  text-align: center;\n}\n\n.resonance-app .info-box {\n  background: rgba(217,119,6,0.05);\n  border-left: 3px solid var(--accent);\n  padding: 16px 24px;\n  border-radius: 0 8px 8px 0;\n  margin: 24px 0;\n}\n.resonance-app .info-box p {\n  color: var(--text2);\n  margin-bottom: 0;\n  font-size: 15px;\n  max-width: none;\n}\n\n.resonance-app footer {\n  padding: 48px 0;\n  text-align: center;\n  color: var(--text3);\n  font-size: 13px;\n}\n.resonance-app footer a { color: var(--accent); text-decoration: none; }\n.resonance-app footer p { max-width: none; text-align: center; }\n\n.resonance-app .equation {\n  font-family: 'JetBrains Mono', monospace;\n  font-size: 17px;\n  color: var(--cyan);\n  background: var(--surface);\n  padding: 16px 24px;\n  border-radius: 8px;\n  margin: 24px 0;\n  display: inline-block;\n  border: 1px solid var(--border);\n}\n\n.resonance-app .knob-grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 16px;\n}\n.resonance-app .knob-item { text-align: center; }\n.resonance-app .knob-display {\n  width: 64px; height: 64px;\n  margin: 0 auto 6px;\n  border-radius: 50%;\n  background: conic-gradient(var(--accent) 0deg, var(--accent) var(--angle, 180deg), var(--surface2) var(--angle, 180deg), var(--surface2) 360deg);\n  display: flex; align-items: center; justify-content: center; position: relative;\n}\n.resonance-app .knob-display::after {\n  content: '';\n  width: 50px; height: 50px;\n  background: var(--bg2);\n  border-radius: 50%;\n  position: absolute;\n}\n.resonance-app .knob-val {\n  font-family: 'JetBrains Mono', monospace;\n  font-size: 12px;\n  color: var(--accent);\n  position: relative;\n  z-index: 1;\n}\n\n@keyframes resAppFadeUp {\n  from { opacity: 0; transform: translateY(20px); }\n  to { opacity: 1; transform: translateY(0); }\n}\n\n\/* ===== TABLET \u2264 900px ===== *\/\n@media (max-width: 900px) {\n  .resonance-app .container { padding: 0 24px; }\n  .resonance-app h1 { font-size: 36px; }\n  .resonance-app h2 { font-size: 28px; }\n  .resonance-app section { padding: 48px 0; }\n  .resonance-app header { padding: 48px 0 36px; }\n  .resonance-app .panel-body { grid-template-columns: 1fr; }\n  .resonance-app .controls {\n    max-height: none;\n    border-right: none;\n    border-bottom: 1px solid var(--border);\n    padding: 20px;\n  }\n  .resonance-app .canvas-wrap { min-height: 380px; }\n  .resonance-app .schematics-grid { grid-template-columns: 1fr; gap: 24px; }\n  .resonance-app .impact-body { grid-template-columns: 1fr; }\n  .resonance-app .afch-body { grid-template-columns: 1fr; }\n  .resonance-app .bell-section { flex-direction: column; }\n  .resonance-app .bell-visual { min-width: auto; width: 100%; }\n  .resonance-app p { max-width: none; }\n  .resonance-app .subtitle { max-width: none; }\n}\n\n\/* ===== MOBILE \u2264 600px ===== *\/\n@media (max-width: 600px) {\n  .resonance-app .container { padding: 0 16px; }\n  .resonance-app h1 { font-size: 28px; }\n  .resonance-app h2 { font-size: 24px; }\n  .resonance-app h3 { font-size: 14px; }\n  .resonance-app p { font-size: 15px; }\n  .resonance-app section { padding: 36px 0; }\n  .resonance-app header { padding: 36px 0 28px; }\n  .resonance-app .panel-header { padding: 14px 16px; }\n  .resonance-app .panel-title { font-size: 11px; }\n  .resonance-app .controls { padding: 16px; }\n  .resonance-app .canvas-wrap { min-height: 300px; }\n  .resonance-app .readouts {\n    grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));\n    gap: 8px; padding: 14px 16px;\n  }\n  .resonance-app .readout-val { font-size: 16px; }\n  .resonance-app .readout-label { font-size: 9px; }\n  .resonance-app .schematic-canvas-wrap { height: 220px; }\n  .resonance-app .schematic-info { padding: 14px 16px; }\n  .resonance-app .schematic-info h4 { font-size: 14px; }\n  .resonance-app .schematic-info p { font-size: 13px; }\n  .resonance-app .equation {\n    font-size: 14px; padding: 12px 16px;\n    display: block; overflow-x: auto;\n  }\n  .resonance-app .info-box { padding: 12px 16px; }\n  .resonance-app .info-box p { font-size: 14px; }\n  .resonance-app footer { padding: 32px 0; }\n}\n\n\/* ===== SMALL MOBILE \u2264 400px ===== *\/\n@media (max-width: 400px) {\n  .resonance-app .container { padding: 0 12px; }\n  .resonance-app h1 { font-size: 24px; }\n  .resonance-app h2 { font-size: 21px; }\n  .resonance-app .canvas-wrap { min-height: 260px; }\n  .resonance-app .schematic-canvas-wrap { height: 180px; }\n  .resonance-app .schematics-grid { gap: 16px; }\n  .resonance-app .readouts { grid-template-columns: 1fr 1fr; }\n}\n<\/style>\n<\/head>\n<body>\n<div class=\"resonance-app\">\n<div class=\"noise-overlay\"><\/div>\n\n<div class=\"container\">\n\n<!-- HEADER -->\n<header>\n  <h3>Vibration Diagnostics<\/h3>\n  <h1>Resonance of Machine Elements and Assemblies<\/h1>\n  <p class=\"subtitle\">Considering the numerous requests to explain the diagnostics of resonance in machine elements, critical speeds, and natural mode shapes of the rotor, I decided to write several articles dedicated to these topics. In this first article I will discuss the resonance of elements and assemblies of the machine.<\/p>\n  <p style=\"color:var(--text3);font-size:15px;margin-top:8px;\">In this article we will examine: how to determine that it is indeed a resonance of machine elements, and how resonance affects the machine's vibration; how three parameters of the vibrating system influence the amplitude and frequency of resonance; and how to use a single-channel vibration analyzer for resonance analysis and diagnostics, as well as the limitations of its use.<\/p>\n<\/header>\n\n<!-- SECTION 1: WHAT IS RESONANCE -->\n<section id=\"sec-intro\">\n  <h2>1. What Is Resonance?<\/h2>\n  <p>Most structures and machines undergo natural oscillations, and therefore periodic external forces acting on them can cause resonance. Resonance is often referred to as oscillations at the natural frequency or at the critical frequency. <strong style=\"color:var(--accent)\">Resonance is the phenomenon of a sharp increase in the amplitude of forced oscillations<\/strong>, which occurs when the frequency of external excitation approaches the resonant frequencies determined by the properties of the system. The increase in oscillation amplitude is only a consequence of resonance \u2014 the cause is the coincidence of the external (excitation) frequency with the internal (natural) frequency of the vibrating system (rotor-bearing).<\/p>\n\n  <p>Resonance is the phenomenon whereby at a certain frequency of the excitation force, the vibrating system becomes particularly responsive to the action of that force. System parameters such as low stiffness and\/or weak damping, acting on the rotor machine at the resonant frequency, can lead to the occurrence of resonance. Resonance does not necessarily lead to machine breakdown or component failure, except when defects in the machine cause vibration, or when a nearby installed machine \"induces\" vibration at the same frequency as the natural frequencies.<\/p>\n\n  <div class=\"info-box\">\n    <p><strong style=\"color:var(--accent)\">Key principle:<\/strong> Resonance does not create vibration \u2014 it only amplifies it. Resonance is not a defect, but a property of the mechanical system. Therefore, resonance does not cause problems unless some oscillation excites it.<\/p>\n  <\/div>\n\n  <p>This is comparable to the oscillations of a bell or a drum. In the case of a bell (Fig. 1), all its energy is in the potential form when it is stationary and at the highest points of its trajectory, and as it passes through the lowest point at maximum velocity, the energy converts to kinetic. Potential energy is proportional to the mass of the bell and the height of the lift relative to the lowest point; kinetic energy is proportional to the mass and the square of the velocity at the measurement point. That is, if you strike the bell, it will resonate at a specific frequency (or frequencies). If it is at rest, it will not oscillate at the resonant frequency.<\/p>\n\n  <div class=\"equation\">\n    E<sub>potential<\/sub> = m\u00b7g\u00b7h &nbsp;&nbsp;&nbsp; E<sub>kinetic<\/sub> = \u00bd\u00b7m\u00b7v\u00b2\n  <\/div>\n\n  <p>Resonance is a property of the machine whether it is running or not. It should be noted that the dynamic stiffness of the shaft when the machine is rotating can differ significantly from the static stiffness when the machine is stopped, while the resonance changes only insignificantly.<\/p>\n\n  <p>There is an established rule, based on practical experience, which states that <strong style=\"color:var(--accent2)\">resonant frequencies measured during machine shutdown (coastdown) are approximately 20 percent lower than the forced vibration frequencies<\/strong>. Resonant frequencies of individual machine assemblies and parts \u2014 such as the shaft, rotor, casing, and foundation \u2014 are oscillations at their natural frequencies.<\/p>\n\n  <p>After machine installation, the resonant frequencies may change their values due to changes in system parameters (mass, stiffness, and damping), which after connecting all the mechanisms of the machine into a single unit may increase or decrease. Additionally, dynamic stiffness, as noted above, can shift the resonant frequencies when machines operate at nominal rotation speed. Most machines are designed so that the rotor does not have the same natural frequency as the shaft. A machine consisting of one or two mechanisms should not be operated at a resonant frequency. However, with wear and changes in clearances, the natural frequency very often shifts toward the operating rotation speed, causing resonance.<\/p>\n\n  <p>The sudden appearance of oscillations at a defect frequency \u2014 such as a loosened fit or other fault \u2014 can cause the machine to vibrate at its resonant frequency. In this case, machine vibration will increase from an acceptable level to an unacceptable one if the oscillations are caused by the resonance of machine assemblies or elements.<\/p>\n<\/section>\n\n<!-- SECTION 2: AMPLITUDE-PHASE FREQUENCY CHARACTERISTIC -->\n<section id=\"sec-afpc\">\n  <h2>2. Resonance During Startup and Shutdown (Fig. 2)<\/h2>\n\n  <div class=\"info-box\">\n    <p><strong style=\"color:var(--accent)\">Example:<\/strong> A two-speed machine operates at 900 RPM and 1200 RPM. The machine has a resonance at 1200 RPM that amplifies vibration at the rotation frequency of 1200 RPM. At 900 RPM, vibration is 2.54 mm\/s, while at 1200 RPM resonance increases the oscillations to 12.7 mm\/s.<\/p>\n  <\/div>\n\n  <p>Resonance can be observed during machine startup, when it passes through the resonant frequency (Fig. 2). As the rotation speed increases, the amplitude will grow to its maximum value at the resonant frequency (n<sub>res<\/sub>) and decrease after passing through it. When the rotor passes through resonance, the <strong style=\"color:var(--blue2)\">vibration phase changes by 180 degrees<\/strong>. At resonance, system oscillations are shifted in phase by 90 degrees relative to the oscillations of the excitation force.<\/p>\n\n  <p>The 180-degree phase shift is often observed only on rotors that have a single correction plane (Fig. 3, left). More complex \"shaft\/rotor-bearing\" systems (Fig. 3, right) have a phase shift that lies in the range of 160\u00b0 to 180\u00b0. Whenever a vibration analysis specialist observes a high oscillation amplitude, they should assume that its rise to an unacceptable level may be related to system resonance.<\/p>\n<\/section>\n\n<!-- SECTION 3: ROTOR CONFIGURATIONS -->\n<section id=\"sec-schematics\">\n  <h2>3. Rotor Configurations (Fig. 3)<\/h2>\n  <p>The vibration behavior of a rotor depends critically on its geometry and how it is supported. A simple rotor with a single correction plane (an overhung disk) shows a clean 180\u00b0 phase shift through resonance. A more complex system \u2014 such as two connected rotors through a cardan shaft \u2014 exhibits multiple coupled modes and the phase shift may deviate from the ideal 180\u00b0.<\/p>\n\n  <div class=\"schematics-grid\">\n    <div class=\"schematic-card\">\n      <div class=\"schematic-canvas-wrap\">\n        <canvas id=\"overhungCanvas\" width=\"500\" height=\"260\"><\/canvas>\n      <\/div>\n      <div class=\"schematic-info\">\n        <h4>Fig. 3 (left): Rotor with a Single Correction Plane (Disk)<\/h4>\n        <p>Simple rotor with a single disk mounted beyond the bearings. Shows a clean resonance with a 180\u00b0 phase shift when passing through the critical speed. Common in fans, flail mowers, mulcher rotors, and pumps with overhung impellers.<\/p>\n      <\/div>\n    <\/div>\n    <div class=\"schematic-card\">\n      <div class=\"schematic-canvas-wrap\">\n        <canvas id=\"cardanCanvas\" width=\"500\" height=\"300\"><\/canvas>\n      <\/div>\n      <div class=\"schematic-info\">\n        <h4>Fig. 3 (right): Complex System \u2014 Two Connected Rotors<\/h4>\n        <p>Two rotors connected through a flexible joint (cardan shaft). The coupled system has a phase shift in the range of 160\u00b0\u2013180\u00b0 when passing through resonance. Vibration at 1\u00d7 and 2\u00d7 shaft speed. Common in drivelines, rolling mills, and industrial power transmission.<\/p>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/section>\n\n<!-- SECTION 4: MASS, STIFFNESS, DAMPING -->\n<section id=\"sec-params\">\n  <h2>4. Mass, Stiffness, and Damping (Figs. 4\u20137)<\/h2>\n  <p>Mass, stiffness, and damping \u2014 these are the three parameters of the vibrating system that affect the frequency and increase the amplitude of oscillations at resonance.<\/p>\n\n  <p><strong style=\"color:var(--accent2)\">Mass<\/strong> characterizes the properties of the body and is a measure of its inertia (the greater the mass, the less acceleration it acquires under the action of a periodic force), which causes its oscillations.<\/p>\n\n  <p><strong style=\"color:var(--accent2)\">Stiffness<\/strong> is a property of the system that opposes the inertial forces arising as a result of mass forces.<\/p>\n\n  <p><strong style=\"color:var(--accent2)\">Damping<\/strong> is a property of the system that reduces the energy of oscillations by converting it to thermal energy due to friction in the mechanical system.<\/p>\n\n  <div class=\"equation\">\n    f<sub>n<\/sub> = (1\/2\u03c0) \u00b7 \u221a(k\/m) &nbsp;&nbsp;&nbsp; Q = 1\/(2\u03b6) &nbsp;&nbsp;&nbsp; A<sub>res<\/sub> = F<sub>0<\/sub>\/(2k\u03b6)\n  <\/div>\n  <p style=\"font-size:14px;color:var(--text3);\">where f<sub>n<\/sub> \u2014 natural frequency, k \u2014 stiffness, m \u2014 mass, \u03b6 \u2014 damping ratio, Q \u2014 quality factor (amplification at resonance), A<sub>res<\/sub> \u2014 resonance amplitude, F<sub>0<\/sub> \u2014 excitation force amplitude.<\/p>\n\n  <p>To reduce resonance, the system parameters are selected so that its resonant frequencies are positioned as far as possible from possible external excitation frequencies. In practice, so-called dynamic vibration absorbers, or dampers, are used for this purpose.<\/p>\n\n  <p>The interactive simulator below (replacing static Figs. 4\u20137 from the original article) shows the Amplitude-Frequency Characteristic (AFC) of a simple vibrating system consisting of mass, spring, and damper. Adjust the parameters to observe these effects in real time:<\/p>\n\n  <div class=\"info-box\">\n    <p>\u261e <strong>Increasing the mass<\/strong> of the structure decreases the resonant frequency.<br>\n    \u261e <strong>Increasing the stiffness<\/strong> of the structure increases the resonant frequency.<br>\n    \u261e <strong>Increasing the damping<\/strong> of the structure decreases the amplitude of resonance. <em>Damping is the only property that controls the vibration amplitude at resonance.<\/em><br>\n    \u261e Increasing damping also slightly lowers the resonant frequency. If you increase the mass \u2014 the resonant frequency decreases; if you decrease the mass \u2014 the resonant frequency increases. Similarly, if you increase the stiffness \u2014 the resonant frequency increases; when you decrease the stiffness \u2014 the resonant frequency decreases.<\/p>\n  <\/div>\n\n  <p>An analogy can be drawn with a guitar string. The tighter you pull the string on the guitar (more stiffness), the higher the tone (resonant frequency) rises \u2014 until the string breaks. If you use the thickest string (greater mass), the tone it produces will be lower.<\/p>\n\n  <div class=\"interactive-panel\">\n    <div class=\"panel-header\">\n      <span class=\"panel-dot r\"><\/span>\n      <span class=\"panel-dot y\"><\/span>\n      <span class=\"panel-dot g\"><\/span>\n      <span class=\"panel-title\">resonance_simulator.exe \u2014 amplitude & phase response<\/span>\n    <\/div>\n    <div class=\"panel-body\">\n      <div class=\"controls\" id=\"mainControls\">\n        \n        <div class=\"control-group\">\n          <h3 style=\"color:var(--accent);margin-bottom:12px;font-size:13px;\">\u2699 System Parameters<\/h3>\n          \n          <div class=\"control-label\">\n            <span>Mass (m)<\/span>\n            <span class=\"control-value\" id=\"val-mass\">10 kg<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-mass\" min=\"1\" max=\"100\" value=\"10\" step=\"0.5\">\n          \n          <div class=\"control-label\" style=\"margin-top:16px;\">\n            <span>Stiffness (k)<\/span>\n            <span class=\"control-value\" id=\"val-stiff\">40000 N\/m<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-stiff\" min=\"5000\" max=\"200000\" value=\"40000\" step=\"500\">\n          \n          <div class=\"control-label\" style=\"margin-top:16px;\">\n            <span>Damping ratio (\u03b6)<\/span>\n            <span class=\"control-value\" id=\"val-damp\">0.05<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-damp\" min=\"0.005\" max=\"0.7\" value=\"0.05\" step=\"0.005\">\n          \n          <div class=\"control-label\" style=\"margin-top:16px;\">\n            <span>Imbalance (e)<\/span>\n            <span class=\"control-value\" id=\"val-imbal\">50 g\u00b7mm<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-imbal\" min=\"5\" max=\"500\" value=\"50\" step=\"5\">\n        <\/div>\n\n        <div class=\"control-group\">\n          <h3 style=\"color:var(--blue2);margin-bottom:12px;font-size:13px;\">\ud83d\udcca Display Options<\/h3>\n          \n          <div class=\"toggle-row\">\n            <label class=\"toggle\">\n              <input type=\"checkbox\" id=\"chk-phase\" checked>\n              <div class=\"toggle-track\"><\/div>\n            <\/label>\n            <span class=\"toggle-label\">Show Phase<\/span>\n          <\/div>\n          \n          <div class=\"toggle-row\">\n            <label class=\"toggle\">\n              <input type=\"checkbox\" id=\"chk-damped-freq\">\n              <div class=\"toggle-track\"><\/div>\n            <\/label>\n            <span class=\"toggle-label\">Show Damped Freq<\/span>\n          <\/div>\n          \n          <div class=\"toggle-row\">\n            <label class=\"toggle\">\n              <input type=\"checkbox\" id=\"chk-bandwidth\">\n              <div class=\"toggle-track\"><\/div>\n            <\/label>\n            <span class=\"toggle-label\">Show Half-Power BW<\/span>\n          <\/div>\n\n          <div class=\"toggle-row\">\n            <label class=\"toggle\">\n              <input type=\"checkbox\" id=\"chk-log\">\n              <div class=\"toggle-track\"><\/div>\n            <\/label>\n            <span class=\"toggle-label\">Log Scale (Amplitude)<\/span>\n          <\/div>\n\n          <div class=\"toggle-row\">\n            <label class=\"toggle\">\n              <input type=\"checkbox\" id=\"chk-multi\">\n              <div class=\"toggle-track\"><\/div>\n            <\/label>\n            <span class=\"toggle-label\">Overlay Multiple \u03b6<\/span>\n          <\/div>\n        <\/div>\n\n        <div class=\"control-group\">\n          <h3 style=\"color:var(--green);margin-bottom:12px;font-size:13px;\">\ud83c\udfed Presets<\/h3>\n          <div class=\"preset-group\">\n            <button class=\"preset-btn\" onclick=\"applyPreset('fan',this)\">Fan<\/button>\n            <button class=\"preset-btn\" onclick=\"applyPreset('pump',this)\">Pump<\/button>\n            <button class=\"preset-btn\" onclick=\"applyPreset('turbine',this)\">Turbine<\/button>\n            <button class=\"preset-btn\" onclick=\"applyPreset('crusher',this)\">Crusher<\/button>\n            <button class=\"preset-btn\" onclick=\"applyPreset('spindle',this)\">Spindle<\/button>\n            <button class=\"preset-btn\" onclick=\"applyPreset('mulcher',this)\">Mulcher<\/button>\n          <\/div>\n        <\/div>\n\n        <div class=\"control-group\">\n          <h3 style=\"color:var(--purple);margin-bottom:12px;font-size:13px;\">\ud83d\udd27 Advanced<\/h3>\n          \n          <div class=\"control-label\">\n            <span>Bearing Stiffness Ratio<\/span>\n            <span class=\"control-value\" id=\"val-bearing\">1.0<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-bearing\" min=\"0.1\" max=\"3\" value=\"1.0\" step=\"0.05\">\n          \n          <div class=\"control-label\" style=\"margin-top:16px;\">\n            <span>Support Flexibility<\/span>\n            <span class=\"control-value\" id=\"val-support\">0%<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-support\" min=\"0\" max=\"80\" value=\"0\" step=\"1\">\n\n          <div class=\"control-label\" style=\"margin-top:16px;\">\n            <span>Freq Range (max RPM)<\/span>\n            <span class=\"control-value\" id=\"val-maxrpm\">6000<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-maxrpm\" min=\"1000\" max=\"30000\" value=\"6000\" step=\"500\">\n        <\/div>\n\n      <\/div>\n      <div class=\"canvas-wrap\">\n        <canvas id=\"resonanceCanvas\" width=\"800\" height=\"500\"><\/canvas>\n      <\/div>\n    <\/div>\n    <div class=\"readouts\" id=\"readouts\">\n      <div class=\"readout\">\n        <div class=\"readout-label\">Natural Freq<\/div>\n        <div class=\"readout-val\" id=\"ro-fn\">\u2014<\/div>\n      <\/div>\n      <div class=\"readout\">\n        <div class=\"readout-label\">Critical RPM<\/div>\n        <div class=\"readout-val\" id=\"ro-rpm\">\u2014<\/div>\n      <\/div>\n      <div class=\"readout\">\n        <div class=\"readout-label\">Peak Amplitude<\/div>\n        <div class=\"readout-val\" id=\"ro-peak\">\u2014<\/div>\n      <\/div>\n      <div class=\"readout\">\n        <div class=\"readout-label\">Q Factor<\/div>\n        <div class=\"readout-val\" id=\"ro-q\">\u2014<\/div>\n      <\/div>\n      <div class=\"readout\">\n        <div class=\"readout-label\">Amplification<\/div>\n        <div class=\"readout-val\" id=\"ro-amp\">\u2014<\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/section>\n\n<!-- SECTION 5: MEASURING RESONANCE -->\n<section id=\"sec-impact\">\n  <h2>5. Measuring Resonance (Fig. 8)<\/h2>\n  <p>One of the most common methods for measuring the resonant frequency of a structure is impact excitation using an instrumented hammer.<\/p>\n\n  <p>The impact on the structure, in the form of an input strike, excites small disturbing forces over a certain frequency range. The oscillations created by the impact represent a transient, short-duration energy transfer process. The spectrum of the impact force is continuous, with maximum amplitude at 0 Hz and subsequent decrease with increasing frequency.<\/p>\n\n  <p>The impact duration and the spectrum shape during impact excitation are determined by the mass and stiffness of both the impact hammer and the machine structure. When using a relatively small hammer on a hard structure, the stiffness of the hammer tip determines the spectrum. <strong style=\"color:var(--cyan)\">The hammer tip acts as a mechanical filter.<\/strong> By selecting the stiffness of the hammer tip, one can choose the frequency range of investigation.<\/p>\n\n  <div class=\"interactive-panel\">\n    <div class=\"panel-header\">\n      <span class=\"panel-dot r\"><\/span>\n      <span class=\"panel-dot y\"><\/span>\n      <span class=\"panel-dot g\"><\/span>\n      <span class=\"panel-title\">impact_test.exe \u2014 pulse shape & spectrum<\/span>\n    <\/div>\n    <div class=\"impact-body\">\n      <div class=\"controls\">\n        <div class=\"control-group\">\n          <h3 style=\"color:var(--cyan);margin-bottom:12px;font-size:13px;\">\ud83d\udd28 Hammer Tip<\/h3>\n          <select class=\"custom-select\" id=\"hammer-tip\">\n            <option value=\"steel\">Steel (hard)<\/option>\n            <option value=\"plastic\" selected>Plastic (medium)<\/option>\n            <option value=\"rubber\">Rubber (soft)<\/option>\n          <\/select>\n          \n          <div class=\"control-label\" style=\"margin-top:16px;\">\n            <span>Impact Force<\/span>\n            <span class=\"control-value\" id=\"val-force\">1000 N<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-force\" min=\"100\" max=\"3000\" value=\"1000\" step=\"50\">\n          \n          <div class=\"control-label\" style=\"margin-top:16px;\">\n            <span>Tip Stiffness<\/span>\n            <span class=\"control-value\" id=\"val-tipstiff\">Medium<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-tipstiff\" min=\"1\" max=\"10\" value=\"5\" step=\"0.5\">\n        <\/div>\n      <\/div>\n      <div class=\"canvas-wrap\">\n        <canvas id=\"impactCanvas\" width=\"700\" height=\"380\"><\/canvas>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <p>When using this measurement technique, it is very important to strike different points of the structure, since not all resonant frequencies can always be measured by striking and measuring at one and the same point. When determining machine resonance, both points \u2014 the impact point and the measurement point \u2014 must be verified (tested).<\/p>\n\n  <p>If the hammer has a soft tip, the main quantity of output energy will excite oscillations at low frequencies. A hammer with a hard tip delivers little energy at any specific frequency, except that its output energy will excite oscillations at high frequencies. The response to the hammer strike can be measured with a single-channel analyzer, provided the machine is stopped and disconnected.<\/p>\n\n  <div class=\"info-box\">\n    <p><strong style=\"color:var(--accent)\">Important limitation:<\/strong> Phase is one of the parameters confirming resonance. The vibration phase during an impact test cannot be measured with a single-channel analyzer, and therefore one cannot say with certainty whether resonance is present on the rotor or not. To determine the phase, an additional speed sensor (inductive or photo-tachometer) is required.<\/p>\n  <\/div>\n<\/section>\n\n<!-- SECTION 6: AFCH RUNDOWN -->\n<section id=\"sec-afch\">\n  <h2>6. Amplitude\u2013Phase Frequency Characteristic \u2014 APFC (Fig. 9)<\/h2>\n  <p>Machine resonance can be determined using a single-channel analyzer as an increase in oscillation amplitude at the resonant frequency and by the 180-degree phase change when passing through resonance \u2014 if amplitude and phase of oscillations are measured at the rotation frequency during machine startup (run-up) or shutdown (coastdown). The characteristic constructed on the basis of these measurements is called the <strong style=\"color:var(--accent)\">Amplitude-Phase Frequency Characteristic (APFC)<\/strong>.<\/p>\n\n  <p>Analysis of the APFC (Fig. 9) allows the vibration analysis specialist to identify the resonant frequencies of the rotor.<\/p>\n\n  <div class=\"interactive-panel\">\n    <div class=\"panel-header\">\n      <span class=\"panel-dot r\"><\/span>\n      <span class=\"panel-dot y\"><\/span>\n      <span class=\"panel-dot g\"><\/span>\n      <span class=\"panel-title\">afch_simulator.exe \u2014 generator rotor rundown<\/span>\n    <\/div>\n    <div class=\"afch-body\">\n      <div class=\"controls\">\n        <div class=\"control-group\">\n          <h3 style=\"color:var(--red2);margin-bottom:12px;font-size:13px;\">\u26a1 Rotor Parameters<\/h3>\n          \n          <div class=\"control-label\">\n            <span>1st Critical (RPM)<\/span>\n            <span class=\"control-value\" id=\"val-crit1\">1200<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-crit1\" min=\"400\" max=\"2000\" value=\"1200\" step=\"25\">\n          \n          <div class=\"control-label\" style=\"margin-top:16px;\">\n            <span>2nd Critical (RPM)<\/span>\n            <span class=\"control-value\" id=\"val-crit2\">2800<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-crit2\" min=\"1500\" max=\"4000\" value=\"2800\" step=\"25\">\n          \n          <div class=\"control-label\" style=\"margin-top:16px;\">\n            <span>Damping @ Brg 3<\/span>\n            <span class=\"control-value\" id=\"val-damp3\">0.04<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-damp3\" min=\"0.01\" max=\"0.2\" value=\"0.04\" step=\"0.005\">\n          \n          <div class=\"control-label\" style=\"margin-top:16px;\">\n            <span>Damping @ Brg 4<\/span>\n            <span class=\"control-value\" id=\"val-damp4\">0.06<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-damp4\" min=\"0.01\" max=\"0.2\" value=\"0.06\" step=\"0.005\">\n          \n          <div class=\"control-label\" style=\"margin-top:16px;\">\n            <span>Imbalance 1st mode<\/span>\n            <span class=\"control-value\" id=\"val-imb1\">100 g\u00b7mm<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-imb1\" min=\"10\" max=\"500\" value=\"100\" step=\"10\">\n          \n          <div class=\"control-label\" style=\"margin-top:16px;\">\n            <span>Imbalance 2nd mode<\/span>\n            <span class=\"control-value\" id=\"val-imb2\">60 g\u00b7mm<\/span>\n          <\/div>\n          <input type=\"range\" id=\"ctrl-imb2\" min=\"10\" max=\"500\" value=\"60\" step=\"10\">\n        <\/div>\n\n        <div class=\"control-group\">\n          <div class=\"toggle-row\">\n            <label class=\"toggle\">\n              <input type=\"checkbox\" id=\"chk-brg3\" checked>\n              <div class=\"toggle-track\"><\/div>\n            <\/label>\n            <span class=\"toggle-label\">Bearing #3<\/span>\n          <\/div>\n          <div class=\"toggle-row\">\n            <label class=\"toggle\">\n              <input type=\"checkbox\" id=\"chk-brg4\" checked>\n              <div class=\"toggle-track\"><\/div>\n            <\/label>\n            <span class=\"toggle-label\">Bearing #4<\/span>\n          <\/div>\n          <div class=\"toggle-row\">\n            <label class=\"toggle\">\n              <input type=\"checkbox\" id=\"chk-afch-phase\">\n              <div class=\"toggle-track\"><\/div>\n            <\/label>\n            <span class=\"toggle-label\">Show Phase<\/span>\n          <\/div>\n        <\/div>\n      <\/div>\n      <div class=\"canvas-wrap\">\n        <canvas id=\"afchCanvas\" width=\"800\" height=\"480\"><\/canvas>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <div class=\"info-box\">\n    <p><strong style=\"color:var(--accent)\">Fig. 9:<\/strong> Amplitude-Phase Frequency Characteristic of a generator rotor during turbine unit coastdown. The APFC is constructed by measuring vibration amplitude and phase at the rotation frequency at bearings #3 and #4 during coastdown from operating speed.<\/p>\n  <\/div>\n\n  <p>If the phase does not change when passing through a suspected resonance, then the amplitude increase may be related to random excitation and is not a rotor resonance. In such cases, in addition to vibration measurements during run-up\/coastdown, it is recommended to perform an \"impact test\".<\/p>\n\n  <p>When using a multi-channel vibration analyzer, the resonance of a structure can be determined with great accuracy by measuring input and output signals from the system at the same time, while controlling the vibration phase and coherence collected during the same time period. Coherence is a dual-channel function used to evaluate the degree of linearity between input and output signals of the system. This means that resonant frequencies can be identified significantly faster.<\/p>\n<\/section>\n\n<!-- SECTION 7: CONSIDERATIONS -->\n<section id=\"sec-considerations\">\n  <h2>7. Some Considerations About Machine Resonance<\/h2>\n  \n  <p>Attention should be paid to the analysis of different types of machines and their operating modes, which may complicate resonance testing:<\/p>\n\n  <p>Due to differences in structural stiffness in the horizontal and vertical directions, the resonant frequency will differ depending on the direction. Therefore, resonances may be most strongly manifested in a particular direction.<\/p>\n\n  <p>As previously discussed, resonant frequencies differ when the machine is running vs. when it is stopped (switched off). Vertical equipment, as a rule, causes a great deal of concern, since during operation of such equipment there is always resonance that occurs during operation of a cantilever-mounted electric motor.<\/p>\n\n  <p>Some machines have a large mass, and therefore cannot be excited with a hammer \u2014 alternative excitation methods are required to determine the actual resonant frequencies. Sometimes, on very large machines, a vibrator is used that is tuned to a specific frequency range, because the vibrator has the ability to deliver large amounts of energy at each individual frequency when oscillating.<\/p>\n\n  <p>And one final consideration \u2014 before conducting resonance testing, it is very useful to first measure the background vibration level (the response to random excitation from the surrounding environment). This will help prevent an error in determining the diagnosis (system resonance) based on the maximum oscillation amplitude at a certain frequency above the background level.<\/p>\n<\/section>\n\n<!-- SECTION 8: SUMMARY -->\n<section id=\"sec-summary\">\n  <h2>8. Summary<\/h2>\n  <p>In this article we discussed the influence of resonant frequencies on machine vibration. All structures and machines have resonant frequencies, but resonance does not affect the machine if there are no frequencies that excite it. If the machine's vibration is excited by its own natural frequency, then there are three options for detuning the system from resonance:<\/p>\n\n  <p><strong style=\"color:var(--accent2)\">Option 1.<\/strong> Shift the frequency of the disturbing force away from the resonant frequency.<\/p>\n  <p><strong style=\"color:var(--accent2)\">Option 2.<\/strong> Shift the resonant frequency away from the frequency of the disturbing force.<\/p>\n  <p><strong style=\"color:var(--accent2)\">Option 3.<\/strong> Increase the damping of the system to reduce the resonance amplification factor.<\/p>\n\n  <p>Options 2 and 3 usually require some structural modifications that cannot be performed unless modal analysis and\/or finite element study have been carried out on the structure.<\/p>\n<\/section>\n\n<\/div>\n\n<footer>\n  <div class=\"container\">\n    <p>Interactive Guide: Resonance of Machine Elements and Assemblies<\/p>\n    <p style=\"margin-top:8px\"><a href=\"https:\/\/vibromera.com\" target=\"_blank\" rel=\"noopener\">vibromera.com<\/a> \u2014 Portable vibration balancing equipment<\/p>\n  <\/div>\n<\/footer>\n<\/div><!-- \/.resonance-app -->\n\n<script>\n\/\/ roundRect polyfill for older browsers\nif (!CanvasRenderingContext2D.prototype.roundRect) {\n  CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {\n    r = typeof r === 'number' ? r : (Array.isArray(r) ? r[0] : 0);\n    r = Math.min(r, w\/2, h\/2);\n    this.moveTo(x + r, y);\n    this.lineTo(x + w - r, y);\n    this.arcTo(x + w, y, x + w, y + r, r);\n    this.lineTo(x + w, y + h - r);\n    this.arcTo(x + w, y + h, x + w - r, y + h, r);\n    this.lineTo(x + r, y + h);\n    this.arcTo(x, y + h, x, y + h - r, r);\n    this.lineTo(x, y + r);\n    this.arcTo(x, y, x + r, y, r);\n    this.closePath();\n  };\n}\n\n\/\/ =============================\n\/\/ MAIN RESONANCE SIMULATOR\n\/\/ =============================\nconst $ = id => document.getElementById(id);\n\nfunction getResonanceParams() {\n  const m = parseFloat($('ctrl-mass').value);\n  const k = parseFloat($('ctrl-stiff').value);\n  const z = parseFloat($('ctrl-damp').value);\n  const e = parseFloat($('ctrl-imbal').value);\n  const bearingRatio = parseFloat($('ctrl-bearing').value);\n  const supportFlex = parseFloat($('ctrl-support').value) \/ 100;\n  const maxRpm = parseFloat($('ctrl-maxrpm').value);\n  \n  const kEffRaw = k * bearingRatio * (1 - supportFlex * 0.6);\n  const kEff = Math.max(kEffRaw, 1e-6); \/\/ guard against near-zero stiffness\n  const wn = Math.sqrt(kEff \/ m);\n  const fn = wn \/ (2 * Math.PI);\n  const critRPM = fn * 60;\n  const Q = 1 \/ (2 * z);\n  \n  return { m, k: kEff, z, e, wn, fn, critRPM, Q, maxRpm };\n}\n\n\/\/ Unbalance response amplitude in \u03bcm\n\/\/ e [g\u00b7mm] = unbalance (mass \u00d7 eccentricity), m [kg] = rotor mass\n\/\/ Since 1 g\u00b7mm = 1e-6 kg\u00b7m, result e\/m is directly in \u03bcm\n\/\/ r = \u03c9\/\u03c9n (frequency ratio)\nfunction resonanceAmplitude(r, z, e, m) {\n  const ecc = e \/ Math.max(m, 0.0001); \/\/ specific unbalance [\u03bcm], guarded\n  const denom = Math.sqrt(Math.pow(1 - r * r, 2) + Math.pow(2 * z * r, 2));\n  return ecc * r * r \/ (denom || 1e-6);\n}\n\nfunction resonancePhase(r, z) {\n  return Math.atan2(2 * z * r, 1 - r * r) * 180 \/ Math.PI;\n}\n\nfunction resizeCanvas(canvas, defaultW, defaultH) {\n  const rect = canvas.parentElement.getBoundingClientRect();\n  const dpr = window.devicePixelRatio || 1;\n  const w = Math.floor(rect.width) || defaultW;\n  const h = Math.floor(rect.height) || defaultH;\n  if (canvas.width !== w * dpr || canvas.height !== h * dpr) {\n    canvas.width = w * dpr;\n    canvas.height = h * dpr;\n    canvas.style.width = w + 'px';\n    canvas.style.height = h + 'px';\n    const ctx = canvas.getContext('2d');\n    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n  }\n  return { w, h };\n}\n\nfunction drawResonance() {\n  const canvas = $('resonanceCanvas');\n  const { w: W, h: H } = resizeCanvas(canvas, 800, 500);\n  const ctx = canvas.getContext('2d');\n  const p = getResonanceParams();\n  \n  const showPhase = $('chk-phase').checked;\n  const showDamped = $('chk-damped-freq').checked;\n  const showBW = $('chk-bandwidth').checked;\n  const logScale = $('chk-log').checked;\n  const showMulti = $('chk-multi').checked;\n  \n  ctx.clearRect(0, 0, W, H);\n  \n  \/\/ Margins \u2014 scale with canvas size\n  const ml = Math.max(55, W * 0.09);\n  const mr = showPhase ? Math.max(55, W * 0.09) : Math.max(20, W * 0.03);\n  const mt = Math.max(25, H * 0.06);\n  const mb = Math.max(45, H * 0.1);\n  const pw = W - ml - mr, ph = H - mt - mb;\n  \n  if (pw < 50 || ph < 50) return; \/\/ too small to draw\n  \n  \/\/ Plot background\n  ctx.fillStyle = '#ffffff';\n  ctx.fillRect(ml, mt, pw, ph);\n  \n  \/\/ Grid lines\n  ctx.strokeStyle = 'rgba(100,116,139,0.35)';\n  ctx.lineWidth = 0.5;\n  const xTicks = W > 500 ? 10 : 5;\n  const yTicks = H > 300 ? 10 : 5;\n  for (let i = 0; i <= xTicks; i++) {\n    const x = ml + (pw * i \/ xTicks);\n    ctx.beginPath(); ctx.moveTo(x, mt); ctx.lineTo(x, mt + ph); ctx.stroke();\n  }\n  for (let i = 0; i <= yTicks; i++) {\n    const y = mt + (ph * i \/ yTicks);\n    ctx.beginPath(); ctx.moveTo(ml, y); ctx.lineTo(ml + pw, y); ctx.stroke();\n  }\n  \n  \/\/ Compute data \u2014 find global max including multi-\u03b6 if active\n  const maxFreqHz = p.maxRpm \/ 60;\n  const fn = p.fn || 0.001;\n  const maxR = maxFreqHz \/ fn;\n  const N = 500;\n  \n  let ampData = [], phaseData = [];\n  let maxAmpMain = 0;      \/\/ peak of main curve (current \u03b6)\n  let maxAmpGlobal = 0;    \/\/ global max for Y-axis scaling (includes overlays)\n  \n  for (let i = 0; i <= N; i++) {\n    const r = (i \/ N) * maxR;\n    const amp = resonanceAmplitude(r, p.z, p.e, p.m);\n    const ph2 = resonancePhase(r, p.z);\n    ampData.push(amp);\n    phaseData.push(ph2);\n    if (amp > maxAmpMain) maxAmpMain = amp;\n  }\n  \n  \/\/ If multi-\u03b6 is on, scan all overlays to find the true global max\n  const multiDampings = [0.01, 0.03, 0.1, 0.2, 0.5];\n  maxAmpGlobal = maxAmpMain;\n  if (showMulti) {\n    for (const dz of multiDampings) {\n      for (let i = 0; i <= N; i++) {\n        const r = (i \/ N) * maxR;\n        const a = resonanceAmplitude(r, dz, p.e, p.m);\n        if (a > maxAmpGlobal) maxAmpGlobal = a;\n      }\n    }\n  }\n  \n  if (maxAmpGlobal < 0.001) maxAmpGlobal = 1;\n  const ampCeil = Math.max(maxAmpGlobal * 1.2, 0.01); \/\/ 20% headroom, min guard\n  \n  \/\/ Log scale range\n  const logMin = -3; \/\/ 0.001\n  const logMax = Math.log10(Math.max(ampCeil, 0.002));\n  \n  \/\/ Mapping helpers with clamping\n  function toX(i) { return ml + (i \/ N) * pw; }\n  \n  function toYAmp(a) {\n    let y;\n    if (logScale) {\n      const v = a > 0.001 ? Math.log10(a) : logMin;\n      y = mt + ph - ((v - logMin) \/ (logMax - logMin)) * ph;\n    } else {\n      y = mt + ph - (a \/ ampCeil) * ph;\n    }\n    return Math.max(mt - 2, Math.min(mt + ph + 2, y)); \/\/ clamp to plot area \u00b12px\n  }\n  \n  function toYPhase(deg) {\n    const y = mt + ph - (deg \/ 180) * ph;\n    return Math.max(mt - 2, Math.min(mt + ph + 2, y));\n  }\n  \n  \/\/ === CLIPPED DRAWING REGION for curves ===\n  ctx.save();\n  ctx.beginPath();\n  ctx.rect(ml, mt, pw, ph);\n  ctx.clip();\n  \n  \/\/ Multi damping overlay\n  if (showMulti) {\n    const colors = [\n      { s: 'rgba(220,38,38,0.25)', f: 'rgba(220,38,38,1)' },\n      { s: 'rgba(234,88,12,0.5)', f: 'rgba(234,88,12,1)' },\n      { s: 'rgba(22,163,74,0.5)',  f: 'rgba(22,163,74,1)' },\n      { s: 'rgba(37,99,235,0.5)', f: 'rgba(37,99,235,1)' },\n      { s: 'rgba(124,58,237,0.5)', f: 'rgba(124,58,237,1)' },\n    ];\n    \n    multiDampings.forEach((dz, di) => {\n      ctx.beginPath();\n      ctx.strokeStyle = colors[di].s;\n      ctx.lineWidth = 1.5;\n      for (let i = 0; i <= N; i++) {\n        const r = (i \/ N) * maxR;\n        const amp = resonanceAmplitude(r, dz, p.e, p.m);\n        const x = toX(i), y = toYAmp(amp);\n        if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);\n      }\n      ctx.stroke();\n      \n      \/\/ Label at r \u2248 1.5 (after peak, where curves separate well)\n      const labelR = 1.5;\n      const li = Math.min(N - 1, Math.max(0, Math.round((labelR \/ maxR) * N)));\n      if (li > 0 && li < N) {\n        const r2 = (li \/ N) * maxR;\n        const a2 = resonanceAmplitude(r2, dz, p.e, p.m);\n        const ly = toYAmp(a2);\n        if (ly > mt + 10 && ly < mt + ph - 10) {\n          ctx.fillStyle = colors[di].f;\n          ctx.font = '10px JetBrains Mono';\n          ctx.textAlign = 'left';\n          ctx.fillText('\u03b6=' + dz, toX(li) + 4, ly - 4);\n        }\n      }\n    });\n  }\n  \n  \/\/ Main amplitude curve \u2014 fill first\n  ctx.beginPath();\n  ctx.moveTo(toX(0), mt + ph);\n  for (let i = 0; i <= N; i++) {\n    ctx.lineTo(toX(i), toYAmp(ampData[i]));\n  }\n  ctx.lineTo(toX(N), mt + ph);\n  ctx.closePath();\n  const grad = ctx.createLinearGradient(0, mt, 0, mt + ph);\n  grad.addColorStop(0, 'rgba(220,38,38,0.1)');\n  grad.addColorStop(1, 'rgba(220,38,38,0.02)');\n  ctx.fillStyle = grad;\n  ctx.fill();\n  \n  \/\/ Main amplitude curve \u2014 stroke\n  ctx.beginPath();\n  ctx.strokeStyle = '#ef4444';\n  ctx.lineWidth = 2.5;\n  ctx.shadowColor = 'rgba(220,38,38,0.25)';\n  ctx.shadowBlur = 6;\n  for (let i = 0; i <= N; i++) {\n    const x = toX(i), y = toYAmp(ampData[i]);\n    if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);\n  }\n  ctx.stroke();\n  ctx.shadowBlur = 0;\n  \n  \/\/ Phase curve\n  if (showPhase) {\n    ctx.beginPath();\n    ctx.strokeStyle = '#3b82f6';\n    ctx.lineWidth = 2;\n    ctx.setLineDash([8, 4]);\n    for (let i = 0; i <= N; i++) {\n      const x = toX(i), y = toYPhase(phaseData[i]);\n      if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);\n    }\n    ctx.stroke();\n    ctx.setLineDash([]);\n  }\n  \n  \/\/ Critical speed marker (only if within range)\n  if (maxR > 1.000001) {\n    const critI = Math.round((1 \/ maxR) * N);\n    if (critI > 0 && critI < N) {\n      const cx = toX(critI);\n      ctx.strokeStyle = 'rgba(217,119,6,0.5)';\n      ctx.lineWidth = 1;\n      ctx.setLineDash([4, 4]);\n      ctx.beginPath(); ctx.moveTo(cx, mt); ctx.lineTo(cx, mt + ph); ctx.stroke();\n      ctx.setLineDash([]);\n      \n      ctx.fillStyle = '#d97706';\n      ctx.font = 'bold 11px JetBrains Mono';\n      \/\/ Keep label inside plot \u2014 flip side if too close to right edge\n      const labelText = 'n\u2081\u1d63\u2091\u209b = ' + Math.round(p.critRPM) + ' RPM';\n      const labelW = ctx.measureText(labelText).width;\n      if (cx + labelW + 10 > ml + pw) {\n        ctx.textAlign = 'right';\n        ctx.fillText(labelText, cx - 6, mt + 16);\n      } else {\n        ctx.textAlign = 'left';\n        ctx.fillText(labelText, cx + 6, mt + 16);\n      }\n    }\n  } else {\n    \/\/ Resonance is outside the displayed frequency range\n    ctx.fillStyle = 'rgba(71,85,105,0.7)';\n    ctx.font = '10px JetBrains Mono';\n    ctx.textAlign = 'left';\n    ctx.fillText('Note: critical speed is above selected max RPM', ml + 10, mt + ph - 10);\n  }\n  \n  \/\/ Half-power bandwidth (uses MAIN curve peak, not overlay global peak)\n  if (showBW && maxAmpMain > 0) {\n    const hpLevel = maxAmpMain \/ Math.sqrt(2);\n    const yHP = toYAmp(hpLevel);\n    \n    let bwLeft = -1, bwRight = -1;\n    for (let i = 1; i < N; i++) {\n      if (bwLeft < 0 && ampData[i] >= hpLevel && ampData[i - 1] < hpLevel) bwLeft = i;\n      if (bwLeft >= 0 && bwRight < 0 && ampData[i] <= hpLevel && ampData[i - 1] > hpLevel) bwRight = i;\n    }\n    \n    if (bwLeft > 0 && bwRight > 0) {\n      ctx.strokeStyle = 'rgba(16,185,129,0.6)';\n      ctx.lineWidth = 1.5;\n      ctx.setLineDash([3, 3]);\n      ctx.beginPath(); ctx.moveTo(toX(bwLeft), mt); ctx.lineTo(toX(bwLeft), mt + ph); ctx.stroke();\n      ctx.beginPath(); ctx.moveTo(toX(bwRight), mt); ctx.lineTo(toX(bwRight), mt + ph); ctx.stroke();\n      ctx.beginPath(); ctx.moveTo(ml, yHP); ctx.lineTo(ml + pw, yHP); ctx.stroke();\n      ctx.setLineDash([]);\n      \n      ctx.fillStyle = '#10b981';\n      ctx.font = '10px JetBrains Mono';\n      ctx.textAlign = 'left';\n      const bwLabelX = toX(bwRight) + 5;\n      if (bwLabelX + 100 > ml + pw) {\n        ctx.textAlign = 'right';\n        ctx.fillText('\u22123dB BW', toX(bwLeft) - 5, yHP - 5);\n      } else {\n        ctx.fillText('\u22123dB bandwidth', bwLabelX, yHP - 5);\n      }\n    }\n  }\n  \n  \/\/ Damped frequency marker\n  if (showDamped && p.z < 1) {\n    const wd = p.wn * Math.sqrt(1 - p.z * p.z);\n    const rd = wd \/ p.wn;\n    const di = Math.round((rd \/ maxR) * N);\n    if (di > 0 && di < N) {\n      ctx.strokeStyle = 'rgba(124,58,237,0.6)';\n      ctx.lineWidth = 1;\n      ctx.setLineDash([2, 4]);\n      ctx.beginPath(); ctx.moveTo(toX(di), mt); ctx.lineTo(toX(di), mt + ph); ctx.stroke();\n      ctx.setLineDash([]);\n      ctx.fillStyle = '#8b5cf6';\n      ctx.font = '10px JetBrains Mono';\n      ctx.textAlign = 'left';\n      ctx.fillText('f\u1d48', toX(di) + 3, mt + ph - 8);\n    }\n  }\n  \n  \/\/ === END CLIP \u2014 restore for axes labels ===\n  ctx.restore();\n  \n  \/\/ Axes labels\n  ctx.fillStyle = '#475569';\n  ctx.font = '11px JetBrains Mono';\n  ctx.textAlign = 'center';\n  \n  \/\/ X axis \u2014 RPM ticks\n  const xLabelTicks = W > 500 ? 10 : 5;\n  for (let i = 0; i <= xLabelTicks; i++) {\n    const rpm = (i \/ xLabelTicks) * p.maxRpm;\n    const x = ml + (i \/ xLabelTicks) * pw;\n    ctx.fillText(Math.round(rpm), x, mt + ph + 18);\n  }\n  ctx.fillText('RPM', ml + pw \/ 2, mt + ph + 38);\n  \n  \/\/ Y axis \u2014 Amplitude ticks\n  ctx.textAlign = 'right';\n  const yLabelTicks = 5;\n  for (let i = 0; i <= yLabelTicks; i++) {\n    let val, y;\n    if (logScale) {\n      const logVal = logMin + (i \/ yLabelTicks) * (logMax - logMin);\n      val = Math.pow(10, logVal);\n      y = mt + ph - (i \/ yLabelTicks) * ph;\n      ctx.fillText(val < 1 ? val.toFixed(2) : val.toFixed(1), ml - 6, y + 4);\n    } else {\n      val = (i \/ yLabelTicks) * ampCeil;\n      y = mt + ph - (i \/ yLabelTicks) * ph;\n      ctx.fillText(val < 10 ? val.toFixed(1) : Math.round(val).toString(), ml - 6, y + 4);\n    }\n  }\n  ctx.save();\n  ctx.translate(14, mt + ph \/ 2);\n  ctx.rotate(-Math.PI \/ 2);\n  ctx.textAlign = 'center';\n  ctx.fillStyle = '#ef4444';\n  ctx.fillText('Amplitude, \u03bcm', 0, 0);\n  ctx.restore();\n  \n  \/\/ Phase Y axis\n  if (showPhase) {\n    ctx.textAlign = 'left';\n    ctx.fillStyle = '#3b82f6';\n    for (let i = 0; i <= 4; i++) {\n      const deg = i * 45;\n      const y = mt + ph - (deg \/ 180) * ph;\n      ctx.fillText(deg + '\u00b0', ml + pw + 6, y + 4);\n    }\n    ctx.save();\n    ctx.translate(W - 10, mt + ph \/ 2);\n    ctx.rotate(Math.PI \/ 2);\n    ctx.textAlign = 'center';\n    ctx.fillText('Phase, \u00b0', 0, 0);\n    ctx.restore();\n  }\n  \n  \/\/ Plot border\n  ctx.strokeStyle = 'rgba(148,163,184,0.5)';\n  ctx.lineWidth = 1;\n  ctx.strokeRect(ml, mt, pw, ph);\n  \n  \/\/ Update readouts\n  $('ro-fn').textContent = p.fn.toFixed(1) + ' Hz';\n  $('ro-rpm').textContent = Math.round(p.critRPM) + ' RPM';\n  $('ro-peak').textContent = maxAmpMain < 10 ? maxAmpMain.toFixed(2) + ' \u03bcm' : Math.round(maxAmpMain) + ' \u03bcm';\n  $('ro-q').textContent = p.Q.toFixed(1);\n  $('ro-amp').textContent = (1 \/ (2 * p.z)).toFixed(1) + '\u00d7';\n}\n\n\/\/ Update labels\nfunction updateLabels() {\n  $('val-mass').textContent = parseFloat($('ctrl-mass').value).toFixed(1) + ' kg';\n  $('val-stiff').textContent = Math.round($('ctrl-stiff').value) + ' N\/m';\n  $('val-damp').textContent = parseFloat($('ctrl-damp').value).toFixed(3);\n  $('val-imbal').textContent = Math.round($('ctrl-imbal').value) + ' g\u00b7mm';\n  $('val-bearing').textContent = parseFloat($('ctrl-bearing').value).toFixed(2);\n  $('val-support').textContent = Math.round($('ctrl-support').value) + '%';\n  $('val-maxrpm').textContent = Math.round($('ctrl-maxrpm').value);\n}\n\n\/\/ Presets\nfunction applyPreset(name, btn) {\n  const presets = {\n    fan:     { m: 15, k: 50000, z: 0.04, e: 100, bearing: 1.0, support: 10, maxrpm: 6000 },\n    pump:    { m: 30, k: 120000, z: 0.03, e: 30, bearing: 1.2, support: 5, maxrpm: 8000 },\n    turbine: { m: 80, k: 180000, z: 0.02, e: 20, bearing: 1.5, support: 3, maxrpm: 12000 },\n    crusher: { m: 50, k: 60000, z: 0.08, e: 200, bearing: 0.8, support: 20, maxrpm: 4000 },\n    spindle: { m: 5, k: 150000, z: 0.01, e: 5, bearing: 2.0, support: 0, maxrpm: 30000 },\n    mulcher: { m: 25, k: 35000, z: 0.06, e: 150, bearing: 0.7, support: 25, maxrpm: 5000 },\n  };\n  const p = presets[name];\n  if (!p) return;\n  $('ctrl-mass').value = p.m;\n  $('ctrl-stiff').value = p.k;\n  $('ctrl-damp').value = p.z;\n  $('ctrl-imbal').value = p.e;\n  $('ctrl-bearing').value = p.bearing;\n  $('ctrl-support').value = p.support;\n  $('ctrl-maxrpm').value = p.maxrpm;\n  \n  document.querySelectorAll('.preset-btn').forEach(b => b.classList.remove('active'));\n  if (btn) btn.classList.add('active');\n  \n  updateLabels();\n  drawResonance();\n}\n\n\/\/ =============================\n\/\/ OVERHUNG ROTOR SCHEMATIC\n\/\/ =============================\nfunction drawOverhung() {\n  const canvas = $('overhungCanvas');\n  if (!canvas) return;\n  const { w: W, h: H } = resizeCanvas(canvas, 500, 260);\n  const ctx = canvas.getContext('2d');\n  ctx.clearRect(0, 0, W, H);\n  \n  const cy = H * 0.47; \/\/ vertical center (slightly above mid for label room)\n  const pad = W * 0.06; \/\/ left\/right padding\n  const totalLen = W - 2 * pad;\n  \n  \/\/ Key X positions (proportional)\n  const xBrg1 = pad + totalLen * 0.22;\n  const xBrg2 = pad + totalLen * 0.58;\n  const xDisk = pad + totalLen * 0.85;\n  const xShaftL = pad + totalLen * 0.08;\n  const xShaftR = pad + totalLen * 0.92;\n  \n  const diskR = Math.min(H * 0.22, 55); \/\/ disk radius\n  const brgW = Math.max(16, W * 0.04);  \/\/ bearing width\n  const brgH = Math.max(30, H * 0.16);  \/\/ bearing pedestal height\n  const shaftT = Math.max(5, W * 0.012); \/\/ shaft thickness\n  const fs = 11; \/\/ fixed font size px\n  \n  \/\/ Foundation\n  const foundY = cy + brgH * 0.7;\n  ctx.fillStyle = '#cbd5e1';\n  ctx.fillRect(xBrg1 - brgW * 1.5, foundY, xBrg2 - xBrg1 + brgW * 3, H * 0.04);\n  ctx.strokeStyle = '#94a3b8';\n  ctx.lineWidth = 1;\n  const hatchSpacing = Math.max(12, W * 0.03);\n  for (let x = xBrg1 - brgW * 1.5; x < xBrg2 + brgW * 1.5; x += hatchSpacing) {\n    ctx.beginPath();\n    ctx.moveTo(x, foundY + H * 0.04);\n    ctx.lineTo(x + hatchSpacing * 0.6, foundY + H * 0.04 + H * 0.04);\n    ctx.stroke();\n  }\n  \n  \/\/ Bearing pedestals\n  function drawBearing(bx) {\n    \/\/ Pedestal\n    ctx.fillStyle = '#94a3b8';\n    ctx.fillRect(bx - brgW \/ 2, cy + shaftT * 1.2, brgW, foundY - cy - shaftT * 1.2);\n    ctx.strokeStyle = '#94a3b8';\n    ctx.lineWidth = 1;\n    ctx.strokeRect(bx - brgW \/ 2, cy + shaftT * 1.2, brgW, foundY - cy - shaftT * 1.2);\n    \n    \/\/ Bearing symbol \u2014 triangle supports\n    const triH = Math.max(6, H * 0.04);\n    ctx.strokeStyle = '#d97706';\n    ctx.lineWidth = 1.5;\n    ctx.beginPath();\n    ctx.moveTo(bx, cy - shaftT * 1.5);\n    ctx.lineTo(bx - triH, cy - shaftT * 1.5 - triH);\n    ctx.lineTo(bx + triH, cy - shaftT * 1.5 - triH);\n    ctx.closePath();\n    ctx.stroke();\n    \/\/ Rolling elements\n    const reR = Math.max(3, H * 0.015);\n    ctx.beginPath(); ctx.arc(bx - reR * 1.5, cy + shaftT * 0.5, reR, 0, Math.PI * 2); ctx.stroke();\n    ctx.beginPath(); ctx.arc(bx + reR * 1.5, cy + shaftT * 0.5, reR, 0, Math.PI * 2); ctx.stroke();\n  }\n  \n  drawBearing(xBrg1);\n  drawBearing(xBrg2);\n  \n  \/\/ Shaft centerline (dashed)\n  ctx.strokeStyle = 'rgba(100,116,139,0.35)';\n  ctx.lineWidth = 1;\n  ctx.setLineDash([6, 5]);\n  ctx.beginPath();\n  ctx.moveTo(pad, cy);\n  ctx.lineTo(W - pad * 0.5, cy);\n  ctx.stroke();\n  ctx.setLineDash([]);\n  \n  \/\/ Shaft (solid)\n  ctx.strokeStyle = '#94a3b8';\n  ctx.lineWidth = shaftT * 2;\n  ctx.lineCap = 'round';\n  ctx.beginPath();\n  ctx.moveTo(xShaftL, cy);\n  ctx.lineTo(xShaftR, cy);\n  ctx.stroke();\n  ctx.lineCap = 'butt';\n  \n  \/\/ Overhung Disk\n  ctx.fillStyle = '#3b82f6';\n  ctx.globalAlpha = 0.75;\n  ctx.beginPath();\n  ctx.ellipse(xDisk, cy, diskR * 0.22, diskR, 0, 0, Math.PI * 2);\n  ctx.fill();\n  ctx.globalAlpha = 1;\n  ctx.strokeStyle = '#2563eb';\n  ctx.lineWidth = 2;\n  ctx.beginPath();\n  ctx.ellipse(xDisk, cy, diskR * 0.22, diskR, 0, 0, Math.PI * 2);\n  ctx.stroke();\n  \n  \/\/ Imbalance spot on disk\n  const imbR = Math.max(4, diskR * 0.11);\n  ctx.fillStyle = '#ef4444';\n  ctx.beginPath();\n  ctx.arc(xDisk, cy - diskR * 0.82, imbR, 0, Math.PI * 2);\n  ctx.fill();\n  ctx.fillStyle = '#d97706';\n  ctx.font = 'bold 9px JetBrains Mono';\n  ctx.textAlign = 'left';\n  ctx.fillText('m', xDisk + imbR * 1.5, cy - diskR * 0.78);\n  \n  \/\/ Whirl orbit (dashed circle at disk)\n  const orbitR = diskR * 0.4;\n  ctx.strokeStyle = 'rgba(220,38,38,0.25)';\n  ctx.lineWidth = 1;\n  ctx.setLineDash([3, 3]);\n  ctx.beginPath();\n  ctx.arc(xDisk, cy, orbitR, 0, Math.PI * 2);\n  ctx.stroke();\n  ctx.setLineDash([]);\n  \n  \/\/ Rotation arrow (near left end of shaft)\n  const arrowX = xShaftL + totalLen * 0.02;\n  const arrowR = Math.max(10, H * 0.06);\n  ctx.strokeStyle = '#10b981';\n  ctx.lineWidth = 1.5;\n  ctx.beginPath();\n  ctx.arc(arrowX, cy, arrowR, -Math.PI * 0.7, Math.PI * 0.3);\n  ctx.stroke();\n  \/\/ Arrowhead\n  const aEnd = Math.PI * 0.3;\n  const ax = arrowX + arrowR * Math.cos(aEnd);\n  const ay = cy + arrowR * Math.sin(aEnd);\n  ctx.beginPath();\n  ctx.moveTo(ax, ay);\n  ctx.lineTo(ax + 4, ay - 6);\n  ctx.moveTo(ax, ay);\n  ctx.lineTo(ax - 5, ay - 3);\n  ctx.stroke();\n  ctx.fillStyle = '#10b981';\n  ctx.font = '10px JetBrains Mono';\n  ctx.textAlign = 'center';\n  ctx.fillText('\u03c9', arrowX - arrowR * 1.4, cy + 4);\n  \n  \/\/ Labels\n  ctx.fillStyle = '#475569';\n  ctx.font = '11px JetBrains Mono';\n  ctx.textAlign = 'center';\n  ctx.fillText('Brg 1', xBrg1, foundY + H * 0.14);\n  ctx.fillText('Brg 2', xBrg2, foundY + H * 0.14);\n  ctx.fillText('Disk', xDisk, foundY + H * 0.14);\n  \n  \/\/ Dimension: overhung length\n  const dimY = cy - diskR - H * 0.08;\n  ctx.strokeStyle = '#d97706';\n  ctx.lineWidth = 1;\n  ctx.beginPath(); ctx.moveTo(xBrg2, dimY); ctx.lineTo(xDisk, dimY); ctx.stroke();\n  \/\/ end ticks\n  ctx.beginPath(); ctx.moveTo(xBrg2, dimY - 4); ctx.lineTo(xBrg2, dimY + 4); ctx.stroke();\n  ctx.beginPath(); ctx.moveTo(xDisk, dimY - 4); ctx.lineTo(xDisk, dimY + 4); ctx.stroke();\n  \/\/ arrowheads\n  ctx.beginPath(); ctx.moveTo(xBrg2 + 1, dimY); ctx.lineTo(xBrg2 + 6, dimY - 3); ctx.lineTo(xBrg2 + 6, dimY + 3); ctx.closePath(); ctx.fillStyle = '#d97706'; ctx.fill();\n  ctx.beginPath(); ctx.moveTo(xDisk - 1, dimY); ctx.lineTo(xDisk - 6, dimY - 3); ctx.lineTo(xDisk - 6, dimY + 3); ctx.closePath(); ctx.fill();\n  ctx.font = '9px JetBrains Mono';\n  ctx.fillText('overhung', (xBrg2 + xDisk) \/ 2, dimY - 6);\n  \n  \/\/ Dimension: bearing span\n  const dimY2 = foundY + H * 0.04 + H * 0.06;\n  ctx.strokeStyle = '#94a3b8';\n  ctx.beginPath(); ctx.moveTo(xBrg1, dimY2); ctx.lineTo(xBrg2, dimY2); ctx.stroke();\n  ctx.beginPath(); ctx.moveTo(xBrg1, dimY2 - 3); ctx.lineTo(xBrg1, dimY2 + 3); ctx.stroke();\n  ctx.beginPath(); ctx.moveTo(xBrg2, dimY2 - 3); ctx.lineTo(xBrg2, dimY2 + 3); ctx.stroke();\n  ctx.fillStyle = '#64748b';\n  ctx.font = '8px JetBrains Mono';\n  ctx.fillText('bearing span', (xBrg1 + xBrg2) \/ 2, dimY2 - 5);\n}\n\n\/\/ =============================\n\/\/ CARDAN SHAFT SCHEMATIC\n\/\/ =============================\nfunction drawCardan() {\n  const canvas = $('cardanCanvas');\n  if (!canvas) return;\n  const { w: W, h: H } = resizeCanvas(canvas, 500, 300);\n  const ctx = canvas.getContext('2d');\n  ctx.clearRect(0, 0, W, H);\n  \n  if (W < 100 || H < 80) return;\n  \n  \/\/ --- Layout constants (all relative) ---\n  const pad = W * 0.03;\n  const usableW = W - pad * 2;\n  const cyMotor = H * 0.38; \/\/ motor centerline\n  const fs = 10; \/\/ fixed font size px\n  const shaftT = Math.max(3, Math.min(6, W * 0.009));\n  const jointAngle = 7 * Math.PI \/ 180; \/\/ 7\u00b0 working angle\n  \n  \/\/ X positions (fraction of usableW from pad)\n  const xMotorL  = pad;\n  const xMotorR  = pad + usableW * 0.14;\n  const xUJ1     = pad + usableW * 0.20;\n  const xUJ2     = pad + usableW * 0.80;\n  const xLoadL   = pad + usableW * 0.86;\n  const xLoadR   = pad + usableW;\n  \n  \/\/ Load is offset vertically \u2014 the whole point of having U-joints\n  const shaftLen = xUJ2 - xUJ1;\n  const cyLoad = cyMotor + Math.tan(jointAngle) * shaftLen; \/\/ load axis lower than motor\n  const xShaftMid = (xUJ1 + xUJ2) \/ 2;\n  const yShaftMid = (cyMotor + cyLoad) \/ 2;\n  \n  \/\/ Box sizes\n  const boxH = Math.min(H * 0.3, 72);\n  const boxW = xMotorR - xMotorL;\n  const loadW = xLoadR - xLoadL;\n  \n  \/\/ Joint radii\n  const jR = Math.max(8, Math.min(14, W * 0.026));\n\n  \/\/ ============ DRAWING ============\n  \n  \/\/ --- Motor centerline (dashed) ---\n  ctx.strokeStyle = 'rgba(100,116,139,0.25)';\n  ctx.lineWidth = 1;\n  ctx.setLineDash([6, 5]);\n  ctx.beginPath();\n  ctx.moveTo(xMotorL, cyMotor);\n  ctx.lineTo(xUJ1 + shaftLen * 0.08, cyMotor);\n  ctx.stroke();\n  \/\/ --- Load centerline (dashed) ---\n  ctx.beginPath();\n  ctx.moveTo(xUJ2 - shaftLen * 0.08, cyLoad);\n  ctx.lineTo(xLoadR, cyLoad);\n  ctx.stroke();\n  ctx.setLineDash([]);\n  \n  \/\/ --- Motor box (at cyMotor) ---\n  const mGrad = ctx.createLinearGradient(xMotorL, cyMotor - boxH\/2, xMotorR, cyMotor + boxH\/2);\n  mGrad.addColorStop(0, '#d1d5db');\n  mGrad.addColorStop(1, '#e5e7eb');\n  ctx.fillStyle = mGrad;\n  ctx.beginPath();\n  ctx.roundRect(xMotorL, cyMotor - boxH\/2, boxW, boxH, 3);\n  ctx.fill();\n  ctx.strokeStyle = '#94a3b8';\n  ctx.lineWidth = 1.5;\n  ctx.stroke();\n  \/\/ Cooling fins\n  ctx.strokeStyle = '#94a3b8';\n  ctx.lineWidth = 0.8;\n  for (let i = 1; i <= 4; i++) {\n    const fy = cyMotor - boxH\/2 + (boxH * i \/ 5);\n    ctx.beginPath(); ctx.moveTo(xMotorL + 3, fy); ctx.lineTo(xMotorR - 3, fy); ctx.stroke();\n  }\n  \n  \/\/ --- Load box (at cyLoad \u2014 offset) ---\n  const lGrad = ctx.createLinearGradient(xLoadL, cyLoad - boxH\/2, xLoadR, cyLoad + boxH\/2);\n  lGrad.addColorStop(0, '#d1d5db');\n  lGrad.addColorStop(1, '#e5e7eb');\n  ctx.fillStyle = lGrad;\n  ctx.beginPath();\n  ctx.roundRect(xLoadL, cyLoad - boxH\/2, loadW, boxH, 3);\n  ctx.fill();\n  ctx.strokeStyle = '#94a3b8';\n  ctx.lineWidth = 1.5;\n  ctx.stroke();\n  \n  \/\/ Box labels (inside boxes)\n  ctx.fillStyle = '#475569';\n  ctx.font = 'bold 8px JetBrains Mono';\n  ctx.textAlign = 'center';\n  ctx.fillText('MOTOR', (xMotorL + xMotorR) \/ 2, cyMotor + 3);\n  ctx.fillText('LOAD', (xLoadL + xLoadR) \/ 2, cyLoad + 3);\n  \n  \/\/ --- Input shaft stub (motor \u2192 UJ1, horizontal at cyMotor) ---\n  ctx.strokeStyle = '#94a3b8';\n  ctx.lineWidth = shaftT * 2;\n  ctx.lineCap = 'round';\n  ctx.beginPath();\n  ctx.moveTo(xMotorR, cyMotor);\n  ctx.lineTo(xUJ1, cyMotor);\n  ctx.stroke();\n  \n  \/\/ --- Output shaft stub (UJ2 \u2192 load, horizontal at cyLoad) ---\n  ctx.beginPath();\n  ctx.moveTo(xUJ2, cyLoad);\n  ctx.lineTo(xLoadL, cyLoad);\n  ctx.stroke();\n  ctx.lineCap = 'butt';\n  \n  \/\/ --- Cardan shaft: ONE STRAIGHT LINE from UJ1 to UJ2 ---\n  ctx.strokeStyle = '#2563eb';\n  ctx.lineWidth = shaftT * 2;\n  ctx.lineCap = 'round';\n  ctx.beginPath();\n  ctx.moveTo(xUJ1, cyMotor);\n  ctx.lineTo(xUJ2, cyLoad);\n  ctx.stroke();\n  ctx.lineCap = 'butt';\n  \n  \/\/ --- Telescopic\/spline section (at midpoint of the straight shaft) ---\n  const splineW = shaftLen * 0.18;\n  \/\/ Calculate points along the shaft line at midpoint \u00b1 spline half-width\n  const dx = xUJ2 - xUJ1, dy = cyLoad - cyMotor;\n  const shaftAngle = Math.atan2(dy, dx);\n  const spL_x = xShaftMid - Math.cos(shaftAngle) * splineW\/2;\n  const spL_y = yShaftMid - Math.sin(shaftAngle) * splineW\/2;\n  const spR_x = xShaftMid + Math.cos(shaftAngle) * splineW\/2;\n  const spR_y = yShaftMid + Math.sin(shaftAngle) * splineW\/2;\n  \/\/ Outer tube\n  ctx.strokeStyle = 'rgba(37,99,235,0.25)';\n  ctx.lineWidth = shaftT * 3.5;\n  ctx.beginPath();\n  ctx.moveTo(spL_x, spL_y);\n  ctx.lineTo(spR_x, spR_y);\n  ctx.stroke();\n  \/\/ Spline teeth (perpendicular to shaft direction)\n  ctx.strokeStyle = 'rgba(37,99,235,0.55)';\n  ctx.lineWidth = 0.8;\n  const perpX = -Math.sin(shaftAngle);\n  const perpY = Math.cos(shaftAngle);\n  const teethN = Math.max(4, Math.floor(splineW \/ 7));\n  for (let i = 0; i < teethN; i++) {\n    const t = (i + 0.5) \/ teethN;\n    const tx = spL_x + (spR_x - spL_x) * t;\n    const ty = spL_y + (spR_y - spL_y) * t;\n    ctx.beginPath();\n    ctx.moveTo(tx + perpX * shaftT * 0.7, ty + perpY * shaftT * 0.7);\n    ctx.lineTo(tx - perpX * shaftT * 0.7, ty - perpY * shaftT * 0.7);\n    ctx.stroke();\n  }\n  \n  \/\/ --- Universal Joints ---\n  function drawUJoint(jx, jy) {\n    \/\/ Background disc\n    ctx.fillStyle = 'rgba(217,119,6,0.08)';\n    ctx.beginPath();\n    ctx.arc(jx, jy, jR * 1.2, 0, Math.PI * 2);\n    ctx.fill();\n    \/\/ Outer ring\n    ctx.strokeStyle = '#d97706';\n    ctx.lineWidth = 2;\n    ctx.beginPath();\n    ctx.arc(jx, jy, jR, 0, Math.PI * 2);\n    ctx.stroke();\n    \/\/ Cross arms\n    ctx.strokeStyle = '#d97706';\n    ctx.lineWidth = 2.5;\n    const cr = jR * 0.65;\n    ctx.beginPath(); ctx.moveTo(jx - cr, jy); ctx.lineTo(jx + cr, jy); ctx.stroke();\n    ctx.beginPath(); ctx.moveTo(jx, jy - cr); ctx.lineTo(jx, jy + cr); ctx.stroke();\n    \/\/ Center hub\n    ctx.fillStyle = '#d97706';\n    ctx.beginPath();\n    ctx.arc(jx, jy, jR * 0.2, 0, Math.PI * 2);\n    ctx.fill();\n    \/\/ Bearing caps at cross ends\n    ctx.fillStyle = '#94a3b8';\n    const capR = jR * 0.15;\n    [[-1,0],[1,0],[0,-1],[0,1]].forEach(([dx,dy]) => {\n      ctx.beginPath();\n      ctx.arc(jx + dx * cr, jy + dy * cr, capR, 0, Math.PI * 2);\n      ctx.fill();\n    });\n  }\n  \n  drawUJoint(xUJ1, cyMotor);\n  drawUJoint(xUJ2, cyLoad);\n  \n  \/\/ --- Angle arc at UJ1 (angle between horizontal input and angled shaft) ---\n  const arcR = jR * 2.2;\n  ctx.strokeStyle = 'rgba(217,119,6,0.6)';\n  ctx.lineWidth = 1.2;\n  ctx.beginPath();\n  ctx.arc(xUJ1, cyMotor, arcR, 0, shaftAngle); \/\/ clockwise: horizontal \u2192 shaft\n  ctx.stroke();\n  \/\/ Angle label\n  ctx.fillStyle = '#d97706';\n  ctx.font = '10px JetBrains Mono';\n  ctx.textAlign = 'left';\n  ctx.fillText('\u03b2', xUJ1 + arcR * 0.85, cyMotor + arcR * 0.55);\n  \n  \/\/ --- Angle arc at UJ2 (angle between angled shaft and horizontal output) ---\n  ctx.strokeStyle = 'rgba(217,119,6,0.6)';\n  ctx.beginPath();\n  ctx.arc(xUJ2, cyLoad, arcR, Math.PI + shaftAngle, Math.PI, true); \/\/ counterclockwise: shaft arrival \u2192 horizontal left\n  ctx.stroke();\n  ctx.textAlign = 'right';\n  ctx.fillText('\u03b2', xUJ2 - arcR * 0.85, cyLoad + arcR * 0.55);\n  \n  \/\/ --- Joint labels ---\n  ctx.font = '9px JetBrains Mono';\n  ctx.textAlign = 'center';\n  ctx.fillStyle = '#d97706';\n  ctx.fillText('U-Joint 1', xUJ1, cyMotor - jR - 8);\n  ctx.fillText('U-Joint 2', xUJ2, cyLoad - jR - 8);\n  \n  \/\/ --- Vibration indicators along shaft ---\n  \/\/ 1\u00d7 sinusoidal wave (above shaft midpoint)\n  ctx.strokeStyle = 'rgba(220,38,38,0.6)';\n  ctx.lineWidth = 1.2;\n  const wave1Y = yShaftMid - shaftT * 5;\n  const waveLen = shaftLen * 0.25;\n  ctx.beginPath();\n  for (let i = 0; i <= 50; i++) {\n    const wx = xShaftMid - waveLen\/2 + (waveLen * i \/ 50);\n    const wy = wave1Y + Math.sin(i * 0.25) * 4;\n    if (i === 0) ctx.moveTo(wx, wy); else ctx.lineTo(wx, wy);\n  }\n  ctx.stroke();\n  ctx.fillStyle = '#ef4444';\n  ctx.font = 'bold 8px JetBrains Mono';\n  ctx.textAlign = 'center';\n  ctx.fillText('1\u00d7', xShaftMid, wave1Y - 7);\n  \n  \/\/ 2\u00d7 sinusoidal wave (below shaft)\n  const wave2Y = Math.max(cyMotor, cyLoad) + boxH * 0.55;\n  ctx.strokeStyle = 'rgba(124,58,237,0.6)';\n  ctx.lineWidth = 1.2;\n  ctx.beginPath();\n  for (let i = 0; i <= 50; i++) {\n    const wx = xShaftMid - waveLen\/2 + (waveLen * i \/ 50);\n    const wy = wave2Y + Math.sin(i * 0.5) * 3;\n    if (i === 0) ctx.moveTo(wx, wy); else ctx.lineTo(wx, wy);\n  }\n  ctx.stroke();\n  ctx.fillStyle = '#8b5cf6';\n  ctx.font = 'bold 8px JetBrains Mono';\n  ctx.fillText('2\u00d7 (joint angle)', xShaftMid, wave2Y - 6);\n  \n  \/\/ --- Dimension: cardan shaft length ---\n  const dimY = H - pad - 15;\n  ctx.strokeStyle = '#10b981';\n  ctx.lineWidth = 1;\n  \/\/ Horizontal line\n  ctx.beginPath(); ctx.moveTo(xUJ1, dimY); ctx.lineTo(xUJ2, dimY); ctx.stroke();\n  \/\/ End ticks\n  ctx.beginPath(); ctx.moveTo(xUJ1, dimY - 4); ctx.lineTo(xUJ1, dimY + 4); ctx.stroke();\n  ctx.beginPath(); ctx.moveTo(xUJ2, dimY - 4); ctx.lineTo(xUJ2, dimY + 4); ctx.stroke();\n  \/\/ Leader lines from joints down to dimension\n  ctx.strokeStyle = 'rgba(5,150,105,0.25)';\n  ctx.setLineDash([2,3]);\n  ctx.beginPath(); ctx.moveTo(xUJ1, cyMotor + jR + 2); ctx.lineTo(xUJ1, dimY - 5); ctx.stroke();\n  ctx.beginPath(); ctx.moveTo(xUJ2, cyLoad + jR + 2); ctx.lineTo(xUJ2, dimY - 5); ctx.stroke();\n  ctx.setLineDash([]);\n  \/\/ Arrowheads\n  ctx.fillStyle = '#10b981';\n  ctx.beginPath(); ctx.moveTo(xUJ1, dimY); ctx.lineTo(xUJ1+5, dimY-2.5); ctx.lineTo(xUJ1+5, dimY+2.5); ctx.closePath(); ctx.fill();\n  ctx.beginPath(); ctx.moveTo(xUJ2, dimY); ctx.lineTo(xUJ2-5, dimY-2.5); ctx.lineTo(xUJ2-5, dimY+2.5); ctx.closePath(); ctx.fill();\n  \/\/ Label\n  ctx.font = '8px JetBrains Mono';\n  ctx.textAlign = 'center';\n  ctx.fillText('shaft length L', (xUJ1 + xUJ2) \/ 2, dimY - 5);\n  \n  \/\/ --- Rotation arrow at motor output ---\n  const arrowX = (xMotorR + xUJ1) \/ 2;\n  const arrowR = Math.min(12, shaftT * 2.5);\n  ctx.strokeStyle = '#10b981';\n  ctx.lineWidth = 1.2;\n  ctx.beginPath();\n  ctx.arc(arrowX, cyMotor, arrowR, -Math.PI * 0.7, Math.PI * 0.35);\n  ctx.stroke();\n  \/\/ Arrowhead\n  const tipAng = Math.PI * 0.35;\n  const tipX = arrowX + arrowR * Math.cos(tipAng);\n  const tipY = cyMotor + arrowR * Math.sin(tipAng);\n  ctx.beginPath();\n  ctx.moveTo(tipX, tipY);\n  ctx.lineTo(tipX + 4, tipY - 5);\n  ctx.moveTo(tipX, tipY);\n  ctx.lineTo(tipX - 4, tipY - 2);\n  ctx.stroke();\n  ctx.fillStyle = '#10b981';\n  ctx.font = '9px JetBrains Mono';\n  ctx.textAlign = 'center';\n  ctx.fillText('\u03c9', arrowX, cyMotor - arrowR - 3);\n}\n\n\/\/ =============================\n\/\/ IMPACT HAMMER VISUALIZATION\n\/\/ =============================\nfunction drawImpact() {\n  const canvas = $('impactCanvas');\n  const { w: W, h: H } = resizeCanvas(canvas, 700, 380);\n  const ctx = canvas.getContext('2d');\n  ctx.clearRect(0, 0, W, H);\n  \n  const tip = $('hammer-tip').value;\n  const force = parseFloat($('ctrl-force').value);\n  const stiff = parseFloat($('ctrl-tipstiff').value);\n  \n  const tipParams = {\n    steel:   { duration: 0.5, color: '#475569', label: 'Steel', maxFreq: 5000 },\n    plastic: { duration: 1.5, color: '#d97706', label: 'Plastic', maxFreq: 2000 },\n    rubber: { duration: 4.0, color: '#10b981', label: 'Rubber', maxFreq: 500 },\n  };\n  const tp = tipParams[tip];\n  const dur = tp.duration * (1 + (10 - stiff) * 0.3);\n  const fMax = tp.maxFreq * (stiff \/ 5);\n  \n  const half = W \/ 2;\n  const ml = 60, mr = 30, mt = 40, mb = 45;\n  \n  \/\/ LEFT: Time domain pulse\n  const pw1 = half - ml - 20, ph = H - mt - mb;\n  \/\/ RIGHT: Frequency spectrum\n  const ml2 = half + 30;\n  const pw2 = W - ml2 - mr;\n  \n  ctx.fillStyle = '#ffffff';\n  ctx.fillRect(ml, mt, pw1, ph);\n  ctx.fillRect(ml2, mt, pw2, ph);\n  \n  \/\/ Grid\n  ctx.strokeStyle = 'rgba(100,116,139,0.35)';\n  ctx.lineWidth = 0.5;\n  for (let i = 0; i <= 5; i++) {\n    const y = mt + (ph * i \/ 5);\n    ctx.beginPath(); ctx.moveTo(ml, y); ctx.lineTo(ml + pw1, y); ctx.stroke();\n    const x = ml + (pw1 * i \/ 5);\n    ctx.beginPath(); ctx.moveTo(x, mt); ctx.lineTo(x, mt + ph); ctx.stroke();\n    ctx.beginPath(); ctx.moveTo(ml2, y); ctx.lineTo(ml2 + pw2, y); ctx.stroke();\n    const x2 = ml2 + (pw2 * i \/ 5);\n    ctx.beginPath(); ctx.moveTo(x2, mt); ctx.lineTo(x2, mt + ph); ctx.stroke();\n  }\n  \n  \/\/ Clip both plot areas\n  ctx.save();\n  ctx.beginPath();\n  ctx.rect(ml, mt, pw1, ph);\n  ctx.rect(ml2, mt, pw2, ph);\n  ctx.clip();\n  \n  \/\/ Draw all three for comparison \u2014 compute global max for Y scaling\n  const maxT = 8;\n  const N = 200;\n  let globalMaxForce = 0;\n  \n  Object.values(tipParams).forEach(params => {\n    const d = params.duration * (1 + (10 - stiff) * 0.3);\n    for (let i = 0; i <= N; i++) {\n      const t = (i \/ N) * maxT;\n      const amp = force * Math.exp(-t \/ d) * Math.sin(Math.PI * t \/ (d * 2));\n      if (amp > globalMaxForce) globalMaxForce = amp;\n    }\n  });\n  if (globalMaxForce < 1) globalMaxForce = 1;\n  const yCeil = globalMaxForce * 1.15; \/\/ 15% headroom\n  \n  Object.entries(tipParams).forEach(([key, params]) => {\n    const d = params.duration * (1 + (10 - stiff) * 0.3);\n    const alpha = key === tip ? 1 : 0.25;\n    ctx.strokeStyle = params.color;\n    ctx.globalAlpha = alpha;\n    ctx.lineWidth = key === tip ? 2.5 : 1.5;\n    ctx.beginPath();\n    \n    for (let i = 0; i <= N; i++) {\n      const t = (i \/ N) * maxT;\n      const amp = force * Math.exp(-t \/ d) * Math.sin(Math.PI * t \/ (d * 2));\n      const normAmp = Math.max(0, amp) \/ yCeil;\n      const x = ml + (t \/ maxT) * pw1;\n      const y = mt + ph - normAmp * ph;\n      if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);\n    }\n    ctx.stroke();\n    ctx.globalAlpha = 1;\n  });\n  \n  \/\/ Fill for active tip\n  ctx.beginPath();\n  ctx.moveTo(ml, mt + ph);\n  for (let i = 0; i <= N; i++) {\n    const t = (i \/ N) * maxT;\n    const amp = force * Math.exp(-t \/ dur) * Math.sin(Math.PI * t \/ (dur * 2));\n    const normAmp = Math.max(0, amp) \/ yCeil;\n    const x = ml + (t \/ maxT) * pw1;\n    const y = mt + ph - normAmp * ph;\n    ctx.lineTo(x, y);\n  }\n  ctx.lineTo(ml + pw1, mt + ph);\n  ctx.closePath();\n  const grad1 = ctx.createLinearGradient(0, mt, 0, mt + ph);\n  grad1.addColorStop(0, tp.color + '30');\n  grad1.addColorStop(1, tp.color + '05');\n  ctx.fillStyle = grad1;\n  ctx.fill();\n  \n  \/\/ Restore from clip\n  ctx.restore();\n  \n  \/\/ Left panel labels\n  ctx.fillStyle = '#475569';\n  ctx.font = '11px JetBrains Mono';\n  ctx.textAlign = 'center';\n  ctx.fillText('Time (ms)', ml + pw1\/2, H - 5);\n  ctx.fillText('PULSE SHAPE', ml + pw1\/2, mt - 10);\n  \n  \/\/ Left Y-axis ticks (force in N)\n  ctx.textAlign = 'right';\n  ctx.font = '9px JetBrains Mono';\n  const yTickCount = 4;\n  for (let i = 0; i <= yTickCount; i++) {\n    const val = (i \/ yTickCount) * yCeil;\n    const y = mt + ph - (i \/ yTickCount) * ph;\n    ctx.fillStyle = '#64748b';\n    ctx.fillText(Math.round(val), ml - 5, y + 3);\n    \/\/ small tick mark\n    ctx.strokeStyle = 'rgba(148,163,184,0.4)';\n    ctx.lineWidth = 0.5;\n    ctx.beginPath(); ctx.moveTo(ml, y); ctx.lineTo(ml + 4, y); ctx.stroke();\n  }\n  ctx.save();\n  ctx.translate(12, mt + ph\/2);\n  ctx.rotate(-Math.PI\/2);\n  ctx.textAlign = 'center';\n  ctx.fillStyle = '#475569';\n  ctx.font = '11px JetBrains Mono';\n  ctx.fillText('Force, N', 0, 0);\n  ctx.restore();\n  \n  \/\/ X-axis ticks for left panel (ms)\n  ctx.textAlign = 'center';\n  ctx.font = '9px JetBrains Mono';\n  ctx.fillStyle = '#64748b';\n  for (let i = 0; i <= 4; i++) {\n    const tVal = (i \/ 4) * maxT;\n    const x = ml + (i \/ 4) * pw1;\n    ctx.fillText(tVal.toFixed(1), x, mt + ph + 14);\n  }\n  \n  \/\/ Spectrum for all tips - clip to right panel\n  \/\/ Spectrum scales with force: |F(\u03c9)| = F_peak * pulse_spectrum_shape\n  ctx.save();\n  ctx.beginPath();\n  ctx.rect(ml2, mt, pw2, ph);\n  ctx.clip();\n  \n  const specMaxFreq = Math.max(500, Math.min(8000, fMax));\n  let globalMaxSpec = 0;\n  \n  \/\/ Pre-scan for max spectrum value\n  Object.values(tipParams).forEach(params => {\n    const d = params.duration * (1 + (10 - stiff) * 0.3);\n    for (let i = 0; i <= N; i++) {\n      const f = (i \/ N) * specMaxFreq;\n      const w = 2 * Math.PI * f;\n      const spectrum = force * (1 \/ Math.sqrt(1 + Math.pow(w * d \/ 1000 * 0.5, 2)));\n      if (spectrum > globalMaxSpec) globalMaxSpec = spectrum;\n    }\n  });\n  if (globalMaxSpec < 1) globalMaxSpec = 1;\n  const specCeil = globalMaxSpec * 1.1;\n  \n  Object.entries(tipParams).forEach(([key, params]) => {\n    const d = params.duration * (1 + (10 - stiff) * 0.3);\n    const alpha = key === tip ? 1 : 0.25;\n    ctx.strokeStyle = params.color;\n    ctx.globalAlpha = alpha;\n    ctx.lineWidth = key === tip ? 2.5 : 1.5;\n    ctx.beginPath();\n    \n    for (let i = 0; i <= N; i++) {\n      const f = (i \/ N) * specMaxFreq;\n      const w = 2 * Math.PI * f;\n      const spectrum = force * (1 \/ Math.sqrt(1 + Math.pow(w * d \/ 1000 * 0.5, 2)));\n      const x = ml2 + (f \/ specMaxFreq) * pw2;\n      const y = mt + ph - (spectrum \/ specCeil) * ph;\n      if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);\n    }\n    ctx.stroke();\n    ctx.globalAlpha = 1;\n  });\n  \n  \/\/ -3dB line\n  const dbLevel = specCeil \/ Math.sqrt(2);\n  ctx.strokeStyle = 'rgba(217,119,6,0.4)';\n  ctx.lineWidth = 1;\n  ctx.setLineDash([4, 4]);\n  const dbY = mt + ph - (dbLevel \/ specCeil) * ph;\n  ctx.beginPath();\n  ctx.moveTo(ml2, dbY);\n  ctx.lineTo(ml2 + pw2, dbY);\n  ctx.stroke();\n  ctx.setLineDash([]);\n  ctx.fillStyle = '#d97706';\n  ctx.font = '9px JetBrains Mono';\n  ctx.textAlign = 'right';\n  ctx.fillText('-3dB', ml2 + pw2 - 5, dbY - 5);\n  \n  \/\/ Restore clip\n  ctx.restore();\n  \n  \/\/ Right panel labels\n  ctx.fillStyle = '#475569';\n  ctx.font = '11px JetBrains Mono';\n  ctx.textAlign = 'center';\n  ctx.fillText('Frequency (Hz)', ml2 + pw2\/2, H - 5);\n  ctx.fillText('FREQUENCY SPECTRUM', ml2 + pw2\/2, mt - 10);\n  \n  \/\/ Right Y-axis ticks (N\u00b7s or relative)\n  ctx.textAlign = 'right';\n  ctx.font = '9px JetBrains Mono';\n  for (let i = 0; i <= yTickCount; i++) {\n    const val = (i \/ yTickCount) * specCeil;\n    const y = mt + ph - (i \/ yTickCount) * ph;\n    ctx.fillStyle = '#64748b';\n    ctx.fillText(Math.round(val), ml2 - 5, y + 3);\n  }\n  ctx.save();\n  ctx.translate(ml2 - 35, mt + ph\/2);\n  ctx.rotate(-Math.PI\/2);\n  ctx.textAlign = 'center';\n  ctx.fillStyle = '#475569';\n  ctx.font = '10px JetBrains Mono';\n  ctx.fillText('|F(\u03c9)|, N', 0, 0);\n  ctx.restore();\n  \n  \/\/ X-axis ticks for right panel (Hz)\n  ctx.textAlign = 'center';\n  ctx.font = '9px JetBrains Mono';\n  ctx.fillStyle = '#64748b';\n  for (let i = 0; i <= 4; i++) {\n    const fVal = (i \/ 4) * specMaxFreq;\n    const x = ml2 + (i \/ 4) * pw2;\n    ctx.fillText(Math.round(fVal), x, mt + ph + 14);\n  }\n  \n  \/\/ Plot borders\n  ctx.strokeStyle = 'rgba(148,163,184,0.5)';\n  ctx.lineWidth = 1;\n  ctx.strokeRect(ml, mt, pw1, ph);\n  ctx.strokeRect(ml2, mt, pw2, ph);\n  \n  \/\/ Legend\n  ctx.textAlign = 'left';\n  const legendY = mt + 18;\n  [['Steel', '#475569'], ['Plastic', '#d97706'], ['Rubber', '#10b981']].forEach(([name, col], i) => {\n    ctx.fillStyle = col;\n    ctx.fillRect(ml2 + 10, legendY + i * 18, 12, 3);\n    ctx.font = '10px JetBrains Mono';\n    ctx.fillText(name, ml2 + 28, legendY + i * 18 + 5);\n  });\n  \n  $('val-force').textContent = Math.round(force) + ' N';\n  $('val-tipstiff').textContent = stiff <= 3 ? 'Soft' : stiff <= 7 ? 'Medium' : 'Hard';\n}\n\n\/\/ =============================\n\/\/ AFCH RUNDOWN SIMULATOR\n\/\/ =============================\nfunction drawAFCH() {\n  const canvas = $('afchCanvas');\n  const { w: W, h: H } = resizeCanvas(canvas, 800, 480);\n  const ctx = canvas.getContext('2d');\n  ctx.clearRect(0, 0, W, H);\n  \n  const crit1 = parseFloat($('ctrl-crit1').value);\n  const crit2 = parseFloat($('ctrl-crit2').value);\n  const damp3 = parseFloat($('ctrl-damp3').value);\n  const damp4 = parseFloat($('ctrl-damp4').value);\n  const imb1 = parseFloat($('ctrl-imb1').value);\n  const imb2 = parseFloat($('ctrl-imb2').value);\n  const showBrg3 = $('chk-brg3').checked;\n  const showBrg4 = $('chk-brg4').checked;\n  const showPhase = $('chk-afch-phase').checked;\n  \n  const ml = 70, mr = showPhase ? 70 : 30, mt = 30, mb = 50;\n  const pw = W - ml - mr;\n  const ampH = showPhase ? (H - mt - mb) * 0.6 : (H - mt - mb);\n  const phaseH = showPhase ? (H - mt - mb) * 0.35 : 0;\n  const phaseTop = mt + ampH + (showPhase ? 20 : 0);\n  \n  \/\/ Background\n  ctx.fillStyle = '#ffffff';\n  ctx.fillRect(ml, mt, pw, ampH);\n  if (showPhase) ctx.fillRect(ml, phaseTop, pw, phaseH);\n  \n  \/\/ Grid\n  ctx.strokeStyle = 'rgba(100,116,139,0.35)';\n  ctx.lineWidth = 0.5;\n  for (let i = 0; i <= 10; i++) {\n    const x = ml + (pw * i \/ 10);\n    ctx.beginPath(); ctx.moveTo(x, mt); ctx.lineTo(x, mt + ampH); ctx.stroke();\n    if (showPhase) { ctx.beginPath(); ctx.moveTo(x, phaseTop); ctx.lineTo(x, phaseTop + phaseH); ctx.stroke(); }\n  }\n  for (let i = 0; i <= 5; i++) {\n    const y = mt + (ampH * i \/ 5);\n    ctx.beginPath(); ctx.moveTo(ml, y); ctx.lineTo(ml + pw, y); ctx.stroke();\n  }\n  \n  const maxRPM = 3600;\n  const N = 500;\n  \n  function computeAmp(rpm, critRPM, damp, imbalance, sensitivity) {\n    const r = rpm \/ critRPM;\n    const num = imbalance * r * r;\n    const denom = Math.sqrt(Math.pow(1 - r * r, 2) + Math.pow(2 * damp * r, 2));\n    return (num \/ (denom || 0.001)) * sensitivity;\n  }\n  \n  function computePhase(rpm, critRPM, damp) {\n    const r = rpm \/ critRPM;\n    return Math.atan2(2 * damp * r, 1 - r * r) * 180 \/ Math.PI;\n  }\n  \n  \/\/ Compute data for both bearings & both modes\n  let maxA = 0;\n  let brg3Data = [], brg4Data = [], brg3Phase = [], brg4Phase = [];\n  \n  for (let i = 0; i <= N; i++) {\n    const rpm = (i \/ N) * maxRPM;\n    \n    const a3_m1 = computeAmp(rpm, crit1, damp3, imb1, 0.8);\n    const a3_m2 = computeAmp(rpm, crit2, damp3, imb2, 0.5);\n    const a3 = a3_m1 + a3_m2;\n    \n    const a4_m1 = computeAmp(rpm, crit1, damp4, imb1, 0.6);\n    const a4_m2 = computeAmp(rpm, crit2, damp4, imb2, 0.7);\n    const a4 = a4_m1 + a4_m2;\n    \n    brg3Data.push(a3);\n    brg4Data.push(a4);\n    \n    const p3_1 = computePhase(rpm, crit1, damp3);\n    const p3_2 = computePhase(rpm, crit2, damp3);\n    brg3Phase.push((p3_1 + p3_2 * 0.5) \/ 1.5);\n    \n    const p4_1 = computePhase(rpm, crit1, damp4);\n    const p4_2 = computePhase(rpm, crit2, damp4);\n    brg4Phase.push((p4_1 + p4_2 * 0.5) \/ 1.5);\n    \n    if (showBrg3 && a3 > maxA) maxA = a3;\n    if (showBrg4 && a4 > maxA) maxA = a4;\n  }\n  \n  if (maxA < 1) maxA = 1;\n  const ampMax = maxA * 1.2;\n  \n  function toX(i) { return ml + (i \/ N) * pw; }\n  function toYA(a) { return Math.max(mt - 2, Math.min(mt + ampH + 2, mt + ampH - (a \/ ampMax) * ampH)); }\n  function toYP(deg) { return Math.max(phaseTop - 2, Math.min(phaseTop + phaseH + 2, phaseTop + phaseH - (deg \/ 180) * phaseH)); }\n  \n  \/\/ Clip to plot area\n  ctx.save();\n  ctx.beginPath();\n  ctx.rect(ml, mt, pw, ampH);\n  if (showPhase) ctx.rect(ml, phaseTop, pw, phaseH);\n  ctx.clip();\n  \n  \/\/ Draw bearing 3\n  if (showBrg3) {\n    \/\/ Fill\n    ctx.beginPath();\n    ctx.moveTo(toX(0), mt + ampH);\n    for (let i = 0; i <= N; i++) ctx.lineTo(toX(i), toYA(brg3Data[i]));\n    ctx.lineTo(toX(N), mt + ampH);\n    ctx.closePath();\n    const g = ctx.createLinearGradient(0, mt, 0, mt + ampH);\n    g.addColorStop(0, 'rgba(220,38,38,0.08)');\n    g.addColorStop(1, 'rgba(220,38,38,0.02)');\n    ctx.fillStyle = g;\n    ctx.fill();\n    \n    \/\/ Line\n    ctx.beginPath();\n    ctx.strokeStyle = '#ef4444';\n    ctx.lineWidth = 2.5;\n    for (let i = 0; i <= N; i++) {\n      if (i === 0) ctx.moveTo(toX(i), toYA(brg3Data[i]));\n      else ctx.lineTo(toX(i), toYA(brg3Data[i]));\n    }\n    ctx.stroke();\n    \n    \/\/ Dots every 20 points\n    ctx.fillStyle = '#ef4444';\n    for (let i = 0; i <= N; i += 12) {\n      ctx.beginPath();\n      ctx.arc(toX(i), toYA(brg3Data[i]), 2.5, 0, Math.PI * 2);\n      ctx.fill();\n    }\n    \n    \/\/ Phase\n    if (showPhase) {\n      ctx.beginPath();\n      ctx.strokeStyle = '#ef4444';\n      ctx.lineWidth = 1.5;\n      ctx.setLineDash([4, 3]);\n      for (let i = 0; i <= N; i++) {\n        if (i === 0) ctx.moveTo(toX(i), toYP(brg3Phase[i]));\n        else ctx.lineTo(toX(i), toYP(brg3Phase[i]));\n      }\n      ctx.stroke();\n      ctx.setLineDash([]);\n    }\n  }\n  \n  \/\/ Draw bearing 4\n  if (showBrg4) {\n    ctx.beginPath();\n    ctx.moveTo(toX(0), mt + ampH);\n    for (let i = 0; i <= N; i++) ctx.lineTo(toX(i), toYA(brg4Data[i]));\n    ctx.lineTo(toX(N), mt + ampH);\n    ctx.closePath();\n    const g = ctx.createLinearGradient(0, mt, 0, mt + ampH);\n    g.addColorStop(0, 'rgba(217,119,6,0.08)');\n    g.addColorStop(1, 'rgba(217,119,6,0.02)');\n    ctx.fillStyle = g;\n    ctx.fill();\n    \n    ctx.beginPath();\n    ctx.strokeStyle = '#d97706';\n    ctx.lineWidth = 2;\n    ctx.setLineDash([6, 3]);\n    for (let i = 0; i <= N; i++) {\n      if (i === 0) ctx.moveTo(toX(i), toYA(brg4Data[i]));\n      else ctx.lineTo(toX(i), toYA(brg4Data[i]));\n    }\n    ctx.stroke();\n    ctx.setLineDash([]);\n    \n    ctx.fillStyle = '#d97706';\n    for (let i = 6; i <= N; i += 12) {\n      ctx.beginPath();\n      ctx.arc(toX(i), toYA(brg4Data[i]), 2.5, 0, Math.PI * 2);\n      ctx.fill();\n    }\n    \n    if (showPhase) {\n      ctx.beginPath();\n      ctx.strokeStyle = '#d97706';\n      ctx.lineWidth = 1.5;\n      ctx.setLineDash([4, 3]);\n      for (let i = 0; i <= N; i++) {\n        if (i === 0) ctx.moveTo(toX(i), toYP(brg4Phase[i]));\n        else ctx.lineTo(toX(i), toYP(brg4Phase[i]));\n      }\n      ctx.stroke();\n      ctx.setLineDash([]);\n    }\n  }\n  \n  \/\/ Critical speed markers\n  [crit1, crit2].forEach((cr, idx) => {\n    const ci = Math.round((cr \/ maxRPM) * N);\n    if (ci > 0 && ci < N) {\n      ctx.strokeStyle = 'rgba(217,119,6,0.4)';\n      ctx.lineWidth = 1;\n      ctx.setLineDash([3, 3]);\n      ctx.beginPath(); ctx.moveTo(toX(ci), mt); ctx.lineTo(toX(ci), mt + ampH); ctx.stroke();\n      ctx.setLineDash([]);\n      ctx.fillStyle = '#d97706';\n      ctx.font = '10px JetBrains Mono';\n      ctx.textAlign = 'center';\n      ctx.fillText('n' + (idx + 1) + 'cr', toX(ci), mt + 14);\n    }\n  });\n  \n  \/\/ End clip region\n  ctx.restore();\n  \n  \/\/ Axes\n  ctx.fillStyle = '#475569';\n  ctx.font = '11px JetBrains Mono';\n  ctx.textAlign = 'center';\n  for (let i = 0; i <= 10; i++) {\n    const rpm = Math.round((i \/ 10) * maxRPM);\n    ctx.fillText(rpm, ml + (i \/ 10) * pw, mt + ampH + (showPhase ? 10 : 20));\n  }\n  ctx.fillText('RPM', ml + pw\/2, H - 5);\n  \n  ctx.textAlign = 'right';\n  for (let i = 0; i <= 5; i++) {\n    const val = ((5 - i) \/ 5 * ampMax).toFixed(0);\n    ctx.fillText(val, ml - 8, mt + (i \/ 5) * ampH + 4);\n  }\n  \n  ctx.save();\n  ctx.translate(15, mt + ampH\/2);\n  ctx.rotate(-Math.PI\/2);\n  ctx.textAlign = 'center';\n  ctx.fillText('Amplitude, \u03bcm', 0, 0);\n  ctx.restore();\n  \n  if (showPhase) {\n    ctx.textAlign = 'right';\n    ctx.fillStyle = '#64748b';\n    for (let i = 0; i <= 4; i++) {\n      const deg = i * 45;\n      ctx.fillText(deg + '\u00b0', ml - 8, phaseTop + phaseH - (deg \/ 180) * phaseH + 4);\n    }\n    ctx.save();\n    ctx.translate(15, phaseTop + phaseH\/2);\n    ctx.rotate(-Math.PI\/2);\n    ctx.textAlign = 'center';\n    ctx.fillText('Phase, \u00b0', 0, 0);\n    ctx.restore();\n  }\n  \n  \/\/ Legend\n  ctx.textAlign = 'left';\n  if (showBrg3) {\n    ctx.fillStyle = '#ef4444';\n    ctx.fillRect(ml + pw - 130, mt + 12, 16, 3);\n    ctx.font = '10px JetBrains Mono';\n    ctx.fillText('Bearing #3', ml + pw - 108, mt + 17);\n  }\n  if (showBrg4) {\n    ctx.fillStyle = '#d97706';\n    ctx.fillRect(ml + pw - 130, mt + 28, 16, 3);\n    ctx.font = '10px JetBrains Mono';\n    ctx.fillText('Bearing #4', ml + pw - 108, mt + 33);\n  }\n  \n  \/\/ Update labels\n  $('val-crit1').textContent = Math.round(crit1);\n  $('val-crit2').textContent = Math.round(crit2);\n  $('val-damp3').textContent = damp3.toFixed(3);\n  $('val-damp4').textContent = damp4.toFixed(3);\n  $('val-imb1').textContent = Math.round(imb1) + ' g\u00b7mm';\n  $('val-imb2').textContent = Math.round(imb2) + ' g\u00b7mm';\n}\n\n\/\/ =============================\n\/\/ EVENT LISTENERS\n\/\/ =============================\nconst allMainControls = ['ctrl-mass','ctrl-stiff','ctrl-damp','ctrl-imbal','ctrl-bearing','ctrl-support','ctrl-maxrpm'];\nallMainControls.forEach(id => {\n  const el = $(id);\n  if (!el) return;\n  el.addEventListener('input', () => { updateLabels(); drawResonance(); });\n});\n\n['chk-phase','chk-damped-freq','chk-bandwidth','chk-log','chk-multi'].forEach(id => {\n  const el = $(id);\n  if (!el) return;\n  el.addEventListener('change', drawResonance);\n});\n\n['hammer-tip','ctrl-force','ctrl-tipstiff'].forEach(id => {\n  const el = $(id);\n  if (!el) return;\n  el.addEventListener('input', drawImpact);\n  el.addEventListener('change', drawImpact);\n});\n\n['ctrl-crit1','ctrl-crit2','ctrl-damp3','ctrl-damp4','ctrl-imb1','ctrl-imb2'].forEach(id => {\n  const el = $(id);\n  if (!el) return;\n  el.addEventListener('input', drawAFCH);\n});\n['chk-brg3','chk-brg4','chk-afch-phase'].forEach(id => {\n  const el = $(id);\n  if (!el) return;\n  el.addEventListener('change', drawAFCH);\n});\n\n\/\/ Initial draw\nfunction drawAll() {\n  updateLabels();\n  drawResonance();\n  drawOverhung();\n  drawCardan();\n  drawImpact();\n  drawAFCH();\n}\n\nwindow.addEventListener('load', drawAll);\n\nlet resizeTimer;\nwindow.addEventListener('resize', () => {\n  clearTimeout(resizeTimer);\n  resizeTimer = setTimeout(drawAll, 80);\n});\n<\/script>\n<\/body>\n<\/html><\/div><\/div><\/div><\/div><\/div>","protected":false},"excerpt":{"rendered":"<p>Resonance in Rotor Dynamics \u2014 Interactive Guide Vibration Diagnostics Resonance of Machine Elements and Assemblies Considering the numerous requests to explain the diagnostics of resonance in machine elements, critical speeds, and natural mode shapes of the rotor, I decided to write several articles dedicated to these topics. In this first [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":6272,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ai_generated_summary":"","footnotes":""},"categories":[1],"tags":[],"class_list":["post-100645","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/vibromera.eu\/nb\/wp-json\/wp\/v2\/posts\/100645","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vibromera.eu\/nb\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vibromera.eu\/nb\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vibromera.eu\/nb\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/vibromera.eu\/nb\/wp-json\/wp\/v2\/comments?post=100645"}],"version-history":[{"count":10,"href":"https:\/\/vibromera.eu\/nb\/wp-json\/wp\/v2\/posts\/100645\/revisions"}],"predecessor-version":[{"id":100837,"href":"https:\/\/vibromera.eu\/nb\/wp-json\/wp\/v2\/posts\/100645\/revisions\/100837"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vibromera.eu\/nb\/wp-json\/wp\/v2\/media\/6272"}],"wp:attachment":[{"href":"https:\/\/vibromera.eu\/nb\/wp-json\/wp\/v2\/media?parent=100645"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vibromera.eu\/nb\/wp-json\/wp\/v2\/categories?post=100645"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vibromera.eu\/nb\/wp-json\/wp\/v2\/tags?post=100645"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}