{"id":10453,"date":"2026-08-06T08:37:45","date_gmt":"2026-08-06T08:37:45","guid":{"rendered":"https:\/\/voyawave.com\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\/"},"modified":"2026-08-10T07:08:22","modified_gmt":"2026-08-10T07:08:22","slug":"%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8","status":"publish","type":"page","link":"https:\/\/voyawave.com\/zh-hans\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\/","title":{"rendered":"\u6a21\u5f0f\u8ba1\u7b97\u5668"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"10453\" class=\"elementor elementor-10453 elementor-10450\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0fd8221 e-con-full wd-section-stretch-content e-flex e-con e-parent\" data-id=\"0fd8221\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-59e3c07 elementor-widget elementor-widget-html\" data-id=\"59e3c07\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\" \/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/>\n  <title>\u6a21\u5f0f\u8ba1\u7b97\u5668<\/title>\n  <style>\n    :root {\n      --accent: #c5142d;\n      --accent-dark: #9f0f24;\n      --accent-soft: #fff1f3;\n      --ink: #171717;\n      --muted: #6f6f73;\n      --line: #ead9dc;\n      --line-strong: #dcbdc3;\n      --card: rgba(255, 255, 255, 0.96);\n      --shadow: 0 14px 36px rgba(52, 25, 31, 0.07);\n      --radius: 18px;\n    }\n\n    * { box-sizing: border-box; }\n\n    body {\n      margin: 0;\n      min-width: 1120px;\n      color: var(--ink);\n      font-family: Inter, \"Microsoft YaHei\", \"PingFang SC\", \"Noto Sans SC\", Arial, sans-serif;\n      background-color: #fbfaf9;\n      background-image:\n        linear-gradient(rgba(197, 20, 45, 0.035) 1px, transparent 1px),\n        linear-gradient(90deg, rgba(197, 20, 45, 0.035) 1px, transparent 1px);\n      background-size: 32px 32px;\n    }\n\n    button, input, select { font: inherit; }\n\n    .app-shell {\n      width: min(1760px, calc(100% - 44px));\n      margin: 22px auto 36px;\n    }\n\n    .topbar {\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      margin-bottom: 18px;\n      padding: 4px 2px;\n    }\n\n    .brand {\n      display: flex;\n      align-items: center;\n      gap: 13px;\n    }\n\n    .brand-mark {\n      width: 42px;\n      height: 42px;\n      border-radius: 12px;\n      display: grid;\n      place-items: center;\n      color: #fff;\n      font-weight: 900;\n      font-size: 21px;\n      background: linear-gradient(145deg, #d21a35, #aa0d24);\n      box-shadow: 0 9px 20px rgba(197, 20, 45, 0.22);\n    }\n\n    .brand-title {\n      margin: 0;\n      font-size: 24px;\n      letter-spacing: 0.02em;\n    }\n\n    .brand-subtitle {\n      margin-top: 3px;\n      color: var(--muted);\n      font-size: 13px;\n    }\n\n    .version-badge {\n      padding: 8px 12px;\n      border: 1px solid var(--line);\n      border-radius: 999px;\n      background: #fff;\n      color: var(--muted);\n      font-size: 13px;\n    }\n\n    .workspace {\n      display: grid;\n      grid-template-columns: 390px minmax(720px, 1fr);\n      gap: 20px;\n      align-items: start;\n    }\n\n    .panel {\n      border: 1px solid var(--line);\n      border-radius: var(--radius);\n      background: var(--card);\n      box-shadow: var(--shadow);\n    }\n\n    .input-panel {\n      padding: 20px 21px 22px;\n      position: sticky;\n      top: 18px;\n    }\n\n    .result-panel {\n      min-height: 760px;\n      overflow: hidden;\n    }\n\n    .section-title {\n      display: flex;\n      align-items: center;\n      gap: 10px;\n      margin: 0 0 17px;\n      font-size: 20px;\n      font-weight: 800;\n    }\n\n    .section-title::before {\n      content: \"\";\n      width: 7px;\n      height: 24px;\n      border-radius: 999px;\n      background: var(--accent);\n    }\n\n    .field-grid {\n      display: grid;\n      grid-template-columns: 1fr 1fr;\n      gap: 13px 12px;\n    }\n\n    .field.full { grid-column: 1 \/ -1; }\n\n    label.field-label {\n      display: block;\n      margin: 0 0 7px;\n      color: #3d393a;\n      font-size: 14px;\n      font-weight: 650;\n    }\n\n    input[type=\"number\"], input[type=\"text\"], select {\n      width: 100%;\n      height: 48px;\n      padding: 0 13px;\n      border: 1px solid var(--line-strong);\n      border-radius: 12px;\n      color: var(--ink);\n      background: #fff;\n      outline: none;\n      transition: border-color 0.18s ease, box-shadow 0.18s ease;\n    }\n\n    input:focus, select:focus {\n      border-color: var(--accent);\n      box-shadow: 0 0 0 4px rgba(197, 20, 45, 0.09);\n    }\n\n    input:disabled {\n      color: #7d7476;\n      background: #f7f4f4;\n    }\n\n    .inline-note {\n      min-height: 18px;\n      margin-top: 7px;\n      color: var(--muted);\n      font-size: 12px;\n      line-height: 1.45;\n    }\n\n    .auto-row {\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      gap: 10px;\n      margin: 13px 0 17px;\n      padding: 11px 12px;\n      border: 1px solid var(--line);\n      border-radius: 12px;\n      background: #fffafa;\n    }\n\n    .auto-row label {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      font-size: 13px;\n      cursor: pointer;\n    }\n\n    input[type=\"checkbox\"], input[type=\"radio\"] { accent-color: var(--accent); }\n\n    .divider {\n      height: 1px;\n      margin: 18px 0;\n      background: var(--line);\n    }\n\n    .tabs {\n      display: grid;\n      grid-template-columns: 1fr 1fr;\n      gap: 8px;\n      margin-bottom: 15px;\n      padding: 5px;\n      border: 1px solid var(--line);\n      border-radius: 14px;\n      background: #faf6f7;\n    }\n\n    .tab-btn {\n      height: 42px;\n      border: 0;\n      border-radius: 10px;\n      color: #5c5052;\n      background: transparent;\n      font-weight: 750;\n      cursor: pointer;\n    }\n\n    .tab-btn.active {\n      color: #fff;\n      background: var(--accent);\n      box-shadow: 0 7px 16px rgba(197, 20, 45, 0.20);\n    }\n\n    .mode-fields { display: none; }\n    .mode-fields.active { display: block; }\n\n    .pol-box {\n      margin-top: 15px;\n    }\n\n    .pol-options {\n      display: grid;\n      grid-template-columns: 1fr 1fr;\n      gap: 10px;\n    }\n\n    .pol-option {\n      position: relative;\n    }\n\n    .pol-option input {\n      position: absolute;\n      opacity: 0;\n      pointer-events: none;\n    }\n\n    .pol-option span {\n      height: 44px;\n      display: grid;\n      place-items: center;\n      border: 1px solid var(--line-strong);\n      border-radius: 12px;\n      background: #fff;\n      font-weight: 800;\n      cursor: pointer;\n    }\n\n    .pol-option input:checked + span {\n      color: var(--accent);\n      border-color: var(--accent);\n      background: var(--accent-soft);\n      box-shadow: inset 0 0 0 1px var(--accent);\n    }\n\n    details.advanced {\n      margin-top: 14px;\n      border: 1px solid var(--line);\n      border-radius: 12px;\n      background: #fff;\n      overflow: hidden;\n    }\n\n    details.advanced summary {\n      padding: 12px 13px;\n      cursor: pointer;\n      color: #4b4345;\n      font-weight: 700;\n      user-select: none;\n    }\n\n    .advanced-body {\n      padding: 0 13px 13px;\n    }\n\n    .primary-btn, .secondary-btn, .outline-btn {\n      height: 50px;\n      border-radius: 12px;\n      font-weight: 800;\n      cursor: pointer;\n      transition: transform 0.16s ease, box-shadow 0.16s ease, background 0.16s ease;\n    }\n\n    .primary-btn {\n      width: 100%;\n      margin-top: 18px;\n      border: 1px solid var(--accent);\n      color: #fff;\n      background: var(--accent);\n      box-shadow: 0 10px 22px rgba(197, 20, 45, 0.22);\n    }\n\n    .primary-btn:hover { background: var(--accent-dark); }\n    .primary-btn:active, .secondary-btn:active, .outline-btn:active { transform: translateY(1px); }\n    .primary-btn:disabled { opacity: 0.7; cursor: wait; }\n\n    .result-head {\n      display: flex;\n      align-items: flex-start;\n      justify-content: space-between;\n      gap: 18px;\n      padding: 20px 22px 17px;\n      border-bottom: 1px solid var(--line);\n    }\n\n    .result-title-wrap .section-title { margin-bottom: 6px; }\n\n    .result-help {\n      color: var(--muted);\n      font-size: 13px;\n    }\n\n    .actions {\n      display: flex;\n      align-items: center;\n      gap: 10px;\n      flex-wrap: wrap;\n      justify-content: flex-end;\n    }\n\n    .secondary-btn, .outline-btn {\n      min-width: 112px;\n      padding: 0 16px;\n      background: #fff;\n    }\n\n    .secondary-btn {\n      border: 1px solid var(--accent);\n      color: var(--accent);\n    }\n\n    .outline-btn {\n      border: 1px solid var(--line-strong);\n      color: #262224;\n    }\n\n    .summary-strip {\n      display: grid;\n      grid-template-columns: repeat(4, minmax(120px, 1fr));\n      gap: 10px;\n      padding: 14px 22px;\n      border-bottom: 1px solid var(--line);\n      background: #fffafa;\n    }\n\n    .summary-item {\n      min-height: 62px;\n      padding: 10px 12px;\n      border: 1px solid var(--line);\n      border-radius: 12px;\n      background: #fff;\n    }\n\n    .summary-key {\n      color: var(--muted);\n      font-size: 12px;\n    }\n\n    .summary-value {\n      margin-top: 5px;\n      font-size: 15px;\n      font-weight: 800;\n      overflow: hidden;\n      text-overflow: ellipsis;\n      white-space: nowrap;\n    }\n\n    .message {\n      display: none;\n      margin: 14px 22px 0;\n      padding: 12px 14px;\n      border-radius: 12px;\n      font-size: 14px;\n      line-height: 1.5;\n    }\n\n    .message.show { display: block; }\n    .message.error { color: #9d1428; border: 1px solid #f0a5b0; background: #fff0f2; }\n    .message.info { color: #5d4b50; border: 1px solid var(--line); background: #fffafa; }\n\n    .legend {\n      display: none;\n      gap: 18px;\n      align-items: center;\n      padding: 13px 22px 0;\n      color: var(--muted);\n      font-size: 13px;\n    }\n\n    .legend.show { display: flex; }\n\n    .legend-item { display: flex; align-items: center; gap: 7px; }\n    .dot { width: 10px; height: 10px; border-radius: 50%; }\n    .dot.blue { background: #2467c9; }\n    .dot.black { background: #202124; }\n\n    .table-wrap {\n      margin: 14px 22px 22px;\n      height: 610px;\n      overflow: auto;\n      border: 1px solid var(--line);\n      border-radius: 14px;\n      background: #fff;\n    }\n\n    table {\n      width: 100%;\n      border-collapse: separate;\n      border-spacing: 0;\n      font-variant-numeric: tabular-nums;\n    }\n\n    th, td {\n      padding: 13px 14px;\n      border-bottom: 1px solid #eee4e6;\n      border-right: 1px solid #f2e9eb;\n      text-align: right;\n      white-space: nowrap;\n    }\n\n    th:last-child, td:last-child { border-right: 0; }\n\n    th {\n      position: sticky;\n      top: 0;\n      z-index: 2;\n      color: #383033;\n      background: #fff6f7;\n      font-size: 13px;\n      font-weight: 850;\n    }\n\n    th:first-child, td:first-child { text-align: center; }\n\n    tbody tr:hover td { background: #fff9fa; }\n    tbody tr.upper-guided td:first-child { box-shadow: inset 4px 0 #2467c9; color: #1f59aa; font-weight: 900; }\n    tbody tr.leaky td:first-child { box-shadow: inset 4px 0 #202124; color: #202124; font-weight: 900; }\n\n    .empty-row td {\n      height: 480px;\n      text-align: center !important;\n      color: #9a8f92;\n      font-size: 15px;\n      background: linear-gradient(180deg, #fff, #fffcfc);\n    }\n\n    .spinner {\n      width: 15px;\n      height: 15px;\n      display: inline-block;\n      margin-right: 8px;\n      border: 2px solid rgba(255,255,255,0.5);\n      border-top-color: #fff;\n      border-radius: 50%;\n      vertical-align: -2px;\n      animation: spin 0.8s linear infinite;\n    }\n\n    @keyframes spin { to { transform: rotate(360deg); } }\n\n    @media (max-width: 1280px) {\n      body { min-width: 1020px; }\n      .workspace { grid-template-columns: 360px minmax(620px, 1fr); }\n      .summary-strip { grid-template-columns: repeat(2, 1fr); }\n    }\n\n    @media print {\n      body { min-width: 0; background: #fff; }\n      .app-shell { width: 100%; margin: 0; }\n      .topbar, .input-panel, .actions, .message { display: none !important; }\n      .workspace { display: block; }\n      .result-panel { border: 0; box-shadow: none; }\n      .table-wrap { height: auto; overflow: visible; margin: 10px 0; border-radius: 0; }\n      th { position: static; }\n      .result-head, .summary-strip { padding-left: 0; padding-right: 0; }\n    }\n  <\/style>\n<\/head>\n<body>\n  <div class=\"app-shell\">\n    <header class=\"topbar\">\n      <div class=\"brand\">\n        <div class=\"brand-mark\">\u25bd<\/div>\n        <div>\n          <h1 class=\"brand-title\">\u6a21\u5f0f\u8ba1\u7b97\u5668<\/h1>\n          <div class=\"brand-subtitle\">Prism Coupler Mode Calculator \u00b7 \u5355\u819c \/ \u53cc\u819c \u00b7 TE \/ TM<\/div>\n        <\/div>\n      <\/div>\n      <div class=\"version-badge\">HTML \u5355\u6587\u4ef6\u7248 \u00b7 Solver V7<\/div>\n    <\/header>\n\n    <main class=\"workspace\">\n      <section class=\"panel input-panel\">\n        <h2 class=\"section-title\">\u8ba1\u7b97\u53c2\u6570<\/h2>\n\n        <div class=\"field-grid\">\n          <div class=\"field\">\n            <label class=\"field-label\" for=\"wavelength\">\u6ce2\u957f (nm)<\/label>\n            <input id=\"wavelength\" type=\"number\" step=\"any\" value=\"635\" \/>\n          <\/div>\n          <div class=\"field\">\n            <label class=\"field-label\" for=\"prismCode\">\u68f1\u955c\u7f16\u53f7<\/label>\n            <input id=\"prismCode\" type=\"number\" step=\"any\" value=\"500\" \/>\n          <\/div>\n          <div class=\"field\">\n            <label class=\"field-label\" for=\"prismIndex\">\u68f1\u955c\u6298\u5c04\u7387<\/label>\n            <input id=\"prismIndex\" type=\"number\" step=\"any\" value=\"1.94596\" \/>\n          <\/div>\n          <div class=\"field\">\n            <label class=\"field-label\" for=\"substrateIndex\">\u57fa\u5e95\u6298\u5c04\u7387<\/label>\n            <input id=\"substrateIndex\" type=\"number\" step=\"any\" value=\"3.4\" \/>\n          <\/div>\n          <div class=\"field full\">\n            <label class=\"field-label\" for=\"prismAngle\">\u68f1\u955c\u89d2\u5ea6 (\u00b0)<\/label>\n            <input id=\"prismAngle\" type=\"number\" step=\"any\" value=\"45\" \/>\n            <div class=\"inline-note\" id=\"prismInfo\">\u5f53\u524d\u6309\u624b\u52a8\u68f1\u955c\u53c2\u6570\u8ba1\u7b97\u3002<\/div>\n          <\/div>\n        <\/div>\n\n        <div class=\"auto-row\">\n          <label><input id=\"autoPrism\" type=\"checkbox\" \/> \u6839\u636e\u68f1\u955c\u7f16\u53f7\u81ea\u52a8\u8ba1\u7b97\u6298\u5c04\u7387\u4e0e\u89d2\u5ea6<\/label>\n        <\/div>\n\n        <div class=\"divider\"><\/div>\n\n        <div class=\"tabs\" role=\"tablist\" aria-label=\"\u819c\u5c42\u7c7b\u578b\">\n          <button class=\"tab-btn active\" id=\"singleTab\" type=\"button\">\u5355\u819c<\/button>\n          <button class=\"tab-btn\" id=\"dualTab\" type=\"button\">\u53cc\u819c<\/button>\n        <\/div>\n\n        <div class=\"mode-fields active\" id=\"singleFields\">\n          <div class=\"field-grid\">\n            <div class=\"field\">\n              <label class=\"field-label\" for=\"film1IndexSingle\">\u819c\u6298\u5c04\u7387<\/label>\n              <input id=\"film1IndexSingle\" type=\"number\" step=\"any\" value=\"1.4733\" \/>\n            <\/div>\n            <div class=\"field\">\n              <label class=\"field-label\" for=\"film1ThicknessSingle\">\u819c\u539a\u5ea6 (\u03bcm)<\/label>\n              <input id=\"film1ThicknessSingle\" type=\"number\" step=\"any\" value=\"90\" \/>\n            <\/div>\n          <\/div>\n        <\/div>\n\n        <div class=\"mode-fields\" id=\"dualFields\">\n          <div class=\"field-grid\">\n            <div class=\"field\">\n              <label class=\"field-label\" for=\"film1IndexDual\">\u4e0a\u819c\u6298\u5c04\u7387 n\u2081<\/label>\n              <input id=\"film1IndexDual\" type=\"number\" step=\"any\" value=\"1.70\" \/>\n            <\/div>\n            <div class=\"field\">\n              <label class=\"field-label\" for=\"film1ThicknessDual\">\u4e0a\u819c\u539a\u5ea6 w\u2081 (\u03bcm)<\/label>\n              <input id=\"film1ThicknessDual\" type=\"number\" step=\"any\" value=\"2.0\" \/>\n            <\/div>\n            <div class=\"field\">\n              <label class=\"field-label\" for=\"film2Index\">\u4e0b\u819c\u6298\u5c04\u7387 n\u2082<\/label>\n              <input id=\"film2Index\" type=\"number\" step=\"any\" value=\"1.45\" \/>\n            <\/div>\n            <div class=\"field\">\n              <label class=\"field-label\" for=\"film2Thickness\">\u4e0b\u819c\u539a\u5ea6 w\u2082 (\u03bcm)<\/label>\n              <input id=\"film2Thickness\" type=\"number\" step=\"any\" value=\"1.0\" \/>\n            <\/div>\n          <\/div>\n        <\/div>\n\n        <div class=\"pol-box\">\n          <label class=\"field-label\">\u504f\u632f\u6a21\u5f0f<\/label>\n          <div class=\"pol-options\">\n            <label class=\"pol-option\"><input type=\"radio\" name=\"pol\" value=\"TE\" checked \/><span>TE<\/span><\/label>\n            <label class=\"pol-option\"><input type=\"radio\" name=\"pol\" value=\"TM\" \/><span>TM<\/span><\/label>\n          <\/div>\n        <\/div>\n\n        <details class=\"advanced\">\n          <summary>\u9ad8\u7ea7\u53c2\u6570<\/summary>\n          <div class=\"advanced-body\">\n            <div class=\"field-grid\">\n              <div class=\"field\">\n                <label class=\"field-label\" for=\"topCladdingIndex\">\u4e0a\u5305\u5c42\u6298\u5c04\u7387<\/label>\n                <input id=\"topCladdingIndex\" type=\"number\" step=\"any\" value=\"1.0\" \/>\n              <\/div>\n              <div class=\"field\">\n                <label class=\"field-label\" for=\"ambientIndex\">\u73af\u5883\u6298\u5c04\u7387<\/label>\n                <input id=\"ambientIndex\" type=\"number\" step=\"any\" value=\"1.0\" \/>\n              <\/div>\n              <div class=\"field\">\n                <label class=\"field-label\" for=\"sampleCount\">\u91c7\u6837\u70b9\u6570<\/label>\n                <input id=\"sampleCount\" type=\"number\" step=\"1\" min=\"50000\" value=\"400000\" \/>\n              <\/div>\n              <div class=\"field\">\n                <label class=\"field-label\" for=\"bisectionIterations\">\u4e8c\u5206\u8fed\u4ee3\u6b21\u6570<\/label>\n                <input id=\"bisectionIterations\" type=\"number\" step=\"1\" min=\"20\" value=\"80\" \/>\n              <\/div>\n            <\/div>\n          <\/div>\n        <\/details>\n\n        <button class=\"primary-btn\" id=\"calculateBtn\" type=\"button\">\u8ba1\u7b97\u6a21\u5f0f<\/button>\n      <\/section>\n\n      <section class=\"panel result-panel\" id=\"printArea\">\n        <div class=\"result-head\">\n          <div class=\"result-title-wrap\">\n            <h2 class=\"section-title\">\u6a21\u5f0f\u8ba1\u7b97\u7ed3\u679c<\/h2>\n            <div class=\"result-help\">\u89d2\u5ea6\u5355\u4f4d\u4e3a \u00b0\uff0cKz \u5355\u4f4d\u4e3a \u03bcm\u207b\u00b9\uff1b\u53cc\u819c\u6a21\u5f0f\u6309\u5206\u652f\u6807\u8272\u3002<\/div>\n          <\/div>\n          <div class=\"actions\">\n            <button class=\"secondary-btn\" id=\"exportBtn\" type=\"button\">\u5bfc\u51fa CSV<\/button>\n            <button class=\"outline-btn\" id=\"printBtn\" type=\"button\">\u6253\u5370<\/button>\n            <button class=\"outline-btn\" id=\"clearBtn\" type=\"button\">\u5168\u90e8\u5220\u9664<\/button>\n          <\/div>\n        <\/div>\n\n        <div class=\"summary-strip\">\n          <div class=\"summary-item\"><div class=\"summary-key\">\u7ed3\u6784<\/div><div class=\"summary-value\" id=\"summaryStructure\">\u2014<\/div><\/div>\n          <div class=\"summary-item\"><div class=\"summary-key\">\u68f1\u955c<\/div><div class=\"summary-value\" id=\"summaryPrism\">\u2014<\/div><\/div>\n          <div class=\"summary-item\"><div class=\"summary-key\">\u6a21\u5f0f\u6570\u91cf<\/div><div class=\"summary-value\" id=\"summaryCount\">0<\/div><\/div>\n          <div class=\"summary-item\"><div class=\"summary-key\">\u8ba1\u7b97\u8017\u65f6<\/div><div class=\"summary-value\" id=\"summaryTime\">\u2014<\/div><\/div>\n        <\/div>\n\n        <div class=\"message\" id=\"messageBox\"><\/div>\n\n        <div class=\"legend\" id=\"legend\">\n          <div class=\"legend-item\"><span class=\"dot blue\"><\/span>\u4e0a\u819c\u5bfc\u6a21\uff1aBeta &gt; n\u2082<\/div>\n          <div class=\"legend-item\"><span class=\"dot black\"><\/span>\u6cc4\u6f0f \/ \u4e0b\u5c42\u5206\u652f\uff1aBeta \u2264 n\u2082<\/div>\n        <\/div>\n\n        <div class=\"table-wrap\">\n          <table>\n            <thead>\n              <tr>\n                <th>\u6a21\u9636<\/th>\n                <th>\u89d2\u5ea6 (ext)<\/th>\n                <th>Beta<\/th>\n                <th>Angle (int)<\/th>\n                <th>Phi (bot)<\/th>\n                <th>Phi (top)<\/th>\n                <th>Kz<\/th>\n              <\/tr>\n            <\/thead>\n            <tbody id=\"resultBody\">\n              <tr class=\"empty-row\"><td colspan=\"7\">\u8f93\u5165\u53c2\u6570\u540e\u70b9\u51fb\u201c\u8ba1\u7b97\u6a21\u5f0f\u201d<\/td><\/tr>\n            <\/tbody>\n          <\/table>\n        <\/div>\n      <\/section>\n    <\/main>\n  <\/div>\n\n  <script id=\"solver-worker\" type=\"javascript\/worker\">\n    'use strict';\n\n    const PI = Math.PI;\n    const TWO_PI = 2 * PI;\n    const BRANCH_TOL = 1e-9;\n\n    function clampUnit(x) { return Math.max(-1, Math.min(1, x)); }\n    function deg(rad) { return rad * 180 \/ PI; }\n    function rad(degValue) { return degValue * PI \/ 180; }\n\n    function c(re, im) { return { re, im }; }\n    function cAdd(a, b) { return c(a.re + b.re, a.im + b.im); }\n    function cSub(a, b) { return c(a.re - b.re, a.im - b.im); }\n    function cMul(a, b) { return c(a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re); }\n    function cScale(a, s) { return c(a.re * s, a.im * s); }\n    function cDiv(a, b) {\n      const d = b.re * b.re + b.im * b.im;\n      if (d < 1e-36) return c(0, 0);\n      return c((a.re * b.re + a.im * b.im) \/ d, (a.im * b.re - a.re * b.im) \/ d);\n    }\n    function cAbs(a) { return Math.hypot(a.re, a.im); }\n    function cArg(a) { return Math.atan2(a.im, a.re); }\n    function cTan(z) {\n      if (Math.abs(z.im) > 20) return c(0, Math.sign(z.im));\n      const den = Math.cos(2 * z.re) + Math.cosh(2 * z.im);\n      if (Math.abs(den) < 1e-18) return c(Math.sign(Math.sin(2 * z.re)) * 1e18, 0);\n      return c(Math.sin(2 * z.re) \/ den, Math.sinh(2 * z.im) \/ den);\n    }\n\n    function kzComplex(beta, n, k0) {\n      const value = n * n - beta * beta;\n      return value >= 0 ? c(k0 * Math.sqrt(value), 0) : c(0, k0 * Math.sqrt(-value));\n    }\n\n    function kzReal(beta, n, k0) {\n      const value = n * n - beta * beta;\n      return value > 0 ? k0 * Math.sqrt(value) : 0;\n    }\n\n    function decayOrPropagationMagnitude(beta, n, k0) {\n      return k0 * Math.sqrt(Math.abs(beta * beta - n * n));\n    }\n\n    function layerAdmittance(beta, n, k0, pol) {\n      const kz = kzComplex(beta, n, k0);\n      return pol === 'TE' ? kz : cScale(kz, 1 \/ (n * n));\n    }\n\n    function reflectivity(yFrom, yLoad) {\n      const den = cAdd(yFrom, yLoad);\n      if (cAbs(den) < 1e-18) return c(0, 0);\n      return cDiv(cSub(yFrom, yLoad), den);\n    }\n\n    function lowerStackInputAdmittance(input, beta, k0) {\n      const ySub = layerAdmittance(beta, input.substrateIndex, k0, input.pol);\n      const y2 = layerAdmittance(beta, input.film2Index, k0, input.pol);\n      const kz2 = kzComplex(beta, input.film2Index, k0);\n      const t = cTan(cScale(kz2, input.film2ThicknessUm));\n      const iYSubT = cMul(c(0, 1), cMul(ySub, t));\n      const denominator = cSub(y2, iYSubT);\n      if (cAbs(denominator) < 1e-18) return c(0, 0);\n      const iY2T = cMul(c(0, 1), cMul(y2, t));\n      const numeratorInner = cSub(ySub, iY2T);\n      return cDiv(cMul(y2, numeratorInner), denominator);\n    }\n\n    function topPhase(input, beta, k0) {\n      const h1 = kzReal(beta, input.film1Index, k0);\n      const gTop = decayOrPropagationMagnitude(beta, input.topCladdingIndex, k0);\n      if (h1 <= 0) return NaN;\n      if (input.pol === 'TE') return Math.atan2(gTop, h1);\n      return Math.atan2((input.film1Index * input.film1Index \/ (input.topCladdingIndex * input.topCladdingIndex)) * gTop, h1);\n    }\n\n    function dualFilmBottomPhasePropagatingLower(input, beta, k0) {\n      const h1 = kzReal(beta, input.film1Index, k0);\n      const h2 = kzReal(beta, input.film2Index, k0);\n      if (h1 <= 0 || h2 <= 0) return NaN;\n\n      const substrateTerm = decayOrPropagationMagnitude(beta, input.substrateIndex, k0);\n      let psiS = 0;\n      let rho = 0;\n      if (input.pol === 'TE') {\n        psiS = Math.atan2(substrateTerm, h2);\n        rho = h2 \/ h1;\n      } else {\n        psiS = Math.atan2((input.film2Index * input.film2Index \/ (input.substrateIndex * input.substrateIndex)) * substrateTerm, h2);\n        rho = (input.film1Index * input.film1Index \/ (input.film2Index * input.film2Index)) * (h2 \/ h1);\n      }\n\n      const x = h2 * input.film2ThicknessUm - psiS;\n      const a = Math.atan(rho * Math.tan(x));\n      const branchIndex = Math.round(x \/ PI);\n      return -(branchIndex * PI + a);\n    }\n\n    function computePoint(input, beta, guidedBranchK, k0) {\n      const h1 = kzReal(beta, input.film1Index, k0);\n      if (h1 <= 0) return null;\n\n      const phiTop = topPhase(input, beta, k0);\n      let phiBot = 0;\n      let guidedArgBranch = false;\n      let rawBotArg = 0;\n\n      if (input.kind === 'single') {\n        const y1 = layerAdmittance(beta, input.film1Index, k0, input.pol);\n        const ys = layerAdmittance(beta, input.substrateIndex, k0, input.pol);\n        phiBot = -0.5 * cArg(reflectivity(y1, ys));\n      } else if (beta < input.film2Index - BRANCH_TOL) {\n        phiBot = dualFilmBottomPhasePropagatingLower(input, beta, k0);\n      } else {\n        const y1 = layerAdmittance(beta, input.film1Index, k0, input.pol);\n        const yBottom = lowerStackInputAdmittance(input, beta, k0);\n        rawBotArg = cArg(reflectivity(y1, yBottom));\n        guidedArgBranch = true;\n        phiBot = -0.5 * (rawBotArg + (guidedBranchK == null ? 0 : TWO_PI * guidedBranchK));\n      }\n\n      return {\n        beta,\n        kz: h1,\n        phiTop,\n        phiBot,\n        rawBotArg,\n        guidedArgBranch,\n        totalPhase: h1 * input.film1ThicknessUm - phiTop - phiBot\n      };\n    }\n\n    function modeInternalAngleDeg(input, beta) {\n      if (beta <= 0 || beta >= input.film1Index) return NaN;\n      return deg(Math.asin(clampUnit(beta \/ input.film1Index)));\n    }\n\n    function prismExternalAngleDeg(input, beta) {\n      const np = input.prismIndex;\n      const n0 = input.ambientIndex;\n      if (beta <= 0 || np <= 0 || n0 <= 0 || beta >= np) return NaN;\n      const thetaBase = Math.asin(clampUnit(beta \/ np));\n      const thetaWaistInside = rad(input.prismAngleDeg) - thetaBase;\n      const s = (np \/ n0) * Math.sin(thetaWaistInside);\n      if (Math.abs(s) > 1) return NaN;\n      return -deg(Math.asin(s));\n    }\n\n    function makeModeResult(input, mode, point) {\n      return {\n        mode,\n        angleExtDeg: prismExternalAngleDeg(input, point.beta),\n        beta: point.beta,\n        angleIntDeg: modeInternalAngleDeg(input, point.beta),\n        phiBot: point.phiBot,\n        phiTop: point.phiTop,\n        kz: point.kz,\n        branch: input.kind === 'single' ? 'single' : (point.beta > input.film2Index + BRANCH_TOL ? 'upper' : 'leaky')\n      };\n    }\n\n    function isSingleFilmLeaky(input) {\n      return input.kind === 'single' && input.substrateIndex > input.film1Index + BRANCH_TOL;\n    }\n\n    function phaseIndexToModeLabel(input, phaseIndex) {\n      return isSingleFilmLeaky(input) ? phaseIndex - 1 : phaseIndex;\n    }\n\n    function refineModeBisection(input, modeLabel, phaseIndex, a, b, guidedBranchK, k0) {\n      const target = phaseIndex * PI;\n      let left = a.beta;\n      let right = b.beta;\n      let pl = a;\n      let pr = b;\n      let fl = pl.totalPhase - target;\n      let fr = pr.totalPhase - target;\n\n      if (Math.abs(fl) < 1e-15) return makeModeResult(input, modeLabel, pl);\n      if (Math.abs(fr) < 1e-15) return makeModeResult(input, modeLabel, pr);\n\n      if (fl * fr > 0) {\n        const denom = pr.totalPhase - pl.totalPhase;\n        let t = Math.abs(denom) > 1e-18 ? (target - pl.totalPhase) \/ denom : 0;\n        t = Math.max(0, Math.min(1, t));\n        const pm = computePoint(input, pl.beta + (pr.beta - pl.beta) * t, guidedBranchK, k0);\n        return makeModeResult(input, modeLabel, pm);\n      }\n\n      let mid = null;\n      for (let iter = 0; iter < input.bisectionIterations; iter++) {\n        const center = 0.5 * (left + right);\n        mid = computePoint(input, center, guidedBranchK, k0);\n        const fm = mid.totalPhase - target;\n        if (Math.abs(fm) < 1e-14 || Math.abs(right - left) < 1e-14) return makeModeResult(input, modeLabel, mid);\n        if (fl * fm <= 0) {\n          right = center;\n          pr = mid;\n          fr = fm;\n        } else {\n          left = center;\n          pl = mid;\n          fl = fm;\n        }\n      }\n      mid = computePoint(input, 0.5 * (left + right), guidedBranchK, k0);\n      return makeModeResult(input, modeLabel, mid);\n    }\n\n    function solve(input) {\n      const started = performance.now();\n      const k0 = TWO_PI \/ (input.wavelengthNm * 1e-3);\n      const count = Math.max(Math.trunc(input.sampleCount), 50000);\n      const betaMin = Math.max(input.topCladdingIndex + 1e-6, 1e-6);\n      const betaMax = input.film1Index - 1e-6;\n\n      if (!(betaMax > betaMin)) throw new Error('\u819c\u6298\u5c04\u7387\u5fc5\u987b\u5927\u4e8e\u4e0a\u5305\u5c42\u6298\u5c04\u7387\u3002');\n\n      const betaA = new Float64Array(count);\n      const validA = new Uint8Array(count);\n      const guidedA = new Uint8Array(count);\n      const rawBotA = new Float64Array(count);\n      const totalA = new Float64Array(count);\n      const phiTopA = new Float64Array(count);\n      const phiBotA = new Float64Array(count);\n      const kzA = new Float64Array(count);\n      const branchKA = new Int32Array(count);\n\n      for (let i = 0; i < count; i++) {\n        const t = i \/ (count - 1);\n        const beta = betaMax + (betaMin - betaMax) * t;\n        betaA[i] = beta;\n\n        const h1 = kzReal(beta, input.film1Index, k0);\n        if (h1 <= 0) continue;\n\n        const pTop = topPhase(input, beta, k0);\n        validA[i] = 1;\n        kzA[i] = h1;\n        phiTopA[i] = pTop;\n\n        if (input.kind === 'single') {\n          const y1 = layerAdmittance(beta, input.film1Index, k0, input.pol);\n          const ys = layerAdmittance(beta, input.substrateIndex, k0, input.pol);\n          const pBot = -0.5 * cArg(reflectivity(y1, ys));\n          phiBotA[i] = pBot;\n          totalA[i] = h1 * input.film1ThicknessUm - pTop - pBot;\n        } else if (beta < input.film2Index - BRANCH_TOL) {\n          const pBot = dualFilmBottomPhasePropagatingLower(input, beta, k0);\n          phiBotA[i] = pBot;\n          totalA[i] = h1 * input.film1ThicknessUm - pTop - pBot;\n        } else {\n          const y1 = layerAdmittance(beta, input.film1Index, k0, input.pol);\n          const yBottom = lowerStackInputAdmittance(input, beta, k0);\n          rawBotA[i] = cArg(reflectivity(y1, yBottom));\n          guidedA[i] = 1;\n        }\n      }\n\n      let begin = -1;\n      for (let i = 0; i <= count; i++) {\n        const inside = i < count && validA[i] && guidedA[i];\n        if (inside && begin < 0) begin = i;\n        if ((!inside || i === count) && begin >= 0) {\n          let offset = 0;\n          let previous = rawBotA[begin];\n          phiBotA[begin] = -0.5 * previous;\n          branchKA[begin] = 0;\n          totalA[begin] = kzA[begin] * input.film1ThicknessUm - phiTopA[begin] - phiBotA[begin];\n\n          for (let j = begin + 1; j < i; j++) {\n            const current = rawBotA[j];\n            const delta = current - previous;\n            if (delta > PI) offset -= TWO_PI;\n            else if (delta < -PI) offset += TWO_PI;\n            const unwrapped = current + offset;\n            phiBotA[j] = -0.5 * unwrapped;\n            branchKA[j] = Math.round(offset \/ TWO_PI);\n            totalA[j] = kzA[j] * input.film1ThicknessUm - phiTopA[j] - phiBotA[j];\n            previous = current;\n          }\n          begin = -1;\n        }\n      }\n\n      const modes = [];\n      const emitted = new Set();\n\n      function pointAt(i) {\n        return {\n          beta: betaA[i],\n          kz: kzA[i],\n          phiTop: phiTopA[i],\n          phiBot: phiBotA[i],\n          totalPhase: totalA[i]\n        };\n      }\n\n      for (let i = 0; i + 1 < count; i++) {\n        if (!validA[i] || !validA[i + 1]) continue;\n        const g1 = totalA[i] \/ PI;\n        const g2 = totalA[i + 1] \/ PI;\n        let first = Math.ceil(Math.min(g1, g2) - 1e-12);\n        const last = Math.floor(Math.max(g1, g2) + 1e-12);\n        if (last < 0) continue;\n        if (first < 0) first = 0;\n\n        let guidedBranchK = null;\n        if (guidedA[i] && guidedA[i + 1] && branchKA[i] === branchKA[i + 1]) guidedBranchK = branchKA[i];\n\n        for (let m = first; m <= last; m++) {\n          const modeLabel = phaseIndexToModeLabel(input, m);\n          if (modeLabel < 0 || emitted.has(modeLabel)) continue;\n          const target = m * PI;\n          const crossed = (totalA[i] <= target && target <= totalA[i + 1]) || (totalA[i + 1] <= target && target <= totalA[i]);\n          if (!crossed) continue;\n          modes.push(refineModeBisection(input, modeLabel, m, pointAt(i), pointAt(i + 1), guidedBranchK, k0));\n          emitted.add(modeLabel);\n        }\n      }\n\n      modes.sort((a, b) => a.mode - b.mode);\n      return { modes, elapsedMs: performance.now() - started };\n    }\n\n    self.onmessage = event => {\n      try {\n        const result = solve(event.data);\n        self.postMessage({ ok: true, ...result });\n      } catch (error) {\n        self.postMessage({ ok: false, error: error && error.message ? error.message : String(error) });\n      }\n    };\n  <\/script>\n\n  <script>\n    'use strict';\n\n    const $ = id => document.getElementById(id);\n    let structureKind = 'single';\n    let currentRows = [];\n    let worker = null;\n\n    const dom = {\n      singleTab: $('singleTab'), dualTab: $('dualTab'),\n      singleFields: $('singleFields'), dualFields: $('dualFields'),\n      autoPrism: $('autoPrism'), prismInfo: $('prismInfo'),\n      calculateBtn: $('calculateBtn'), clearBtn: $('clearBtn'), printBtn: $('printBtn'), exportBtn: $('exportBtn'),\n      resultBody: $('resultBody'), messageBox: $('messageBox'), legend: $('legend'),\n      summaryStructure: $('summaryStructure'), summaryPrism: $('summaryPrism'), summaryCount: $('summaryCount'), summaryTime: $('summaryTime')\n    };\n\n    function finiteNumber(id, label, options = {}) {\n      const raw = $(id).value.trim();\n      const value = Number(raw);\n      if (!raw || !Number.isFinite(value)) throw new Error(`\u8bf7\u8f93\u5165\u6709\u6548\u7684\u201c${label}\u201d\u3002`);\n      if (options.positive && value <= 0) throw new Error(`\u201c${label}\u201d\u5fc5\u987b\u5927\u4e8e 0\u3002`);\n      if (options.min != null && value < options.min) throw new Error(`\u201c${label}\u201d\u4e0d\u80fd\u5c0f\u4e8e ${options.min}\u3002`);\n      return value;\n    }\n\n    function sellmeierThreeTerm(lambdaUm, a1, b1, a2, b2, a3, b3) {\n      const l2 = lambdaUm * lambdaUm;\n      const n2 = 1 + a1 * l2 \/ (l2 - b1 * b1) + a2 * l2 \/ (l2 - b2 * b2) + a3 * l2 \/ (l2 - b3 * b3);\n      return Math.sqrt(n2);\n    }\n\n    function tiO2ExtraordinaryIndex(lambdaNm) {\n      const invL2 = 1 \/ (lambdaNm * lambdaNm);\n      return Math.sqrt(2.9713 + 5.1891e-5 \/ (1.2280e-5 - invL2) - 4.2950e-8 * lambdaNm * lambdaNm);\n    }\n\n    function tiO2OrdinaryIndex(lambdaNm) {\n      const invL2 = 1 \/ (lambdaNm * lambdaNm);\n      return Math.sqrt(3.2089 + 3.4000e-5 \/ (1.2270e-5 - invL2) - 3.2545e-8 * lambdaNm * lambdaNm);\n    }\n\n    function classifyPrismMaterial(code) {\n      if (code >= 0 && code <= 1500) return 'GGG';\n      if (code >= 4200 && code <= 6400) return 'TiO\u2082';\n      if (code >= 6500 && code <= 7000) return 'ZrO\u2082';\n      if (code >= 7550 && code <= 7900) return '\u03b1-Al\u2082O\u2083';\n      return null;\n    }\n\n    function prismAngleFromCode(code, material) {\n      if (material === 'GGG') return (code + 4000) \/ 100;\n      if (material === 'TiO\u2082') return code \/ 100;\n      if (material === 'ZrO\u2082') return (code - 1500) \/ 100;\n      if (material === '\u03b1-Al\u2082O\u2083') return (code - 1300) \/ 100;\n      return NaN;\n    }\n\n    function autoPrismIndex(wavelengthNm, material, pol) {\n      const lambdaUm = wavelengthNm * 1e-3;\n      if (material === 'GGG') return sellmeierThreeTerm(lambdaUm, 1.7727, 0.1567, 0.9767, 0.01375, 4.9668, 22.715);\n      if (material === 'TiO\u2082') return pol === 'TE' ? tiO2ExtraordinaryIndex(wavelengthNm) : tiO2OrdinaryIndex(wavelengthNm);\n      if (material === 'ZrO\u2082') return sellmeierThreeTerm(lambdaUm, 1.347091, 0.062543, 2.117788, 0.166739, 9.452943, 24.320570);\n      if (material === '\u03b1-Al\u2082O\u2083') {\n        return pol === 'TE'\n          ? sellmeierThreeTerm(lambdaUm, 1.5039759, 0.0740288, 0.55069141, 0.1216529, 6.5927379, 20.072248)\n          : sellmeierThreeTerm(lambdaUm, 1.4313493, 0.0726631, 0.65054713, 0.1193242, 5.3414021, 18.028251);\n      }\n      return NaN;\n    }\n\n    function selectedPol() {\n      return document.querySelector('input[name=\"pol\"]:checked').value;\n    }\n\n    function updateAutoPrism() {\n      const enabled = dom.autoPrism.checked;\n      $('prismIndex').disabled = enabled;\n      $('prismAngle').disabled = enabled;\n\n      if (!enabled) {\n        dom.prismInfo.textContent = '\u5f53\u524d\u6309\u624b\u52a8\u68f1\u955c\u53c2\u6570\u8ba1\u7b97\u3002';\n        return;\n      }\n\n      const code = Number($('prismCode').value);\n      const wavelength = Number($('wavelength').value);\n      const material = classifyPrismMaterial(code);\n      if (!Number.isFinite(code) || !Number.isFinite(wavelength) || !material) {\n        dom.prismInfo.textContent = '\u68f1\u955c\u7f16\u53f7\u4e0d\u5728\u5df2\u652f\u6301\u8303\u56f4\u5185\uff0c\u8bf7\u53d6\u6d88\u81ea\u52a8\u8ba1\u7b97\u540e\u624b\u52a8\u8f93\u5165\u3002';\n        return;\n      }\n      const angle = prismAngleFromCode(code, material);\n      const n = autoPrismIndex(wavelength, material, selectedPol());\n      $('prismAngle').value = Number.isFinite(angle) ? angle.toFixed(6) : '';\n      $('prismIndex').value = Number.isFinite(n) ? n.toFixed(8) : '';\n      dom.prismInfo.textContent = `${material} \u00b7 \u81ea\u52a8\u89d2\u5ea6 ${angle.toFixed(4)}\u00b0 \u00b7 \u81ea\u52a8\u6298\u5c04\u7387 ${n.toFixed(8)}`;\n    }\n\n    function switchStructure(kind) {\n      structureKind = kind;\n      const single = kind === 'single';\n      dom.singleTab.classList.toggle('active', single);\n      dom.dualTab.classList.toggle('active', !single);\n      dom.singleFields.classList.toggle('active', single);\n      dom.dualFields.classList.toggle('active', !single);\n      dom.legend.classList.toggle('show', !single && currentRows.length > 0);\n    }\n\n    function collectInput() {\n      updateAutoPrism();\n      const input = {\n        kind: structureKind,\n        pol: selectedPol(),\n        wavelengthNm: finiteNumber('wavelength', '\u6ce2\u957f', { positive: true }),\n        prismCode: finiteNumber('prismCode', '\u68f1\u955c\u7f16\u53f7'),\n        prismIndex: finiteNumber('prismIndex', '\u68f1\u955c\u6298\u5c04\u7387', { positive: true }),\n        substrateIndex: finiteNumber('substrateIndex', '\u57fa\u5e95\u6298\u5c04\u7387', { positive: true }),\n        prismAngleDeg: finiteNumber('prismAngle', '\u68f1\u955c\u89d2\u5ea6'),\n        topCladdingIndex: finiteNumber('topCladdingIndex', '\u4e0a\u5305\u5c42\u6298\u5c04\u7387', { positive: true }),\n        ambientIndex: finiteNumber('ambientIndex', '\u73af\u5883\u6298\u5c04\u7387', { positive: true }),\n        sampleCount: Math.trunc(finiteNumber('sampleCount', '\u91c7\u6837\u70b9\u6570', { min: 50000 })),\n        bisectionIterations: Math.trunc(finiteNumber('bisectionIterations', '\u4e8c\u5206\u8fed\u4ee3\u6b21\u6570', { min: 20 })),\n        film1Index: structureKind === 'single'\n          ? finiteNumber('film1IndexSingle', '\u819c\u6298\u5c04\u7387', { positive: true })\n          : finiteNumber('film1IndexDual', '\u4e0a\u819c\u6298\u5c04\u7387', { positive: true }),\n        film1ThicknessUm: structureKind === 'single'\n          ? finiteNumber('film1ThicknessSingle', '\u819c\u539a\u5ea6', { positive: true })\n          : finiteNumber('film1ThicknessDual', '\u4e0a\u819c\u539a\u5ea6', { positive: true }),\n        film2Index: structureKind === 'dual' ? finiteNumber('film2Index', '\u4e0b\u819c\u6298\u5c04\u7387', { positive: true }) : 0,\n        film2ThicknessUm: structureKind === 'dual' ? finiteNumber('film2Thickness', '\u4e0b\u819c\u539a\u5ea6', { positive: true }) : 0\n      };\n\n      if (input.prismIndex <= input.topCladdingIndex) throw new Error('\u68f1\u955c\u6298\u5c04\u7387\u5e94\u5927\u4e8e\u4e0a\u5305\u5c42\u6298\u5c04\u7387\u3002');\n      if (input.kind === 'dual' && input.film1Index <= 0) throw new Error('\u4e0a\u819c\u6298\u5c04\u7387\u5fc5\u987b\u5927\u4e8e 0\u3002');\n      return input;\n    }\n\n    function setMessage(text, type = 'info') {\n      dom.messageBox.textContent = text;\n      dom.messageBox.className = `message show ${type}`;\n    }\n\n    function clearMessage() {\n      dom.messageBox.textContent = '';\n      dom.messageBox.className = 'message';\n    }\n\n    function formatNumber(value, digits) {\n      return Number.isFinite(value) ? value.toFixed(digits) : '\u2014';\n    }\n\n    function renderRows(rows, kind) {\n      dom.resultBody.innerHTML = '';\n      if (!rows.length) {\n        dom.resultBody.innerHTML = '<tr class=\"empty-row\"><td colspan=\"7\">\u5f53\u524d\u53c2\u6570\u4e0b\u672a\u641c\u7d22\u5230\u6709\u6548\u6a21\u5f0f<\/td><\/tr>';\n        return;\n      }\n\n      const fragment = document.createDocumentFragment();\n      for (const row of rows) {\n        const tr = document.createElement('tr');\n        if (kind === 'dual') tr.className = row.branch === 'upper' ? 'upper-guided' : 'leaky';\n        tr.innerHTML = `\n          <td>${row.mode}<\/td>\n          <td>${formatNumber(row.angleExtDeg, 6)}<\/td>\n          <td>${formatNumber(row.beta, 8)}<\/td>\n          <td>${formatNumber(row.angleIntDeg, 6)}<\/td>\n          <td>${formatNumber(row.phiBot, 8)}<\/td>\n          <td>${formatNumber(row.phiTop, 8)}<\/td>\n          <td>${formatNumber(row.kz, 8)}<\/td>`;\n        fragment.appendChild(tr);\n      }\n      dom.resultBody.appendChild(fragment);\n    }\n\n    function structureSummary(input) {\n      if (input.kind === 'single') {\n        const relation = input.film1Index > input.substrateIndex ? 'nf > ns' : input.film1Index < input.substrateIndex ? 'nf < ns' : 'nf = ns';\n        return `\u5355\u819c \u00b7 ${input.pol} \u00b7 ${relation}`;\n      }\n      return `\u53cc\u819c \u00b7 ${input.pol} \u00b7 n\u2081=${input.film1Index.toFixed(4)}, n\u2082=${input.film2Index.toFixed(4)}`;\n    }\n\n    function prismSummary(input) {\n      const material = classifyPrismMaterial(input.prismCode);\n      return `${material || '\u624b\u52a8'} \u00b7 n=${input.prismIndex.toFixed(6)} \u00b7 B=${input.prismAngleDeg.toFixed(3)}\u00b0`;\n    }\n\n    function createWorker() {\n      if (worker) worker.terminate();\n      const source = $('solver-worker').textContent;\n      const blob = new Blob([source], { type: 'text\/javascript' });\n      worker = new Worker(URL.createObjectURL(blob));\n      return worker;\n    }\n\n    function setBusy(busy) {\n      dom.calculateBtn.disabled = busy;\n      dom.calculateBtn.innerHTML = busy ? '<span class=\"spinner\"><\/span>\u6b63\u5728\u8ba1\u7b97\u2026' : '\u8ba1\u7b97\u6a21\u5f0f';\n    }\n\n    function calculate() {\n      clearMessage();\n      let input;\n      try {\n        input = collectInput();\n      } catch (error) {\n        setMessage(error.message, 'error');\n        return;\n      }\n\n      setBusy(true);\n      dom.summaryStructure.textContent = structureSummary(input);\n      dom.summaryPrism.textContent = prismSummary(input);\n      dom.summaryCount.textContent = '\u8ba1\u7b97\u4e2d\u2026';\n      dom.summaryTime.textContent = '\u2014';\n      dom.legend.classList.toggle('show', input.kind === 'dual');\n\n      const activeWorker = createWorker();\n      activeWorker.onmessage = event => {\n        setBusy(false);\n        const data = event.data;\n        if (!data.ok) {\n          setMessage(data.error || '\u8ba1\u7b97\u5931\u8d25\u3002', 'error');\n          dom.summaryCount.textContent = '0';\n          return;\n        }\n        currentRows = data.modes;\n        renderRows(currentRows, input.kind);\n        dom.summaryCount.textContent = String(currentRows.length);\n        dom.summaryTime.textContent = `${data.elapsedMs.toFixed(1)} ms`;\n        if (!currentRows.length) setMessage('\u5f53\u524d\u53c2\u6570\u8303\u56f4\u5185\u6ca1\u6709\u627e\u5230\u6ee1\u8db3\u76f8\u4f4d\u6761\u4ef6\u7684\u6a21\u5f0f\uff0c\u8bf7\u68c0\u67e5\u819c\u5c42\u6298\u5c04\u7387\u3001\u539a\u5ea6\u6216\u4e0a\u5305\u5c42\u53c2\u6570\u3002', 'info');\n      };\n      activeWorker.onerror = event => {\n        setBusy(false);\n        setMessage(`\u8ba1\u7b97\u7ebf\u7a0b\u5f02\u5e38\uff1a${event.message || '\u672a\u77e5\u9519\u8bef'}`, 'error');\n      };\n      activeWorker.postMessage(input);\n    }\n\n    function clearResults() {\n      currentRows = [];\n      dom.resultBody.innerHTML = '<tr class=\"empty-row\"><td colspan=\"7\">\u8f93\u5165\u53c2\u6570\u540e\u70b9\u51fb\u201c\u8ba1\u7b97\u6a21\u5f0f\u201d<\/td><\/tr>';\n      dom.summaryStructure.textContent = '\u2014';\n      dom.summaryPrism.textContent = '\u2014';\n      dom.summaryCount.textContent = '0';\n      dom.summaryTime.textContent = '\u2014';\n      dom.legend.classList.remove('show');\n      clearMessage();\n    }\n\n    function exportCsv() {\n      if (!currentRows.length) {\n        setMessage('\u6682\u65e0\u53ef\u5bfc\u51fa\u7684\u6a21\u5f0f\u7ed3\u679c\u3002', 'error');\n        return;\n      }\n      const header = ['Mode', 'Angle(ext)', 'Beta', 'Angle(int)', 'Phi(bot)', 'Phi(top)', 'Kz', 'Branch'];\n      const lines = [header.join(',')];\n      for (const row of currentRows) {\n        lines.push([\n          row.mode,\n          formatNumber(row.angleExtDeg, 10),\n          formatNumber(row.beta, 12),\n          formatNumber(row.angleIntDeg, 10),\n          formatNumber(row.phiBot, 12),\n          formatNumber(row.phiTop, 12),\n          formatNumber(row.kz, 12),\n          row.branch\n        ].join(','));\n      }\n      const blob = new Blob(['\\uFEFF' + lines.join('\\n')], { type: 'text\/csv;charset=utf-8' });\n      const url = URL.createObjectURL(blob);\n      const a = document.createElement('a');\n      a.href = url;\n      a.download = `\u6a21\u5f0f\u8ba1\u7b97\u7ed3\u679c_${new Date().toISOString().slice(0, 19).replace(\/[:T]\/g, '-')}.csv`;\n      a.click();\n      URL.revokeObjectURL(url);\n    }\n\n    dom.singleTab.addEventListener('click', () => switchStructure('single'));\n    dom.dualTab.addEventListener('click', () => switchStructure('dual'));\n    dom.autoPrism.addEventListener('change', updateAutoPrism);\n    $('prismCode').addEventListener('input', updateAutoPrism);\n    $('wavelength').addEventListener('input', updateAutoPrism);\n    document.querySelectorAll('input[name=\"pol\"]').forEach(el => el.addEventListener('change', updateAutoPrism));\n    dom.calculateBtn.addEventListener('click', calculate);\n    dom.clearBtn.addEventListener('click', clearResults);\n    dom.printBtn.addEventListener('click', () => window.print());\n    dom.exportBtn.addEventListener('click', exportCsv);\n\n    updateAutoPrism();\n  <\/script>\n<\/body>\n<\/html>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>\u6a21\u5f0f\u8ba1\u7b97\u5668 \u25bd \u6a21\u5f0f\u8ba1\u7b97\u5668 Prism <\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-10453","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>\u6a21\u5f0f\u8ba1\u7b97\u5668 - VoyaWave<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/voyawave.com\/zh-hans\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\/\" \/>\n<meta property=\"og:locale\" content=\"zh_CN\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"\u6a21\u5f0f\u8ba1\u7b97\u5668 - VoyaWave\" \/>\n<meta property=\"og:description\" content=\"\u6a21\u5f0f\u8ba1\u7b97\u5668 \u25bd \u6a21\u5f0f\u8ba1\u7b97\u5668 Prism\" \/>\n<meta property=\"og:url\" content=\"https:\/\/voyawave.com\/zh-hans\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\/\" \/>\n<meta property=\"og:site_name\" content=\"VoyaWave\" \/>\n<meta property=\"article:modified_time\" content=\"2026-08-10T07:08:22+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 \u5206\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/voyawave.com\\\/zh-hans\\\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\\\/\",\"url\":\"https:\\\/\\\/voyawave.com\\\/zh-hans\\\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\\\/\",\"name\":\"\u6a21\u5f0f\u8ba1\u7b97\u5668 - VoyaWave\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/springgreen-quail-104003.hostingersite.com\\\/#website\"},\"datePublished\":\"2026-08-06T08:37:45+00:00\",\"dateModified\":\"2026-08-10T07:08:22+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/voyawave.com\\\/zh-hans\\\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\\\/#breadcrumb\"},\"inLanguage\":\"zh-Hans\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/voyawave.com\\\/zh-hans\\\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/voyawave.com\\\/zh-hans\\\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/voyawave.com\\\/zh-hans\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"\u6a21\u5f0f\u8ba1\u7b97\u5668\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/springgreen-quail-104003.hostingersite.com\\\/#website\",\"url\":\"https:\\\/\\\/springgreen-quail-104003.hostingersite.com\\\/\",\"name\":\"Shenzhen VoyaWave Optics Technology Co., Ltd.\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/springgreen-quail-104003.hostingersite.com\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/springgreen-quail-104003.hostingersite.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"zh-Hans\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/springgreen-quail-104003.hostingersite.com\\\/#organization\",\"name\":\"\u542f\u626c\u5149\u5b66\",\"url\":\"https:\\\/\\\/springgreen-quail-104003.hostingersite.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"zh-Hans\",\"@id\":\"https:\\\/\\\/springgreen-quail-104003.hostingersite.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/voyawave.com\\\/wp-content\\\/uploads\\\/2025\\\/05\\\/2-240920110542592-scaled.png\",\"contentUrl\":\"https:\\\/\\\/voyawave.com\\\/wp-content\\\/uploads\\\/2025\\\/05\\\/2-240920110542592-scaled.png\",\"width\":2560,\"height\":766,\"caption\":\"\u542f\u626c\u5149\u5b66\"},\"image\":{\"@id\":\"https:\\\/\\\/springgreen-quail-104003.hostingersite.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"\u6a21\u5f0f\u8ba1\u7b97\u5668 - VoyaWave","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/voyawave.com\/zh-hans\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\/","og_locale":"zh_CN","og_type":"article","og_title":"\u6a21\u5f0f\u8ba1\u7b97\u5668 - VoyaWave","og_description":"\u6a21\u5f0f\u8ba1\u7b97\u5668 \u25bd \u6a21\u5f0f\u8ba1\u7b97\u5668 Prism","og_url":"https:\/\/voyawave.com\/zh-hans\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\/","og_site_name":"VoyaWave","article_modified_time":"2026-08-10T07:08:22+00:00","twitter_card":"summary_large_image","twitter_misc":{"\u9884\u8ba1\u9605\u8bfb\u65f6\u95f4":"1 \u5206"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/voyawave.com\/zh-hans\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\/","url":"https:\/\/voyawave.com\/zh-hans\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\/","name":"\u6a21\u5f0f\u8ba1\u7b97\u5668 - VoyaWave","isPartOf":{"@id":"https:\/\/springgreen-quail-104003.hostingersite.com\/#website"},"datePublished":"2026-08-06T08:37:45+00:00","dateModified":"2026-08-10T07:08:22+00:00","breadcrumb":{"@id":"https:\/\/voyawave.com\/zh-hans\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\/#breadcrumb"},"inLanguage":"zh-Hans","potentialAction":[{"@type":"ReadAction","target":["https:\/\/voyawave.com\/zh-hans\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/voyawave.com\/zh-hans\/%e6%a8%a1%e5%bc%8f%e8%ae%a1%e7%ae%97%e5%99%a8\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/voyawave.com\/zh-hans\/"},{"@type":"ListItem","position":2,"name":"\u6a21\u5f0f\u8ba1\u7b97\u5668"}]},{"@type":"WebSite","@id":"https:\/\/springgreen-quail-104003.hostingersite.com\/#website","url":"https:\/\/springgreen-quail-104003.hostingersite.com\/","name":"Shenzhen VoyaWave Optics Technology Co., Ltd.","description":"","publisher":{"@id":"https:\/\/springgreen-quail-104003.hostingersite.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/springgreen-quail-104003.hostingersite.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"zh-Hans"},{"@type":"Organization","@id":"https:\/\/springgreen-quail-104003.hostingersite.com\/#organization","name":"\u542f\u626c\u5149\u5b66","url":"https:\/\/springgreen-quail-104003.hostingersite.com\/","logo":{"@type":"ImageObject","inLanguage":"zh-Hans","@id":"https:\/\/springgreen-quail-104003.hostingersite.com\/#\/schema\/logo\/image\/","url":"https:\/\/voyawave.com\/wp-content\/uploads\/2025\/05\/2-240920110542592-scaled.png","contentUrl":"https:\/\/voyawave.com\/wp-content\/uploads\/2025\/05\/2-240920110542592-scaled.png","width":2560,"height":766,"caption":"\u542f\u626c\u5149\u5b66"},"image":{"@id":"https:\/\/springgreen-quail-104003.hostingersite.com\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/voyawave.com\/zh-hans\/wp-json\/wp\/v2\/pages\/10453","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/voyawave.com\/zh-hans\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/voyawave.com\/zh-hans\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/voyawave.com\/zh-hans\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/voyawave.com\/zh-hans\/wp-json\/wp\/v2\/comments?post=10453"}],"version-history":[{"count":6,"href":"https:\/\/voyawave.com\/zh-hans\/wp-json\/wp\/v2\/pages\/10453\/revisions"}],"predecessor-version":[{"id":10554,"href":"https:\/\/voyawave.com\/zh-hans\/wp-json\/wp\/v2\/pages\/10453\/revisions\/10554"}],"wp:attachment":[{"href":"https:\/\/voyawave.com\/zh-hans\/wp-json\/wp\/v2\/media?parent=10453"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}