{"id":4694,"date":"2026-02-24T21:32:45","date_gmt":"2026-02-25T01:32:45","guid":{"rendered":"https:\/\/faculty.fiu.edu\/~theobald\/?page_id=4694"},"modified":"2026-02-25T11:25:37","modified_gmt":"2026-02-25T15:25:37","slug":"goldman-2","status":"publish","type":"page","link":"https:\/\/faculty.fiu.edu\/~theobald\/fun\/neurobiology-fun\/goldman-2\/","title":{"rendered":"goldman"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"4694\" class=\"elementor elementor-4694\">\n\t\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-b3496c4 elementor-section-full_width elementor-section-stretched elementor-section-height-default elementor-section-height-default\" data-id=\"b3496c4\" data-element_type=\"section\" data-settings=\"{&quot;stretch_section&quot;:&quot;section-stretched&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-no\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-0782726\" data-id=\"0782726\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-f29fcc8 elementor-widget__width-inherit elementor-widget elementor-widget-html\" data-id=\"f29fcc8\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<!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>Nernst and Goldman (Interactive)<\/title>\n\n  \n  <!-- MathJax for equations -->\n  <script defer src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/mathjax\/3.2.0\/es5\/tex-mml-chtml.js\"><\/script>\n\n<style>\n  :root{\n    --bg: #0f1115;\n    --panel: #141823;\n    --panel2: #10131a;\n    --fg: #e6e8ee;\n    --muted: rgba(230,232,238,0.65);\n    --grid: rgba(230,232,238,0.10);\n\n    --na: #909000;\n    --k:  #8e60e6;\n    --cl: #169000;\n\n    --accent: rgba(230,232,238,0.12);\n    --ring: rgba(230,232,238,0.25);\n    --danger: rgba(255,120,120,0.85);\n\n    --track-h: 4px;\n    --thumb: 16px;\n    --dual-sep: 28px; \/* vertical separation between the two tracks *\/\n  }\n\n  \/* Elementor: allow breakout *\/\n  selector{ overflow: visible !important; }\n  selector .elementor-container,\n  selector .elementor-column,\n  selector .elementor-widget-wrap{\n    overflow: visible !important;\n    max-width: none !important;\n  }\n\n  .ng-fullbleed, .ng-fullbleed *{ box-sizing: border-box; }\n\n  \/* Full-bleed breakout *\/\n  .ng-fullbleed{\n    position: relative;\n    width: 100vw;\n    margin-left: calc(50% - 50vw);\n    margin-right: calc(50% - 50vw);\n    overflow-x: hidden;\n\n    background: var(--bg);\n    color: var(--fg);\n    font: 14px\/1.35 system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, \"Helvetica Neue\", Arial, \"Noto Sans\", \"Liberation Sans\", sans-serif;\n\n    padding: 0;\n    border: 0;\n    max-width: 100vw;\n  }\n\n  .ng-fullbleed .app{\n    height: 88vh;\n    overflow: hidden;\n\n    display: grid;\n    grid-template-columns: 340px minmax(0, 1fr);\n    gap: 12px;\n\n    padding: 12px;\n    padding-left: max(12px, env(safe-area-inset-left));\n    padding-right: max(12px, env(safe-area-inset-right));\n  }\n\n  .ng-fullbleed .left{\n    background: var(--panel);\n    border: 1px solid var(--accent);\n    border-radius: 14px;\n    padding: 12px;\n    overflow: auto;\n    height: 100%;\n  }\n\n  .ng-fullbleed .right{\n    background: var(--panel2);\n    border: 1px solid var(--accent);\n    border-radius: 14px;\n    padding: 10px;\n\n    display: grid;\n    grid-template-rows: auto 1fr;\n    gap: 8px;\n\n    overflow: hidden;\n    height: 100%;\n    min-height: 0;\n  }\n\n  .ng-fullbleed h1{\n    margin: 0 0 10px 0;\n    font-size: 18px;\n    font-weight: 650;\n    letter-spacing: 0.2px;\n  }\n\n  .ng-fullbleed .eq{\n    margin: 0 0 10px 0;\n    color: var(--muted);\n    font-size: 13px;\n    overflow-x: auto; \/* long MathJax can scroll locally *\/\n  }\n\n  .ng-fullbleed .group{\n    border: 1px solid var(--accent);\n    border-radius: 12px;\n    padding: 10px;\n    margin: 10px 0;\n    background: rgba(255,255,255,0.02);\n  }\n\n  .ng-fullbleed .group-title{\n    display: flex;\n    align-items: baseline;\n    justify-content: space-between;\n    gap: 10px;\n\n    margin: 0 0 6px 0;\n    font-size: 12px;\n    text-transform: uppercase;\n    letter-spacing: 0.08em;\n    color: rgba(230,232,238,0.90);\n  }\n\n  .ng-fullbleed .row{\n    display:flex;\n    align-items:center;\n    justify-content: space-between;\n    gap: 10px;\n    margin-top: 8px;\n  }\n\n  .ng-fullbleed .row label{\n    color: rgba(230,232,238,0.88);\n    font-size: 12px;\n    user-select: none;\n    white-space: nowrap;\n    display:flex;\n    gap: 8px;\n    align-items:center;\n  }\n\n  .ng-fullbleed .ion-na{ color: var(--na); }\n  .ng-fullbleed .ion-k{ color: var(--k); }\n  .ng-fullbleed .ion-cl{ color: var(--cl); }\n\n  .ng-fullbleed .ctl{\n    display: grid;\n    grid-template-columns: 1fr auto auto;\n    gap: 8px;\n    align-items: center;\n    margin: 8px 0 0 0;\n  }\n\n  \/* make the whole label (including brackets\/out\/in) bright *\/\n  .ng-fullbleed .ctl > label{\n    color: rgba(230,232,238,0.90);\n    font-size: 12px;\n  }\n  .ng-fullbleed .ctl > label *{ color: inherit; }\n\n  .ng-fullbleed .ctl .vals{\n    display:flex;\n    gap: 10px;\n    justify-content:flex-end;\n    font-variant-numeric: tabular-nums;\n    user-select: none;\n    opacity: 1;\n  }\n\n  .ng-fullbleed .pill{\n    padding: 2px 7px;\n    border-radius: 10px;\n    border: 1px solid var(--accent);\n    background: rgba(255,255,255,0.03);\n    min-width: 54px;\n    text-align: right;\n    color: rgba(230,232,238,0.93);\n    font-weight: 520;\n  }\n\n  \/* -----------------------------\n     Range input base styling\n     ----------------------------- *\/\n  .ng-fullbleed input[type=\"range\"]{\n    width: 100%;\n    margin: 0;\n    accent-color: var(--fg);\n  }\n\n  \/* WebKit (Chrome\/Safari\/Edge) *\/\n  .ng-fullbleed input[type=\"range\"]::-webkit-slider-runnable-track{\n    height: var(--track-h);\n    background: rgba(230,232,238,0.12);\n    border-radius: 999px;\n  }\n  .ng-fullbleed input[type=\"range\"]::-webkit-slider-thumb{\n    -webkit-appearance: none;\n    appearance: none;\n    width: var(--thumb);\n    height: var(--thumb);\n    margin-top: calc((var(--track-h) - var(--thumb)) \/ 2);\n    border-radius: 50%;\n    background: rgba(230,232,238,0.95);\n    border: 1px solid rgba(0,0,0,0.35);\n    cursor: grab;\n  }\n\n  \/* Firefox *\/\n  .ng-fullbleed input[type=\"range\"]::-moz-range-track{\n    height: var(--track-h);\n    background: rgba(230,232,238,0.12);\n    border-radius: 999px;\n  }\n  .ng-fullbleed input[type=\"range\"]::-moz-range-thumb{\n    width: var(--thumb);\n    height: var(--thumb);\n    border-radius: 50%;\n    background: rgba(230,232,238,0.95);\n    border: 1px solid rgba(0,0,0,0.35);\n    cursor: grab;\n  }\n  \/* IMPORTANT: do NOT set global ::-moz-range-progress here (that broke your permeability sliders) *\/\n\n  \/* -----------------------------\n     Dual sliders (ion concentrations)\n     ----------------------------- *\/\n  .ng-fullbleed .dual{\n    grid-column: 1 \/ span 3;\n    position: relative;\n    height: calc(var(--thumb) + var(--dual-sep) + var(--thumb));\n    margin-top: 4px;\n    margin-bottom: 14px;\n  }\n\n  .ng-fullbleed .dual .centerline{\n    position:absolute;\n    left:0; right:0;\n    top: 50%;\n    height: 1px;\n    background: rgba(230,232,238,0.08);\n    transform: translateY(-50%);\n    pointer-events:none;\n  }\n\n  .ng-fullbleed .dual input[type=\"range\"]{\n    position:absolute;\n    left:0;\n    width:100%;\n    height: var(--thumb);\n    background: transparent;\n    pointer-events:none; \/* thumbs re-enabled below *\/\n  }\n\n  .ng-fullbleed .dual input.dual-a{ top: 0; }\n  .ng-fullbleed .dual input.dual-b{ top: calc(var(--thumb) + var(--dual-sep)); }\n\n  .ng-fullbleed .dual input[type=\"range\"]::-webkit-slider-thumb{ pointer-events:auto; }\n  .ng-fullbleed .dual input[type=\"range\"]::-moz-range-thumb{ pointer-events:auto; }\n\n  \/* Ion-specific colors *\/\n  .ng-fullbleed #na_dual input[type=\"range\"]{ accent-color: var(--na); }\n  .ng-fullbleed #k_dual  input[type=\"range\"]{ accent-color: var(--k); }\n  .ng-fullbleed #cl_dual input[type=\"range\"]{ accent-color: var(--cl); }\n\n  \/* Firefox fill ONLY for ion dual sliders *\/\n  .ng-fullbleed #na_dual input[type=\"range\"]::-moz-range-progress{\n    height: var(--track-h);\n    border-radius: 999px;\n    background: color-mix(in srgb, var(--na) 55%, transparent);\n  }\n  .ng-fullbleed #k_dual input[type=\"range\"]::-moz-range-progress{\n    height: var(--track-h);\n    border-radius: 999px;\n    background: color-mix(in srgb, var(--k) 55%, transparent);\n  }\n  .ng-fullbleed #cl_dual input[type=\"range\"]::-moz-range-progress{\n    height: var(--track-h);\n    border-radius: 999px;\n    background: color-mix(in srgb, var(--cl) 55%, transparent);\n  }\n\n  \/* Small spacing polish: keep title close to its block *\/\n  .ng-fullbleed .group > .ctl:first-of-type{ margin-top: 2px; }\n\n  .ng-fullbleed .radios{\n    display: grid;\n    gap: 6px;\n    margin-top: 6px;\n    user-select: none;\n    color: var(--muted);\n    font-size: 12px;\n  }\n\n  .ng-fullbleed .radios label{\n    display: flex;\n    gap: 8px;\n    align-items: center;\n    margin: 0;\n    cursor: pointer;\n  }\n\n  .ng-fullbleed .hint{\n    font-size: 12px;\n    color: var(--muted);\n    margin-top: 8px;\n  }\n\n  .ng-fullbleed .btn{\n    appearance: none;\n    border: 1px solid var(--accent);\n    background: rgba(255,255,255,0.03);\n    color: var(--fg);\n    padding: 8px 10px;\n    border-radius: 10px;\n    cursor: pointer;\n    font-size: 12px;\n    letter-spacing: 0.02em;\n  }\n  .ng-fullbleed .btn:hover{ background: rgba(255,255,255,0.06); }\n  .ng-fullbleed .btn:active{ transform: translateY(1px); }\n\n  .ng-fullbleed .canvasWrap{\n    position: relative;\n    width: 100%;\n    height: 100%;\n    border-radius: 12px;\n    overflow: hidden;\n    background: #0b0d12;\n    border: 1px solid var(--accent);\n    min-height: 0;\n  }\n\n  .ng-fullbleed canvas{\n    display: block;\n    width: 100%;\n    height: 100%;\n  }\n\n  html, body{ overflow-x: hidden; }\n\n\/* ---- Firefox: force custom range appearance (fix thick white sliders) ---- *\/\n@-moz-document url-prefix() {\n  .ng-fullbleed input[type=\"range\"]{\n    -moz-appearance: none !important;\n    appearance: none !important;\n    background: transparent !important;\n    border: 0 !important;\n    height: var(--thumb) !important; \/* prevents the \u201cgiant white pill\u201d *\/\n  }\n\n  \/* Default (single sliders: temp + permeabilities) *\/\n  .ng-fullbleed input[type=\"range\"]::-moz-range-track{\n    height: var(--track-h) !important;\n    border-radius: 999px !important;\n    background: rgba(230,232,238,0.12) !important;\n    border: 0 !important;\n  }\n  .ng-fullbleed input[type=\"range\"]::-moz-range-progress{\n    height: var(--track-h) !important;\n    border-radius: 999px !important;\n    background: rgba(230,232,238,0.35) !important; \/* subtle filled portion *\/\n    border: 0 !important;\n  }\n\n  .ng-fullbleed input[type=\"range\"]::-moz-range-thumb{\n    width: var(--thumb) !important;\n    height: var(--thumb) !important;\n    border-radius: 50% !important;\n    background: rgba(230,232,238,0.95) !important;\n    border: 1px solid rgba(0,0,0,0.35) !important;\n  }\n\n  \/* Keep ion dual sliders colored (override the default progress fill) *\/\n  .ng-fullbleed #na_dual input[type=\"range\"]::-moz-range-progress{\n    background: color-mix(in srgb, var(--na) 55%, transparent) !important;\n  }\n  .ng-fullbleed #k_dual input[type=\"range\"]::-moz-range-progress{\n    background: color-mix(in srgb, var(--k) 55%, transparent) !important;\n  }\n  .ng-fullbleed #cl_dual input[type=\"range\"]::-moz-range-progress{\n    background: color-mix(in srgb, var(--cl) 55%, transparent) !important;\n  }\n}\n\n\/* Let ion-colored spans override label color *\/\n.ng-fullbleed .row label .ion-na,\n.ng-fullbleed .row label .ion-k,\n.ng-fullbleed .row label .ion-cl,\n.ng-fullbleed .ctl label .ion-na,\n.ng-fullbleed .ctl label .ion-k,\n.ng-fullbleed .ctl label .ion-cl{\n  color: inherit; \/* fallback if you want *\/\n}\n\n\/* Force the intended ion colors (wins over label {color: var(--fg)} rules) *\/\n.ng-fullbleed .ion-na{ color: var(--na) !important; }\n.ng-fullbleed .ion-k { color: var(--k)  !important; }\n.ng-fullbleed .ion-cl{ color: var(--cl) !important; }\n  \n<\/style>\n\n<\/head>\n\n<body>\n<div class=\"ng-fullbleed\">\n  \n<div class=\"app\">\n  <aside class=\"left\">\n    <h1>Nernst and Goldman<\/h1>\n\n<p class=\"eq\">\n  $$E_r = \\frac{RT}{zF}\\ln\\left(\\frac{[\\text{Ion}]_\\text{out}}{[\\text{Ion}]_\\text{in}}\\right)$$\n<\/p>\n\n    <p class=\"eq\">\n      $$E_m = \\frac{RT}{F}\\ln\\left(\\frac{\n      P_{\\text{K}^+}[\\text{K}^+]_\\text{out} +\n      P_{\\text{Na}^+}[\\text{Na}^+]_\\text{out} +\n      P_{\\text{Cl}^-}[\\text{Cl}^-]_\\text{in}}\n      {P_{\\text{K}^+}[\\text{K}^+]_\\text{in} +\n      P_{\\text{Na}^+}[\\text{Na}^+]_\\text{in} +\n      P_{\\text{Cl}^-}[\\text{Cl}^-]_\\text{out}}\\right)$$\n    <\/p>\n    <!-- <p class=\"eq\"> -->\n    <!--   $$E_r = \\frac{RT}{zF}\\ln\\left(\\frac{[\\text{Ion}]_\\text{out}}{[\\text{Ion}]_\\text{in}}\\right)\\qquad -->\n    <!--   E_m = \\frac{RT}{F}\\ln\\left(\\frac{P_{\\text{K}^+}[\\text{K}^+]_\\text{out} + P_{\\text{Na}^+}[\\text{Na}^+]_\\text{out} + P_{\\text{Cl}^-}[\\text{Cl}^-]_\\text{in}} -->\n    <!--   {P_{\\text{K}^+}[\\text{K}^+]_\\text{in} + P_{\\text{Na}^+}[\\text{Na}^+]_\\text{in} + P_{\\text{Cl}^-}[\\text{Cl}^-]_\\text{out}}\\right)$$ -->\n    <!-- <\/p> -->\n\n    <div class=\"group\">\n      <div class=\"group-title\">Top controls<\/div>\n\n      <div class=\"ctl\">\n        <label for=\"temp_slider\">Temperature (K)<\/label>\n        <div class=\"vals\" style=\"grid-column: 2 \/ span 2;\">\n          <span class=\"pill\" id=\"temp_slider_display\">310<\/span>\n        <\/div>\n        <input type=\"range\" min=\"0\" max=\"350\" value=\"310\" step=\"1\" id=\"temp_slider\" style=\"grid-column: 1 \/ span 3;\">\n      <\/div>\n\n      <div class=\"row\">\n        <div style=\"display:flex; gap:14px; align-items:center; flex-wrap:wrap;\">\n          <label><input id=\"colorize\" type=\"checkbox\" checked> Voltage coloration<\/label>\n          <label><input id=\"scope_guides\" type=\"checkbox\" checked> Scope indicators<\/label>\n        <\/div>\n        <div style=\"display:flex; gap:8px; align-items:center;\">\n          <span class=\"pill\" id=\"vm_readout\">-65.0 mV<\/span>\n        <\/div>\n      <\/div>\n    <\/div>\n\n    <div class=\"group\">\n      <div class=\"group-title\">Mode<\/div>\n      <div class=\"radios\">\n        <label><input type=\"radio\" name=\"mode\" value=\"goldman\" checked> Goldman<\/label>\n        <label><input type=\"radio\" name=\"mode\" value=\"nanernst\"> Nernst <span class=\"ion-na\">Na<sup>+<\/sup><\/span><\/label>\n        <label><input type=\"radio\" name=\"mode\" value=\"knernst\"> Nernst <span class=\"ion-k\">K<sup>+<\/sup><\/span><\/label>\n        <label><input type=\"radio\" name=\"mode\" value=\"clnernst\"> Nernst <span class=\"ion-cl\">Cl<sup>-<\/sup><\/span><\/label>\n      <\/div>\n    <\/div>\n\n    <div class=\"group\">\n      <div class=\"group-title\">Concentrations<\/div>\n\n      <!-- Sodium dual -->\n      <div class=\"ctl\">\n        <label>[<span class=\"ion-na\">Na<sup>+<\/sup><\/span>] out \/ in<\/label>\n        <div class=\"vals\">\n          <span class=\"pill\" id=\"na_out_display\">440<\/span> \/\n          <span class=\"pill\" id=\"na_in_display\">50<\/span>\n        <\/div>\n        <div class=\"dual\" id=\"na_dual\">\n          <div class=\"centerline\"><\/div>\n          <input class=\"dual-a\" type=\"range\" min=\"0\" max=\"1000\" value=\"440\" step=\"1\" id=\"na_out\">\n          <input class=\"dual-b\" type=\"range\" min=\"0\" max=\"1000\" value=\"50\" step=\"1\" id=\"na_in\">\n        <\/div>\n      <\/div>\n\n      <!-- Potassium dual -->\n      <div class=\"ctl\">\n        <label>[<span class=\"ion-k\">K<sup>+<\/sup><\/span>] out \/ in<\/label>\n        <div class=\"vals\">\n          <span class=\"pill\" id=\"k_out_display\">20<\/span> \/\n          <span class=\"pill\" id=\"k_in_display\">400<\/span>\n        <\/div>\n        <div class=\"dual\" id=\"k_dual\">\n          <div class=\"centerline\"><\/div>\n          <input class=\"dual-a\" type=\"range\" min=\"0\" max=\"1000\" value=\"20\" step=\"1\" id=\"k_out\">\n          <input class=\"dual-b\" type=\"range\" min=\"0\" max=\"1000\" value=\"400\" step=\"1\" id=\"k_in\">\n        <\/div>\n      <\/div>\n\n      <!-- Chloride dual -->\n      <div class=\"ctl\">\n        <label>[<span class=\"ion-cl\">Cl<sup>-<\/sup><\/span>] out \/ in<\/label>\n        <div class=\"vals\">\n          <span class=\"pill\" id=\"cl_out_display\">560<\/span> \/\n          <span class=\"pill\" id=\"cl_in_display\">52<\/span>\n        <\/div>\n        <div class=\"dual\" id=\"cl_dual\">\n          <div class=\"centerline\"><\/div>\n          <input class=\"dual-a\" type=\"range\" min=\"0\" max=\"1000\" value=\"560\" step=\"1\" id=\"cl_out\">\n          <input class=\"dual-b\" type=\"range\" min=\"0\" max=\"1000\" value=\"52\" step=\"1\" id=\"cl_in\">\n        <\/div>\n      <\/div>\n\n      <div class=\"hint\">Each ion uses one \u201cmembrane\u201d control: top thumb = outside, bottom thumb = inside.<\/div>\n    <\/div>\n\n    <div class=\"group\">\n      <div class=\"group-title\">Permeabilities<\/div>\n\n      <div class=\"ctl\">\n        <label for=\"nap_slider\">P<sub><span class=\"ion-na\">Na<\/span><\/sub> (log10)<\/label>\n        <div class=\"vals\" style=\"grid-column: 2 \/ span 2;\">\n          <span class=\"pill\" id=\"nap_slider_display\">10<\/span>\n        <\/div>\n        <input type=\"range\" min=\"-1\" max=\"4\" value=\"1.5\" step=\"0.1\" id=\"nap_slider\" style=\"grid-column: 1 \/ span 3;\">\n      <\/div>\n\n      <div class=\"ctl\">\n        <label for=\"kp_slider\">P<sub><span class=\"ion-k\">K<\/span><\/sub> (log10)<\/label>\n        <div class=\"vals\" style=\"grid-column: 2 \/ span 2;\">\n          <span class=\"pill\" id=\"kp_slider_display\">500<\/span>\n        <\/div>\n        <input type=\"range\" min=\"-1\" max=\"4\" value=\"3\" step=\"0.1\" id=\"kp_slider\" style=\"grid-column: 1 \/ span 3;\">\n      <\/div>\n\n      <div class=\"ctl\">\n        <label for=\"clp_slider\">P<sub><span class=\"ion-cl\">Cl<\/span><\/sub> (log10)<\/label>\n        <div class=\"vals\" style=\"grid-column: 2 \/ span 2;\">\n          <span class=\"pill\" id=\"clp_slider_display\">10<\/span>\n        <\/div>\n        <input type=\"range\" min=\"-1\" max=\"4\" value=\"2.5\" step=\"0.1\" id=\"clp_slider\" style=\"grid-column: 1 \/ span 3;\">\n      <\/div>\n\n      <div class=\"hint\">\n        In Goldman mode, permeabilities control the membrane potential. In Nernst modes,\n        permeabilities are ignored (but still visualized as channels).\n      <\/div>\n    <\/div>\n\n    <div class=\"group\">\n      <div class=\"group-title\">Reset<\/div>\n      <button class=\"btn\" id=\"reset_btn\" type=\"button\">Reset values<\/button>\n      <div class=\"hint\">Resets concentrations, permeabilities, temperature, and the scope trace.<\/div>\n    <\/div>\n  <\/aside>\n\n  <main class=\"right\">\n    <div class=\"hint\">Top: cell + ions + channels. Bottom: oscilloscope trace.<\/div>\n    <div class=\"canvasWrap\">\n      <canvas id=\"canvas_main\"><\/canvas>\n    <\/div>\n  <\/main>\n<\/div>\n\n<script>\n\/* ---------------------------\n   Shared utilities\n---------------------------- *\/\nfunction safeExp(x){\n  if (x > 50) x = 50;\n  if (x < -50) x = -50;\n  return Math.exp(x);\n}\nfunction dlog(x, ymin, ymid, ymax, xmid1, xmid2, k1, k2){\n  const s1 = 1.0 \/ (1.0 + safeExp(-k1 * (x - xmid1)));\n  const s2 = 1.0 \/ (1.0 + safeExp(-k2 * (x - xmid2)));\n  return ymin + (ymid - ymin) * s1 + (ymax - ymid) * s2;\n}\n\/\/ v in Volts\nfunction vcol(v){\n  if (!Number.isFinite(v)) v = -0.065;\n  v = Math.max(-0.12, Math.min(0.06, v));\n  const r = dlog(v, 0.0, 0.5, 1.0, -0.085, -0.045, 210, 250);\n  const g = dlog(v, 0.0, 0.5, 1.0, -0.085, -0.010, 210, 160);\n  const b = dlog(v, 1.0, 0.5, 1.0, -0.075,  0.030, 500, 160);\n  return {\n    r: Math.max(0, Math.min(255, Math.round((Number.isFinite(r) ? r : 0.5) * 255))),\n    g: Math.max(0, Math.min(255, Math.round((Number.isFinite(g) ? g : 0.5) * 255))),\n    b: Math.max(0, Math.min(255, Math.round((Number.isFinite(b) ? b : 0.5) * 255))),\n  };\n}\nfunction clamp(v, lo, hi){ return Math.min(Math.max(v, lo), hi); }\nfunction logSafe(x){ return Math.log(Math.max(x, 1e-12)); }\n\n\/* ---------------------------\n   Oscilloscope\n---------------------------- *\/\nclass RingBuffer {\n  constructor(n, fillValue=0){\n    this.n = n;\n    this.a = new Float32Array(n);\n    this.i = 0;\n    this.full = false;\n    this.fill(fillValue);\n  }\n  fill(v){\n    this.a.fill(v);\n    this.i = 0;\n    this.full = true;\n  }\n  push(v){\n    this.a[this.i] = v;\n    this.i = (this.i + 1) % this.n;\n    if (this.i === 0) this.full = true;\n  }\n  toArray(){\n    if (!this.full) return Array.from(this.a.slice(0, this.i));\n    const out = new Float32Array(this.n);\n    out.set(this.a.slice(this.i), 0);\n    out.set(this.a.slice(0, this.i), this.n - this.i);\n    return out;\n  }\n}\n\n\/* ---------------------------\n   Model\n---------------------------- *\/\nconst COLORS = {\n  na: getComputedStyle(document.documentElement).getPropertyValue(\"--na\").trim() || \"#909000\",\n  k:  getComputedStyle(document.documentElement).getPropertyValue(\"--k\").trim()  || \"#8e60e6\",\n  cl: getComputedStyle(document.documentElement).getPropertyValue(\"--cl\").trim() || \"#169000\",\n  fg: getComputedStyle(document.documentElement).getPropertyValue(\"--fg\").trim() || \"#e6e8ee\",\n};\n\nclass CellModel {\n  constructor(){\n    this.resetDefaults();\n    this.R = 8.31;\n    this.F = 96485.3;\n    this.numIons = 900;\n    this.ions = [];\n  }\n\n  resetDefaults(){\n    \/\/ Concentrations\n    this.nao = 440; this.nai = 50;\n    this.ko  = 20;  this.ki  = 400;\n    this.clo = 560; this.cli = 52;\n\n    \/\/ Permeabilities (linear)\n    this.nap = 32;\n    this.kp  = 1000;\n    this.clp = 316;\n\n    \/\/ Conditions\n    this.temp = 310;\n    this.mode = \"goldman\";\n    this.v = -65; \/\/ mV\n  }\n\n  nernst(ion){\n    const T = this.temp;\n    if (ion === \"K\")  return (this.R*T\/this.F) * logSafe(this.ko\/this.ki) * 1000;\n    if (ion === \"Na\") return (this.R*T\/this.F) * logSafe(this.nao\/this.nai) * 1000;\n    if (ion === \"Cl\") return (this.R*T\/this.F) * logSafe(this.cli\/this.clo) * 1000; \/\/ z = -1\n    return 0;\n  }\n\n  goldman(){\n    const T = this.temp;\n    const num = (this.kp*this.ko + this.nap*this.nao + this.clp*this.cli);\n    const den = (this.kp*this.ki + this.nap*this.nai + this.clp*this.clo);\n    if (den <= 0 || num <= 0) return 0;\n    return (this.R*T\/this.F) * logSafe(num\/den) * 1000;\n  }\n\n  targetVm(){\n    if (this.mode === \"goldman\") return this.goldman();\n    if (this.mode === \"nanernst\") return this.nernst(\"Na\");\n    if (this.mode === \"knernst\")  return this.nernst(\"K\");\n    if (this.mode === \"clnernst\") return this.nernst(\"Cl\");\n    return this.goldman();\n  }\n\n  step(dt){\n    const target = this.targetVm();\n    \/\/ relax toward target (a little smoother than instantaneous)\n    this.v += 0.25 * (target - this.v);\n  }\n}\n\nclass CellView {\n  constructor(model){\n    this.m = model;\n    this.viewport = {x:0, y:0, w:100, h:100};\n    this.cx = 0; this.cy = 0; this.r = 10;\n  }\n\n  setViewport(x,y,w,h){\n    this.viewport = {x,y,w,h};\n    const pad = Math.max(10, Math.min(18, h*0.06));\n    this.cx = x + w*0.50;\n    this.r  = Math.max(12, Math.min(w*0.32, (h - pad)*0.85));\n    \/\/ this.r  = Math.max(12, Math.min(w*0.32, (h - pad)*0.85));\n    \/\/ this.cy = y + h - pad;\n    this.r = Math.min(w*0.32, h*0.85);\n    this.cy = y + h;\n  }\n\n  initIons(){\n    const m = this.m;\n    m.ions.length = 0;\n    const {x,y,w,h} = this.viewport;\n\n    const pickKind = (i, naCount, kCount) => {\n      if (i < naCount) return \"na\";\n      if (i < naCount + kCount) return \"k\";\n      return \"cl\";\n    };\n\n    const totalOut = m.nao + m.ko + m.clo;\n    const totalIn  = m.nai + m.ki + m.cli;\n\n    const outCount = Math.floor(m.numIons * 0.55);\n    const inCount  = m.numIons - outCount;\n\n    const naOutN = Math.floor(outCount * (m.nao\/Math.max(totalOut,1)));\n    const kOutN  = Math.floor(outCount * (m.ko\/Math.max(totalOut,1)));\n\n    const naInN = Math.floor(inCount * (m.nai\/Math.max(totalIn,1)));\n    const kInN  = Math.floor(inCount * (m.ki\/Math.max(totalIn,1)));\n\n    const insideCell = (px, py) => {\n      const dx = px - this.cx;\n      const dy = py - this.cy;\n      return (dx*dx + dy*dy < this.r*this.r) && (py <= this.cy);\n    };\n\n    const insidePoint = () => {\n      let px, py;\n      for (let tries=0; tries<700; tries++){\n        px = x + Math.random()*w;\n        py = y + Math.random()*h;\n        if (insideCell(px, py)) return [px, py];\n      }\n      return [this.cx, this.cy - this.r*0.5];\n    };\n\n    const outsidePoint = () => {\n      let px, py;\n      for (let tries=0; tries<700; tries++){\n        px = x + Math.random()*w;\n        py = y + Math.random()*h;\n        if (!insideCell(px, py) || py > this.cy) return [px, py];\n      }\n      return [x + w*0.8, y + h*0.5];\n    };\n\n    \/\/ Brownian-ish motion state\n    const mk = (px,py,inside,kind)=>({\n      x:px,y:py,\n      vx:(Math.random()-0.5)*110,\n      vy:(Math.random()-0.5)*110,\n      inside,kind\n    });\n\n    for (let i=0; i<outCount; i++){\n      const [px, py] = outsidePoint();\n      m.ions.push(mk(px,py,false, pickKind(i, naOutN, kOutN)));\n    }\n    for (let i=0; i<inCount; i++){\n      const [px, py] = insidePoint();\n      m.ions.push(mk(px,py,true, pickKind(i, naInN, kInN)));\n    }\n  }\n\n  stepIons(dt){\n    const m = this.m;\n    const {x,y,w,h} = this.viewport;\n\n    const insideCell = (px, py) => {\n      const dx = px - this.cx;\n      const dy = py - this.cy;\n      return (dx*dx + dy*dy < this.r*this.r) && (py <= this.cy);\n    };\n\n      \/\/ Thermal scaling (Brownian motion ~ sqrt(T))\n      const T = Math.max(this.m.temp, 0);     \/\/ avoid negative\n      const Tscale = Math.sqrt(T \/ 310);      \/\/ normalize to physiological temp\n\n      const acc  = 3600 * Tscale;\n      const drag = 0.96 + 0.04 * Tscale;      \/\/ slightly smoother at higher T\n      const vmax = 2400 * Tscale;\n\n\n    for (const p of m.ions){\n      \/\/ random acceleration (white noise)\n      p.vx = (p.vx * drag) + (Math.random()-0.5) * acc * dt;\n      p.vy = (p.vy * drag) + (Math.random()-0.5) * acc * dt;\n\n      \/\/ clamp speed\n      p.vx = clamp(p.vx, -vmax, vmax);\n      p.vy = clamp(p.vy, -vmax, vmax);\n\n      let nx = p.x + p.vx*dt;\n      let ny = p.y + p.vy*dt;\n\n      if (p.inside){\n        if (insideCell(nx, ny)) { p.x = nx; p.y = ny; }\n        else {\n          \/\/ reflect off membrane boundary\n          p.vx *= -0.6; p.vy *= -0.6;\n          p.x = clamp(p.x, x+1, x+w-2);\n          p.y = clamp(p.y, y+1, this.cy-2);\n        }\n        if (p.y > this.cy) p.y = this.cy - 2;\n      } else {\n        if (!insideCell(nx, ny)) { p.x = nx; p.y = ny; }\n        else {\n          p.vx *= -0.6; p.vy *= -0.6;\n        }\n        \/\/ box bounds\n        if (p.x < x+1 || p.x > x+w-2) p.vx *= -0.7;\n        if (p.y < y+1 || p.y > y+h-2) p.vy *= -0.7;\n        p.x = clamp(p.x, x+1, x+w-2);\n        p.y = clamp(p.y, y+1, y+h-2);\n      }\n    }\n  }\n\n  drawChannels(ctx){\n    const m = this.m;\n\n    \/\/ Normalize permeability weights so channel counts stay in a sane range.\n    const total = Math.max(1e-9, (m.nap + m.kp + m.clp));\n    const baseN = 70; \/\/ total channels drawn\n\n    const nNa = Math.max(0, Math.round(baseN * (m.nap\/total)));\n    const nK  = Math.max(0, Math.round(baseN * (m.kp\/total)));\n    const nCl = Math.max(0, baseN - nNa - nK);\n\n    const add = (kind, n, phase)=>{\n      for (let i=0;i<n;i++){\n        const t = (i + 0.5) \/ n; \/\/ 0..1\n        const ang = Math.PI * (0.06 + 0.88*t); \/\/ along the arc (semi-circle)\n        \/\/ membrane arc point\n        const px = this.cx + this.r * Math.cos(Math.PI - ang);\n        const py = this.cy - this.r * Math.sin(Math.PI - ang);\n\n        \/\/ normal direction (pointing outward)\n        const nx = Math.cos(Math.PI - ang);\n        const ny = -Math.sin(Math.PI - ang);\n\n        const len = 24; \/\/ channel length\n        const w = 11;\n        const jitter = (Math.random()-0.5)*1.2;\n\n        ctx.save();\n        ctx.translate(px + nx*jitter, py + ny*jitter);\n        ctx.rotate(Math.atan2(ny, nx) + phase);\n        ctx.fillStyle = (kind===\"na\") ? COLORS.na : (kind===\"k\") ? COLORS.k : COLORS.cl;\n        ctx.globalAlpha = 0.85;\n        ctx.fillRect(-w\/2, -len\/2, w, len);\n        ctx.restore();\n      }\n    };\n\n    add(\"na\", nNa, 0.0);\n    add(\"k\",  nK,  0.03);\n    add(\"cl\", nCl, -0.03);\n  }\n\n  draw(ctx, colorize=true){\n    const m = this.m;\n    const {x,y,w,h} = this.viewport;\n\n    \/\/ Background\n    ctx.fillStyle = \"#000000\";\n    ctx.fillRect(x, y, w, h);\n\n    \/\/ Cell body (semi-circle)\n    ctx.beginPath();\n    if (colorize){\n      const c = vcol(m.v*1e-3); ctx.fillStyle = `rgb(${c.r},${c.g},${c.b},0.45)`;\n    } else {\n      ctx.fillStyle = \"rgb(35,40,52)\";\n    }\n    ctx.strokeStyle = \"rgba(245,245,220,0.85)\";\n    ctx.lineWidth = 3;\n    ctx.arc(this.cx, this.cy, this.r, Math.PI, 0);\n    ctx.fill();\n    ctx.stroke();\n\n    \/\/ Channels along membrane\n    this.drawChannels(ctx);\n\n    \/\/ Ions\n    for (const p of m.ions){\n      ctx.fillStyle = (p.kind===\"na\") ? COLORS.na : (p.kind===\"k\") ? COLORS.k : COLORS.cl;\n      \/\/ ctx.fillRect(p.x, p.y, 4, 4);\n      ctx.save();\n      ctx.shadowBlur = 10;\n      ctx.shadowColor = ctx.fillStyle;\n      ctx.fillRect(p.x, p.y, 4, 4);\n      ctx.restore();\n    }\n\n    \/\/ simple electrode tip illustration\n    ctx.save();\n    ctx.translate(this.cx, this.cy);\n    ctx.rotate(Math.PI\/180 * 30);\n    ctx.strokeStyle = \"rgba(200,200,200,0.55)\";\n    ctx.lineWidth = 2;\n    ctx.beginPath();\n    ctx.moveTo(1, -0.94*this.r);\n    ctx.lineTo(5, -1.50*this.r);\n    ctx.moveTo(-1, -0.94*this.r);\n    ctx.lineTo(-5, -1.50*this.r);\n    ctx.stroke();\n    ctx.restore();\n  }\n}\n\nclass OscilloscopeView {\n  constructor(model, {n=900} = {}){\n    this.m = model;\n    this.viewport = {x:0,y:0,w:100,h:100};\n    this.vmin = -90; this.vmax = 60;   \/\/ mV\n    this.buf = new RingBuffer(n);\n    this.buf.fill(-65);\n  }\n  setViewport(x,y,w,h){ this.viewport = {x,y,w,h}; }\n  reset(){ this.buf.fill(this.m.v); }\n  sample(v_mV){ this.buf.push(v_mV); }\n\n  draw(ctx, guides=false, eK=null, eNa=null){\n    const {x,y,w,h} = this.viewport;\n\n    ctx.fillStyle = \"rgba(0,0,0,0.22)\";\n    ctx.fillRect(x, y, w, h);\n\n    \/\/ Grid\n    ctx.save();\n    ctx.strokeStyle = \"rgba(230,232,238,0.10)\";\n    ctx.lineWidth = 1;\n    const nx = 6, ny = 4;\n    for (let i=1;i<nx;i++){\n      const X = x + (i\/nx)*w;\n      ctx.beginPath(); ctx.moveTo(X, y); ctx.lineTo(X, y+h); ctx.stroke();\n    }\n    for (let j=1;j<ny;j++){\n      const Y = y + (j\/ny)*h;\n      ctx.beginPath(); ctx.moveTo(x, Y); ctx.lineTo(x+w, Y); ctx.stroke();\n    }\n    ctx.restore();\n\n    const data = this.buf.toArray();\n    if (!data || data.length < 2) return;\n\n    const yFromV = (v)=> {\n      const t = (v - this.vmin) \/ (this.vmax - this.vmin);\n      const clamped = Math.max(0, Math.min(1, t));\n      return y + (1 - clamped) * h;\n    };\n\n\n    \/\/ Y-axis labels (mV)\n    ctx.save();\n    ctx.fillStyle = \"rgba(230,232,238,0.70)\";\n    ctx.font = \"12px system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, sans-serif\";\n    ctx.textAlign = \"left\";\n    ctx.textBaseline = \"middle\";\n    const ticks = [-80, -40, 0, 40];\n    for (const tv of ticks){\n      const Y = yFromV(tv);\n      ctx.fillText(`${tv}`, x + 6, Y);\n    }\n    ctx.restore();\n\n    \/\/ Optional guide lines: 0 mV, E_K, E_Na\n    if (guides){\n      const drawGuide = (v, label, col)=>{\n        if (!Number.isFinite(v)) return;\n        const Y = yFromV(v);\n        ctx.save();\n        ctx.setLineDash([6,6]);\n        ctx.strokeStyle = col;\n        ctx.lineWidth = 1.5;\n        ctx.beginPath();\n        ctx.moveTo(x+0.5, Y+0.5);\n        ctx.lineTo(x+w-0.5, Y+0.5);\n        ctx.stroke();\n        ctx.setLineDash([]);\n        ctx.fillStyle = col;\n        ctx.font = \"12px system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, sans-serif\";\n        ctx.textAlign = \"right\";\n        ctx.textBaseline = \"bottom\";\n        ctx.fillText(label, x+w-8, Y-3);\n        ctx.restore();\n      };\n      drawGuide(0, \"0 mV\", \"rgba(230,232,238,0.65)\");\n      drawGuide(eK, \"E_K\", COLORS.k);\n      drawGuide(eNa, \"E_Na\", COLORS.na);\n    }\n\n    ctx.beginPath();\n    for (let i=0;i<data.length;i++){\n      const X = x + (i\/(data.length-1)) * w;\n      const Y = yFromV(data[i]);\n      if (i===0) ctx.moveTo(X,Y);\n      else ctx.lineTo(X,Y);\n    }\n\n    ctx.strokeStyle = \"rgba(140, 200, 255, 0.9)\";\n    ctx.lineWidth = 2;\n    ctx.lineJoin = \"round\";\n    ctx.lineCap = \"round\";\n    ctx.stroke();\n  }\n}\n\n\/* ---------------------------\n   UI wiring\n---------------------------- *\/\nconst canvas = document.getElementById(\"canvas_main\");\nconst ctx = canvas.getContext(\"2d\", {alpha:false});\n\nconst ui = {\n  \/\/ top\n  temp: document.getElementById(\"temp_slider\"),\n  tempDisp: document.getElementById(\"temp_slider_display\"),\n  colorize: document.getElementById(\"colorize\"),\n  scopeGuides: document.getElementById(\"scope_guides\"),\n  vmReadout: document.getElementById(\"vm_readout\"),\n\n  \/\/ concentrations (dual)\n  nao: document.getElementById(\"na_out\"),\n  nai: document.getElementById(\"na_in\"),\n  ko:  document.getElementById(\"k_out\"),\n  ki:  document.getElementById(\"k_in\"),\n  clo: document.getElementById(\"cl_out\"),\n  cli: document.getElementById(\"cl_in\"),\n\n  naoDisp: document.getElementById(\"na_out_display\"),\n  naiDisp: document.getElementById(\"na_in_display\"),\n  koDisp:  document.getElementById(\"k_out_display\"),\n  kiDisp:  document.getElementById(\"k_in_display\"),\n  cloDisp: document.getElementById(\"cl_out_display\"),\n  cliDisp: document.getElementById(\"cl_in_display\"),\n\n  \/\/ permeabilities\n  nap: document.getElementById(\"nap_slider\"),\n  kp:  document.getElementById(\"kp_slider\"),\n  clp: document.getElementById(\"clp_slider\"),\n  napDisp: document.getElementById(\"nap_slider_display\"),\n  kpDisp:  document.getElementById(\"kp_slider_display\"),\n  clpDisp: document.getElementById(\"clp_slider_display\"),\n\n  \/\/ mode\n  modeRadios: document.querySelectorAll(\"input[name='mode']\"),\n\n  \/\/ reset\n  resetBtn: document.getElementById(\"reset_btn\"),\n};\n\nconst DEFAULTS = {\n  nao: 440, nai: 50,\n  ko: 20,  ki: 400,\n  clo: 560, cli: 52,\n  napLog: 1.5, kpLog: 3, clpLog: 2.5,\n  temp: 310,\n  mode: \"goldman\",\n  colorize: true,\n};\n\nconst model = new CellModel();\nconst cellView = new CellView(model);\nconst oscView = new OscilloscopeView(model);\n\n\/\/ Improve dual-slider usability when thumbs overlap:\n\/\/ whichever slider you interact with should come to front.\nfunction bringToFront(el){\n  el.style.zIndex = 3;\n  \/\/ sibling down\n  const sibs = el.parentElement.querySelectorAll(\"input[type='range']\");\n  sibs.forEach(s => { if (s !== el) s.style.zIndex = 2; });\n}\n[\"nao\",\"nai\",\"ko\",\"ki\",\"clo\",\"cli\"].forEach(k=>{\n  ui[k].addEventListener(\"pointerdown\", ()=>bringToFront(ui[k]));\n});\n\nfunction applyUIToModel(){\n  model.temp = Number(ui.temp.value);\n\n  model.nao = Number(ui.nao.value);\n  model.nai = Number(ui.nai.value);\n  model.ko  = Number(ui.ko.value);\n  model.ki  = Number(ui.ki.value);\n  model.clo = Number(ui.clo.value);\n  model.cli = Number(ui.cli.value);\n\n  model.nap = 10 ** Number(ui.nap.value);\n  model.kp  = 10 ** Number(ui.kp.value);\n  model.clp = 10 ** Number(ui.clp.value);\n\n  ui.tempDisp.textContent = model.temp;\n\n  ui.naoDisp.textContent = model.nao;\n  ui.naiDisp.textContent = model.nai;\n  ui.koDisp.textContent  = model.ko;\n  ui.kiDisp.textContent  = model.ki;\n  ui.cloDisp.textContent = model.clo;\n  ui.cliDisp.textContent = model.cli;\n\n  ui.napDisp.textContent = model.nap.toPrecision(3);\n  ui.kpDisp.textContent  = model.kp.toPrecision(3);\n  ui.clpDisp.textContent = model.clp.toPrecision(3);\n\n  ui.vmReadout.textContent = `${model.v.toFixed(1)} mV`;\n}\n\nfunction resize(){\n  const dpr = window.devicePixelRatio || 1;\n  const rect = canvas.getBoundingClientRect();\n  canvas.width  = Math.max(1, Math.floor(rect.width * dpr));\n  canvas.height = Math.max(1, Math.floor(rect.height * dpr));\n\n  const W = canvas.width, H = canvas.height;\n  const gap = Math.floor(10 * dpr);\n\n  const cellH = Math.floor(H * 0.62);\n  const oscH  = H - cellH - gap;\n\n  cellView.setViewport(0, 0, W, cellH);\n  oscView.setViewport(0, cellH + gap, W, Math.max(1, oscH));\n\n  cellView.initIons();\n}\n\n  \/\/ Keep canvas crisp when Elementor changes layout without window resizing\n  const wrap = document.querySelector(\".ng-fullbleed .canvasWrap\");\n  if (wrap && \"ResizeObserver\" in window){\n      const ro = new ResizeObserver(() => resize());\n      ro.observe(wrap);\n  }\n\n  \nfunction setUIFromDefaults(){\n  ui.temp.value = DEFAULTS.temp;\n\n  ui.nao.value = DEFAULTS.nao;\n  ui.nai.value = DEFAULTS.nai;\n  ui.ko.value  = DEFAULTS.ko;\n  ui.ki.value  = DEFAULTS.ki;\n  ui.clo.value = DEFAULTS.clo;\n  ui.cli.value = DEFAULTS.cli;\n\n  ui.nap.value = DEFAULTS.napLog;\n  ui.kp.value  = DEFAULTS.kpLog;\n  ui.clp.value = DEFAULTS.clpLog;\n\n  ui.colorize.checked = DEFAULTS.colorize;\n\n  \/\/ mode radios\n  ui.modeRadios.forEach(r => r.checked = (r.value === DEFAULTS.mode));\n  model.mode = DEFAULTS.mode;\n}\n\nfunction hardReset(){\n  setUIFromDefaults();\n  model.resetDefaults();\n  oscView.reset();\n  cellView.initIons();\n  applyUIToModel();\n}\n\n\/\/ Mode radios\nui.modeRadios.forEach(r=>{\n  r.addEventListener(\"change\", ()=>{\n    if (r.checked) model.mode = r.value;\n  });\n});\n\n\/\/ Inputs: update displays, re-seed ions when concentrations change\nconst concKeys = [\"nao\",\"nai\",\"ko\",\"ki\",\"clo\",\"cli\"];\nconcKeys.forEach(k=>{\n  ui[k].addEventListener(\"input\", ()=>{\n    applyUIToModel();\n    cellView.initIons();\n  });\n});\n[\"temp\",\"nap\",\"kp\",\"clp\"].forEach(k=>{\n  ui[k].addEventListener(\"input\", ()=>{\n    applyUIToModel();\n  });\n});\n\nui.resetBtn.addEventListener(\"click\", ()=>{\n  hardReset();\n});\n\n\/\/ Animation loop\nlet last = performance.now();\nfunction frame(now){\n  const dt = Math.min(0.05, (now - last) \/ 1000);\n  last = now;\n\n  applyUIToModel();\n  model.step(dt);\n  cellView.stepIons(dt);\n  oscView.sample(model.v);\n\n  \/\/ Draw\n  ctx.clearRect(0,0,canvas.width, canvas.height);\n  const cellViewportH = Math.floor(canvas.height * 0.62);\n  cellView.draw(ctx, ui.colorize.checked);\n  oscView.draw(ctx, (ui.scopeGuides && ui.scopeGuides.checked), model.nernst(\"K\"), model.nernst(\"Na\"));\n\n  requestAnimationFrame(frame);\n}\n\nsetUIFromDefaults();\napplyUIToModel();\nresize();\nwindow.addEventListener(\"resize\", resize);\nrequestAnimationFrame(frame);\n<\/script>\n\n<\/div>\n\n<\/body>\n<\/html>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Nernst and Goldman (Interactive) Nernst and Goldman $$E_r = frac{RT}{zF}lnleft(frac{[text{Ion}]_text{out}}{[text{Ion}]_text{in}}right)$$ $$E_m = frac{RT}{F}lnleft(frac{ P_{text{K}^+}[text{K}^+]_text{out} + P_{text{Na}^+}[text{Na}^+]_text{out} + P_{text{Cl}^-}[text{Cl}^-]_text{in}} {P_{text{K}^+}[text{K}^+]_text{in} + P_{text{Na}^+}[text{Na}^+]_text{in} + P_{text{Cl}^-}[text{Cl}^-]_text{out}}right)$$ Top controls Temperature (K) 310 Voltage coloration Scope indicators -65.0 mV Mode Goldman Nernst Na+ Nernst K+ Nernst Cl&#8211; Concentrations [Na+] out \/ in 440 \/ 50 [K+] out \/ in 20 [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"parent":4438,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-4694","page","type-page","status-publish","hentry","entry"],"_links":{"self":[{"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/4694","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/comments?post=4694"}],"version-history":[{"count":181,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/4694\/revisions"}],"predecessor-version":[{"id":4884,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/4694\/revisions\/4884"}],"up":[{"embeddable":true,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/4438"}],"wp:attachment":[{"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/media?parent=4694"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}