{"id":21112,"date":"2026-08-13T12:06:03","date_gmt":"2026-08-13T12:06:03","guid":{"rendered":"https:\/\/jobsnocap.com\/?page_id=21112"},"modified":"2026-08-13T12:07:52","modified_gmt":"2026-08-13T12:07:52","slug":"proposalx","status":"publish","type":"page","link":"https:\/\/jobsnocap.com\/lo\/proposalx\/","title":{"rendered":"proposalx"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"21112\" class=\"elementor elementor-21112\" data-elementor-settings=\"{&quot;ha_cmc_init_switcher&quot;:&quot;no&quot;}\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-cca35b6 e-con-full e-flex e-con e-parent\" data-id=\"cca35b6\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;_ha_eqh_enable&quot;:false}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0ccadac elementor-widget elementor-widget-html\" data-id=\"0ccadac\" 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=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Proposal Analysis Dashboard<\/title>\n  \n  <!-- FontAwesome Icons via CDN -->\n  <link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.4.0\/css\/all.min.css\">\n  \n  <!-- Google Fonts: Inter -->\n  <link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n  <link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n  <link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@300;400;500;600;700;800&display=swap\" rel=\"stylesheet\">\n  \n  <!-- Document Parsing Libraries via CDN -->\n  <!-- JSZip for PowerPoint (.pptx) Parsing -->\n  <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jszip\/3.10.1\/jszip.min.js\"><\/script>\n  <!-- Mammoth for Word (.docx) Parsing -->\n  <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/mammoth\/1.6.0\/mammoth.browser.min.js\"><\/script>\n  <!-- SheetJS for Excel (.xlsx\/.xls) Parsing -->\n  <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/xlsx\/0.18.5\/xlsx.full.min.js\"><\/script>\n  <!-- PDF.js for PDF Parsing -->\n  <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.4.120\/pdf.min.js\"><\/script>\n  \n  <!-- Tailwind CSS via CDN -->\n  <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n  <script>\n    \/\/ Set worker source path for PDF.js immediately\n    pdfjsLib.GlobalWorkerOptions.workerSrc = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.4.120\/pdf.worker.min.js';\n\n    tailwind.config = {\n      important: '#proposal-analyzer-app',\n      theme: {\n        extend: {\n          fontFamily: {\n            sans: ['Inter', 'sans-serif'],\n          },\n          colors: {\n            indigo: {\n              50: '#f5f3ff',\n              100: '#e0e7ff',\n              500: '#6366f1',\n              600: '#4f46e5',\n              700: '#4338ca',\n              800: '#3730a3',\n            }\n          }\n        }\n      }\n    }\n  <\/script>\n\n  <!-- Scoped CSS Resets for WordPress Isolation -->\n  <style>\n    \/* Prevent leaks from WordPress or other global themes by isolation *\/\n    #proposal-analyzer-app {\n      all: initial; \/* Reset all standard styles on the app wrapper *\/\n      display: block;\n      background-color: #f8fafc; \/* slate-50 *\/\n      color: #0f172a; \/* slate-900 *\/\n      font-family: 'Inter', system-ui, -apple-system, sans-serif;\n      min-height: 100vh;\n      box-sizing: border-box;\n      line-height: 1.5;\n      font-size: 16px;\n    }\n\n    #proposal-analyzer-app * {\n      box-sizing: border-box;\n      transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform;\n      transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n      transition-duration: 200ms;\n    }\n\n    \/* Core typography reset inside container *\/\n    #proposal-analyzer-app h1, \n    #proposal-analyzer-app h2, \n    #proposal-analyzer-app h3, \n    #proposal-analyzer-app h4, \n    #proposal-analyzer-app h5, \n    #proposal-analyzer-app h6 {\n      font-family: 'Inter', system-ui, sans-serif;\n      font-weight: 700;\n      color: #1e293b;\n      margin: 0;\n    }\n\n    #proposal-analyzer-app button, \n    #proposal-analyzer-app input, \n    #proposal-analyzer-app select, \n    #proposal-analyzer-app textarea {\n      font-family: 'Inter', sans-serif;\n      font-size: 100%;\n      line-height: inherit;\n      color: inherit;\n      margin: 0;\n      padding: 0;\n      border-radius: 0.375rem;\n    }\n\n    #proposal-analyzer-app textarea {\n      resize: vertical;\n    }\n\n    #proposal-analyzer-app svg {\n      display: inline-block;\n    }\n\n    \/* Custom scrollbar styling inside analyzer elements *\/\n    #proposal-analyzer-app ::-webkit-scrollbar {\n      width: 6px;\n      height: 6px;\n    }\n    #proposal-analyzer-app ::-webkit-scrollbar-track {\n      background: #f1f5f9;\n    }\n    #proposal-analyzer-app ::-webkit-scrollbar-thumb {\n      background: #cbd5e1;\n      border-radius: 3px;\n    }\n    #proposal-analyzer-app ::-webkit-scrollbar-thumb:hover {\n      background: #94a3b8;\n    }\n\n    \/* Custom Spinner Keyframe *\/\n    @keyframes custom-spin {\n      to { transform: rotate(360deg); }\n    }\n    .animate-spin-custom {\n      animation: custom-spin 1s linear infinite;\n    }\n  <\/style>\n<\/head>\n<body class=\"bg-gray-100 m-0 p-0\">\n\n  <!-- WordPress Isolated App Wrapper -->\n  <div id=\"proposal-analyzer-app\" class=\"w-full relative overflow-x-hidden\">\n    \n    <!-- Top Glassmorphic Navigation -->\n    <header class=\"sticky top-0 z-40 bg-white\/80 backdrop-blur-md border-b border-slate-200\/80 px-4 py-4 md:px-8 flex items-center justify-between shadow-sm\">\n      <div class=\"flex items-center gap-3\">\n        <div class=\"bg-indigo-600 text-white p-2 rounded-xl flex items-center justify-center shadow-md shadow-indigo-200\">\n          <i class=\"fa-solid fa-square-poll-horizontal text-xl\"><\/i>\n        <\/div>\n        <h1 class=\"text-xl md:text-2xl font-extrabold bg-gradient-to-r from-indigo-600 to-indigo-800 bg-clip-text text-transparent tracking-tight\" data-i18n=\"title\">\n          Proposal Analysis Dashboard\n        <\/h1>\n      <\/div>\n      \n      <div class=\"flex items-center gap-4\">\n        <!-- Language Dropdown -->\n        <div class=\"relative\">\n          <select id=\"lang-selector\" class=\"appearance-none bg-slate-50 border border-slate-200 text-slate-700 font-semibold px-4 py-2 pr-8 rounded-xl cursor-pointer hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-indigo-500\/20 text-sm transition-all shadow-sm\">\n            <option value=\"en\">English \ud83c\uddec\ud83c\udde7<\/option>\n            <option value=\"lo\">\u0e9e\u0eb2\u0eaa\u0eb2\u0ea5\u0eb2\u0ea7 \ud83c\uddf1\ud83c\udde6<\/option>\n          <\/select>\n          <div class=\"absolute right-3 top-3 pointer-events-none text-slate-400 text-xs\">\n            <i class=\"fa-solid fa-chevron-down\"><\/i>\n          <\/div>\n        <\/div>\n        \n        <!-- Settings Gear Button -->\n        <button id=\"gear-btn\" class=\"w-10 h-10 flex items-center justify-center bg-slate-50 hover:bg-slate-100 border border-slate-200 text-slate-600 hover:text-indigo-600 rounded-xl transition shadow-sm hover:rotate-45\" title=\"Settings\">\n          <i class=\"fa-solid fa-gear text-lg\"><\/i>\n        <\/button>\n      <\/div>\n    <\/header>\n\n    <!-- Main Content Layout Grid -->\n    <main class=\"max-w-7xl mx-auto px-4 py-8 md:px-8\">\n      <div class=\"grid grid-cols-1 lg:grid-cols-12 gap-8 items-start\">\n        \n        <!-- LEFT COLUMN: Inputs & Options (5 cols) -->\n        <section class=\"lg:col-span-5 space-y-6\">\n          \n          <!-- Input Card 1: Call for Proposal Criteria -->\n          <div class=\"bg-white border border-slate-200\/80 rounded-2xl p-5 shadow-md shadow-slate-100 hover:shadow-lg transition-all duration-300\">\n            <div class=\"flex items-center justify-between mb-3\">\n              <div class=\"flex items-center gap-2\">\n                <div class=\"w-1.5 h-6 bg-indigo-600 rounded-full\"><\/div>\n                <h3 class=\"text-sm font-extrabold text-slate-800\" data-i18n=\"callCriteria\">Call for Proposal Criteria<\/h3>\n              <\/div>\n              <label class=\"cursor-pointer bg-slate-50 hover:bg-indigo-50 hover:text-indigo-600 text-slate-500 transition px-3 py-1.5 rounded-xl text-xs font-semibold flex items-center gap-1.5 border border-slate-200 hover:border-indigo-100 shadow-sm select-none\">\n                <i class=\"fa-solid fa-file-arrow-up\"><\/i>\n                <span data-i18n=\"fileUpload\">Upload File<\/span>\n                <input type=\"file\" accept=\".txt,.pdf,.docx,.xlsx,.xls,.pptx\" class=\"hidden file-input\" data-target=\"criteria-input\">\n              <\/label>\n            <\/div>\n            <textarea id=\"criteria-input\" class=\"w-full h-44 p-4 border border-slate-200 rounded-xl focus:outline-none focus:ring-4 focus:ring-indigo-500\/10 focus:border-indigo-500 text-sm bg-slate-50\/50 hover:bg-slate-50 focus:bg-white transition-all\" data-i18n=\"callCriteriaPlaceholder\" placeholder=\"Paste the call requirements or upload a file (PDF, Word, Excel, PPTX, TXT)...\"><\/textarea>\n          <\/div>\n\n          <!-- Input Card 2: Draft Proposal -->\n          <div class=\"bg-white border border-slate-200\/80 rounded-2xl p-5 shadow-md shadow-slate-100 hover:shadow-lg transition-all duration-300\">\n            <div class=\"flex items-center justify-between mb-3\">\n              <div class=\"flex items-center gap-2\">\n                <div class=\"w-1.5 h-6 bg-indigo-600 rounded-full\"><\/div>\n                <h3 class=\"text-sm font-extrabold text-slate-800\" data-i18n=\"draftProposal\">Draft Proposal<\/h3>\n              <\/div>\n              <label class=\"cursor-pointer bg-slate-50 hover:bg-indigo-50 hover:text-indigo-600 text-slate-500 transition px-3 py-1.5 rounded-xl text-xs font-semibold flex items-center gap-1.5 border border-slate-200 hover:border-indigo-100 shadow-sm select-none\">\n                <i class=\"fa-solid fa-file-arrow-up\"><\/i>\n                <span data-i18n=\"fileUpload\">Upload File<\/span>\n                <input type=\"file\" accept=\".txt,.pdf,.docx,.xlsx,.xls,.pptx\" class=\"hidden file-input\" data-target=\"proposal-input\">\n              <\/label>\n            <\/div>\n            <textarea id=\"proposal-input\" class=\"w-full h-48 p-4 border border-slate-200 rounded-xl focus:outline-none focus:ring-4 focus:ring-indigo-500\/10 focus:border-indigo-500 text-sm bg-slate-50\/50 hover:bg-slate-50 focus:bg-white transition-all\" data-i18n=\"draftProposalPlaceholder\" placeholder=\"Paste your draft proposal or upload a file (PDF, Word, Excel, PPTX, TXT)...\"><\/textarea>\n          <\/div>\n\n          <!-- Primary Action Button -->\n          <button id=\"analyze-btn\" class=\"w-full py-4 bg-indigo-600 hover:bg-indigo-700 text-white rounded-2xl text-base font-extrabold flex items-center justify-center gap-2.5 transition duration-300 shadow-md shadow-indigo-100 hover:shadow-lg active:scale-[0.98]\">\n            <i class=\"fa-solid fa-magnifying-glass-chart\"><\/i>\n            <span id=\"analyze-btn-text\" data-i18n=\"analyzeBtn\">Analyze Proposal<\/span>\n            <span id=\"analyze-spinner\" class=\"hidden\">\n              <svg class=\"animate-spin-custom h-5 w-5 text-white\" fill=\"none\" viewBox=\"0 0 24 24\">\n                <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"><\/circle>\n                <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"><\/path>\n              <\/svg>\n            <\/span>\n          <\/button>\n\n        <\/section>\n\n        <!-- RIGHT COLUMN: Visualization Dashboard (7 cols) -->\n        <section class=\"lg:col-span-7 h-full\">\n          \n          <!-- Empty State (Shown by default) -->\n          <div id=\"dashboard-empty-state\" class=\"bg-white border border-slate-200 border-dashed rounded-3xl p-8 md:p-12 flex flex-col items-center justify-center text-center h-full min-h-[500px] shadow-sm\">\n            <div class=\"w-24 h-24 bg-slate-50 rounded-full flex items-center justify-center text-slate-300 mb-5 border border-slate-100 shadow-inner\">\n              <i class=\"fa-solid fa-chart-line text-4xl bg-gradient-to-br from-indigo-500 to-purple-600 bg-clip-text text-transparent\"><\/i>\n            <\/div>\n            <h4 class=\"text-lg font-extrabold text-slate-700 mb-2\" data-i18n=\"emptyStateTitle\">Ready for Analysis<\/h4>\n            <p class=\"text-sm text-slate-400 max-w-sm leading-relaxed\" data-i18n=\"emptyStateDesc\">Enter the Call Criteria and your Draft Proposal on the left, then click \"Analyze Proposal\" to view the detailed visual breakdown.<\/p>\n          <\/div>\n\n          <!-- Dynamic Analysis Results Dashboard (Hidden initially) -->\n          <div id=\"dashboard-results\" class=\"space-y-6 hidden\">\n            \n            <!-- Overall Score Row -->\n            <div class=\"bg-white border border-slate-200\/80 rounded-3xl p-6 shadow-md shadow-slate-100 flex items-center justify-center relative overflow-hidden\">\n              \n              <!-- Circular Score Gauge -->\n              <div class=\"flex flex-col items-center justify-center p-2 w-full\">\n                <div class=\"relative w-40 h-40 flex items-center justify-center\">\n                  <svg class=\"w-full h-full transform -rotate-90\" viewBox=\"0 0 100 100\">\n                    <!-- Gauge Background Track -->\n                    <circle cx=\"50\" cy=\"50\" r=\"40\" stroke=\"#f1f5f9\" stroke-width=\"8.5\" fill=\"transparent\" \/>\n                    <!-- Gauge Dynamic Colored Path -->\n                    <circle id=\"score-gauge-circle\" cx=\"50\" cy=\"50\" r=\"40\" stroke=\"#6366f1\" stroke-width=\"8.5\" fill=\"transparent\"\n                            stroke-dasharray=\"251.32\" stroke-dashoffset=\"251.32\" stroke-linecap=\"round\" class=\"transition-all duration-1000 ease-out\" \/>\n                  <\/svg>\n                  \n                  <div class=\"absolute flex flex-col items-center justify-center\">\n                    <span id=\"score-gauge-text\" class=\"text-5xl font-extrabold text-slate-800 tracking-tight leading-none\">0<\/span>\n                    <span class=\"text-[9px] uppercase font-extrabold tracking-widest text-slate-400 mt-1\" data-i18n=\"scoreTitle\">Overall Score<\/span>\n                  <\/div>\n                <\/div>\n              <\/div>\n              \n            <\/div>\n            \n            <!-- Detailed Criteria List section -->\n            <div class=\"space-y-4\">\n              <div class=\"flex items-center gap-2 border-b border-slate-200\/50 pb-3\">\n                <div class=\"bg-indigo-50 text-indigo-600 w-8 h-8 rounded-lg flex items-center justify-center\">\n                  <i class=\"fa-solid fa-list-check\"><\/i>\n                <\/div>\n                <h4 class=\"text-base font-extrabold text-slate-800\" data-i18n=\"criteriaBreakdown\">\n                  Detailed Criteria Breakdown\n                <\/h4>\n              <\/div>\n              \n              <!-- Dynamic Grid of Cards injected here -->\n              <div id=\"criteria-cards-grid\" class=\"grid grid-cols-1 md:grid-cols-2 gap-5\"><\/div>\n            <\/div>\n            \n          <\/div>\n\n        <\/section>\n        \n      <\/div>\n    <\/main>\n\n    <!-- SETTINGS MODAL DIALOG (Includes Admin verification screens) -->\n    <div id=\"settings-modal\" class=\"fixed inset-0 bg-slate-900\/60 backdrop-blur-sm z-50 flex items-center justify-center hidden opacity-0 transition-opacity duration-300\">\n      \n      <!-- Settings Panel -->\n      <div id=\"modal-card\" class=\"bg-white rounded-3xl shadow-2xl w-full max-w-md mx-4 overflow-hidden transform scale-95 transition-transform duration-300 border border-slate-100\">\n        \n        <!-- Screen 1: Password Authenticator -->\n        <div id=\"modal-password-screen\" class=\"p-6\">\n          <div class=\"flex items-center gap-3 mb-4 text-indigo-600\">\n            <div class=\"bg-indigo-50 p-2.5 rounded-2xl\">\n              <i class=\"fa-solid fa-lock text-xl\"><\/i>\n            <\/div>\n            <h3 class=\"text-lg font-bold text-slate-800\" data-i18n=\"settings\">Settings<\/h3>\n          <\/div>\n          \n          <p class=\"text-xs text-slate-500 mb-4 leading-relaxed\" data-i18n=\"passwordPrompt\">Enter administrator password to access settings:<\/p>\n          \n          <div class=\"mb-5\">\n            <input type=\"password\" id=\"admin-password-input\" class=\"w-full p-3 border border-slate-200 bg-slate-50\/50 focus:bg-white rounded-xl focus:ring-4 focus:ring-indigo-500\/10 focus:border-indigo-500 focus:outline-none transition-all text-center tracking-widest text-lg font-bold\" placeholder=\"\u2022\u2022\u2022\u2022\">\n            \n            <p id=\"password-error\" class=\"text-rose-600 text-xs mt-2 hidden flex items-center gap-1.5 justify-center\">\n              <i class=\"fa-solid fa-circle-exclamation\"><\/i>\n              <span data-i18n=\"incorrectPassword\">Incorrect password!<\/span>\n            <\/p>\n          <\/div>\n          \n          <div class=\"flex justify-end gap-3 pt-2\">\n            <button id=\"close-modal-btn\" class=\"px-4 py-2.5 border border-slate-200 text-slate-600 hover:bg-slate-50 rounded-xl text-xs font-bold transition shadow-sm\" data-i18n=\"cancel\">Cancel<\/button>\n            <button id=\"submit-password-btn\" class=\"px-5 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-xl text-xs font-bold transition shadow-sm shadow-indigo-100\">Confirm<\/button>\n          <\/div>\n        <\/div>\n\n        <!-- Screen 2: Configurator Panel -->\n        <div id=\"modal-config-screen\" class=\"p-6 hidden\">\n          <div class=\"flex items-center gap-3 mb-5 text-indigo-600\">\n            <div class=\"bg-indigo-50 p-2.5 rounded-2xl\">\n              <i class=\"fa-solid fa-sliders text-xl\"><\/i>\n            <\/div>\n            <h3 class=\"text-lg font-bold text-slate-800\" data-i18n=\"settings\">Settings<\/h3>\n          <\/div>\n          \n          <div class=\"space-y-4 mb-6\">\n            <!-- API Key input -->\n            <div>\n              <label class=\"block text-xs font-bold text-slate-700 mb-1.5 uppercase tracking-wide\" data-i18n=\"apiKey\">API Key<\/label>\n              <div class=\"relative\">\n                <input type=\"password\" id=\"api-key-input\" class=\"w-full p-3 pr-10 border border-slate-200 bg-slate-50\/50 focus:bg-white rounded-xl focus:ring-4 focus:ring-indigo-500\/10 focus:border-indigo-500 focus:outline-none text-xs font-mono transition-all\" placeholder=\"AQ.Ab8...\">\n                <button id=\"toggle-key-visibility\" class=\"absolute right-3 top-3 text-slate-400 hover:text-slate-600 transition\" type=\"button\">\n                  <i class=\"fa-solid fa-eye\" id=\"eye-icon\"><\/i>\n                <\/button>\n              <\/div>\n            <\/div>\n\n            <!-- Model Selection dropdown -->\n            <div>\n              <label class=\"block text-xs font-bold text-slate-700 mb-1.5 uppercase tracking-wide\" data-i18n=\"modelName\">Model Name<\/label>\n              <select id=\"model-selector\" class=\"w-full p-3 border border-slate-200 bg-slate-50\/50 focus:bg-white rounded-xl focus:ring-4 focus:ring-indigo-500\/10 focus:border-indigo-500 focus:outline-none text-xs font-semibold transition-all\">\n                <option value=\"gemini-3.5-flash\">Gemini 3.5 Flash (Recommended)<\/option>\n                <option value=\"gemini-2.5-flash\">Gemini 2.5 Flash<\/option>\n                <option value=\"gemini-flash-latest\">Gemini Flash Latest<\/option>\n                <option value=\"custom\">Custom...<\/option>\n              <\/select>\n            <\/div>\n            \n            <!-- Custom Model Input (shown only if custom is selected) -->\n            <div id=\"custom-model-container\" class=\"hidden\">\n              <input type=\"text\" id=\"custom-model-input\" class=\"w-full p-3 border border-slate-200 bg-slate-50\/50 focus:bg-white rounded-xl focus:ring-4 focus:ring-indigo-500\/10 focus:border-indigo-500 focus:outline-none text-xs font-mono transition-all\" placeholder=\"gemini-2.5-pro\">\n            <\/div>\n            \n            <div class=\"flex justify-between items-center bg-slate-50 p-3.5 border border-slate-200\/50 rounded-2xl\">\n              <span class=\"text-[11px] text-slate-500 font-semibold leading-tight\">Pre-configured Admin API Key:<\/span>\n              <button id=\"reset-default-btn\" class=\"text-xs text-indigo-600 hover:text-indigo-800 font-bold transition flex items-center gap-1.5\">\n                <i class=\"fa-solid fa-rotate-left\"><\/i>\n                <span data-i18n=\"resetToDefault\">Use Default Key<\/span>\n              <\/button>\n            <\/div>\n          <\/div>\n\n          <div class=\"flex justify-end gap-3 border-t border-slate-100 pt-4\">\n            <button id=\"close-config-btn\" class=\"px-4 py-2.5 border border-slate-200 text-slate-600 hover:bg-slate-50 rounded-xl text-xs font-bold transition shadow-sm\" data-i18n=\"cancel\">Cancel<\/button>\n            <button id=\"save-config-btn\" class=\"px-5 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-xl text-xs font-bold transition shadow-sm shadow-indigo-100\" data-i18n=\"save\">Save Settings<\/button>\n          <\/div>\n        <\/div>\n\n      <\/div>\n    <\/div>\n\n  <\/div>\n\n  <!-- APPLICATION CORE JAVASCRIPT -->\n  <script>\n    \/\/ ----------------------------------------------------\n    \/\/ CONSTANTS & DICTIONARIES\n    \/\/ ----------------------------------------------------\n    const DEFAULT_API_KEY = \"AQ.Ab8RN6JjX1LNtTjOMpEDeBktyZiKjWXMoky885rdG3_Ytsi7Rg\";\n    const DEFAULT_MODEL = \"gemini-3.5-flash\";\n    const ADMIN_PASSWORD = \"1243\";\n    \n    const TRANSLATIONS = {\n      en: {\n        title: \"Proposal Analysis Dashboard\",\n        callCriteria: \"Call for Proposal Criteria\",\n        callCriteriaPlaceholder: \"Paste the call requirements or upload a file (PDF, Word, Excel, PPTX, TXT)...\",\n        draftProposal: \"Draft Proposal\",\n        draftProposalPlaceholder: \"Paste your draft proposal or upload a file (PDF, Word, Excel, PPTX, TXT)...\",\n        analyzeBtn: \"Analyze Proposal\",\n        analyzingBtn: \"Analyzing...\",\n        settings: \"Settings\",\n        passwordPrompt: \"Enter administrator password to access settings:\",\n        incorrectPassword: \"Incorrect password!\",\n        apiKey: \"API Key\",\n        modelName: \"Model Name\",\n        save: \"Save Settings\",\n        cancel: \"Cancel\",\n        scoreTitle: \"Overall Score\",\n        criteriaBreakdown: \"Detailed Criteria Breakdown\",\n        strengths: \"Strengths\",\n        improvements: \"Improvements\",\n        missingKey: \"Please configure your API key in settings first!\",\n        missingInputs: \"Please enter both the Call for Proposal Criteria and the Draft Proposal.\",\n        errorOccurred: \"An error occurred during analysis: \",\n        noStrengths: \"No specific strengths identified.\",\n        noImprovements: \"No improvements suggested.\",\n        rating: \"Rating\",\n        fileUpload: \"Upload File\",\n        settingsSaved: \"Settings saved successfully!\",\n        resetToDefault: \"Use Default Key\",\n        emptyStateTitle: \"Ready for Analysis\",\n        emptyStateDesc: \"Enter the Call Criteria and your Draft Proposal on the left, then click \\\"Analyze Proposal\\\" to view the detailed visual breakdown.\"\n      },\n      lo: {\n        title: \"\u0ec1\u0e9c\u0e87\u0ea7\u0eb4\u0ec0\u0e84\u0eb2\u0eb0\u0e82\u0ecd\u0ec9\u0eaa\u0eb0\u0ec0\u0edc\u0eb5 (Proposal)\",\n        callCriteria: \"\u0ec0\u0e81\u0e99\u0e81\u0eb2\u0e99\u0e84\u0eb1\u0e94\u0ec0\u0ea5\u0eb7\u0ead\u0e81\u0e82\u0ead\u0e87 Call for Proposal\",\n        callCriteriaPlaceholder: \"\u0ea7\u0eb2\u0e87\u0e82\u0ecd\u0ec9\u0e81\u0eb3\u0e99\u0ebb\u0e94\u0e82\u0ead\u0e87\u0e97\u0eb6\u0e99 \u0eab\u0ebc\u0eb7 \u0ead\u0eb1\u0e9a\u0ec2\u0eab\u0ea5\u0e94\u0ec4\u0e9f\u0ea5\u0ecc (PDF, Word, Excel, PPTX, TXT)...\",\n        draftProposal: \"\u0eae\u0ec8\u0eb2\u0e87\u0e82\u0ecd\u0ec9\u0eaa\u0eb0\u0ec0\u0edc\u0eb5 (Draft Proposal)\",\n        draftProposalPlaceholder: \"\u0ea7\u0eb2\u0e87\u0eae\u0ec8\u0eb2\u0e87\u0e82\u0ecd\u0ec9\u0eaa\u0eb0\u0ec0\u0edc\u0eb5\u0e82\u0ead\u0e87\u0e97\u0ec8\u0eb2\u0e99 \u0eab\u0ebc\u0eb7 \u0ead\u0eb1\u0e9a\u0ec2\u0eab\u0ea5\u0e94\u0ec4\u0e9f\u0ea5\u0ecc (PDF, Word, Excel, PPTX, TXT)...\",\n        analyzeBtn: \"\u0ea7\u0eb4\u0ec0\u0e84\u0eb2\u0eb0\u0e82\u0ecd\u0ec9\u0eaa\u0eb0\u0ec0\u0edc\u0eb5\",\n        analyzingBtn: \"\u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0ea7\u0eb4\u0ec0\u0e84\u0eb2\u0eb0...\",\n        settings: \"\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2\",\n        passwordPrompt: \"\u0e9b\u0ec9\u0ead\u0e99\u0ea5\u0eb0\u0eab\u0eb1\u0e94\u0e9c\u0ec8\u0eb2\u0e99\u0e9c\u0eb9\u0ec9\u0e94\u0eb9\u0ec1\u0ea5\u0ea5\u0eb0\u0e9a\u0ebb\u0e9a\u0ec0\u0e9e\u0eb7\u0ec8\u0ead\u0ec0\u0e82\u0ebb\u0ec9\u0eb2\u0ec0\u0e96\u0eb4\u0e87\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2:\",\n        incorrectPassword: \"\u0ea5\u0eb0\u0eab\u0eb1\u0e94\u0e9c\u0ec8\u0eb2\u0e99\u0e9a\u0ecd\u0ec8\u0e96\u0eb7\u0e81\u0e95\u0ec9\u0ead\u0e87!\",\n        apiKey: \"\u0ea5\u0eb0\u0eab\u0eb1\u0e94 API Key\",\n        modelName: \"\u0e8a\u0eb7\u0ec8\u0e95\u0ebb\u0ea7\u0ec1\u0e9a\u0e9a AI (Model)\",\n        save: \"\u0e9a\u0eb1\u0e99\u0e97\u0eb6\u0e81\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2\",\n        cancel: \"\u0e8d\u0ebb\u0e81\u0ec0\u0ea5\u0eb5\u0e81\",\n        scoreTitle: \"\u0e84\u0eb0\u0ec1\u0e99\u0e99\u0ea5\u0ea7\u0ea1\",\n        criteriaBreakdown: \"\u0ea5\u0eb2\u0e8d\u0ea5\u0eb0\u0ead\u0ebd\u0e94\u0ec0\u0e81\u0e99\u0e81\u0eb2\u0e99\u0e9b\u0eb0\u0ec0\u0ea1\u0eb5\u0e99\",\n        strengths: \"\u0e88\u0eb8\u0e94\u0ec0\u0e94\u0eb1\u0ec8\u0e99\/\u0e82\u0ecd\u0ec9\u0e94\u0eb5\",\n        improvements: \"\u0e82\u0ecd\u0ec9\u0e84\u0ea7\u0e99\u0e9b\u0eb1\u0e9a\u0e9b\u0eb8\u0e87\",\n        missingKey: \"\u0e81\u0eb0\u0ea5\u0eb8\u0e99\u0eb2\u0e81\u0eb3\u0e99\u0ebb\u0e94\u0e84\u0ec8\u0eb2 API key \u0e82\u0ead\u0e87\u0e97\u0ec8\u0eb2\u0e99\u0ec3\u0e99\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2\u0e81\u0ec8\u0ead\u0e99!\",\n        missingInputs: \"\u0e81\u0eb0\u0ea5\u0eb8\u0e99\u0eb2\u0e9b\u0ec9\u0ead\u0e99\u0e82\u0ecd\u0ec9\u0ea1\u0eb9\u0e99\u0e97\u0eb1\u0e87 \u0ec0\u0e81\u0e99\u0e81\u0eb2\u0e99\u0e84\u0eb1\u0e94\u0ec0\u0ea5\u0eb7\u0ead\u0e81 \u0ec1\u0ea5\u0eb0 \u0eae\u0ec8\u0eb2\u0e87\u0e82\u0ecd\u0ec9\u0eaa\u0eb0\u0ec0\u0edc\u0eb5.\",\n        errorOccurred: \"\u0ec0\u0e81\u0eb5\u0e94\u0e82\u0ecd\u0ec9\u0e9c\u0eb4\u0e94\u0e9e\u0eb2\u0e94\u0ec3\u0e99\u0ea5\u0eb0\u0eab\u0ea7\u0ec8\u0eb2\u0e87\u0e81\u0eb2\u0e99\u0ea7\u0eb4\u0ec0\u0e84\u0eb2\u0eb0: \",\n        noStrengths: \"\u0e9a\u0ecd\u0ec8\u0e9e\u0ebb\u0e9a\u0e88\u0eb8\u0e94\u0ec0\u0e94\u0eb1\u0ec8\u0e99\u0e97\u0eb5\u0ec8\u0e8a\u0eb1\u0e94\u0ec0\u0e88\u0e99.\",\n        noImprovements: \"\u0e9a\u0ecd\u0ec8\u0ea1\u0eb5\u0e82\u0ecd\u0ec9\u0ec1\u0e99\u0eb0\u0e99\u0eb3\u0e81\u0eb2\u0e99\u0e9b\u0eb1\u0e9a\u0e9b\u0eb8\u0e87.\",\n        rating: \"\u0e84\u0eb0\u0ec1\u0e99\u0e99\",\n        fileUpload: \"\u0ead\u0eb1\u0e9a\u0ec2\u0eab\u0ea5\u0e94\u0ec4\u0e9f\u0ea5\u0ecc\",\n        settingsSaved: \"\u0e9a\u0eb1\u0e99\u0e97\u0eb6\u0e81\u0e81\u0eb2\u0e99\u0e95\u0eb1\u0ec9\u0e87\u0e84\u0ec8\u0eb2\u0eaa\u0eb3\u0ec0\u0ea5\u0eb1\u0e94\u0ec1\u0ea5\u0ec9\u0ea7!\",\n        resetToDefault: \"\u0ec3\u0e8a\u0ec9 API Key \u0ec0\u0ea5\u0eb5\u0ec8\u0ea1\u0e95\u0ebb\u0ec9\u0e99\",\n        emptyStateTitle: \"\u0e9e\u0ec9\u0ead\u0ea1\u0eaa\u0eb3\u0ea5\u0eb1\u0e9a\u0e81\u0eb2\u0e99\u0ea7\u0eb4\u0ec0\u0e84\u0eb2\u0eb0\",\n        emptyStateDesc: \"\u0e9b\u0ec9\u0ead\u0e99\u0ec0\u0e81\u0e99\u0e81\u0eb2\u0e99\u0e84\u0eb1\u0e94\u0ec0\u0ea5\u0eb7\u0ead\u0e81 \u0ec1\u0ea5\u0eb0 \u0eae\u0ec8\u0eb2\u0e87\u0e82\u0ecd\u0ec9\u0eaa\u0eb0\u0ec0\u0edc\u0eb5\u0e82\u0ead\u0e87\u0e97\u0ec8\u0eb2\u0e99\u0ea2\u0eb9\u0ec8\u0e94\u0ec9\u0eb2\u0e99\u0e8a\u0ec9\u0eb2\u0e8d, \u0e88\u0eb2\u0e81\u0e99\u0eb1\u0ec9\u0e99\u0e84\u0ea5\u0eb4\u0e81 '\u0ea7\u0eb4\u0ec0\u0e84\u0eb2\u0eb0\u0e82\u0ecd\u0ec9\u0eaa\u0eb0\u0ec0\u0edc\u0eb5' \u0ec0\u0e9e\u0eb7\u0ec8\u0ead\u0ec0\u0e9a\u0eb4\u0ec8\u0e87\u0e9c\u0ebb\u0e99\u0ea7\u0eb4\u0ec0\u0e84\u0eb2\u0eb0.\"\n      }\n    };\n\n    \/\/ Global application state\n    let currentLanguage = 'en';\n\n    \/\/ ----------------------------------------------------\n    \/\/ INITIALIZATION & STATE RECOVERY\n    \/\/ ----------------------------------------------------\n    document.addEventListener('DOMContentLoaded', () => {\n      const langSelector = document.getElementById('lang-selector');\n      currentLanguage = langSelector.value || 'en';\n      \n      updateUILanguage();\n      \n      langSelector.addEventListener('change', (e) => {\n        currentLanguage = e.target.value;\n        updateUILanguage();\n      });\n\n      \/\/ Settings Modal triggers\n      document.getElementById('gear-btn').addEventListener('click', openModal);\n      document.getElementById('close-modal-btn').addEventListener('click', closeModal);\n      document.getElementById('close-config-btn').addEventListener('click', closeModal);\n      \n      \/\/ Authenticate password\n      document.getElementById('submit-password-btn').addEventListener('click', checkAdminPassword);\n      document.getElementById('admin-password-input').addEventListener('keydown', (e) => {\n        if (e.key === 'Enter') checkAdminPassword();\n      });\n\n      \/\/ Settings storage actions\n      document.getElementById('save-config-btn').addEventListener('click', saveConfig);\n      document.getElementById('reset-default-btn').addEventListener('click', () => {\n        document.getElementById('api-key-input').value = DEFAULT_API_KEY;\n      });\n      document.getElementById('toggle-key-visibility').addEventListener('click', toggleKeyVisibility);\n      \n      \/\/ Model configuration elements\n      document.getElementById('model-selector').addEventListener('change', (e) => {\n        const container = document.getElementById('custom-model-container');\n        if (e.target.value === 'custom') {\n          container.classList.remove('hidden');\n          document.getElementById('custom-model-input').value = '';\n          document.getElementById('custom-model-input').focus();\n        } else {\n          container.classList.add('hidden');\n        }\n      });\n\n      \/\/ File reading bindings\n      document.querySelectorAll('.file-input').forEach(input => {\n        input.addEventListener('change', handleFileUpload);\n      });\n\n      \/\/ Primary Trigger\n      document.getElementById('analyze-btn').addEventListener('click', handleAnalysisRequest);\n    });\n\n    \/\/ ----------------------------------------------------\n    \/\/ TRANSLATION RENDERER\n    \/\/ ----------------------------------------------------\n    function updateUILanguage() {\n      document.querySelectorAll('[data-i18n]').forEach(el => {\n        const key = el.getAttribute('data-i18n');\n        const translation = TRANVAL = TRANSLATIONS[currentLanguage][key];\n        \n        if (translation) {\n          if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {\n            el.setAttribute('placeholder', translation);\n          } else {\n            el.innerHTML = translation;\n          }\n        }\n      });\n    }\n\n    \/\/ ----------------------------------------------------\n    \/\/ SETTINGS MODAL INTERACTIVE LOGIC\n    \/\/ ----------------------------------------------------\n    const modal = document.getElementById('settings-modal');\n    const modalCard = document.getElementById('modal-card');\n\n    function openModal() {\n      modal.classList.remove('hidden');\n      modal.offsetHeight; \/\/ force reflow\n      modal.classList.remove('opacity-0');\n      modalCard.classList.remove('scale-95');\n      \n      document.getElementById('modal-password-screen').classList.remove('hidden');\n      document.getElementById('modal-config-screen').classList.add('hidden');\n      \n      document.getElementById('admin-password-input').value = '';\n      document.getElementById('password-error').classList.add('hidden');\n      document.getElementById('admin-password-input').focus();\n    }\n\n    function closeModal() {\n      modal.classList.add('opacity-0');\n      modalCard.classList.add('scale-95');\n      setTimeout(() => {\n        modal.classList.add('hidden');\n      }, 300);\n    }\n\n    function checkAdminPassword() {\n      const passwordVal = document.getElementById('admin-password-input').value;\n      const errorMsg = document.getElementById('password-error');\n      \n      if (passwordVal === ADMIN_PASSWORD) {\n        errorMsg.classList.add('hidden');\n        \n        document.getElementById('modal-password-screen').classList.add('hidden');\n        document.getElementById('modal-config-screen').classList.remove('hidden');\n        \n        document.getElementById('api-key-input').value = getApiKey();\n        \n        const activeModel = getModelName();\n        const modelSelect = document.getElementById('model-selector');\n        const customInput = document.getElementById('custom-model-input');\n        const customContainer = document.getElementById('custom-model-container');\n        \n        if (['gemini-2.5-flash', 'gemini-3.5-flash', 'gemini-flash-latest'].includes(activeModel)) {\n          modelSelect.value = activeModel;\n          customContainer.classList.add('hidden');\n        } else {\n          modelSelect.value = 'custom';\n          customContainer.classList.remove('hidden');\n          customInput.value = activeModel;\n        }\n\n        document.getElementById('api-key-input').focus();\n      } else {\n        errorMsg.classList.remove('hidden');\n        document.getElementById('admin-password-input').value = '';\n        document.getElementById('admin-password-input').focus();\n      }\n    }\n\n    function getApiKey() {\n      const saved = localStorage.getItem('proposal_analyzer_api_key');\n      return saved ? saved.trim() : DEFAULT_API_KEY;\n    }\n\n    function getModelName() {\n      const saved = localStorage.getItem('proposal_analyzer_model');\n      return saved ? saved.trim() : DEFAULT_MODEL;\n    }\n\n    function saveConfig() {\n      const keyInput = document.getElementById('api-key-input').value;\n      localStorage.setItem('proposal_analyzer_api_key', keyInput.trim());\n      \n      const modelSelect = document.getElementById('model-selector').value;\n      let finalModel = modelSelect;\n      if (modelSelect === 'custom') {\n        finalModel = document.getElementById('custom-model-input').value.trim() || DEFAULT_MODEL;\n      }\n      localStorage.setItem('proposal_analyzer_model', finalModel);\n      \n      closeModal();\n      showToast(TRANSLATIONS[currentLanguage].settingsSaved);\n    }\n\n    function toggleKeyVisibility() {\n      const input = document.getElementById('api-key-input');\n      const eyeIcon = document.getElementById('eye-icon');\n      \n      if (input.type === 'password') {\n        input.type = 'text';\n        eyeIcon.classList.remove('fa-eye');\n        eyeIcon.classList.add('fa-eye-slash');\n      } else {\n        input.type = 'password';\n        eyeIcon.classList.remove('fa-eye-slash');\n        eyeIcon.classList.add('fa-eye');\n      }\n    }\n\n    \/\/ ----------------------------------------------------\n    \/\/ TOAST NOTIFIER SYSTEM\n    \/\/ ----------------------------------------------------\n    function showToast(message, isError = false) {\n      const toast = document.createElement('div');\n      toast.className = 'fixed bottom-6 right-6 px-4 py-3 rounded-2xl shadow-xl flex items-center gap-3 text-sm z-50 border transition-all duration-300 transform translate-y-8 opacity-0 font-semibold ' + \n        (isError ? 'bg-rose-50 text-rose-800 border-rose-100 shadow-rose-50' : 'bg-slate-900 text-white border-slate-800 shadow-slate-100');\n      \n      toast.innerHTML = isError \n        ? `<i class=\"fa-solid fa-circle-exclamation text-rose-500 text-base\"><\/i><span>${message}<\/span>`\n        : `<i class=\"fa-solid fa-circle-check text-emerald-500 text-base\"><\/i><span>${message}<\/span>`;\n        \n      document.getElementById('proposal-analyzer-app').appendChild(toast);\n      \n      setTimeout(() => {\n        toast.classList.remove('translate-y-8', 'opacity-0');\n      }, 20);\n      \n      setTimeout(() => {\n        toast.classList.add('translate-y-8', 'opacity-0');\n        setTimeout(() => { toast.remove(); }, 300);\n      }, 4000);\n    }\n\n    \/\/ ----------------------------------------------------\n    \/\/ MULTI-FORMAT FILE PARSING CONTROLLER\n    \/\/ ----------------------------------------------------\n    async function handleFileUpload(e) {\n      const file = e.target.files[0];\n      if (!file) return;\n\n      const textareaId = e.target.getAttribute('data-target');\n      const textareaEl = document.getElementById(textareaId);\n      \n      const labelSpan = e.target.previousElementSibling;\n      const originalText = labelSpan.innerHTML;\n      \n      labelSpan.innerHTML = currentLanguage === 'lo' ? '<i class=\"fa-solid fa-spinner animate-spin mr-1\"><\/i> \u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0ead\u0ec8\u0eb2\u0e99\u0ec4\u0e9f\u0ea5\u0ecc...' : '<i class=\"fa-solid fa-spinner animate-spin mr-1\"><\/i> Reading file...';\n      textareaEl.value = currentLanguage === 'lo' ? '\u0e81\u0eb3\u0ea5\u0eb1\u0e87\u0ead\u0ec8\u0eb2\u0e99 \u0ec1\u0ea5\u0eb0 \u0ea7\u0eb4\u0ec0\u0e84\u0eb2\u0eb0\u0e82\u0ecd\u0ec9\u0ea1\u0eb9\u0e99\u0e88\u0eb2\u0e81\u0ec4\u0e9f\u0ea5\u0ecc...' : 'Reading and extracting text from file...';\n      textareaEl.disabled = true;\n\n      try {\n        const text = await parseFileContent(file);\n        textareaEl.value = text;\n        textareaEl.dispatchEvent(new Event('input'));\n        showToast(currentLanguage === 'lo' ? '\u0ead\u0eb1\u0e9a\u0ec2\u0eab\u0ea5\u0e94 \u0ec1\u0ea5\u0eb0 \u0ead\u0ec8\u0eb2\u0e99\u0ec4\u0e9f\u0ea5\u0ecc\u0eaa\u0eb3\u0ec0\u0ea5\u0eb1\u0e94!' : 'File uploaded and parsed successfully!');\n      } catch (err) {\n        console.error(err);\n        textareaEl.value = '';\n        showToast((currentLanguage === 'lo' ? '\u0e9a\u0ecd\u0ec8\u0eaa\u0eb2\u0ea1\u0eb2\u0e94\u0ead\u0ec8\u0eb2\u0e99\u0ec4\u0e9f\u0ea5\u0ecc\u0ec4\u0e94\u0ec9: ' : 'Failed to parse file: ') + err.message, true);\n      } finally {\n        labelSpan.innerHTML = originalText;\n        textareaEl.disabled = false;\n        e.target.value = '';\n      }\n    }\n\n    async function parseFileContent(file) {\n      const extension = file.name.split('.').pop().toLowerCase();\n      \n      if (extension === 'txt') {\n        return new Promise((resolve, reject) => {\n          const reader = new FileReader();\n          reader.onload = (e) => resolve(e.target.result);\n          reader.onerror = (err) => reject(err);\n          reader.readAsText(file);\n        });\n      }\n      \n      if (extension === 'pdf') {\n        return new Promise((resolve, reject) => {\n          const reader = new FileReader();\n          reader.onload = async (e) => {\n            try {\n              const arrayBuffer = e.target.result;\n              const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;\n              let fullText = \"\";\n              for (let i = 1; i <= pdf.numPages; i++) {\n                const page = await pdf.getPage(i);\n                const textContent = await page.getTextContent();\n                const pageText = textContent.items.map(item => item.str).join(\" \");\n                fullText += pageText + \"\\n\";\n              }\n              resolve(fullText.trim());\n            } catch (err) {\n              reject(new Error(\"Failed to parse PDF: \" + err.message));\n            }\n          };\n          reader.onerror = (err) => reject(err);\n          reader.readAsArrayBuffer(file);\n        });\n      }\n      \n      if (extension === 'docx') {\n        return new Promise((resolve, reject) => {\n          const reader = new FileReader();\n          reader.onload = async (e) => {\n            try {\n              const arrayBuffer = e.target.result;\n              const result = await mammoth.extractRawText({ arrayBuffer: arrayBuffer });\n              resolve(result.value.trim());\n            } catch (err) {\n              reject(new Error(\"Failed to parse DOCX: \" + err.message));\n            }\n          };\n          reader.onerror = (err) => reject(err);\n          reader.readAsArrayBuffer(file);\n        });\n      }\n      \n      if (extension === 'xlsx' || extension === 'xls') {\n        return new Promise((resolve, reject) => {\n          const reader = new FileReader();\n          reader.onload = (e) => {\n            try {\n              const arrayBuffer = e.target.result;\n              const workbook = XLSX.read(arrayBuffer, { type: 'array' });\n              let fullText = \"\";\n              workbook.SheetNames.forEach(sheetName => {\n                const worksheet = workbook.Sheets[sheetName];\n                const csv = XLSX.utils.sheet_to_csv(worksheet);\n                if (csv.trim()) {\n                  fullText += `--- Sheet: ${sheetName} ---\\n${csv}\\n\\n`;\n                }\n              });\n              resolve(fullText.trim());\n            } catch (err) {\n              reject(new Error(\"Failed to parse Excel: \" + err.message));\n            }\n          };\n          reader.onerror = (err) => reject(err);\n          reader.readAsArrayBuffer(file);\n        });\n      }\n      \n      if (extension === 'pptx') {\n        return new Promise((resolve, reject) => {\n          const reader = new FileReader();\n          reader.onload = async (e) => {\n            try {\n              const arrayBuffer = e.target.result;\n              const zip = await JSZip.loadAsync(arrayBuffer);\n              let fullText = \"\";\n              \n              const slideFiles = Object.keys(zip.files).filter(name => name.match(\/^ppt\\\/slides\\\/slide\\d+\\.xml$\/));\n              slideFiles.sort((a, b) => {\n                const numA = parseInt(a.match(\/\\d+\/)[0], 10);\n                const numB = parseInt(b.match(\/\\d+\/)[0], 10);\n                return numA - numB;\n              });\n              \n              for (const slideFile of slideFiles) {\n                const slideXmlText = await zip.files[slideFile].async(\"string\");\n                const parser = new DOMParser();\n                const xmlDoc = parser.parseFromString(slideXmlText, \"text\/xml\");\n                const textNodes = xmlDoc.getElementsByTagName(\"a:t\");\n                let slideText = \"\";\n                for (let i = 0; i < textNodes.length; i++) {\n                  slideText += textNodes[i].textContent + \" \";\n                }\n                const slideNum = slideFile.match(\/\\d+\/)[0];\n                if (slideText.trim()) {\n                  fullText += `--- Slide ${slideNum} ---\\n${slideText.trim()}\\n\\n`;\n                }\n              }\n              resolve(fullText.trim());\n            } catch (err) {\n              reject(new Error(\"Failed to parse PPTX: \" + err.message));\n            }\n          };\n          reader.onerror = (err) => reject(err);\n          reader.readAsArrayBuffer(file);\n        });\n      }\n      \n      throw new Error(`Unsupported file type: .${extension}`);\n    }\n\n    \/\/ ----------------------------------------------------\n    \/\/ GEMINI INTEGRATION & SYSTEM PROMPT BUILDER\n    \/\/ ----------------------------------------------------\n    async function handleAnalysisRequest() {\n      const criteriaVal = document.getElementById('criteria-input').value.trim();\n      const proposalVal = document.getElementById('proposal-input').value.trim();\n      \n      if (!criteriaVal || !proposalVal) {\n        showToast(TRANSLATIONS[currentLanguage].missingInputs, true);\n        return;\n      }\n\n      const apiKey = getApiKey();\n      if (!apiKey) {\n        showToast(TRANSLATIONS[currentLanguage].missingKey, true);\n        return;\n      }\n\n      setLoadingState(true);\n\n      const targetLanguage = (currentLanguage === 'lo') ? 'Lao' : 'English';\n      const activeModel = getModelName();\n\n      \/\/ System instruction for proposal analysis only\n      let systemPrompt = `You are an expert, brutally honest, and highly critical grant evaluator. Your objective is to give unvarnished feedback so the author can improve. Output strictly in ${targetLanguage}.\\n`;\n      systemPrompt += `Format your response exactly as follows using Markdown:\\n`;\n      systemPrompt += `## 1. Overall Score\\n[Score 0-100]\\n`;\n      systemPrompt += `## 2. Detailed Criteria Breakdown\\n`;\n      systemPrompt += `### [Criterion Name]\\n* Rating: [Score out of 10]\\n* What it does well: [Factual strengths or \"None\"]\\n* Improvements: [Brutal, direct critique on what to fix]`;\n\n      const userPrompt = `Call for Proposal Criteria:\\n\"\"\"\\n${criteriaVal}\\n\"\"\"\\n\\nDraft Proposal to Evaluate:\\n\"\"\"\\n${proposalVal}\\n\"\"\"`;\n\n      try {\n        const response = await fetch(`https:\/\/generativelanguage.googleapis.com\/v1beta\/models\/${activeModel}:generateContent?key=${apiKey}`, {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application\/json'\n          },\n          body: JSON.stringify({\n            contents: [\n              {\n                parts: [\n                  { text: userPrompt }\n                ]\n              }\n            ],\n            systemInstruction: {\n              parts: [\n                { text: systemPrompt }\n              ]\n            },\n            generationConfig: {\n              temperature: 0.15\n            }\n          })\n        });\n\n        const data = await response.json();\n\n        if (!response.ok) {\n          const apiErrorMessage = data.error?.message || response.statusText;\n          throw new Error(apiErrorMessage);\n        }\n\n        const rawText = data.candidates?.[0]?.content?.parts?.[0]?.text;\n        if (!rawText) {\n          throw new Error(\"No response text content returned from the model.\");\n        }\n\n        const analysisData = parseMarkdownResponse(rawText);\n        renderVisualDashboard(analysisData);\n        \n        showToast(currentLanguage === 'lo' ? '\u0e81\u0eb2\u0e99\u0ea7\u0eb4\u0ec0\u0e84\u0eb2\u0eb0\u0eaa\u0eb3\u0ec0\u0ea5\u0eb1\u0e94\u0ec1\u0ea5\u0ec9\u0ea7!' : 'Analysis completed successfully!');\n\n      } catch (err) {\n        console.error(err);\n        showToast(TRANSLATIONS[currentLanguage].errorOccurred + err.message, true);\n      } finally {\n        setLoadingState(false);\n      }\n    }\n\n    function setLoadingState(isLoading) {\n      const btn = document.getElementById('analyze-btn');\n      const textSpan = document.getElementById('analyze-btn-text');\n      const spinner = document.getElementById('analyze-spinner');\n      \n      if (isLoading) {\n        btn.disabled = true;\n        btn.classList.add('opacity-75', 'cursor-not-allowed');\n        textSpan.innerHTML = TRANSLATIONS[currentLanguage].analyzingBtn;\n        spinner.classList.remove('hidden');\n      } else {\n        btn.disabled = false;\n        btn.classList.remove('opacity-75', 'cursor-not-allowed');\n        textSpan.innerHTML = TRANSLATIONS[currentLanguage].analyzeBtn;\n        spinner.classList.add('hidden');\n      }\n    }\n\n    \/\/ ----------------------------------------------------\n    \/\/ AI RESPONSE MARKDOWN PARSER\n    \/\/ ----------------------------------------------------\n    function parseMarkdownResponse(markdown) {\n      \/\/ 1. Overall Score Parsing\n      const scoreMatch = markdown.match(\/## 1\\.\\s*Overall\\s*Score\\s*[\\r\\n]+(?:\\s*)*(\\d+)\/i) \n        || markdown.match(\/Overall\\s*Score\\s*:\\s*(\\d+)\/i)\n        || markdown.match(\/## 1\\.\\s*Overall\\s*Score\\s*:\\s*(\\d+)\/i)\n        || markdown.match(\/Overall\\s*Score\\s*[\\r\\n]+(?:\\s*)*(\\d+)\/i);\n      \n      const parsedScore = scoreMatch ? parseInt(scoreMatch[1], 10) : 60;\n\n      \/\/ 2. Detailed Criteria Breakdown Section\n      const criteriaSectionRegex = \/## 2\\.\\s*Detailed\\s*Criteria\\s*Breakdown\/i;\n      let criteriaText = \"\";\n      const headerMatch = markdown.match(criteriaSectionRegex);\n      if (headerMatch) {\n        const headerIdx = markdown.indexOf(headerMatch[0]);\n        criteriaText = markdown.substring(headerIdx + headerMatch[0].length);\n      } else {\n        const fallbackRegex = \/##\\s*\\d*\\.?\\s*Detailed\\s*Criteria\/i;\n        const fallbackMatch = markdown.match(fallbackRegex);\n        if (fallbackMatch) {\n          const headerIdx = markdown.indexOf(fallbackMatch[0]);\n          criteriaText = markdown.substring(headerIdx + fallbackMatch[0].length);\n        } else {\n          criteriaText = markdown;\n        }\n      }\n\n      \/\/ Split blocks by \"### \"\n      const criteriaList = [];\n      const blocks = criteriaText.split(\/###\\s+\/);\n      const startBlockIdx = blocks[0].includes('Rating:') ? 0 : 1;\n\n      for (let i = startBlockIdx; i < blocks.length; i++) {\n        const block = blocks[i].trim();\n        if (!block) continue;\n\n        const lines = block.split(\/\\r?\\n\/);\n        const title = lines[0].trim();\n\n        let rating = 5;\n        let strengths = \"\";\n        let improvements = \"\";\n        let targetField = null;\n\n        for (let j = 1; j < lines.length; j++) {\n          const line = lines[j].trim();\n          if (!line) continue;\n\n          const ratingMatch = line.match(\/^\\*\\s*Rating:\\s*(\\d+)\/i);\n          if (ratingMatch) {\n            rating = parseInt(ratingMatch[1], 10);\n            targetField = null;\n            continue;\n          }\n\n          const strengthsMatch = line.match(\/^\\*\\s*What\\s+it\\s+does\\s+well:\\s*(.*)\/i);\n          if (strengthsMatch) {\n            strengths = strengthsMatch[1].trim();\n            targetField = 'strengths';\n            continue;\n          }\n\n          const improvementsMatch = line.match(\/^\\*\\s*Improvements:\\s*(.*)\/i);\n          if (improvementsMatch) {\n            improvements = improvementsMatch[1].trim();\n            targetField = 'improvements';\n            continue;\n          }\n\n          if (targetField === 'strengths') {\n            strengths += \"\\n\" + line.replace(\/^\\*\\s*\/, '');\n          } else if (targetField === 'improvements') {\n            improvements += \"\\n\" + line.replace(\/^\\*\\s*\/, '');\n          }\n        }\n\n        if (title) {\n          criteriaList.push({\n            title,\n            rating,\n            strengths: strengths.trim() || TRANSLATIONS[currentLanguage].noStrengths,\n            improvements: improvements.trim() || TRANSLATIONS[currentLanguage].noImprovements\n          });\n        }\n      }\n\n      return {\n        score: parsedScore,\n        criteria: criteriaList\n      };\n    }\n\n    function formatTextToHTML(text) {\n      if (!text) return \"\";\n      \n      let escaped = text\n        .replace(\/&\/g, \"&amp;\")\n        .replace(\/<\/g, \"&lt;\")\n        .replace(\/>\/g, \"&gt;\")\n        .replace(\/\"\/g, \"&quot;\")\n        .replace(\/'\/g, \"&#039;\");\n\n      escaped = escaped.replace(\/\\*\\*(.*?)\\*\\*\/g, \"<strong>$1<\/strong>\");\n\n      const lines = escaped.split('\\n').map(line => {\n        const trimmed = line.trim();\n        if (trimmed.startsWith('* ')) {\n          return `<li class=\"ml-4 list-disc mb-1\">${trimmed.substring(2)}<\/li>`;\n        }\n        return line;\n      });\n\n      return lines.join('<br>');\n    }\n\n    \/\/ ----------------------------------------------------\n    \/\/ VISUAL DASHBOARD GENERATION\n    \/\/ ----------------------------------------------------\n    function renderVisualDashboard(data) {\n      document.getElementById('dashboard-empty-state').classList.add('hidden');\n      const resultsContainer = document.getElementById('dashboard-results');\n      resultsContainer.classList.remove('hidden');\n\n      const score = data.score;\n      const scoreCircle = document.getElementById('score-gauge-circle');\n      \n      const circumference = 251.327;\n      const offset = circumference - (score \/ 100) * circumference;\n      \n      let strokeColor = '#10b981';\n      if (score < 50) {\n        strokeColor = '#f43f5e';\n      } else if (score < 75) {\n        strokeColor = '#f59e0b';\n      }\n      \n      scoreCircle.setAttribute('stroke', strokeColor);\n      scoreCircle.style.strokeDashoffset = circumference;\n      setTimeout(() => {\n        scoreCircle.style.strokeDashoffset = offset;\n      }, 150);\n\n      animateCountUp('score-gauge-text', score, 1000);\n\n      const criteriaGrid = document.getElementById('criteria-cards-grid');\n      criteriaGrid.innerHTML = '';\n\n      data.criteria.forEach((c, index) => {\n        const rating = c.rating;\n        \n        let badgeClass = \"bg-emerald-50 text-emerald-700 border border-emerald-100\";\n        if (rating < 5) {\n          badgeClass = \"bg-rose-50 text-rose-700 border border-rose-100\";\n        } else if (rating < 8) {\n          badgeClass = \"bg-amber-50 text-amber-700 border border-amber-100\";\n        }\n\n        const card = document.createElement('div');\n        card.className = `bg-white border border-slate-200\/80 rounded-3xl p-5 shadow-sm space-y-4 flex flex-col justify-between hover:shadow-md transition-all duration-300 transform scale-95 opacity-0`;\n        \n        card.innerHTML = `\n          <div>\n            <div class=\"flex justify-between items-start gap-4 mb-3\">\n              <h5 class=\"font-extrabold text-slate-800 text-sm leading-snug\">${c.title}<\/h5>\n              <span class=\"${badgeClass} px-2.5 py-0.5 rounded-full text-xs font-extrabold whitespace-nowrap\">\n                <span data-i18n=\"rating\">Rating<\/span>: ${rating}\/10\n              <\/span>\n            <\/div>\n            \n            <div class=\"space-y-3\">\n              <!-- Strengths Box -->\n              <div class=\"bg-emerald-50\/40 border border-emerald-100\/60 rounded-2xl p-3 text-xs space-y-1.5 shadow-inner\">\n                <div class=\"text-emerald-700 font-bold flex items-center gap-1.5 uppercase tracking-wider text-[10px]\">\n                  <i class=\"fa-solid fa-circle-check text-emerald-500 text-xs\"><\/i>\n                  <span data-i18n=\"strengths\">Strengths<\/span>\n                <\/div>\n                <div class=\"text-slate-700 leading-relaxed\">${formatTextToHTML(c.strengths)}<\/div>\n              <\/div>\n              \n              <!-- Improvements Box -->\n              <div class=\"bg-rose-50\/40 border border-rose-100\/60 rounded-2xl p-3 text-xs space-y-1.5 shadow-inner\">\n                <div class=\"text-rose-700 font-bold flex items-center gap-1.5 uppercase tracking-wider text-[10px]\">\n                  <i class=\"fa-solid fa-circle-exclamation text-rose-500 text-xs\"><\/i>\n                  <span data-i18n=\"improvements\">Improvements<\/span>\n                <\/div>\n                <div class=\"text-slate-700 leading-relaxed\">${formatTextToHTML(c.improvements)}<\/div>\n              <\/div>\n            <\/div>\n          <\/div>\n        `;\n        \n        criteriaGrid.appendChild(card);\n        updateUILanguage();\n\n        setTimeout(() => {\n          card.classList.remove('scale-95', 'opacity-0');\n        }, index * 100 + 100);\n      });\n    }\n\n    function animateCountUp(elementId, targetValue, duration = 1000, suffix = '') {\n      const el = document.getElementById(elementId);\n      if (!el) return;\n      \n      const start = 0;\n      const startTime = performance.now();\n      \n      function update(currentTime) {\n        const elapsed = currentTime - startTime;\n        const progress = Math.min(elapsed \/ duration, 1);\n        \n        const easeProgress = progress * (2 - progress);\n        const currentValue = Math.floor(start + easeProgress * (targetValue - start));\n        \n        el.textContent = currentValue + suffix;\n        \n        if (progress < 1) {\n          requestAnimationFrame(update);\n        } else {\n          el.textContent = targetValue + suffix;\n        }\n      }\n      \n      requestAnimationFrame(update);\n    }\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>Proposal Analysis Dashboard English \ud83c\uddec\ud83c\udde7\u0e9e\u0eb2\u0eaa\u0eb2\u0ea5\u0eb2\u0ea7 \ud83c\uddf1\ud83c\udde6 Call for Proposal Criteria Upload File Draft Proposal Upload File Analyze Proposal Ready for Analysis Enter the Call Criteria and your Draft Proposal on the left, then click &#8220;Analyze Proposal&#8221; to view the detailed visual breakdown. 0 Overall Score Detailed Criteria Breakdown Settings Enter administrator password to access settings: [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":8086,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"site-sidebar-layout":"no-sidebar","site-content-layout":"page-builder","ast-site-content-layout":"full-width-container","site-content-style":"unboxed","site-sidebar-style":"unboxed","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"disabled","ast-breadcrumbs-content":"","ast-featured-img":"disabled","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"default","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":"","_members_access_role":[],"_members_access_error":""},"class_list":["post-21112","page","type-page","status-publish","has-post-thumbnail","hentry"],"acf":[],"a3_pvc":{"activated":false,"total_views":0,"today_views":0},"_links":{"self":[{"href":"https:\/\/jobsnocap.com\/lo\/wp-json\/wp\/v2\/pages\/21112","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jobsnocap.com\/lo\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/jobsnocap.com\/lo\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/jobsnocap.com\/lo\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/jobsnocap.com\/lo\/wp-json\/wp\/v2\/comments?post=21112"}],"version-history":[{"count":5,"href":"https:\/\/jobsnocap.com\/lo\/wp-json\/wp\/v2\/pages\/21112\/revisions"}],"predecessor-version":[{"id":21120,"href":"https:\/\/jobsnocap.com\/lo\/wp-json\/wp\/v2\/pages\/21112\/revisions\/21120"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/jobsnocap.com\/lo\/wp-json\/wp\/v2\/media\/8086"}],"wp:attachment":[{"href":"https:\/\/jobsnocap.com\/lo\/wp-json\/wp\/v2\/media?parent=21112"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}