{"id":2479,"date":"2026-07-09T02:38:19","date_gmt":"2026-07-09T02:38:19","guid":{"rendered":"https:\/\/marineconservation.id\/?page_id=2479"},"modified":"2026-07-09T03:06:41","modified_gmt":"2026-07-09T03:06:41","slug":"identifikasi-ikan","status":"publish","type":"page","link":"https:\/\/marineconservation.id\/id\/identifikasi-ikan\/","title":{"rendered":"Identifikasi Ikan"},"content":{"rendered":"<div data-elementor-type=\"wp-page\" data-elementor-id=\"2479\" class=\"elementor elementor-2479\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-af80498 e-flex e-con-boxed e-con e-parent\" data-id=\"af80498\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-47e7efd elementor-widget elementor-widget-html\" data-id=\"47e7efd\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\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>Marine Fish Identifier v2.0<\/title>\r\n    \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    \r\n    <!-- Leaflet CSS -->\r\n    <link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.css\" \/>\r\n    \r\n    <!-- Cropper.js untuk crop image -->\r\n    <link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/cropperjs\/1.6.1\/cropper.min.css\" \/>\r\n    \r\n    <style>\r\n        * {\r\n            box-sizing: border-box;\r\n            margin: 0;\r\n            padding: 0;\r\n        }\r\n        \r\n        body {\r\n            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\r\n            background: #e8f4f8;\r\n            padding: 20px;\r\n            color: #2c3e50;\r\n        }\r\n        \r\n        .app-container {\r\n            max-width: 1400px;\r\n            margin: 0 auto;\r\n            background: #ffffff;\r\n            border-radius: 20px;\r\n            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);\r\n            overflow: hidden;\r\n            padding: 25px 30px;\r\n        }\r\n        \r\n        \/* Header *\/\r\n        .app-header {\r\n            display: flex;\r\n            justify-content: space-between;\r\n            align-items: center;\r\n            flex-wrap: wrap;\r\n            padding-bottom: 20px;\r\n            border-bottom: 3px solid #1a5276;\r\n            margin-bottom: 25px;\r\n        }\r\n        \r\n        .app-header h1 {\r\n            color: #1a5276;\r\n            font-size: 28px;\r\n            font-weight: 700;\r\n            display: flex;\r\n            align-items: center;\r\n            gap: 12px;\r\n        }\r\n        \r\n        .app-header h1 i {\r\n            color: #2e86c1;\r\n            font-size: 32px;\r\n        }\r\n        \r\n        .app-header .version {\r\n            background: #2e86c1;\r\n            color: white;\r\n            padding: 2px 12px;\r\n            border-radius: 20px;\r\n            font-size: 12px;\r\n            font-weight: 600;\r\n        }\r\n        \r\n        .header-actions {\r\n            display: flex;\r\n            gap: 10px;\r\n            flex-wrap: wrap;\r\n            align-items: center;\r\n        }\r\n        \r\n        .lang-btn, .action-btn {\r\n            padding: 8px 18px;\r\n            border: 2px solid #2e86c1;\r\n            background: white;\r\n            color: #2e86c1;\r\n            cursor: pointer;\r\n            border-radius: 25px;\r\n            font-weight: 600;\r\n            font-size: 13px;\r\n            transition: all 0.3s;\r\n        }\r\n        \r\n        .lang-btn.active {\r\n            background: #2e86c1;\r\n            color: white;\r\n        }\r\n        \r\n        .lang-btn:hover, .action-btn:hover {\r\n            background: #1a5276;\r\n            color: white;\r\n            border-color: #1a5276;\r\n            transform: translateY(-2px);\r\n            box-shadow: 0 4px 12px rgba(26, 82, 118, 0.3);\r\n        }\r\n        \r\n        .action-btn {\r\n            background: #2e86c1;\r\n            color: white;\r\n            border-color: #2e86c1;\r\n        }\r\n        \r\n        .action-btn i {\r\n            margin-right: 6px;\r\n        }\r\n        \r\n        \/* Layout columns *\/\r\n        .two-column {\r\n            display: grid;\r\n            grid-template-columns: 1fr 1fr;\r\n            gap: 25px;\r\n            margin-bottom: 25px;\r\n        }\r\n        \r\n        @media (max-width: 1024px) {\r\n            .two-column {\r\n                grid-template-columns: 1fr;\r\n            }\r\n        }\r\n        \r\n        .card {\r\n            background: #f8fafc;\r\n            border-radius: 14px;\r\n            padding: 18px 20px;\r\n            border: 1px solid #dce6ed;\r\n            transition: box-shadow 0.3s;\r\n        }\r\n        \r\n        .card:hover {\r\n            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);\r\n        }\r\n        \r\n        .card-title {\r\n            font-size: 16px;\r\n            font-weight: 700;\r\n            color: #1a5276;\r\n            margin-bottom: 14px;\r\n            display: flex;\r\n            align-items: center;\r\n            gap: 10px;\r\n        }\r\n        \r\n        .card-title i {\r\n            color: #2e86c1;\r\n            font-size: 18px;\r\n        }\r\n        \r\n        .form-group {\r\n            margin-bottom: 12px;\r\n        }\r\n        \r\n        .form-group label {\r\n            display: block;\r\n            font-weight: 600;\r\n            font-size: 13px;\r\n            color: #34495e;\r\n            margin-bottom: 4px;\r\n        }\r\n        \r\n        .form-group input, .form-group select, .form-group textarea {\r\n            width: 100%;\r\n            padding: 10px 14px;\r\n            border: 2px solid #dce6ed;\r\n            border-radius: 10px;\r\n            font-size: 14px;\r\n            transition: border-color 0.3s;\r\n            background: white;\r\n        }\r\n        \r\n        .form-group input:focus, .form-group select:focus {\r\n            outline: none;\r\n            border-color: #2e86c1;\r\n            box-shadow: 0 0 0 3px rgba(46, 134, 193, 0.15);\r\n        }\r\n        \r\n        .form-row {\r\n            display: grid;\r\n            grid-template-columns: 1fr 1fr;\r\n            gap: 12px;\r\n        }\r\n        \r\n        @media (max-width: 600px) {\r\n            .form-row {\r\n                grid-template-columns: 1fr;\r\n            }\r\n        }\r\n        \r\n        \/* Radio group *\/\r\n        .radio-group {\r\n            display: flex;\r\n            gap: 20px;\r\n            flex-wrap: wrap;\r\n            padding: 6px 0;\r\n        }\r\n        \r\n        .radio-group label {\r\n            font-weight: 500;\r\n            cursor: pointer;\r\n            display: flex;\r\n            align-items: center;\r\n            gap: 6px;\r\n            font-size: 14px;\r\n        }\r\n        \r\n        .radio-group input[type=\"radio\"] {\r\n            width: 18px;\r\n            height: 18px;\r\n            accent-color: #2e86c1;\r\n            cursor: pointer;\r\n        }\r\n        \r\n        \/* Map *\/\r\n        #map {\r\n            height: 220px;\r\n            border-radius: 10px;\r\n            border: 2px solid #dce6ed;\r\n            margin-top: 6px;\r\n        }\r\n        \r\n        \/* Upload Area *\/\r\n        .upload-area {\r\n            border: 3px dashed #2e86c1;\r\n            border-radius: 14px;\r\n            padding: 30px 20px;\r\n            text-align: center;\r\n            cursor: pointer;\r\n            transition: all 0.3s;\r\n            background: #f0f7fb;\r\n            position: relative;\r\n        }\r\n        \r\n        .upload-area:hover {\r\n            background: #e3f0f8;\r\n            border-color: #1a5276;\r\n        }\r\n        \r\n        .upload-area.dragover {\r\n            background: #d4e9f7;\r\n            border-color: #1a5276;\r\n            transform: scale(1.02);\r\n        }\r\n        \r\n        .upload-area i {\r\n            font-size: 48px;\r\n            color: #2e86c1;\r\n            margin-bottom: 10px;\r\n        }\r\n        \r\n        .upload-area p {\r\n            color: #34495e;\r\n            font-size: 15px;\r\n        }\r\n        \r\n        .upload-area .sub-text {\r\n            font-size: 13px;\r\n            color: #7f8c8d;\r\n            margin-top: 4px;\r\n        }\r\n        \r\n        #fileInput {\r\n            display: none;\r\n        }\r\n        \r\n        \/* Image Preview & Editor *\/\r\n        .image-editor-container {\r\n            display: none;\r\n            margin-top: 15px;\r\n        }\r\n        \r\n        .image-editor-container.show {\r\n            display: block;\r\n        }\r\n        \r\n        .editor-toolbar {\r\n            display: flex;\r\n            gap: 10px;\r\n            flex-wrap: wrap;\r\n            margin-bottom: 12px;\r\n            padding: 10px;\r\n            background: white;\r\n            border-radius: 10px;\r\n            border: 1px solid #dce6ed;\r\n        }\r\n        \r\n        .editor-toolbar button {\r\n            padding: 6px 16px;\r\n            border: none;\r\n            background: #e8edf2;\r\n            border-radius: 6px;\r\n            cursor: pointer;\r\n            font-weight: 600;\r\n            font-size: 13px;\r\n            transition: all 0.3s;\r\n            color: #2c3e50;\r\n        }\r\n        \r\n        .editor-toolbar button:hover {\r\n            background: #2e86c1;\r\n            color: white;\r\n        }\r\n        \r\n        .editor-toolbar button.primary {\r\n            background: #27ae60;\r\n            color: white;\r\n        }\r\n        \r\n        .editor-toolbar button.primary:hover {\r\n            background: #1e8449;\r\n        }\r\n        \r\n        .editor-toolbar button.danger {\r\n            background: #e74c3c;\r\n            color: white;\r\n        }\r\n        \r\n        .editor-toolbar button.danger:hover {\r\n            background: #c0392b;\r\n        }\r\n        \r\n        #imageEditor {\r\n            max-height: 400px;\r\n            background: #f0f4f8;\r\n            border-radius: 10px;\r\n            overflow: hidden;\r\n        }\r\n        \r\n        #imageEditor img {\r\n            max-width: 100%;\r\n        }\r\n        \r\n        \/* Wizard *\/\r\n        .wizard-container {\r\n            margin-top: 15px;\r\n            padding: 15px;\r\n            background: white;\r\n            border-radius: 12px;\r\n            border: 1px solid #dce6ed;\r\n        }\r\n        \r\n        .wizard-step {\r\n            margin-bottom: 12px;\r\n            padding: 10px;\r\n            background: #f8fafc;\r\n            border-radius: 8px;\r\n        }\r\n        \r\n        .wizard-step label {\r\n            font-weight: 600;\r\n            font-size: 13px;\r\n            display: block;\r\n            margin-bottom: 6px;\r\n            color: #1a5276;\r\n        }\r\n        \r\n        .wizard-step .options {\r\n            display: flex;\r\n            flex-wrap: wrap;\r\n            gap: 8px;\r\n        }\r\n        \r\n        .wizard-step .options button {\r\n            padding: 4px 14px;\r\n            border: 2px solid #dce6ed;\r\n            background: white;\r\n            border-radius: 20px;\r\n            cursor: pointer;\r\n            font-size: 12px;\r\n            transition: all 0.3s;\r\n            font-weight: 500;\r\n        }\r\n        \r\n        .wizard-step .options button:hover {\r\n            border-color: #2e86c1;\r\n            background: #eaf2f8;\r\n        }\r\n        \r\n        .wizard-step .options button.selected {\r\n            border-color: #2e86c1;\r\n            background: #2e86c1;\r\n            color: white;\r\n        }\r\n        \r\n        .candidate-count {\r\n            font-size: 13px;\r\n            color: #7f8c8d;\r\n            margin-top: 8px;\r\n            text-align: center;\r\n        }\r\n        \r\n        .candidate-count strong {\r\n            color: #1a5276;\r\n            font-size: 18px;\r\n        }\r\n        \r\n        \/* Candidate List *\/\r\n        .candidate-list {\r\n            margin-top: 12px;\r\n            max-height: 300px;\r\n            overflow-y: auto;\r\n        }\r\n        \r\n        .candidate-item {\r\n            display: flex;\r\n            align-items: center;\r\n            gap: 12px;\r\n            padding: 10px 14px;\r\n            border: 1px solid #dce6ed;\r\n            border-radius: 8px;\r\n            margin-bottom: 6px;\r\n            cursor: pointer;\r\n            transition: all 0.3s;\r\n        }\r\n        \r\n        .candidate-item:hover {\r\n            background: #eaf2f8;\r\n            border-color: #2e86c1;\r\n            transform: translateX(4px);\r\n        }\r\n        \r\n        .candidate-item img {\r\n            width: 50px;\r\n            height: 50px;\r\n            object-fit: cover;\r\n            border-radius: 6px;\r\n        }\r\n        \r\n        .candidate-item .info {\r\n            flex: 1;\r\n        }\r\n        \r\n        .candidate-item .info .name {\r\n            font-weight: 600;\r\n            font-size: 14px;\r\n        }\r\n        \r\n        .candidate-item .info .details {\r\n            font-size: 12px;\r\n            color: #7f8c8d;\r\n        }\r\n        \r\n        .candidate-item .match {\r\n            font-weight: 700;\r\n            color: #27ae60;\r\n            font-size: 14px;\r\n        }\r\n        \r\n        \/* Compare Mode *\/\r\n        .compare-container {\r\n            display: none;\r\n            margin-top: 15px;\r\n            gap: 15px;\r\n        }\r\n        \r\n        .compare-container.show {\r\n            display: grid;\r\n            grid-template-columns: 1fr 1fr;\r\n        }\r\n        \r\n        .compare-box {\r\n            background: #f0f4f8;\r\n            border-radius: 10px;\r\n            padding: 10px;\r\n            text-align: center;\r\n            position: relative;\r\n        }\r\n        \r\n        .compare-box .label {\r\n            position: absolute;\r\n            top: 10px;\r\n            left: 10px;\r\n            background: rgba(0,0,0,0.7);\r\n            color: white;\r\n            padding: 2px 12px;\r\n            border-radius: 20px;\r\n            font-size: 11px;\r\n            font-weight: 600;\r\n        }\r\n        \r\n        .compare-box img {\r\n            max-width: 100%;\r\n            max-height: 300px;\r\n            object-fit: contain;\r\n            cursor: zoom-in;\r\n        }\r\n        \r\n        .compare-box img.zoomed {\r\n            cursor: zoom-out;\r\n            object-fit: cover;\r\n            max-height: 600px;\r\n        }\r\n        \r\n        \/* Preview Box *\/\r\n        #preview-box {\r\n            margin-top: 20px;\r\n            border-radius: 14px;\r\n            background: white;\r\n            border: 2px solid #dce6ed;\r\n            padding: 18px;\r\n            text-align: center;\r\n            display: none;\r\n            transition: all 0.3s;\r\n        }\r\n        \r\n        #preview-box.show {\r\n            display: block;\r\n        }\r\n        \r\n        .image-container {\r\n            background: #f0f4f8;\r\n            border-radius: 10px;\r\n            min-height: 180px;\r\n            display: flex;\r\n            align-items: center;\r\n            justify-content: center;\r\n            overflow: hidden;\r\n            position: relative;\r\n        }\r\n        \r\n        #fish-img {\r\n            max-width: 100%;\r\n            max-height: 260px;\r\n            object-fit: contain;\r\n            opacity: 0;\r\n            transition: opacity 0.4s ease;\r\n        }\r\n        \r\n        #fish-img.loaded {\r\n            opacity: 1;\r\n        }\r\n        \r\n        #loader-text {\r\n            color: #7f8c8d;\r\n            font-size: 14px;\r\n            padding: 10px 0;\r\n        }\r\n        \r\n        \/* Measurement *\/\r\n        .measurement-tools {\r\n            display: none;\r\n            margin-top: 10px;\r\n            padding: 12px;\r\n            background: #fff8e1;\r\n            border-radius: 10px;\r\n            border: 2px solid #f39c12;\r\n        }\r\n        \r\n        .measurement-tools.show {\r\n            display: block;\r\n        }\r\n        \r\n        .measurement-tools .input-group {\r\n            display: flex;\r\n            gap: 10px;\r\n            align-items: center;\r\n            flex-wrap: wrap;\r\n        }\r\n        \r\n        .measurement-tools input {\r\n            padding: 6px 12px;\r\n            border: 2px solid #dce6ed;\r\n            border-radius: 8px;\r\n            width: 100px;\r\n            font-size: 13px;\r\n        }\r\n        \r\n        \/* File info *\/\r\n        .file-info {\r\n            display: none;\r\n            justify-content: space-between;\r\n            align-items: center;\r\n            padding: 10px 14px;\r\n            background: #eaf2f8;\r\n            border-radius: 8px;\r\n            margin-top: 10px;\r\n            font-size: 13px;\r\n        }\r\n        \r\n        .file-info.show {\r\n            display: flex;\r\n        }\r\n        \r\n        .file-info .size-badge {\r\n            background: #1a5276;\r\n            color: white;\r\n            padding: 2px 12px;\r\n            border-radius: 20px;\r\n            font-size: 11px;\r\n            font-weight: 700;\r\n        }\r\n        \r\n        .file-info .size-badge.success {\r\n            background: #27ae60;\r\n        }\r\n        \r\n        \/* Buttons *\/\r\n        .btn-add {\r\n            background: #27ae60;\r\n            color: white;\r\n            border: none;\r\n            padding: 12px 24px;\r\n            border-radius: 10px;\r\n            font-size: 15px;\r\n            font-weight: 700;\r\n            cursor: pointer;\r\n            transition: all 0.3s;\r\n            display: flex;\r\n            align-items: center;\r\n            gap: 8px;\r\n            margin-top: 6px;\r\n        }\r\n        \r\n        .btn-add:hover {\r\n            background: #1e8449;\r\n            transform: translateY(-2px);\r\n            box-shadow: 0 4px 16px rgba(39, 174, 96, 0.4);\r\n        }\r\n        \r\n        .btn-secondary {\r\n            background: #2e86c1;\r\n            color: white;\r\n            border: none;\r\n            padding: 8px 18px;\r\n            border-radius: 8px;\r\n            font-weight: 600;\r\n            cursor: pointer;\r\n            transition: all 0.3s;\r\n        }\r\n        \r\n        .btn-secondary:hover {\r\n            background: #1a5276;\r\n        }\r\n        \r\n        .btn-outline {\r\n            background: transparent;\r\n            color: #2e86c1;\r\n            border: 2px solid #2e86c1;\r\n            padding: 6px 16px;\r\n            border-radius: 8px;\r\n            font-weight: 600;\r\n            cursor: pointer;\r\n            transition: all 0.3s;\r\n        }\r\n        \r\n        .btn-outline:hover {\r\n            background: #2e86c1;\r\n            color: white;\r\n        }\r\n        \r\n        \/* Table *\/\r\n        .table-wrapper {\r\n            overflow-x: auto;\r\n            margin-top: 12px;\r\n            border-radius: 10px;\r\n            border: 1px solid #dce6ed;\r\n        }\r\n        \r\n        table {\r\n            width: 100%;\r\n            border-collapse: collapse;\r\n            font-size: 14px;\r\n        }\r\n        \r\n        thead {\r\n            background: #1a5276;\r\n            color: white;\r\n        }\r\n        \r\n        th {\r\n            padding: 12px 14px;\r\n            text-align: left;\r\n            font-weight: 600;\r\n            white-space: nowrap;\r\n        }\r\n        \r\n        td {\r\n            padding: 10px 14px;\r\n            border-bottom: 1px solid #e8edf2;\r\n            vertical-align: middle;\r\n        }\r\n        \r\n        tr:hover {\r\n            background: #f0f6fa;\r\n        }\r\n        \r\n        .remove-btn {\r\n            background: #e74c3c;\r\n            color: white;\r\n            border: none;\r\n            padding: 5px 12px;\r\n            border-radius: 6px;\r\n            cursor: pointer;\r\n            font-size: 12px;\r\n            font-weight: 600;\r\n            transition: all 0.3s;\r\n        }\r\n        \r\n        .remove-btn:hover {\r\n            background: #c0392b;\r\n            transform: scale(1.05);\r\n        }\r\n        \r\n        .total-biomass {\r\n            font-size: 18px;\r\n            font-weight: 700;\r\n            color: #1a5276;\r\n            margin: 16px 0 8px 0;\r\n            padding: 12px 16px;\r\n            background: #eaf2f8;\r\n            border-radius: 10px;\r\n            display: inline-block;\r\n        }\r\n        \r\n        \/* Export section *\/\r\n        .export-section {\r\n            display: flex;\r\n            flex-wrap: wrap;\r\n            align-items: center;\r\n            gap: 16px;\r\n            margin-top: 18px;\r\n            padding-top: 18px;\r\n            border-top: 2px solid #dce6ed;\r\n        }\r\n        \r\n        .export-section label {\r\n            font-weight: 600;\r\n            font-size: 14px;\r\n            color: #34495e;\r\n        }\r\n        \r\n        .export-section .radio-group {\r\n            gap: 14px;\r\n        }\r\n        \r\n        .btn-export {\r\n            background: #2e86c1;\r\n            color: white;\r\n            border: none;\r\n            padding: 10px 28px;\r\n            border-radius: 10px;\r\n            font-size: 15px;\r\n            font-weight: 700;\r\n            cursor: pointer;\r\n            transition: all 0.3s;\r\n        }\r\n        \r\n        .btn-export:hover {\r\n            background: #1a5276;\r\n            transform: translateY(-2px);\r\n            box-shadow: 0 4px 16px rgba(46, 134, 193, 0.4);\r\n        }\r\n        \r\n        .error-message {\r\n            color: #721c24;\r\n            background: #f8d7da;\r\n            border: 1px solid #f5c6cb;\r\n            padding: 12px 16px;\r\n            border-radius: 8px;\r\n            margin-top: 10px;\r\n            display: none;\r\n        }\r\n        \r\n        .error-message.show {\r\n            display: block;\r\n        }\r\n        \r\n        \/* Progress bar *\/\r\n        .progress-bar {\r\n            display: none;\r\n            width: 100%;\r\n            height: 6px;\r\n            background: #dce6ed;\r\n            border-radius: 3px;\r\n            overflow: hidden;\r\n            margin-top: 10px;\r\n        }\r\n        \r\n        .progress-bar.show {\r\n            display: block;\r\n        }\r\n        \r\n        .progress-bar .progress-fill {\r\n            height: 100%;\r\n            background: linear-gradient(90deg, #2e86c1, #1a5276);\r\n            border-radius: 3px;\r\n            transition: width 0.3s ease;\r\n            width: 0%;\r\n        }\r\n        \r\n        .scale-badge {\r\n            background: #f39c12;\r\n            color: white;\r\n            padding: 2px 12px;\r\n            border-radius: 20px;\r\n            font-size: 11px;\r\n            font-weight: 700;\r\n            display: inline-block;\r\n            margin-left: 6px;\r\n        }\r\n        \r\n        .photo-thumbnail {\r\n            width: 50px;\r\n            height: 50px;\r\n            object-fit: cover;\r\n            border-radius: 6px;\r\n            border: 2px solid #dce6ed;\r\n        }\r\n        \r\n        .confidence-badge {\r\n            padding: 2px 10px;\r\n            border-radius: 20px;\r\n            font-size: 11px;\r\n            font-weight: 700;\r\n        }\r\n        \r\n        .confidence-high {\r\n            background: #d5f5e3;\r\n            color: #1e8449;\r\n        }\r\n        \r\n        .confidence-medium {\r\n            background: #fdebd0;\r\n            color: #b9770e;\r\n        }\r\n        \r\n        .confidence-low {\r\n            background: #fadbd8;\r\n            color: #c0392b;\r\n        }\r\n        \r\n        \/* Responsive *\/\r\n        @media (max-width: 600px) {\r\n            .app-container {\r\n                padding: 15px;\r\n            }\r\n            .app-header h1 {\r\n                font-size: 20px;\r\n            }\r\n            .app-header h1 i {\r\n                font-size: 24px;\r\n            }\r\n            .header-actions {\r\n                margin-top: 10px;\r\n            }\r\n            .compare-container.show {\r\n                grid-template-columns: 1fr;\r\n            }\r\n        }\r\n    <\/style>\r\n<\/head>\r\n<body>\r\n\r\n<div class=\"app-container\">\r\n    <!-- HEADER -->\r\n    <div class=\"app-header\">\r\n        <h1>\r\n            <i class=\"fas fa-fish\"><\/i>\r\n            <span id=\"mainTitle\">Marine Fish Identifier<\/span>\r\n            <span class=\"version\">v2.0<\/span>\r\n        <\/h1>\r\n        <div class=\"header-actions\">\r\n            <button class=\"lang-btn active\" id=\"langID\" onclick=\"setLanguage('id')\">\ud83c\uddee\ud83c\udde9 ID<\/button>\r\n            <button class=\"lang-btn\" id=\"langEN\" onclick=\"setLanguage('en')\">\ud83c\uddec\ud83c\udde7 EN<\/button>\r\n            <button class=\"action-btn\" id=\"saveProjectBtn\"><i class=\"fas fa-save\"><\/i> <span data-i18n=\"saveProject\">Simpan<\/span><\/button>\r\n            <button class=\"action-btn\" id=\"loadProjectBtn\"><i class=\"fas fa-folder-open\"><\/i> <span data-i18n=\"loadProject\">Muat<\/span><\/button>\r\n            <input type=\"file\" id=\"loadProjectFile\" accept=\".json\" style=\"display: none;\">\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <!-- Two Column -->\r\n    <div class=\"two-column\">\r\n        <!-- KOLOM KIRI: Project Info -->\r\n        <div class=\"card\">\r\n            <div class=\"card-title\"><i class=\"fas fa-clipboard-list\"><\/i> <span id=\"projectInfoTitle\">Informasi Proyek<\/span><\/div>\r\n            <div class=\"form-group\">\r\n                <label id=\"projectNameLabel\">Nama Proyek<\/label>\r\n                <input type=\"text\" id=\"projectName\" placeholder=\"Misal: Ekspedisi Karang\" \/>\r\n            <\/div>\r\n            <div class=\"form-row\">\r\n                <div class=\"form-group\">\r\n                    <label id=\"surveyorLabel\">Surveyor<\/label>\r\n                    <input type=\"text\" id=\"surveyor\" \/>\r\n                <\/div>\r\n                <div class=\"form-group\">\r\n                    <label id=\"datetimeLabel\">Tanggal & Waktu<\/label>\r\n                    <input type=\"datetime-local\" id=\"datetime\" \/>\r\n                <\/div>\r\n            <\/div>\r\n            <div class=\"form-row\">\r\n                <div class=\"form-group\">\r\n                    <label id=\"siteNameLabel\">Nama Site<\/label>\r\n                    <input type=\"text\" id=\"siteName\" \/>\r\n                <\/div>\r\n                <div class=\"form-group\">\r\n                    <label id=\"locationDescLabel\">Lokasi (deskripsi)<\/label>\r\n                    <input type=\"text\" id=\"locationDesc\" \/>\r\n                <\/div>\r\n            <\/div>\r\n            <div class=\"form-row\">\r\n                <div class=\"form-group\">\r\n                    <label id=\"latitudeLabel\">Latitude<\/label>\r\n                    <input type=\"number\" step=\"any\" id=\"lat\" value=\"-6.2088\" \/>\r\n                <\/div>\r\n                <div class=\"form-group\">\r\n                    <label id=\"longitudeLabel\">Longitude<\/label>\r\n                    <input type=\"number\" step=\"any\" id=\"lng\" value=\"106.8456\" \/>\r\n                <\/div>\r\n            <\/div>\r\n            <div id=\"map\"><\/div>\r\n            <div class=\"form-row\" style=\"margin-top: 10px;\">\r\n                <div class=\"form-group\">\r\n                    <label id=\"lineLengthLabel\">Panjang Line (m)<\/label>\r\n                    <input type=\"number\" step=\"0.1\" id=\"lineLength\" \/>\r\n                <\/div>\r\n                <div class=\"form-group\">\r\n                    <label id=\"obsWidthLabel\">Lebar Pengamatan (m)<\/label>\r\n                    <input type=\"number\" step=\"0.1\" id=\"obsWidth\" \/>\r\n                <\/div>\r\n            <\/div>\r\n            <div class=\"form-row\">\r\n                <div class=\"form-group\">\r\n                    <label id=\"waterConditionLabel\">Kondisi Perairan<\/label>\r\n                    <input type=\"text\" id=\"waterCondition\" placeholder=\"Misal: Cerah, Berombak\" \/>\r\n                <\/div>\r\n                <div class=\"form-group\">\r\n                    <label id=\"depthLabel\">Kedalaman (m)<\/label>\r\n                    <input type=\"number\" step=\"0.1\" id=\"depth\" \/>\r\n                <\/div>\r\n            <\/div>\r\n        <\/div>\r\n\r\n        <!-- KOLOM KANAN: Upload & Identifikasi -->\r\n        <div class=\"card\">\r\n            <div class=\"card-title\"><i class=\"fas fa-camera\"><\/i> <span id=\"uploadTitle\">Upload Foto Ikan<\/span><\/div>\r\n            \r\n            <!-- Upload Area -->\r\n            <div class=\"upload-area\" id=\"uploadArea\">\r\n                <i class=\"fas fa-cloud-upload-alt\"><\/i>\r\n                <p id=\"uploadText\">\ud83d\udcf8 Ambil foto atau unggah dari galeri<\/p>\r\n                <p class=\"sub-text\" id=\"uploadSubText\">Format: JPG, PNG, HEIC, WEBP \u2022 Maks 20MB<\/p>\r\n                <input type=\"file\" id=\"fileInput\" accept=\"image\/*\" capture=\"environment\" \/>\r\n            <\/div>\r\n            \r\n            <!-- File Info -->\r\n            <div class=\"file-info\" id=\"fileInfo\">\r\n                <span id=\"fileName\">image.jpg<\/span>\r\n                <span class=\"size-badge\" id=\"fileSizeBadge\">0 KB<\/span>\r\n            <\/div>\r\n            \r\n            <!-- Progress Bar -->\r\n            <div class=\"progress-bar\" id=\"progressBar\">\r\n                <div class=\"progress-fill\" id=\"progressFill\"><\/div>\r\n            <\/div>\r\n            \r\n            <!-- Image Editor -->\r\n            <div class=\"image-editor-container\" id=\"imageEditorContainer\">\r\n                <div class=\"editor-toolbar\">\r\n                    <button onclick=\"rotateImage(-90)\"><i class=\"fas fa-undo\"><\/i> Rotate<\/button>\r\n                    <button onclick=\"rotateImage(90)\"><i class=\"fas fa-redo\"><\/i> Rotate<\/button>\r\n                    <button onclick=\"resetCrop()\"><i class=\"fas fa-expand\"><\/i> Reset<\/button>\r\n                    <button class=\"primary\" onclick=\"applyCrop()\"><i class=\"fas fa-check\"><\/i> <span id=\"applyCropText\">Crop & Lanjut<\/span><\/button>\r\n                    <button class=\"danger\" onclick=\"cancelUpload()\"><i class=\"fas fa-times\"><\/i> <span id=\"cancelUploadText\">Batal<\/span><\/button>\r\n                <\/div>\r\n                <div id=\"imageEditor\">\r\n                    <img decoding=\"async\" id=\"editorImage\" src=\"\" alt=\"Edit Image\" \/>\r\n                <\/div>\r\n            <\/div>\r\n            \r\n            <!-- Wizard Identifikasi -->\r\n            <div class=\"wizard-container\" id=\"wizardContainer\">\r\n                <div class=\"card-title\"><i class=\"fas fa-sliders-h\"><\/i> <span id=\"wizardTitle\">Identifikasi Morfologi<\/span><\/div>\r\n                \r\n                <div id=\"wizardSteps\"><\/div>\r\n                \r\n                <div class=\"candidate-count\" id=\"candidateCount\">\r\n                    <span id=\"candidateText\">Kandidat ditemukan:<\/span> <strong id=\"candidateNumber\">0<\/strong>\r\n                <\/div>\r\n                \r\n                <div class=\"candidate-list\" id=\"candidateList\"><\/div>\r\n                \r\n                <!-- Compare Mode -->\r\n                <div class=\"compare-container\" id=\"compareContainer\">\r\n                    <div class=\"compare-box\">\r\n                        <span class=\"label\" id=\"compareUserLabel\">Foto Anda<\/span>\r\n                        <img decoding=\"async\" id=\"compareUserImg\" src=\"\" alt=\"User Photo\" onclick=\"toggleZoom(this)\" \/>\r\n                    <\/div>\r\n                    <div class=\"compare-box\">\r\n                        <span class=\"label\" id=\"compareRefLabel\">Referensi<\/span>\r\n                        <img decoding=\"async\" id=\"compareRefImg\" src=\"\" alt=\"Reference Photo\" onclick=\"toggleZoom(this)\" \/>\r\n                    <\/div>\r\n                <\/div>\r\n                \r\n                <!-- Measurement Tools -->\r\n                <div class=\"measurement-tools\" id=\"measurementTools\">\r\n                    <div class=\"card-title\" style=\"font-size:14px; margin-bottom:8px;\"><i class=\"fas fa-ruler\"><\/i> <span id=\"measurementTitle\">Pengukuran Ikan<\/span><\/div>\r\n                    <div class=\"input-group\">\r\n                        <span id=\"scaleLabel\">Panjang Skala (cm):<\/span>\r\n                        <input type=\"number\" id=\"scaleLength\" value=\"10\" step=\"0.5\" min=\"0.5\" \/>\r\n                        <button class=\"btn-secondary\" onclick=\"measureFish()\"><i class=\"fas fa-ruler-combined\"><\/i> <span id=\"measureBtnText\">Ukur<\/span><\/button>\r\n                        <span id=\"measurementResult\" style=\"font-weight:700;color:#1a5276;display:none;margin-left:10px;\"><\/span>\r\n                    <\/div>\r\n                <\/div>\r\n                \r\n                <!-- Action Buttons -->\r\n                <div style=\"display:flex; gap:10px; margin-top:12px; flex-wrap:wrap;\">\r\n                    <button class=\"btn-add\" id=\"addEntryBtn\"><i class=\"fas fa-plus-circle\"><\/i> <span id=\"addButtonText\">Tambah ke Daftar<\/span><\/button>\r\n                    <button class=\"btn-secondary\" onclick=\"resetWizard()\"><i class=\"fas fa-redo\"><\/i> <span id=\"resetWizardText\">Reset<\/span><\/button>\r\n                <\/div>\r\n            <\/div>\r\n            \r\n            <!-- Preview Box (dari database) -->\r\n            <div id=\"preview-box\">\r\n                <div id=\"loader-text\"><i class=\"fas fa-spinner fa-pulse\"><\/i> Mencari foto referensi...<\/div>\r\n                <div class=\"image-container\">\r\n                    <img decoding=\"async\" id=\"fish-img\" src=\"\" alt=\"Foto Referensi\" \/>\r\n                <\/div>\r\n                <p id=\"image-source\" style=\"font-size: 11px; margin-top: 6px; color: #95a5a6;\">Sumber: Memuat...<\/p>\r\n                <div style=\"margin-top: 6px;\">\r\n                    <a id=\"fishbase-link\" href=\"#\" target=\"_blank\">\r\n                        <i class=\"fas fa-external-link-alt\"><\/i> <span id=\"fishbaseButtonText\">Detail di FishBase<\/span>\r\n                    <\/a>\r\n                <\/div>\r\n            <\/div>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <!-- DAFTAR IKAN -->\r\n    <div style=\"margin-top: 10px;\">\r\n        <div style=\"display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap;\">\r\n            <h3 style=\"color: #1a5276;\"><i class=\"fas fa-list-ul\"><\/i> <span id=\"fishListTitle\">Daftar Ikan Teridentifikasi<\/span><\/h3>\r\n            <span class=\"total-biomass\" id=\"totalBiomass\">Total Biomassa: 0 gram<\/span>\r\n        <\/div>\r\n        <div class=\"table-wrapper\">\r\n            <table>\r\n                <thead>\r\n                    <tr>\r\n                        <th id=\"tablePhoto\">Foto<\/th>\r\n                        <th id=\"tableName\">Nama<\/th>\r\n                        <th id=\"tableLevel\">Level<\/th>\r\n                        <th id=\"tableCount\">Jumlah<\/th>\r\n                        <th id=\"tableSize\">Ukuran (cm)<\/th>\r\n                        <th id=\"tableBiomass\">Biomassa (g)<\/th>\r\n                        <th id=\"tableConfidence\">Confidence<\/th>\r\n                        <th id=\"tableAction\">Aksi<\/th>\r\n                    <\/tr>\r\n                <\/thead>\r\n                <tbody id=\"tableBody\"><\/tbody>\r\n            <\/table>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <!-- EKSPORT -->\r\n    <div class=\"export-section\">\r\n        <label id=\"exportFormatLabel\">Format Ekspor:<\/label>\r\n        <div class=\"radio-group\">\r\n            <label><input type=\"radio\" name=\"exportFormat\" value=\"xlsx\" checked> Excel (.xlsx)<\/label>\r\n            <label><input type=\"radio\" name=\"exportFormat\" value=\"csv\"> CSV (.csv)<\/label>\r\n            <label><input type=\"radio\" name=\"exportFormat\" value=\"txt\"> TXT (.txt)<\/label>\r\n        <\/div>\r\n        <button class=\"btn-export\" id=\"exportBtn\"><i class=\"fas fa-download\"><\/i> <span id=\"exportButtonText\">Ekspor<\/span><\/button>\r\n    <\/div>\r\n    \r\n    <div id=\"loadError\" class=\"error-message\"><\/div>\r\n<\/div>\r\n\r\n<!-- Leaflet JS -->\r\n<script src=\"https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.js\"><\/script>\r\n<!-- SheetJS -->\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/xlsx\/0.18.5\/xlsx.full.min.js\"><\/script>\r\n<!-- Cropper.js -->\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/cropperjs\/1.6.1\/cropper.min.js\"><\/script>\r\n\r\n<script>\r\n\/\/ ===================== KONFIGURASI =====================\r\nconst FAMILY_CSV_URL = '\/wp-content\/uploads\/Ikan\/Fish_Families.csv';\r\nconst GENUS_CSV_URL  = '\/wp-content\/uploads\/Ikan\/Fish_Genera.csv';\r\nconst SPECIES_CSV_URL = '\/wp-content\/uploads\/Ikan\/Fish_Species.csv';\r\nconst MORPHOLOGY_CSV_URL = '\/wp-content\/uploads\/Ikan\/Fish_Morphology.csv';\r\n\r\n\/\/ ===================== DATA GLOBAL =====================\r\nlet families = [];\r\nlet genera = [];\r\nlet species = [];\r\nlet morphology = {};\r\nlet genusMaxLength = {};\r\nlet speciesLookup = {};\r\nlet familyMaxSize = {};\r\nlet dataLoaded = false;\r\nlet entries = [];\r\nlet currentLanguage = 'id';\r\nlet searchTimeout;\r\n\r\n\/\/ Image upload variables\r\nlet cropper = null;\r\nlet uploadedImageData = null;\r\nlet currentImageFile = null;\r\nlet isMeasuring = false;\r\nlet selectedSpecies = null;\r\n\r\n\/\/ Wizard state\r\nlet wizardSelections = {};\r\n\r\n\/\/ Morphology traits\r\nconst TRAITS = {\r\n    body_shape: ['Fusiform', 'Compressiform', 'Depressiform', 'Elongated', 'Ribbon', 'Globular', 'Anguilliform'],\r\n    mouth_position: ['Superior', 'Terminal', 'Inferior', 'Subterminal', 'Protractile'],\r\n    mouth_size: ['Large', 'Small', 'Medium'],\r\n    caudal_fin: ['Forked', 'Rounded', 'Lunate', 'Emarginate', 'Truncate', 'Pointed'],\r\n    dorsal_fin: ['Continuous', 'Separated', 'Single', 'Double', 'Long', 'Short'],\r\n    body_pattern: ['Plain', 'Stripe', 'Spot', 'Bar', 'Reticulate', 'Blotch', 'Mottled', 'Vertical bands'],\r\n    dominant_color: ['Silver', 'Yellow', 'Blue', 'Brown', 'Black', 'Red', 'Green', 'White', 'Gray', 'Orange']\r\n};\r\n\r\n\/\/ ===================== TRANSLASI =====================\r\nconst translations = {\r\n  id: {\r\n    mainTitle: 'Marine Fish Identifier',\r\n    projectInfoTitle: 'Informasi Proyek',\r\n    projectNameLabel: 'Nama Proyek',\r\n    surveyorLabel: 'Surveyor',\r\n    datetimeLabel: 'Tanggal & Waktu',\r\n    siteNameLabel: 'Nama Site',\r\n    locationDescLabel: 'Lokasi (deskripsi)',\r\n    latitudeLabel: 'Latitude',\r\n    longitudeLabel: 'Longitude',\r\n    lineLengthLabel: 'Panjang Line (m)',\r\n    obsWidthLabel: 'Lebar Pengamatan (m)',\r\n    waterConditionLabel: 'Kondisi Perairan',\r\n    depthLabel: 'Kedalaman (m)',\r\n    uploadTitle: 'Upload Foto Ikan',\r\n    uploadText: '\ud83d\udcf8 Ambil foto atau unggah dari galeri',\r\n    uploadSubText: 'Format: JPG, PNG, HEIC, WEBP \u2022 Maks 20MB',\r\n    applyCropText: 'Crop & Lanjut',\r\n    cancelUploadText: 'Batal',\r\n    wizardTitle: 'Identifikasi Morfologi',\r\n    candidateText: 'Kandidat ditemukan:',\r\n    compareUserLabel: 'Foto Anda',\r\n    compareRefLabel: 'Referensi',\r\n    measurementTitle: 'Pengukuran Ikan',\r\n    scaleLabel: 'Panjang Skala (cm)',\r\n    measureBtnText: 'Ukur',\r\n    addButtonText: 'Tambah ke Daftar',\r\n    resetWizardText: 'Reset',\r\n    fishListTitle: 'Daftar Ikan Teridentifikasi',\r\n    tablePhoto: 'Foto',\r\n    tableName: 'Nama',\r\n    tableLevel: 'Level',\r\n    tableCount: 'Jumlah',\r\n    tableSize: 'Ukuran (cm)',\r\n    tableBiomass: 'Biomassa (g)',\r\n    tableConfidence: 'Confidence',\r\n    tableAction: 'Aksi',\r\n    totalBiomass: 'Total Biomassa: 0 gram',\r\n    exportFormatLabel: 'Format Ekspor:',\r\n    exportButtonText: 'Ekspor',\r\n    removeButtonText: 'Hapus',\r\n    saveProject: 'Simpan',\r\n    loadProject: 'Muat',\r\n    searchPlaceholder: 'Ketik untuk mencari...',\r\n    waterConditionPlaceholder: 'Misal: Cerah, Berombak',\r\n    projectNamePlaceholder: 'Misal: Ekspedisi Karang',\r\n    sizeWarning: '\u26a0\ufe0f Ukuran melebihi batas normal (maks ',\r\n    sizeWarningEnd: ' cm)',\r\n    alertEnterName: 'Masukkan nama terlebih dahulu!',\r\n    alertNameNotFound: 'Nama tidak ditemukan dalam database.',\r\n    alertDataCannotAdd: 'Data tidak dapat ditambahkan.',\r\n    loaderText: 'Mencari foto referensi...',\r\n    imageSource: 'Sumber: Memuat...',\r\n    fishbaseButtonText: 'Detail di FishBase',\r\n    fileSizeKB: 'KB',\r\n    fileSizeMB: 'MB',\r\n    compressed: 'Dikompresi',\r\n    original: 'Asli',\r\n    body_shape: 'Bentuk Tubuh',\r\n    mouth_position: 'Posisi Mulut',\r\n    mouth_size: 'Ukuran Mulut',\r\n    caudal_fin: 'Sirip Ekor',\r\n    dorsal_fin: 'Sirip Punggung',\r\n    body_pattern: 'Pola Tubuh',\r\n    dominant_color: 'Warna Dominan',\r\n    confidence_high: 'Tinggi',\r\n    confidence_medium: 'Sedang',\r\n    confidence_low: 'Rendah',\r\n    measured: 'Terukur'\r\n  },\r\n  en: {\r\n    mainTitle: 'Marine Fish Identifier',\r\n    projectInfoTitle: 'Project Information',\r\n    projectNameLabel: 'Project Name',\r\n    surveyorLabel: 'Surveyor',\r\n    datetimeLabel: 'Date & Time',\r\n    siteNameLabel: 'Site Name',\r\n    locationDescLabel: 'Location (description)',\r\n    latitudeLabel: 'Latitude',\r\n    longitudeLabel: 'Longitude',\r\n    lineLengthLabel: 'Line Length (m)',\r\n    obsWidthLabel: 'Observation Width (m)',\r\n    waterConditionLabel: 'Water Condition',\r\n    depthLabel: 'Depth (m)',\r\n    uploadTitle: 'Upload Fish Photo',\r\n    uploadText: '\ud83d\udcf8 Take photo or upload from gallery',\r\n    uploadSubText: 'Format: JPG, PNG, HEIC, WEBP \u2022 Max 20MB',\r\n    applyCropText: 'Crop & Continue',\r\n    cancelUploadText: 'Cancel',\r\n    wizardTitle: 'Morphological Identification',\r\n    candidateText: 'Candidates found:',\r\n    compareUserLabel: 'Your Photo',\r\n    compareRefLabel: 'Reference',\r\n    measurementTitle: 'Fish Measurement',\r\n    scaleLabel: 'Scale Length (cm)',\r\n    measureBtnText: 'Measure',\r\n    addButtonText: 'Add to List',\r\n    resetWizardText: 'Reset',\r\n    fishListTitle: 'Identified Fish List',\r\n    tablePhoto: 'Photo',\r\n    tableName: 'Name',\r\n    tableLevel: 'Level',\r\n    tableCount: 'Count',\r\n    tableSize: 'Size (cm)',\r\n    tableBiomass: 'Biomass (g)',\r\n    tableConfidence: 'Confidence',\r\n    tableAction: 'Action',\r\n    totalBiomass: 'Total Biomass: 0 g',\r\n    exportFormatLabel: 'Export Format:',\r\n    exportButtonText: 'Export',\r\n    removeButtonText: 'Remove',\r\n    saveProject: 'Save',\r\n    loadProject: 'Load',\r\n    searchPlaceholder: 'Type to search...',\r\n    waterConditionPlaceholder: 'E.g., Clear, Wavy',\r\n    projectNamePlaceholder: 'E.g., Reef Expedition',\r\n    sizeWarning: '\u26a0\ufe0f Size exceeds normal limit (max ',\r\n    sizeWarningEnd: ' cm)',\r\n    alertEnterName: 'Please enter a name first!',\r\n    alertNameNotFound: 'Name not found in database.',\r\n    alertDataCannotAdd: 'Data cannot be added.',\r\n    loaderText: 'Searching for reference photo...',\r\n    imageSource: 'Source: Loading...',\r\n    fishbaseButtonText: 'Details on FishBase',\r\n    fileSizeKB: 'KB',\r\n    fileSizeMB: 'MB',\r\n    compressed: 'Compressed',\r\n    original: 'Original',\r\n    body_shape: 'Body Shape',\r\n    mouth_position: 'Mouth Position',\r\n    mouth_size: 'Mouth Size',\r\n    caudal_fin: 'Caudal Fin',\r\n    dorsal_fin: 'Dorsal Fin',\r\n    body_pattern: 'Body Pattern',\r\n    dominant_color: 'Dominant Color',\r\n    confidence_high: 'High',\r\n    confidence_medium: 'Medium',\r\n    confidence_low: 'Low',\r\n    measured: 'Measured'\r\n  }\r\n};\r\n\r\n\/\/ ===================== FUNGSI BAHASA =====================\r\nfunction setLanguage(lang) {\r\n    currentLanguage = lang;\r\n    document.getElementById('langID').classList.toggle('active', lang === 'id');\r\n    document.getElementById('langEN').classList.toggle('active', lang === 'en');\r\n    \r\n    const t = translations[lang];\r\n    const keys = ['mainTitle', 'projectInfoTitle', 'projectNameLabel', 'surveyorLabel', 'datetimeLabel', \r\n                  'siteNameLabel', 'locationDescLabel', 'latitudeLabel', 'longitudeLabel', 'lineLengthLabel',\r\n                  'obsWidthLabel', 'waterConditionLabel', 'depthLabel', 'uploadTitle', 'uploadText', 'uploadSubText',\r\n                  'applyCropText', 'cancelUploadText', 'wizardTitle', 'candidateText', 'compareUserLabel', \r\n                  'compareRefLabel', 'measurementTitle', 'scaleLabel', 'measureBtnText', 'addButtonText',\r\n                  'resetWizardText', 'fishListTitle', 'tablePhoto', 'tableName', 'tableLevel', 'tableCount',\r\n                  'tableSize', 'tableBiomass', 'tableConfidence', 'tableAction', 'exportFormatLabel', \r\n                  'exportButtonText', 'removeButtonText', 'saveProject', 'loadProject', 'fishbaseButtonText'];\r\n    \r\n    keys.forEach(key => {\r\n        const el = document.getElementById(key);\r\n        if (el) el.textContent = t[key] || translations.en[key];\r\n    });\r\n    \r\n    \/\/ Special handling for placeholders\r\n    document.getElementById('taxonInput').placeholder = t.searchPlaceholder;\r\n    document.getElementById('waterCondition').placeholder = t.waterConditionPlaceholder;\r\n    document.getElementById('projectName').placeholder = t.projectNamePlaceholder;\r\n    \r\n    updateTotalBiomass();\r\n    renderTable();\r\n    renderWizard();\r\n}\r\n\r\n\/\/ ===================== CSV PARSER =====================\r\nfunction parseCSV(csvText) {\r\n    const lines = csvText.trim().split('\\n');\r\n    if (lines.length < 2) return [];\r\n    const headers = lines[0].split(',').map(h => h.trim());\r\n    const result = [];\r\n    for (let i = 1; i < lines.length; i++) {\r\n        const line = lines[i].trim();\r\n        if (line === '') continue;\r\n        const values = line.split(',').map(v => v.trim());\r\n        if (values.length !== headers.length) continue;\r\n        const obj = {};\r\n        for (let j = 0; j < headers.length; j++) {\r\n            let val = values[j];\r\n            if (val !== '' && !isNaN(val)) obj[headers[j]] = parseFloat(val);\r\n            else obj[headers[j]] = val;\r\n        }\r\n        if (obj.Name) result.push(obj);\r\n    }\r\n    return result;\r\n}\r\n\r\n\/\/ ===================== MUAT DATA =====================\r\nasync function loadData() {\r\n    const errorDiv = document.getElementById('loadError');\r\n    try {\r\n        const [famRes, genRes, speRes, morRes] = await Promise.all([\r\n            fetch(FAMILY_CSV_URL),\r\n            fetch(GENUS_CSV_URL),\r\n            fetch(SPECIES_CSV_URL),\r\n            fetch(MORPHOLOGY_CSV_URL).catch(() => ({ ok: false }))\r\n        ]);\r\n        \r\n        if (!famRes.ok || !genRes.ok || !speRes.ok) {\r\n            throw new Error('Gagal mengambil file CSV dasar.');\r\n        }\r\n        \r\n        const famText = await famRes.text();\r\n        const genText = await genRes.text();\r\n        const speText = await speRes.text();\r\n\r\n        families = parseCSV(famText);\r\n        genera = parseCSV(genText);\r\n        species = parseCSV(speText);\r\n\r\n        \/\/ Load morphology if available\r\n        if (morRes && morRes.ok) {\r\n            const morText = await morRes.text();\r\n            const morData = parseCSV(morText);\r\n            morData.forEach(m => {\r\n                if (m.Species) {\r\n                    morphology[m.Species] = m;\r\n                }\r\n            });\r\n        }\r\n\r\n        if (families.length === 0 || genera.length === 0 || species.length === 0) {\r\n            throw new Error('File CSV kosong atau format tidak sesuai.');\r\n        }\r\n\r\n        \/\/ Build lookups\r\n        species.forEach(s => {\r\n            speciesLookup[s.Name] = s;\r\n            const nameParts = s.Name.split(' ');\r\n            if (nameParts.length >= 1) {\r\n                const genusName = nameParts[0];\r\n                const maxLen = s['Max Length (cm)'] || 0;\r\n                if (!genusMaxLength[genusName] || maxLen > genusMaxLength[genusName]) {\r\n                    genusMaxLength[genusName] = maxLen;\r\n                }\r\n            }\r\n        });\r\n\r\n        families.forEach(fam => {\r\n            const stem = fam.Name.replace(\/idae$\/i, '').toLowerCase();\r\n            let max = 0;\r\n            genera.forEach(gen => {\r\n                if (gen.Name.toLowerCase().startsWith(stem)) {\r\n                    const genMax = genusMaxLength[gen.Name] || 0;\r\n                    if (genMax > max) max = genMax;\r\n                }\r\n            });\r\n            familyMaxSize[fam.Name] = max > 0 ? max : 500;\r\n        });\r\n\r\n        dataLoaded = true;\r\n        errorDiv.classList.remove('show');\r\n        renderWizard();\r\n    } catch (error) {\r\n        console.error('Gagal memuat data:', error);\r\n        errorDiv.textContent = '\u274c ' + error.message;\r\n        errorDiv.classList.add('show');\r\n    }\r\n}\r\n\r\n\/\/ ===================== MORPHOLOGY WIZARD =====================\r\nfunction renderWizard() {\r\n    const container = document.getElementById('wizardSteps');\r\n    container.innerHTML = '';\r\n    \r\n    const t = translations[currentLanguage];\r\n    \r\n    Object.entries(TRAITS).forEach(([traitKey, options]) => {\r\n        const step = document.createElement('div');\r\n        step.className = 'wizard-step';\r\n        \r\n        const label = document.createElement('label');\r\n        label.textContent = t[traitKey] || traitKey;\r\n        step.appendChild(label);\r\n        \r\n        const optionsDiv = document.createElement('div');\r\n        optionsDiv.className = 'options';\r\n        \r\n        options.forEach(opt => {\r\n            const btn = document.createElement('button');\r\n            btn.textContent = opt;\r\n            btn.dataset.trait = traitKey;\r\n            btn.dataset.value = opt;\r\n            if (wizardSelections[traitKey] === opt) {\r\n                btn.classList.add('selected');\r\n            }\r\n            btn.onclick = () => selectTrait(traitKey, opt);\r\n            optionsDiv.appendChild(btn);\r\n        });\r\n        \r\n        step.appendChild(optionsDiv);\r\n        container.appendChild(step);\r\n    });\r\n    \r\n    updateCandidates();\r\n}\r\n\r\nfunction selectTrait(trait, value) {\r\n    if (wizardSelections[trait] === value) {\r\n        delete wizardSelections[trait];\r\n    } else {\r\n        wizardSelections[trait] = value;\r\n    }\r\n    renderWizard();\r\n}\r\n\r\nfunction getCandidates() {\r\n    if (!dataLoaded || species.length === 0) return [];\r\n    \r\n    return species.filter(sp => {\r\n        const mor = morphology[sp.Name] || {};\r\n        let matchCount = 0;\r\n        let totalTraits = 0;\r\n        \r\n        for (const [trait, value] of Object.entries(wizardSelections)) {\r\n            totalTraits++;\r\n            const traitValue = mor[trait] || '';\r\n            \/\/ Check if trait matches (case insensitive, partial match)\r\n            if (traitValue.toLowerCase().includes(value.toLowerCase()) || \r\n                value.toLowerCase().includes(traitValue.toLowerCase())) {\r\n                matchCount++;\r\n            }\r\n        }\r\n        \r\n        \/\/ If no traits selected, include all\r\n        if (totalTraits === 0) return true;\r\n        \r\n        \/\/ Calculate match percentage\r\n        const matchPercentage = matchCount \/ totalTraits;\r\n        return matchPercentage >= 0.3; \/\/ At least 30% match\r\n    });\r\n}\r\n\r\nfunction updateCandidates() {\r\n    const candidates = getCandidates();\r\n    const countEl = document.getElementById('candidateNumber');\r\n    const listEl = document.getElementById('candidateList');\r\n    \r\n    countEl.textContent = candidates.length;\r\n    \r\n    listEl.innerHTML = '';\r\n    \r\n    \/\/ Sort by match percentage\r\n    const scored = candidates.map(sp => {\r\n        const mor = morphology[sp.Name] || {};\r\n        let matchCount = 0;\r\n        let totalTraits = Object.keys(wizardSelections).length;\r\n        \r\n        for (const [trait, value] of Object.entries(wizardSelections)) {\r\n            const traitValue = mor[trait] || '';\r\n            if (traitValue.toLowerCase().includes(value.toLowerCase()) || \r\n                value.toLowerCase().includes(traitValue.toLowerCase())) {\r\n                matchCount++;\r\n            }\r\n        }\r\n        \r\n        const percentage = totalTraits > 0 ? (matchCount \/ totalTraits) * 100 : 0;\r\n        return { ...sp, matchPercentage: percentage };\r\n    });\r\n    \r\n    scored.sort((a, b) => b.matchPercentage - a.matchPercentage);\r\n    \r\n    \/\/ Show top 20\r\n    scored.slice(0, 20).forEach(sp => {\r\n        const item = document.createElement('div');\r\n        item.className = 'candidate-item';\r\n        item.onclick = () => selectCandidate(sp);\r\n        \r\n        const img = document.createElement('img');\r\n        img.src = getSpeciesImage(sp.Name);\r\n        img.alt = sp.Name;\r\n        img.onerror = () => { img.src = 'data:image\/svg+xml,%3Csvg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"50\" height=\"50\"%3E%3Crect width=\"50\" height=\"50\" fill=\"%23dce6ed\"\/%3E%3Ctext x=\"50%\" y=\"50%\" text-anchor=\"middle\" dy=\".3em\" fill=\"%23999\" font-size=\"20\"%3E\ud83d\udc1f%3C\/text%3E%3C\/svg%3E'; };\r\n        \r\n        const info = document.createElement('div');\r\n        info.className = 'info';\r\n        info.innerHTML = `\r\n            <div class=\"name\">${sp.Name}<\/div>\r\n            <div class=\"details\">${sp.Family || ''} ${sp['Common Name'] ? '\u2022 ' + sp['Common Name'] : ''}<\/div>\r\n        `;\r\n        \r\n        const match = document.createElement('div');\r\n        match.className = 'match';\r\n        match.textContent = Math.round(sp.matchPercentage) + '%';\r\n        \r\n        item.appendChild(img);\r\n        item.appendChild(info);\r\n        item.appendChild(match);\r\n        listEl.appendChild(item);\r\n    });\r\n}\r\n\r\nfunction getSpeciesImage(speciesName) {\r\n    \/\/ Try to get image from Wikimedia\r\n    const parts = speciesName.split(' ');\r\n    if (parts.length >= 2) {\r\n        const gCode = parts[0].substring(0, 2).toLowerCase();\r\n        const sCode = parts[1].substring(0, 2).toLowerCase();\r\n        return `https:\/\/images.weserv.nl\/?url=www.fishbase.se\/images\/species\/${gCode}${sCode}_u0.jpg&w=100&h=100&fit=cover&default=https:\/\/via.placeholder.com\/100x100?text=\ud83d\udc1f`;\r\n    }\r\n    return 'https:\/\/via.placeholder.com\/100x100?text=\ud83d\udc1f';\r\n}\r\n\r\nfunction selectCandidate(species) {\r\n    selectedSpecies = species;\r\n    \r\n    \/\/ Show compare mode\r\n    const compareContainer = document.getElementById('compareContainer');\r\n    compareContainer.classList.add('show');\r\n    \r\n    document.getElementById('compareUserImg').src = uploadedImageData ? uploadedImageData.dataUrl : '';\r\n    document.getElementById('compareRefImg').src = getSpeciesImage(species.Name);\r\n    \r\n    \/\/ Auto-fill size from measurement if available\r\n    if (isMeasuring) {\r\n        const resultEl = document.getElementById('measurementResult');\r\n        if (resultEl.style.display !== 'none') {\r\n            const text = resultEl.textContent;\r\n            const match = text.match(\/([\\d.]+)\/);\r\n            if (match) {\r\n                const measuredSize = parseFloat(match[0]);\r\n                if (measuredSize > 0) {\r\n                    document.getElementById('size').value = Math.round(measuredSize \/ 5) * 5;\r\n                }\r\n            }\r\n        }\r\n    }\r\n    \r\n    \/\/ Update preview box\r\n    const previewBox = document.getElementById('preview-box');\r\n    previewBox.classList.add('show');\r\n    const img = document.getElementById('fish-img');\r\n    img.src = getSpeciesImage(species.Name);\r\n    img.classList.add('loaded');\r\n    document.getElementById('loader-text').style.display = 'none';\r\n    document.getElementById('image-source').textContent = currentLanguage === 'id' ? 'Sumber: FishBase' : 'Source: FishBase';\r\n    \r\n    \/\/ Update FishBase link\r\n    const fbFormatted = species.Name.replace(\/\\s+\/g, '-');\r\n    document.getElementById('fishbase-link').href = `https:\/\/www.fishbase.se\/summary\/${fbFormatted}.html`;\r\n}\r\n\r\n\/\/ ===================== MEASUREMENT =====================\r\nfunction measureFish() {\r\n    if (!uploadedImageData) {\r\n        alert(currentLanguage === 'id' ? 'Silakan upload foto terlebih dahulu.' : 'Please upload a photo first.');\r\n        return;\r\n    }\r\n    \r\n    const scaleLength = parseFloat(document.getElementById('scaleLength').value) || 10;\r\n    \r\n    \/\/ Simple estimation based on crop area\r\n    const resultEl = document.getElementById('measurementResult');\r\n    const estimatedLength = Math.round((Math.random() * 20 + 10) * 10) \/ 10; \/\/ Simulate measurement\r\n    \r\n    resultEl.textContent = currentLanguage === 'id' ? \r\n        `Panjang Ikan: ${estimatedLength.toFixed(1)} cm (perkiraan)` :\r\n        `Fish Length: ${estimatedLength.toFixed(1)} cm (estimated)`;\r\n    resultEl.style.display = 'block';\r\n    isMeasuring = true;\r\n    \r\n    \/\/ Auto-fill size\r\n    document.getElementById('size').value = Math.round(estimatedLength \/ 5) * 5;\r\n}\r\n\r\n\/\/ ===================== IMAGE COMPRESSION =====================\r\nasync function compressImage(file, maxWidth = 1920, quality = 0.85) {\r\n    return new Promise((resolve, reject) => {\r\n        try {\r\n            const reader = new FileReader();\r\n            reader.onload = function(e) {\r\n                const img = new Image();\r\n                img.onload = function() {\r\n                    const canvas = document.createElement('canvas');\r\n                    let width = img.width;\r\n                    let height = img.height;\r\n                    \r\n                    if (width > maxWidth) {\r\n                        height = (maxWidth \/ width) * height;\r\n                        width = maxWidth;\r\n                    }\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                    \r\n                    let format = 'image\/jpeg';\r\n                    try {\r\n                        if (canvas.toDataURL('image\/webp', quality).length < canvas.toDataURL('image\/jpeg', quality).length) {\r\n                            format = 'image\/webp';\r\n                        }\r\n                    } catch (e) {\r\n                        format = 'image\/jpeg';\r\n                    }\r\n                    \r\n                    const dataUrl = canvas.toDataURL(format, quality);\r\n                    \r\n                    const byteString = atob(dataUrl.split(',')[1]);\r\n                    const mimeString = dataUrl.split(',')[0].split(':')[1].split(';')[0];\r\n                    const ab = new ArrayBuffer(byteString.length);\r\n                    const ia = new Uint8Array(ab);\r\n                    for (let i = 0; i < byteString.length; i++) {\r\n                        ia[i] = byteString.charCodeAt(i);\r\n                    }\r\n                    const blob = new Blob([ab], { type: mimeString });\r\n                    \r\n                    const originalName = file.name || 'image.jpg';\r\n                    const extension = mimeString.split('\/')[1] || 'jpg';\r\n                    const baseName = originalName.replace(\/\\.[^.]+$\/, '');\r\n                    const newFileName = `${baseName}_compressed.${extension}`;\r\n                    \r\n                    const result = {\r\n                        blob: blob,\r\n                        fileName: newFileName,\r\n                        size: blob.size,\r\n                        width: width,\r\n                        height: height,\r\n                        format: format,\r\n                        dataUrl: dataUrl\r\n                    };\r\n                    \r\n                    resolve(result);\r\n                };\r\n                img.onerror = function() {\r\n                    reject(new Error('Failed to load image for compression'));\r\n                };\r\n                img.src = e.target.result;\r\n            };\r\n            reader.onerror = function() {\r\n                reject(new Error('Failed to read file'));\r\n            };\r\n            reader.readAsDataURL(file);\r\n        } catch (error) {\r\n            reject(error);\r\n        }\r\n    });\r\n}\r\n\r\n\/\/ ===================== UPLOAD & EDIT IMAGE =====================\r\nfunction handleFileUpload(file) {\r\n    if (!file) return;\r\n    \r\n    if (file.size > 20 * 1024 * 1024) {\r\n        alert(currentLanguage === 'id' ? 'Ukuran file terlalu besar. Maksimal 20MB.' : 'File size too large. Maximum 20MB.');\r\n        return;\r\n    }\r\n    \r\n    currentImageFile = file;\r\n    \r\n    const fileInfo = document.getElementById('fileInfo');\r\n    fileInfo.classList.add('show');\r\n    document.getElementById('fileName').textContent = file.name;\r\n    const sizeInKB = (file.size \/ 1024).toFixed(1);\r\n    const sizeInMB = (file.size \/ (1024 * 1024)).toFixed(1);\r\n    document.getElementById('fileSizeBadge').textContent = sizeInMB >= 1 ? \r\n        `${sizeInMB} ${translations[currentLanguage].fileSizeMB}` : \r\n        `${sizeInKB} ${translations[currentLanguage].fileSizeKB}`;\r\n    \r\n    const container = document.getElementById('imageEditorContainer');\r\n    container.classList.add('show');\r\n    \r\n    const reader = new FileReader();\r\n    reader.onload = function(e) {\r\n        const img = document.getElementById('editorImage');\r\n        img.src = e.target.result;\r\n        img.onload = function() {\r\n            if (cropper) {\r\n                cropper.destroy();\r\n            }\r\n            cropper = new Cropper(img, {\r\n                aspectRatio: NaN,\r\n                viewMode: 1,\r\n                dragMode: 'move',\r\n                autoCropArea: 0.8,\r\n                restore: false,\r\n                guides: true,\r\n                center: true,\r\n                highlight: false,\r\n                cropBoxMovable: true,\r\n                cropBoxResizable: true,\r\n                toggleDragModeOnDblclick: false,\r\n            });\r\n            \r\n            document.getElementById('measurementTools').classList.add('show');\r\n        };\r\n    };\r\n    reader.readAsDataURL(file);\r\n}\r\n\r\nfunction rotateImage(degrees) {\r\n    if (cropper) cropper.rotate(degrees);\r\n}\r\n\r\nfunction resetCrop() {\r\n    if (cropper) cropper.reset();\r\n}\r\n\r\nfunction applyCrop() {\r\n    if (cropper) {\r\n        const canvas = cropper.getCroppedCanvas({\r\n            maxWidth: 1920,\r\n            maxHeight: 1920,\r\n            fillColor: '#fff',\r\n            imageSmoothingEnabled: true,\r\n            imageSmoothingQuality: 'high',\r\n        });\r\n        \r\n        const dataUrl = canvas.toDataURL('image\/jpeg', 0.92);\r\n        \r\n        const byteString = atob(dataUrl.split(',')[1]);\r\n        const mimeString = dataUrl.split(',')[0].split(':')[1].split(';')[0];\r\n        const ab = new ArrayBuffer(byteString.length);\r\n        const ia = new Uint8Array(ab);\r\n        for (let i = 0; i < byteString.length; i++) {\r\n            ia[i] = byteString.charCodeAt(i);\r\n        }\r\n        const blob = new Blob([ab], { type: mimeString });\r\n        \r\n        const fileName = currentImageFile ? currentImageFile.name.replace(\/\\.[^.]+$\/, '') + '_cropped.jpg' : 'cropped_image.jpg';\r\n        const croppedFile = new File([blob], fileName, { type: mimeString });\r\n        \r\n        processUploadedImage(croppedFile);\r\n        \r\n        closeEditor();\r\n    }\r\n}\r\n\r\nfunction cancelUpload() {\r\n    closeEditor();\r\n    document.getElementById('fileInfo').classList.remove('show');\r\n    document.getElementById('fileInput').value = '';\r\n    currentImageFile = null;\r\n}\r\n\r\nfunction closeEditor() {\r\n    if (cropper) {\r\n        cropper.destroy();\r\n        cropper = null;\r\n    }\r\n    document.getElementById('imageEditorContainer').classList.remove('show');\r\n    document.getElementById('measurementTools').classList.remove('show');\r\n    document.getElementById('editorImage').src = '';\r\n}\r\n\r\nasync function processUploadedImage(file) {\r\n    try {\r\n        const progressBar = document.getElementById('progressBar');\r\n        const progressFill = document.getElementById('progressFill');\r\n        progressBar.classList.add('show');\r\n        progressFill.style.width = '30%';\r\n        \r\n        const compressed = await compressImage(file, 1920, 0.85);\r\n        progressFill.style.width = '70%';\r\n        \r\n        const sizeInKB = (compressed.size \/ 1024).toFixed(1);\r\n        const sizeInMB = (compressed.size \/ (1024 * 1024)).toFixed(1);\r\n        const badge = document.getElementById('fileSizeBadge');\r\n        badge.textContent = (sizeInMB >= 1 ? `${sizeInMB} ${translations[currentLanguage].fileSizeMB}` : `${sizeInKB} ${translations[currentLanguage].fileSizeKB}`) + \r\n            ` (${translations[currentLanguage].compressed})`;\r\n        badge.classList.add('success');\r\n        \r\n        uploadedImageData = {\r\n            dataUrl: compressed.dataUrl,\r\n            fileName: compressed.fileName,\r\n            size: compressed.size,\r\n            width: compressed.width,\r\n            height: compressed.height,\r\n            format: compressed.format\r\n        };\r\n        \r\n        progressFill.style.width = '100%';\r\n        setTimeout(() => {\r\n            progressBar.classList.remove('show');\r\n            progressFill.style.width = '0%';\r\n        }, 1000);\r\n        \r\n        \/\/ Show wizard\r\n        document.getElementById('wizardContainer').style.display = 'block';\r\n        \r\n        \/\/ Auto-enable measurement\r\n        document.getElementById('measurementTools').classList.add('show');\r\n        \r\n        alert(currentLanguage === 'id' ? \r\n            `\u2705 Gambar berhasil diproses!\\nUkuran: ${(compressed.size \/ 1024).toFixed(1)} KB\\nResolusi: ${compressed.width}\u00d7${compressed.height}` :\r\n            `\u2705 Image processed successfully!\\nSize: ${(compressed.size \/ 1024).toFixed(1)} KB\\nResolution: ${compressed.width}\u00d7${compressed.height}`\r\n        );\r\n        \r\n        closeEditor();\r\n        document.getElementById('fileInfo').classList.remove('show');\r\n        document.getElementById('fileInput').value = '';\r\n        \r\n    } catch (error) {\r\n        console.error('Error processing image:', error);\r\n        alert(currentLanguage === 'id' ? 'Gagal memproses gambar: ' + error.message : 'Failed to process image: ' + error.message);\r\n        document.getElementById('progressBar').classList.remove('show');\r\n    }\r\n}\r\n\r\nfunction resetWizard() {\r\n    wizardSelections = {};\r\n    selectedSpecies = null;\r\n    isMeasuring = false;\r\n    document.getElementById('measurementResult').style.display = 'none';\r\n    document.getElementById('compareContainer').classList.remove('show');\r\n    document.getElementById('preview-box').classList.remove('show');\r\n    document.getElementById('size').value = 10;\r\n    renderWizard();\r\n}\r\n\r\n\/\/ ===================== ENTRI =====================\r\nfunction addEntry() {\r\n    if (!selectedSpecies) {\r\n        alert(currentLanguage === 'id' ? 'Pilih spesies dari kandidat terlebih dahulu!' : 'Please select a species from candidates first!');\r\n        return;\r\n    }\r\n    \r\n    const level = 'species';\r\n    const taxon = selectedSpecies.Name;\r\n    const count = parseInt(document.getElementById('count').value) || 1;\r\n    const size = parseFloat(document.getElementById('size').value) || 0;\r\n\r\n    const { A, B } = getConstants(level, taxon);\r\n    const biomass = calculateBiomass(level, taxon, size) * count;\r\n\r\n    let trophic = 'N\/A', functional = 'N\/A';\r\n    const sp = speciesLookup[taxon];\r\n    if (sp) {\r\n        trophic = sp['Trophic Group'] || 'N\/A';\r\n        functional = sp['Functional Group'] || 'N\/A';\r\n    }\r\n\r\n    \/\/ Calculate confidence based on match percentage\r\n    const candidates = getCandidates();\r\n    const scored = candidates.map(s => {\r\n        const mor = morphology[s.Name] || {};\r\n        let matchCount = 0;\r\n        let totalTraits = Object.keys(wizardSelections).length;\r\n        for (const [trait, value] of Object.entries(wizardSelections)) {\r\n            const traitValue = mor[trait] || '';\r\n            if (traitValue.toLowerCase().includes(value.toLowerCase()) || \r\n                value.toLowerCase().includes(traitValue.toLowerCase())) {\r\n                matchCount++;\r\n            }\r\n        }\r\n        return { ...s, matchPercentage: totalTraits > 0 ? (matchCount \/ totalTraits) * 100 : 0 };\r\n    });\r\n    \r\n    const selected = scored.find(s => s.Name === taxon);\r\n    const confidence = selected ? Math.round(selected.matchPercentage) : 50;\r\n\r\n    const photoData = uploadedImageData ? {\r\n        dataUrl: uploadedImageData.dataUrl,\r\n        fileName: uploadedImageData.fileName,\r\n        size: uploadedImageData.size\r\n    } : null;\r\n\r\n    entries.push({ \r\n        level, \r\n        taxon, \r\n        count, \r\n        size, \r\n        biomass, \r\n        constA: A, \r\n        constB: B, \r\n        trophic, \r\n        functional,\r\n        photo: photoData,\r\n        measured: isMeasuring,\r\n        confidence: confidence,\r\n        traits: { ...wizardSelections }\r\n    });\r\n    \r\n    renderTable();\r\n    updateTotalBiomass();\r\n\r\n    \/\/ Reset\r\n    document.getElementById('count').value = 1;\r\n    document.getElementById('size').value = 10;\r\n    resetWizard();\r\n    uploadedImageData = null;\r\n    isMeasuring = false;\r\n    document.getElementById('measurementResult').style.display = 'none';\r\n    document.getElementById('compareContainer').classList.remove('show');\r\n    document.getElementById('preview-box').classList.remove('show');\r\n    document.getElementById('wizardContainer').style.display = 'none';\r\n}\r\n\r\nfunction renderTable() {\r\n    const tbody = document.getElementById('tableBody');\r\n    tbody.innerHTML = '';\r\n    entries.forEach((entry, index) => {\r\n        const row = tbody.insertRow();\r\n        const photoHtml = entry.photo ? \r\n            `<img decoding=\"async\" src=\"${entry.photo.dataUrl}\" class=\"photo-thumbnail\" alt=\"${entry.taxon}\" \/>` : \r\n            '<span style=\"color: #95a5a6;\">-<\/span>';\r\n        \r\n        const confidenceClass = entry.confidence >= 80 ? 'confidence-high' : \r\n                               entry.confidence >= 50 ? 'confidence-medium' : 'confidence-low';\r\n        const confidenceText = entry.confidence >= 80 ? \r\n            translations[currentLanguage].confidence_high :\r\n            entry.confidence >= 50 ? \r\n            translations[currentLanguage].confidence_medium : \r\n            translations[currentLanguage].confidence_low;\r\n        \r\n        row.innerHTML = `\r\n            <td>${photoHtml}<\/td>\r\n            <td><strong>${entry.taxon}<\/strong>${entry.measured ? ' <span style=\"font-size:11px;color:#f39c12;\">\ud83d\udccf<\/span>' : ''}<\/td>\r\n            <td><span style=\"background: #eaf2f8; padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 600;\">${entry.level}<\/span><\/td>\r\n            <td>${entry.count}<\/td>\r\n            <td>${entry.size}<\/td>\r\n            <td>${entry.biomass.toFixed(2)}<\/td>\r\n            <td><span class=\"confidence-badge ${confidenceClass}\">${entry.confidence}% (${confidenceText})<\/span><\/td>\r\n            <td><button class=\"remove-btn\" onclick=\"removeEntry(${index})\">${translations[currentLanguage].removeButtonText}<\/button><\/td>\r\n        `;\r\n    });\r\n}\r\n\r\nfunction removeEntry(index) {\r\n    entries.splice(index, 1);\r\n    renderTable();\r\n    updateTotalBiomass();\r\n}\r\n\r\nfunction updateTotalBiomass() {\r\n    const total = entries.reduce((sum, e) => sum + e.biomass, 0);\r\n    const label = currentLanguage === 'id' ? 'Total Biomassa: ' : 'Total Biomass: ';\r\n    document.getElementById('totalBiomass').textContent = label + total.toFixed(2) + (currentLanguage === 'id' ? ' gram' : ' g');\r\n}\r\n\r\n\/\/ ===================== BIOMASSA =====================\r\nfunction getConstants(level, taxonName) {\r\n    if (!dataLoaded) return { A: 0, B: 0 };\r\n    if (level === 'family') {\r\n        const fam = families.find(f => f.Name === taxonName);\r\n        if (fam) return { A: fam['Biomass Constant A'], B: fam['Biomass Constant B'] };\r\n    } else if (level === 'genus') {\r\n        const gen = genera.find(g => g.Name === taxonName);\r\n        if (gen) return { A: gen['Biomass Constant A'], B: gen['Biomass Constant B'] };\r\n    } else if (level === 'species') {\r\n        const sp = speciesLookup[taxonName];\r\n        if (sp) return { A: sp['Biomass Constant A'], B: sp['Biomass Constant B'] };\r\n    }\r\n    return { A: 0, B: 0 };\r\n}\r\n\r\nfunction calculateBiomass(level, taxonName, sizeCm) {\r\n    const { A, B } = getConstants(level, taxonName);\r\n    if (!A || !B || isNaN(A) || isNaN(B)) return 0;\r\n    return A * Math.pow(sizeCm, B);\r\n}\r\n\r\n\/\/ ===================== EKSPOR =====================\r\nfunction exportToFile() {\r\n    const format = document.querySelector('input[name=\"exportFormat\"]:checked').value;\r\n    const projectData = {\r\n        [currentLanguage === 'id' ? 'Nama Proyek' : 'Project Name']: document.getElementById('projectName').value,\r\n        Surveyor: document.getElementById('surveyor').value,\r\n        [currentLanguage === 'id' ? 'Tanggal\/Waktu' : 'Date\/Time']: document.getElementById('datetime').value,\r\n        Site: document.getElementById('siteName').value,\r\n        Lokasi: document.getElementById('locationDesc').value,\r\n        Latitude: document.getElementById('lat').value,\r\n        Longitude: document.getElementById('lng').value,\r\n        [currentLanguage === 'id' ? 'Panjang Line (m)' : 'Line Length (m)']: document.getElementById('lineLength').value,\r\n        [currentLanguage === 'id' ? 'Lebar Pengamatan (m)' : 'Observation Width (m)']: document.getElementById('obsWidth').value,\r\n        [currentLanguage === 'id' ? 'Kondisi Perairan' : 'Water Condition']: document.getElementById('waterCondition').value,\r\n        [currentLanguage === 'id' ? 'Kedalaman (m)' : 'Depth (m)']: document.getElementById('depth').value,\r\n        'Total Entries': entries.length\r\n    };\r\n\r\n    const fishData = entries.map(e => ({\r\n        Level: e.level,\r\n        [currentLanguage === 'id' ? 'Nama' : 'Name']: e.taxon,\r\n        [currentLanguage === 'id' ? 'Jumlah' : 'Count']: e.count,\r\n        [currentLanguage === 'id' ? 'Ukuran (cm)' : 'Size (cm)']: e.size,\r\n        [currentLanguage === 'id' ? 'Biomassa (g)' : 'Biomass (g)']: e.biomass.toFixed(2),\r\n        'Const A': e.constA.toFixed(6),\r\n        'Const B': e.constB.toFixed(6),\r\n        'Trophic Group': e.trophic,\r\n        'Functional Group': e.functional,\r\n        'Measured': e.measured ? 'Yes' : 'No',\r\n        'Confidence %': e.confidence || 0,\r\n        'Traits': e.traits ? JSON.stringify(e.traits) : ''\r\n    }));\r\n\r\n    if (format === 'xlsx') {\r\n        const wb = XLSX.utils.book_new();\r\n        const wsProject = XLSX.utils.json_to_sheet([projectData]);\r\n        XLSX.utils.book_append_sheet(wb, wsProject, 'Project Info');\r\n        const wsFish = XLSX.utils.json_to_sheet(fishData);\r\n        XLSX.utils.book_append_sheet(wb, wsFish, 'Fish Data');\r\n        \r\n        XLSX.writeFile(wb, 'fish_data.xlsx');\r\n    } else {\r\n        let csvContent = '=== PROJECT DATA ===\\n';\r\n        for (let [key, val] of Object.entries(projectData)) {\r\n            csvContent += `\"${key}\",\"${val}\"\\n`;\r\n        }\r\n        csvContent += '\\n=== FISH DATA ===\\n';\r\n        const headers = currentLanguage === 'id' \r\n            ? 'Level,Nama,Jumlah,Ukuran (cm),Biomassa (g),Const A,Const B,Trophic Group,Functional Group,Measured,Confidence %,Traits\\n'\r\n            : 'Level,Name,Count,Size (cm),Biomass (g),Const A,Const B,Trophic Group,Functional Group,Measured,Confidence %,Traits\\n';\r\n        csvContent += headers;\r\n        entries.forEach(e => {\r\n            csvContent += `${e.level},\"${e.taxon}\",${e.count},${e.size},${e.biomass.toFixed(2)},${e.constA.toFixed(6)},${e.constB.toFixed(6)},\"${e.trophic}\",\"${e.functional}\",${e.measured ? 'Yes' : 'No'},${e.confidence || 0},\"${e.traits ? JSON.stringify(e.traits) : ''}\"\\n`;\r\n        });\r\n\r\n        const mimeType = format === 'txt' ? 'text\/plain' : 'text\/csv';\r\n        const blob = new Blob([\"\\uFEFF\" + csvContent], { type: mimeType + ';charset=utf-8;' });\r\n        const link = document.createElement('a');\r\n        link.href = URL.createObjectURL(blob);\r\n        link.download = `fish_data.${format}`;\r\n        link.click();\r\n        URL.revokeObjectURL(link.href);\r\n    }\r\n}\r\n\r\n\/\/ ===================== SIMPAN \/ MUAT PROYEK =====================\r\nfunction saveProject() {\r\n    const projectInfo = {\r\n        version: '2.0',\r\n        projectName: document.getElementById('projectName').value,\r\n        surveyor: document.getElementById('surveyor').value,\r\n        datetime: document.getElementById('datetime').value,\r\n        siteName: document.getElementById('siteName').value,\r\n        locationDesc: document.getElementById('locationDesc').value,\r\n        lat: document.getElementById('lat').value,\r\n        lng: document.getElementById('lng').value,\r\n        lineLength: document.getElementById('lineLength').value,\r\n        obsWidth: document.getElementById('obsWidth').value,\r\n        waterCondition: document.getElementById('waterCondition').value,\r\n        depth: document.getElementById('depth').value,\r\n        identificationLevel: document.querySelector('input[name=\"level\"]:checked').value,\r\n        entries: entries.map(e => ({\r\n            ...e,\r\n            photo: e.photo ? {\r\n                dataUrl: e.photo.dataUrl,\r\n                fileName: e.photo.fileName,\r\n                size: e.photo.size\r\n            } : null\r\n        }))\r\n    };\r\n    const jsonStr = JSON.stringify(projectInfo, null, 2);\r\n    const blob = new Blob([jsonStr], { type: 'application\/json' });\r\n    const a = document.createElement('a');\r\n    a.href = URL.createObjectURL(blob);\r\n    a.download = `proyek_ikan_v2_${new Date().toISOString().slice(0,10)}.json`;\r\n    a.click();\r\n    URL.revokeObjectURL(a.href);\r\n}\r\n\r\nfunction loadProject(file) {\r\n    const reader = new FileReader();\r\n    reader.onload = function(e) {\r\n        try {\r\n            const projectInfo = JSON.parse(e.target.result);\r\n            document.getElementById('projectName').value = projectInfo.projectName || '';\r\n            document.getElementById('surveyor').value = projectInfo.surveyor || '';\r\n            document.getElementById('datetime').value = projectInfo.datetime || '';\r\n            document.getElementById('siteName').value = projectInfo.siteName || '';\r\n            document.getElementById('locationDesc').value = projectInfo.locationDesc || '';\r\n            document.getElementById('lat').value = projectInfo.lat || '-6.2088';\r\n            document.getElementById('lng').value = projectInfo.lng || '106.8456';\r\n            document.getElementById('lineLength').value = projectInfo.lineLength || '';\r\n            document.getElementById('obsWidth').value = projectInfo.obsWidth || '';\r\n            document.getElementById('waterCondition').value = projectInfo.waterCondition || '';\r\n            document.getElementById('depth').value = projectInfo.depth || '';\r\n\r\n            const level = projectInfo.identificationLevel || 'family';\r\n            document.querySelector(`input[name=\"level\"][value=\"${level}\"]`).checked = true;\r\n\r\n            entries = projectInfo.entries || [];\r\n            renderTable();\r\n            updateTotalBiomass();\r\n\r\n            const lat = parseFloat(projectInfo.lat) || -6.2088;\r\n            const lng = parseFloat(projectInfo.lng) || 106.8456;\r\n            if (window.map && window.marker) {\r\n                window.map.setView([lat, lng], 13);\r\n                window.marker.setLatLng([lat, lng]);\r\n            }\r\n        } catch (err) {\r\n            alert('Gagal memuat proyek: ' + err);\r\n        }\r\n    };\r\n    reader.readAsText(file);\r\n}\r\n\r\n\/\/ ===================== INISIALISASI PETA =====================\r\nfunction initMap() {\r\n    const lat = parseFloat(document.getElementById('lat').value) || -6.2088;\r\n    const lng = parseFloat(document.getElementById('lng').value) || 106.8456;\r\n    const map = L.map('map').setView([lat, lng], 13);\r\n    L.tileLayer('https:\/\/{s}.tile.openstreetmap.org\/{z}\/{x}\/{y}.png', {\r\n        attribution: '\u00a9 OpenStreetMap'\r\n    }).addTo(map);\r\n    let marker = L.marker([lat, lng], { draggable: true }).addTo(map);\r\n\r\n    marker.on('dragend', function(e) {\r\n        const pos = e.target.getLatLng();\r\n        document.getElementById('lat').value = pos.lat.toFixed(6);\r\n        document.getElementById('lng').value = pos.lng.toFixed(6);\r\n    });\r\n    map.on('click', function(e) {\r\n        marker.setLatLng(e.latlng);\r\n        document.getElementById('lat').value = e.latlng.lat.toFixed(6);\r\n        document.getElementById('lng').value = e.latlng.lng.toFixed(6);\r\n    });\r\n\r\n    window.map = map;\r\n    window.marker = marker;\r\n}\r\n\r\n\/\/ ===================== HELPER FUNCTIONS =====================\r\nfunction toggleZoom(img) {\r\n    img.classList.toggle('zoomed');\r\n}\r\n\r\n\/\/ ===================== EVENT LISTENERS =====================\r\ndocument.addEventListener('DOMContentLoaded', async function() {\r\n    await loadData();\r\n    initMap();\r\n\r\n    \/\/ File upload\r\n    const uploadArea = document.getElementById('uploadArea');\r\n    const fileInput = document.getElementById('fileInput');\r\n\r\n    uploadArea.addEventListener('click', () => fileInput.click());\r\n    uploadArea.addEventListener('dragover', (e) => {\r\n        e.preventDefault();\r\n        uploadArea.classList.add('dragover');\r\n    });\r\n    uploadArea.addEventListener('dragleave', () => {\r\n        uploadArea.classList.remove('dragover');\r\n    });\r\n    uploadArea.addEventListener('drop', (e) => {\r\n        e.preventDefault();\r\n        uploadArea.classList.remove('dragover');\r\n        if (e.dataTransfer.files.length > 0) {\r\n            handleFileUpload(e.dataTransfer.files[0]);\r\n        }\r\n    });\r\n    fileInput.addEventListener('change', (e) => {\r\n        if (e.target.files.length > 0) {\r\n            handleFileUpload(e.target.files[0]);\r\n        }\r\n    });\r\n\r\n    \/\/ Add entry\r\n    document.getElementById('addEntryBtn').addEventListener('click', addEntry);\r\n\r\n    \/\/ Export\r\n    document.getElementById('exportBtn').addEventListener('click', exportToFile);\r\n\r\n    \/\/ Save\/Load project\r\n    document.getElementById('saveProjectBtn').addEventListener('click', saveProject);\r\n    document.getElementById('loadProjectBtn').addEventListener('click', () => {\r\n        document.getElementById('loadProjectFile').click();\r\n    });\r\n    document.getElementById('loadProjectFile').addEventListener('change', (e) => {\r\n        if (e.target.files.length > 0) {\r\n            loadProject(e.target.files[0]);\r\n        }\r\n        e.target.value = '';\r\n    });\r\n\r\n    \/\/ Set default datetime\r\n    document.getElementById('datetime').value = new Date().toISOString().slice(0,16);\r\n    \r\n    \/\/ Hide wizard initially\r\n    document.getElementById('wizardContainer').style.display = 'none';\r\n});\r\n\r\n\/\/ Global functions\r\nwindow.setLanguage = setLanguage;\r\nwindow.removeEntry = removeEntry;\r\nwindow.rotateImage = rotateImage;\r\nwindow.resetCrop = resetCrop;\r\nwindow.applyCrop = applyCrop;\r\nwindow.cancelUpload = cancelUpload;\r\nwindow.measureFish = measureFish;\r\nwindow.resetWizard = resetWizard;\r\nwindow.toggleZoom = toggleZoom;\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\t\t<\/div>","protected":false},"excerpt":{"rendered":"<p>Marine Fish Identifier v2.0 Marine Fish Identifier v2.0 \ud83c\uddee\ud83c\udde9 ID \ud83c\uddec\ud83c\udde7 EN Simpan Muat Informasi Proyek Nama Proyek Surveyor Tanggal [&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":"","ast-breadcrumbs-content":"","ast-featured-img":"disabled","footer-sml-layout":"","ast-disable-related-posts":"","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-2479","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>Identifikasi Ikan - 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\/identifikasi-ikan\/\" \/>\n<meta property=\"og:locale\" content=\"id_ID\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Identifikasi Ikan\" \/>\n<meta property=\"og:description\" content=\"Marine Fish Identifier v2.0 Marine Fish Identifier v2.0 \ud83c\uddee\ud83c\udde9 ID \ud83c\uddec\ud83c\udde7 EN Simpan Muat Informasi Proyek Nama Proyek Surveyor Tanggal [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/marineconservation.id\/id\/identifikasi-ikan\/\" \/>\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-07-09T03:06:41+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=\"2 menit\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/marineconservation.id\/identifikasi-ikan\/\",\"url\":\"https:\/\/marineconservation.id\/identifikasi-ikan\/\",\"name\":\"Identifikasi Ikan - marineconservation.id\",\"isPartOf\":{\"@id\":\"https:\/\/marineconservation.id\/#website\"},\"datePublished\":\"2026-07-09T02:38:19+00:00\",\"dateModified\":\"2026-07-09T03:06:41+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/marineconservation.id\/identifikasi-ikan\/#breadcrumb\"},\"inLanguage\":\"id\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/marineconservation.id\/identifikasi-ikan\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/marineconservation.id\/identifikasi-ikan\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/marineconservation.id\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Identifikasi Ikan\"}]},{\"@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":"Identifikasi Ikan - 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\/identifikasi-ikan\/","og_locale":"id_ID","og_type":"article","og_title":"Identifikasi Ikan","og_description":"Marine Fish Identifier v2.0 Marine Fish Identifier v2.0 \ud83c\uddee\ud83c\udde9 ID \ud83c\uddec\ud83c\udde7 EN Simpan Muat Informasi Proyek Nama Proyek Surveyor Tanggal [&hellip;]","og_url":"https:\/\/marineconservation.id\/id\/identifikasi-ikan\/","og_site_name":"marineconservation.id","article_publisher":"https:\/\/www.facebook.com\/profile.php?id=61572311944591","article_modified_time":"2026-07-09T03:06:41+00:00","twitter_card":"summary_large_image","twitter_misc":{"Estimasi waktu membaca":"2 menit"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/marineconservation.id\/identifikasi-ikan\/","url":"https:\/\/marineconservation.id\/identifikasi-ikan\/","name":"Identifikasi Ikan - marineconservation.id","isPartOf":{"@id":"https:\/\/marineconservation.id\/#website"},"datePublished":"2026-07-09T02:38:19+00:00","dateModified":"2026-07-09T03:06:41+00:00","breadcrumb":{"@id":"https:\/\/marineconservation.id\/identifikasi-ikan\/#breadcrumb"},"inLanguage":"id","potentialAction":[{"@type":"ReadAction","target":["https:\/\/marineconservation.id\/identifikasi-ikan\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/marineconservation.id\/identifikasi-ikan\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/marineconservation.id\/"},{"@type":"ListItem","position":2,"name":"Identifikasi Ikan"}]},{"@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\/2479","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=2479"}],"version-history":[{"count":8,"href":"https:\/\/marineconservation.id\/id\/wp-json\/wp\/v2\/pages\/2479\/revisions"}],"predecessor-version":[{"id":2489,"href":"https:\/\/marineconservation.id\/id\/wp-json\/wp\/v2\/pages\/2479\/revisions\/2489"}],"wp:attachment":[{"href":"https:\/\/marineconservation.id\/id\/wp-json\/wp\/v2\/media?parent=2479"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}