{"id":952,"date":"2026-02-25T12:30:00","date_gmt":"2026-02-25T12:30:00","guid":{"rendered":"https:\/\/marineconservation.id\/?page_id=952"},"modified":"2026-03-07T09:24:22","modified_gmt":"2026-03-07T09:24:22","slug":"analisis-bentik","status":"publish","type":"page","link":"https:\/\/marineconservation.id\/id\/analisis-bentik\/","title":{"rendered":"Analisis Bentik"},"content":{"rendered":"<style>.elementor-952 .elementor-element.elementor-element-ca03f26{--display:flex;--flex-direction:column;--container-widget-width:100%;--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;}.elementor-952 .elementor-element.elementor-element-c68406d{--display:flex;--flex-direction:column;--container-widget-width:100%;--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;}.elementor-952 .elementor-element.elementor-element-b3fd8c1{--spacer-size:50px;}.elementor-952 .elementor-element.elementor-element-a820a27{--display:flex;--flex-direction:column;--container-widget-width:100%;--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;}.elementor-952 .elementor-element.elementor-element-48c99c4{--display:flex;}.elementor-952 .elementor-element.elementor-element-7f13728{--display:flex;}.elementor-952 .elementor-element.elementor-element-24c40f3{--display:flex;}.elementor-952 .elementor-element.elementor-element-cf51626{--display:flex;}.elementor-widget-heading .elementor-heading-title{font-family:var( --e-global-typography-primary-font-family ), Sans-serif;font-weight:var( --e-global-typography-primary-font-weight );color:var( --e-global-color-primary );}.elementor-952 .elementor-element.elementor-element-ac3c328{text-align:center;}.elementor-952 .elementor-element.elementor-element-ac3c328 .elementor-heading-title{font-family:\"Roboto\", Sans-serif;font-size:59px;font-weight:600;color:#00399F;}.elementor-952 .elementor-element.elementor-element-e3e6322{--display:flex;--flex-direction:column;--container-widget-width:100%;--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;}.elementor-952 .elementor-element.elementor-element-213eef6 .elementor-wrapper{--video-aspect-ratio:1.77777;}.elementor-952 .elementor-element.elementor-element-159f926{--display:flex;--flex-direction:column;--container-widget-width:100%;--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;}.elementor-952 .elementor-element.elementor-element-46e9cc7{--spacer-size:50px;}.elementor-952 .elementor-element.elementor-element-2ef186f{--display:flex;--flex-direction:column;--container-widget-width:100%;--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;}.elementor-952 .elementor-element.elementor-element-073cc7a .elementor-heading-title{color:#00399F;}.elementor-952 .elementor-element.elementor-element-fbe1928{--display:flex;}.elementor-952 .elementor-element.elementor-element-7fe9992 .elementor-wrapper{--video-aspect-ratio:1.77777;}<\/style>\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"952\" class=\"elementor elementor-952\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-ca03f26 e-flex e-con-boxed e-con e-parent\" data-id=\"ca03f26\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-c8823df elementor-widget elementor-widget-html\" data-id=\"c8823df\" data-element_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>\r\n<html lang=\"id\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <title>Aplikasi Analisis Bentik - Titik Acak & Luasan Poligon<\/title>\r\n    <!-- Leaflet CSS -->\r\n    <link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.css\" \/>\r\n    <!-- Font Awesome -->\r\n    <link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.0.0-beta3\/css\/all.min.css\">\r\n    <style>\r\n        \/* (style tidak diubah, sama persis dengan aslinya) *\/\r\n        * { box-sizing: border-box; font-family: 'Segoe UI', Roboto, system-ui, sans-serif; }\r\n        body { background: #f0f5f9; margin: 0; padding: 20px; display: flex; justify-content: center; }\r\n        .app-container { max-width: 1400px; width: 100%; background: white; border-radius: 24px; box-shadow: 0 20px 40px rgba(0,20,40,.2); overflow: hidden; }\r\n        header { background: #0b3b5c; color: white; padding: 20px 30px; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; }\r\n        .header-title h1 { margin: 0; font-weight: 400; font-size: 1.8rem; }\r\n        .header-title p { margin: 5px 0 0; opacity: 0.8; }\r\n        .language-selector { background: white; border-radius: 40px; padding: 5px; display: flex; gap: 5px; }\r\n        .lang-btn { background: transparent; border: none; padding: 8px 16px; border-radius: 40px; cursor: pointer; font-weight: 600; color: #0b3b5c; transition: 0.2s; }\r\n        .lang-btn.active { background: #0b3b5c; color: white; }\r\n        .step-indicator { display: flex; background: #e9f0f5; padding: 15px 30px; gap: 30px; border-bottom: 1px solid #cbd6e3; }\r\n        .step { display: flex; align-items: center; gap: 8px; color: #2c5777; }\r\n        .step.active { font-weight: bold; color: #0b3b5c; }\r\n        .step i { font-size: 1.2rem; width: 24px; }\r\n        .content { padding: 30px; }\r\n        .card { background: #f9fcff; border: 1px solid #dde7f0; border-radius: 20px; padding: 25px; margin-bottom: 30px; }\r\n        .hidden { display: none !important; }\r\n        .form-row { display: flex; flex-wrap: wrap; gap: 20px; margin-bottom: 20px; }\r\n        .form-group { flex: 1 1 200px; }\r\n        label { display: block; font-weight: 500; margin-bottom: 6px; color: #1e3b5c; }\r\n        input, select, textarea { width: 100%; padding: 12px 15px; border: 2px solid #cbd6e3; border-radius: 14px; font-size: 1rem; transition: 0.2s; }\r\n        input:focus, select:focus { border-color: #0b3b5c; outline: none; }\r\n        .map-container { height: 300px; border-radius: 16px; overflow: hidden; border: 2px solid #cbd6e3; margin: 20px 0; }\r\n        .btn { background: #0b3b5c; color: white; border: none; padding: 14px 30px; border-radius: 40px; font-size: 1.1rem; font-weight: 600; cursor: pointer; transition: 0.2s; box-shadow: 0 4px 10px rgba(11,59,92,0.3); }\r\n        .btn:hover { background: #145a82; transform: translateY(-2px); }\r\n        .btn-outline { background: white; color: #0b3b5c; border: 2px solid #0b3b5c; box-shadow: none; }\r\n        .btn-outline:hover { background: #e9f0f5; }\r\n        .btn-danger { background: #b52b2b; }\r\n        .photo-gallery { display: flex; flex-wrap: wrap; gap: 15px; margin: 20px 0; align-items: center; }\r\n        .photo-thumb { border: 3px solid #cbd6e3; border-radius: 16px; padding: 5px; background: white; cursor: pointer; transition: 0.2s; width: 120px; }\r\n        .photo-thumb.active { border-color: #0b3b5c; transform: scale(1.02); box-shadow: 0 8px 15px rgba(0,0,0,0.1); }\r\n        .photo-thumb img { width: 100%; height: 90px; object-fit: cover; border-radius: 12px; }\r\n        .photo-thumb .photo-index { text-align: center; font-weight: bold; margin-top: 5px; }\r\n        .canvas-area { display: flex; flex-wrap: wrap; gap: 30px; }\r\n        .canvas-wrapper { flex: 2; min-width: 500px; background: #1a2e3f; border-radius: 20px; padding: 15px; }\r\n        #analysisCanvas { width: 100%; height: auto; background: #2a4055; border-radius: 12px; cursor: crosshair; display: block; }\r\n        .point-panel, .polygon-panel { flex: 1; min-width: 300px; background: white; border-radius: 20px; border: 1px solid #dde7f0; padding: 20px; max-height: 600px; overflow-y: auto; }\r\n        .point-item, .polygon-item { background: #f0f7fe; border-radius: 12px; padding: 8px 12px; margin-bottom: 8px; display: flex; align-items: center; gap: 10px; cursor: pointer; transition: 0.1s; }\r\n        .point-item:hover, .polygon-item:hover { background: #e2effa; }\r\n        .point-number { font-weight: 700; background: #0b3b5c; color: white; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; border-radius: 50%; flex-shrink: 0; cursor: pointer; font-size: 0.7rem; }\r\n        .point-select { flex: 1; padding: 2px 4px; border-radius: 20px; border: 1px solid #b5c9db; cursor: pointer; font-size: 0.8rem; }\r\n        .info-msg { background: #e1f0fa; padding: 10px; border-radius: 12px; margin: 10px 0; font-size: 0.95rem; }\r\n        .flex-row { display: flex; gap: 15px; align-items: center; flex-wrap: wrap; }\r\n        .badge { background: #0b3b5c; color: white; border-radius: 40px; padding: 4px 12px; font-size: 0.9rem; }\r\n        .calibration-panel { background: #fff3cd; border: 2px solid #ffc107; border-radius: 16px; padding: 15px; margin-bottom: 15px; }\r\n        .panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }\r\n        .reset-zoom-btn { background: white; border: 1px solid #0b3b5c; color: #0b3b5c; padding: 4px 10px; border-radius: 20px; font-size: 0.8rem; cursor: pointer; }\r\n        .reset-zoom-btn:hover { background: #e9f0f5; }\r\n        .mode-selector, .level-selector { display: flex; gap: 20px; margin: 10px 0; background: #e9f0f5; padding: 10px 15px; border-radius: 40px; justify-content: center; flex-wrap: wrap; }\r\n        .mode-selector label, .level-selector label { display: flex; align-items: center; gap: 8px; font-weight: 500; cursor: pointer; }\r\n        .mode-selector input[type=\"radio\"], .level-selector input[type=\"radio\"] { width: auto; margin-right: 5px; }\r\n        .polygon-color { width: 24px; height: 24px; border-radius: 6px; flex-shrink: 0; }\r\n        .polygon-info { flex: 1; }\r\n        .polygon-category { font-weight: 600; font-size: 0.95rem; }\r\n        .polygon-area { font-size: 0.8rem; color: #2c5777; }\r\n        .polygon-actions button { background: none; border: none; color: #0b3b5c; cursor: pointer; font-size: 1rem; margin-left: 5px; }\r\n        .polygon-actions button:hover { color: #145a82; }\r\n        .popup-overlay { position: fixed; top:0; left:0; right:0; bottom:0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }\r\n        .popup-content { background: white; padding: 30px; border-radius: 24px; width: 400px; max-width: 90%; }\r\n        .popup-content h3 { margin-top: 0; }\r\n        .popup-content input { margin-bottom: 20px; width: 100%; }\r\n        .popup-buttons { display: flex; gap: 10px; justify-content: flex-end; }\r\n        canvas { cursor: crosshair; }\r\n    <\/style>\r\n<\/head>\r\n<body>\r\n<div class=\"app-container\">\r\n    <header>\r\n        <div class=\"header-title\">\r\n            <h1><i class=\"fas fa-water\" style=\"margin-right: 12px;\"><\/i> <span data-i18n=\"app_title\">Analisis Bentik - Titik Acak & Luasan Poligon<\/span><\/h1>\r\n            <p data-i18n=\"app_subtitle\">Metode standar LIPI \u2022 Kalibrasi skala \u2022 Luasan poligon \u2022 Ekspor Excel<\/p>\r\n        <\/div>\r\n        <div style=\"display: flex; gap: 10px; align-items: center;\">\r\n            <div class=\"language-selector\">\r\n                <button class=\"lang-btn active\" id=\"lang-id\" onclick=\"setLanguage('id')\">Indonesia<\/button>\r\n                <button class=\"lang-btn\" id=\"lang-en\" onclick=\"setLanguage('en')\">English<\/button>\r\n            <\/div>\r\n            <!-- TOMBOL SIMPAN & MUAT PROYEK -->\r\n            <div style=\"display: flex; gap: 5px;\">\r\n                <button class=\"btn-outline\" id=\"saveProjectBtnHeader\"><i class=\"fas fa-save\"><\/i> <span data-i18n=\"save_project\">Simpan Proyek<\/span><\/button>\r\n                <button class=\"btn-outline\" id=\"loadProjectBtnHeader\"><i class=\"fas fa-folder-open\"><\/i> <span data-i18n=\"load_project\">Muat Proyek<\/span><\/button>\r\n                <input type=\"file\" id=\"loadProjectFile\" accept=\".json\" style=\"display: none;\">\r\n            <\/div>\r\n        <\/div>\r\n    <\/header>\r\n    <div class=\"step-indicator\">\r\n        <div class=\"step\" id=\"step1-indicator\"><i class=\"fas fa-clipboard-list\"><\/i> <span data-i18n=\"step1\">1. Info Proyek<\/span><\/div>\r\n        <div class=\"step\" id=\"step2-indicator\"><i class=\"fas fa-camera\"><\/i> <span data-i18n=\"step2\">2. Upload Foto<\/span><\/div>\r\n        <div class=\"step\" id=\"step3-indicator\"><i class=\"fas fa-draw-polygon\"><\/i> <span data-i18n=\"step3\">3. Area & Analisis<\/span><\/div>\r\n        <div class=\"step\" id=\"step4-indicator\"><i class=\"fas fa-check-double\"><\/i> <span data-i18n=\"step4\">4. Identifikasi<\/span><\/div>\r\n    <\/div>\r\n\r\n    <div class=\"content\">\r\n        <!-- Langkah 1: Info Proyek -->\r\n        <div id=\"step1\" class=\"card\">\r\n            <h2><i class=\"fas fa-clipboard-list\"><\/i> <span data-i18n=\"project_info\">Informasi Proyek<\/span><\/h2>\r\n            <div class=\"form-row\">\r\n                <div class=\"form-group\"><label data-i18n=\"project_name\">Nama Project<\/label><input type=\"text\" id=\"nama_project\" placeholder=\"Contoh: Monitoring 2025\"><\/div>\r\n                <div class=\"form-group\"><label data-i18n=\"surveyor\">Surveyor<\/label><input type=\"text\" id=\"surveyor\" placeholder=\"Nama lengkap\"><\/div>\r\n            <\/div>\r\n            <div class=\"form-row\">\r\n                <div class=\"form-group\"><label data-i18n=\"date\">Tanggal<\/label><input type=\"date\" id=\"tanggal\"><\/div>\r\n                <div class=\"form-group\"><label data-i18n=\"time\">Jam<\/label><input type=\"time\" id=\"jam\"><\/div>\r\n                <div class=\"form-group\"><label data-i18n=\"site\">Site<\/label><input type=\"text\" id=\"site\" placeholder=\"Misal: Pulau A\"><\/div>\r\n            <\/div>\r\n            <div class=\"form-row\">\r\n                <div class=\"form-group\"><label data-i18n=\"repetition\">Pengulangan (opsional)<\/label><input type=\"text\" id=\"pengulangan\" placeholder=\"contoh: ke-2\"><\/div>\r\n                <div class=\"form-group\"><label data-i18n=\"depth\">Kedalaman (m)<\/label><input type=\"number\" id=\"kedalaman\" step=\"0.1\"><\/div>\r\n                <div class=\"form-group\"><label data-i18n=\"visibility\">Visibility (m)<\/label><input type=\"number\" id=\"visibility\" step=\"0.1\"><\/div>\r\n            <\/div>\r\n            <div class=\"form-row\">\r\n                <div class=\"form-group\"><label data-i18n=\"structure_type\">Tipe Struktur<\/label>\r\n                    <select id=\"tipe_struktur\">\r\n                        <option data-i18n=\"fringing\">Terumbu Karang Tepi (Fringing Reefs)<\/option>\r\n                        <option data-i18n=\"barrier\">Terumbu Karang Penghalang (Barrier Reefs)<\/option>\r\n                        <option data-i18n=\"atoll\">Terumbu Karang Atol (Atoll Reefs)<\/option>\r\n                        <option data-i18n=\"patch\">Terumbu Karang Patch (Patch Reefs)<\/option>\r\n                    <\/select>\r\n                <\/div>\r\n                <div class=\"form-group\"><label data-i18n=\"reef_position\">Posisi Karang<\/label>\r\n                    <select id=\"posisi_karang\">\r\n                        <option data-i18n=\"reef_flat\">Reef Flat (Dataran terumbu\/dangkal)<\/option>\r\n                        <option data-i18n=\"reef_crest\">Reef Crest (Puncak terumbu\/energi tinggi)<\/option>\r\n                        <option data-i18n=\"fore_reef\">Fore Reef (Lereng depan\/laut dalam)<\/option>\r\n                    <\/select>\r\n                <\/div>\r\n                <div class=\"form-group\"><label data-i18n=\"location_name\">Nama Lokasi<\/label><input type=\"text\" id=\"lokasi_nama\" placeholder=\"opsional\"><\/div>\r\n            <\/div>\r\n            <label data-i18n=\"location\">Lokasi (klik peta atau isi koordinat)<\/label>\r\n            <div class=\"map-container\" id=\"map\"><\/div>\r\n            <div class=\"form-row\">\r\n                <div class=\"form-group\"><label>Latitude<\/label><input type=\"text\" id=\"lat\" placeholder=\"cth: -8.1234\"><\/div>\r\n                <div class=\"form-group\"><label>Longitude<\/label><input type=\"text\" id=\"lng\" placeholder=\"cth: 115.1234\"><\/div>\r\n            <\/div>\r\n            <button class=\"btn\" id=\"saveProjectBtn\"><i class=\"fas fa-save\"><\/i> <span data-i18n=\"save_continue\">Simpan & Lanjut<\/span><\/button>\r\n        <\/div>\r\n\r\n        <!-- Langkah 2: Upload Foto (dengan kompresi otomatis) -->\r\n        <div id=\"step2\" class=\"card hidden\">\r\n            <h2><i class=\"fas fa-camera\"><\/i> <span data-i18n=\"upload_photos\">Upload Foto<\/span><\/h2>\r\n            <input type=\"file\" id=\"photoInput\" accept=\"image\/*\" multiple>\r\n            <p class=\"info-msg\"><i class=\"fas fa-info-circle\"><\/i> Foto akan dikompresi otomatis (maks lebar 1200px, kualitas 85%) untuk efisiensi.<\/p>\r\n            <div class=\"photo-gallery\" id=\"photoGallery\"><\/div>\r\n            <div class=\"flex-row\">\r\n                <button class=\"btn\" id=\"gotoAreaBtn\" disabled><i class=\"fas fa-arrow-right\"><\/i> <span data-i18n=\"goto_area\">Lanjut ke Penandaan Area<\/span><\/button>\r\n            <\/div>\r\n        <\/div>\r\n\r\n        <!-- Langkah 3: Area & Analisis -->\r\n        <div id=\"step3\" class=\"card hidden\">\r\n            <h2><i class=\"fas fa-draw-polygon\"><\/i> <span data-i18n=\"define_area\">Tentukan Area & Pilih Metode Analisis<\/span><\/h2>\r\n            <div class=\"photo-gallery\" id=\"activePhotoGallery\"><\/div>\r\n\r\n            <!-- Panel Kalibrasi Skala -->\r\n            <div class=\"calibration-panel\" id=\"calibrationPanel\">\r\n                <strong><i class=\"fas fa-ruler\"><\/i> <span data-i18n=\"scale_calibration\">Kalibrasi Skala<\/span><\/strong>\r\n                <div style=\"display: flex; gap: 15px; align-items: center; margin-top: 10px; flex-wrap: wrap;\">\r\n                    <button class=\"btn-outline\" id=\"startCalibrationBtn\"><i class=\"fas fa-pencil-ruler\"><\/i> <span data-i18n=\"draw_ref_line\">Gambar Garis Referensi<\/span><\/button>\r\n                    <span><span data-i18n=\"line_length\">Panjang garis (px)<\/span>: <span id=\"lineLengthPx\">0<\/span><\/span>\r\n                    <input type=\"number\" id=\"realLengthCm\" placeholder=\"Panjang sebenarnya (cm)\" step=\"0.1\" style=\"width: 180px;\">\r\n                    <button class=\"btn-outline\" id=\"applyScaleBtn\" disabled><span data-i18n=\"apply_scale\">Terapkan Skala<\/span><\/button>\r\n                    <span><span data-i18n=\"scale\">Skala<\/span>: <span id=\"scaleValue\">-<\/span> cm\/px<\/span>\r\n                <\/div>\r\n                <p class=\"info-msg\" style=\"margin-top: 8px;\" id=\"calibStatus\" data-i18n=\"not_calibrated\">Belum dikalibrasi.<\/p>\r\n            <\/div>\r\n\r\n            <!-- Tombol gambar poligon area -->\r\n            <div class=\"flex-row\" style=\"margin-bottom: 15px;\">\r\n                <button class=\"btn-outline\" id=\"clearPolygonBtn\"><i class=\"fas fa-undo\"><\/i> <span data-i18n=\"reset_area\">Reset Poligon Area<\/span><\/button>\r\n                <button class=\"btn-outline\" id=\"finishPolygonBtn\"><i class=\"fas fa-check\"><\/i> <span data-i18n=\"finish_area\">Selesai Poligon Area<\/span><\/button>\r\n                <span class=\"badge\" id=\"polygonStatus\" data-i18n=\"area_not_closed\">Poligon area belum ditutup<\/span>\r\n            <\/div>\r\n\r\n            <!-- Pilihan Level Identifikasi (Basic \/ Advanced) - Aktif setelah poligon area ditutup -->\r\n            <div class=\"level-selector\" id=\"levelSelector\" style=\"opacity:0.5; pointer-events:none;\">\r\n                <label><input type=\"radio\" name=\"identificationLevel\" value=\"basic\" checked disabled> <i class=\"fas fa-list\"><\/i> <span data-i18n=\"level_basic\">Basic (Kategori LIPI)<\/span><\/label>\r\n                <label><input type=\"radio\" name=\"identificationLevel\" value=\"advanced\" disabled> <i class=\"fas fa-list-ul\"><\/i> <span data-i18n=\"level_advanced\">Advanced (Termasuk Genus)<\/span><\/label>\r\n            <\/div>\r\n\r\n            <!-- Pilihan Metode (aktif setelah level dipilih) -->\r\n            <div class=\"mode-selector\" id=\"modeSelector\" style=\"opacity:0.5; pointer-events:none;\">\r\n                <label><input type=\"radio\" name=\"analysisMode\" value=\"point\" disabled> <i class=\"fas fa-shuffle\"><\/i> <span data-i18n=\"point_method\">Metode Titik Acak (30 titik)<\/span><\/label>\r\n                <label><input type=\"radio\" name=\"analysisMode\" value=\"polygon\" disabled> <i class=\"fas fa-draw-polygon\"><\/i> <span data-i18n=\"polygon_method\">Metode Luasan (Poligon Tutupan)<\/span><\/label>\r\n            <\/div>\r\n\r\n            <div class=\"canvas-area\">\r\n                <div class=\"canvas-wrapper\">\r\n                    <canvas id=\"analysisCanvas\" width=\"600\" height=\"400\"><\/canvas>\r\n                    <div class=\"flex-row\" style=\"margin-top:15px\">\r\n                        <button class=\"btn\" id=\"generatePointsBtn\" style=\"display: none;\"><i class=\"fas fa-shuffle\"><\/i> <span data-i18n=\"generate_points\">Generate 30 Titik Acak<\/span><\/button>\r\n                        <button class=\"btn\" id=\"addCoverPolygonBtn\" style=\"display: none;\"><i class=\"fas fa-plus\"><\/i> <span data-i18n=\"add_polygon\">Tambah Poligon Tutupan<\/span><\/button>\r\n                    <\/div>\r\n                    <p class=\"info-msg\" id=\"canvasHelp\" data-i18n=\"canvas_help\">Klik kiri untuk menambah titik poligon area. Klik kanan untuk menutup poligon.<\/p>\r\n                <\/div>\r\n\r\n                <!-- Panel Titik Acak -->\r\n                <div class=\"point-panel\" id=\"pointPanel\" style=\"display: none;\">\r\n                    <div class=\"panel-header\">\r\n                        <h3 style=\"margin:0\"><span data-i18n=\"random_points\">30 Titik Acak<\/span> - Foto <span id=\"activePhotoId\">1<\/span><\/h3>\r\n                        <button class=\"reset-zoom-btn\" id=\"resetZoomBtn\"><i class=\"fas fa-search-minus\"><\/i> <span data-i18n=\"reset_zoom\">Reset Zoom<\/span><\/button>\r\n                    <\/div>\r\n                    <div id=\"pointsList\"><\/div>\r\n                <\/div>\r\n\r\n                <!-- Panel Poligon Tutupan (dengan tombol zoom) -->\r\n                <div class=\"polygon-panel\" id=\"coverPolygonPanel\" style=\"display: none;\">\r\n                    <div class=\"panel-header\">\r\n                        <h3 style=\"margin:0\"><span data-i18n=\"cover_polygons\">Poligon Tutupan<\/span> - Foto <span id=\"activePhotoIdCover\">1<\/span><\/h3>\r\n                        <div>\r\n                            <button class=\"reset-zoom-btn\" id=\"zoomInCoverBtn\"><i class=\"fas fa-search-plus\"><\/i><\/button>\r\n                            <button class=\"reset-zoom-btn\" id=\"zoomOutCoverBtn\"><i class=\"fas fa-search-minus\"><\/i><\/button>\r\n                            <button class=\"reset-zoom-btn\" id=\"resetZoomCoverBtn\"><i class=\"fas fa-undo\"><\/i> <span data-i18n=\"reset\">Reset<\/span><\/button>\r\n                        <\/div>\r\n                    <\/div>\r\n                    <div id=\"coverPolygonsList\"><\/div>\r\n                <\/div>\r\n            <\/div>\r\n        <\/div>\r\n\r\n        <!-- Tombol Export Excel -->\r\n        <div class=\"card hidden\" id=\"exportSection\">\r\n            <button class=\"btn\" id=\"exportExcelBtn\"><i class=\"fas fa-file-excel\"><\/i> <span data-i18n=\"export_excel\">Export ke Excel<\/span><\/button>\r\n        <\/div>\r\n    <\/div>\r\n<\/div>\r\n\r\n<!-- Popup untuk input kategori poligon tutupan -->\r\n<div id=\"categoryPopup\" class=\"popup-overlay\" style=\"display: none;\">\r\n    <div class=\"popup-content\">\r\n        <h3 id=\"popupTitle\" data-i18n=\"polygon_category\">Kategori Poligon<\/h3>\r\n        <input type=\"text\" id=\"categoryInput\" placeholder=\"Ketik atau pilih dari daftar\" list=\"categoryList\" autocomplete=\"off\">\r\n        <datalist id=\"categoryList\"><\/datalist>\r\n        <div class=\"popup-buttons\">\r\n            <button class=\"btn-outline\" id=\"popupCancel\" data-i18n=\"cancel\">Batal<\/button>\r\n            <button class=\"btn\" id=\"popupSave\" data-i18n=\"save\">Simpan<\/button>\r\n        <\/div>\r\n    <\/div>\r\n<\/div>\r\n\r\n<!-- Library -->\r\n<script src=\"https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.js\"><\/script>\r\n<script src=\"https:\/\/cdn.sheetjs.com\/xlsx-0.20.2\/package\/dist\/xlsx.full.min.js\"><\/script>\r\n<script>\r\n    (function() {\r\n        \/\/ ---------- Data Global ----------\r\n        let projectData = {\r\n            nama_project: '',\r\n            surveyor: '',\r\n            waktu: '',\r\n            lokasi: { lat: -8.65, lng: 115.216, nama: '' },\r\n            site: '',\r\n            pengulangan: '',\r\n            kedalaman: '',\r\n            visibility: '',\r\n            tipe_struktur: 'Terumbu Karang Tepi (Fringing Reefs)',\r\n            posisi_karang: 'Reef Flat (Dataran terumbu\/dangkal)'\r\n        };\r\n\r\n        let photos = []; \/\/ setiap elemen: { id, dataURL, image, width, height, polygonPoints, isPolygonClosed, randomPoints, selectedCategories, scale, coverPolygons [] }\r\n        let activePhotoIndex = 0;\r\n\r\n        \/\/ Mode analisis\r\n        let analysisMode = 'point';\r\n        \/\/ Level identifikasi\r\n        let identificationLevel = 'basic'; \/\/ 'basic' atau 'advanced'\r\n\r\n        \/\/ Kalibrasi\r\n        let calibrationMode = false;\r\n        let calibrationPoints = [];\r\n        let lineLengthPx = 0;\r\n\r\n        \/\/ Zoom\r\n        let zoomState = { active: false, x: 0, y: 0, factor: 2, zoomedPointIndex: null };\r\n\r\n        \/\/ Menggambar poligon tutupan\r\n        let drawingCoverPolygon = false;\r\n        let currentCoverPolygonPoints = [];\r\n\r\n        \/\/ Pan\r\n        let isPanning = false;\r\n        let lastPanX = 0, lastPanY = 0;\r\n\r\n        \/\/ Bahasa\r\n        let currentLang = 'id';\r\n        const translations = {\r\n            id: {\r\n                app_title: 'Analisis Bentik - Titik Acak & Luasan Poligon',\r\n                app_subtitle: 'Metode standar LIPI \u2022 Kalibrasi skala \u2022 Luasan poligon \u2022 Ekspor Excel',\r\n                step1: '1. Info Proyek',\r\n                step2: '2. Upload Foto',\r\n                step3: '3. Area & Analisis',\r\n                step4: '4. Identifikasi',\r\n                project_info: 'Informasi Proyek',\r\n                project_name: 'Nama Project',\r\n                surveyor: 'Surveyor',\r\n                date: 'Tanggal',\r\n                time: 'Jam',\r\n                site: 'Site',\r\n                repetition: 'Pengulangan (opsional)',\r\n                depth: 'Kedalaman (m)',\r\n                visibility: 'Visibility (m)',\r\n                structure_type: 'Tipe Struktur',\r\n                fringing: 'Terumbu Karang Tepi (Fringing Reefs)',\r\n                barrier: 'Terumbu Karang Penghalang (Barrier Reefs)',\r\n                atoll: 'Terumbu Karang Atol (Atoll Reefs)',\r\n                patch: 'Terumbu Karang Patch (Patch Reefs)',\r\n                reef_position: 'Posisi Karang',\r\n                reef_flat: 'Reef Flat (Dataran terumbu\/dangkal)',\r\n                reef_crest: 'Reef Crest (Puncak terumbu\/energi tinggi)',\r\n                fore_reef: 'Fore Reef (Lereng depan\/laut dalam)',\r\n                location_name: 'Nama Lokasi',\r\n                location: 'Lokasi (klik peta atau isi koordinat)',\r\n                save_continue: 'Simpan & Lanjut',\r\n                upload_photos: 'Upload Foto',\r\n                goto_area: 'Lanjut ke Penandaan Area',\r\n                define_area: 'Tentukan Area & Pilih Metode Analisis',\r\n                scale_calibration: 'Kalibrasi Skala',\r\n                draw_ref_line: 'Gambar Garis Referensi',\r\n                line_length: 'Panjang garis (px)',\r\n                apply_scale: 'Terapkan Skala',\r\n                scale: 'Skala',\r\n                not_calibrated: 'Belum dikalibrasi.',\r\n                reset_area: 'Reset Poligon Area',\r\n                finish_area: 'Selesai Poligon Area',\r\n                area_not_closed: 'Poligon area belum ditutup',\r\n                level_basic: 'Basic (Kategori LIPI)',\r\n                level_advanced: 'Advanced (Termasuk Genus)',\r\n                point_method: 'Metode Titik Acak (30 titik)',\r\n                polygon_method: 'Metode Luasan (Poligon Tutupan)',\r\n                generate_points: 'Generate 30 Titik Acak',\r\n                add_polygon: 'Tambah Poligon Tutupan',\r\n                canvas_help: 'Klik kiri untuk menambah titik poligon area. Klik kanan untuk menutup poligon.',\r\n                random_points: '30 Titik Acak',\r\n                reset_zoom: 'Reset Zoom',\r\n                cover_polygons: 'Poligon Tutupan',\r\n                reset: 'Reset',\r\n                export_excel: 'Export ke Excel',\r\n                polygon_category: 'Kategori Poligon',\r\n                cancel: 'Batal',\r\n                save: 'Simpan',\r\n                area_closed: 'Poligon area ditutup',\r\n                area_reset: 'Poligon area direset',\r\n                min_3_points: 'Minimal 3 titik untuk menutup poligon area.',\r\n                area_already_closed: 'Poligon area sudah ditutup.',\r\n                draw_ref_line_instruction: 'Klik dua titik pada gambar untuk garis referensi.',\r\n                scale_applied: 'Kalibrasi selesai. Skala = ',\r\n                category_required: 'Kategori tidak boleh kosong',\r\n                drawing_in_progress: 'Sedang menggambar poligon. Selesaikan atau batalkan dengan klik kanan.',\r\n                start_new_polygon: 'Klik \"Tambah Poligon Tutupan\" untuk mulai menggambar poligon baru.',\r\n                select_category: '-- Pilih kategori --',\r\n                no_points: 'Generate titik terlebih dahulu.',\r\n                no_polygons: 'Belum ada poligon tutupan. Klik \"Tambah Poligon Tutupan\" untuk mulai.',\r\n                area: 'Luas',\r\n                area_px: 'px\u00b2',\r\n                area_cm: 'cm\u00b2',\r\n                photo: 'Foto',\r\n                \/\/ Terjemahan baru\r\n                save_project: 'Simpan Proyek',\r\n                load_project: 'Muat Proyek',\r\n            },\r\n            en: {\r\n                app_title: 'Benthic Analysis - Random Points & Area Polygons',\r\n                app_subtitle: 'LIPI standard method \u2022 Scale calibration \u2022 Area polygons \u2022 Excel export',\r\n                step1: '1. Project Info',\r\n                step2: '2. Upload Photos',\r\n                step3: '3. Area & Analysis',\r\n                step4: '4. Identification',\r\n                project_info: 'Project Information',\r\n                project_name: 'Project Name',\r\n                surveyor: 'Surveyor',\r\n                date: 'Date',\r\n                time: 'Time',\r\n                site: 'Site',\r\n                repetition: 'Repetition (optional)',\r\n                depth: 'Depth (m)',\r\n                visibility: 'Visibility (m)',\r\n                structure_type: 'Structure Type',\r\n                fringing: 'Fringing Reef',\r\n                barrier: 'Barrier Reef',\r\n                atoll: 'Atoll Reef',\r\n                patch: 'Patch Reef',\r\n                reef_position: 'Reef Position',\r\n                reef_flat: 'Reef Flat',\r\n                reef_crest: 'Reef Crest',\r\n                fore_reef: 'Fore Reef',\r\n                location_name: 'Location Name',\r\n                location: 'Location (click map or enter coordinates)',\r\n                save_continue: 'Save & Continue',\r\n                upload_photos: 'Upload Photos',\r\n                goto_area: 'Go to Area Marking',\r\n                define_area: 'Define Area & Choose Analysis Method',\r\n                scale_calibration: 'Scale Calibration',\r\n                draw_ref_line: 'Draw Reference Line',\r\n                line_length: 'Line length (px)',\r\n                apply_scale: 'Apply Scale',\r\n                scale: 'Scale',\r\n                not_calibrated: 'Not calibrated.',\r\n                reset_area: 'Reset Area Polygon',\r\n                finish_area: 'Finish Area Polygon',\r\n                area_not_closed: 'Area polygon not closed',\r\n                level_basic: 'Basic (LIPI categories)',\r\n                level_advanced: 'Advanced (Includes Genus)',\r\n                point_method: 'Random Point Method (30 points)',\r\n                polygon_method: 'Area Polygon Method (Cover polygons)',\r\n                generate_points: 'Generate 30 Random Points',\r\n                add_polygon: 'Add Cover Polygon',\r\n                canvas_help: 'Left click to add area polygon points. Right click to close polygon.',\r\n                random_points: '30 Random Points',\r\n                reset_zoom: 'Reset Zoom',\r\n                cover_polygons: 'Cover Polygons',\r\n                reset: 'Reset',\r\n                export_excel: 'Export to Excel',\r\n                polygon_category: 'Polygon Category',\r\n                cancel: 'Cancel',\r\n                save: 'Save',\r\n                area_closed: 'Area polygon closed',\r\n                area_reset: 'Area polygon reset',\r\n                min_3_points: 'Minimum 3 points to close area polygon.',\r\n                area_already_closed: 'Area polygon already closed.',\r\n                draw_ref_line_instruction: 'Click two points on the image for reference line.',\r\n                scale_applied: 'Calibration complete. Scale = ',\r\n                category_required: 'Category cannot be empty',\r\n                drawing_in_progress: 'Drawing in progress. Finish or cancel with right click.',\r\n                start_new_polygon: 'Click \"Add Cover Polygon\" to start drawing a new polygon.',\r\n                select_category: '-- Select category --',\r\n                no_points: 'Generate points first.',\r\n                no_polygons: 'No cover polygons yet. Click \"Add Cover Polygon\" to start.',\r\n                area: 'Area',\r\n                area_px: 'px\u00b2',\r\n                area_cm: 'cm\u00b2',\r\n                photo: 'Photo',\r\n                \/\/ Terjemahan baru\r\n                save_project: 'Save Project',\r\n                load_project: 'Load Project',\r\n            }\r\n        };\r\n\r\n        \/\/ Fungsi terjemahan\r\n        function t(key) {\r\n            return translations[currentLang][key] || key;\r\n        }\r\n\r\n        \/\/ Set bahasa\r\n        window.setLanguage = function(lang) {\r\n            currentLang = lang;\r\n            document.querySelectorAll('[data-i18n]').forEach(el => {\r\n                const key = el.getAttribute('data-i18n');\r\n                if (translations[lang][key]) {\r\n                    if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {\r\n                        el.placeholder = translations[lang][key];\r\n                    } else if (el.tagName === 'OPTION') {\r\n                        el.textContent = translations[lang][key];\r\n                    } else {\r\n                        el.innerHTML = translations[lang][key];\r\n                    }\r\n                }\r\n            });\r\n            \/\/ Update active button\r\n            document.getElementById('lang-id').classList.toggle('active', lang === 'id');\r\n            document.getElementById('lang-en').classList.toggle('active', lang === 'en');\r\n            \/\/ Update dynamic texts\r\n            updateDynamicTexts();\r\n        };\r\n\r\n        function updateDynamicTexts() {\r\n            const photo = photos[activePhotoIndex];\r\n            if (photo) {\r\n                if (photo.isPolygonClosed) {\r\n                    document.getElementById('polygonStatus').innerText = t('area_closed');\r\n                } else {\r\n                    document.getElementById('polygonStatus').innerText = t('area_not_closed');\r\n                }\r\n            }\r\n            if (photo && photo.scale) {\r\n                document.getElementById('calibStatus').innerText = t('scale_applied') + photo.scale.toFixed(4) + ' cm\/px';\r\n            } else {\r\n                document.getElementById('calibStatus').innerText = t('not_calibrated');\r\n            }\r\n            document.getElementById('canvasHelp').innerText = t('canvas_help');\r\n            \/\/ Update panel titles\r\n            const pointTitle = document.querySelector('#pointPanel h3');\r\n            if (pointTitle) pointTitle.innerHTML = t('random_points') + ' - ' + t('photo') + ' <span id=\"activePhotoId\">' + (activePhotoIndex+1) + '<\/span>';\r\n            const coverTitle = document.querySelector('#coverPolygonPanel h3');\r\n            if (coverTitle) coverTitle.innerHTML = t('cover_polygons') + ' - ' + t('photo') + ' <span id=\"activePhotoIdCover\">' + (activePhotoIndex+1) + '<\/span>';\r\n        }\r\n\r\n        \/\/ Daftar kategori lengkap\r\n        const subCategoryList = [\r\n            { code: \"ACB\", name: \"Acropora Branching\", parent: \"HC\" },\r\n            { code: \"ACE\", name: \"Acropora Encrusting\", parent: \"HC\" },\r\n            { code: \"ACS\", name: \"Acropora Submassive\", parent: \"HC\" },\r\n            { code: \"ACD\", name: \"Acropora Digitate\", parent: \"HC\" },\r\n            { code: \"ACT\", name: \"Acropora Tabulate\", parent: \"HC\" },\r\n            { code: \"CB\", name: \"Coral Branching\", parent: \"HC\" },\r\n            { code: \"CE\", name: \"Coral Encrusting\", parent: \"HC\" },\r\n            { code: \"CF\", name: \"Coral Foliose\", parent: \"HC\" },\r\n            { code: \"CM\", name: \"Coral Massive\", parent: \"HC\" },\r\n            { code: \"CS\", name: \"Coral Submassive\", parent: \"HC\" },\r\n            { code: \"CMR\", name: \"Coral Mushroom\", parent: \"HC\" },\r\n            { code: \"CHL\", name: \"Coral Heliopora\", parent: \"HC\" },\r\n            { code: \"CME\", name: \"Coral Millepora\", parent: \"HC\" },\r\n            { code: \"CTU\", name: \"Coral Tubipora\", parent: \"HC\" },\r\n            { code: \"DC\", name: \"Recently Dead Coral\", parent: \"DC\" },\r\n            { code: \"DCA\", name: \"Dead Coral with algae\", parent: \"DCA\" },\r\n            { code: \"SC\", name: \"Soft Coral\", parent: \"SC\" },\r\n            { code: \"SP\", name: \"Sponge\", parent: \"SP\" },\r\n            { code: \"ZO\", name: \"Zoanthid\", parent: \"OT\" },\r\n            { code: \"OT\", name: \"Other(Fauna)\", parent: \"OT\" },\r\n            { code: \"AA\", name: \"Algal assemblage\", parent: \"FS\" },\r\n            { code: \"CA\", name: \"Coralline algae\", parent: \"OT\" },\r\n            { code: \"HA\", name: \"Halimeda\", parent: \"OT\" },\r\n            { code: \"MA\", name: \"Makro Algae\", parent: \"FS\" },\r\n            { code: \"TA\", name: \"Turf Algae\", parent: \"DCA\" },\r\n            { code: \"S\", name: \"Sand\", parent: \"S\" },\r\n            { code: \"R\", name: \"Rubble\", parent: \"R\" },\r\n            { code: \"SI\", name: \"Silt\", parent: \"SI\" },\r\n            { code: \"RK\", name: \"Rock\", parent: \"RK\" },\r\n            { code: \"TWS\", name: \"Tape, Wand, Shadow\", parent: \"TWS\" }\r\n        ];\r\n\r\n        \/\/ ================== REVISI SESUAI PERMINTAAN ==================\r\n        const hardCoralGenus = [\r\n            \"Acanthastrea\", \"Acropora\", \"Agaricia\", \"Alveopora\", \"Anomastraea\", \"Astrea\", \"Astrangia\", \"Astreopora\",\r\n            \"Australogyra\", \"Australomussa\", \"Balanophyllia\", \"Barabattoia\", \"Blastomussa\", \"Boninastrea\", \"Cantharellus\",\r\n            \"Catalaphyllia\", \"Caulastrea\", \"Cladocora\", \"Coelastrea\", \"Coeloseris\", \"Colpophyllia\", \"Coscinaraea\", \"Ctenactis\",\r\n            \"Ctenella\", \"Cycloseris\", \"Cynarina\", \"Cyphastrea\", \"Dendrogyra\", \"Diaseris\", \"Dichocoenia\", \"Diploastrea\",\r\n            \"Diploria\", \"Dipsastraea\", \"Duncanopsammia\", \"Echinomorpha\", \"Echinophyllia\", \"Echinopora\", \"Erythrastrea\",\r\n            \"Euphyllia\", \"Eusmilia\", \"Favia\", \"Favites\", \"Fungia\", \"Galaxea\", \"Goniastrea\", \"Goniopora\", \"Gyrosmilia\",\r\n            \"Halomitra\", \"Heliofungia\", \"Herpolitha\", \"Heterocyathus\", \"Heteropsammia\", \"Horastrea\", \"Hydnophora\",\r\n            \"Indophyllia\", \"Isophyllia\", \"Isopora\", \"Leptastrea\", \"Leptoria\", \"Leptoseris\", \"Lithophyllon\", \"Lobophyllia\",\r\n            \"Madracis\", \"Manicina\", \"Meandrina\", \"Merulina\", \"Micromussa\", \"Montastrea\", \"Montigyra\", \"Montipora\", \"Moseleya\",\r\n            \"Mussa\", \"Mussismilia\", \"Mycedium\", \"Mycetophyllia\", \"Nemenzophyllia\", \"Oculina\", \"Oulastrea\", \"Oulophyllia\",\r\n            \"Oxypora\", \"Pachyseris\", \"Palauastrea\", \"Paraclavarina\", \"Paragoniastrea\", \"Parasimplastrea\", \"Pavona\", \"Pectinia\",\r\n            \"Physogyra\", \"Physophyllia\", \"Platygyra\", \"Plerogyra\", \"Plesiastrea\", \"Pocillopora\", \"Podabacia\", \"Polyphyllia\",\r\n            \"Porites\", \"Poritipora\", \"Psammocora\", \"Pseudosiderastrea\", \"Sandalolitha\", \"Scapophyllia\", \"Schizoculina\",\r\n            \"Scolymia\", \"Seriatopora\", \"Siderastrea\", \"Simplastrea\", \"Solenastrea\", \"Stephanocoenia\", \"Stylaraea\",\r\n            \"Stylocoeniella\", \"Stylophora\", \"Symphyllia\", \"Trachyphyllia\", \"Turbinaria\", \"Zoopilus\",\r\n            \"Heliopora (Blue Coral)\", \"Millepora (Fire Coral)\", \"Tubipora (Organ Pipe Coral)\"\r\n        ];\r\n\r\n        const softCoralGenus = [\r\n            \"Acrossota\", \"Anthelia\", \"Anthellia\", \"Antipathes (Black Coral)\", \"Asterospicularia\", \"Carijoa\",\r\n            \"Cespitularia\", \"Cladiella\", \"Clavularia\", \"Corallium (Precious Coral)\", \"Dendronephthya\", \"Efflatounaria\",\r\n            \"Heteroxenia\", \"Isis\", \"Klyxum\", \"Litophyton\", \"Lobophytum\", \"Nephthea\", \"Ovabunda\",\r\n            \"Rumphella\", \"Sansibia\", \"Sarcophyton\", \"Sarcothelia\", \"Sinularia\", \"Sympodium\", \"Xenia\"\r\n        ];\r\n        \/\/ ================== AKHIR REVISI ==================\r\n\r\n        function buildFullCategoryList() {\r\n            let list = [];\r\n            subCategoryList.forEach(c => list.push(c.code + ' - ' + c.name));\r\n            hardCoralGenus.forEach(g => list.push('HCG - ' + g));\r\n            softCoralGenus.forEach(g => list.push('SCG - ' + g));\r\n            return list;\r\n        }\r\n        const fullCategoryList = buildFullCategoryList();\r\n        const datalist = document.getElementById('categoryList');\r\n        fullCategoryList.forEach(opt => {\r\n            const option = document.createElement('option');\r\n            option.value = opt;\r\n            datalist.appendChild(option);\r\n        });\r\n\r\n        \/\/ Fungsi untuk mendapatkan opsi dropdown berdasarkan level\r\n        function getCategoryOptions(selectedValue) {\r\n            let options = '';\r\n            if (identificationLevel === 'basic') {\r\n                options = subCategoryList.map(cat => `<option value=\"${cat.code}\" ${selectedValue === cat.code ? 'selected' : ''}>${cat.code} - ${cat.name}<\/option>`).join('');\r\n            } else {\r\n                \/\/ advanced: gabungan subCategoryList + genus\r\n                const allOptions = [];\r\n                subCategoryList.forEach(cat => allOptions.push({ value: cat.code, text: cat.code + ' - ' + cat.name }));\r\n                hardCoralGenus.forEach(g => allOptions.push({ value: 'HCG - ' + g, text: 'HCG - ' + g }));\r\n                softCoralGenus.forEach(g => allOptions.push({ value: 'SCG - ' + g, text: 'SCG - ' + g }));\r\n                options = allOptions.map(opt => `<option value=\"${opt.value}\" ${selectedValue === opt.value ? 'selected' : ''}>${opt.text}<\/option>`).join('');\r\n            }\r\n            return options;\r\n        }\r\n\r\n        \/\/ Fungsi untuk memperbarui dropdown di panel titik acak sesuai level\r\n        function renderPointListForActivePhoto() {\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo) return;\r\n            const container = document.getElementById('pointsList');\r\n            if (!photo.randomPoints || photo.randomPoints.length === 0) {\r\n                container.innerHTML = '<p class=\"info-msg\">' + t('no_points') + '<\/p>';\r\n                return;\r\n            }\r\n            let html = '';\r\n            photo.randomPoints.forEach((p, idx) => {\r\n                const selected = photo.selectedCategories[idx] || '';\r\n                html += `<div class=\"point-item\">\r\n                    <span class=\"point-number\" data-index=\"${idx}\">${idx+1}<\/span>\r\n                    <select class=\"point-select\" data-index=\"${idx}\">\r\n                        <option value=\"\">${t('select_category')}<\/option>\r\n                        ${getCategoryOptions(selected)}\r\n                    <\/select>\r\n                <\/div>`;\r\n            });\r\n            container.innerHTML = html;\r\n\r\n            document.querySelectorAll('.point-select').forEach(select => {\r\n                select.addEventListener('change', (e) => {\r\n                    const idx = e.target.dataset.index;\r\n                    photo.selectedCategories[idx] = e.target.value;\r\n                    drawPhotoOnCanvas();\r\n                });\r\n            });\r\n        }\r\n\r\n        \/\/ Fungsi untuk memperbarui daftar poligon tutupan (tidak bergantung level, karena kategori disimpan sebagai teks)\r\n        function renderCoverPolygonListForActivePhoto() {\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo) return;\r\n            const container = document.getElementById('coverPolygonsList');\r\n            if (!photo.coverPolygons || photo.coverPolygons.length === 0) {\r\n                container.innerHTML = '<p class=\"info-msg\">' + t('no_polygons') + '<\/p>';\r\n                return;\r\n            }\r\n            let html = '';\r\n            photo.coverPolygons.forEach((poly, idx) => {\r\n                const areaPx = polygonArea(poly.points);\r\n                const areaCm2 = photo.scale ? (areaPx * photo.scale * photo.scale).toFixed(2) : '?';\r\n                html += `<div class=\"polygon-item\">\r\n                    <div class=\"polygon-color\" style=\"background: ${poly.color}\"><\/div>\r\n                    <div class=\"polygon-info\">\r\n                        <div class=\"polygon-category\">${poly.category}<\/div>\r\n                        <div class=\"polygon-area\">${t('area')}: ${areaPx.toFixed(2)} ${t('area_px')} (${areaCm2} ${t('area_cm')})<\/div>\r\n                    <\/div>\r\n                    <div class=\"polygon-actions\">\r\n                        <button class=\"edit-cover-polygon\" data-index=\"${idx}\"><i class=\"fas fa-edit\"><\/i><\/button>\r\n                        <button class=\"delete-cover-polygon\" data-index=\"${idx}\"><i class=\"fas fa-trash\"><\/i><\/button>\r\n                    <\/div>\r\n                <\/div>`;\r\n            });\r\n            container.innerHTML = html;\r\n\r\n            document.querySelectorAll('.edit-cover-polygon').forEach(btn => {\r\n                btn.addEventListener('click', (e) => {\r\n                    const idx = e.currentTarget.dataset.index;\r\n                    editCoverPolygon(parseInt(idx));\r\n                });\r\n            });\r\n            document.querySelectorAll('.delete-cover-polygon').forEach(btn => {\r\n                btn.addEventListener('click', (e) => {\r\n                    const idx = e.currentTarget.dataset.index;\r\n                    deleteCoverPolygon(parseInt(idx));\r\n                });\r\n            });\r\n        }\r\n\r\n        \/\/ ---------- Inisialisasi Peta ----------\r\n        const map = L.map('map').setView([projectData.lokasi.lat, projectData.lokasi.lng], 13);\r\n        L.tileLayer('https:\/\/{s}.tile.openstreetmap.org\/{z}\/{x}\/{y}.png', { attribution: '&copy; OpenStreetMap' }).addTo(map);\r\n        let marker = L.marker([projectData.lokasi.lat, projectData.lokasi.lng]).addTo(map);\r\n        map.on('click', (e) => {\r\n            const { lat, lng } = e.latlng;\r\n            document.getElementById('lat').value = lat.toFixed(6);\r\n            document.getElementById('lng').value = lng.toFixed(6);\r\n            marker.setLatLng([lat, lng]);\r\n            projectData.lokasi.lat = lat;\r\n            projectData.lokasi.lng = lng;\r\n        });\r\n        document.getElementById('lat').addEventListener('input', updateMarkerFromInput);\r\n        document.getElementById('lng').addEventListener('input', updateMarkerFromInput);\r\n        function updateMarkerFromInput() {\r\n            const lat = parseFloat(document.getElementById('lat').value);\r\n            const lng = parseFloat(document.getElementById('lng').value);\r\n            if (!isNaN(lat) && !isNaN(lng)) {\r\n                marker.setLatLng([lat, lng]);\r\n                map.panTo([lat, lng]);\r\n                projectData.lokasi.lat = lat;\r\n                projectData.lokasi.lng = lng;\r\n            }\r\n        }\r\n\r\n        \/\/ ---------- UI Navigation ----------\r\n        function showStep(step) {\r\n            document.querySelectorAll('#step1, #step2, #step3, #exportSection').forEach(el => el.classList.add('hidden'));\r\n            if (step === 1) document.getElementById('step1').classList.remove('hidden');\r\n            if (step === 2) document.getElementById('step2').classList.remove('hidden');\r\n            if (step === 3) {\r\n                document.getElementById('step3').classList.remove('hidden');\r\n                document.getElementById('exportSection').classList.remove('hidden');\r\n            }\r\n            document.querySelectorAll('.step').forEach((el, idx) => {\r\n                if (idx+1 === step) el.classList.add('active');\r\n                else el.classList.remove('active');\r\n            });\r\n        }\r\n\r\n        document.getElementById('saveProjectBtn').addEventListener('click', () => {\r\n            projectData.nama_project = document.getElementById('nama_project').value;\r\n            projectData.surveyor = document.getElementById('surveyor').value;\r\n            const tgl = document.getElementById('tanggal').value;\r\n            const jam = document.getElementById('jam').value;\r\n            projectData.waktu = tgl + ' ' + jam;\r\n            projectData.site = document.getElementById('site').value;\r\n            projectData.pengulangan = document.getElementById('pengulangan').value;\r\n            projectData.kedalaman = document.getElementById('kedalaman').value;\r\n            projectData.visibility = document.getElementById('visibility').value;\r\n            projectData.tipe_struktur = document.getElementById('tipe_struktur').value;\r\n            projectData.posisi_karang = document.getElementById('posisi_karang').value;\r\n            projectData.lokasi.nama = document.getElementById('lokasi_nama').value;\r\n            showStep(2);\r\n        });\r\n\r\n        \/\/ ========== FUNGSI KOMPRESI GAMBAR ==========\r\n        function compressImage(file, maxWidth = 1200, quality = 0.85) {\r\n            return new Promise((resolve, reject) => {\r\n                const reader = new FileReader();\r\n                reader.readAsDataURL(file);\r\n                reader.onload = (e) => {\r\n                    const img = new Image();\r\n                    img.src = e.target.result;\r\n                    img.onload = () => {\r\n                        const canvas = document.createElement('canvas');\r\n                        let width = img.width;\r\n                        let height = img.height;\r\n                        \/\/ Hitung dimensi baru jika lebar > maxWidth\r\n                        if (width > maxWidth) {\r\n                            height = Math.round((height * maxWidth) \/ width);\r\n                            width = maxWidth;\r\n                        }\r\n                        canvas.width = width;\r\n                        canvas.height = height;\r\n                        const ctx = canvas.getContext('2d');\r\n                        ctx.drawImage(img, 0, 0, width, height);\r\n                        \/\/ Konversi ke blob JPEG dengan kualitas tertentu\r\n                        canvas.toBlob((blob) => {\r\n                            const compressedReader = new FileReader();\r\n                            compressedReader.readAsDataURL(blob);\r\n                            compressedReader.onload = () => resolve(compressedReader.result);\r\n                            compressedReader.onerror = reject;\r\n                        }, 'image\/jpeg', quality);\r\n                    };\r\n                    img.onerror = reject;\r\n                };\r\n                reader.onerror = reject;\r\n            });\r\n        }\r\n\r\n        document.getElementById('photoInput').addEventListener('change', async function(e) {\r\n            const files = Array.from(e.target.files);\r\n            if (files.length === 0) return;\r\n\r\n            for (const file of files) {\r\n                try {\r\n                    const compressedDataURL = await compressImage(file);\r\n                    const img = new Image();\r\n                    img.onload = () => {\r\n                        photos.push({\r\n                            id: photos.length + 1,\r\n                            dataURL: compressedDataURL,\r\n                            image: img,\r\n                            width: img.width,\r\n                            height: img.height,\r\n                            polygonPoints: [],\r\n                            isPolygonClosed: false,\r\n                            randomPoints: [],\r\n                            selectedCategories: [],\r\n                            coverPolygons: [],\r\n                            scale: null\r\n                        });\r\n                        updatePhotoGallery();\r\n                        if (photos.length > 0) {\r\n                            document.getElementById('gotoAreaBtn').disabled = false;\r\n                        }\r\n                        if (activePhotoIndex === 0 && photos.length === 1) {\r\n                            activePhotoIndex = 0;\r\n                        }\r\n                    };\r\n                    img.src = compressedDataURL;\r\n                } catch (err) {\r\n                    console.error('Gagal mengompres gambar', err);\r\n                    alert('Gagal memproses gambar: ' + file.name);\r\n                }\r\n            }\r\n        });\r\n\r\n        function updatePhotoGallery() {\r\n            const gallery = document.getElementById('photoGallery');\r\n            gallery.innerHTML = '';\r\n            photos.forEach((photo, index) => {\r\n                const thumb = document.createElement('div');\r\n                thumb.className = 'photo-thumb' + (index === activePhotoIndex ? ' active' : '');\r\n                thumb.innerHTML = `<img decoding=\"async\" src=\"${photo.dataURL}\" \/><div class=\"photo-index\">${t('photo')} ${index+1}<\/div>`;\r\n                thumb.addEventListener('click', () => {\r\n                    activePhotoIndex = index;\r\n                    updatePhotoGallery();\r\n                });\r\n                gallery.appendChild(thumb);\r\n            });\r\n        }\r\n\r\n        document.getElementById('gotoAreaBtn').addEventListener('click', () => {\r\n            if (photos.length === 0) return;\r\n            activePhotoIndex = 0;\r\n            loadActivePhotoToCanvas();\r\n            showStep(3);\r\n            updateActivePhotoGallery();\r\n            resetCalibrationUI();\r\n        });\r\n\r\n        function updateActivePhotoGallery() {\r\n            const gallery = document.getElementById('activePhotoGallery');\r\n            if (!gallery) return;\r\n            gallery.innerHTML = '';\r\n            photos.forEach((photo, index) => {\r\n                const thumb = document.createElement('div');\r\n                thumb.className = 'photo-thumb' + (index === activePhotoIndex ? ' active' : '');\r\n                thumb.innerHTML = `<img decoding=\"async\" src=\"${photo.dataURL}\" \/><div class=\"photo-index\">${t('photo')} ${index+1}<\/div>`;\r\n                thumb.addEventListener('click', () => {\r\n                    activePhotoIndex = index;\r\n                    loadActivePhotoToCanvas();\r\n                    updateActivePhotoGallery();\r\n                    renderPointListForActivePhoto();\r\n                    renderCoverPolygonListForActivePhoto();\r\n                    resetCalibrationUI();\r\n                    resetZoom();\r\n                    updateModeSelectorState();\r\n                });\r\n                gallery.appendChild(thumb);\r\n            });\r\n        }\r\n\r\n        \/\/ Canvas\r\n        const canvas = document.getElementById('analysisCanvas');\r\n        const ctx = canvas.getContext('2d');\r\n\r\n        function loadActivePhotoToCanvas() {\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo) return;\r\n            const maxW = 600;\r\n            const scale = Math.min(1, maxW \/ photo.width);\r\n            canvas.width = photo.width * scale;\r\n            canvas.height = photo.height * scale;\r\n            photo.canvasScale = scale;\r\n            resetZoom();\r\n            drawPhotoOnCanvas();\r\n            document.getElementById('activePhotoId').innerText = activePhotoIndex + 1;\r\n            document.getElementById('activePhotoIdCover').innerText = activePhotoIndex + 1;\r\n            if (photo.scale) {\r\n                document.getElementById('scaleValue').innerText = photo.scale.toFixed(4);\r\n                document.getElementById('calibStatus').innerText = t('scale_applied') + photo.scale.toFixed(4) + ' cm\/px';\r\n            } else {\r\n                document.getElementById('scaleValue').innerText = '-';\r\n                document.getElementById('calibStatus').innerText = t('not_calibrated');\r\n            }\r\n            updateModeSelectorState();\r\n        }\r\n\r\n        function drawPhotoOnCanvas() {\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo) return;\r\n            ctx.clearRect(0, 0, canvas.width, canvas.height);\r\n            \r\n            ctx.save();\r\n            if (zoomState.active) {\r\n                ctx.translate(zoomState.x, zoomState.y);\r\n                ctx.scale(zoomState.factor, zoomState.factor);\r\n                ctx.translate(-zoomState.x, -zoomState.y);\r\n            }\r\n\r\n            \/\/ Gambar foto\r\n            ctx.drawImage(photo.image, 0, 0, canvas.width, canvas.height);\r\n\r\n            \/\/ Garis kalibrasi\r\n            if (calibrationMode && calibrationPoints.length > 0) {\r\n                ctx.beginPath();\r\n                ctx.strokeStyle = '#ffaa00';\r\n                ctx.lineWidth = 4;\r\n                ctx.setLineDash([10, 5]);\r\n                calibrationPoints.forEach((p, i) => {\r\n                    if (i === 0) ctx.moveTo(p.x, p.y);\r\n                    else ctx.lineTo(p.x, p.y);\r\n                });\r\n                ctx.stroke();\r\n                ctx.setLineDash([]);\r\n                calibrationPoints.forEach((p, i) => {\r\n                    ctx.beginPath();\r\n                    ctx.fillStyle = 'gold';\r\n                    ctx.arc(p.x, p.y, 8, 0, 2*Math.PI);\r\n                    ctx.fill();\r\n                    ctx.fillStyle = 'black';\r\n                    ctx.font = 'bold 14px monospace';\r\n                    ctx.fillText(i+1, p.x+12, p.y-12);\r\n                });\r\n            }\r\n\r\n            \/\/ Poligon area (hanya stroke, tanpa fill) - titik diperkecil\r\n            if (photo.polygonPoints.length > 0) {\r\n                ctx.beginPath();\r\n                ctx.strokeStyle = '#00ffaa';\r\n                ctx.lineWidth = 2;\r\n                photo.polygonPoints.forEach((p, i) => {\r\n                    if (i === 0) ctx.moveTo(p.x, p.y);\r\n                    else ctx.lineTo(p.x, p.y);\r\n                });\r\n                if (photo.isPolygonClosed) {\r\n                    ctx.closePath();\r\n                }\r\n                ctx.stroke();\r\n                photo.polygonPoints.forEach((p, i) => {\r\n                    ctx.beginPath();\r\n                    ctx.fillStyle = 'yellow';\r\n                    ctx.arc(p.x, p.y, 2, 0, 2*Math.PI);\r\n                    ctx.fill();\r\n                    ctx.fillStyle = 'black';\r\n                    ctx.font = '8px bold';\r\n                    ctx.fillText(i+1, p.x+4, p.y-4);\r\n                });\r\n            }\r\n\r\n            \/\/ Poligon tutupan - stroke dan titik lebih kecil\r\n            if (photo.coverPolygons) {\r\n                photo.coverPolygons.forEach((poly, idx) => {\r\n                    if (poly.points.length < 3) return;\r\n                    ctx.beginPath();\r\n                    ctx.strokeStyle = poly.color || '#ff3333';\r\n                    ctx.lineWidth = 2;\r\n                    poly.points.forEach((p, i) => {\r\n                        if (i === 0) ctx.moveTo(p.x, p.y);\r\n                        else ctx.lineTo(p.x, p.y);\r\n                    });\r\n                    ctx.closePath();\r\n                    ctx.fillStyle = poly.color ? poly.color + '33' : 'rgba(255,51,51,0.2)';\r\n                    ctx.fill();\r\n                    ctx.stroke();\r\n                    ctx.fillStyle = 'black';\r\n                    ctx.font = 'bold 10px monospace';\r\n                    ctx.shadowColor = 'white';\r\n                    ctx.shadowBlur = 3;\r\n                    const cx = poly.points.reduce((s,p) => s + p.x, 0) \/ poly.points.length;\r\n                    const cy = poly.points.reduce((s,p) => s + p.y, 0) \/ poly.points.length;\r\n                    ctx.fillText(idx+1, cx, cy);\r\n                    ctx.shadowBlur = 0;\r\n                });\r\n            }\r\n\r\n            \/\/ Random points (mode point) - ukuran 3\r\n            if (analysisMode === 'point' && photo.randomPoints) {\r\n                photo.randomPoints.forEach((p, idx) => {\r\n                    ctx.beginPath();\r\n                    let fillColor;\r\n                    if (zoomState.active && zoomState.zoomedPointIndex === idx) {\r\n                        fillColor = '#3366ff';\r\n                    } else {\r\n                        fillColor = photo.selectedCategories && photo.selectedCategories[idx] ? '#4caf50' : '#f44336';\r\n                    }\r\n                    ctx.fillStyle = fillColor;\r\n                    ctx.arc(p.x, p.y, 3, 0, 2*Math.PI);\r\n                    ctx.fill();\r\n                    ctx.strokeStyle = 'white';\r\n                    ctx.lineWidth = 1;\r\n                    ctx.stroke();\r\n                    ctx.fillStyle = 'white';\r\n                    ctx.font = 'bold 8px monospace';\r\n                    ctx.shadowColor = 'black';\r\n                    ctx.shadowBlur = 2;\r\n                    ctx.fillText(idx+1, p.x-5, p.y-6);\r\n                    ctx.shadowBlur = 0;\r\n                });\r\n            }\r\n\r\n            \/\/ Sedang menggambar poligon tutupan - titik lebih kecil\r\n            if (drawingCoverPolygon && currentCoverPolygonPoints.length > 0) {\r\n                ctx.beginPath();\r\n                ctx.strokeStyle = '#ffaa00';\r\n                ctx.lineWidth = 2;\r\n                ctx.setLineDash([5, 5]);\r\n                currentCoverPolygonPoints.forEach((p, i) => {\r\n                    if (i === 0) ctx.moveTo(p.x, p.y);\r\n                    else ctx.lineTo(p.x, p.y);\r\n                });\r\n                ctx.stroke();\r\n                ctx.setLineDash([]);\r\n                currentCoverPolygonPoints.forEach((p, i) => {\r\n                    ctx.beginPath();\r\n                    ctx.fillStyle = 'orange';\r\n                    ctx.arc(p.x, p.y, 2, 0, 2*Math.PI);\r\n                    ctx.fill();\r\n                });\r\n            }\r\n\r\n            ctx.restore();\r\n        }\r\n\r\n        \/\/ Fungsi konversi koordinat layar ke dunia (memperhitungkan zoom)\r\n        function screenToWorld(x, y) {\r\n            if (!zoomState.active) return { x, y };\r\n            return {\r\n                x: zoomState.x + (x - zoomState.x) \/ zoomState.factor,\r\n                y: zoomState.y + (y - zoomState.y) \/ zoomState.factor\r\n            };\r\n        }\r\n\r\n        \/\/ Event canvas\r\n        canvas.addEventListener('click', (e) => {\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo) return;\r\n            const rect = canvas.getBoundingClientRect();\r\n            const scaleX = canvas.width \/ rect.width;\r\n            const scaleY = canvas.height \/ rect.height;\r\n            let x = (e.clientX - rect.left) * scaleX;\r\n            let y = (e.clientY - rect.top) * scaleY;\r\n\r\n            if (calibrationMode) {\r\n                if (calibrationPoints.length < 2) {\r\n                    calibrationPoints.push({ x, y });\r\n                    if (calibrationPoints.length === 2) {\r\n                        const dx = calibrationPoints[1].x - calibrationPoints[0].x;\r\n                        const dy = calibrationPoints[1].y - calibrationPoints[0].y;\r\n                        lineLengthPx = Math.sqrt(dx*dx + dy*dy);\r\n                        document.getElementById('lineLengthPx').innerText = lineLengthPx.toFixed(2);\r\n                        document.getElementById('applyScaleBtn').disabled = false;\r\n                    }\r\n                    drawPhotoOnCanvas();\r\n                }\r\n                return;\r\n            }\r\n\r\n            \/\/ Konversi koordinat ke dunia jika zoom aktif\r\n            if (zoomState.active) {\r\n                const world = screenToWorld(x, y);\r\n                x = world.x;\r\n                y = world.y;\r\n            }\r\n\r\n            if (!photo.isPolygonClosed) {\r\n                photo.polygonPoints.push({ x, y });\r\n                drawPhotoOnCanvas();\r\n                document.getElementById('polygonStatus').innerText = t('area_not_closed');\r\n            } else {\r\n                if (analysisMode === 'point') {\r\n                    alert(t('area_already_closed'));\r\n                } else if (analysisMode === 'polygon') {\r\n                    if (drawingCoverPolygon) {\r\n                        currentCoverPolygonPoints.push({ x, y });\r\n                        drawPhotoOnCanvas();\r\n                    } else {\r\n                        alert(t('start_new_polygon'));\r\n                    }\r\n                }\r\n            }\r\n        });\r\n\r\n        canvas.addEventListener('contextmenu', (e) => {\r\n            e.preventDefault();\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo) return;\r\n\r\n            if (calibrationMode) {\r\n                calibrationMode = false;\r\n                calibrationPoints = [];\r\n                document.getElementById('calibStatus').innerText = t('not_calibrated');\r\n                drawPhotoOnCanvas();\r\n                return;\r\n            }\r\n\r\n            if (!photo.isPolygonClosed) {\r\n                if (photo.polygonPoints.length < 3) {\r\n                    alert(t('min_3_points'));\r\n                    return;\r\n                }\r\n                photo.isPolygonClosed = true;\r\n                drawPhotoOnCanvas();\r\n                document.getElementById('polygonStatus').innerText = t('area_closed');\r\n                \/\/ Aktifkan level selector setelah area ditutup\r\n                enableLevelSelector();\r\n            } else {\r\n                if (analysisMode === 'polygon' && drawingCoverPolygon) {\r\n                    if (currentCoverPolygonPoints.length < 3) {\r\n                        alert(t('min_3_points'));\r\n                        drawingCoverPolygon = false;\r\n                        currentCoverPolygonPoints = [];\r\n                        drawPhotoOnCanvas();\r\n                        return;\r\n                    }\r\n                    finishDrawingCoverPolygon();\r\n                }\r\n            }\r\n        });\r\n\r\n        document.getElementById('finishPolygonBtn').addEventListener('click', () => {\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo) return;\r\n            if (!photo.isPolygonClosed && photo.polygonPoints.length >= 3) {\r\n                photo.isPolygonClosed = true;\r\n                drawPhotoOnCanvas();\r\n                document.getElementById('polygonStatus').innerText = t('area_closed');\r\n                enableLevelSelector();\r\n            } else if (photo.isPolygonClosed) {\r\n                alert(t('area_already_closed'));\r\n            } else {\r\n                alert(t('min_3_points'));\r\n            }\r\n        });\r\n\r\n        document.getElementById('clearPolygonBtn').addEventListener('click', () => {\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo) return;\r\n            photo.polygonPoints = [];\r\n            photo.isPolygonClosed = false;\r\n            photo.randomPoints = [];\r\n            photo.selectedCategories = [];\r\n            photo.coverPolygons = [];\r\n            drawPhotoOnCanvas();\r\n            renderPointListForActivePhoto();\r\n            renderCoverPolygonListForActivePhoto();\r\n            resetZoom();\r\n            document.getElementById('polygonStatus').innerText = t('area_reset');\r\n            disableLevelSelector();\r\n            disableModeSelector();\r\n        });\r\n\r\n        \/\/ Fungsi untuk mengaktifkan\/menonaktifkan level selector\r\n        function enableLevelSelector() {\r\n            const levelDiv = document.getElementById('levelSelector');\r\n            levelDiv.style.opacity = '1';\r\n            levelDiv.style.pointerEvents = 'auto';\r\n            document.querySelectorAll('input[name=\"identificationLevel\"]').forEach(r => r.disabled = false);\r\n        }\r\n\r\n        function disableLevelSelector() {\r\n            const levelDiv = document.getElementById('levelSelector');\r\n            levelDiv.style.opacity = '0.5';\r\n            levelDiv.style.pointerEvents = 'none';\r\n            document.querySelectorAll('input[name=\"identificationLevel\"]').forEach(r => r.disabled = true);\r\n        }\r\n\r\n        \/\/ Event listener untuk level\r\n        document.querySelectorAll('input[name=\"identificationLevel\"]').forEach(radio => {\r\n            radio.addEventListener('change', (e) => {\r\n                identificationLevel = e.target.value;\r\n                \/\/ Perbarui dropdown titik acak jika ada\r\n                if (photos[activePhotoIndex] && photos[activePhotoIndex].randomPoints) {\r\n                    renderPointListForActivePhoto();\r\n                }\r\n                \/\/ Aktifkan mode selector setelah level dipilih\r\n                enableModeSelector();\r\n            });\r\n        });\r\n\r\n        function enableModeSelector() {\r\n            const modeDiv = document.getElementById('modeSelector');\r\n            modeDiv.style.opacity = '1';\r\n            modeDiv.style.pointerEvents = 'auto';\r\n            document.querySelectorAll('input[name=\"analysisMode\"]').forEach(r => r.disabled = false);\r\n            \/\/ Set default mode point\r\n            document.querySelector('input[name=\"analysisMode\"][value=\"point\"]').checked = true;\r\n            analysisMode = 'point';\r\n            document.getElementById('pointPanel').style.display = 'block';\r\n            document.getElementById('coverPolygonPanel').style.display = 'none';\r\n            document.getElementById('generatePointsBtn').style.display = 'inline-block';\r\n            document.getElementById('addCoverPolygonBtn').style.display = 'none';\r\n            drawPhotoOnCanvas();\r\n        }\r\n\r\n        function disableModeSelector() {\r\n            const modeDiv = document.getElementById('modeSelector');\r\n            modeDiv.style.opacity = '0.5';\r\n            modeDiv.style.pointerEvents = 'none';\r\n            document.querySelectorAll('input[name=\"analysisMode\"]').forEach(r => r.disabled = true);\r\n        }\r\n\r\n        function updateModeSelectorState() {\r\n            const photo = photos[activePhotoIndex];\r\n            if (photo && photo.isPolygonClosed) {\r\n                enableLevelSelector();\r\n            } else {\r\n                disableLevelSelector();\r\n                disableModeSelector();\r\n            }\r\n        }\r\n\r\n        \/\/ Tombol tambah poligon tutupan\r\n        document.getElementById('addCoverPolygonBtn').addEventListener('click', () => {\r\n            if (analysisMode !== 'polygon') return;\r\n            if (drawingCoverPolygon) {\r\n                alert(t('drawing_in_progress'));\r\n                return;\r\n            }\r\n            drawingCoverPolygon = true;\r\n            currentCoverPolygonPoints = [];\r\n            drawPhotoOnCanvas();\r\n        });\r\n\r\n        function finishDrawingCoverPolygon() {\r\n            window.pendingCoverPolygon = [...currentCoverPolygonPoints];\r\n            drawingCoverPolygon = false;\r\n            currentCoverPolygonPoints = [];\r\n            document.getElementById('popupTitle').innerText = t('polygon_category');\r\n            document.getElementById('categoryInput').value = '';\r\n            document.getElementById('categoryPopup').style.display = 'flex';\r\n        }\r\n\r\n        document.getElementById('popupSave').addEventListener('click', () => {\r\n            const category = document.getElementById('categoryInput').value.trim();\r\n            if (!category) {\r\n                alert(t('category_required'));\r\n                return;\r\n            }\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo) return;\r\n            if (!window.pendingCoverPolygon) return;\r\n            const color = '#' + Math.floor(Math.random()*16777215).toString(16);\r\n            photo.coverPolygons.push({\r\n                points: window.pendingCoverPolygon,\r\n                category: category,\r\n                color: color\r\n            });\r\n            window.pendingCoverPolygon = null;\r\n            document.getElementById('categoryPopup').style.display = 'none';\r\n            drawPhotoOnCanvas();\r\n            renderCoverPolygonListForActivePhoto();\r\n        });\r\n\r\n        document.getElementById('popupCancel').addEventListener('click', () => {\r\n            window.pendingCoverPolygon = null;\r\n            document.getElementById('categoryPopup').style.display = 'none';\r\n            drawPhotoOnCanvas();\r\n        });\r\n\r\n        function editCoverPolygon(index) {\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo || !photo.coverPolygons[index]) return;\r\n            const poly = photo.coverPolygons[index];\r\n            window.editingCoverPolygonIndex = index;\r\n            window.pendingCoverPolygon = poly.points;\r\n            document.getElementById('popupTitle').innerText = t('polygon_category');\r\n            document.getElementById('categoryInput').value = poly.category;\r\n            document.getElementById('categoryPopup').style.display = 'flex';\r\n            const saveBtn = document.getElementById('popupSave');\r\n            const originalClick = saveBtn.onclick;\r\n            saveBtn.onclick = () => {\r\n                const newCategory = document.getElementById('categoryInput').value.trim();\r\n                if (!newCategory) return;\r\n                photo.coverPolygons[index].category = newCategory;\r\n                document.getElementById('categoryPopup').style.display = 'none';\r\n                drawPhotoOnCanvas();\r\n                renderCoverPolygonListForActivePhoto();\r\n                saveBtn.onclick = originalClick;\r\n            };\r\n            const cancelBtn = document.getElementById('popupCancel');\r\n            const originalCancel = cancelBtn.onclick;\r\n            cancelBtn.onclick = () => {\r\n                document.getElementById('categoryPopup').style.display = 'none';\r\n                cancelBtn.onclick = originalCancel;\r\n                saveBtn.onclick = originalClick;\r\n            };\r\n        }\r\n\r\n        function deleteCoverPolygon(index) {\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo) return;\r\n            photo.coverPolygons.splice(index, 1);\r\n            drawPhotoOnCanvas();\r\n            renderCoverPolygonListForActivePhoto();\r\n        }\r\n\r\n        \/\/ Generate titik\r\n        document.getElementById('generatePointsBtn').addEventListener('click', () => {\r\n            if (analysisMode !== 'point') return;\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo) return;\r\n            if (!photo.isPolygonClosed || photo.polygonPoints.length < 3) {\r\n                alert(t('min_3_points'));\r\n                return;\r\n            }\r\n            const xs = photo.polygonPoints.map(p => p.x);\r\n            const ys = photo.polygonPoints.map(p => p.y);\r\n            const minX = Math.min(...xs);\r\n            const maxX = Math.max(...xs);\r\n            const minY = Math.min(...ys);\r\n            const maxY = Math.max(...ys);\r\n\r\n            const points = [];\r\n            const maxAttempts = 5000;\r\n            let attempts = 0;\r\n            while (points.length < 30 && attempts < maxAttempts) {\r\n                const x = minX + Math.random() * (maxX - minX);\r\n                const y = minY + Math.random() * (maxY - minY);\r\n                if (pointInPolygon(x, y, photo.polygonPoints)) {\r\n                    points.push({ x, y, number: points.length+1 });\r\n                }\r\n                attempts++;\r\n            }\r\n            if (points.length < 30) {\r\n                alert('Hanya dapat menghasilkan ' + points.length + ' titik. Coba poligon yang lebih besar.');\r\n            }\r\n            photo.randomPoints = points;\r\n            photo.selectedCategories = new Array(points.length).fill('');\r\n            drawPhotoOnCanvas();\r\n            renderPointListForActivePhoto();\r\n            resetZoom();\r\n        });\r\n\r\n        function pointInPolygon(x, y, poly) {\r\n            let inside = false;\r\n            for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {\r\n                const xi = poly[i].x, yi = poly[i].y;\r\n                const xj = poly[j].x, yj = poly[j].y;\r\n                const intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) \/ (yj - yi) + xi);\r\n                if (intersect) inside = !inside;\r\n            }\r\n            return inside;\r\n        }\r\n\r\n        \/\/ Zoom untuk mode point (klik nomor)\r\n        document.getElementById('pointsList').addEventListener('click', (e) => {\r\n            const target = e.target.closest('.point-number');\r\n            if (!target) return;\r\n            e.stopPropagation();\r\n            const idx = target.dataset.index;\r\n            if (idx === undefined) return;\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo || !photo.randomPoints || !photo.randomPoints[idx]) return;\r\n            const point = photo.randomPoints[idx];\r\n            if (zoomState.active && zoomState.zoomedPointIndex === parseInt(idx)) {\r\n                resetZoom();\r\n            } else {\r\n                setZoom(parseInt(idx));\r\n            }\r\n        });\r\n\r\n        function setZoom(pointIndex) {\r\n            const photo = photos[activePhotoIndex];\r\n            if (!photo || !photo.randomPoints || !photo.randomPoints[pointIndex]) return;\r\n            const point = photo.randomPoints[pointIndex];\r\n            const s = zoomState.factor;\r\n            const cw = canvas.width;\r\n            const ch = canvas.height;\r\n            const px = (cw\/2 - s * point.x) \/ (1 - s);\r\n            const py = (ch\/2 - s * point.y) \/ (1 - s);\r\n            zoomState.active = true;\r\n            zoomState.x = px;\r\n            zoomState.y = py;\r\n            zoomState.zoomedPointIndex = pointIndex;\r\n            drawPhotoOnCanvas();\r\n        }\r\n\r\n        function resetZoom() {\r\n            zoomState.active = false;\r\n            zoomState.zoomedPointIndex = null;\r\n            drawPhotoOnCanvas();\r\n        }\r\n\r\n        document.getElementById('resetZoomBtn').addEventListener('click', resetZoom);\r\n        document.getElementById('resetZoomCoverBtn').addEventListener('click', resetZoom);\r\n\r\n        \/\/ Pan untuk mode luasan\r\n        canvas.addEventListener('mousedown', (e) => {\r\n            if (analysisMode === 'polygon' && zoomState.active) {\r\n                e.preventDefault();\r\n                isPanning = true;\r\n                lastPanX = e.clientX;\r\n                lastPanY = e.clientY;\r\n                canvas.style.cursor = 'grabbing';\r\n            }\r\n        });\r\n\r\n        canvas.addEventListener('mousemove', (e) => {\r\n            if (isPanning) {\r\n                e.preventDefault();\r\n                const dx = e.clientX - lastPanX;\r\n                const dy = e.clientY - lastPanY;\r\n                const rect = canvas.getBoundingClientRect();\r\n                const factorX = canvas.width \/ rect.width;\r\n                const factorY = canvas.height \/ rect.height;\r\n                zoomState.x -= dx * factorX \/ zoomState.factor;\r\n                zoomState.y -= dy * factorY \/ zoomState.factor;\r\n                lastPanX = e.clientX;\r\n                lastPanY = e.clientY;\r\n                drawPhotoOnCanvas();\r\n            }\r\n        });\r\n\r\n        canvas.addEventListener('mouseup', () => {\r\n            if (isPanning) {\r\n                isPanning = false;\r\n                canvas.style.cursor = 'crosshair';\r\n            }\r\n        });\r\n\r\n        canvas.addEventListener('mouseleave', () => {\r\n            if (isPanning) {\r\n                isPanning = false;\r\n                canvas.style.cursor = 'crosshair';\r\n            }\r\n        });\r\n\r\n        \/\/ Zoom untuk mode luasan\r\n        document.getElementById('zoomInCoverBtn').addEventListener('click', () => {\r\n            if (analysisMode !== 'polygon') return;\r\n            zoomState.factor *= 1.2;\r\n            zoomState.x = canvas.width \/ 2;\r\n            zoomState.y = canvas.height \/ 2;\r\n            zoomState.active = true;\r\n            zoomState.zoomedPointIndex = null;\r\n            drawPhotoOnCanvas();\r\n        });\r\n\r\n        document.getElementById('zoomOutCoverBtn').addEventListener('click', () => {\r\n            if (analysisMode !== 'polygon') return;\r\n            zoomState.factor \/= 1.2;\r\n            if (zoomState.factor < 1) zoomState.factor = 1;\r\n            zoomState.x = canvas.width \/ 2;\r\n            zoomState.y = canvas.height \/ 2;\r\n            zoomState.active = zoomState.factor > 1;\r\n            zoomState.zoomedPointIndex = null;\r\n            drawPhotoOnCanvas();\r\n        });\r\n\r\n        \/\/ Kalibrasi\r\n        document.getElementById('startCalibrationBtn').addEventListener('click', () => {\r\n            if (!photos[activePhotoIndex]) return;\r\n            calibrationMode = true;\r\n            calibrationPoints = [];\r\n            lineLengthPx = 0;\r\n            document.getElementById('lineLengthPx').innerText = '0';\r\n            document.getElementById('applyScaleBtn').disabled = true;\r\n            document.getElementById('calibStatus').innerText = t('draw_ref_line_instruction');\r\n            drawPhotoOnCanvas();\r\n        });\r\n\r\n        document.getElementById('applyScaleBtn').addEventListener('click', () => {\r\n            const realLength = parseFloat(document.getElementById('realLengthCm').value);\r\n            if (isNaN(realLength) || realLength <= 0) {\r\n                alert('Masukkan panjang sebenarnya dalam cm.');\r\n                return;\r\n            }\r\n            if (lineLengthPx === 0) {\r\n                alert('Gambar garis referensi terlebih dahulu.');\r\n                return;\r\n            }\r\n            const scale = realLength \/ lineLengthPx;\r\n            const photo = photos[activePhotoIndex];\r\n            photo.scale = scale;\r\n            document.getElementById('scaleValue').innerText = scale.toFixed(4);\r\n            document.getElementById('calibStatus').innerText = t('scale_applied') + scale.toFixed(4) + ' cm\/px';\r\n            calibrationMode = false;\r\n            calibrationPoints = [];\r\n            drawPhotoOnCanvas();\r\n        });\r\n\r\n        function resetCalibrationUI() {\r\n            calibrationMode = false;\r\n            calibrationPoints = [];\r\n            lineLengthPx = 0;\r\n            document.getElementById('lineLengthPx').innerText = '0';\r\n            document.getElementById('applyScaleBtn').disabled = true;\r\n            document.getElementById('realLengthCm').value = '';\r\n            const photo = photos[activePhotoIndex];\r\n            if (photo && photo.scale) {\r\n                document.getElementById('scaleValue').innerText = photo.scale.toFixed(4);\r\n                document.getElementById('calibStatus').innerText = t('scale_applied') + photo.scale.toFixed(4) + ' cm\/px';\r\n            } else {\r\n                document.getElementById('scaleValue').innerText = '-';\r\n                document.getElementById('calibStatus').innerText = t('not_calibrated');\r\n            }\r\n        }\r\n\r\n        \/\/ Pilihan mode\r\n        document.querySelectorAll('input[name=\"analysisMode\"]').forEach(radio => {\r\n            radio.addEventListener('change', (e) => {\r\n                analysisMode = e.target.value;\r\n                if (analysisMode === 'point') {\r\n                    document.getElementById('pointPanel').style.display = 'block';\r\n                    document.getElementById('coverPolygonPanel').style.display = 'none';\r\n                    document.getElementById('generatePointsBtn').style.display = 'inline-block';\r\n                    document.getElementById('addCoverPolygonBtn').style.display = 'none';\r\n                } else {\r\n                    document.getElementById('pointPanel').style.display = 'none';\r\n                    document.getElementById('coverPolygonPanel').style.display = 'block';\r\n                    document.getElementById('generatePointsBtn').style.display = 'none';\r\n                    document.getElementById('addCoverPolygonBtn').style.display = 'inline-block';\r\n                }\r\n                drawPhotoOnCanvas();\r\n            });\r\n        });\r\n\r\n        \/\/ Hitung luas poligon\r\n        function polygonArea(points) {\r\n            if (points.length < 3) return 0;\r\n            let area = 0;\r\n            for (let i = 0, j = points.length - 1; i < points.length; j = i++) {\r\n                area += (points[j].x + points[i].x) * (points[j].y - points[i].y);\r\n            }\r\n            return Math.abs(area \/ 2);\r\n        }\r\n\r\n        \/\/ Export Excel\r\n        document.getElementById('exportExcelBtn').addEventListener('click', () => {\r\n            const wb = XLSX.utils.book_new();\r\n\r\n            const infoData = [\r\n                ['Parameter', 'Nilai'],\r\n                ['Nama Project', projectData.nama_project],\r\n                ['Surveyor', projectData.surveyor],\r\n                ['Waktu', projectData.waktu],\r\n                ['Lokasi (Lat,Lng)', `${projectData.lokasi.lat}, ${projectData.lokasi.lng}`],\r\n                ['Nama Lokasi', projectData.lokasi.nama],\r\n                ['Site', projectData.site],\r\n                ['Pengulangan', projectData.pengulangan],\r\n                ['Kedalaman (m)', projectData.kedalaman],\r\n                ['Visibility (m)', projectData.visibility],\r\n                ['Tipe Struktur', projectData.tipe_struktur],\r\n                ['Posisi Karang', projectData.posisi_karang],\r\n                ['Jumlah Foto', photos.length]\r\n            ];\r\n            const wsInfo = XLSX.utils.aoa_to_sheet(infoData);\r\n            XLSX.utils.book_append_sheet(wb, wsInfo, 'Info Proyek');\r\n\r\n            const mainCategories = [\"HC\",\"DC\",\"DCA\",\"SC\",\"SP\",\"FS\",\"OT\",\"R\",\"S\",\"SI\",\"RK\",\"TWS\"];\r\n            function getParent(code) {\r\n                const found = subCategoryList.find(c => c.code === code);\r\n                return found ? found.parent : code;\r\n            }\r\n\r\n            let allSelectedCategories = [];\r\n            photos.forEach(photo => {\r\n                if (photo.selectedCategories) allSelectedCategories.push(...photo.selectedCategories);\r\n            });\r\n            const totalTitik = allSelectedCategories.length;\r\n\r\n            const mainCount = {};\r\n            mainCategories.forEach(c => mainCount[c] = 0);\r\n            allSelectedCategories.forEach(code => {\r\n                if (code) {\r\n                    if (code.startsWith('HCG')) {\r\n                        mainCount['HC'] = (mainCount['HC'] || 0) + 1;\r\n                    } else if (code.startsWith('SCG')) {\r\n                        mainCount['SC'] = (mainCount['SC'] || 0) + 1;\r\n                    } else {\r\n                        const parent = getParent(code);\r\n                        if (mainCount.hasOwnProperty(parent)) mainCount[parent]++;\r\n                        else mainCount[parent] = 1;\r\n                    }\r\n                }\r\n            });\r\n            const mainRows = [['Kategori Utama', 'Jumlah', 'Persentase']];\r\n            for (let cat of mainCategories) {\r\n                const count = mainCount[cat] || 0;\r\n                const pct = totalTitik ? (count \/ totalTitik * 100).toFixed(2) : 0;\r\n                mainRows.push([cat, count, pct + '%']);\r\n            }\r\n            const wsMain = XLSX.utils.aoa_to_sheet(mainRows);\r\n            XLSX.utils.book_append_sheet(wb, wsMain, 'Persentase Utama');\r\n\r\n            const subCount = {};\r\n            subCategoryList.forEach(c => subCount[c.code] = 0);\r\n            allSelectedCategories.forEach(code => { \r\n                if (code) {\r\n                    if (code.startsWith('HCG')) {\r\n                        \/\/ tidak masuk subCount\r\n                    } else if (code.startsWith('SCG')) {\r\n                        \/\/ tidak masuk\r\n                    } else {\r\n                        subCount[code]++; \r\n                    }\r\n                }\r\n            });\r\n            const subRows = [['Subkategori', 'Jumlah', 'Persentase']];\r\n            for (let cat of subCategoryList) {\r\n                const count = subCount[cat.code] || 0;\r\n                const pct = totalTitik ? (count \/ totalTitik * 100).toFixed(2) : 0;\r\n                subRows.push([cat.code + ' - ' + cat.name, count, pct + '%']);\r\n            }\r\n            \/\/ Tambahkan genus\r\n            hardCoralGenus.forEach(g => {\r\n                const count = allSelectedCategories.filter(c => c === 'HCG - ' + g).length;\r\n                if (count > 0) {\r\n                    const pct = totalTitik ? (count \/ totalTitik * 100).toFixed(2) : 0;\r\n                    subRows.push(['HCG - ' + g, count, pct + '%']);\r\n                }\r\n            });\r\n            softCoralGenus.forEach(g => {\r\n                const count = allSelectedCategories.filter(c => c === 'SCG - ' + g).length;\r\n                if (count > 0) {\r\n                    const pct = totalTitik ? (count \/ totalTitik * 100).toFixed(2) : 0;\r\n                    subRows.push(['SCG - ' + g, count, pct + '%']);\r\n                }\r\n            });\r\n            const wsSub = XLSX.utils.aoa_to_sheet(subRows);\r\n            XLSX.utils.book_append_sheet(wb, wsSub, 'Persentase Subkategori');\r\n\r\n            \/\/ Ringkasan Luasan per kategori detail\r\n            const coverSummary = {};\r\n            let totalLuasCm2 = 0;\r\n            photos.forEach((photo, idx) => {\r\n                if (photo.coverPolygons && photo.coverPolygons.length > 0) {\r\n                    photo.coverPolygons.forEach(poly => {\r\n                        const areaPx = polygonArea(poly.points);\r\n                        const areaCm2 = photo.scale ? areaPx * photo.scale * photo.scale : 0;\r\n                        const cat = poly.category;\r\n                        if (!coverSummary[cat]) coverSummary[cat] = 0;\r\n                        coverSummary[cat] += areaCm2;\r\n                        totalLuasCm2 += areaCm2;\r\n                    });\r\n                }\r\n            });\r\n            const coverRows = [['Kategori', 'Luas (cm\u00b2)', 'Persentase']];\r\n            for (let cat in coverSummary) {\r\n                const luas = coverSummary[cat];\r\n                const pct = totalLuasCm2 ? (luas \/ totalLuasCm2 * 100).toFixed(2) : 0;\r\n                coverRows.push([cat, luas.toFixed(2), pct + '%']);\r\n            }\r\n            if (coverRows.length === 1) coverRows.push(['Tidak ada poligon', '0', '0%']);\r\n            const wsCover = XLSX.utils.aoa_to_sheet(coverRows);\r\n            XLSX.utils.book_append_sheet(wb, wsCover, 'Ringkasan Luasan');\r\n\r\n            \/\/ Tambahan: Persentase Utama Luasan (mengelompokkan ke kategori utama)\r\n            const mainCoverCount = {};\r\n            let totalMainLuas = 0;\r\n            photos.forEach(photo => {\r\n                if (photo.coverPolygons && photo.coverPolygons.length > 0 && photo.scale) {\r\n                    photo.coverPolygons.forEach(poly => {\r\n                        const cat = poly.category;\r\n                        let mainCat = 'Lainnya';\r\n                        if (cat.startsWith('HCG - ')) {\r\n                            mainCat = 'HC';\r\n                        } else if (cat.startsWith('SCG - ')) {\r\n                            mainCat = 'SC';\r\n                        } else {\r\n                            const parts = cat.split(' - ');\r\n                            if (parts.length >= 2) {\r\n                                const code = parts[0].trim();\r\n                                const found = subCategoryList.find(c => c.code === code);\r\n                                if (found) {\r\n                                    mainCat = found.parent;\r\n                                }\r\n                            }\r\n                        }\r\n                        const areaPx = polygonArea(poly.points);\r\n                        const areaCm2 = areaPx * photo.scale * photo.scale;\r\n                        if (!mainCoverCount[mainCat]) mainCoverCount[mainCat] = 0;\r\n                        mainCoverCount[mainCat] += areaCm2;\r\n                        totalMainLuas += areaCm2;\r\n                    });\r\n                }\r\n            });\r\n            const mainCoverRows = [['Kategori Utama', 'Luas (cm\u00b2)', 'Persentase']];\r\n            for (let cat of mainCategories) {\r\n                const luas = mainCoverCount[cat] || 0;\r\n                const pct = totalMainLuas ? (luas \/ totalMainLuas * 100).toFixed(2) : 0;\r\n                mainCoverRows.push([cat, luas.toFixed(2), pct + '%']);\r\n            }\r\n            \/\/ Tambahkan kategori lain yang mungkin muncul (misal 'Lainnya')\r\n            for (let cat in mainCoverCount) {\r\n                if (!mainCategories.includes(cat)) {\r\n                    const luas = mainCoverCount[cat];\r\n                    const pct = totalMainLuas ? (luas \/ totalMainLuas * 100).toFixed(2) : 0;\r\n                    mainCoverRows.push([cat, luas.toFixed(2), pct + '%']);\r\n                }\r\n            }\r\n            if (mainCoverRows.length === 1) mainCoverRows.push(['Tidak ada poligon', '0', '0%']);\r\n            const wsMainCover = XLSX.utils.aoa_to_sheet(mainCoverRows);\r\n            XLSX.utils.book_append_sheet(wb, wsMainCover, 'Persentase Utama (Luasan)');\r\n\r\n            const detailRows = [['Foto', 'Nomor Titik', 'Kategori']];\r\n            photos.forEach((photo, idx) => {\r\n                if (photo.randomPoints && photo.selectedCategories) {\r\n                    photo.selectedCategories.forEach((cat, i) => {\r\n                        detailRows.push([`Foto ${idx+1}`, i+1, cat || '(belum diisi)']);\r\n                    });\r\n                } else {\r\n                    detailRows.push([`Foto ${idx+1}`, '-', 'Belum generate titik']);\r\n                }\r\n            });\r\n            const wsDetail = XLSX.utils.aoa_to_sheet(detailRows);\r\n            XLSX.utils.book_append_sheet(wb, wsDetail, 'Detail per Titik');\r\n\r\n            \/\/ === TAMBAHAN: Luas Area Analisis (Poligon Area) ===\r\n            const areaRows = [['Foto', 'Luas Area (px\u00b2)', 'Luas Area (cm\u00b2)', 'Keterangan Kalibrasi']];\r\n            photos.forEach((photo, idx) => {\r\n                let areaPx = 0;\r\n                let areaCm2 = 0;\r\n                let calibNote = '';\r\n                if (photo.polygonPoints && photo.polygonPoints.length >= 3 && photo.isPolygonClosed) {\r\n                    areaPx = polygonArea(photo.polygonPoints);\r\n                    if (photo.scale) {\r\n                        areaCm2 = areaPx * photo.scale * photo.scale;\r\n                        calibNote = 'Terkalibrasi';\r\n                    } else {\r\n                        calibNote = 'Belum kalibrasi';\r\n                    }\r\n                } else {\r\n                    calibNote = 'Poligon area belum ditutup atau tidak ada';\r\n                }\r\n                areaRows.push([`Foto ${idx+1}`, areaPx.toFixed(2), areaCm2.toFixed(2), calibNote]);\r\n            });\r\n            const wsArea = XLSX.utils.aoa_to_sheet(areaRows);\r\n            XLSX.utils.book_append_sheet(wb, wsArea, 'Luas Area Analisis');\r\n            \/\/ === END TAMBAHAN ===\r\n\r\n            \/\/ Tentukan nama file berdasarkan tanggal\r\n            let fileName = 'analisis_bentik.xlsx'; \/\/ default\r\n            const tglInput = document.getElementById('tanggal').value;\r\n            if (tglInput) {\r\n                fileName = tglInput + '_Analisis_Bentik.xlsx';\r\n            }\r\n            XLSX.writeFile(wb, fileName);\r\n        });\r\n\r\n        \/\/ ========== FUNGSI SIMPAN & MUAT PROYEK ==========\r\n        document.getElementById('saveProjectBtnHeader').addEventListener('click', () => {\r\n            saveProject();\r\n        });\r\n\r\n        document.getElementById('loadProjectBtnHeader').addEventListener('click', () => {\r\n            document.getElementById('loadProjectFile').click();\r\n        });\r\n\r\n        document.getElementById('loadProjectFile').addEventListener('change', (e) => {\r\n            const file = e.target.files[0];\r\n            if (!file) return;\r\n            const reader = new FileReader();\r\n            reader.onload = (event) => {\r\n                try {\r\n                    const data = JSON.parse(event.target.result);\r\n                    loadProject(data);\r\n                } catch (err) {\r\n                    alert('Gagal memuat file: ' + err);\r\n                }\r\n            };\r\n            reader.readAsText(file);\r\n        });\r\n\r\n        function saveProject() {\r\n            \/\/ Kumpulkan data yang perlu disimpan\r\n            const saveData = {\r\n                projectData: projectData,\r\n                photos: photos.map(p => {\r\n                    \/\/ Salin properti yang diperlukan, jangan simpan objek Image\r\n                    const { image, ...rest } = p;\r\n                    return rest;\r\n                }),\r\n                analysisMode: analysisMode,\r\n                identificationLevel: identificationLevel,\r\n                activePhotoIndex: activePhotoIndex\r\n            };\r\n            const jsonStr = JSON.stringify(saveData, null, 2);\r\n            const blob = new Blob([jsonStr], { type: 'application\/json' });\r\n            const url = URL.createObjectURL(blob);\r\n            const a = document.createElement('a');\r\n            a.href = url;\r\n            a.download = 'proyek_bentik.json';\r\n            a.click();\r\n            URL.revokeObjectURL(url);\r\n        }\r\n\r\n        async function loadProject(data) {\r\n            \/\/ Reset current state\r\n            photos = [];\r\n            \/\/ Rekonstruksi photos dari data\r\n            const photoPromises = data.photos.map(async (pData) => {\r\n                return new Promise((resolve, reject) => {\r\n                    const img = new Image();\r\n                    img.onload = () => {\r\n                        resolve({\r\n                            ...pData,\r\n                            image: img\r\n                        });\r\n                    };\r\n                    img.onerror = reject;\r\n                    img.src = pData.dataURL;\r\n                });\r\n            });\r\n            try {\r\n                const loadedPhotos = await Promise.all(photoPromises);\r\n                photos = loadedPhotos;\r\n                projectData = data.projectData;\r\n                analysisMode = data.analysisMode || 'point';\r\n                identificationLevel = data.identificationLevel || 'basic';\r\n                activePhotoIndex = data.activePhotoIndex || 0;\r\n\r\n                \/\/ Perbarui UI\r\n                \/\/ Isi form proyek\r\n                document.getElementById('nama_project').value = projectData.nama_project || '';\r\n                document.getElementById('surveyor').value = projectData.surveyor || '';\r\n                if (projectData.waktu) {\r\n                    const parts = projectData.waktu.split(' ');\r\n                    document.getElementById('tanggal').value = parts[0] || '';\r\n                    document.getElementById('jam').value = parts[1] || '';\r\n                }\r\n                document.getElementById('site').value = projectData.site || '';\r\n                document.getElementById('pengulangan').value = projectData.pengulangan || '';\r\n                document.getElementById('kedalaman').value = projectData.kedalaman || '';\r\n                document.getElementById('visibility').value = projectData.visibility || '';\r\n                document.getElementById('tipe_struktur').value = projectData.tipe_struktur || '';\r\n                document.getElementById('posisi_karang').value = projectData.posisi_karang || '';\r\n                document.getElementById('lokasi_nama').value = projectData.lokasi?.nama || '';\r\n                document.getElementById('lat').value = projectData.lokasi?.lat || '';\r\n                document.getElementById('lng').value = projectData.lokasi?.lng || '';\r\n\r\n                \/\/ Perbarui peta\r\n                if (projectData.lokasi?.lat && projectData.lokasi?.lng) {\r\n                    marker.setLatLng([projectData.lokasi.lat, projectData.lokasi.lng]);\r\n                    map.panTo([projectData.lokasi.lat, projectData.lokasi.lng]);\r\n                }\r\n\r\n                \/\/ Perbarui gallery foto\r\n                updatePhotoGallery();\r\n                updateActivePhotoGallery();\r\n\r\n                \/\/ Muat foto aktif ke canvas\r\n                if (photos.length > 0) {\r\n                    loadActivePhotoToCanvas();\r\n                    \/\/ Perbarui panel titik dan poligon\r\n                    renderPointListForActivePhoto();\r\n                    renderCoverPolygonListForActivePhoto();\r\n                }\r\n\r\n                \/\/ Atur mode selector sesuai state\r\n                document.querySelector(`input[name=\"analysisMode\"][value=\"${analysisMode}\"]`).checked = true;\r\n                document.querySelector(`input[name=\"identificationLevel\"][value=\"${identificationLevel}\"]`).checked = true;\r\n\r\n                if (analysisMode === 'point') {\r\n                    document.getElementById('pointPanel').style.display = 'block';\r\n                    document.getElementById('coverPolygonPanel').style.display = 'none';\r\n                    document.getElementById('generatePointsBtn').style.display = 'inline-block';\r\n                    document.getElementById('addCoverPolygonBtn').style.display = 'none';\r\n                } else {\r\n                    document.getElementById('pointPanel').style.display = 'none';\r\n                    document.getElementById('coverPolygonPanel').style.display = 'block';\r\n                    document.getElementById('generatePointsBtn').style.display = 'none';\r\n                    document.getElementById('addCoverPolygonBtn').style.display = 'inline-block';\r\n                }\r\n\r\n                resetCalibrationUI();\r\n\r\n                if (photos.length > 0) {\r\n                    showStep(3);\r\n                } else {\r\n                    showStep(1);\r\n                }\r\n\r\n                updateDynamicTexts();\r\n            } catch (err) {\r\n                alert('Gagal memuat gambar: ' + err);\r\n            }\r\n        }\r\n\r\n        \/\/ Set tanggal hari ini\r\n        document.getElementById('tanggal').valueAsDate = new Date();\r\n\r\n        \/\/ Inisialisasi: nonaktifkan level selector dan mode selector\r\n        disableLevelSelector();\r\n        disableModeSelector();\r\n        setLanguage('id');\r\n    })();\r\n<\/script>\r\n<\/body>\r\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-c68406d e-flex e-con-boxed e-con e-parent\" data-id=\"c68406d\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-b3fd8c1 elementor-widget elementor-widget-spacer\" data-id=\"b3fd8c1\" data-element_type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-a820a27 e-flex e-con-boxed e-con e-parent\" data-id=\"a820a27\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-48c99c4 e-con-full e-flex e-con e-child\" data-id=\"48c99c4\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-7f13728 e-con-full e-flex e-con e-child\" data-id=\"7f13728\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-24c40f3 e-con-full e-flex e-con e-child\" data-id=\"24c40f3\" data-element_type=\"container\">\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-cf51626 e-con-full e-flex e-con e-child\" data-id=\"cf51626\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-ac3c328 elementor-widget elementor-widget-heading\" data-id=\"ac3c328\" data-element_type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Tutorial<\/h2>\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\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-e3e6322 e-flex e-con-boxed e-con e-parent\" data-id=\"e3e6322\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-213eef6 elementor-widget elementor-widget-video\" data-id=\"213eef6\" data-element_type=\"widget\" data-settings=\"{&quot;youtube_url&quot;:&quot;https:\\\/\\\/www.youtube.com\\\/watch?v=vp8aJtBw9ww&quot;,&quot;cc_load_policy&quot;:&quot;yes&quot;,&quot;video_type&quot;:&quot;youtube&quot;,&quot;controls&quot;:&quot;yes&quot;}\" data-widget_type=\"video.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-wrapper elementor-open-inline\">\n\t\t\t<div class=\"elementor-video\"><\/div>\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-159f926 e-flex e-con-boxed e-con e-parent\" data-id=\"159f926\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-46e9cc7 elementor-widget elementor-widget-spacer\" data-id=\"46e9cc7\" data-element_type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-2ef186f e-flex e-con-boxed e-con e-parent\" data-id=\"2ef186f\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-073cc7a elementor-widget elementor-widget-heading\" data-id=\"073cc7a\" data-element_type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Menyimpan dan Melanjutkan Proyekmu<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-fbe1928 e-con-full e-flex e-con e-child\" data-id=\"fbe1928\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7fe9992 elementor-widget elementor-widget-video\" data-id=\"7fe9992\" data-element_type=\"widget\" data-settings=\"{&quot;youtube_url&quot;:&quot;https:\\\/\\\/www.youtube.com\\\/watch?v=3ARmXCFe0cs&quot;,&quot;video_type&quot;:&quot;youtube&quot;,&quot;controls&quot;:&quot;yes&quot;}\" data-widget_type=\"video.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-wrapper elementor-open-inline\">\n\t\t\t<div class=\"elementor-video\"><\/div>\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>","protected":false},"excerpt":{"rendered":"<p>Aplikasi Analisis Bentik &#8211; Titik Acak &#038; Luasan Poligon Analisis Bentik &#8211; Titik Acak &#038; Luasan Poligon Metode standar LIPI [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"site-sidebar-layout":"no-sidebar","site-content-layout":"","ast-site-content-layout":"full-width-container","site-content-style":"default","site-sidebar-style":"default","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":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-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":"","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-4)","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-4)","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-4)","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":""},"class_list":["post-952","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v22.3 (Yoast SEO v24.7) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Analisis Bentik - marineconservation.id<\/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:\/\/marineconservation.id\/id\/analisis-bentik\/\" \/>\n<meta property=\"og:locale\" content=\"id_ID\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Analisis Bentik\" \/>\n<meta property=\"og:description\" content=\"Aplikasi Analisis Bentik &#8211; Titik Acak &#038; Luasan Poligon Analisis Bentik &#8211; Titik Acak &#038; Luasan Poligon Metode standar LIPI [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/marineconservation.id\/id\/analisis-bentik\/\" \/>\n<meta property=\"og:site_name\" content=\"marineconservation.id\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/profile.php?id=61572311944591\" \/>\n<meta property=\"article:modified_time\" content=\"2026-03-07T09:24:22+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Estimasi waktu membaca\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 menit\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/marineconservation.id\/analisis-bentik\/\",\"url\":\"https:\/\/marineconservation.id\/analisis-bentik\/\",\"name\":\"Analisis Bentik - marineconservation.id\",\"isPartOf\":{\"@id\":\"https:\/\/marineconservation.id\/#website\"},\"datePublished\":\"2026-02-25T12:30:00+00:00\",\"dateModified\":\"2026-03-07T09:24:22+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/marineconservation.id\/analisis-bentik\/#breadcrumb\"},\"inLanguage\":\"id\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/marineconservation.id\/analisis-bentik\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/marineconservation.id\/analisis-bentik\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/marineconservation.id\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Analisis Bentik\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/marineconservation.id\/#website\",\"url\":\"https:\/\/marineconservation.id\/\",\"name\":\"marineconservation.id\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/marineconservation.id\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/marineconservation.id\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"id\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/marineconservation.id\/#organization\",\"name\":\"marineconservation.id\",\"url\":\"https:\/\/marineconservation.id\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"id\",\"@id\":\"https:\/\/marineconservation.id\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/marineconservation.id\/wp-content\/uploads\/2025\/02\/Marine-Conservation-Indonesia.png\",\"contentUrl\":\"https:\/\/marineconservation.id\/wp-content\/uploads\/2025\/02\/Marine-Conservation-Indonesia.png\",\"width\":500,\"height\":500,\"caption\":\"marineconservation.id\"},\"image\":{\"@id\":\"https:\/\/marineconservation.id\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/profile.php?id=61572311944591\",\"https:\/\/www.instagram.com\/marineconservation.id\/\",\"https:\/\/www.youtube.com\/watch?v=nOmjtk-dTg8&list=LL\"]}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Analisis Bentik - marineconservation.id","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:\/\/marineconservation.id\/id\/analisis-bentik\/","og_locale":"id_ID","og_type":"article","og_title":"Analisis Bentik","og_description":"Aplikasi Analisis Bentik &#8211; Titik Acak &#038; Luasan Poligon Analisis Bentik &#8211; Titik Acak &#038; Luasan Poligon Metode standar LIPI [&hellip;]","og_url":"https:\/\/marineconservation.id\/id\/analisis-bentik\/","og_site_name":"marineconservation.id","article_publisher":"https:\/\/www.facebook.com\/profile.php?id=61572311944591","article_modified_time":"2026-03-07T09:24:22+00:00","twitter_card":"summary_large_image","twitter_misc":{"Estimasi waktu membaca":"1 menit"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/marineconservation.id\/analisis-bentik\/","url":"https:\/\/marineconservation.id\/analisis-bentik\/","name":"Analisis Bentik - marineconservation.id","isPartOf":{"@id":"https:\/\/marineconservation.id\/#website"},"datePublished":"2026-02-25T12:30:00+00:00","dateModified":"2026-03-07T09:24:22+00:00","breadcrumb":{"@id":"https:\/\/marineconservation.id\/analisis-bentik\/#breadcrumb"},"inLanguage":"id","potentialAction":[{"@type":"ReadAction","target":["https:\/\/marineconservation.id\/analisis-bentik\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/marineconservation.id\/analisis-bentik\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/marineconservation.id\/"},{"@type":"ListItem","position":2,"name":"Analisis Bentik"}]},{"@type":"WebSite","@id":"https:\/\/marineconservation.id\/#website","url":"https:\/\/marineconservation.id\/","name":"marineconservation.id","description":"","publisher":{"@id":"https:\/\/marineconservation.id\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/marineconservation.id\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"id"},{"@type":"Organization","@id":"https:\/\/marineconservation.id\/#organization","name":"marineconservation.id","url":"https:\/\/marineconservation.id\/","logo":{"@type":"ImageObject","inLanguage":"id","@id":"https:\/\/marineconservation.id\/#\/schema\/logo\/image\/","url":"https:\/\/marineconservation.id\/wp-content\/uploads\/2025\/02\/Marine-Conservation-Indonesia.png","contentUrl":"https:\/\/marineconservation.id\/wp-content\/uploads\/2025\/02\/Marine-Conservation-Indonesia.png","width":500,"height":500,"caption":"marineconservation.id"},"image":{"@id":"https:\/\/marineconservation.id\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/profile.php?id=61572311944591","https:\/\/www.instagram.com\/marineconservation.id\/","https:\/\/www.youtube.com\/watch?v=nOmjtk-dTg8&list=LL"]}]}},"_links":{"self":[{"href":"https:\/\/marineconservation.id\/id\/wp-json\/wp\/v2\/pages\/952","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/marineconservation.id\/id\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/marineconservation.id\/id\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/marineconservation.id\/id\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/marineconservation.id\/id\/wp-json\/wp\/v2\/comments?post=952"}],"version-history":[{"count":86,"href":"https:\/\/marineconservation.id\/id\/wp-json\/wp\/v2\/pages\/952\/revisions"}],"predecessor-version":[{"id":1843,"href":"https:\/\/marineconservation.id\/id\/wp-json\/wp\/v2\/pages\/952\/revisions\/1843"}],"wp:attachment":[{"href":"https:\/\/marineconservation.id\/id\/wp-json\/wp\/v2\/media?parent=952"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}