{"id":821,"date":"2026-09-19T13:49:29","date_gmt":"2026-09-19T06:49:29","guid":{"rendered":"https:\/\/bumdeskamajaya.my\/?page_id=821"},"modified":"2026-09-24T15:35:56","modified_gmt":"2026-09-24T08:35:56","slug":"preview-keramik","status":"publish","type":"page","link":"http:\/\/centralkeramik.com\/preview-keramik\/","title":{"rendered":"preview keramik"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"821\" class=\"elementor elementor-821\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7344c19 e-flex e-con-boxed e-con e-parent\" data-id=\"7344c19\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-561aeb9 elementor-widget elementor-widget-shortcode\" data-id=\"561aeb9\" data-element_type=\"widget\" data-widget_type=\"shortcode.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-shortcode\">\n    <div id=\"kmj-tv8\">\n\n        <div class=\"kmj-tv8-header\">\n            <div>\n                <strong>Tile Visualizer<\/strong>\n                <small>Preview keramik & granite<\/small>\n            <\/div>\n\n            <div class=\"kmj-tv8-actions\">\n                <button type=\"button\" id=\"kmj-tv8-reset\">Reset<\/button>\n                <button type=\"button\" id=\"kmj-tv8-full\">Fullscreen<\/button>\n                <button type=\"button\" id=\"kmj-tv8-download\">Download HD<\/button>\n            <\/div>\n        <\/div>\n\n        <div class=\"kmj-tv8-tabs\">\n            <button type=\"button\" id=\"kmj-tv8-floor-tab\" class=\"active\">\n                LANTAI\n            <\/button>\n\n            <button type=\"button\" id=\"kmj-tv8-wall-tab\">\n                DINDING\n            <\/button>\n        <\/div>\n\n        <div class=\"kmj-tv8-layout\">\n\n            <div class=\"kmj-tv8-view\">\n\n                <div id=\"kmj-tv8-stage\">\n\n                    <div id=\"kmj-tv8-loading\">\n                        Membuat ruangan...\n                    <\/div>\n\n                <\/div>\n\n                <div class=\"kmj-tv8-help\">\n                    <span>\ud83d\uddb1 Drag = Putar<\/span>\n                    <span>\ud83e\udd0f 2 Jari = Zoom<\/span>\n                    <span>Shift + Drag = Geser<\/span>\n                <\/div>\n\n            <\/div>\n\n            <div class=\"kmj-tv8-panel\">\n\n                <div class=\"kmj-tv8-light-box\">\n\n                    <div class=\"kmj-tv8-heading\">\n                        Pencahayaan Ruangan\n                    <\/div>\n\n                    <label class=\"kmj-tv8-control\">\n\n                        <span>\n                            <b>Kecerahan Ruangan<\/b>\n                            <strong id=\"kmj-tv8-light-value\">100%<\/strong>\n                        <\/span>\n\n                        <input\n                            id=\"kmj-tv8-light\"\n                            type=\"range\"\n                            min=\"20\"\n                            max=\"180\"\n                            value=\"100\"\n                        >\n\n                    <\/label>\n\n                    <small>\n                        Mengatur terang \/ gelap seluruh lampu ruangan.\n                    <\/small>\n\n                <\/div>\n\n\n                <!-- FLOOR -->\n\n                <div\n                    id=\"kmj-tv8-floor-panel\"\n                    class=\"kmj-tv8-panel-content active\"\n                >\n\n                    <div class=\"kmj-tv8-heading\">\n                        Keramik Lantai\n                    <\/div>\n\n                    <input\n                        id=\"kmj-tv8-floor-search\"\n                        class=\"kmj-tv8-search\"\n                        type=\"text\"\n                        placeholder=\"Cari keramik \/ granite...\"\n                    >\n\n                    <div\n                        id=\"kmj-tv8-floor-products\"\n                        class=\"kmj-tv8-products\"\n                    >\n                        Memuat produk...\n                    <\/div>\n\n                    <div class=\"kmj-tv8-selected\">\n\n                        <small>\n                            Keramik lantai terpilih\n                        <\/small>\n\n                        <div id=\"kmj-tv8-floor-selected\">\n                            Belum memilih produk\n                        <\/div>\n\n                    <\/div>\n\n                    <div class=\"kmj-tv8-settings\">\n\n                        <div class=\"kmj-tv8-heading\">\n                            Pengaturan Lantai\n                        <\/div>\n\n                        <label class=\"kmj-tv8-control\">\n                            <span>\n                                <b>Ukuran Pola<\/b>\n                                <strong id=\"kmj-tv8-floor-scale-value\">100%<\/strong>\n                            <\/span>\n\n                            <input\n                                id=\"kmj-tv8-floor-scale\"\n                                type=\"range\"\n                                min=\"50\"\n                                max=\"200\"\n                                value=\"100\"\n                            >\n                        <\/label>\n\n                        <label class=\"kmj-tv8-control\">\n                            <span>\n                                <b>Nat<\/b>\n                                <strong id=\"kmj-tv8-floor-grout-value\">2 mm<\/strong>\n                            <\/span>\n\n                            <input\n                                id=\"kmj-tv8-floor-grout\"\n                                type=\"range\"\n                                min=\"0\"\n                                max=\"8\"\n                                value=\"2\"\n                                step=\"0.5\"\n                            >\n                        <\/label>\n\n                        <label class=\"kmj-tv8-control\">\n                            <span>\n                                <b>Warna Nat<\/b>\n                            <\/span>\n\n                            <input\n                                id=\"kmj-tv8-floor-grout-color\"\n                                type=\"color\"\n                                value=\"#cccccc\"\n                            >\n                        <\/label>\n\n                        <label class=\"kmj-tv8-check\">\n                            <input\n                                id=\"kmj-tv8-floor-gloss\"\n                                type=\"checkbox\"\n                                checked\n                            >\n                            Efek glossy\n                        <\/label>\n\n                        <label class=\"kmj-tv8-control\">\n                            <span>\n                                <b>Refleksi<\/b>\n                                <strong id=\"kmj-tv8-floor-reflection-value\">20%<\/strong>\n                            <\/span>\n\n                            <input\n                                id=\"kmj-tv8-floor-reflection\"\n                                type=\"range\"\n                                min=\"0\"\n                                max=\"100\"\n                                value=\"20\"\n                            >\n                        <\/label>\n\n                        <label class=\"kmj-tv8-control\">\n                            <span>\n                                <b>Rotasi<\/b>\n                                <strong id=\"kmj-tv8-floor-rotation-value\">0\u00b0<\/strong>\n                            <\/span>\n\n                            <input\n                                id=\"kmj-tv8-floor-rotation\"\n                                type=\"range\"\n                                min=\"0\"\n                                max=\"270\"\n                                step=\"90\"\n                                value=\"0\"\n                            >\n                        <\/label>\n\n                    <\/div>\n\n                <\/div>\n\n\n                <!-- WALL -->\n\n                <div\n                    id=\"kmj-tv8-wall-panel\"\n                    class=\"kmj-tv8-panel-content\"\n                >\n\n                    <div class=\"kmj-tv8-heading\">\n                        Keramik Dinding\n                    <\/div>\n\n                    <input\n                        id=\"kmj-tv8-wall-search\"\n                        class=\"kmj-tv8-search\"\n                        type=\"text\"\n                        placeholder=\"Cari keramik \/ granite...\"\n                    >\n\n                    <div\n                        id=\"kmj-tv8-wall-products\"\n                        class=\"kmj-tv8-products\"\n                    >\n                        Memuat produk...\n                    <\/div>\n\n                    <div class=\"kmj-tv8-selected\">\n\n                        <small>\n                            Keramik dinding terpilih\n                        <\/small>\n\n                        <div id=\"kmj-tv8-wall-selected\">\n                            Belum memilih produk\n                        <\/div>\n\n                    <\/div>\n\n                    <div class=\"kmj-tv8-settings\">\n\n                        <div class=\"kmj-tv8-heading\">\n                            Pengaturan Dinding\n                        <\/div>\n\n                        <label class=\"kmj-tv8-control\">\n                            <span>\n                                <b>Ukuran Pola<\/b>\n                                <strong id=\"kmj-tv8-wall-scale-value\">100%<\/strong>\n                            <\/span>\n\n                            <input\n                                id=\"kmj-tv8-wall-scale\"\n                                type=\"range\"\n                                min=\"50\"\n                                max=\"200\"\n                                value=\"100\"\n                            >\n                        <\/label>\n\n                        <label class=\"kmj-tv8-control\">\n                            <span>\n                                <b>Nat<\/b>\n                                <strong id=\"kmj-tv8-wall-grout-value\">2 mm<\/strong>\n                            <\/span>\n\n                            <input\n                                id=\"kmj-tv8-wall-grout\"\n                                type=\"range\"\n                                min=\"0\"\n                                max=\"8\"\n                                value=\"2\"\n                                step=\"0.5\"\n                            >\n                        <\/label>\n\n                        <label class=\"kmj-tv8-control\">\n                            <span>\n                                <b>Warna Nat<\/b>\n                            <\/span>\n\n                            <input\n                                id=\"kmj-tv8-wall-grout-color\"\n                                type=\"color\"\n                                value=\"#cccccc\"\n                            >\n                        <\/label>\n\n                        <label class=\"kmj-tv8-check\">\n                            <input\n                                id=\"kmj-tv8-wall-gloss\"\n                                type=\"checkbox\"\n                                checked\n                            >\n                            Efek glossy\n                        <\/label>\n\n                        <label class=\"kmj-tv8-control\">\n                            <span>\n                                <b>Refleksi<\/b>\n                                <strong id=\"kmj-tv8-wall-reflection-value\">20%<\/strong>\n                            <\/span>\n\n                            <input\n                                id=\"kmj-tv8-wall-reflection\"\n                                type=\"range\"\n                                min=\"0\"\n                                max=\"100\"\n                                value=\"20\"\n                            >\n                        <\/label>\n\n                        <label class=\"kmj-tv8-control\">\n                            <span>\n                                <b>Rotasi<\/b>\n                                <strong id=\"kmj-tv8-wall-rotation-value\">0\u00b0<\/strong>\n                            <\/span>\n\n                            <input\n                                id=\"kmj-tv8-wall-rotation\"\n                                type=\"range\"\n                                min=\"0\"\n                                max=\"270\"\n                                step=\"90\"\n                                value=\"0\"\n                            >\n                        <\/label>\n\n                    <\/div>\n\n                <\/div>\n\n            <\/div>\n\n        <\/div>\n\n    <\/div>\n\n\n    <style>\n\n    #kmj-tv8{\n        width:100%;\n        max-width:1500px;\n        margin:25px auto;\n        font-family:Arial,sans-serif;\n        color:#222;\n        box-sizing:border-box;\n    }\n\n    #kmj-tv8 *,\n    #kmj-tv8 *:before,\n    #kmj-tv8 *:after{\n        box-sizing:border-box;\n    }\n\n    .kmj-tv8-header{\n        display:flex;\n        justify-content:space-between;\n        align-items:center;\n        gap:15px;\n        margin-bottom:12px;\n    }\n\n    .kmj-tv8-header strong{\n        display:block;\n        font-size:24px;\n    }\n\n    .kmj-tv8-header small{\n        color:#777;\n    }\n\n    .kmj-tv8-actions{\n        display:flex;\n        gap:7px;\n        flex-wrap:wrap;\n    }\n\n    .kmj-tv8-actions button,\n    .kmj-tv8-tabs button{\n        border:0;\n        padding:10px 14px;\n        border-radius:7px;\n        cursor:pointer;\n        background:#222;\n        color:#fff;\n        touch-action:manipulation;\n    }\n\n    .kmj-tv8-tabs{\n        display:flex;\n        gap:8px;\n        margin-bottom:12px;\n    }\n\n    .kmj-tv8-tabs button{\n        min-width:110px;\n        background:#ddd;\n        color:#222;\n    }\n\n    .kmj-tv8-tabs button.active{\n        background:#111;\n        color:#fff;\n    }\n\n    .kmj-tv8-layout{\n        display:grid;\n        grid-template-columns:minmax(0,1fr) 360px;\n        gap:15px;\n    }\n\n    #kmj-tv8-stage{\n        width:100%;\n        height:650px;\n        background:#d8d8d8;\n        border-radius:12px;\n        overflow:hidden;\n        position:relative;\n        box-shadow:0 8px 30px rgba(0,0,0,.12);\n\n        \/*\n         * PENTING UNTUK HP\n         * Browser tidak mengambil alih\n         * gesture touch.\n         *\/\n        touch-action:none;\n        user-select:none;\n        -webkit-user-select:none;\n        -webkit-touch-callout:none;\n    }\n\n    #kmj-tv8-stage canvas{\n        display:block;\n        width:100%!important;\n        height:100%!important;\n        touch-action:none;\n    }\n\n    #kmj-tv8-loading{\n        position:absolute;\n        inset:0;\n        z-index:10;\n        display:flex;\n        align-items:center;\n        justify-content:center;\n        background:#eee;\n        color:#555;\n    }\n\n    .kmj-tv8-help{\n        text-align:center;\n        margin-top:8px;\n        color:#777;\n        font-size:11px;\n        display:flex;\n        gap:15px;\n        justify-content:center;\n        flex-wrap:wrap;\n    }\n\n    .kmj-tv8-panel{\n        background:#fff;\n        border:1px solid #e4e4e4;\n        border-radius:12px;\n        padding:15px;\n        max-height:650px;\n        overflow-y:auto;\n        -webkit-overflow-scrolling:touch;\n    }\n\n    .kmj-tv8-light-box{\n        padding:13px;\n        background:#f5f7f9;\n        border:1px solid #e3e6e8;\n        border-radius:10px;\n        margin-bottom:15px;\n    }\n\n    .kmj-tv8-heading{\n        font-weight:700;\n        font-size:14px;\n        margin-bottom:10px;\n    }\n\n    .kmj-tv8-panel-content{\n        display:none;\n    }\n\n    .kmj-tv8-panel-content.active{\n        display:block;\n    }\n\n    .kmj-tv8-search{\n        width:100%;\n        padding:11px;\n        border:1px solid #ddd;\n        border-radius:7px;\n        margin-bottom:10px;\n        outline:none;\n        font-size:14px;\n    }\n\n    .kmj-tv8-products{\n        display:grid;\n        grid-template-columns:1fr 1fr;\n        gap:8px;\n        max-height:270px;\n        overflow-y:auto;\n        margin-bottom:12px;\n        -webkit-overflow-scrolling:touch;\n    }\n\n    .kmj-tv8-product{\n        border:1px solid #ddd;\n        border-radius:8px;\n        padding:7px;\n        cursor:pointer;\n        background:#fff;\n    }\n\n    .kmj-tv8-product:hover{\n        border-color:#777;\n    }\n\n    .kmj-tv8-product.active{\n        border:2px solid #111;\n        padding:6px;\n    }\n\n    .kmj-tv8-product img{\n        width:100%;\n        height:95px;\n        object-fit:cover;\n        display:block;\n        border-radius:6px;\n        background:#eee;\n    }\n\n    .kmj-tv8-product-name{\n        font-size:11px;\n        font-weight:600;\n        margin-top:6px;\n        line-height:1.3;\n    }\n\n    .kmj-tv8-product-size{\n        font-size:10px;\n        color:#777;\n        margin-top:3px;\n    }\n\n    .kmj-tv8-selected{\n        background:#fafafa;\n        border:1px solid #e5e5e5;\n        border-radius:8px;\n        padding:10px;\n        margin-bottom:15px;\n        font-size:12px;\n    }\n\n    .kmj-tv8-selected small{\n        display:block;\n        color:#777;\n        margin-bottom:4px;\n    }\n\n    .kmj-tv8-settings{\n        border-top:1px solid #eee;\n        padding-top:14px;\n    }\n\n    .kmj-tv8-control{\n        display:block;\n        margin-bottom:15px;\n    }\n\n    .kmj-tv8-control span{\n        display:flex;\n        justify-content:space-between;\n        font-size:12px;\n        margin-bottom:7px;\n    }\n\n    .kmj-tv8-control strong{\n        color:#777;\n    }\n\n    .kmj-tv8-control input[type=\"range\"]{\n        width:100%;\n        height:28px;\n        cursor:pointer;\n    }\n\n    .kmj-tv8-control input[type=\"color\"]{\n        width:100%;\n        height:36px;\n        padding:2px;\n    }\n\n    .kmj-tv8-check{\n        display:flex;\n        gap:8px;\n        align-items:center;\n        font-size:12px;\n        margin-bottom:15px;\n    }\n\n    #kmj-tv8.kmj-tv8-fullscreen{\n        position:fixed;\n        inset:0;\n        z-index:999999;\n        background:#fff;\n        padding:12px;\n        max-width:none;\n        margin:0;\n    }\n\n    #kmj-tv8.kmj-tv8-fullscreen #kmj-tv8-stage{\n        height:calc(100vh - 160px);\n    }\n\n\n    \/*\n     * =========================================\n     * TABLET\n     * =========================================\n     *\/\n\n    @media(max-width:900px){\n\n        .kmj-tv8-layout{\n            grid-template-columns:1fr;\n        }\n\n        .kmj-tv8-panel{\n            max-height:none;\n        }\n\n        #kmj-tv8-stage{\n            height:520px;\n        }\n\n    }\n\n\n    \/*\n     * =========================================\n     * HP\n     * =========================================\n     *\/\n\n    @media(max-width:600px){\n\n        #kmj-tv8{\n            width:100%;\n            max-width:100%;\n            margin:0;\n            padding:8px;\n        }\n\n        .kmj-tv8-header{\n            flex-direction:column;\n            align-items:flex-start;\n            gap:8px;\n        }\n\n        .kmj-tv8-header strong{\n            font-size:20px;\n        }\n\n        .kmj-tv8-actions{\n            display:grid;\n            grid-template-columns:\n                repeat(3,1fr);\n            width:100%;\n            gap:5px;\n        }\n\n        .kmj-tv8-actions button{\n            width:100%;\n            min-width:0;\n            padding:9px 4px;\n            font-size:11px;\n        }\n\n        .kmj-tv8-tabs{\n            width:100%;\n            margin-bottom:8px;\n        }\n\n        .kmj-tv8-tabs button{\n            flex:1;\n            min-width:0;\n            padding:9px 8px;\n            font-size:12px;\n        }\n\n        .kmj-tv8-layout{\n            display:flex;\n            flex-direction:column;\n            gap:10px;\n        }\n\n        .kmj-tv8-view{\n            width:100%;\n        }\n\n        #kmj-tv8-stage{\n            width:100%;\n            height:58vh;\n            min-height:320px;\n            max-height:520px;\n            border-radius:10px;\n        }\n\n        .kmj-tv8-help{\n            font-size:10px;\n            gap:8px;\n            line-height:1.5;\n        }\n\n        .kmj-tv8-panel{\n            width:100%;\n            padding:10px;\n            border-radius:10px;\n            max-height:none;\n        }\n\n        .kmj-tv8-products{\n            grid-template-columns:\n                repeat(2,1fr);\n            max-height:240px;\n        }\n\n        .kmj-tv8-product img{\n            height:85px;\n        }\n\n        .kmj-tv8-control input[type=\"range\"]{\n            height:32px;\n        }\n\n        #kmj-tv8.kmj-tv8-fullscreen{\n            padding:6px;\n        }\n\n        #kmj-tv8.kmj-tv8-fullscreen\n        #kmj-tv8-stage{\n            height:calc(100vh - 145px);\n            min-height:0;\n            max-height:none;\n        }\n\n    }\n\n\n    \/*\n     * =========================================\n     * HP KECIL\n     * =========================================\n     *\/\n\n    @media(max-width:380px){\n\n        #kmj-tv8-stage{\n            height:52vh;\n            min-height:290px;\n        }\n\n        .kmj-tv8-actions button{\n            font-size:10px;\n            padding:8px 3px;\n        }\n\n        .kmj-tv8-product img{\n            height:75px;\n        }\n\n    }\n\n    <\/style>\n\n\n    <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/three.js\/r128\/three.min.js\"><\/script>\n\n\n    <script>\n\n    (function(){\n\n        \"use strict\";\n\n        const AJAX_URL =\n            \"http:\\\/\\\/centralkeramik.com\\\/wp-admin\\\/admin-ajax.php\";\n\n        const NONCE =\n            \"c94e2ebeec\";\n\n\n        let scene;\n        let camera;\n        let renderer;\n\n        let floorMesh;\n        let backWall;\n        let leftWall;\n        let rightWall;\n        let ceiling;\n\n        let floorMaterial;\n        let wallMaterial;\n\n        let floorImage = null;\n        let wallImage = null;\n\n        let floorProduct = null;\n        let wallProduct = null;\n\n        let floorSize = {\n            w:50,\n            h:50\n        };\n\n        let wallSize = {\n            w:50,\n            h:50\n        };\n\n        let floorScale = 100;\n        let wallScale = 100;\n\n        let floorGrout = 2;\n        let wallGrout = 2;\n\n        let floorGroutColor = \"#cccccc\";\n        let wallGroutColor = \"#cccccc\";\n\n        let floorGloss = true;\n        let wallGloss = true;\n\n        let floorReflection = 20;\n        let wallReflection = 20;\n\n        let floorRotation = 0;\n        let wallRotation = 0;\n\n\n        \/*\n         * GLOBAL LIGHT\n         *\/\n\n        let globalBrightness = 1;\n\n        let ambientLight;\n        let mainLight;\n        let fillLight;\n        let ceilingLight;\n\n\n        \/*\n         * CAMERA\n         *\/\n\n        let cameraDistance = 7;\n\n        let cameraYaw = 0;\n\n        let cameraPitch = .15;\n\n        let target =\n            new THREE.Vector3(\n                0,\n                1.45,\n                -2\n            );\n\n\n        let dragging = false;\n        let panning = false;\n\n        let previousX = 0;\n        let previousY = 0;\n\n\n        \/*\n         * TOUCH\n         *\n         * 1 jari = rotate\n         * 2 jari = pinch zoom\n         *\/\n\n        let lastTouch = null;\n        let lastPinchDistance = null;\n\n\n        const stage =\n            document.getElementById(\n                \"kmj-tv8-stage\"\n            );\n\n\n        \/*\n         * ==================================\n         * INIT\n         * ==================================\n         *\/\n\n        function init(){\n\n            scene =\n                new THREE.Scene();\n\n            scene.background =\n                new THREE.Color(\n                    0xd8dadd\n                );\n\n\n            camera =\n                new THREE.PerspectiveCamera(\n                    42,\n                    stage.clientWidth \/\n                    stage.clientHeight,\n                    .01,\n                    100\n                );\n\n\n            renderer =\n                new THREE.WebGLRenderer({\n                    antialias:true,\n                    preserveDrawingBuffer:true,\n                    alpha:false\n                });\n\n\n            renderer.setPixelRatio(\n                Math.min(\n                    window.devicePixelRatio || 1,\n                    2\n                )\n            );\n\n\n            renderer.setSize(\n                stage.clientWidth,\n                stage.clientHeight\n            );\n\n\n            renderer.shadowMap.enabled =\n                true;\n\n            renderer.shadowMap.type =\n                THREE.PCFSoftShadowMap;\n\n\n            renderer.outputEncoding =\n                THREE.sRGBEncoding;\n\n\n            renderer.toneMapping =\n                THREE.ACESFilmicToneMapping;\n\n\n            renderer.toneMappingExposure =\n                1;\n\n\n            stage.innerHTML = \"\";\n\n            stage.appendChild(\n                renderer.domElement\n            );\n\n\n            createRoom();\n\n            createLights();\n\n            updateCamera();\n\n            bindEvents();\n\n            loadProducts();\n\n            animate();\n\n        }\n\n\n        \/*\n         * ==================================\n         * ROOM\n         * ==================================\n         *\/\n\n        function createRoom(){\n\n            const W = 7;\n            const D = 7;\n            const H = 4.2;\n\n\n            const floorGeometry =\n                new THREE.PlaneGeometry(\n                    W,\n                    D\n                );\n\n\n            floorMaterial =\n                new THREE.MeshStandardMaterial({\n                    color:0xffffff,\n                    roughness:.42,\n                    metalness:0\n                });\n\n\n            floorMesh =\n                new THREE.Mesh(\n                    floorGeometry,\n                    floorMaterial\n                );\n\n\n            floorMesh.rotation.x =\n                -Math.PI \/ 2;\n\n\n            floorMesh.position.set(\n                0,\n                0,\n                -D \/ 2 + .5\n            );\n\n\n            floorMesh.receiveShadow =\n                true;\n\n\n            scene.add(\n                floorMesh\n            );\n\n\n            const wallGeometry =\n                new THREE.PlaneGeometry(\n                    W,\n                    H\n                );\n\n\n            wallMaterial =\n                new THREE.MeshStandardMaterial({\n                    color:0xffffff,\n                    roughness:.5,\n                    metalness:0\n                });\n\n\n            backWall =\n                new THREE.Mesh(\n                    wallGeometry,\n                    wallMaterial\n                );\n\n\n            backWall.position.set(\n                0,\n                H \/ 2,\n                -D + .5\n            );\n\n\n            backWall.receiveShadow =\n                true;\n\n\n            scene.add(\n                backWall\n            );\n\n\n            const sideGeometry =\n                new THREE.PlaneGeometry(\n                    D,\n                    H\n                );\n\n\n            leftWall =\n                new THREE.Mesh(\n                    sideGeometry,\n                    new THREE.MeshStandardMaterial({\n                        color:0xf4f4f4,\n                        roughness:.62\n                    })\n                );\n\n\n            leftWall.rotation.y =\n                Math.PI \/ 2;\n\n\n            leftWall.position.set(\n                -W \/ 2,\n                H \/ 2,\n                -D \/ 2 + .5\n            );\n\n\n            leftWall.receiveShadow =\n                true;\n\n\n            scene.add(\n                leftWall\n            );\n\n\n            rightWall =\n                new THREE.Mesh(\n                    sideGeometry,\n                    new THREE.MeshStandardMaterial({\n                        color:0xf4f4f4,\n                        roughness:.62\n                    })\n                );\n\n\n            rightWall.rotation.y =\n                -Math.PI \/ 2;\n\n\n            rightWall.position.set(\n                W \/ 2,\n                H \/ 2,\n                -D \/ 2 + .5\n            );\n\n\n            rightWall.receiveShadow =\n                true;\n\n\n            scene.add(\n                rightWall\n            );\n\n\n            const ceilingGeometry =\n                new THREE.PlaneGeometry(\n                    W,\n                    D\n                );\n\n\n            ceiling =\n                new THREE.Mesh(\n                    ceilingGeometry,\n                    new THREE.MeshStandardMaterial({\n                        color:0xf7f7f7,\n                        roughness:.8\n                    })\n                );\n\n\n            ceiling.rotation.x =\n                Math.PI \/ 2;\n\n\n            ceiling.position.set(\n                0,\n                H,\n                -D \/ 2 + .5\n            );\n\n\n            scene.add(\n                ceiling\n            );\n\n        }\n\n\n        \/*\n         * ==================================\n         * LIGHTING\n         * ==================================\n         *\/\n\n        function createLights(){\n\n            ambientLight =\n                new THREE.HemisphereLight(\n                    0xffffff,\n                    0x777777,\n                    1.05\n                );\n\n\n            scene.add(\n                ambientLight\n            );\n\n\n            mainLight =\n                new THREE.DirectionalLight(\n                    0xffffff,\n                    1.45\n                );\n\n\n            mainLight.position.set(\n                2,\n                7,\n                3\n            );\n\n\n            mainLight.castShadow =\n                true;\n\n\n            mainLight.shadow.mapSize.width =\n                2048;\n\n            mainLight.shadow.mapSize.height =\n                2048;\n\n\n            scene.add(\n                mainLight\n            );\n\n\n            fillLight =\n                new THREE.DirectionalLight(\n                    0xffffff,\n                    .35\n                );\n\n\n            fillLight.position.set(\n                -4,\n                4,\n                2\n            );\n\n\n            scene.add(\n                fillLight\n            );\n\n\n            ceilingLight =\n                new THREE.PointLight(\n                    0xfff5df,\n                    1.2,\n                    15\n                );\n\n\n            ceilingLight.position.set(\n                0,\n                3.8,\n                -2\n            );\n\n\n            ceilingLight.castShadow =\n                true;\n\n\n            scene.add(\n                ceilingLight\n            );\n\n\n            updateGlobalLighting();\n\n        }\n\n\n        function updateGlobalLighting(){\n\n            if(!ambientLight){\n                return;\n            }\n\n\n            ambientLight.intensity =\n                1.05 *\n                globalBrightness;\n\n\n            mainLight.intensity =\n                1.45 *\n                globalBrightness;\n\n\n            fillLight.intensity =\n                .35 *\n                globalBrightness;\n\n\n            ceilingLight.intensity =\n                1.2 *\n                globalBrightness;\n\n        }\n\n\n        \/*\n         * ==================================\n         * CAMERA\n         * ==================================\n         *\/\n\n        function updateCamera(){\n\n            const x =\n                target.x +\n                Math.sin(cameraYaw) *\n                Math.cos(cameraPitch) *\n                cameraDistance;\n\n\n            const y =\n                target.y +\n                Math.sin(cameraPitch) *\n                cameraDistance;\n\n\n            const z =\n                target.z +\n                Math.cos(cameraYaw) *\n                Math.cos(cameraPitch) *\n                cameraDistance;\n\n\n            camera.position.set(\n                x,\n                y,\n                z\n            );\n\n\n            camera.lookAt(\n                target\n            );\n\n        }\n\n\n        \/*\n         * ==================================\n         * PARSE SIZE\n         * ==================================\n         *\/\n\n        function parseSize(name){\n\n            if(!name){\n\n                return {\n                    w:50,\n                    h:50\n                };\n\n            }\n\n\n            const match =\n                name.match(\n                    \/(\\d+(?:[.,]\\d+)?)\\s*[x\u00d7]\\s*(\\d+(?:[.,]\\d+)?)\/i\n                );\n\n\n            if(!match){\n\n                return {\n                    w:50,\n                    h:50\n                };\n\n            }\n\n\n            const w =\n                parseFloat(\n                    match[1]\n                    .replace(\",\",\".\")\n                );\n\n\n            const h =\n                parseFloat(\n                    match[2]\n                    .replace(\",\",\".\")\n                );\n\n\n            if(\n                !isFinite(w) ||\n                !isFinite(h) ||\n                w <= 0 ||\n                h <= 0\n            ){\n\n                return {\n                    w:50,\n                    h:50\n                };\n\n            }\n\n\n            return {\n                w:w,\n                h:h\n            };\n\n        }\n\n\n        \/*\n         * ==================================\n         * TEXTURE GENERATOR\n         * ==================================\n         *\/\n\n        function buildTexture(\n            image,\n            size,\n            scale,\n            grout,\n            groutColor,\n            rotation\n        ){\n\n            if(!image){\n                return null;\n            }\n\n\n            const base = 512;\n\n            const aspect =\n                size.w \/\n                size.h;\n\n\n            let tileWidth;\n            let tileHeight;\n\n\n            if(aspect >= 1){\n\n                tileWidth = base;\n\n                tileHeight =\n                    base \/ aspect;\n\n            }else{\n\n                tileHeight = base;\n\n                tileWidth =\n                    base * aspect;\n\n            }\n\n\n            tileWidth *=\n                scale \/ 100;\n\n            tileHeight *=\n                scale \/ 100;\n\n\n            tileWidth =\n                Math.max(\n                    32,\n                    Math.round(tileWidth)\n                );\n\n\n            tileHeight =\n                Math.max(\n                    32,\n                    Math.round(tileHeight)\n                );\n\n\n            const groutPixels =\n                Math.max(\n                    0,\n                    grout * 2\n                );\n\n\n            const canvas =\n                document.createElement(\n                    \"canvas\"\n                );\n\n\n            canvas.width =\n                tileWidth;\n\n            canvas.height =\n                tileHeight;\n\n\n            const ctx =\n                canvas.getContext(\n                    \"2d\"\n                );\n\n\n            ctx.fillStyle =\n                groutColor;\n\n\n            ctx.fillRect(\n                0,\n                0,\n                canvas.width,\n                canvas.height\n            );\n\n\n            const x =\n                groutPixels \/ 2;\n\n\n            const y =\n                groutPixels \/ 2;\n\n\n            const w =\n                canvas.width -\n                groutPixels;\n\n\n            const h =\n                canvas.height -\n                groutPixels;\n\n\n            ctx.save();\n\n\n            ctx.beginPath();\n\n\n            ctx.rect(\n                x,\n                y,\n                Math.max(1,w),\n                Math.max(1,h)\n            );\n\n\n            ctx.clip();\n\n\n            ctx.translate(\n                x + w \/ 2,\n                y + h \/ 2\n            );\n\n\n            ctx.rotate(\n                rotation *\n                Math.PI \/\n                180\n            );\n\n\n            const iw =\n                image.naturalWidth ||\n                image.width;\n\n\n            const ih =\n                image.naturalHeight ||\n                image.height;\n\n\n            const imageRatio =\n                iw \/ ih;\n\n\n            const tileRatio =\n                w \/ h;\n\n\n            let drawW;\n            let drawH;\n\n\n            if(\n                imageRatio >\n                tileRatio\n            ){\n\n                drawH = h;\n\n                drawW =\n                    h *\n                    imageRatio;\n\n            }else{\n\n                drawW = w;\n\n                drawH =\n                    w \/\n                    imageRatio;\n\n            }\n\n\n            ctx.drawImage(\n                image,\n                -drawW \/ 2,\n                -drawH \/ 2,\n                drawW,\n                drawH\n            );\n\n\n            ctx.restore();\n\n\n            if(grout > 0){\n\n                ctx.strokeStyle =\n                    groutColor;\n\n\n                ctx.lineWidth =\n                    Math.max(\n                        1,\n                        groutPixels\n                    );\n\n\n                ctx.strokeRect(\n                    x,\n                    y,\n                    w,\n                    h\n                );\n\n            }\n\n\n            const texture =\n                new THREE.CanvasTexture(\n                    canvas\n                );\n\n\n            texture.wrapS =\n                THREE.RepeatWrapping;\n\n\n            texture.wrapT =\n                THREE.RepeatWrapping;\n\n\n            texture.minFilter =\n                THREE.LinearMipmapLinearFilter;\n\n\n            texture.magFilter =\n                THREE.LinearFilter;\n\n\n            texture.anisotropy =\n                renderer.capabilities\n                .getMaxAnisotropy();\n\n\n            texture.encoding =\n                THREE.sRGBEncoding;\n\n\n            texture.needsUpdate =\n                true;\n\n\n            return texture;\n\n        }\n\n\n        \/*\n         * ==================================\n         * FLOOR\n         * ==================================\n         *\/\n\n        function applyFloor(){\n\n            if(\n                !floorImage ||\n                !floorProduct\n            ){\n                return;\n            }\n\n\n            const roomWidth = 7;\n            const roomDepth = 7;\n\n\n            const tileW =\n                floorSize.w \/ 100;\n\n\n            const tileH =\n                floorSize.h \/ 100;\n\n\n            const visualW =\n                tileW *\n                floorScale \/\n                100;\n\n\n            const visualH =\n                tileH *\n                floorScale \/\n                100;\n\n\n            const texture =\n                buildTexture(\n                    floorImage,\n                    floorSize,\n                    floorScale,\n                    floorGrout,\n                    floorGroutColor,\n                    floorRotation\n                );\n\n\n            if(!texture){\n                return;\n            }\n\n\n            texture.repeat.x =\n                roomWidth \/\n                visualW;\n\n\n            texture.repeat.y =\n                roomDepth \/\n                visualH;\n\n\n            texture.offset.set(\n                0,\n                0\n            );\n\n\n            floorMaterial.map =\n                texture;\n\n\n            floorMaterial.color.set(\n                0xffffff\n            );\n\n\n            floorMaterial.roughness =\n                floorGloss\n                    ? .24\n                    : .48;\n\n\n            floorMaterial.metalness =\n                floorGloss\n                    ? .05\n                    : 0;\n\n\n            floorMaterial.clearcoat =\n                floorGloss\n                    ? floorReflection \/ 100\n                    : 0;\n\n\n            floorMaterial.needsUpdate =\n                true;\n\n        }\n\n\n        \/*\n         * ==================================\n         * WALL\n         * ==================================\n         *\/\n\n        function applyWall(){\n\n            if(\n                !wallImage ||\n                !wallProduct\n            ){\n                return;\n            }\n\n\n            const roomWidth = 7;\n            const roomHeight = 4.2;\n\n\n            const tileW =\n                wallSize.w \/ 100;\n\n\n            const tileH =\n                wallSize.h \/ 100;\n\n\n            const visualW =\n                tileW *\n                wallScale \/\n                100;\n\n\n            const visualH =\n                tileH *\n                wallScale \/\n                100;\n\n\n            const texture =\n                buildTexture(\n                    wallImage,\n                    wallSize,\n                    wallScale,\n                    wallGrout,\n                    wallGroutColor,\n                    wallRotation\n                );\n\n\n            if(!texture){\n                return;\n            }\n\n\n            texture.repeat.x =\n                roomWidth \/\n                visualW;\n\n\n            texture.repeat.y =\n                roomHeight \/\n                visualH;\n\n\n            texture.offset.set(\n                0,\n                0\n            );\n\n\n            wallMaterial.map =\n                texture;\n\n\n            wallMaterial.color.set(\n                0xffffff\n            );\n\n\n            wallMaterial.roughness =\n                wallGloss\n                    ? .25\n                    : .55;\n\n\n            wallMaterial.metalness =\n                wallGloss\n                    ? .03\n                    : 0;\n\n\n            wallMaterial.clearcoat =\n                wallGloss\n                    ? wallReflection \/ 100\n                    : 0;\n\n\n            wallMaterial.needsUpdate =\n                true;\n\n        }\n\n\n        \/*\n         * ==================================\n         * PRODUCTS\n         * ==================================\n         *\/\n\n        function loadProducts(){\n\n            getProducts(\n                \"\",\n                \"floor\"\n            );\n\n            getProducts(\n                \"\",\n                \"wall\"\n            );\n\n        }\n\n\n        function getProducts(\n            search,\n            type\n        ){\n\n            const container =\n                document.getElementById(\n                    type === \"floor\"\n                        ? \"kmj-tv8-floor-products\"\n                        : \"kmj-tv8-wall-products\"\n                );\n\n\n            container.innerHTML =\n                '<div style=\"grid-column:1\/-1;font-size:12px;color:#777;\">Memuat produk...<\/div>';\n\n\n            const body =\n                new URLSearchParams();\n\n\n            body.append(\n                \"action\",\n                \"kmj_tv8_products\"\n            );\n\n\n            body.append(\n                \"nonce\",\n                NONCE\n            );\n\n\n            body.append(\n                \"search\",\n                search\n            );\n\n\n            fetch(\n                AJAX_URL,\n                {\n                    method:\"POST\",\n                    headers:{\n                        \"Content-Type\":\n                            \"application\/x-www-form-urlencoded;charset=UTF-8\"\n                    },\n                    body:body.toString()\n                }\n            )\n            .then(\n                response =>\n                    response.json()\n            )\n            .then(\n                result => {\n\n                    if(\n                        !result ||\n                        !result.success\n                    ){\n\n                        container.innerHTML =\n                            '<div style=\"grid-column:1\/-1;color:#c00;font-size:12px;\">Gagal memuat produk.<\/div>';\n\n                        return;\n\n                    }\n\n\n                    renderProducts(\n                        result.data,\n                        type\n                    );\n\n                }\n            )\n            .catch(\n                function(){\n\n                    container.innerHTML =\n                        '<div style=\"grid-column:1\/-1;color:#c00;font-size:12px;\">Koneksi gagal.<\/div>';\n\n                }\n            );\n\n        }\n\n\n        function renderProducts(\n            products,\n            type\n        ){\n\n            const container =\n                document.getElementById(\n                    type === \"floor\"\n                        ? \"kmj-tv8-floor-products\"\n                        : \"kmj-tv8-wall-products\"\n                );\n\n\n            container.innerHTML = \"\";\n\n\n            if(\n                !products ||\n                !products.length\n            ){\n\n                container.innerHTML =\n                    '<div style=\"grid-column:1\/-1;color:#777;font-size:12px;\">Produk tidak ditemukan.<\/div>';\n\n                return;\n\n            }\n\n\n            products.forEach(\n                function(product){\n\n                    const box =\n                        document.createElement(\n                            \"div\"\n                        );\n\n\n                    box.className =\n                        \"kmj-tv8-product\";\n\n\n                    const current =\n                        type === \"floor\"\n                            ? floorProduct\n                            : wallProduct;\n\n\n                    if(\n                        current &&\n                        parseInt(\n                            current.id\n                        ) ===\n                        parseInt(\n                            product.id\n                        )\n                    ){\n\n                        box.classList.add(\n                            \"active\"\n                        );\n\n                    }\n\n\n                    box.innerHTML =\n\n                        '<img decoding=\"async\" src=\"' +\n                        escapeHtml(\n                            product.image\n                        ) +\n                        '\" alt=\"\">' +\n\n                        '<div class=\"kmj-tv8-product-name\">' +\n                        escapeHtml(\n                            product.name\n                        ) +\n                        '<\/div>' +\n\n                        '<div class=\"kmj-tv8-product-size\">' +\n                        escapeHtml(\n                            product.sizeText\n                        ) +\n                        '<\/div>';\n\n\n                    box.addEventListener(\n                        \"click\",\n                        function(){\n\n                            chooseProduct(\n                                product,\n                                type\n                            );\n\n                        }\n                    );\n\n\n                    container.appendChild(\n                        box\n                    );\n\n                }\n            );\n\n        }\n\n\n        \/*\n         * ==================================\n         * CHOOSE PRODUCT\n         * ==================================\n         *\/\n\n        function chooseProduct(\n            product,\n            type\n        ){\n\n            const size =\n                parseSize(\n                    product.name\n                );\n\n\n            loadImage(\n                product.image,\n                function(image){\n\n                    if(type === \"floor\"){\n\n                        floorProduct =\n                            product;\n\n                        floorSize =\n                            size;\n\n                        floorImage =\n                            image;\n\n\n                        document.getElementById(\n                            \"kmj-tv8-floor-selected\"\n                        ).innerHTML =\n                            \"<strong>\" +\n                            escapeHtml(\n                                product.name\n                            ) +\n                            \"<\/strong><br>\" +\n                            size.w +\n                            \" \u00d7 \" +\n                            size.h +\n                            \" cm\";\n\n\n                        applyFloor();\n\n\n                        getProducts(\n                            document.getElementById(\n                                \"kmj-tv8-floor-search\"\n                            ).value,\n                            \"floor\"\n                        );\n\n\n                    }else{\n\n                        wallProduct =\n                            product;\n\n                        wallSize =\n                            size;\n\n                        wallImage =\n                            image;\n\n\n                        document.getElementById(\n                            \"kmj-tv8-wall-selected\"\n                        ).innerHTML =\n                            \"<strong>\" +\n                            escapeHtml(\n                                product.name\n                            ) +\n                            \"<\/strong><br>\" +\n                            size.w +\n                            \" \u00d7 \" +\n                            size.h +\n                            \" cm\";\n\n\n                        applyWall();\n\n\n                        getProducts(\n                            document.getElementById(\n                                \"kmj-tv8-wall-search\"\n                            ).value,\n                            \"wall\"\n                        );\n\n                    }\n\n                }\n            );\n\n        }\n\n\n        \/*\n         * ==================================\n         * IMAGE\n         * ==================================\n         *\/\n\n        function loadImage(\n            url,\n            callback\n        ){\n\n            if(!url){\n                return;\n            }\n\n\n            const image =\n                new Image();\n\n\n            image.crossOrigin =\n                \"anonymous\";\n\n\n            image.onload =\n                function(){\n\n                    callback(\n                        image\n                    );\n\n                };\n\n\n            image.onerror =\n                function(){\n\n                    const retry =\n                        new Image();\n\n\n                    retry.onload =\n                        function(){\n\n                            callback(\n                                retry\n                            );\n\n                        };\n\n\n                    retry.src =\n                        url;\n\n                };\n\n\n            image.src =\n                url;\n\n        }\n\n\n        \/*\n         * ==================================\n         * ESCAPE\n         * ==================================\n         *\/\n\n        function escapeHtml(\n            value\n        ){\n\n            return String(\n                value || \"\"\n            )\n            .replace(\n                \/&\/g,\n                \"&amp;\"\n            )\n            .replace(\n                \/<\/g,\n                \"&lt;\"\n            )\n            .replace(\n                \/>\/g,\n                \"&gt;\"\n            )\n            .replace(\n                \/\"\/g,\n                \"&quot;\"\n            )\n            .replace(\n                \/'\/g,\n                \"&#039;\"\n            );\n\n        }\n\n\n        \/*\n         * ==================================\n         * TOUCH DISTANCE\n         * ==================================\n         *\/\n\n        function getTouchDistance(\n            touches\n        ){\n\n            const dx =\n                touches[0].clientX -\n                touches[1].clientX;\n\n\n            const dy =\n                touches[0].clientY -\n                touches[1].clientY;\n\n\n            return Math.sqrt(\n                dx * dx +\n                dy * dy\n            );\n\n        }\n\n\n        \/*\n         * ==================================\n         * EVENTS\n         * ==================================\n         *\/\n\n        function bindEvents(){\n\n            \/*\n             * TAB\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-floor-tab\"\n            )\n            .addEventListener(\n                \"click\",\n                function(){\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-floor-tab\"\n                    )\n                    .classList.add(\n                        \"active\"\n                    );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-wall-tab\"\n                    )\n                    .classList.remove(\n                        \"active\"\n                    );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-floor-panel\"\n                    )\n                    .classList.add(\n                        \"active\"\n                    );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-wall-panel\"\n                    )\n                    .classList.remove(\n                        \"active\"\n                    );\n\n                }\n            );\n\n\n            document\n            .getElementById(\n                \"kmj-tv8-wall-tab\"\n            )\n            .addEventListener(\n                \"click\",\n                function(){\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-wall-tab\"\n                    )\n                    .classList.add(\n                        \"active\"\n                    );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-floor-tab\"\n                    )\n                    .classList.remove(\n                        \"active\"\n                    );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-wall-panel\"\n                    )\n                    .classList.add(\n                        \"active\"\n                    );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-floor-panel\"\n                    )\n                    .classList.remove(\n                        \"active\"\n                    );\n\n                }\n            );\n\n\n            \/*\n             * GLOBAL LIGHT\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-light\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    globalBrightness =\n                        parseInt(\n                            this.value,\n                            10\n                        ) \/ 100;\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-light-value\"\n                    )\n                    .textContent =\n                        this.value +\n                        \"%\";\n\n\n                    updateGlobalLighting();\n\n                }\n            );\n\n\n            \/*\n             * FLOOR SCALE\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-floor-scale\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    floorScale =\n                        parseInt(\n                            this.value,\n                            10\n                        );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-floor-scale-value\"\n                    )\n                    .textContent =\n                        floorScale +\n                        \"%\";\n\n\n                    applyFloor();\n\n                }\n            );\n\n\n            \/*\n             * WALL SCALE\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-wall-scale\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    wallScale =\n                        parseInt(\n                            this.value,\n                            10\n                        );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-wall-scale-value\"\n                    ).textContent =\n                        wallScale +\n                        \"%\";\n\n\n                    applyWall();\n\n                }\n            );\n\n\n            \/*\n             * FLOOR GROUT\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-floor-grout\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    floorGrout =\n                        parseFloat(\n                            this.value\n                        );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-floor-grout-value\"\n                    )\n                    .textContent =\n                        floorGrout +\n                        \" mm\";\n\n\n                    applyFloor();\n\n                }\n            );\n\n\n            \/*\n             * WALL GROUT\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-wall-grout\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    wallGrout =\n                        parseFloat(\n                            this.value\n                        );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-wall-grout-value\"\n                    )\n                    .textContent =\n                        wallGrout +\n                        \" mm\";\n\n\n                    applyWall();\n\n                }\n            );\n\n\n            \/*\n             * NAT COLOR\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-floor-grout-color\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    floorGroutColor =\n                        this.value;\n\n                    applyFloor();\n\n                }\n            );\n\n\n            document\n            .getElementById(\n                \"kmj-tv8-wall-grout-color\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    wallGroutColor =\n                        this.value;\n\n                    applyWall();\n\n                }\n            );\n\n\n            \/*\n             * GLOSS\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-floor-gloss\"\n            )\n            .addEventListener(\n                \"change\",\n                function(){\n\n                    floorGloss =\n                        this.checked;\n\n                    applyFloor();\n\n                }\n            );\n\n\n            document\n            .getElementById(\n                \"kmj-tv8-wall-gloss\"\n            )\n            .addEventListener(\n                \"change\",\n                function(){\n\n                    wallGloss =\n                        this.checked;\n\n                    applyWall();\n\n                }\n            );\n\n\n            \/*\n             * REFLECTION\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-floor-reflection\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    floorReflection =\n                        parseInt(\n                            this.value,\n                            10\n                        );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-floor-reflection-value\"\n                    )\n                    .textContent =\n                        floorReflection +\n                        \"%\";\n\n\n                    applyFloor();\n\n                }\n            );\n\n\n            document\n            .getElementById(\n                \"kmj-tv8-wall-reflection\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    wallReflection =\n                        parseInt(\n                            this.value,\n                            10\n                        );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-wall-reflection-value\"\n                    )\n                    .textContent =\n                        wallReflection +\n                        \"%\";\n\n\n                    applyWall();\n\n                }\n            );\n\n\n            \/*\n             * ROTATION\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-floor-rotation\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    floorRotation =\n                        parseInt(\n                            this.value,\n                            10\n                        );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-floor-rotation-value\"\n                    )\n                    .textContent =\n                        floorRotation +\n                        \"\u00b0\";\n\n\n                    applyFloor();\n\n                }\n            );\n\n\n            document\n            .getElementById(\n                \"kmj-tv8-wall-rotation\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    wallRotation =\n                        parseInt(\n                            this.value,\n                            10\n                        );\n\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8-wall-rotation-value\"\n                    )\n                    .textContent =\n                        wallRotation +\n                        \"\u00b0\";\n\n\n                    applyWall();\n\n                }\n            );\n\n\n            \/*\n             * SEARCH\n             *\/\n\n            let floorTimer;\n            let wallTimer;\n\n\n            document\n            .getElementById(\n                \"kmj-tv8-floor-search\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    clearTimeout(\n                        floorTimer\n                    );\n\n\n                    const value =\n                        this.value;\n\n\n                    floorTimer =\n                        setTimeout(\n                            function(){\n\n                                getProducts(\n                                    value,\n                                    \"floor\"\n                                );\n\n                            },\n                            300\n                        );\n\n                }\n            );\n\n\n            document\n            .getElementById(\n                \"kmj-tv8-wall-search\"\n            )\n            .addEventListener(\n                \"input\",\n                function(){\n\n                    clearTimeout(\n                        wallTimer\n                    );\n\n\n                    const value =\n                        this.value;\n\n\n                    wallTimer =\n                        setTimeout(\n                            function(){\n\n                                getProducts(\n                                    value,\n                                    \"wall\"\n                                );\n\n                            },\n                            300\n                        );\n\n                }\n            );\n\n\n            \/*\n             * ==================================\n             * DESKTOP MOUSE DRAG\n             * ==================================\n             *\/\n\n            renderer.domElement.addEventListener(\n                \"mousedown\",\n                function(event){\n\n                    dragging = true;\n\n                    panning =\n                        event.shiftKey;\n\n\n                    previousX =\n                        event.clientX;\n\n\n                    previousY =\n                        event.clientY;\n\n                }\n            );\n\n\n            window.addEventListener(\n                \"mouseup\",\n                function(){\n\n                    dragging = false;\n\n                    panning = false;\n\n                }\n            );\n\n\n            window.addEventListener(\n                \"mousemove\",\n                function(event){\n\n                    if(!dragging){\n                        return;\n                    }\n\n\n                    const dx =\n                        event.clientX -\n                        previousX;\n\n\n                    const dy =\n                        event.clientY -\n                        previousY;\n\n\n                    previousX =\n                        event.clientX;\n\n\n                    previousY =\n                        event.clientY;\n\n\n                    if(panning){\n\n                        target.x -=\n                            dx * .006;\n\n\n                        target.y +=\n                            dy * .006;\n\n\n                        target.y =\n                            Math.max(\n                                .5,\n                                Math.min(\n                                    3.3,\n                                    target.y\n                                )\n                            );\n\n                    }else{\n\n                        cameraYaw -=\n                            dx * .008;\n\n\n                        cameraPitch +=\n                            dy * .006;\n\n\n                        cameraPitch =\n                            Math.max(\n                                -.8,\n                                Math.min(\n                                    1,\n                                    cameraPitch\n                                )\n                            );\n\n                    }\n\n\n                    updateCamera();\n\n                }\n            );\n\n\n            \/*\n             * ==================================\n             * DESKTOP SCROLL ZOOM\n             * ==================================\n             *\/\n\n            renderer.domElement.addEventListener(\n                \"wheel\",\n                function(event){\n\n                    event.preventDefault();\n\n\n                    cameraDistance *=\n                        event.deltaY > 0\n                            ? 1.12\n                            : .88;\n\n\n                    cameraDistance =\n                        Math.max(\n                            2,\n                            Math.min(\n                                30,\n                                cameraDistance\n                            )\n                        );\n\n\n                    updateCamera();\n\n                },\n                {\n                    passive:false\n                }\n            );\n\n\n            \/*\n             * ==================================\n             * MOBILE TOUCH START\n             * ==================================\n             *\/\n\n            renderer.domElement.addEventListener(\n                \"touchstart\",\n                function(event){\n\n                    \/*\n                     * 1 JARI\n                     *\/\n                    if(\n                        event.touches.length === 1\n                    ){\n\n                        lastTouch = {\n                            x:\n                                event.touches[0]\n                                .clientX,\n\n                            y:\n                                event.touches[0]\n                                .clientY\n                        };\n\n\n                        lastPinchDistance =\n                            null;\n\n                    }\n\n\n                    \/*\n                     * 2 JARI\n                     *\/\n                    else if(\n                        event.touches.length === 2\n                    ){\n\n                        lastTouch = null;\n\n\n                        lastPinchDistance =\n                            getTouchDistance(\n                                event.touches\n                            );\n\n                    }\n\n                },\n                {\n                    passive:true\n                }\n            );\n\n\n            \/*\n             * ==================================\n             * MOBILE TOUCH MOVE\n             * ==================================\n             *\/\n\n            renderer.domElement.addEventListener(\n                \"touchmove\",\n                function(event){\n\n                    \/*\n                     * =================================\n                     * 2 JARI = PINCH ZOOM\n                     * =================================\n                     *\/\n\n                    if(\n                        event.touches.length === 2\n                    ){\n\n                        event.preventDefault();\n\n\n                        const distance =\n                            getTouchDistance(\n                                event.touches\n                            );\n\n\n                        if(\n                            lastPinchDistance !== null\n                        ){\n\n                            const difference =\n                                distance -\n                                lastPinchDistance;\n\n\n                            \/*\n                             * Smooth zoom\n                             *\/\n                            if(\n                                Math.abs(\n                                    difference\n                                ) > .2\n                            ){\n\n                                cameraDistance *=\n                                    Math.pow(\n                                        .985,\n                                        difference\n                                    );\n\n\n                                cameraDistance =\n                                    Math.max(\n                                        2,\n                                        Math.min(\n                                            30,\n                                            cameraDistance\n                                        )\n                                    );\n\n\n                                updateCamera();\n\n                            }\n\n                        }\n\n\n                        lastPinchDistance =\n                            distance;\n\n\n                        return;\n\n                    }\n\n\n                    \/*\n                     * =================================\n                     * 1 JARI = ROTATE\n                     * =================================\n                     *\/\n\n                    if(\n                        !lastTouch ||\n                        event.touches.length !== 1\n                    ){\n\n                        return;\n\n                    }\n\n\n                    event.preventDefault();\n\n\n                    const touch =\n                        event.touches[0];\n\n\n                    const dx =\n                        touch.clientX -\n                        lastTouch.x;\n\n\n                    const dy =\n                        touch.clientY -\n                        lastTouch.y;\n\n\n                    cameraYaw -=\n                        dx * .004;\n\n\n                    cameraPitch +=\n                        dy * .003;\n\n\n                    cameraPitch =\n                        Math.max(\n                            -.7,\n                            Math.min(\n                                .9,\n                                cameraPitch\n                            )\n                        );\n\n\n                    lastTouch = {\n                        x:\n                            touch.clientX,\n\n                        y:\n                            touch.clientY\n                    };\n\n\n                    updateCamera();\n\n                },\n                {\n                    passive:false\n                }\n            );\n\n\n            \/*\n             * ==================================\n             * TOUCH END\n             * ==================================\n             *\/\n\n            renderer.domElement.addEventListener(\n                \"touchend\",\n                function(){\n\n                    lastTouch = null;\n\n                    lastPinchDistance = null;\n\n                }\n            );\n\n\n            renderer.domElement.addEventListener(\n                \"touchcancel\",\n                function(){\n\n                    lastTouch = null;\n\n                    lastPinchDistance = null;\n\n                }\n            );\n\n\n            \/*\n             * ==================================\n             * RESET\n             * ==================================\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-reset\"\n            )\n            .addEventListener(\n                \"click\",\n                resetAll\n            );\n\n\n            \/*\n             * ==================================\n             * FULLSCREEN\n             * ==================================\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-full\"\n            )\n            .addEventListener(\n                \"click\",\n                function(){\n\n                    document\n                    .getElementById(\n                        \"kmj-tv8\"\n                    )\n                    .classList.toggle(\n                        \"kmj-tv8-fullscreen\"\n                    );\n\n\n                    setTimeout(\n                        resize,\n                        150\n                    );\n\n                }\n            );\n\n\n            \/*\n             * ==================================\n             * DOWNLOAD\n             * ==================================\n             *\/\n\n            document\n            .getElementById(\n                \"kmj-tv8-download\"\n            )\n            .addEventListener(\n                \"click\",\n                downloadHD\n            );\n\n\n            window.addEventListener(\n                \"resize\",\n                resize\n            );\n\n        }\n\n\n        \/*\n         * ==================================\n         * RESET\n         * ==================================\n         *\/\n\n        function resetAll(){\n\n            globalBrightness = 1;\n\n            floorScale = 100;\n            wallScale = 100;\n\n            floorGrout = 2;\n            wallGrout = 2;\n\n            floorGroutColor =\n                \"#cccccc\";\n\n            wallGroutColor =\n                \"#cccccc\";\n\n            floorGloss = true;\n            wallGloss = true;\n\n            floorReflection = 20;\n            wallReflection = 20;\n\n            floorRotation = 0;\n            wallRotation = 0;\n\n\n            document.getElementById(\n                \"kmj-tv8-light\"\n            ).value = 100;\n\n\n            document.getElementById(\n                \"kmj-tv8-light-value\"\n            ).textContent =\n                \"100%\";\n\n\n            document.getElementById(\n                \"kmj-tv8-floor-scale\"\n            ).value = 100;\n\n\n            document.getElementById(\n                \"kmj-tv8-wall-scale\"\n            ).value = 100;\n\n\n            document.getElementById(\n                \"kmj-tv8-floor-scale-value\"\n            ).textContent =\n                \"100%\";\n\n\n            document.getElementById(\n                \"kmj-tv8-wall-scale-value\"\n            ).textContent =\n                \"100%\";\n\n\n            document.getElementById(\n                \"kmj-tv8-floor-grout\"\n            ).value = 2;\n\n\n            document.getElementById(\n                \"kmj-tv8-wall-grout\"\n            ).value = 2;\n\n\n            document.getElementById(\n                \"kmj-tv8-floor-grout-value\"\n            ).textContent =\n                \"2 mm\";\n\n\n            document.getElementById(\n                \"kmj-tv8-wall-grout-value\"\n            ).textContent =\n                \"2 mm\";\n\n\n            document.getElementById(\n                \"kmj-tv8-floor-grout-color\"\n            ).value =\n                \"#cccccc\";\n\n\n            document.getElementById(\n                \"kmj-tv8-wall-grout-color\"\n            ).value =\n                \"#cccccc\";\n\n\n            document.getElementById(\n                \"kmj-tv8-floor-gloss\"\n            ).checked = true;\n\n\n            document.getElementById(\n                \"kmj-tv8-wall-gloss\"\n            ).checked = true;\n\n\n            document.getElementById(\n                \"kmj-tv8-floor-reflection\"\n            ).value = 20;\n\n\n            document.getElementById(\n                \"kmj-tv8-wall-reflection\"\n            ).value = 20;\n\n\n            document.getElementById(\n                \"kmj-tv8-floor-reflection-value\"\n            ).textContent =\n                \"20%\";\n\n\n            document.getElementById(\n                \"kmj-tv8-wall-reflection-value\"\n            ).textContent =\n                \"20%\";\n\n\n            document.getElementById(\n                \"kmj-tv8-floor-rotation\"\n            ).value = 0;\n\n\n            document.getElementById(\n                \"kmj-tv8-wall-rotation\"\n            ).value = 0;\n\n\n            document.getElementById(\n                \"kmj-tv8-floor-rotation-value\"\n            ).textContent =\n                \"0\u00b0\";\n\n\n            document.getElementById(\n                \"kmj-tv8-wall-rotation-value\"\n            ).textContent =\n                \"0\u00b0\";\n\n\n            cameraDistance = 7;\n\n            cameraYaw = 0;\n\n            cameraPitch = .15;\n\n\n            target.set(\n                0,\n                1.45,\n                -2\n            );\n\n\n            updateCamera();\n\n            updateGlobalLighting();\n\n            applyFloor();\n\n            applyWall();\n\n        }\n\n\n        \/*\n         * ==================================\n         * RESIZE\n         * ==================================\n         *\/\n\n        function resize(){\n\n            if(!renderer){\n                return;\n            }\n\n\n            const width =\n                stage.clientWidth;\n\n\n            const height =\n                stage.clientHeight;\n\n\n            if(\n                width <= 0 ||\n                height <= 0\n            ){\n\n                return;\n\n            }\n\n\n            camera.aspect =\n                width \/ height;\n\n\n            camera.updateProjectionMatrix();\n\n\n            renderer.setSize(\n                width,\n                height\n            );\n\n        }\n\n\n        \/*\n         * ==================================\n         * DOWNLOAD HD\n         * ==================================\n         *\/\n\n        function downloadHD(){\n\n            const width = 2400;\n\n\n            const height =\n                Math.round(\n                    width *\n                    stage.clientHeight \/\n                    stage.clientWidth\n                );\n\n\n            renderer.setPixelRatio(1);\n\n\n            renderer.setSize(\n                width,\n                height,\n                false\n            );\n\n\n            renderer.render(\n                scene,\n                camera\n            );\n\n\n            try{\n\n                const image =\n                    renderer.domElement\n                    .toDataURL(\n                        \"image\/png\"\n                    );\n\n\n                const link =\n                    document.createElement(\n                        \"a\"\n                    );\n\n\n                link.href =\n                    image;\n\n\n                link.download =\n                    \"tile-visualizer-HD.png\";\n\n\n                document.body.appendChild(\n                    link\n                );\n\n\n                link.click();\n\n\n                link.remove();\n\n            }catch(error){\n\n                alert(\n                    \"Gagal membuat gambar HD. Periksa URL gambar produk WooCommerce.\"\n                );\n\n            }\n\n\n            resize();\n\n        }\n\n\n        \/*\n         * ==================================\n         * ANIMATION\n         * ==================================\n         *\/\n\n        function animate(){\n\n            requestAnimationFrame(\n                animate\n            );\n\n\n            renderer.render(\n                scene,\n                camera\n            );\n\n        }\n\n\n        init();\n\n    })();\n\n    <\/script>\n\n    <\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-821","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>preview keramik - central keramik<\/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:\/\/centralkeramik.com\/preview-keramik\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"preview keramik - central keramik\" \/>\n<meta property=\"og:url\" content=\"https:\/\/centralkeramik.com\/preview-keramik\/\" \/>\n<meta property=\"og:site_name\" content=\"central keramik\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-24T08:35:56+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/centralkeramik.com\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_yg6bxwyg6bxwyg6b.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1376\" \/>\n\t<meta property=\"og:image:height\" content=\"768\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/centralkeramik.com\\\/preview-keramik\\\/\",\"url\":\"https:\\\/\\\/centralkeramik.com\\\/preview-keramik\\\/\",\"name\":\"preview keramik - central keramik\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/centralkeramik.com\\\/#website\"},\"datePublished\":\"2026-09-19T06:49:29+00:00\",\"dateModified\":\"2026-09-24T08:35:56+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/centralkeramik.com\\\/preview-keramik\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/centralkeramik.com\\\/preview-keramik\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/centralkeramik.com\\\/preview-keramik\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/centralkeramik.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"preview keramik\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/centralkeramik.com\\\/#website\",\"url\":\"https:\\\/\\\/centralkeramik.com\\\/\",\"name\":\"central keramik\",\"description\":\"Central Keramik | Toko Keramik, Granit &amp; Sanitary\",\"publisher\":{\"@id\":\"https:\\\/\\\/centralkeramik.com\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/centralkeramik.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/centralkeramik.com\\\/#organization\",\"name\":\"central keramik\",\"url\":\"https:\\\/\\\/centralkeramik.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/centralkeramik.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"http:\\\/\\\/centralkeramik.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/44-removebg-preview.png\",\"contentUrl\":\"http:\\\/\\\/centralkeramik.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/44-removebg-preview.png\",\"width\":500,\"height\":500,\"caption\":\"central keramik\"},\"image\":{\"@id\":\"https:\\\/\\\/centralkeramik.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.tiktok.com\\\/@centralkeramiktegal?_r=1&_t=ZS-99wKZpih9PN\",\"https:\\\/\\\/www.instagram.com\\\/centralkeramiktegal?stkn=MmJqaWVqZGVhY2J4\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"preview keramik - central keramik","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:\/\/centralkeramik.com\/preview-keramik\/","og_locale":"en_US","og_type":"article","og_title":"preview keramik - central keramik","og_url":"https:\/\/centralkeramik.com\/preview-keramik\/","og_site_name":"central keramik","article_modified_time":"2026-09-24T08:35:56+00:00","og_image":[{"width":1376,"height":768,"url":"http:\/\/centralkeramik.com\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_yg6bxwyg6bxwyg6b.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/centralkeramik.com\/preview-keramik\/","url":"https:\/\/centralkeramik.com\/preview-keramik\/","name":"preview keramik - central keramik","isPartOf":{"@id":"https:\/\/centralkeramik.com\/#website"},"datePublished":"2026-09-19T06:49:29+00:00","dateModified":"2026-09-24T08:35:56+00:00","breadcrumb":{"@id":"https:\/\/centralkeramik.com\/preview-keramik\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/centralkeramik.com\/preview-keramik\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/centralkeramik.com\/preview-keramik\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/centralkeramik.com\/"},{"@type":"ListItem","position":2,"name":"preview keramik"}]},{"@type":"WebSite","@id":"https:\/\/centralkeramik.com\/#website","url":"https:\/\/centralkeramik.com\/","name":"central keramik","description":"Central Keramik | Toko Keramik, Granit &amp; Sanitary","publisher":{"@id":"https:\/\/centralkeramik.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/centralkeramik.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/centralkeramik.com\/#organization","name":"central keramik","url":"https:\/\/centralkeramik.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/centralkeramik.com\/#\/schema\/logo\/image\/","url":"http:\/\/centralkeramik.com\/wp-content\/uploads\/2026\/09\/44-removebg-preview.png","contentUrl":"http:\/\/centralkeramik.com\/wp-content\/uploads\/2026\/09\/44-removebg-preview.png","width":500,"height":500,"caption":"central keramik"},"image":{"@id":"https:\/\/centralkeramik.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.tiktok.com\/@centralkeramiktegal?_r=1&_t=ZS-99wKZpih9PN","https:\/\/www.instagram.com\/centralkeramiktegal?stkn=MmJqaWVqZGVhY2J4"]}]}},"_links":{"self":[{"href":"http:\/\/centralkeramik.com\/wp-json\/wp\/v2\/pages\/821","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/centralkeramik.com\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"http:\/\/centralkeramik.com\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"http:\/\/centralkeramik.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/centralkeramik.com\/wp-json\/wp\/v2\/comments?post=821"}],"version-history":[{"count":10,"href":"http:\/\/centralkeramik.com\/wp-json\/wp\/v2\/pages\/821\/revisions"}],"predecessor-version":[{"id":1074,"href":"http:\/\/centralkeramik.com\/wp-json\/wp\/v2\/pages\/821\/revisions\/1074"}],"wp:attachment":[{"href":"http:\/\/centralkeramik.com\/wp-json\/wp\/v2\/media?parent=821"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}