{"id":534,"date":"2026-08-10T21:26:18","date_gmt":"2026-08-10T14:26:18","guid":{"rendered":"https:\/\/app.mitunet.com\/home\/?p=534"},"modified":"2026-08-10T21:43:23","modified_gmt":"2026-08-10T14:43:23","slug":"nen-anh-online","status":"publish","type":"post","link":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/","title":{"rendered":"C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh"},"content":{"rendered":"<p>\u1ee8ng d\u1ee5ng n\u00e9n \u1ea3nh online n\u00e0y gi\u00fap b\u1ea1n gi\u1ea3m dung l\u01b0\u1ee3ng h\u00ecnh \u1ea3nh nhanh ch\u00f3ng m\u00e0 kh\u00f4ng c\u1ea7n c\u00e0i \u0111\u1eb7t ph\u1ea7n m\u1ec1m tr\u00ean m\u00e1y t\u00ednh. Ch\u1ec9 v\u1edbi v\u00e0i thao t\u00e1c \u0111\u01a1n gi\u1ea3n, b\u1ea1n c\u00f3 th\u1ec3 n\u00e9n \u1ea3nh JPG, PNG, WebP v\u00e0 nhi\u1ec1u \u0111\u1ecbnh d\u1ea1ng ph\u1ed5 bi\u1ebfn kh\u00e1c, \u0111\u1ed3ng th\u1eddi gi\u1eef ch\u1ea5t l\u01b0\u1ee3ng h\u00ecnh \u1ea3nh \u1edf m\u1ee9c t\u1ed1t. \u0110\u00e2y l\u00e0 gi\u1ea3i ph\u00e1p ph\u00f9 h\u1ee3p khi c\u1ea7n t\u1ed1i \u01b0u \u1ea3nh \u0111\u1ec3 \u0111\u0103ng website, g\u1eedi email, chia s\u1ebb l\u00ean m\u1ea1ng x\u00e3 h\u1ed9i ho\u1eb7c ti\u1ebft ki\u1ec7m dung l\u01b0\u1ee3ng l\u01b0u tr\u1eef.<\/p>\n\n        <div\n            class=\"itools-wrapper\"\n            id=\"itools_93703\"\n            data-tool=\"compressor\"\n        >\n\n            \n                        <div class=\"itool-box\" data-module=\"compressor\">\n            <div class=\"itool-title\">\n                <h2>\ud83d\udddc\ufe0f N\u00e9n \u1ea3nh online<\/h2>\n                <p>Gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh ngay tr\u00ean tr\u00ecnh duy\u1ec7t.<\/p>\n            <\/div>\n\n            <label class=\"itool-upload\">\n                <input\n                    type=\"file\"\n                    class=\"compress-files\"\n                    accept=\"image\/jpeg,image\/png,image\/webp,image\/avif,image\/bmp,image\/gif\"\n                    multiple\n                >\n                <span class=\"upload-icon\">\ud83d\udcc1<\/span>\n                <strong>Ch\u1ecdn \u1ea3nh<\/strong>\n                <small>JPG, PNG, WebP, AVIF, BMP ho\u1eb7c GIF<\/small>\n            <\/label>\n\n            <div class=\"compress-options\">\n                <label>\n                    Ch\u1ea5t l\u01b0\u1ee3ng\n                    <select class=\"compress-quality\">\n                        <option value=\"0.5\">50%<\/option>\n                        <option value=\"0.6\">60%<\/option>\n                        <option value=\"0.7\">70%<\/option>\n                        <option value=\"0.8\" selected>80%<\/option>\n                        <option value=\"0.9\">90%<\/option>\n                        <option value=\"0.95\">95%<\/option>\n                    <\/select>\n                    <small class=\"field-help\">JPG\/WebP\/AVIF s\u1eed d\u1ee5ng ch\u1ea5t l\u01b0\u1ee3ng. PNG\/BMP kh\u00f4ng d\u00f9ng.<\/small>\n                <\/label>\n\n                <label>\n                    \u0110\u1ecbnh d\u1ea1ng xu\u1ea5t\n                    <select class=\"compress-output\">\n                        <option value=\"image\/webp\" selected>WebP<\/option>\n                        <option value=\"image\/avif\">AVIF<\/option>\n                        <option value=\"image\/jpeg\">JPG<\/option>\n                        <option value=\"image\/png\">PNG<\/option>\n                        <option value=\"image\/bmp\">BMP<\/option>\n                    <\/select>\n                <\/label>\n            <\/div>\n\n            <div class=\"compress-list\"><\/div>\n\n            <button type=\"button\" class=\"itool-primary compress-start\">\n                \ud83d\udddc\ufe0f B\u1eaft \u0111\u1ea7u n\u00e9n\n            <\/button>\n\n            <div class=\"itool-message\"><\/div>\n        <\/div>\n        \n            \n        <\/div>\n\n        <style>\n.itools-wrapper{\n    --it-primary:#111827;\n    --it-border:#e2e8f0;\n    --it-bg:#fff;\n    --it-soft:#f8fafc;\n    --it-text:#111827;\n    --it-muted:#64748b;\n    max-width:1100px;\n    margin:30px auto;\n    font-family:Arial,Helvetica,sans-serif;\n    color:var(--it-text)\n}\n.itools-wrapper *,.itools-wrapper *::before,.itools-wrapper *::after{box-sizing:border-box}\n.itools-tabs,.converter-mode-tabs,.editor-mode-tabs{\n    display:grid;\n    grid-template-columns:repeat(4,1fr);\n    gap:6px;\n    padding:6px;\n    margin-bottom:20px;\n    background:#f8fafc;\n    border:1px solid var(--it-border);\n    border-radius:14px\n}\n.converter-mode-tabs,.editor-mode-tabs{grid-template-columns:1fr 1fr}\n.itools-tab,.converter-mode,.editor-mode{\n    border:0;\n    background:transparent;\n    padding:14px 10px;\n    border-radius:10px;\n    cursor:pointer;\n    font-size:15px;\n    font-weight:600;\n    color:#64748b\n}\n.itools-tab:hover,.converter-mode:hover,.editor-mode:hover{background:#fff}\n.itools-tab.active,.converter-mode.active,.editor-mode.active{\n    background:#111827;\n    color:#fff\n}\n.itools-panel,.converter-mode-panel,.editor-mode-panel{display:none}\n.itools-panel.active,.converter-mode-panel.active,.editor-mode-panel.active{display:block}\n.itool-box{\n    background:#fff;\n    border:1px solid var(--it-border);\n    border-radius:18px;\n    padding:28px;\n    box-shadow:0 8px 30px rgba(15,23,42,.05)\n}\n.itool-title{text-align:center;margin-bottom:25px}\n.itool-title h2{margin:0 0 8px;font-size:27px}\n.itool-title p{margin:0;color:var(--it-muted)}\n.itool-upload{\n    min-height:170px;\n    border:2px dashed #cbd5e1;\n    border-radius:16px;\n    display:flex;\n    align-items:center;\n    justify-content:center;\n    flex-direction:column;\n    gap:7px;\n    cursor:pointer;\n    background:#fafafa;\n    transition:.2s\n}\n.itool-upload:hover,.itool-upload.dragover{\n    border-color:#111827;\n    background:#f1f5f9\n}\n.itool-upload input{display:none}\n.upload-icon{font-size:34px}\n.itool-upload strong{font-size:16px}\n.itool-upload small{color:#64748b;text-align:center}\n.small-upload{min-height:130px}\n.compress-options,.converter-options,.document-options,.batch-options{\n    display:grid;\n    grid-template-columns:repeat(2,1fr);\n    gap:15px;\n    margin:20px 0\n}\n.document-options{grid-template-columns:repeat(4,1fr)}\n.batch-options{grid-template-columns:repeat(3,1fr)}\n.itool-box label{\n    display:block;\n    font-size:14px;\n    font-weight:600;\n    color:#334155\n}\n.itool-box select,\n.itool-box input[type=number],\n.itool-box input[type=text]{\n    display:block;\n    width:100%;\n    margin-top:7px;\n    border:1px solid #cbd5e1;\n    border-radius:9px;\n    padding:11px;\n    background:#fff;\n    font-size:14px\n}\n.itool-box input[type=range]{width:100%;margin-top:12px}\n.itool-box input[type=color]{\n    display:block;\n    width:100%;\n    height:42px;\n    margin-top:7px;\n    padding:2px;\n    border:1px solid #cbd5e1;\n    border-radius:9px;\n    background:#fff\n}\n.field-help{\n    display:block;\n    margin-top:5px;\n    color:#94a3b8!important;\n    font-size:12px!important;\n    font-weight:400!important\n}\n.itool-primary{\n    width:100%;\n    border:0;\n    border-radius:10px;\n    padding:14px 18px;\n    background:#111827;\n    color:#fff;\n    font-size:15px;\n    font-weight:700;\n    cursor:pointer;\n    margin-top:20px\n}\n.itool-primary:hover{background:#000}\n.itool-primary:disabled{opacity:.6;cursor:not-allowed}\n.itool-secondary{\n    border:1px solid #cbd5e1;\n    background:#fff;\n    border-radius:9px;\n    padding:10px;\n    cursor:pointer;\n    font-weight:600\n}\n.itool-secondary:hover{background:#f8fafc}\n.itool-message{\n    margin-top:15px;\n    text-align:center;\n    color:#64748b;\n    font-size:14px\n}\n.compress-list,.convert-list,.document-list{margin-top:15px}\n.file-row{\n    display:flex;\n    align-items:center;\n    justify-content:space-between;\n    gap:12px;\n    padding:12px;\n    border:1px solid #e2e8f0;\n    border-radius:10px;\n    margin-bottom:8px;\n    background:#fff\n}\n.file-name{\n    overflow:hidden;\n    text-overflow:ellipsis;\n    white-space:nowrap\n}\n.file-size{color:#64748b;font-size:13px}\n.download-link{\n    display:inline-block;\n    margin-top:10px;\n    padding:9px 13px;\n    background:#111827;\n    color:#fff!important;\n    text-decoration:none;\n    border-radius:8px;\n    font-size:13px\n}\n.crop-editor{\n    display:grid;\n    grid-template-columns:minmax(0,1fr) 260px;\n    gap:20px;\n    margin-top:22px\n}\n.crop-stage{\n    position:relative;\n    min-height:450px;\n    background:#111827;\n    border-radius:12px;\n    overflow:hidden;\n    display:flex;\n    align-items:center;\n    justify-content:center;\n    touch-action:none\n}\n.crop-canvas{\n    display:block;\n    max-width:100%;\n    max-height:650px;\n    user-select:none;\n    pointer-events:none\n}\n.crop-selection{position:absolute;inset:0;pointer-events:none}\n.crop-shade{position:absolute;background:rgba(0,0,0,.58);pointer-events:none}\n.shade-top{left:0;right:0;top:0}\n.shade-left{left:0}\n.shade-right{right:0}\n.shade-bottom{left:0;right:0;bottom:0}\n.crop-box{\n    position:absolute;\n    border:2px solid #fff;\n    box-shadow:0 0 0 1px rgba(0,0,0,.4);\n    cursor:move;\n    pointer-events:auto;\n    touch-action:none\n}\n.crop-move-area{position:absolute;inset:10px;cursor:move}\n.crop-handle{\n    position:absolute;\n    width:13px;\n    height:13px;\n    background:#fff;\n    border:2px solid #111827;\n    border-radius:3px;\n    z-index:10\n}\n.crop-handle.nw{left:-7px;top:-7px;cursor:nwse-resize}\n.crop-handle.n{left:50%;top:-7px;transform:translateX(-50%);cursor:ns-resize}\n.crop-handle.ne{right:-7px;top:-7px;cursor:nesw-resize}\n.crop-handle.e{right:-7px;top:50%;transform:translateY(-50%);cursor:ew-resize}\n.crop-handle.se{right:-7px;bottom:-7px;cursor:nwse-resize}\n.crop-handle.s{left:50%;bottom:-7px;transform:translateX(-50%);cursor:ns-resize}\n.crop-handle.sw{left:-7px;bottom:-7px;cursor:nesw-resize}\n.crop-handle.w{left:-7px;top:50%;transform:translateY(-50%);cursor:ew-resize}\n.crop-controls{\n    display:flex;\n    flex-direction:column;\n    gap:15px;\n    padding:15px;\n    background:#f8fafc;\n    border:1px solid #e2e8f0;\n    border-radius:12px\n}\n.crop-control-title{font-size:17px;font-weight:700}\n.crop-zoom-title{font-size:14px;font-weight:600;margin-top:5px}\n.crop-buttons{display:grid;grid-template-columns:1fr 1fr;gap:8px}\n.check-line{display:flex!important;align-items:center;gap:8px}\n.check-line input{margin:0}\n.crop-info{\n    display:flex;\n    justify-content:space-between;\n    gap:15px;\n    padding:12px 0;\n    color:#64748b;\n    font-size:13px\n}\n.result-card{\n    margin-top:20px;\n    padding:18px;\n    background:#f8fafc;\n    border-radius:12px;\n    text-align:center\n}\n.result-card img{\n    max-width:100%;\n    max-height:500px;\n    display:block;\n    margin:0 auto 15px;\n    border-radius:8px\n}\n.watermark-upload-grid{\n    display:grid;\n    grid-template-columns:1fr 1fr;\n    gap:15px\n}\n.watermark-workspace{\n    display:grid;\n    grid-template-columns:minmax(0,1.5fr) minmax(270px,.7fr);\n    gap:25px;\n    margin-top:20px;\n    align-items:start\n}\n@media(min-width:751px){\n    .watermark-workspace{\n        height:min(650px,calc(100vh - 40px));\n        min-height:300px\n    }\n    .watermark-preview{\n        height:100%;\n        min-height:0\n    }\n    .watermark-settings{\n        height:100%;\n        max-height:100%;\n        overflow-y:auto;\n        overflow-x:hidden;\n        padding-right:5px;\n        box-sizing:border-box;\n        overscroll-behavior:contain;\n        min-height:0\n    }\n    .watermark-settings .text-style-buttons{display:flex;gap:6px;flex-wrap:wrap;margin-top:5px}\n.text-style-btn{min-width:36px;height:34px;padding:0 9px;border:1px solid #d1d5db;background:#fff;border-radius:8px;cursor:pointer;font-size:16px;line-height:1}\n.text-style-btn.active{background:#111827;color:#fff;border-color:#111827}\n.emoji-palette{\n        display:grid;\n        grid-template-columns:repeat(8,minmax(0,1fr));\n        width:100%;\n        max-width:100%;\n        box-sizing:border-box;\n        overflow:visible\n    }\n    .watermark-settings .emoji-choice{\n        min-width:0;\n        width:100%;\n        max-width:100%;\n        box-sizing:border-box;\n        display:flex;\n        align-items:center;\n        justify-content:center;\n        overflow:hidden\n    }\n}\n.watermark-workspace{\n    align-items:start\n}\n.watermark-preview{\n    min-height:300px;\n    height:min(650px,calc(100vh - 40px));\n    background:#e5e7eb;\n    border-radius:12px;\n    display:flex;\n    align-items:center;\n    justify-content:center;\n    overflow:hidden;\n    position:relative;\n    align-self:start;\n    touch-action:none;\n    box-sizing:border-box;\n    z-index:2\n}\n.watermark-canvas{\n    max-width:100%;\n    max-height:100%;\n    display:block\n}\n.watermark-drag-logo{\n    position:absolute;\n    border:2px dashed #111827;\n    cursor:move;\n    touch-action:none;\n    user-select:none;\n    line-height:0;\n    z-index:5\n}\n.watermark-logo-preview{\n    display:block;\n    width:100%;\n    height:100%;\n    object-fit:contain;\n    pointer-events:none;\n    user-select:none\n}\n.wm-resize-handle{\n    position:absolute;\n    width:15px;\n    height:15px;\n    right:-8px;\n    bottom:-8px;\n    background:#fff;\n    border:2px solid #111827;\n    border-radius:3px;\n    cursor:nwse-resize;\n    touch-action:none\n}\n.watermark-settings{\n    display:flex;\n    flex-direction:column;\n    gap:12px\n}\n.settings-section-title{\n    font-size:16px;\n    font-weight:800;\n    padding-top:5px;\n    border-top:1px solid #e2e8f0\n}\n.range-value,.opacity-value,.margin-value,\n.batch-size-value,.batch-opacity-value,.batch-margin-value{\n    display:block;\n    text-align:right;\n    color:#64748b;\n    font-size:12px\n}\n.editor-two-col{\n    display:grid;\n    grid-template-columns:1fr 1fr;\n    gap:10px\n}\n.editor-inline-buttons{\n    display:grid;\n    grid-template-columns:1fr 1fr;\n    gap:8px\n}\n.emoji-palette{\n    display:grid;\n    grid-template-columns:repeat(8,minmax(0,1fr));\n    gap:5px;\n    width:100%;\n    max-width:100%;\n    box-sizing:border-box;\n    overflow:hidden\n}\n.emoji-choice{\n    min-width:0!important;\n    width:100%!important;\n    max-width:100%!important;\n    min-height:42px!important;\n    box-sizing:border-box;\n    border:1px solid #e2e8f0!important;\n    background:#fff!important;\n    color:#111827!important;\n    -webkit-text-fill-color:#111827!important;\n    border-radius:8px;\n    padding:5px!important;\n    margin:0!important;\n    cursor:pointer!important;\n    font-family:\"Segoe UI Emoji\",\"Apple Color Emoji\",\"Noto Color Emoji\",\"Twemoji Mozilla\",\"Arial Unicode MS\",sans-serif!important;\n    font-size:24px!important;\n    font-style:normal!important;\n    font-weight:400!important;\n    line-height:1!important;\n    text-align:center!important;\n    text-indent:0!important;\n    text-transform:none!important;\n    letter-spacing:normal!important;\n    white-space:nowrap!important;\n    display:flex!important;\n    align-items:center!important;\n    justify-content:center!important;\n    visibility:visible!important;\n    opacity:1!important;\n    overflow:visible!important;\n    appearance:none!important;\n    -webkit-appearance:none!important;\n    text-shadow:none!important;\n    transform:none;\n}\n.emoji-choice:hover{background:#f8fafc}\n.editor-object-layer{\n    position:absolute;\n    inset:0;\n    pointer-events:none;\n    z-index:20!important;\n    overflow:visible;\n}\n.editor-object{\n    position:absolute;\n    transform:translate(-50%,-50%);\n    cursor:move;\n    user-select:none;\n    touch-action:none;\n    pointer-events:auto;\n    white-space:pre\n}\n.editor-object.selected{\n    outline:2px dashed #111827;\n    outline-offset:5px\n}\n.editor-object-resize-handle{\n    position:absolute;\n    width:14px;\n    height:14px;\n    right:-10px;\n    bottom:-10px;\n    box-sizing:border-box;\n    background:#fff;\n    border:2px solid #111827;\n    border-radius:50%;\n    cursor:nwse-resize;\n    touch-action:none;\n    z-index:10\n}\n.editor-object.text-object{\n    line-height:1.15;\n    text-align:center\n}\n.editor-object.emoji-object{\n    line-height:1\n}\n.converter-notice{\n    display:flex;\n    flex-direction:column;\n    gap:4px;\n    padding:14px;\n    background:#f8fafc;\n    border:1px solid #e2e8f0;\n    border-radius:10px;\n    margin-bottom:15px\n}\n.converter-notice span{\n    color:#64748b;\n    font-size:13px\n}\n.batch-file-list{\n    margin-top:15px;\n    max-height:300px;\n    overflow:auto\n}\n.batch-result{\n    margin-top:20px\n}\n.batch-result-grid{\n    display:grid;\n    grid-template-columns:repeat(3,1fr);\n    gap:10px\n}\n.batch-result-item{\n    padding:10px;\n    border:1px solid #e2e8f0;\n    border-radius:10px;\n    background:#f8fafc;\n    text-align:center\n}\n.batch-result-item img{\n    max-width:100%;\n    max-height:180px;\n    display:block;\n    margin:0 auto 8px;\n    border-radius:6px\n}\n.document-preview-host{\n    position:fixed;\n    left:-100000px;\n    top:-100000px;\n    width:1200px;\n    background:#fff;\n    z-index:-1\n}\n.document-render-page{\n    background:#fff;\n    box-sizing:border-box;\n    overflow:hidden;\n    margin:0;\n    padding:45px;\n    color:#111;\n    font-family:Arial,sans-serif;\n    width:1000px;\n    min-height:1414px\n}\n.document-render-page table{\n    width:100%;\n    border-collapse:collapse\n}\n.document-render-page td,\n.document-render-page th{\n    border:1px solid #999;\n    padding:5px;\n    font-size:14px\n}\n.document-render-page img{\n    max-width:100%\n}\n.document-render-page h1{font-size:30px}\n.document-render-page h2{font-size:25px}\n.document-render-page h3{font-size:21px}\n.document-render-page p{line-height:1.45}\n@media(max-width:750px){\n    .itools-wrapper{margin:15px auto}\n    .itool-box{padding:17px}\n    .itools-tabs{grid-template-columns:1fr 1fr}\n    .itool-title h2{font-size:23px}\n    .compress-options,\n    .converter-options,\n    .document-options,\n    .batch-options,\n    .crop-editor,\n    .watermark-workspace,\n    .watermark-upload-grid{grid-template-columns:1fr}\n    .crop-stage{min-height:320px}\n    .crop-info{flex-direction:column}\n    .file-row{flex-wrap:wrap}\n    .watermark-settings{\n        height:auto;\n        max-height:none;\n        overflow:visible\n        }\n    .watermark-preview{\n        position:relative;\n        height:auto;\n        min-height:300px\n    }\n    .watermark-canvas{\n        max-height:650px\n    }\n    .watermark-drag-logo{max-width:70%}\n    .emoji-palette{grid-template-columns:repeat(6,minmax(0,1fr))}\n    .batch-result-grid{grid-template-columns:1fr 1fr}\n}\n\n\/* =========================================================\n   V2 RESPONSIVE \/ MOBILE EDITOR FIXES\n   ========================================================= *\/\n.emoji-choice{\n    font-family:\"Segoe UI Emoji\",\"Apple Color Emoji\",\"Noto Color Emoji\",\"Twemoji Mozilla\",\"Arial Unicode MS\",sans-serif!important;\n    font-variant-emoji:emoji;\n    font-size:24px!important;\n    min-height:42px!important;\n    color:#111827!important;\n    -webkit-text-fill-color:#111827!important;\n    line-height:1!important;\n    visibility:visible!important;\n    opacity:1!important;\n}\n.emoji-choice.selected{background:#111827;color:#fff;border-color:#111827;transform:translateY(-1px)}\n.watermark-settings{overscroll-behavior:contain;}\n@media(max-width:750px){\n    .watermark-workspace{\n        grid-template-columns:1fr;\n        align-items:start;\n        gap:16px;\n    }\n    .watermark-preview{\n        position:sticky;\n        top:10px;\n        z-index:30;\n        height:min(48vh,420px);\n        min-height:230px;\n        max-height:420px;\n        overflow:hidden;\n        box-shadow:0 5px 18px rgba(0,0,0,.12);\n    }\n    .watermark-settings{\n        height:auto!important;\n        max-height:none!important;\n        overflow:visible!important;\n        min-height:0;\n        position:relative;\n        z-index:10;\n    }\n    .watermark-canvas{max-width:100%;max-height:100%;}\n    .watermark-drag-logo{max-width:70%;}\n    .emoji-palette{grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;overflow:visible;}\n    .emoji-choice{font-size:22px;padding:7px 4px;min-height:40px;}\n}\n@media(max-width:750px) and (max-height:700px){\n    .watermark-preview{height:42vh;min-height:210px;max-height:330px;}\n}\n@media(min-width:751px){\n    .watermark-settings{overflow-y:auto;overflow-x:hidden;}\n}\n\/* Emoji rendering hardening: override common WordPress\/theme button rules. *\/\n.emoji-palette{\n    display:grid!important;\n    visibility:visible!important;\n    opacity:1!important;\n    position:relative;\n    z-index:25;\n    isolation:isolate;\n}\n.emoji-palette .emoji-choice{\n    visibility:visible!important;\n    opacity:1!important;\n    color:#111827!important;\n    -webkit-text-fill-color:#111827!important;\n    background:#fff!important;\n    font-family:\"Segoe UI Emoji\",\"Apple Color Emoji\",\"Noto Color Emoji\",\"Twemoji Mozilla\",\"Arial Unicode MS\",sans-serif!important;\n    font-size:24px!important;\n    line-height:1!important;\n    min-height:42px!important;\n    display:flex!important;\n}\n.emoji-palette .emoji-choice:hover,.emoji-palette .emoji-choice:focus{\n    color:#111827!important;\n    -webkit-text-fill-color:#111827!important;\n    background:#f8fafc!important;\n    outline:2px solid #cbd5e1;\n    outline-offset:1px;\n}\n.emoji-palette .emoji-choice.selected{\n    background:#111827!important;\n    color:#fff!important;\n    -webkit-text-fill-color:#fff!important;\n}\n@media(max-width:750px){\n    .emoji-palette .emoji-choice{font-size:24px!important;min-height:42px!important;padding:5px!important;}\n}\n\n\/* Emoji picker: use real image icons so WordPress emoji replacement cannot hide them. *\/\n.emoji-palette .emoji-choice .image-tools-emoji-icon,\n.emoji-palette .emoji-choice .image-tools-emoji-fallback{\n    display:block!important;\n    visibility:visible!important;\n    opacity:1!important;\n    pointer-events:none!important;\n}\n.emoji-palette .emoji-choice .image-tools-emoji-icon{\n    width:28px!important;height:28px!important;max-width:28px!important;max-height:28px!important;\n    object-fit:contain!important;border:0!important;background:transparent!important;\n}\n@media(max-width:750px){\n    .emoji-palette .emoji-choice .image-tools-emoji-icon{\n        width:28px!important;height:28px!important;max-width:28px!important;max-height:28px!important;\n    }\n}\n\n\/* =========================================================\n   V2.3 MOBILE EDITOR SCROLL + EMOJI HARD FIX\n   ========================================================= *\/\n@media(max-width:750px){\n    .watermark-workspace{\n        display:grid!important;\n        grid-template-columns:1fr!important;\n        grid-template-rows:minmax(220px,44vh) minmax(0,1fr)!important;\n        gap:12px!important;\n        height:calc(100svh - 24px)!important;\n        min-height:520px!important;\n        max-height:820px!important;\n        overflow:hidden!important;\n        align-items:stretch!important;\n    }\n    .watermark-preview{\n        position:relative!important;\n        inset:auto!important;\n        width:100%!important;\n        height:auto!important;\n        min-height:220px!important;\n        max-height:none!important;\n        overflow:hidden!important;\n        z-index:2!important;\n        align-self:stretch!important;\n        box-shadow:0 4px 14px rgba(0,0,0,.12)!important;\n    }\n    .watermark-settings{\n        grid-row:2!important;\n        height:auto!important;\n        min-height:0!important;\n        max-height:none!important;\n        overflow-y:auto!important;\n        overflow-x:hidden!important;\n        -webkit-overflow-scrolling:touch!important;\n        overscroll-behavior:contain!important;\n        touch-action:pan-y!important;\n        padding-right:4px!important;\n        position:relative!important;\n        z-index:10!important;\n    }\n}\n@media(max-width:750px) and (max-height:700px){\n    .watermark-workspace{\n        grid-template-rows:minmax(200px,40vh) minmax(0,1fr)!important;\n        min-height:500px!important;\n    }\n}\n\n\/* =========================================================\n   V4 EMOJI PICKER LAYOUT FIX\n   - Palette gets its own bounded scroll area.\n   - It can never overlap the controls below it.\n   - The parent settings panel remains independently scrollable.\n   - No high z-index on the palette.\n   ========================================================= *\/\n.emoji-palette{\n    display:grid!important;\n    grid-template-columns:repeat(8,minmax(0,1fr))!important;\n    gap:6px!important;\n    width:100%!important;\n    max-width:100%!important;\n    height:auto!important;\n    max-height:280px!important;\n    min-height:0!important;\n    box-sizing:border-box!important;\n    padding:2px!important;\n    margin:0!important;\n    visibility:visible!important;\n    opacity:1!important;\n    overflow-y:auto!important;\n    overflow-x:hidden!important;\n    position:relative!important;\n    z-index:1!important;\n    align-content:start!important;\n    overscroll-behavior:contain!important;\n    -webkit-overflow-scrolling:touch!important;\n}\n.emoji-palette .emoji-choice{\n    appearance:none!important;\n    -webkit-appearance:none!important;\n    display:flex!important;\n    align-items:center!important;\n    justify-content:center!important;\n    width:100%!important;\n    min-width:0!important;\n    min-height:42px!important;\n    height:42px!important;\n    padding:4px!important;\n    margin:0!important;\n    border:1px solid #dbe3ec!important;\n    border-radius:8px!important;\n    background:#fff!important;\n    color:#111827!important;\n    -webkit-text-fill-color:#111827!important;\n    opacity:1!important;\n    visibility:visible!important;\n    overflow:hidden!important;\n    cursor:pointer!important;\n    box-sizing:border-box!important;\n    flex:none!important;\n}\n.emoji-palette .emoji-choice img.image-tools-emoji-icon{\n    display:block!important;\n    width:30px!important;\n    height:30px!important;\n    max-width:30px!important;\n    max-height:30px!important;\n    min-width:30px!important;\n    min-height:30px!important;\n    object-fit:contain!important;\n    visibility:visible!important;\n    opacity:1!important;\n    pointer-events:none!important;\n    border:0!important;\n    background:transparent!important;\n    box-shadow:none!important;\n}\n.emoji-palette .emoji-choice .image-tools-emoji-fallback{\n    display:block!important;\n    font-family:\"Segoe UI Emoji\",\"Apple Color Emoji\",\"Noto Color Emoji\",\"Arial Unicode MS\",sans-serif!important;\n    font-size:27px!important;\n    line-height:1!important;\n    visibility:visible!important;\n    opacity:1!important;\n    pointer-events:none!important;\n}\n.emoji-palette .emoji-choice.selected{\n    background:#111827!important;\n    border-color:#111827!important;\n    color:#fff!important;\n    -webkit-text-fill-color:#fff!important;\n    transform:none!important;\n}\n\n\/* Desktop: keep the whole right settings column scrollable. *\/\n@media(min-width:751px){\n    .watermark-workspace{\n        height:min(650px,calc(100vh - 40px))!important;\n        min-height:300px!important;\n        align-items:stretch!important;\n    }\n    .watermark-settings{\n        height:100%!important;\n        max-height:100%!important;\n        min-height:0!important;\n        overflow-y:auto!important;\n        overflow-x:hidden!important;\n        padding-right:8px!important;\n        box-sizing:border-box!important;\n        overscroll-behavior:contain!important;\n    }\n}\n\n\/* Mobile: preview is a fixed-height first row; tools have their own scroll. *\/\n@media(max-width:750px){\n    .watermark-workspace{\n        display:grid!important;\n        grid-template-columns:1fr!important;\n        grid-template-rows:minmax(210px,42vh) minmax(0,1fr)!important;\n        gap:12px!important;\n        height:calc(100svh - 24px)!important;\n        min-height:500px!important;\n        max-height:900px!important;\n        overflow:hidden!important;\n        align-items:stretch!important;\n    }\n    .watermark-preview{\n        grid-row:1!important;\n        position:relative!important;\n        width:100%!important;\n        height:100%!important;\n        min-height:210px!important;\n        max-height:none!important;\n        overflow:hidden!important;\n        z-index:2!important;\n    }\n    .watermark-settings{\n        grid-row:2!important;\n        height:100%!important;\n        min-height:0!important;\n        max-height:none!important;\n        overflow-y:auto!important;\n        overflow-x:hidden!important;\n        -webkit-overflow-scrolling:touch!important;\n        overscroll-behavior:contain!important;\n        touch-action:pan-y!important;\n        padding-right:6px!important;\n        position:relative!important;\n        z-index:3!important;\n    }\n    .emoji-palette{\n        grid-template-columns:repeat(6,minmax(0,1fr))!important;\n        max-height:230px!important;\n        gap:6px!important;\n    }\n    .emoji-palette .emoji-choice{\n        min-height:40px!important;\n        height:40px!important;\n    }\n}\n@media(max-width:750px) and (max-height:700px){\n    .watermark-workspace{\n        grid-template-rows:minmax(190px,38vh) minmax(0,1fr)!important;\n        min-height:480px!important;\n    }\n    .emoji-palette{max-height:200px!important;}\n}\n\n\/* =========================================================\n   V5 FINAL EMOJI \/ SETTINGS SCROLL FIX\n   - Do NOT create a nested scrollbar inside the emoji palette.\n   - The entire tools panel (.watermark-settings) is the only\n     vertical scrolling area on desktop and mobile.\n   - The emoji list expands naturally, so no rows are clipped\n     or covered by the controls below.\n   ========================================================= *\/\n.emoji-palette{\n    display:grid!important;\n    grid-template-columns:repeat(8,minmax(0,1fr))!important;\n    gap:6px!important;\n    width:100%!important;\n    max-width:100%!important;\n    height:auto!important;\n    max-height:none!important;\n    min-height:0!important;\n    box-sizing:border-box!important;\n    padding:2px 2px 6px!important;\n    margin:0!important;\n    overflow:visible!important;\n    position:relative!important;\n    z-index:auto!important;\n    align-content:start!important;\n    isolation:auto!important;\n}\n.emoji-palette .emoji-choice{\n    position:relative!important;\n    z-index:1!important;\n    display:flex!important;\n    align-items:center!important;\n    justify-content:center!important;\n    width:100%!important;\n    min-width:0!important;\n    max-width:100%!important;\n    min-height:42px!important;\n    height:42px!important;\n    padding:4px!important;\n    margin:0!important;\n    box-sizing:border-box!important;\n    overflow:hidden!important;\n    flex:none!important;\n}\n.emoji-palette .emoji-choice img.image-tools-emoji-icon{\n    display:block!important;\n    width:30px!important;\n    height:30px!important;\n    min-width:30px!important;\n    min-height:30px!important;\n    max-width:30px!important;\n    max-height:30px!important;\n    object-fit:contain!important;\n    visibility:visible!important;\n    opacity:1!important;\n    pointer-events:none!important;\n    border:0!important;\n    background:transparent!important;\n    box-shadow:none!important;\n}\n.emoji-palette .emoji-choice .image-tools-emoji-fallback{\n    display:block!important;\n    font-family:\"Segoe UI Emoji\",\"Apple Color Emoji\",\"Noto Color Emoji\",\"Arial Unicode MS\",sans-serif!important;\n    font-size:27px!important;\n    line-height:1!important;\n    visibility:visible!important;\n    opacity:1!important;\n    pointer-events:none!important;\n}\n.emoji-palette .emoji-choice.selected{\n    background:#111827!important;\n    border-color:#111827!important;\n    color:#fff!important;\n    -webkit-text-fill-color:#fff!important;\n    transform:none!important;\n}\n\n\/* Desktop: the settings panel is the sole scroll container. *\/\n@media(min-width:751px){\n    .watermark-workspace{\n        height:min(650px,calc(100vh - 40px))!important;\n        min-height:300px!important;\n        align-items:stretch!important;\n        overflow:visible!important;\n    }\n    .watermark-settings{\n        height:100%!important;\n        max-height:100%!important;\n        min-height:0!important;\n        overflow-y:auto!important;\n        overflow-x:hidden!important;\n        overscroll-behavior:contain!important;\n        -webkit-overflow-scrolling:touch!important;\n        padding-right:8px!important;\n        box-sizing:border-box!important;\n    }\n}\n\n\/* Mobile: preview stays in the first row; the entire tools panel\n   in the second row scrolls as one continuous area. *\/\n@media(max-width:750px){\n    .watermark-workspace{\n        display:grid!important;\n        grid-template-columns:1fr!important;\n        grid-template-rows:minmax(210px,42vh) minmax(0,1fr)!important;\n        gap:12px!important;\n        height:calc(100svh - 24px)!important;\n        min-height:500px!important;\n        max-height:900px!important;\n        overflow:hidden!important;\n        align-items:stretch!important;\n    }\n    .watermark-preview{\n        grid-row:1!important;\n        position:relative!important;\n        width:100%!important;\n        height:100%!important;\n        min-height:210px!important;\n        max-height:none!important;\n        overflow:hidden!important;\n        z-index:2!important;\n    }\n    .watermark-settings{\n        grid-row:2!important;\n        height:100%!important;\n        min-height:0!important;\n        max-height:none!important;\n        overflow-y:auto!important;\n        overflow-x:hidden!important;\n        -webkit-overflow-scrolling:touch!important;\n        overscroll-behavior:contain!important;\n        touch-action:pan-y!important;\n        padding:0 6px 18px 0!important;\n        position:relative!important;\n        z-index:3!important;\n        box-sizing:border-box!important;\n    }\n    .emoji-palette{\n        grid-template-columns:repeat(6,minmax(0,1fr))!important;\n        gap:6px!important;\n        max-height:none!important;\n        overflow:visible!important;\n    }\n    .emoji-palette .emoji-choice{\n        min-height:40px!important;\n        height:40px!important;\n    }\n}\n@media(max-width:750px) and (max-height:700px){\n    .watermark-workspace{\n        grid-template-rows:minmax(190px,38vh) minmax(0,1fr)!important;\n        min-height:480px!important;\n    }\n}\n\n\n\n\/* =========================================================\n   V6 EMOJI GRID FINAL FIX\n   - Every emoji button is a true square.\n   - The palette participates in normal document flow.\n   - No emoji row can overlap the controls below it.\n   - Only .watermark-settings is a vertical scroll container.\n   ========================================================= *\/\n.emoji-palette{\n    display:grid!important;\n    grid-template-columns:repeat(8,minmax(0,1fr))!important;\n    grid-auto-rows:auto!important;\n    gap:6px!important;\n    width:100%!important;\n    height:auto!important;\n    min-height:0!important;\n    max-height:none!important;\n    margin:0!important;\n    padding:2px 2px 8px!important;\n    box-sizing:border-box!important;\n    overflow:visible!important;\n    position:relative!important;\n    z-index:1!important;\n    align-items:start!important;\n    align-content:start!important;\n}\n\n.emoji-palette > .emoji-choice{\n    position:relative!important;\n    display:flex!important;\n    align-items:center!important;\n    justify-content:center!important;\n    width:100%!important;\n    height:auto!important;\n    min-width:0!important;\n    min-height:0!important;\n    max-width:none!important;\n    max-height:none!important;\n    aspect-ratio:1 \/ 1!important;\n    box-sizing:border-box!important;\n    padding:2px!important;\n    margin:0!important;\n    border:1px solid #dbe3ec!important;\n    border-radius:9px!important;\n    background:#fff!important;\n    overflow:hidden!important;\n    flex:none!important;\n    line-height:1!important;\n    font-size:24px!important;\n}\n\n.emoji-palette > .emoji-choice > .image-tools-emoji-icon{\n    display:block!important;\n    width:72%!important;\n    height:72%!important;\n    min-width:0!important;\n    min-height:0!important;\n    max-width:100%!important;\n    max-height:100%!important;\n    object-fit:contain!important;\n    margin:0!important;\n    padding:0!important;\n    border:0!important;\n    box-shadow:none!important;\n    background:transparent!important;\n    pointer-events:none!important;\n}\n\n.emoji-palette > .emoji-choice > .image-tools-emoji-fallback{\n    display:flex!important;\n    align-items:center!important;\n    justify-content:center!important;\n    width:100%!important;\n    height:100%!important;\n    min-width:0!important;\n    min-height:0!important;\n    margin:0!important;\n    padding:0!important;\n    font-family:\"Segoe UI Emoji\",\"Apple Color Emoji\",\"Noto Color Emoji\",\"Arial Unicode MS\",sans-serif!important;\n    font-size:clamp(18px,2.2vw,28px)!important;\n    line-height:1!important;\n    white-space:nowrap!important;\n    pointer-events:none!important;\n}\n\n\/* Make sure the controls after the palette remain in normal flow. *\/\n.emoji-palette + *{\n    position:relative!important;\n    z-index:2!important;\n}\n\n@media(min-width:751px){\n    .emoji-palette{\n        grid-template-columns:repeat(8,minmax(0,1fr))!important;\n    }\n    .watermark-settings{\n        overflow-y:auto!important;\n        overflow-x:hidden!important;\n    }\n}\n\n@media(max-width:750px){\n    .emoji-palette{\n        grid-template-columns:repeat(6,minmax(0,1fr))!important;\n        gap:6px!important;\n        max-height:none!important;\n        overflow:visible!important;\n    }\n    .emoji-palette > .emoji-choice{\n        aspect-ratio:1 \/ 1!important;\n        min-height:0!important;\n        height:auto!important;\n        font-size:22px!important;\n    }\n    .watermark-settings{\n        overflow-y:auto!important;\n        overflow-x:hidden!important;\n        -webkit-overflow-scrolling:touch!important;\n    }\n}\n\n\n\/* =========================================================\n   V7 STABLE EMOJI LAYOUT\n   ========================================================= *\/\n.emoji-palette-shell{\n    display:block!important;width:100%!important;max-width:100%!important;\n    height:auto!important;min-height:0!important;max-height:none!important;\n    margin:0!important;padding:0!important;overflow:visible!important;\n    position:relative!important;clear:both!important;flex:0 0 auto!important;\n}\n.emoji-palette-shell .emoji-palette{\n    display:grid!important;grid-template-columns:repeat(8,minmax(0,1fr))!important;\n    grid-auto-flow:row!important;grid-auto-rows:auto!important;\n    align-items:start!important;align-content:start!important;justify-items:stretch!important;\n    gap:6px!important;width:100%!important;max-width:100%!important;\n    height:auto!important;min-height:0!important;max-height:none!important;\n    margin:0!important;padding:2px!important;overflow:visible!important;\n    position:static!important;box-sizing:border-box!important;\n}\n.emoji-palette-shell .emoji-palette > .emoji-choice{\n    display:flex!important;position:relative!important;width:100%!important;\n    max-width:100%!important;min-width:0!important;height:auto!important;\n    min-height:0!important;aspect-ratio:1 \/ 1!important;padding:3px!important;\n    margin:0!important;border:1px solid #dbe3ec!important;border-radius:9px!important;\n    box-sizing:border-box!important;align-items:center!important;justify-content:center!important;\n    overflow:hidden!important;line-height:1!important;white-space:normal!important;flex:none!important;\n}\n.emoji-palette-shell .emoji-palette > .emoji-choice img.image-tools-emoji-icon{\n    display:block!important;width:78%!important;height:78%!important;max-width:78%!important;\n    max-height:78%!important;min-width:0!important;min-height:0!important;object-fit:contain!important;\n    margin:0!important;padding:0!important;border:0!important;box-shadow:none!important;pointer-events:none!important;\n}\n.emoji-palette-shell .emoji-palette > .emoji-choice .image-tools-emoji-fallback{\n    display:block!important;width:auto!important;height:auto!important;max-width:100%!important;\n    max-height:100%!important;font-size:clamp(18px,5vw,28px)!important;line-height:1!important;\n    margin:0!important;padding:0!important;pointer-events:none!important;\n}\n.emoji-controls-row{\n    display:grid!important;grid-template-columns:minmax(0,1fr) minmax(0,1fr)!important;\n    gap:10px!important;width:100%!important;height:auto!important;min-height:0!important;\n    margin:0!important;padding:0!important;position:relative!important;clear:both!important;flex:0 0 auto!important;\n}\n.emoji-controls-row > label,.emoji-controls-row input{min-width:0!important;position:relative!important;}\n.emoji-controls-row ~ .itool-secondary{position:relative!important;clear:both!important;flex:0 0 auto!important;}\n.watermark-settings > *{position:relative;flex-shrink:0;}\n@media(min-width:751px){\n    .watermark-workspace{height:min(650px,calc(100vh - 40px))!important;min-height:300px!important;overflow:visible!important;align-items:stretch!important;}\n    .watermark-settings{height:100%!important;min-height:0!important;max-height:100%!important;overflow-y:auto!important;overflow-x:hidden!important;overscroll-behavior:contain!important;-webkit-overflow-scrolling:touch!important;padding-right:8px!important;}\n    .emoji-palette-shell .emoji-palette{grid-template-columns:repeat(8,minmax(0,1fr))!important;}\n}\n@media(max-width:750px){\n    .watermark-workspace{display:grid!important;grid-template-columns:minmax(0,1fr)!important;grid-template-rows:minmax(210px,42vh) minmax(0,1fr)!important;gap:12px!important;height:calc(100svh - 24px)!important;min-height:500px!important;max-height:900px!important;overflow:hidden!important;align-items:stretch!important;}\n    .watermark-preview{grid-row:1!important;width:100%!important;height:100%!important;min-height:210px!important;max-height:none!important;position:relative!important;overflow:hidden!important;z-index:2!important;}\n    .watermark-settings{grid-row:2!important;width:100%!important;height:100%!important;min-height:0!important;max-height:none!important;overflow-y:auto!important;overflow-x:hidden!important;overscroll-behavior:contain!important;-webkit-overflow-scrolling:touch!important;touch-action:pan-y!important;padding:0 6px 18px 0!important;position:relative!important;z-index:3!important;}\n    .emoji-palette-shell .emoji-palette{grid-template-columns:repeat(6,minmax(0,1fr))!important;gap:6px!important;}\n}\n@media(max-width:420px){.emoji-palette-shell .emoji-palette{grid-template-columns:repeat(6,minmax(0,1fr))!important;gap:5px!important;padding:2px!important;}}\n@media(max-width:750px) and (max-height:700px){.watermark-workspace{grid-template-rows:minmax(190px,38vh) minmax(0,1fr)!important;min-height:480px!important;}}\n\n\n\/* =========================================================\n   V8 IMAGE TRANSFORM + CROP\n   ========================================================= *\/\n.editor-transform-buttons,\n.editor-crop-buttons{display:grid;grid-template-columns:1fr 1fr;gap:8px}\n.editor-transform-status{\n    display:flex;flex-wrap:wrap;justify-content:space-between;gap:6px 12px;\n    padding:8px 10px;border:1px solid #e2e8f0;border-radius:9px;\n    background:#f8fafc;color:#475569;font-size:12px\n}\n.editor-transform-status strong{color:#111827}\n.editor-crop-help{display:block}\n.editor-crop-layer{\n    position:absolute;inset:0;z-index:15!important;pointer-events:none;\n    display:none;overflow:hidden;touch-action:none\n}\n.editor-crop-layer.active{display:block;z-index:40!important}\n.editor-crop-box{\n    position:absolute;left:5%;top:5%;width:90%;height:90%;\n    border:2px solid #fff;box-shadow:0 0 0 9999px rgba(15,23,42,.48),0 0 0 1px rgba(15,23,42,.85);\n    box-sizing:border-box;pointer-events:auto;touch-action:none;cursor:move\n}\n.editor-crop-box::before,.editor-crop-box::after{\n    content:\"\";position:absolute;pointer-events:none\n}\n.editor-crop-box::before{\n    inset:33.333% 0;border-top:1px solid rgba(255,255,255,.55);border-bottom:1px solid rgba(255,255,255,.55)\n}\n.editor-crop-box::after{\n    inset:0 33.333%;border-left:1px solid rgba(255,255,255,.55);border-right:1px solid rgba(255,255,255,.55)\n}\n.editor-crop-handle{\n    position:absolute;width:14px;height:14px;border:2px solid #111827;background:#fff;border-radius:3px;\n    box-sizing:border-box;pointer-events:auto;z-index:2\n}\n.editor-crop-handle.n{left:50%;top:-8px;transform:translateX(-50%);cursor:ns-resize}\n.editor-crop-handle.ne{right:-8px;top:-8px;cursor:nesw-resize}\n.editor-crop-handle.e{right:-8px;top:50%;transform:translateY(-50%);cursor:ew-resize}\n.editor-crop-handle.se{right:-8px;bottom:-8px;cursor:nwse-resize}\n.editor-crop-handle.s{left:50%;bottom:-8px;transform:translateX(-50%);cursor:ns-resize}\n.editor-crop-handle.sw{left:-8px;bottom:-8px;cursor:nesw-resize}\n.editor-crop-handle.w{left:-8px;top:50%;transform:translateY(-50%);cursor:ew-resize}\n.editor-crop-handle.nw{left:-8px;top:-8px;cursor:nwse-resize}\n.editor-crop-layer:not(.active){visibility:hidden}\n.watermark-preview.crop-mode .watermark-canvas{cursor:default}\n@media(max-width:750px){\n    .editor-transform-buttons,.editor-crop-buttons{grid-template-columns:1fr 1fr}\n    .editor-crop-handle{width:18px;height:18px}\n}\n\n<\/style>\n\n<script>\n(function(){\n\"use strict\";\n\n\/* =========================================================\n   GLOBAL HELPERS\n   ========================================================= *\/\n\nfunction formatBytes(bytes){\n    if(!bytes) return \"0 B\";\n    var u=[\"B\",\"KB\",\"MB\",\"GB\"], i=Math.min(\n        Math.floor(Math.log(bytes)\/Math.log(1024)),\n        u.length-1\n    );\n    return (bytes\/Math.pow(1024,i)).toFixed(i===0?0:2)+\" \"+u[i];\n}\n\nfunction extensionFromMime(type){\n    return ({\n        \"image\/jpeg\":\"jpg\",\n        \"image\/png\":\"png\",\n        \"image\/webp\":\"webp\",\n        \"image\/avif\":\"avif\",\n        \"image\/bmp\":\"bmp\"\n    })[type] || \"png\";\n}\n\nfunction loadImage(file){\n    return new Promise(function(resolve,reject){\n        var maxBytes=25*1024*1024;\n        var maxPixels=40000000;\n\n        if(!file || !\/^image\\\/\/i.test(file.type)){\n            reject(new Error(\"\u0110\u1ecbnh d\u1ea1ng \u1ea3nh kh\u00f4ng h\u1ee3p l\u1ec7.\"));\n            return;\n        }\n\n        if(file.size>maxBytes){\n            reject(new Error(\"\u1ea2nh v\u01b0\u1ee3t qu\u00e1 gi\u1edbi h\u1ea1n 25 MB.\"));\n            return;\n        }\n\n        var url=URL.createObjectURL(file);\n        var img=new Image();\n\n        img.onload=function(){\n            URL.revokeObjectURL(url);\n\n            if(!img.naturalWidth || !img.naturalHeight){\n                reject(new Error(\"\u1ea2nh kh\u00f4ng h\u1ee3p l\u1ec7.\"));\n                return;\n            }\n\n            if(img.naturalWidth*img.naturalHeight>maxPixels){\n                reject(new Error(\"\u1ea2nh c\u00f3 k\u00edch th\u01b0\u1edbc qu\u00e1 l\u1edbn. Gi\u1edbi h\u1ea1n 40 megapixel.\"));\n                return;\n            }\n\n            resolve(img);\n        };\n\n        img.onerror=function(){\n            URL.revokeObjectURL(url);\n            reject(new Error(\"Kh\u00f4ng th\u1ec3 \u0111\u1ecdc \u1ea3nh ho\u1eb7c \u0111\u1ecbnh d\u1ea1ng ch\u01b0a \u0111\u01b0\u1ee3c tr\u00ecnh duy\u1ec7t h\u1ed7 tr\u1ee3.\"));\n        };\n\n        img.src=url;\n    });\n}\n\nfunction canvasToBlob(canvas,type,quality){\n    return new Promise(function(resolve){\n        canvas.toBlob(\n            resolve,\n            type,\n            type===\"image\/png\" || type===\"image\/bmp\" ? undefined : quality\n        );\n    });\n}\n\nfunction bmpBlob(canvas){\n    var w=canvas.width,h=canvas.height;\n    var ctx=canvas.getContext(\"2d\");\n    var data=ctx.getImageData(0,0,w,h).data;\n    var rowSize=Math.floor((24*w+31)\/32)*4;\n    var pixelSize=rowSize*h;\n    var offset=54;\n    var total=offset+pixelSize;\n    var buf=new ArrayBuffer(total);\n    var dv=new DataView(buf);\n\n    function w16(p,v){dv.setUint16(p,v,true)}\n    function w32(p,v){dv.setUint32(p,v,true)}\n\n    w16(0,0x4d42);\n    w32(2,total);\n    w16(6,0);\n    w16(8,0);\n    w32(10,offset);\n    w32(14,40);\n    w32(18,w);\n    w32(22,h);\n    w16(26,1);\n    w16(28,24);\n    w32(30,0);\n    w32(34,pixelSize);\n    w32(38,2835);\n    w32(42,2835);\n    w32(46,0);\n    w32(50,0);\n\n    var p=offset;\n\n    for(var y=h-1;y>=0;y--){\n        for(var x=0;x<w;x++){\n            var i=(y*w+x)*4;\n            dv.setUint8(p++,data[i+2]);\n            dv.setUint8(p++,data[i+1]);\n            dv.setUint8(p++,data[i]);\n        }\n        while((p-offset)%rowSize)p++;\n    }\n\n    return new Blob([buf],{type:\"image\/bmp\"});\n}\n\nfunction makeBlob(canvas,type,quality){\n    if(type===\"image\/bmp\"){\n        return Promise.resolve(bmpBlob(canvas));\n    }\n    return canvasToBlob(canvas,type,quality);\n}\n\nfunction canEncode(type){\n    var c=document.createElement(\"canvas\");\n    try{\n        return !!c.toDataURL(type).startsWith(\"data:\"+type);\n    }catch(e){\n        return false;\n    }\n}\n\nfunction ensureCanvasSize(width,height,label){\n    var w=Math.ceil(Number(width)||0);\n    var h=Math.ceil(Number(height)||0);\n    var maxPixels=40000000;\n    var maxSide=32767;\n\n    if(w<1 || h<1){\n        throw new Error((label||\"\u1ea2nh\")+\" c\u00f3 k\u00edch th\u01b0\u1edbc kh\u00f4ng h\u1ee3p l\u1ec7.\");\n    }\n    if(w>maxSide || h>maxSide){\n        throw new Error((label||\"\u1ea2nh\")+\" v\u01b0\u1ee3t gi\u1edbi h\u1ea1n chi\u1ec1u d\u00e0i canvas c\u1ee7a tr\u00ecnh duy\u1ec7t.\");\n    }\n    if(w*h>maxPixels){\n        throw new Error((label||\"\u1ea2nh\")+\" qu\u00e1 l\u1edbn \u0111\u1ec3 x\u1eed l\u00fd an to\u00e0n tr\u00ean tr\u00ecnh duy\u1ec7t. Vui l\u00f2ng gi\u1ea3m \u0111\u1ed9 ph\u00e2n gi\u1ea3i ho\u1eb7c ch\u1ecdn scale th\u1ea5p h\u01a1n.\");\n    }\n    return {width:w,height:h};\n}\n\nfunction canvasPixelCount(canvas){\n    return (canvas.width||0)*(canvas.height||0);\n}\n\nfunction escapeHtml(text){\n    var d=document.createElement(\"div\");\n    d.textContent=text;\n    return d.innerHTML;\n}\n\nfunction setupDragDrop(upload){\n    if(!upload)return;\n\n    var input=upload.querySelector('input[type=\"file\"]');\n    if(!input)return;\n\n    [\"dragenter\",\"dragover\"].forEach(function(name){\n        upload.addEventListener(name,function(e){\n            e.preventDefault();\n            e.stopPropagation();\n            upload.classList.add(\"dragover\");\n        });\n    });\n\n    [\"dragleave\",\"drop\"].forEach(function(name){\n        upload.addEventListener(name,function(e){\n            e.preventDefault();\n            e.stopPropagation();\n            upload.classList.remove(\"dragover\");\n        });\n    });\n\n    upload.addEventListener(\"drop\",function(e){\n        var files=Array.from((e.dataTransfer&&e.dataTransfer.files)||[]);\n        if(!files.length)return;\n\n        try{\n            var dt=new DataTransfer();\n            if(input.multiple){\n                files.forEach(function(file){dt.items.add(file);});\n            }else{\n                dt.items.add(files[0]);\n            }\n            input.files=dt.files;\n            input.dispatchEvent(new Event(\"change\",{bubbles:true}));\n        }catch(err){\n            \/* M\u1ed9t s\u1ed1 browser c\u0169 kh\u00f4ng cho g\u00e1n input.files. Khi \u0111\u00f3\n             * ng\u01b0\u1eddi d\u00f9ng v\u1eabn c\u00f3 th\u1ec3 d\u00f9ng n\u00fat ch\u1ecdn file b\u00ecnh th\u01b0\u1eddng. *\/\n            console.warn(\"Drag & drop kh\u00f4ng th\u1ec3 g\u00e1n file v\u00e0o input:\",err);\n        }\n    });\n}\n\nfunction downloadBlob(blob,name){\n    var url=URL.createObjectURL(blob);\n    var a=document.createElement(\"a\");\n    a.href=url;\n    a.download=name;\n    document.body.appendChild(a);\n    a.click();\n    a.remove();\n    setTimeout(function(){URL.revokeObjectURL(url)},30000);\n}\n\nfunction parseSelection(value,max){\n    value=(value||\"all\").trim().toLowerCase();\n\n    if(value===\"all\" || !value){\n        var all=[];\n        for(var i=1;i<=max;i++)all.push(i);\n        return all;\n    }\n\n    if(!\/^\\d+(?:\\s*-\\s*\\d+)?(?:\\s*,\\s*\\d+(?:\\s*-\\s*\\d+)?)*$\/.test(value)){\n        throw new Error(\"Danh s\u00e1ch trang\/slide\/sheet kh\u00f4ng h\u1ee3p l\u1ec7. V\u00ed d\u1ee5: all, 1,2,5 ho\u1eb7c 1-5.\");\n    }\n\n    var out=[];\n    value.split(\",\").forEach(function(part){\n        part=part.trim();\n        if(part.indexOf(\"-\")>=0){\n            var a=part.split(\"-\");\n            var from=parseInt(a[0],10);\n            var to=parseInt(a[1],10);\n            if(from>to){var tmp=from;from=to;to=tmp;}\n            if(from<1 || to>max){\n                throw new Error(\"Trang\/slide\/sheet ph\u1ea3i n\u1eb1m trong kho\u1ea3ng 1 \u0111\u1ebfn \"+max+\".\");\n            }\n            for(var n=from;n<=to;n++){\n                if(out.indexOf(n)<0)out.push(n);\n            }\n        }else{\n            var p=parseInt(part,10);\n            if(p<1 || p>max){\n                throw new Error(\"Trang\/slide\/sheet \"+p+\" kh\u00f4ng t\u1ed3n t\u1ea1i. Ph\u1ea1m vi h\u1ee3p l\u1ec7: 1 \u0111\u1ebfn \"+max+\".\");\n            }\n            if(out.indexOf(p)<0)out.push(p);\n        }\n    });\n\n    if(!out.length){\n        throw new Error(\"Kh\u00f4ng c\u00f3 trang\/slide\/sheet h\u1ee3p l\u1ec7 \u0111\u1ec3 x\u1eed l\u00fd.\");\n    }\n\n    return out.sort(function(a,b){return a-b;});\n}\n\nfunction sleep(ms){\n    return new Promise(function(resolve){setTimeout(resolve,ms)});\n}\n\nfunction loadScriptOnce(src,testFn){\n    return new Promise(function(resolve,reject){\n        try{\n            if(testFn && testFn()){\n                resolve();\n                return;\n            }\n        }catch(e){}\n\n        var existing=document.querySelector('script[data-itools-src=\"'+src+'\"]');\n\n        if(existing){\n            existing.addEventListener(\"load\",function(){resolve()});\n            existing.addEventListener(\"error\",function(){reject(new Error(\"Kh\u00f4ng t\u1ea3i \u0111\u01b0\u1ee3c th\u01b0 vi\u1ec7n.\"))});\n            return;\n        }\n\n        var s=document.createElement(\"script\");\n        s.src=src;\n        s.async=true;\n        s.dataset.itoolsSrc=src;\n\n        s.onload=function(){resolve()};\n        s.onerror=function(){reject(new Error(\"Kh\u00f4ng t\u1ea3i \u0111\u01b0\u1ee3c th\u01b0 vi\u1ec7n: \"+src))};\n\n        document.head.appendChild(s);\n    });\n}\n\n\n\/* =========================================================\n   TABS\n   ========================================================= *\/\n\nfunction initTabs(wrapper){\n    var tabs=wrapper.querySelectorAll(\".itools-tab\");\n    var panels=wrapper.querySelectorAll(\".itools-panel\");\n\n    tabs.forEach(function(tab){\n        tab.addEventListener(\"click\",function(){\n            var target=tab.dataset.tab;\n\n            tabs.forEach(function(x){x.classList.remove(\"active\")});\n            panels.forEach(function(x){x.classList.remove(\"active\")});\n\n            tab.classList.add(\"active\");\n\n            var p=wrapper.querySelector('[data-panel=\"'+target+'\"]');\n            if(p)p.classList.add(\"active\");\n        });\n    });\n}\n\nfunction initInnerTabs(box,buttonSelector,panelSelector,dataName){\n    var buttons=box.querySelectorAll(buttonSelector);\n    var panels=box.querySelectorAll(panelSelector);\n\n    buttons.forEach(function(btn){\n        btn.addEventListener(\"click\",function(){\n            var target=btn.dataset[dataName];\n\n            buttons.forEach(function(x){x.classList.remove(\"active\")});\n            panels.forEach(function(x){x.classList.remove(\"active\")});\n\n            btn.classList.add(\"active\");\n\n            var p=box.querySelector(\n                panelSelector+'[data-'+dataName.replace(\/([A-Z])\/g,\"-$1\").toLowerCase()+'-panel=\"'+target+'\"]'\n            );\n\n            if(p)p.classList.add(\"active\");\n        });\n    });\n}\n\n\n\/* =========================================================\n   FILE LIST\n   ========================================================= *\/\n\nfunction renderFileList(list,files){\n    list.innerHTML=\"\";\n\n    files.forEach(function(file){\n        var row=document.createElement(\"div\");\n        row.className=\"file-row\";\n\n        row.innerHTML=\n            '<span class=\"file-name\">'+escapeHtml(file.name)+'<\/span>'+\n            '<span class=\"file-size\">'+formatBytes(file.size)+'<\/span>';\n\n        list.appendChild(row);\n    });\n}\n\n\n\/* =========================================================\n   COMPRESSOR\n   ========================================================= *\/\n\nfunction initCompressor(box){\n    var input=box.querySelector(\".compress-files\");\n    var list=box.querySelector(\".compress-list\");\n    var button=box.querySelector(\".compress-start\");\n    var quality=box.querySelector(\".compress-quality\");\n    var output=box.querySelector(\".compress-output\");\n    var message=box.querySelector(\".itool-message\");\n    var files=[];\n\n    input.addEventListener(\"change\",function(){\n        files=Array.from(input.files||[]);\n        renderFileList(list,files);\n    });\n\n    button.addEventListener(\"click\",async function(){\n        if(!files.length){\n            message.textContent=\"Vui l\u00f2ng ch\u1ecdn \u00edt nh\u1ea5t m\u1ed9t \u1ea3nh.\";\n            return;\n        }\n\n        var type=output.value;\n\n        if(type===\"image\/avif\"&&!canEncode(type)){\n            message.textContent=\"Tr\u00ecnh duy\u1ec7t n\u00e0y ch\u01b0a h\u1ed7 tr\u1ee3 xu\u1ea5t AVIF. H\u00e3y ch\u1ecdn WebP, JPG, PNG ho\u1eb7c BMP.\";\n            return;\n        }\n\n        button.disabled=true;\n        message.textContent=\"\u0110ang n\u00e9n \u1ea3nh...\";\n        list.innerHTML=\"\";\n\n        for(var file of files){\n            try{\n                var img=await loadImage(file);\n                var canvas=document.createElement(\"canvas\");\n\n                canvas.width=img.naturalWidth;\n                canvas.height=img.naturalHeight;\n\n                var ctx=canvas.getContext(\"2d\");\n\n                if(type===\"image\/jpeg\"){\n                    ctx.fillStyle=\"#fff\";\n                    ctx.fillRect(0,0,canvas.width,canvas.height);\n                }\n\n                ctx.drawImage(img,0,0);\n\n                var blob=await makeBlob(canvas,type,parseFloat(quality.value));\n\n                if(!blob)throw new Error(\"Kh\u00f4ng t\u1ea1o \u0111\u01b0\u1ee3c file.\");\n\n                var ext=extensionFromMime(type);\n                var name=file.name.replace(\/\\.[^\/.]+$\/,\"\");\n                var newName=name+\"-compressed.\"+ext;\n                var saving=Math.round((1-blob.size\/file.size)*100);\n\n                var row=document.createElement(\"div\");\n                row.className=\"file-row\";\n\n                var info=document.createElement(\"span\");\n                info.className=\"file-size\";\n                info.innerHTML=\n                    \"<strong>\"+escapeHtml(newName)+\"<\/strong><br>\"+\n                    formatBytes(file.size)+\" \u2192 \"+formatBytes(blob.size)+\" (\"+saving+\"%)\";\n\n                var link=document.createElement(\"a\");\n                link.className=\"download-link\";\n                link.textContent=\"\u2b07\ufe0f T\u1ea3i xu\u1ed1ng\";\n                link.href=URL.createObjectURL(blob);\n                link.download=newName;\n\n                row.appendChild(info);\n                row.appendChild(link);\n                list.appendChild(row);\n\n            }catch(e){\n                console.error(e);\n\n                var er=document.createElement(\"div\");\n                er.className=\"file-row\";\n                er.textContent=\"Kh\u00f4ng th\u1ec3 x\u1eed l\u00fd: \"+file.name;\n                list.appendChild(er);\n            }\n        }\n\n        message.textContent=\"\u0110\u00e3 x\u1eed l\u00fd \"+files.length+\" \u1ea3nh.\";\n        button.disabled=false;\n    });\n\n    setupDragDrop(input.closest(\".itool-upload\"));\n}\n\n\n\/* =========================================================\n   CROPPER\n   ========================================================= *\/\n\nfunction initCropper(box){\n    var input=box.querySelector(\".crop-file\");\n    var stage=box.querySelector(\".crop-stage\");\n    var canvas=box.querySelector(\".crop-canvas\");\n    var selection=box.querySelector(\".crop-selection\");\n    var cropBox=box.querySelector(\".crop-box\");\n    var ow=box.querySelector(\".crop-output-width\");\n    var oh=box.querySelector(\".crop-output-height\");\n    var lock=box.querySelector(\".crop-lock-ratio\");\n    var format=box.querySelector(\".crop-format\");\n    var zoomInput=box.querySelector(\".crop-zoom\");\n    var reset=box.querySelector(\".crop-reset\");\n    var center=box.querySelector(\".crop-center\");\n    var button=box.querySelector(\".crop-start\");\n    var result=box.querySelector(\".crop-result\");\n    var message=box.querySelector(\".itool-message\");\n    var infoSel=box.querySelector(\".crop-info-selection\");\n    var infoImg=box.querySelector(\".crop-info-image\");\n\n    var image=null;\n    var imageScale=1;\n    var imageLeft=0;\n    var imageTop=0;\n    var imageDisplayWidth=0;\n    var imageDisplayHeight=0;\n    var crop={x:0,y:0,width:0,height:0};\n    var drag=null;\n    var initialRatio=1;\n\n    function layout(){\n        if(!image)return;\n\n        var maxW=Math.max(100,stage.clientWidth-20);\n        var maxH=Math.min(650,Math.max(260,stage.clientHeight-20));\n\n        imageScale=Math.min(\n            maxW\/image.naturalWidth,\n            maxH\/image.naturalHeight,\n            1\n        );\n\n        imageDisplayWidth=image.naturalWidth*imageScale;\n        imageDisplayHeight=image.naturalHeight*imageScale;\n\n        canvas.width=Math.round(imageDisplayWidth);\n        canvas.height=Math.round(imageDisplayHeight);\n\n        canvas.style.width=canvas.width+\"px\";\n        canvas.style.height=canvas.height+\"px\";\n\n        imageLeft=(stage.clientWidth-imageDisplayWidth)\/2;\n        imageTop=(stage.clientHeight-imageDisplayHeight)\/2;\n\n        drawCanvas();\n        clampCrop();\n        updateUI();\n    }\n\n    function drawCanvas(){\n        var ctx=canvas.getContext(\"2d\");\n        ctx.clearRect(0,0,canvas.width,canvas.height);\n        ctx.drawImage(image,0,0,canvas.width,canvas.height);\n    }\n\n    function setupCrop(){\n        if(!image)return;\n\n        var z=parseFloat(zoomInput.value)||1;\n        var w=imageDisplayWidth\/z;\n        var h=imageDisplayHeight\/z;\n\n        w=Math.max(30,Math.min(w,imageDisplayWidth));\n        h=Math.max(30,Math.min(h,imageDisplayHeight));\n\n        crop.width=w;\n        crop.height=h;\n        crop.x=imageLeft+(imageDisplayWidth-w)\/2;\n        crop.y=imageTop+(imageDisplayHeight-h)\/2;\n\n        initialRatio=w\/h;\n        updateUI();\n    }\n\n    function clampCrop(){\n        crop.width=Math.min(crop.width,imageDisplayWidth);\n        crop.height=Math.min(crop.height,imageDisplayHeight);\n\n        crop.x=Math.max(\n            imageLeft,\n            Math.min(crop.x,imageLeft+imageDisplayWidth-crop.width)\n        );\n\n        crop.y=Math.max(\n            imageTop,\n            Math.min(crop.y,imageTop+imageDisplayHeight-crop.height)\n        );\n    }\n\n    function updateUI(){\n        if(!image)return;\n\n        clampCrop();\n\n        cropBox.style.left=crop.x+\"px\";\n        cropBox.style.top=crop.y+\"px\";\n        cropBox.style.width=crop.width+\"px\";\n        cropBox.style.height=crop.height+\"px\";\n\n        selection.style.display=\"block\";\n\n        var top=crop.y;\n        var left=crop.x;\n        var right=stage.clientWidth-(crop.x+crop.width);\n        var bottom=stage.clientHeight-(crop.y+crop.height);\n\n        selection.querySelector(\".shade-top\").style.height=top+\"px\";\n\n        selection.querySelector(\".shade-left\").style.top=top+\"px\";\n        selection.querySelector(\".shade-left\").style.width=left+\"px\";\n        selection.querySelector(\".shade-left\").style.height=crop.height+\"px\";\n\n        selection.querySelector(\".shade-right\").style.top=top+\"px\";\n        selection.querySelector(\".shade-right\").style.width=right+\"px\";\n        selection.querySelector(\".shade-right\").style.height=crop.height+\"px\";\n\n        selection.querySelector(\".shade-bottom\").style.height=bottom+\"px\";\n\n        infoSel.textContent=\n            \"V\u00f9ng ch\u1ecdn: \"+\n            Math.round(crop.width\/imageScale)+\n            \" \u00d7 \"+\n            Math.round(crop.height\/imageScale)+\n            \" px\";\n\n        infoImg.textContent=\n            \"\u1ea2nh g\u1ed1c: \"+\n            image.naturalWidth+\n            \" \u00d7 \"+\n            image.naturalHeight+\n            \" px\";\n    }\n\n    function point(e){\n        var r=stage.getBoundingClientRect();\n\n        return {\n            x:e.clientX-r.left,\n            y:e.clientY-r.top\n        };\n    }\n\n    function startDrag(e,mode){\n        if(!image)return;\n\n        e.preventDefault();\n\n        var p=point(e);\n\n        drag={\n            mode:mode,\n            sx:p.x,\n            sy:p.y,\n            orig:{\n                x:crop.x,\n                y:crop.y,\n                w:crop.width,\n                h:crop.height\n            }\n        };\n\n        if(mode===\"move\"){\n            initialRatio=crop.width\/crop.height;\n        }\n    }\n\n    function moveDrag(e){\n        if(!drag)return;\n\n        e.preventDefault();\n\n        var p=point(e);\n        var dx=p.x-drag.sx;\n        var dy=p.y-drag.sy;\n        var o=drag.orig;\n        var min=30;\n\n        var left=o.x;\n        var top=o.y;\n        var right=o.x+o.w;\n        var bottom=o.y+o.h;\n\n        if(drag.mode===\"move\"){\n            left=o.x+dx;\n            top=o.y+dy;\n\n            crop.x=Math.max(\n                imageLeft,\n                Math.min(left,imageLeft+imageDisplayWidth-o.w)\n            );\n\n            crop.y=Math.max(\n                imageTop,\n                Math.min(top,imageTop+imageDisplayHeight-o.h)\n            );\n\n            updateUI();\n            return;\n        }\n\n        var m=30;\n\n        if(drag.mode.indexOf(\"e\")>=0){\n            right=Math.max(\n                o.x+m,\n                Math.min(o.x+o.w+dx,imageLeft+imageDisplayWidth)\n            );\n        }\n\n        if(drag.mode.indexOf(\"w\")>=0){\n            left=Math.min(\n                o.x+o.w-m,\n                Math.max(o.x+dx,imageLeft)\n            );\n        }\n\n        if(drag.mode.indexOf(\"s\")>=0){\n            bottom=Math.max(\n                o.y+m,\n                Math.min(o.y+o.h+dy,imageTop+imageDisplayHeight)\n            );\n        }\n\n        if(drag.mode.indexOf(\"n\")>=0){\n            top=Math.min(\n                o.y+o.h-m,\n                Math.max(o.y+dy,imageTop)\n            );\n        }\n\n        if(lock.checked){\n            var ratio=initialRatio||o.w\/o.h;\n\n            if(\n                drag.mode.indexOf(\"e\")>=0 ||\n                drag.mode.indexOf(\"w\")>=0\n            ){\n                var nw=right-left;\n                var nh=nw\/ratio;\n\n                if(drag.mode.indexOf(\"n\")>=0){\n                    top=bottom-nh;\n                }else{\n                    bottom=top+nh;\n                }\n            }else{\n                var nh2=bottom-top;\n                var nw2=nh2*ratio;\n\n                if(drag.mode.indexOf(\"w\")>=0){\n                    left=right-nw2;\n                }else{\n                    right=left+nw2;\n                }\n            }\n\n            if(left<imageLeft){\n                left=imageLeft;\n                right=left+(bottom-top)*ratio;\n            }\n\n            if(right>imageLeft+imageDisplayWidth){\n                right=imageLeft+imageDisplayWidth;\n                left=right-(bottom-top)*ratio;\n            }\n\n            if(top<imageTop){\n                top=imageTop;\n                bottom=top+(right-left)\/ratio;\n            }\n\n            if(bottom>imageTop+imageDisplayHeight){\n                bottom=imageTop+imageDisplayHeight;\n                top=bottom-(right-left)\/ratio;\n            }\n        }\n\n        crop.x=left;\n        crop.y=top;\n        crop.width=Math.max(m,right-left);\n        crop.height=Math.max(m,bottom-top);\n\n        updateUI();\n    }\n\n    document.addEventListener(\"pointermove\",moveDrag);\n    document.addEventListener(\"pointerup\",function(){drag=null});\n\n    cropBox.querySelectorAll(\".crop-handle\").forEach(function(h){\n        h.addEventListener(\"pointerdown\",function(e){\n            startDrag(e,h.dataset.handle);\n        });\n    });\n\n    cropBox.querySelector(\".crop-move-area\").addEventListener(\n        \"pointerdown\",\n        function(e){startDrag(e,\"move\")}\n    );\n\n    input.addEventListener(\"change\",async function(){\n        var file=input.files&&input.files[0];\n\n        if(!file)return;\n\n        try{\n            image=await loadImage(file);\n            infoImg.textContent=\n                \"\u1ea2nh g\u1ed1c: \"+\n                image.naturalWidth+\n                \" \u00d7 \"+\n                image.naturalHeight+\n                \" px\";\n\n            layout();\n            setupCrop();\n\n        }catch(e){\n            message.textContent=e.message;\n        }\n    });\n\n    window.addEventListener(\"resize\",function(){\n        if(image)layout();\n    });\n\n    reset.addEventListener(\"click\",function(){\n        if(!image)return;\n\n        zoomInput.value=\"1\";\n        layout();\n        setupCrop();\n        message.textContent=\"\u0110\u00e3 reset v\u00f9ng c\u1eaft.\";\n    });\n\n    center.addEventListener(\"click\",function(){\n        if(!image)return;\n\n        crop.x=imageLeft+(imageDisplayWidth-crop.width)\/2;\n        crop.y=imageTop+(imageDisplayHeight-crop.height)\/2;\n        updateUI();\n    });\n\n    zoomInput.addEventListener(\"input\",function(){\n        if(!image)return;\n        setupCrop();\n    });\n\n    ow.addEventListener(\"input\",function(){\n        if(lock.checked&&crop.width){\n            var r=crop.width\/crop.height;\n            var v=parseInt(ow.value,10);\n\n            if(v)oh.value=Math.round(v\/r);\n        }\n    });\n\n    oh.addEventListener(\"input\",function(){\n        if(lock.checked&&crop.height){\n            var r=crop.width\/crop.height;\n            var v=parseInt(oh.value,10);\n\n            if(v)ow.value=Math.round(v*r);\n        }\n    });\n\n    button.addEventListener(\"click\",async function(){\n        if(!image){\n            message.textContent=\"Vui l\u00f2ng ch\u1ecdn \u1ea3nh.\";\n            return;\n        }\n\n        var tw=parseInt(ow.value,10);\n        var th=parseInt(oh.value,10);\n\n        if(!tw||!th){\n            message.textContent=\"K\u00edch th\u01b0\u1edbc xu\u1ea5t kh\u00f4ng h\u1ee3p l\u1ec7.\";\n            return;\n        }\n\n        var type=format.value;\n\n        if(type===\"image\/avif\"&&!canEncode(type)){\n            message.textContent=\"Tr\u00ecnh duy\u1ec7t ch\u01b0a h\u1ed7 tr\u1ee3 xu\u1ea5t AVIF.\";\n            return;\n        }\n\n        var sx=(crop.x-imageLeft)\/imageScale;\n        var sy=(crop.y-imageTop)\/imageScale;\n        var sw=crop.width\/imageScale;\n        var sh=crop.height\/imageScale;\n\n        var out=document.createElement(\"canvas\");\n        out.width=tw;\n        out.height=th;\n\n        var ctx=out.getContext(\"2d\");\n\n        if(type===\"image\/jpeg\"){\n            ctx.fillStyle=\"#fff\";\n            ctx.fillRect(0,0,tw,th);\n        }\n\n        ctx.imageSmoothingEnabled=true;\n        ctx.imageSmoothingQuality=\"high\";\n\n        ctx.drawImage(\n            image,\n            sx,sy,sw,sh,\n            0,0,tw,th\n        );\n\n        var blob=await makeBlob(out,type,.9);\n\n        if(!blob){\n            message.textContent=\"Kh\u00f4ng th\u1ec3 t\u1ea1o \u1ea3nh.\";\n            return;\n        }\n\n        var url=URL.createObjectURL(blob);\n        var ext=extensionFromMime(type);\n\n        result.innerHTML=\n            '<div class=\"result-card\">'+\n            '<img decoding=\"async\" src=\"'+url+'\" alt=\"\u1ea2nh \u0111\u00e3 c\u1eaft\">'+\n            '<strong>'+tw+\" \u00d7 \"+th+\" px<\/strong><br>\"+\n            '<span>'+formatBytes(blob.size)+'<\/span>'+\n            '<\/div>';\n\n        var link=document.createElement(\"a\");\n        link.className=\"download-link\";\n        link.textContent=\"\u2b07\ufe0f T\u1ea3i \u1ea3nh xu\u1ed1ng\";\n        link.href=url;\n        link.download=\"cropped-image.\"+ext;\n\n        result.querySelector(\".result-card\").appendChild(link);\n\n        message.textContent=\"\u0110\u00e3 c\u1eaft \u1ea3nh th\u00e0nh c\u00f4ng.\";\n    });\n\n    setupDragDrop(input.closest(\".itool-upload\"));\n}\n\n\n\/* =========================================================\n   IMAGE CONVERTER\n   ========================================================= *\/\n\nfunction initConverter(box){\n    var modeButtons=box.querySelectorAll(\".converter-mode\");\n    var modePanels=box.querySelectorAll(\".converter-mode-panel\");\n\n    modeButtons.forEach(function(btn){\n        btn.addEventListener(\"click\",function(){\n            var target=btn.dataset.convertMode;\n\n            modeButtons.forEach(function(x){x.classList.remove(\"active\")});\n            modePanels.forEach(function(x){x.classList.remove(\"active\")});\n\n            btn.classList.add(\"active\");\n\n            var p=box.querySelector(\n                '[data-convert-mode-panel=\"'+target+'\"]'\n            );\n\n            if(p)p.classList.add(\"active\");\n        });\n    });\n\n    initImageConverter(box);\n    initDocumentConverter(box);\n}\n\n\n\/* =========================================================\n   IMAGE CONVERTER\n   ========================================================= *\/\n\nfunction initImageConverter(box){\n    var input=box.querySelector(\".convert-files\");\n    var list=box.querySelector(\".convert-list\");\n    var button=box.querySelector(\".convert-start\");\n    var format=box.querySelector(\".convert-format\");\n    var quality=box.querySelector(\".convert-quality\");\n    var message=box.querySelector(\".converter-mode-panel[data-convert-mode-panel='image'] .itool-message\");\n    var files=[];\n\n    input.addEventListener(\"change\",function(){\n        files=Array.from(input.files||[]);\n        renderFileList(list,files);\n    });\n\n    button.addEventListener(\"click\",async function(){\n        if(!files.length){\n            message.textContent=\"Vui l\u00f2ng ch\u1ecdn \u1ea3nh.\";\n            return;\n        }\n\n        var type=format.value;\n\n        if(type===\"image\/avif\"&&!canEncode(type)){\n            message.textContent=\"Tr\u00ecnh duy\u1ec7t n\u00e0y ch\u01b0a h\u1ed7 tr\u1ee3 xu\u1ea5t AVIF.\";\n            return;\n        }\n\n        button.disabled=true;\n        message.textContent=\"\u0110ang chuy\u1ec3n \u0111\u1ed5i...\";\n        list.innerHTML=\"\";\n\n        for(var file of files){\n            try{\n                var img=await loadImage(file);\n                var canvas=document.createElement(\"canvas\");\n\n                canvas.width=img.naturalWidth;\n                canvas.height=img.naturalHeight;\n\n                var ctx=canvas.getContext(\"2d\");\n\n                if(type===\"image\/jpeg\"){\n                    ctx.fillStyle=\"#fff\";\n                    ctx.fillRect(0,0,canvas.width,canvas.height);\n                }\n\n                ctx.drawImage(img,0,0);\n\n                var blob=await makeBlob(\n                    canvas,\n                    type,\n                    parseFloat(quality.value)\n                );\n\n                if(!blob)throw new Error(\"Kh\u00f4ng t\u1ea1o \u0111\u01b0\u1ee3c file.\");\n\n                var ext=extensionFromMime(type);\n                var base=file.name.replace(\/\\.[^\/.]+$\/,\"\");\n                var newName=base+\"-converted.\"+ext;\n\n                var row=document.createElement(\"div\");\n                row.className=\"file-row\";\n\n                var info=document.createElement(\"span\");\n                info.className=\"file-size\";\n                info.innerHTML=\n                    \"<strong>\"+escapeHtml(newName)+\"<\/strong><br>\"+\n                    formatBytes(blob.size);\n\n                var link=document.createElement(\"a\");\n                link.className=\"download-link\";\n                link.textContent=\"\u2b07\ufe0f T\u1ea3i xu\u1ed1ng\";\n                link.href=URL.createObjectURL(blob);\n                link.download=newName;\n\n                row.appendChild(info);\n                row.appendChild(link);\n                list.appendChild(row);\n\n            }catch(e){\n                console.error(e);\n\n                var er=document.createElement(\"div\");\n                er.className=\"file-row\";\n                er.textContent=\"Kh\u00f4ng th\u1ec3 chuy\u1ec3n \u0111\u1ed5i: \"+file.name;\n                list.appendChild(er);\n            }\n        }\n\n        message.textContent=\"\u0110\u00e3 chuy\u1ec3n \u0111\u1ed5i \"+files.length+\" \u1ea3nh.\";\n        button.disabled=false;\n    });\n\n    setupDragDrop(input.closest(\".itool-upload\"));\n}\n\n\n\/* =========================================================\n   LAZY LIBRARIES\n   ========================================================= *\/\n\nvar libPromises={};\n\nfunction loadPDFJS(){\n    if(libPromises.pdf)return libPromises.pdf;\n\n    libPromises.pdf=import(\n        \"https:\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@6.1.200\/build\/pdf.mjs\"\n    ).then(function(pdfjsLib){\n        pdfjsLib.GlobalWorkerOptions.workerSrc=\n            \"https:\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@6.1.200\/build\/pdf.worker.mjs\";\n\n        window.__IT_PDFJS=pdfjsLib;\n        return pdfjsLib;\n    });\n\n    return libPromises.pdf;\n}\n\nfunction loadSheetJS(){\n    if(libPromises.xlsx)return libPromises.xlsx;\n\n    libPromises.xlsx=loadScriptOnce(\n        \"https:\/\/cdn.sheetjs.com\/xlsx-0.20.3\/package\/dist\/xlsx.full.min.js\",\n        function(){return !!window.XLSX}\n    ).then(function(){\n        if(!window.XLSX){\n            throw new Error(\"Kh\u00f4ng kh\u1edfi t\u1ea1o \u0111\u01b0\u1ee3c SheetJS.\");\n        }\n\n        return window.XLSX;\n    });\n\n    return libPromises.xlsx;\n}\n\nfunction loadMammoth(){\n    if(libPromises.mammoth)return libPromises.mammoth;\n\n    libPromises.mammoth=loadScriptOnce(\n        \"https:\/\/cdn.jsdelivr.net\/npm\/mammoth@1.12.0\/mammoth.browser.min.js\",\n        function(){return !!window.mammoth}\n    ).then(function(){\n        if(!window.mammoth){\n            throw new Error(\"Kh\u00f4ng kh\u1edfi t\u1ea1o \u0111\u01b0\u1ee3c Mammoth.\");\n        }\n\n        return window.mammoth;\n    });\n\n    return libPromises.mammoth;\n}\n\nfunction loadHtml2Canvas(){\n    if(libPromises.html2canvas)return libPromises.html2canvas;\n\n    libPromises.html2canvas=loadScriptOnce(\n        \"https:\/\/cdn.jsdelivr.net\/npm\/html2canvas@1.4.1\/dist\/html2canvas.min.js\",\n        function(){return !!window.html2canvas}\n    ).then(function(){\n        if(!window.html2canvas){\n            throw new Error(\"Kh\u00f4ng kh\u1edfi t\u1ea1o \u0111\u01b0\u1ee3c html2canvas.\");\n        }\n\n        return window.html2canvas;\n    });\n\n    return libPromises.html2canvas;\n}\n\nfunction loadPptxViewJS(){\n    if(libPromises.pptx)return libPromises.pptx;\n\n    libPromises.pptx=loadScriptOnce(\n        \"https:\/\/cdn.jsdelivr.net\/npm\/jszip@3.10.1\/dist\/jszip.min.js\",\n        function(){return !!window.JSZip}\n    ).then(function(){\n        return loadScriptOnce(\n            \"https:\/\/cdn.jsdelivr.net\/npm\/pptxviewjs@1.1.9\/dist\/PptxViewJS.min.js\",\n            function(){return !!window.PptxViewJS}\n        );\n    }).then(function(){\n        if(!window.PptxViewJS){\n            throw new Error(\"Kh\u00f4ng kh\u1edfi t\u1ea1o \u0111\u01b0\u1ee3c PptxViewJS.\");\n        }\n\n        return window.PptxViewJS;\n    });\n\n    return libPromises.pptx;\n}\n\n\n\/* =========================================================\n   DOCUMENT CONVERTER\n   ========================================================= *\/\n\nfunction initDocumentConverter(box){\n\n    var input=box.querySelector(\".document-files\");\n    var list=box.querySelector(\".document-list\");\n    var button=box.querySelector(\".document-start\");\n    var format=box.querySelector(\".document-output-format\");\n    var quality=box.querySelector(\".document-output-quality\");\n    var pageInput=box.querySelector(\".document-pages\");\n    var scaleInput=box.querySelector(\".document-scale\");\n    var message=box.querySelector(\".converter-mode-panel[data-convert-mode-panel='document'] .itool-message\");\n    var files=[];\n\n    input.addEventListener(\"change\",function(){\n        files=Array.from(input.files||[]);\n        renderFileList(list,files);\n    });\n\n    button.addEventListener(\"click\",async function(){\n\n        if(!files.length){\n            message.textContent=\"Vui l\u00f2ng ch\u1ecdn \u00edt nh\u1ea5t m\u1ed9t file.\";\n            return;\n        }\n\n        var type=format.value;\n\n        if(type===\"image\/avif\"&&!canEncode(type)){\n            message.textContent=\"Tr\u00ecnh duy\u1ec7t ch\u01b0a h\u1ed7 tr\u1ee3 AVIF.\";\n            return;\n        }\n\n        button.disabled=true;\n        cleanupListObjectURLs(list);\n        list.innerHTML=\"\";\n        message.textContent=\"\u0110ang t\u1ea3i th\u01b0 vi\u1ec7n c\u1ea7n thi\u1ebft...\";\n\n        var totalDone=0;\n\n        try{\n\n            for(var file of files){\n\n                var ext=file.name.toLowerCase().split(\".\").pop();\n\n                try{\n\n                    if(ext===\"pdf\"){\n                        totalDone+=await convertPDF(\n                            file,\n                            type,\n                            parseFloat(quality.value),\n                            pageInput.value,\n                            parseFloat(scaleInput.value),\n                            list,\n                            message\n                        );\n\n                    }else if(ext===\"xlsx\" || ext===\"xls\"){\n                        totalDone+=await convertExcel(\n                            file,\n                            type,\n                            parseFloat(quality.value),\n                            parseFloat(scaleInput.value),\n                            pageInput.value,\n                            list,\n                            message\n                        );\n\n                    }else if(ext===\"docx\"){\n                        totalDone+=await convertDOCX(\n                            file,\n                            type,\n                            parseFloat(quality.value),\n                            parseFloat(scaleInput.value),\n                            pageInput.value,\n                            list,\n                            message\n                        );\n\n                    }else if(ext===\"pptx\"){\n                        totalDone+=await convertPPTX(\n                            file,\n                            type,\n                            parseFloat(quality.value),\n                            pageInput.value,\n                            parseFloat(scaleInput.value),\n                            list,\n                            message\n                        );\n\n                    }else if(ext===\"doc\" || ext===\"ppt\"){\n                        addDocumentError(\n                            list,\n                            file.name,\n                            \"DOC\/PPT \u0111\u1eddi c\u0169 kh\u00f4ng \u0111\u01b0\u1ee3c parser browser-side n\u00e0y h\u1ed7 tr\u1ee3. H\u00e3y l\u01b0u l\u1ea1i th\u00e0nh DOCX\/PPTX r\u1ed3i th\u1eed l\u1ea1i.\"\n                        );\n\n                    }else{\n                        addDocumentError(\n                            list,\n                            file.name,\n                            \"\u0110\u1ecbnh d\u1ea1ng ch\u01b0a \u0111\u01b0\u1ee3c h\u1ed7 tr\u1ee3.\"\n                        );\n                    }\n\n                }catch(err){\n                    console.error(err);\n\n                    addDocumentError(\n                        list,\n                        file.name,\n                        err && err.message\n                            ? err.message\n                            : \"Kh\u00f4ng th\u1ec3 x\u1eed l\u00fd file.\"\n                    );\n                }\n            }\n\n            message.textContent=\n                \"Ho\u00e0n t\u1ea5t. \u0110\u00e3 t\u1ea1o \"+totalDone+\" \u1ea3nh.\";\n\n        }catch(e){\n            console.error(e);\n            message.textContent=e.message||\"C\u00f3 l\u1ed7i khi x\u1eed l\u00fd.\";\n        }\n\n        button.disabled=false;\n    });\n\n    setupDragDrop(input.closest(\".itool-upload\"));\n}\n\nfunction addDocumentError(list,name,error){\n    var row=document.createElement(\"div\");\n    row.className=\"file-row\";\n    row.innerHTML=\n        \"<span><strong>\"+escapeHtml(name)+\"<\/strong><br>\"+\n        '<span class=\"file-size\">'+escapeHtml(error)+'<\/span><\/span>';\n    list.appendChild(row);\n}\n\nasync function convertPDF(\n    file,\n    outputType,\n    quality,\n    selection,\n    scale,\n    list,\n    message\n){\n    var pdfjsLib=await loadPDFJS();\n    message.textContent=\"\u0110ang \u0111\u1ecdc PDF: \"+file.name;\n\n    var data=await file.arrayBuffer();\n    var pdf=await pdfjsLib.getDocument({data:data}).promise;\n    var pages=parseSelection(selection,pdf.numPages);\n    var done=0;\n\n    for(var i=0;i<pages.length;i++){\n        var pageNo=pages[i];\n        message.textContent=\"PDF \"+file.name+\": trang \"+(i+1)+\"\/\"+pages.length+\" (trang PDF \"+pageNo+\")\";\n\n        var page=await pdf.getPage(pageNo);\n        var viewport=page.getViewport({scale:Math.max(.5,parseFloat(scale)||1)*1.5});\n        var dims=ensureCanvasSize(viewport.width,viewport.height,\"Trang PDF\");\n        var canvas=document.createElement(\"canvas\");\n        canvas.width=dims.width;\n        canvas.height=dims.height;\n\n        try{\n            var ctx=canvas.getContext(\"2d\",{alpha:false});\n            await page.render({canvasContext:ctx,viewport:viewport}).promise;\n            var blob=await makeBlob(canvas,outputType,quality);\n            if(!blob)throw new Error(\"Kh\u00f4ng t\u1ea1o \u0111\u01b0\u1ee3c \u1ea3nh t\u1eeb PDF.\");\n\n            var ext=extensionFromMime(outputType);\n            var base=file.name.replace(\/\\.[^\/.]+$\/i,\"\");\n            var name=base+\"-page-\"+pageNo+\".\"+ext;\n            addDocumentDownload(list,name,blob,\"Trang \"+pageNo+\" \u2022 \"+formatBytes(blob.size));\n            done++;\n        }finally{\n            canvas.width=1;\n            canvas.height=1;\n        }\n        await sleep(0);\n    }\n    return done;\n}\n\nasync function convertExcel(\n    file,\n    outputType,\n    quality,\n    scale,\n    selection,\n    list,\n    message\n){\n    var XLSX=await loadSheetJS();\n    await loadHtml2Canvas();\n    message.textContent=\"\u0110ang \u0111\u1ecdc Excel: \"+file.name;\n\n    var buffer=await file.arrayBuffer();\n    var workbook=XLSX.read(buffer,{type:\"array\",cellStyles:true,cellDates:true});\n    if(!workbook.SheetNames.length)throw new Error(\"File Excel kh\u00f4ng c\u00f3 worksheet.\");\n\n    var selected=parseSelection(selection,workbook.SheetNames.length);\n    var done=0;\n\n    for(var i=0;i<selected.length;i++){\n        var sheetIndex=selected[i]-1;\n        var sheetName=workbook.SheetNames[sheetIndex];\n        message.textContent=\"Excel \"+file.name+\": sheet \"+(i+1)+\"\/\"+selected.length+\" \u2014 \"+sheetName;\n        var sheet=workbook.Sheets[sheetName];\n        var html=XLSX.utils.sheet_to_html(sheet,{header:\"\",footer:\"\",editable:false});\n\n        var host=document.createElement(\"div\");\n        host.className=\"document-preview-host\";\n        var page=document.createElement(\"div\");\n        page.className=\"document-render-page\";\n        page.style.width=(1000*parseFloat(scale))+\"px\";\n        page.style.minHeight=(1414*parseFloat(scale))+\"px\";\n        page.innerHTML=\"<h2>\"+escapeHtml(sheetName)+\"<\/h2>\"+html;\n        host.appendChild(page);\n        document.body.appendChild(host);\n\n        try{\n            await sleep(50);\n            var shot=await window.html2canvas(page,{scale:Math.max(1,parseFloat(scale)||1),backgroundColor:\"#ffffff\",useCORS:true,logging:false});\n            ensureCanvasSize(shot.width,shot.height,\"Sheet Excel\");\n            var blob=await makeBlob(shot,outputType,quality);\n            if(!blob)throw new Error(\"Kh\u00f4ng t\u1ea1o \u0111\u01b0\u1ee3c \u1ea3nh t\u1eeb Excel.\");\n\n            var ext=extensionFromMime(outputType);\n            var base=file.name.replace(\/\\.[^\/.]+$\/i,\"\");\n            var name=base+\"-\"+safeFileName(sheetName)+\".\"+ext;\n            addDocumentDownload(list,name,blob,\"Sheet: \"+sheetName+\" \u2022 \"+formatBytes(blob.size));\n            done++;\n            shot.width=1; shot.height=1;\n        }finally{\n            host.remove();\n        }\n        await sleep(0);\n    }\n    return done;\n}\n\nasync function convertDOCX(\n    file,\n    outputType,\n    quality,\n    scale,\n    selection,\n    list,\n    message\n){\n    var mammoth=await loadMammoth();\n    await loadHtml2Canvas();\n    message.textContent=\"\u0110ang \u0111\u1ecdc Word: \"+file.name;\n\n    var arrayBuffer=await file.arrayBuffer();\n    var result=await mammoth.convertToHtml({arrayBuffer:arrayBuffer});\n    var html=result.value||\"\";\n    if(!html.trim())throw new Error(\"DOCX kh\u00f4ng c\u00f3 n\u1ed9i dung c\u00f3 th\u1ec3 hi\u1ec3n th\u1ecb.\");\n\n    var host=document.createElement(\"div\");\n    host.className=\"document-preview-host\";\n    var source=document.createElement(\"div\");\n    source.className=\"document-render-page\";\n    source.style.width=(1000*scale)+\"px\";\n    source.innerHTML=html;\n    host.appendChild(source);\n    document.body.appendChild(host);\n\n    var pageHeight=1414*scale;\n    var pageInnerHeight=Math.max(200,pageHeight-90*scale);\n    var nodes=Array.from(source.children);\n    var pages=[];\n    var current=null;\n\n    function newPage(){\n        var page=document.createElement(\"div\");\n        page.className=\"document-render-page\";\n        page.style.width=(1000*scale)+\"px\";\n        page.style.minHeight=pageHeight+\"px\";\n        page.style.height=pageHeight+\"px\";\n        page.style.overflow=\"hidden\";\n        current=page;\n        pages.push(page);\n        return page;\n    }\n\n    try{\n        newPage();\n        for(var i=0;i<nodes.length;i++){\n            var node=nodes[i];\n            current.appendChild(node);\n            await sleep(0);\n            if(current.scrollHeight>pageInnerHeight && current.children.length>1){\n                current.removeChild(node);\n                current=newPage();\n                current.appendChild(node);\n            }\n        }\n\n        \/* N\u1ebfu DOCX ch\u1ec9 c\u00f3 text node ngo\u00e0i element, \u0111\u1ea3m b\u1ea3o v\u1eabn c\u00f3 \u00edt nh\u1ea5t 1 trang. *\/\n        if(!pages.length)newPage();\n\n        var selected=parseSelection(selection,pages.length);\n        var base=file.name.replace(\/\\.[^\/.]+$\/i,\"\");\n        var ext=extensionFromMime(outputType);\n        var done=0;\n\n        for(var p=0;p<selected.length;p++){\n            var pageNo=selected[p];\n            message.textContent=\"Word \"+file.name+\": trang \"+(p+1)+\"\/\"+selected.length+\" (trang \"+pageNo+\")\";\n            document.body.appendChild(pages[pageNo-1]);\n            await sleep(30);\n            var shot=await window.html2canvas(pages[pageNo-1],{scale:Math.max(1,scale),backgroundColor:\"#ffffff\",useCORS:true,logging:false});\n            ensureCanvasSize(shot.width,shot.height,\"Trang DOCX\");\n            var blob=await makeBlob(shot,outputType,quality);\n            if(!blob)throw new Error(\"Kh\u00f4ng t\u1ea1o \u0111\u01b0\u1ee3c \u1ea3nh t\u1eeb DOCX.\");\n            var name=base+\"-page-\"+pageNo+\".\"+ext;\n            addDocumentDownload(list,name,blob,\"DOCX \u2192 HTML \u2192 \u1ea3nh \u2022 Trang \"+pageNo+\" \u2022 \"+formatBytes(blob.size));\n            done++;\n            shot.width=1; shot.height=1;\n            pages[pageNo-1].remove();\n            await sleep(0);\n        }\n\n        if(result.messages && result.messages.length){\n            var warning=document.createElement(\"div\");\n            warning.className=\"file-row\";\n            warning.innerHTML=\"<span class='file-size'>L\u01b0u \u00fd DOCX: \"+result.messages.length+\" c\u1ea3nh b\u00e1o v\u1ec1 \u0111\u1ecbnh d\u1ea1ng trong qu\u00e1 tr\u00ecnh chuy\u1ec3n \u0111\u1ed5i. B\u1ed1 c\u1ee5c c\u00f3 th\u1ec3 kh\u00e1c Microsoft Word.<\/span>\";\n            list.appendChild(warning);\n        }\n        return done;\n    }finally{\n        host.remove();\n        pages.forEach(function(page){if(page.parentNode)page.parentNode.removeChild(page);});\n    }\n}\n\nasync function convertPPTX(\n    file,\n    outputType,\n    quality,\n    selection,\n    scale,\n    list,\n    message\n){\n    var PptxViewJS=await loadPptxViewJS();\n\n    message.textContent=\"\u0110ang \u0111\u1ecdc PowerPoint: \"+file.name;\n\n    if(!PptxViewJS.PPTXViewer){\n        throw new Error(\"PptxViewJS kh\u00f4ng cung c\u1ea5p PPTXViewer.\");\n    }\n\n    var host=document.createElement(\"div\");\n    host.style.position=\"fixed\";\n    host.style.left=\"-100000px\";\n    host.style.top=\"-100000px\";\n    host.style.width=\"1280px\";\n    host.style.height=\"720px\";\n    host.style.background=\"#fff\";\n\n    var canvas=document.createElement(\"canvas\");\n    var pptDims=ensureCanvasSize(1280*scale,720*scale,\"Slide PPTX\");\n    canvas.width=pptDims.width;\n    canvas.height=pptDims.height;\n\n    host.appendChild(canvas);\n    document.body.appendChild(host);\n\n    var viewer=new PptxViewJS.PPTXViewer({\n        canvas:canvas\n    });\n\n    await viewer.loadFile(file);\n\n    var count=viewer.getSlideCount();\n    var slides=parseSelection(selection,count);\n    var done=0;\n\n    for(var i=0;i<slides.length;i++){\n\n        var slideNo=slides[i];\n\n        message.textContent=\n            \"PPTX \"+file.name+\": slide \"+(i+1)+\"\/\"+slides.length+\" (slide \"+slideNo+\")\";\n\n        await viewer.goToSlide(slideNo-1);\n        await viewer.render();\n\n        \/*\n         * Ch\u1edd browser ho\u00e0n t\u1ea5t paint c\u1ee7a canvas.\n         *\/\n        await sleep(80);\n\n        var copy=document.createElement(\"canvas\");\n        copy.width=canvas.width;\n        copy.height=canvas.height;\n\n        copy.getContext(\"2d\").drawImage(\n            canvas,\n            0,\n            0,\n            copy.width,\n            copy.height\n        );\n\n        var blob=await makeBlob(\n            copy,\n            outputType,\n            quality\n        );\n\n        if(!blob)throw new Error(\"Kh\u00f4ng t\u1ea1o \u0111\u01b0\u1ee3c \u1ea3nh t\u1eeb PPTX.\");\n\n        var ext=extensionFromMime(outputType);\n        var base=file.name.replace(\/\\.[^\/.]+$\/,\"\");\n        var name=base+\"-slide-\"+slideNo+\".\"+ext;\n\n        addDocumentDownload(\n            list,\n            name,\n            blob,\n            \"Slide \"+slideNo+\" \u2022 \"+formatBytes(blob.size)\n        );\n\n        done++;\n        await sleep(0);\n    }\n\n    host.remove();\n\n    return done;\n}\n\nfunction safeFileName(name){\n    return String(name||\"sheet\")\n        .replace(\/[\\\\\/:*?\"<>|]+\/g,\"-\")\n        .replace(\/\\s+\/g,\"-\")\n        .substring(0,80);\n}\n\nfunction addDocumentDownload(list,name,blob,meta){\n    if(!list._itoolObjectURLs)list._itoolObjectURLs=[];\n    var url=URL.createObjectURL(blob);\n    list._itoolObjectURLs.push(url);\n\n    var row=document.createElement(\"div\");\n    row.className=\"file-row\";\n    var info=document.createElement(\"span\");\n    info.className=\"file-size\";\n    info.innerHTML=\"<strong>\"+escapeHtml(name)+\"<\/strong><br>\"+escapeHtml(meta||formatBytes(blob.size));\n\n    var link=document.createElement(\"a\");\n    link.className=\"download-link\";\n    link.textContent=\"\u2b07\ufe0f T\u1ea3i xu\u1ed1ng\";\n    link.href=url;\n    link.download=name;\n\n    row.appendChild(info);\n    row.appendChild(link);\n    list.appendChild(row);\n}\n\nfunction cleanupListObjectURLs(list){\n    if(!list || !list._itoolObjectURLs)return;\n    list._itoolObjectURLs.forEach(function(url){\n        try{URL.revokeObjectURL(url);}catch(e){}\n    });\n    list._itoolObjectURLs=[];\n}\n\n\n\n\/* =========================================================\n   WATERMARK \/ EDITOR\n   ========================================================= *\/\n\nfunction initWatermark(box){\n\n    var editorButtons=box.querySelectorAll(\".editor-mode\");\n    var editorPanels=box.querySelectorAll(\".editor-mode-panel\");\n\n    editorButtons.forEach(function(btn){\n        btn.addEventListener(\"click\",function(){\n            var target=btn.dataset.editorMode;\n\n            editorButtons.forEach(function(x){x.classList.remove(\"active\")});\n            editorPanels.forEach(function(x){x.classList.remove(\"active\")});\n\n            btn.classList.add(\"active\");\n\n            var p=box.querySelector(\n                '[data-editor-mode-panel=\"'+target+'\"]'\n            );\n\n            if(p)p.classList.add(\"active\");\n        });\n    });\n\n    initSingleEditor(box);\n    initBatchWatermark(box);\n}\n\n\n\/* =========================================================\n   SINGLE EDITOR\n   ========================================================= *\/\n\nfunction initSingleEditor(box){\n    var imageInput=box.querySelector(\".watermark-image\");\n    var logoInput=box.querySelector(\".watermark-logo\");\n    var canvas=box.querySelector(\".watermark-canvas\");\n    var preview=box.querySelector(\".watermark-preview\");\n    var dragLogo=box.querySelector(\".watermark-drag-logo\");\n    var logoPreview=box.querySelector(\".watermark-logo-preview\");\n    var resizeHandle=box.querySelector(\".wm-resize-handle\");\n    var position=box.querySelector(\".watermark-position\");\n    var size=box.querySelector(\".watermark-size\");\n    var opacity=box.querySelector(\".watermark-opacity\");\n    var margin=box.querySelector(\".watermark-margin\");\n    var format=box.querySelector(\".watermark-format\");\n    var button=box.querySelector(\".watermark-start\");\n    var resetBtn=box.querySelector(\".watermark-reset\");\n    var result=box.querySelector(\".watermark-result\");\n    var message=box.querySelector(\".editor-mode-panel[data-editor-mode-panel='single'] .itool-message\");\n    var sizeValue=box.querySelector(\".range-value\");\n    var opacityValue=box.querySelector(\".opacity-value\");\n    var marginValue=box.querySelector(\".margin-value\");\n    var textInput=box.querySelector(\".editor-text-input\");\n    var textColor=box.querySelector(\".editor-text-color\");\n    var textSize=box.querySelector(\".editor-text-size\");\n    var textFont=box.querySelector(\".editor-text-font\");\n    var textWeight=box.querySelector(\".editor-text-weight\");\n    var addText=box.querySelector(\".add-text-object\");\n    var updateText=box.querySelector(\".update-text-object\");\n    var emojiInput=box.querySelector(\".editor-emoji-input\");\n    var emojiSize=box.querySelector(\".editor-emoji-size\");\n    var addEmoji=box.querySelector(\".add-emoji-object\");\n    var deleteObject=box.querySelector(\".delete-selected-object\");\n    var objectLayer=box.querySelector(\".editor-object-layer\");\n    var flipXBtn=box.querySelector(\".editor-flip-x\");\n    var flipYBtn=box.querySelector(\".editor-flip-y\");\n    var rotateLeftBtn=box.querySelector(\".editor-rotate-left\");\n    var rotateRightBtn=box.querySelector(\".editor-rotate-right\");\n    var resetTransformBtn=box.querySelector(\".editor-reset-transform\");\n    var rotationValue=box.querySelector(\".editor-rotation-value\");\n    var flipValue=box.querySelector(\".editor-transform-flip-value\");\n    var cropToggle=box.querySelector(\".editor-crop-toggle\");\n    var cropReset=box.querySelector(\".editor-crop-reset\");\n    var cropApply=box.querySelector(\".editor-crop-apply\");\n    var cropRatio=box.querySelector(\".editor-crop-ratio\");\n    var cropLayer=box.querySelector(\".editor-crop-layer\");\n    var cropBox=box.querySelector(\".editor-crop-box\");\n\n\n    var mainImage=null;\n    var logoImage=null;\n    var logoPreviewURL=null;\n    var resultURL=null;\n    var displayScale=1;\n    var logoState={x:.75,y:.75,w:.20,h:.20};\n    var logoDrag=null;\n    var objects=[];\n    var selectedObjectId=null;\n    var objectDrag=null;\n    var objectResize=null;\n    var transformState={rotation:0,flipX:false,flipY:false};\n    var cropState={active:false,x:0,y:0,w:1,h:1};\n    var cropDrag=null;\n\n    var MIN_SIZE=8;\n    var MAX_SIZE=300;\n    var MAX_OUTPUT_PIXELS=40000000;\n\n    function clamp(v,min,max){return Math.max(min,Math.min(max,v));}\n\n    function editorFont(obj,size){\n        if(obj.type===\"emoji\"){\n            return size+\"px \\\"Segoe UI Emoji\\\",\\\"Apple Color Emoji\\\",\\\"Noto Color Emoji\\\",\\\"Twemoji Mozilla\\\",sans-serif\";\n        }\n        return (obj.italic?\"italic \":\"\")+(obj.weight||400)+\" \"+size+\"px \\\"\"+String(obj.font||\"Arial\").replace(\/\\\"\/g,\"\\\\\\\"\")+\"\\\"\";\n    }\n\n    function getObjectMetrics(ctx,obj,size){\n        ctx.save();\n        ctx.font=editorFont(obj,size);\n        var m=ctx.measureText(obj.text||\"\");\n        var width=Math.max(1,m.width);\n        var ascent=m.actualBoundingBoxAscent||size*.75;\n        var descent=m.actualBoundingBoxDescent||size*.25;\n        ctx.restore();\n        return {width:width,height:Math.max(size,ascent+descent)};\n    }\n\n    function clampObjectPosition(obj){\n        if(!mainImage||!canvas.width||!canvas.height)return;\n        var ctx=canvas.getContext(\"2d\");\n        var metrics=getObjectMetrics(ctx,obj,obj.size);\n        var halfW=Math.min(.49,(metrics.width\/2)\/canvas.width);\n        var halfH=Math.min(.49,(metrics.height\/2)\/canvas.height);\n        obj.x=clamp(obj.x,halfW,1-halfW);\n        obj.y=clamp(obj.y,halfH,1-halfH);\n    }\n\n    function transformedSize(){\n        if(!mainImage)return {width:0,height:0};\n        var w=mainImage.naturalWidth,h=mainImage.naturalHeight;\n        if(transformState.rotation===90||transformState.rotation===270){\n            return {width:h,height:w};\n        }\n        return {width:w,height:h};\n    }\n\n    function drawTransformedImage(ctx,img,outWidth,outHeight){\n        ctx.save();\n        ctx.translate(outWidth\/2,outHeight\/2);\n        ctx.rotate(transformState.rotation*Math.PI\/180);\n        ctx.scale(transformState.flipX?-1:1,transformState.flipY?-1:1);\n        ctx.drawImage(img,-img.naturalWidth\/2,-img.naturalHeight\/2);\n        ctx.restore();\n    }\n\n    function updateTransformStatus(){\n        if(rotationValue)rotationValue.textContent=(transformState.rotation||0)+\"\u00b0\";\n        if(flipValue){\n            if(transformState.flipX&&transformState.flipY)flipValue.textContent=\"L\u1eadt ngang + d\u1ecdc\";\n            else if(transformState.flipX)flipValue.textContent=\"L\u1eadt ngang\";\n            else if(transformState.flipY)flipValue.textContent=\"L\u1eadt d\u1ecdc\";\n            else flipValue.textContent=\"B\u00ecnh th\u01b0\u1eddng\";\n        }\n    }\n\n\n    var emojiImageCache={};\n    function getEmojiImage(value){\n        if(!value)return null;\n        var key=value;\n        if(emojiImageCache[key])return emojiImageCache[key];\n        var img=new Image();\n        img.crossOrigin=\"anonymous\";\n        img.src=\"https:\/\/cdn.jsdelivr.net\/gh\/twitter\/twemoji@14.0.2\/assets\/svg\/\"+emojiCodePoints(value)+\".svg\";\n        emojiImageCache[key]=img;\n        img.onload=function(){ if(mainImage){ draw(); } };\n        return img;\n    }\n\n    function drawObjectToCanvas(ctx,obj,outWidth,outHeight){\n        var px=Math.max(MIN_SIZE,obj.size);\n        var x=obj.x*outWidth;\n        var y=obj.y*outHeight;\n        ctx.save();\n        ctx.textAlign=\"center\";\n        ctx.textBaseline=\"middle\";\n        ctx.font=editorFont(obj,px);\n\n        if(obj.type===\"text\"){\n            ctx.fillStyle=obj.color||\"#ffffff\";\n            if(obj.shadow){\n                ctx.shadowColor=\"rgba(0,0,0,.38)\";\n                ctx.shadowBlur=Math.max(2,px*.08);\n                ctx.shadowOffsetX=Math.max(1,px*.05);\n                ctx.shadowOffsetY=Math.max(1,px*.05);\n            }\n        }else{\n            ctx.fillStyle=\"#000000\";\n        }\n\n        if(obj.type===\"emoji\") {\n            var emojiImg=getEmojiImage(obj.text);\n            if(emojiImg && emojiImg.complete && emojiImg.naturalWidth){\n                var es=Math.max(MIN_SIZE,obj.size);\n                ctx.drawImage(emojiImg,x-es\/2,y-es\/2,es,es);\n            }else{\n                ctx.fillStyle=\"#111827\";\n                ctx.fillText(obj.text,x,y);\n            }\n        } else {\n            ctx.fillText(obj.text,x,y);\n        }\n\n        if(obj.type===\"text\" && obj.underline){\n            var metrics=getObjectMetrics(ctx,obj,px);\n            var lineY=y+metrics.height*.32;\n            ctx.save();\n            ctx.shadowColor=\"transparent\";\n            ctx.shadowBlur=0;\n            ctx.shadowOffsetX=0;\n            ctx.shadowOffsetY=0;\n            ctx.strokeStyle=obj.color||\"#ffffff\";\n            ctx.lineWidth=Math.max(1,px*.045);\n            ctx.beginPath();\n            ctx.moveTo(x-metrics.width\/2,lineY);\n            ctx.lineTo(x+metrics.width\/2,lineY);\n            ctx.stroke();\n            ctx.restore();\n        }\n        ctx.restore();\n    }\n\n    function draw(){\n        if(!mainImage)return;\n        var dims=transformedSize();\n        var mw=900,mh=650;\n        displayScale=Math.min(mw\/dims.width,mh\/dims.height,1);\n        var w=Math.max(1,Math.round(dims.width*displayScale));\n        var h=Math.max(1,Math.round(dims.height*displayScale));\n        canvas.width=w;\n        canvas.height=h;\n        var ctx=canvas.getContext(\"2d\");\n        ctx.clearRect(0,0,w,h);\n        drawTransformedImage(ctx,mainImage,w,h);\n\n        if(logoImage){\n            var lw=w*logoState.w;\n            var lh=logoImage.naturalHeight\/logoImage.naturalWidth*lw;\n            logoState.h=clamp(lh\/h,.001,1);\n            logoState.x=clamp(logoState.x,0,1-logoState.w);\n            logoState.y=clamp(logoState.y,0,1-logoState.h);\n            ctx.save();\n            ctx.globalAlpha=parseInt(opacity.value,10)\/100;\n            ctx.drawImage(logoImage,logoState.x*w,logoState.y*h,lw,lh);\n            ctx.restore();\n            updateLogoOverlay();\n        }else{\n            dragLogo.style.display=\"none\";\n        }\n\n        objects.forEach(clampObjectPosition);\n        renderObjectsOverlay();\n        updateCropOverlay();\n        updateTransformStatus();\n    }\n\n    function updateLogoOverlay(){\n        if(!mainImage||!logoImage)return;\n        var canvasRect=canvas.getBoundingClientRect();\n        var previewRect=preview.getBoundingClientRect();\n        if(!canvasRect.width||!canvasRect.height)return;\n        var lw=canvasRect.width*logoState.w;\n        var lh=canvasRect.height*logoState.h;\n        var ox=canvasRect.left-previewRect.left;\n        var oy=canvasRect.top-previewRect.top;\n        dragLogo.style.display=\"block\";\n        dragLogo.style.left=(ox+logoState.x*canvasRect.width)+\"px\";\n        dragLogo.style.top=(oy+logoState.y*canvasRect.height)+\"px\";\n        dragLogo.style.width=lw+\"px\";\n        dragLogo.style.height=lh+\"px\";\n        logoPreview.style.opacity=parseInt(opacity.value,10)\/100;\n    }\n\n    function setQuick(v){\n        if(!logoImage)return;\n        var m=parseInt(margin.value,10)\/100;\n        var w=logoState.w,h=logoState.h;\n        position.value=v;\n        switch(v){\n            case\"top-left\": logoState.x=m;logoState.y=m;break;\n            case\"top-center\": logoState.x=(1-w)\/2;logoState.y=m;break;\n            case\"top-right\": logoState.x=1-w-m;logoState.y=m;break;\n            case\"center\": logoState.x=(1-w)\/2;logoState.y=(1-h)\/2;break;\n            case\"bottom-left\": logoState.x=m;logoState.y=1-h-m;break;\n            case\"bottom-center\": logoState.x=(1-w)\/2;logoState.y=1-h-m;break;\n            case\"bottom-right\": logoState.x=1-w-m;logoState.y=1-h-m;break;\n        }\n        logoState.x=clamp(logoState.x,0,1-logoState.w);\n        logoState.y=clamp(logoState.y,0,1-logoState.h);\n        draw();\n    }\n\n    function pointer(e){\n        var r=preview.getBoundingClientRect();\n        return {x:e.clientX-r.left,y:e.clientY-r.top};\n    }\n\n    function startLogoDrag(e,mode){\n        if(!logoImage)return;\n        e.preventDefault();\n        e.stopPropagation();\n        if(dragLogo.setPointerCapture){try{dragLogo.setPointerCapture(e.pointerId);}catch(err){}}\n        var p=pointer(e);\n        logoDrag={mode:mode,sx:p.x,sy:p.y,x:logoState.x,y:logoState.y,w:logoState.w,h:logoState.h};\n        position.value=\"free\";\n    }\n\n    function moveLogo(e){\n        if(!logoDrag||!logoImage)return;\n        e.preventDefault();\n        var p=pointer(e),r=canvas.getBoundingClientRect();\n        if(!r.width||!r.height)return;\n        var dx=(p.x-logoDrag.sx)\/r.width,dy=(p.y-logoDrag.sy)\/r.height;\n        if(logoDrag.mode===\"move\"){\n            logoState.x=clamp(logoDrag.x+dx,0,1-logoDrag.w);\n            logoState.y=clamp(logoDrag.y+dy,0,1-logoDrag.h);\n        }else{\n            var ratio=logoImage.naturalHeight\/logoImage.naturalWidth;\n            var nw=clamp(logoDrag.w+dx,.03,1-logoDrag.x);\n            var nh=clamp(nw*ratio*r.width\/r.height,.03,1-logoDrag.y);\n            logoState.w=nw;logoState.h=nh;\n            size.value=Math.round(nw*100);\n            sizeValue.textContent=size.value+\"%\";\n        }\n        draw();\n    }\n\n    dragLogo.addEventListener(\"pointerdown\",function(e){startLogoDrag(e,\"move\")});\n    resizeHandle.addEventListener(\"pointerdown\",function(e){startLogoDrag(e,\"resize\")});\n    document.addEventListener(\"pointermove\",moveLogo,{passive:false});\n    document.addEventListener(\"pointerup\",function(){logoDrag=null;});\n\n    function toggleTextStyle(styleName){\n        var obj=selectedObject();\n        if(!obj||obj.type!==\"text\"){\n            message.textContent=\"H\u00e3y ch\u1ecdn m\u1ed9t \u0111\u1ed1i t\u01b0\u1ee3ng ch\u1eef tr\u01b0\u1edbc.\";\n            return;\n        }\n        obj[styleName]=!obj[styleName];\n        syncStyleButtons(obj);\n        renderObjectsOverlay();\n        draw();\n    }\n\n    function syncStyleButtons(obj){\n        box.querySelectorAll(\".text-style-btn\").forEach(function(btn){\n            btn.classList.toggle(\"active\",!!(obj&&obj[btn.dataset.textStyle]));\n        });\n    }\n\n    box.querySelectorAll(\".text-style-btn\").forEach(function(btn){\n        btn.addEventListener(\"click\",function(){toggleTextStyle(btn.dataset.textStyle);});\n    });\n\n    function createObject(type,value){\n        if(!mainImage){message.textContent=\"Vui l\u00f2ng ch\u1ecdn \u1ea3nh tr\u01b0\u1edbc.\";return;}\n        value=String(value||\"\").trim();\n        if(!value)return;\n        var id=\"obj_\"+Date.now()+\"_\"+Math.floor(Math.random()*10000);\n        var obj={\n            id:id,type:type,text:value,x:.5,y:.5,\n            size:clamp(parseInt(type===\"text\"?textSize.value:emojiSize.value,10)||48,MIN_SIZE,MAX_SIZE),\n            color:type===\"text\"?textColor.value:\"#ffffff\",\n            font:type===\"text\"?textFont.value:\"Arial\",\n            weight:type===\"text\"?parseInt(textWeight.value,10)||400:400,\n            italic:false,underline:false,shadow:false\n        };\n        objects.push(obj);selectedObjectId=id;\n        clampObjectPosition(obj);\n        renderObjectsOverlay();syncSelectedControls();draw();\n    }\n\n    function selectedObject(){\n        return objects.find(function(o){return o.id===selectedObjectId;})||null;\n    }\n\n    function renderObjectsOverlay(){\n        objectLayer.innerHTML=\"\";\n        if(!mainImage)return;\n        var canvasRect=canvas.getBoundingClientRect();\n        var previewRect=preview.getBoundingClientRect();\n        if(!canvasRect.width||!canvasRect.height)return;\n        var visualScale=canvasRect.width\/canvas.width;\n\n        objects.forEach(function(obj){\n            var el=document.createElement(\"div\");\n            el.className=\"editor-object \"+(obj.type===\"text\"?\"text-object\":\"emoji-object\");\n            if(obj.id===selectedObjectId)el.classList.add(\"selected\");\n            el.dataset.objectId=obj.id;\n            el.textContent=obj.text;\n            el.style.left=(canvasRect.left-previewRect.left+obj.x*canvasRect.width)+\"px\";\n            el.style.top=(canvasRect.top-previewRect.top+obj.y*canvasRect.height)+\"px\";\n            el.style.fontSize=(obj.size*visualScale)+\"px\";\n            if(obj.type===\"text\"){\n                el.style.color=obj.color;\n                el.style.fontFamily=obj.font;\n                el.style.fontWeight=obj.weight;\n                el.style.fontStyle=obj.italic?\"italic\":\"normal\";\n                el.style.textDecoration=obj.underline?\"underline\":\"none\";\n                el.style.textShadow=obj.shadow?\"2px 2px 3px rgba(0,0,0,.38)\":\"none\";\n            }else{\n                el.style.fontFamily='\"Segoe UI Emoji\",\"Apple Color Emoji\",\"Noto Color Emoji\",\"Twemoji Mozilla\",sans-serif';\n                el.style.fontVariantEmoji=\"emoji\";\n                \/* Keep the editable overlay visible even if WordPress rewrites emoji. *\/\n                el.innerHTML=\"\";\n                var emojiImg=document.createElement(\"img\");\n                emojiImg.alt=obj.text;\n                emojiImg.draggable=false;\n                emojiImg.src=\"https:\/\/cdn.jsdelivr.net\/gh\/twitter\/twemoji@14.0.2\/assets\/svg\/\"+emojiCodePoints(obj.text)+\".svg\";\n                emojiImg.style.cssText=\"display:block!important;width:\"+(obj.size*visualScale)+\"px!important;height:\"+(obj.size*visualScale)+\"px!important;max-width:none!important;object-fit:contain!important;pointer-events:none!important;\";\n                emojiImg.onerror=function(){\n                    emojiImg.remove();\n                    var span=document.createElement(\"span\");\n                    span.textContent=obj.text;\n                    span.style.cssText='font-family:\"Segoe UI Emoji\",\"Apple Color Emoji\",\"Noto Color Emoji\",sans-serif!important;font-size:'+(obj.size*visualScale)+'px!important;line-height:1!important;';\n                    el.appendChild(span);\n                };\n                el.appendChild(emojiImg);\n            }\n            if(obj.id===selectedObjectId){\n                var handle=document.createElement(\"span\");\n                handle.className=\"editor-object-resize-handle\";\n                handle.title=\"K\u00e9o \u0111\u1ec3 ph\u00f3ng to \/ thu nh\u1ecf\";\n                handle.addEventListener(\"pointerdown\",function(e){e.preventDefault();e.stopPropagation();startObjectResize(e,obj.id);});\n                el.appendChild(handle);\n            }\n            el.addEventListener(\"pointerdown\",function(e){\n                if(!e.target.classList || !e.target.classList.contains(\"editor-object-resize-handle\")){selectObject(obj.id);startObjectDrag(e,obj.id);}\n            });\n            objectLayer.appendChild(el);\n        });\n    }\n\n    function selectObject(id){\n        selectedObjectId=id;renderObjectsOverlay();syncSelectedControls();\n    }\n\n    function syncSelectedControls(){\n        var obj=selectedObject();\n        if(!obj){syncStyleButtons(null);return;}\n        if(obj.type===\"text\"){\n            textInput.value=obj.text;textColor.value=obj.color;textSize.value=obj.size;textFont.value=obj.font;textWeight.value=obj.weight;syncStyleButtons(obj);\n        }else{\n            emojiInput.value=obj.text;emojiSize.value=obj.size;syncStyleButtons(null);\n        }\n    }\n\n\n    function resetCropState(){\n        cropState={active:false,x:0,y:0,w:1,h:1};\n        if(cropLayer)cropLayer.classList.remove(\"active\");\n        if(preview)preview.classList.remove(\"crop-mode\");\n        if(cropToggle)cropToggle.textContent=\"\u2702\ufe0f B\u1eadt khung c\u1eaft\";\n    }\n\n    function resetCropRect(){\n        cropState.x=0;cropState.y=0;cropState.w=1;cropState.h=1;\n        updateCropOverlay();\n    }\n\n    function setCropRatio(value){\n        if(!mainImage||!canvas.width||!canvas.height)return;\n        if(value===\"free\"){updateCropOverlay();return;}\n        var parts=value.split(\":\");\n        var ratio=parseFloat(parts[0])\/parseFloat(parts[1]);\n        if(!isFinite(ratio)||ratio<=0)return;\n        var current=canvas.width\/canvas.height;\n        var x=cropState.x,y=cropState.y,w=cropState.w,h=cropState.h;\n        if(w\/h>ratio){\n            w=h*ratio;\n        }else{\n            h=w\/ratio;\n        }\n        w=clamp(w,.05,1);h=clamp(h,.05,1);\n        x=clamp(x+(cropState.w-w)\/2,0,1-w);\n        y=clamp(y+(cropState.h-h)\/2,0,1-h);\n        cropState.x=x;cropState.y=y;cropState.w=w;cropState.h=h;\n        updateCropOverlay();\n    }\n\n    function updateCropOverlay(){\n        if(!cropLayer||!cropBox||!canvas||!preview)return;\n        if(!cropState.active){\n            cropLayer.classList.remove(\"active\");\n            preview.classList.remove(\"crop-mode\");\n            return;\n        }\n        var cr=canvas.getBoundingClientRect();\n        var pr=preview.getBoundingClientRect();\n        if(!cr.width||!cr.height)return;\n        cropLayer.classList.add(\"active\");\n        preview.classList.add(\"crop-mode\");\n        var left=cr.left-pr.left+cropState.x*cr.width;\n        var top=cr.top-pr.top+cropState.y*cr.height;\n        cropBox.style.left=left+\"px\";\n        cropBox.style.top=top+\"px\";\n        cropBox.style.width=(cropState.w*cr.width)+\"px\";\n        cropBox.style.height=(cropState.h*cr.height)+\"px\";\n        cropLayer.setAttribute(\"aria-hidden\",\"false\");\n    }\n\n    function cropPointerPosition(e){\n        var r=canvas.getBoundingClientRect();\n        return {\n            x:clamp((e.clientX-r.left)\/Math.max(r.width,1),0,1),\n            y:clamp((e.clientY-r.top)\/Math.max(r.height,1),0,1)\n        };\n    }\n\n    function startCropDrag(e,mode){\n        if(!cropState.active||!mainImage)return;\n        e.preventDefault();e.stopPropagation();\n        var p=cropPointerPosition(e);\n        cropDrag={\n            mode:mode,sx:p.x,sy:p.y,\n            x:cropState.x,y:cropState.y,w:cropState.w,h:cropState.h\n        };\n        if(cropBox.setPointerCapture){try{cropBox.setPointerCapture(e.pointerId);}catch(err){}}\n    }\n\n    function applyCropRatioToRect(state,ratio,anchorX,anchorY){\n        if(ratio===\"free\")return state;\n        var parts=ratio.split(\":\");\n        var target=parseFloat(parts[0])\/parseFloat(parts[1]);\n        if(!isFinite(target)||target<=0)return state;\n        var maxW=state.w,maxH=state.h;\n        if(maxW\/maxH>target)maxW=maxH*target;\n        else maxH=maxW\/target;\n        var x=anchorX===\"right\"?state.x+state.w-maxW:anchorX===\"center\"?state.x+(state.w-maxW)\/2:state.x;\n        var y=anchorY===\"bottom\"?state.y+state.h-maxH:anchorY===\"center\"?state.y+(state.h-maxH)\/2:state.y;\n        state.w=clamp(maxW,.05,1);state.h=clamp(maxH,.05,1);\n        state.x=clamp(x,0,1-state.w);state.y=clamp(y,0,1-state.h);\n        return state;\n    }\n\n    function moveCrop(e){\n        if(!cropDrag)return;\n        e.preventDefault();\n        var p=cropPointerPosition(e);\n        var dx=p.x-cropDrag.sx,dy=p.y-cropDrag.sy;\n        var min=.03;\n        var s={\n            x:cropDrag.x,y:cropDrag.y,w:cropDrag.w,h:cropDrag.h\n        };\n        var mode=cropDrag.mode;\n        if(mode===\"move\"){\n            s.x=clamp(cropDrag.x+dx,0,1-cropDrag.w);\n            s.y=clamp(cropDrag.y+dy,0,1-cropDrag.h);\n        }else{\n            var left=cropDrag.x,top=cropDrag.y,right=cropDrag.x+cropDrag.w,bottom=cropDrag.y+cropDrag.h;\n            if(mode.indexOf(\"w\")>=0)left=clamp(cropDrag.x+dx,0,right-min);\n            if(mode.indexOf(\"e\")>=0)right=clamp(cropDrag.x+cropDrag.w+dx,left+min,1);\n            if(mode.indexOf(\"n\")>=0)top=clamp(cropDrag.y+dy,0,bottom-min);\n            if(mode.indexOf(\"s\")>=0)bottom=clamp(cropDrag.y+cropDrag.h+dy,top+min,1);\n            s.x=left;s.y=top;s.w=right-left;s.h=bottom-top;\n            if(cropRatio.value!==\"free\"){\n                var parts=cropRatio.value.split(\":\");\n                var target=parseFloat(parts[0])\/parseFloat(parts[1]);\n                if(isFinite(target)&&target>0){\n                    var fixedX=mode.indexOf(\"w\")>=0?\"right\":mode.indexOf(\"e\")>=0?\"left\":\"center\";\n                    var fixedY=mode.indexOf(\"n\")>=0?\"bottom\":mode.indexOf(\"s\")>=0?\"top\":\"center\";\n                    if(s.w\/s.h>target){\n                        s.w=s.h*target;\n                    }else{\n                        s.h=s.w\/target;\n                    }\n                    if(fixedX===\"right\")s.x=cropDrag.x+cropDrag.w-s.w;\n                    else if(fixedX===\"center\")s.x=cropDrag.x+(cropDrag.w-s.w)\/2;\n                    if(fixedY===\"bottom\")s.y=cropDrag.y+cropDrag.h-s.h;\n                    else if(fixedY===\"center\")s.y=cropDrag.y+(cropDrag.h-s.h)\/2;\n                    s.x=clamp(s.x,0,1-s.w);s.y=clamp(s.y,0,1-s.h);\n                }\n            }\n        }\n        cropState.x=s.x;cropState.y=s.y;cropState.w=s.w;cropState.h=s.h;\n        updateCropOverlay();\n    }\n\n    document.addEventListener(\"pointermove\",moveCrop,{passive:false});\n    document.addEventListener(\"pointerup\",function(){cropDrag=null;});\n\n    if(cropBox){\n        cropBox.addEventListener(\"pointerdown\",function(e){\n            var handle=e.target.closest ? e.target.closest(\"[data-crop-handle]\") : null;\n            startCropDrag(e,handle?handle.getAttribute(\"data-crop-handle\"):\"move\");\n        });\n    }\n\n    if(cropToggle)cropToggle.addEventListener(\"click\",function(){\n        if(!mainImage){message.textContent=\"Vui l\u00f2ng ch\u1ecdn \u1ea3nh tr\u01b0\u1edbc khi b\u1eadt khung c\u1eaft.\";return;}\n        cropState.active=!cropState.active;\n        if(cropState.active){\n            if(cropState.w>=.999&&cropState.h>=.999){cropState.x=.05;cropState.y=.05;cropState.w=.9;cropState.h=.9;}\n            cropToggle.textContent=\"\u2705 \u0110ang ch\u1ec9nh khung c\u1eaft\";\n        }else{\n            cropToggle.textContent=\"\u2702\ufe0f B\u1eadt khung c\u1eaft\";\n        }\n        updateCropOverlay();\n    });\n\n    if(cropReset)cropReset.addEventListener(\"click\",function(){\n        resetCropRect();\n        message.textContent=\"\u0110\u00e3 \u0111\u1eb7t l\u1ea1i v\u00f9ng c\u1eaft v\u1ec1 to\u00e0n b\u1ed9 \u1ea3nh.\";\n    });\n\n    if(cropRatio)cropRatio.addEventListener(\"change\",function(){\n        if(!mainImage)return;\n        setCropRatio(cropRatio.value);\n    });\n\n    async function applyEditorCrop(){\n        if(!mainImage){\n            message.textContent=\"Vui l\u00f2ng ch\u1ecdn \u1ea3nh tr\u01b0\u1edbc khi c\u1eaft.\";\n            return;\n        }\n        if(!cropState.active){\n            message.textContent=\"H\u00e3y b\u1eadt khung c\u1eaft v\u00e0 ch\u1ecdn v\u00f9ng c\u1ea7n c\u1eaft tr\u01b0\u1edbc.\";\n            return;\n        }\n        var dims=transformedSize();\n        var sx=Math.max(0,Math.round(cropState.x*dims.width));\n        var sy=Math.max(0,Math.round(cropState.y*dims.height));\n        var sw=Math.max(1,Math.round(cropState.w*dims.width));\n        var sh=Math.max(1,Math.round(cropState.h*dims.height));\n        sw=Math.min(sw,dims.width-sx);\n        sh=Math.min(sh,dims.height-sy);\n        if(sw<2||sh<2){\n            message.textContent=\"V\u00f9ng c\u1eaft qu\u00e1 nh\u1ecf.\";\n            return;\n        }\n        if(sw*sh>MAX_OUTPUT_PIXELS){\n            message.textContent=\"V\u00f9ng c\u1eaft qu\u00e1 l\u1edbn \u0111\u1ec3 x\u1eed l\u00fd an to\u00e0n.\";\n            return;\n        }\n\n        if(cropApply)cropApply.disabled=true;\n        try{\n            \/* Render only the transformed base image. Logo\/Text\/Emoji are NOT\n               baked into the crop, so the user can continue editing them. *\/\n            var full=document.createElement(\"canvas\");\n            full.width=dims.width;\n            full.height=dims.height;\n            var fctx=full.getContext(\"2d\");\n            drawTransformedImage(fctx,mainImage,full.width,full.height);\n\n            var out=document.createElement(\"canvas\");\n            out.width=sw;\n            out.height=sh;\n            var octx=out.getContext(\"2d\");\n            octx.drawImage(full,sx,sy,sw,sh,0,0,sw,sh);\n\n            var blob=await new Promise(function(resolve,reject){\n                out.toBlob(function(b){b?resolve(b):reject(new Error(\"Kh\u00f4ng th\u1ec3 t\u1ea1o \u1ea3nh sau khi c\u1eaft.\"));},\"image\/png\");\n            });\n            var url=URL.createObjectURL(blob);\n            var img=new Image();\n            img.onload=function(){\n                \/* Map existing overlay positions from the old transformed\n                   canvas into the new cropped canvas. *\/\n                var oldW=dims.width,oldH=dims.height;\n                if(logoImage){\n                    var oldLX=logoState.x*oldW;\n                    var oldLY=logoState.y*oldH;\n                    var oldLW=logoState.w*oldW;\n                    var oldLH=logoState.h*oldH;\n                    logoState.x=(oldLX-sx)\/sw;\n                    logoState.y=(oldLY-sy)\/sh;\n                    logoState.w=oldLW\/sw;\n                    logoState.h=oldLH\/sh;\n                    logoState.x=clamp(logoState.x,0,Math.max(0,1-logoState.w));\n                    logoState.y=clamp(logoState.y,0,Math.max(0,1-logoState.h));\n                    logoState.w=clamp(logoState.w,.001,1);\n                    logoState.h=clamp(logoState.h,.001,1);\n                }\n                objects.forEach(function(obj){\n                    var oldX=obj.x*oldW,oldY=obj.y*oldH;\n                    obj.x=(oldX-sx)\/sw;\n                    obj.y=(oldY-sy)\/sh;\n                    clampObjectPosition(obj);\n                });\n\n                mainImage=img;\n                transformState={rotation:0,flipX:false,flipY:false};\n                cropState={active:false,x:0,y:0,w:1,h:1};\n                if(cropLayer)cropLayer.classList.remove(\"active\");\n                if(preview)preview.classList.remove(\"crop-mode\");\n                if(cropToggle)cropToggle.textContent=\"\u2702\ufe0f B\u1eadt khung c\u1eaft\";\n                URL.revokeObjectURL(url);\n                full.width=1;full.height=1;out.width=1;out.height=1;\n                draw();\n                message.textContent=\"\u0110\u00e3 c\u1eaft \u1ea3nh theo \u0111\u00fang v\u00f9ng b\u1ea1n ch\u1ecdn. B\u1ea1n c\u00f3 th\u1ec3 ti\u1ebfp t\u1ee5c ch\u1ec9nh s\u1eeda.\";\n                if(cropApply)cropApply.disabled=false;\n            };\n            img.onerror=function(){\n                URL.revokeObjectURL(url);\n                if(cropApply)cropApply.disabled=false;\n                message.textContent=\"Kh\u00f4ng th\u1ec3 t\u1ea3i \u1ea3nh sau khi c\u1eaft.\";\n            };\n            img.src=url;\n        }catch(err){\n            console.error(err);\n            if(cropApply)cropApply.disabled=false;\n            message.textContent=err.message||\"Kh\u00f4ng th\u1ec3 \u00e1p d\u1ee5ng v\u00f9ng c\u1eaft.\";\n        }\n    }\n\n    if(cropApply)cropApply.addEventListener(\"click\",function(){applyEditorCrop();});\n\n    function applyTransform(action){\n        if(!mainImage){message.textContent=\"Vui l\u00f2ng ch\u1ecdn \u1ea3nh tr\u01b0\u1edbc.\";return;}\n        if(action===\"flipX\")transformState.flipX=!transformState.flipX;\n        if(action===\"flipY\")transformState.flipY=!transformState.flipY;\n        if(action===\"left\")transformState.rotation=(transformState.rotation+270)%360;\n        if(action===\"right\")transformState.rotation=(transformState.rotation+90)%360;\n        draw();\n        message.textContent=\"\u0110\u00e3 c\u1eadp nh\u1eadt bi\u1ebfn \u0111\u1ed5i \u1ea3nh.\";\n    }\n\n    if(flipXBtn)flipXBtn.addEventListener(\"click\",function(){applyTransform(\"flipX\");});\n    if(flipYBtn)flipYBtn.addEventListener(\"click\",function(){applyTransform(\"flipY\");});\n    if(rotateLeftBtn)rotateLeftBtn.addEventListener(\"click\",function(){applyTransform(\"left\");});\n    if(rotateRightBtn)rotateRightBtn.addEventListener(\"click\",function(){applyTransform(\"right\");});\n    if(resetTransformBtn)resetTransformBtn.addEventListener(\"click\",function(){\n        transformState={rotation:0,flipX:false,flipY:false};\n        draw();\n        message.textContent=\"\u0110\u00e3 \u0111\u1eb7t l\u1ea1i l\u1eadt\/xoay \u1ea3nh.\";\n    });\n\n    function startObjectDrag(e,id){\n        e.preventDefault();e.stopPropagation();\n        var obj=objects.find(function(o){return o.id===id;});if(!obj)return;\n        if(e.currentTarget&&e.currentTarget.setPointerCapture){try{e.currentTarget.setPointerCapture(e.pointerId);}catch(err){}}\n        var p=pointer(e);\n        objectDrag={id:id,sx:p.x,sy:p.y,x:obj.x,y:obj.y};\n    }\n\n    function moveObject(e){\n        if(!objectDrag)return;e.preventDefault();\n        var obj=objects.find(function(o){return o.id===objectDrag.id;});if(!obj)return;\n        var p=pointer(e),r=canvas.getBoundingClientRect();if(!r.width||!r.height)return;\n        obj.x=objectDrag.x+(p.x-objectDrag.sx)\/r.width;\n        obj.y=objectDrag.y+(p.y-objectDrag.sy)\/r.height;\n        clampObjectPosition(obj);renderObjectsOverlay();\n    }\n    document.addEventListener(\"pointermove\",moveObject,{passive:false});\n\n    function startObjectResize(e,id){\n        e.preventDefault();e.stopPropagation();\n        var obj=objects.find(function(o){return o.id===id;});if(!obj)return;\n        var p=pointer(e);objectResize={id:id,sx:p.x,sy:p.y,size:obj.size};selectedObjectId=id;\n    }\n\n    function moveObjectResize(e){\n        if(!objectResize)return;e.preventDefault();\n        var obj=objects.find(function(o){return o.id===objectResize.id;});if(!obj||!mainImage)return;\n        var p=pointer(e),r=canvas.getBoundingClientRect();if(!r.width)return;\n        var delta=((p.x-objectResize.sx)+(p.y-objectResize.sy))\/2;\n        var visualScale=r.width\/mainImage.naturalWidth;\n        obj.size=clamp(Math.round(objectResize.size+delta\/Math.max(visualScale,.0001)),MIN_SIZE,MAX_SIZE);\n        if(obj.type===\"text\")textSize.value=obj.size;else emojiSize.value=obj.size;\n        clampObjectPosition(obj);renderObjectsOverlay();\n    }\n    document.addEventListener(\"pointermove\",moveObjectResize,{passive:false});\n    document.addEventListener(\"pointerup\",function(){objectDrag=null;objectResize=null;logoDrag=null;});\n\n    imageInput.addEventListener(\"change\",async function(){\n        var f=imageInput.files&&imageInput.files[0];if(!f)return;\n        try{mainImage=await loadImage(f);objects=[];selectedObjectId=null;transformState={rotation:0,flipX:false,flipY:false};cropState={active:false,x:0,y:0,w:1,h:1};if(cropToggle)cropToggle.textContent=\"\u2702\ufe0f B\u1eadt khung c\u1eaft\";if(cropLayer)cropLayer.classList.remove(\"active\");draw();message.textContent=\"\u0110\u00e3 t\u1ea3i \u1ea3nh: \"+f.name;}catch(e){message.textContent=e.message;}\n    });\n\n    logoInput.addEventListener(\"change\",async function(){\n        var f=logoInput.files&&logoInput.files[0];if(!f)return;\n        try{\n            logoImage=await loadImage(f);\n            if(logoPreviewURL)URL.revokeObjectURL(logoPreviewURL);\n            logoPreviewURL=URL.createObjectURL(f);logoPreview.src=logoPreviewURL;\n            var w=parseInt(size.value,10)\/100;\n            logoState.w=w;logoState.x=clamp(1-w-parseInt(margin.value,10)\/100,0,1-w);\n            logoState.y=clamp(1-logoState.h-parseInt(margin.value,10)\/100,0,1-logoState.h);\n            draw();\n        }catch(e){message.textContent=e.message;}\n    });\n\n    position.addEventListener(\"change\",function(){if(position.value!==\"free\")setQuick(position.value);});\n    size.addEventListener(\"input\",function(){sizeValue.textContent=size.value+\"%\";if(logoImage){logoState.w=clamp(parseInt(size.value,10)\/100,.03,.95);logoState.x=clamp(logoState.x,0,1-logoState.w);draw();}});\n    opacity.addEventListener(\"input\",function(){opacityValue.textContent=opacity.value+\"%\";draw();});\n    margin.addEventListener(\"input\",function(){marginValue.textContent=margin.value+\"%\";if(position.value!==\"free\")setQuick(position.value);});\n\n    resetBtn.addEventListener(\"click\",function(){\n        if(!logoImage)return;\n        logoState.w=clamp(parseInt(size.value,10)\/100,.03,.95);\n        logoState.x=clamp(1-logoState.w-parseInt(margin.value,10)\/100,0,1-logoState.w);\n        logoState.y=clamp(1-logoState.h-parseInt(margin.value,10)\/100,0,1-logoState.h);\n        position.value=\"free\";draw();message.textContent=\"\u0110\u00e3 \u0111\u1eb7t l\u1ea1i v\u1ecb tr\u00ed logo.\";\n    });\n\n    addText.addEventListener(\"click\",function(){createObject(\"text\",textInput.value);});\n\n    updateText.addEventListener(\"click\",function(){\n        var obj=selectedObject();\n        if(!obj||obj.type!==\"text\"){message.textContent=\"H\u00e3y ch\u1ecdn m\u1ed9t \u0111\u1ed1i t\u01b0\u1ee3ng ch\u1eef tr\u01b0\u1edbc.\";return;}\n        obj.text=textInput.value||\"\";obj.color=textColor.value;obj.size=clamp(parseInt(textSize.value,10)||48,MIN_SIZE,MAX_SIZE);obj.font=textFont.value;obj.weight=parseInt(textWeight.value,10)||400;\n        clampObjectPosition(obj);renderObjectsOverlay();draw();message.textContent=\"\u0110\u00e3 c\u1eadp nh\u1eadt ch\u1eef.\";\n    });\n\n    \/* =========================================================\n       EMOJI PALETTE - V2.3\n       Build the picker entirely from data-emoji attributes.\n       This prevents WordPress wp-emoji from replacing the source\n       glyphs before our JavaScript can read them.\n       ========================================================= *\/\n    function emojiCodePoints(value){\n        return Array.from(value).map(function(ch){return ch.codePointAt(0).toString(16);}).join(\"-\");\n    }\n\n    function emojiValueFromButton(btn){\n        var v=btn.getAttribute(\"data-emoji\");\n        if(v)return v;\n        var img=btn.querySelector(\"img.emoji\");\n        if(img){\n            v=img.getAttribute(\"alt\")||img.getAttribute(\"aria-label\")||\"\";\n            if(v)return v.trim();\n        }\n        return String(btn.textContent||\"\").trim();\n    }\n\n    function makeEmojiIcon(btn,value){\n        btn.innerHTML=\"\";\n        var img=document.createElement(\"img\");\n        img.className=\"image-tools-emoji-icon\";\n        img.alt=\"\";\n        img.setAttribute(\"aria-hidden\",\"true\");\n        img.draggable=false;\n        img.decoding=\"async\";\n        img.loading=\"eager\";\n        var cp=emojiCodePoints(value);\n        var urls=[\n            \"https:\/\/cdn.jsdelivr.net\/gh\/twitter\/twemoji@14.0.2\/assets\/svg\/\"+cp+\".svg\",\n            \"https:\/\/unpkg.com\/@twitter\/twemoji@latest\/assets\/svg\/\"+cp+\".svg\"\n        ];\n        var n=0;\n        function fail(){\n            n++;\n            if(n<urls.length){img.src=urls[n];return;}\n            img.remove();\n            var span=document.createElement(\"span\");\n            span.className=\"image-tools-emoji-fallback\";\n            span.textContent=value;\n            btn.appendChild(span);\n        }\n        img.onerror=fail;\n        img.src=urls[0];\n        btn.appendChild(img);\n    }\n\n    function setupEmojiPalette(box){\n        var palette=box.querySelector(\".emoji-palette\");\n        if(!palette)return;\n        var source=Array.prototype.slice.call(palette.querySelectorAll(\".emoji-choice\"));\n        source.forEach(function(btn){\n            var value=emojiValueFromButton(btn);\n            if(!value)return;\n            btn.setAttribute(\"data-emoji\",value);\n            btn.setAttribute(\"aria-label\",value);\n            btn.setAttribute(\"title\",value+\" \u2014 nh\u1ea5n \u0111\u1ec3 ch\u1ecdn; nh\u1ea5n 2 l\u1ea7n \u0111\u1ec3 ch\u00e8n\");\n            makeEmojiIcon(btn,value);\n        });\n        return source;\n    }\n\n    var emojiButtons=setupEmojiPalette(box) || [];\n\n    function getSelectedEmoji(btn){\n        return btn.getAttribute(\"data-emoji\")||emojiValueFromButton(btn);\n    }\n\n    emojiButtons.forEach(function(btn){\n        btn.addEventListener(\"click\",function(e){\n            e.preventDefault();\n            e.stopPropagation();\n            var value=getSelectedEmoji(btn);\n            if(!value)return;\n            emojiInput.value=value;\n            emojiButtons.forEach(function(x){x.classList.remove(\"selected\");});\n            btn.classList.add(\"selected\");\n            message.textContent=mainImage?\"\u0110\u00e3 ch\u1ecdn \"+value+\". Nh\u1ea5n \u201c\u2795 Th\u00eam emoji\u201d \u0111\u1ec3 ch\u00e8n v\u00e0o \u1ea3nh.\":\"\u0110\u00e3 ch\u1ecdn \"+value+\". H\u00e3y t\u1ea3i \u1ea3nh tr\u01b0\u1edbc.\";\n        });\n        btn.addEventListener(\"dblclick\",function(e){\n            e.preventDefault();\n            e.stopPropagation();\n            var value=getSelectedEmoji(btn);\n            if(!value)return;\n            emojiInput.value=value;\n            if(mainImage){\n                createObject(\"emoji\",value);\n                message.textContent=\"\u0110\u00e3 ch\u00e8n emoji \"+value+\" v\u00e0o \u1ea3nh.\";\n            }else{\n                message.textContent=\"\u0110\u00e3 ch\u1ecdn \"+value+\". H\u00e3y t\u1ea3i \u1ea3nh tr\u01b0\u1edbc.\";\n            }\n        });\n    });\n\n    \/* Clicking the Add button is the primary insertion path. *\/\n    addEmoji.addEventListener(\"click\",function(e){\n        e.preventDefault();\n        e.stopPropagation();\n        var emoji=String(emojiInput.value||\"\").trim();\n        if(!emoji){message.textContent=\"H\u00e3y ch\u1ecdn m\u1ed9t emoji trong danh s\u00e1ch ho\u1eb7c nh\u1eadp emoji tr\u01b0\u1edbc.\";return;}\n        if(!mainImage){message.textContent=\"Vui l\u00f2ng ch\u1ecdn \u1ea3nh tr\u01b0\u1edbc khi ch\u00e8n emoji.\";return;}\n        createObject(\"emoji\",emoji);\n        message.textContent=\"\u0110\u00e3 ch\u00e8n emoji \"+emoji+\" v\u00e0o \u1ea3nh.\";\n    });\n\n    deleteObject.addEventListener(\"click\",function(){\n        if(!selectedObjectId){message.textContent=\"Ch\u01b0a ch\u1ecdn \u0111\u1ed1i t\u01b0\u1ee3ng.\";return;}\n        objects=objects.filter(function(o){return o.id!==selectedObjectId;});\n        selectedObjectId=null;renderObjectsOverlay();syncSelectedControls();draw();message.textContent=\"\u0110\u00e3 x\u00f3a \u0111\u1ed1i t\u01b0\u1ee3ng.\";\n    });\n\n    button.addEventListener(\"click\",async function(){\n        if(!mainImage){message.textContent=\"Vui l\u00f2ng ch\u1ecdn \u1ea3nh ch\u00ednh.\";return;}\n        var type=format.value;\n        if(type===\"image\/avif\"&&!canEncode(type)){message.textContent=\"Tr\u00ecnh duy\u1ec7t ch\u01b0a h\u1ed7 tr\u1ee3 xu\u1ea5t AVIF.\";return;}\n        if(mainImage.naturalWidth*mainImage.naturalHeight>MAX_OUTPUT_PIXELS){message.textContent=\"\u1ea2nh qu\u00e1 l\u1edbn \u0111\u1ec3 xu\u1ea5t an to\u00e0n. Vui l\u00f2ng d\u00f9ng \u1ea3nh nh\u1ecf h\u01a1n 40 MP.\";return;}\n        button.disabled=true;\n        try{\n            var dims=transformedSize();\n            ensureCanvasSize(dims.width,dims.height,\"\u1ea2nh xu\u1ea5t\");\n            var full=document.createElement(\"canvas\");\n            full.width=dims.width;full.height=dims.height;\n            var fctx=full.getContext(\"2d\");\n            if(type===\"image\/jpeg\"){fctx.fillStyle=\"#fff\";fctx.fillRect(0,0,full.width,full.height);}\n            drawTransformedImage(fctx,mainImage,full.width,full.height);\n            if(logoImage){\n                var lw=full.width*logoState.w,lh=logoImage.naturalHeight\/logoImage.naturalWidth*lw;\n                fctx.save();fctx.globalAlpha=parseInt(opacity.value,10)\/100;\n                fctx.drawImage(logoImage,logoState.x*full.width,logoState.y*full.height,lw,lh);\n                fctx.restore();\n            }\n            objects.forEach(function(obj){clampObjectPosition(obj);drawObjectToCanvas(fctx,obj,full.width,full.height);});\n\n            var sx=0,sy=0,sw=full.width,sh=full.height;\n            if(cropState.active){\n                sx=Math.max(0,Math.round(cropState.x*full.width));\n                sy=Math.max(0,Math.round(cropState.y*full.height));\n                sw=Math.max(1,Math.round(cropState.w*full.width));\n                sh=Math.max(1,Math.round(cropState.h*full.height));\n                sw=Math.min(sw,full.width-sx);\n                sh=Math.min(sh,full.height-sy);\n            }\n            ensureCanvasSize(sw,sh,\"V\u00f9ng c\u1eaft\");\n            var out=document.createElement(\"canvas\");\n            out.width=sw;out.height=sh;\n            var ctx=out.getContext(\"2d\");\n            if(type===\"image\/jpeg\"){ctx.fillStyle=\"#fff\";ctx.fillRect(0,0,out.width,out.height);}\n            ctx.drawImage(full,sx,sy,sw,sh,0,0,sw,sh);\n            var blob=await makeBlob(out,type,.9);\n            if(!blob)throw new Error(\"Kh\u00f4ng th\u1ec3 t\u1ea1o \u1ea3nh.\");\n            if(resultURL)URL.revokeObjectURL(resultURL);\n            resultURL=URL.createObjectURL(blob);\n            var ext=extensionFromMime(type);\n            result.innerHTML='<div class=\"result-card\"><img decoding=\"async\" src=\"'+resultURL+'\" alt=\"\u1ea2nh \u0111\u00e3 ch\u1ec9nh s\u1eeda\"><strong>'+formatBytes(blob.size)+'<\/strong><\/div>';\n            var link=document.createElement(\"a\");link.className=\"download-link\";link.textContent=\"\u2b07\ufe0f T\u1ea3i \u1ea3nh xu\u1ed1ng\";link.href=resultURL;link.download=\"edited-image.\"+ext;result.querySelector(\".result-card\").appendChild(link);\n            message.textContent=\"\u0110\u00e3 xu\u1ea5t \u1ea3nh th\u00e0nh c\u00f4ng.\";\n            out.width=1;out.height=1;full.width=1;full.height=1;\n        }catch(e){console.error(e);message.textContent=e.message||\"Kh\u00f4ng th\u1ec3 xu\u1ea5t \u1ea3nh.\";}\n        finally{button.disabled=false;}\n    });\n\n    window.addEventListener(\"resize\",function(){if(mainImage){requestAnimationFrame(function(){updateLogoOverlay();renderObjectsOverlay();updateCropOverlay();});}});\n    if(window.ResizeObserver){var ro=new ResizeObserver(function(){if(mainImage){updateLogoOverlay();renderObjectsOverlay();updateCropOverlay();}});ro.observe(preview);}\n\n    setupDragDrop(imageInput.closest(\".itool-upload\"));\n    setupDragDrop(logoInput.closest(\".itool-upload\"));\n}\n\n\n\/* =========================================================\n   BATCH LOGO\n   ========================================================= *\/\n\nfunction initBatchWatermark(box){\n\n    var imagesInput=box.querySelector(\".batch-images\");\n    var logoInput=box.querySelector(\".batch-logo\");\n\n    var position=box.querySelector(\".batch-position\");\n    var size=box.querySelector(\".batch-size\");\n    var opacity=box.querySelector(\".batch-opacity\");\n    var margin=box.querySelector(\".batch-margin\");\n    var format=box.querySelector(\".batch-format\");\n\n    var sizeValue=box.querySelector(\".batch-size-value\");\n    var opacityValue=box.querySelector(\".batch-opacity-value\");\n    var marginValue=box.querySelector(\".batch-margin-value\");\n\n    var list=box.querySelector(\".batch-file-list\");\n    var button=box.querySelector(\".batch-start\");\n    var result=box.querySelector(\".batch-result\");\n    var message=box.querySelector(\".editor-mode-panel[data-editor-mode-panel='batch'] .itool-message\");\n\n    var images=[];\n    var logo=null;\n    var resultURLs=[];\n    var MAX_BATCH_FILES=100;\n    var MAX_BATCH_PIXELS=40000000;\n\n    imagesInput.addEventListener(\n        \"change\",\n        function(){\n            images=Array.from(imagesInput.files||[]);\n            if(images.length>MAX_BATCH_FILES){\n                images=images.slice(0,MAX_BATCH_FILES);\n                message.textContent=\"Batch t\u1ed1i \u0111a \"+MAX_BATCH_FILES+\" \u1ea3nh m\u1ed7i l\u1ea7n.\";\n            }\n\n            list.innerHTML=\"\";\n\n            images.forEach(\n                function(file,index){\n\n                    var row=document.createElement(\"div\");\n                    row.className=\"file-row\";\n\n                    row.innerHTML=\n                        \"<span>\"+\n                        \"<strong>\"+(index+1)+\". \"+\n                        escapeHtml(file.name)+\n                        \"<\/strong>\"+\n                        \"<\/span>\"+\n                        '<span class=\"file-size\">'+\n                        formatBytes(file.size)+\n                        \"<\/span>\";\n\n                    list.appendChild(row);\n                }\n            );\n\n            message.textContent=\n                \"\u0110\u00e3 ch\u1ecdn \"+images.length+\" \u1ea3nh.\";\n        }\n    );\n\n    logoInput.addEventListener(\n        \"change\",\n        function(){\n            logo=logoInput.files&&logoInput.files[0]\n                ? logoInput.files[0]\n                : null;\n\n            if(logo){\n                message.textContent=\n                    \"\u0110\u00e3 ch\u1ecdn logo: \"+logo.name;\n            }\n        }\n    );\n\n    size.addEventListener(\n        \"input\",\n        function(){\n            sizeValue.textContent=\n                size.value+\"%\";\n        }\n    );\n\n    opacity.addEventListener(\n        \"input\",\n        function(){\n            opacityValue.textContent=\n                opacity.value+\"%\";\n        }\n    );\n\n    margin.addEventListener(\n        \"input\",\n        function(){\n            marginValue.textContent=\n                margin.value+\"%\";\n        }\n    );\n\n    button.addEventListener(\n        \"click\",\n        async function(){\n\n            if(!images.length){\n                message.textContent=\n                    \"Vui l\u00f2ng ch\u1ecdn \u00edt nh\u1ea5t m\u1ed9t \u1ea3nh.\";\n                return;\n            }\n\n            if(!logo){\n                message.textContent=\n                    \"Vui l\u00f2ng ch\u1ecdn logo.\";\n                return;\n            }\n\n            var type=format.value;\n\n            if(\n                type===\"image\/avif\" &&\n                !canEncode(type)\n            ){\n                message.textContent=\n                    \"Tr\u00ecnh duy\u1ec7t ch\u01b0a h\u1ed7 tr\u1ee3 AVIF.\";\n                return;\n            }\n\n            button.disabled=true;\n            resultURLs.forEach(function(url){try{URL.revokeObjectURL(url);}catch(e){}});\n            resultURLs=[];\n            result.innerHTML=\"\";\n            message.textContent=\n                \"\u0110ang \u0111\u1ecdc logo...\";\n\n            try{\n\n                var logoImage=await loadImage(logo);\n\n                var grid=document.createElement(\"div\");\n                grid.className=\"batch-result-grid\";\n\n                result.appendChild(grid);\n\n                var logoSize=\n                    parseInt(size.value,10)\/100;\n\n                var alpha=\n                    parseInt(opacity.value,10)\/100;\n\n                var marginPct=\n                    parseInt(margin.value,10)\/100;\n\n                for(var i=0;i<images.length;i++){\n\n                    var file=images[i];\n\n                    message.textContent=\n                        \"\u0110ang x\u1eed l\u00fd \"+(i+1)+\"\/\"+\n                        images.length+\": \"+\n                        file.name;\n\n                    try{\n\n                        var image=await loadImage(file);\n\n                        ensureCanvasSize(image.naturalWidth,image.naturalHeight,\"\u1ea2nh batch\");\n                        var out=document.createElement(\"canvas\");\n                        out.width=image.naturalWidth;\n                        out.height=image.naturalHeight;\n\n                        var ctx=out.getContext(\"2d\");\n\n                        if(type===\"image\/jpeg\"){\n                            ctx.fillStyle=\"#fff\";\n                            ctx.fillRect(\n                                0,\n                                0,\n                                out.width,\n                                out.height\n                            );\n                        }\n\n                        ctx.drawImage(\n                            image,\n                            0,\n                            0,\n                            out.width,\n                            out.height\n                        );\n\n                        var lw=\n                            out.width*logoSize;\n\n                        var lh=\n                            logoImage.naturalHeight\/\n                            logoImage.naturalWidth*\n                            lw;\n\n                        var x=marginPct*out.width;\n                        var y=marginPct*out.height;\n\n                        switch(position.value){\n\n                            case\"top-left\":\n                                break;\n\n                            case\"top-center\":\n                                x=(out.width-lw)\/2;\n                                break;\n\n                            case\"top-right\":\n                                x=out.width-lw-marginPct*out.width;\n                                break;\n\n                            case\"center\":\n                                x=(out.width-lw)\/2;\n                                y=(out.height-lh)\/2;\n                                break;\n\n                            case\"bottom-left\":\n                                y=out.height-lh-marginPct*out.height;\n                                break;\n\n                            case\"bottom-center\":\n                                x=(out.width-lw)\/2;\n                                y=out.height-lh-marginPct*out.height;\n                                break;\n\n                            case\"bottom-right\":\n                                x=out.width-lw-marginPct*out.width;\n                                y=out.height-lh-marginPct*out.height;\n                                break;\n                        }\n\n                        x=Math.max(\n                            0,\n                            Math.min(\n                                out.width-lw,\n                                x\n                            )\n                        );\n\n                        y=Math.max(\n                            0,\n                            Math.min(\n                                out.height-lh,\n                                y\n                            )\n                        );\n\n                        ctx.save();\n\n                        ctx.globalAlpha=alpha;\n\n                        ctx.drawImage(\n                            logoImage,\n                            x,\n                            y,\n                            lw,\n                            lh\n                        );\n\n                        ctx.restore();\n\n                        var blob=await makeBlob(\n                            out,\n                            type,\n                            .9\n                        );\n\n                        if(!blob){\n                            throw new Error(\n                                \"Kh\u00f4ng t\u1ea1o \u0111\u01b0\u1ee3c \u1ea3nh.\"\n                            );\n                        }\n\n                        var ext=\n                            extensionFromMime(type);\n\n                        var base=\n                            file.name.replace(\n                                \/\\.[^\/.]+$\/,\n                                \"\"\n                            );\n\n                        var name=\n                            base+\n                            \"-logo.\"+\n                            ext;\n\n                        var item=\n                            document.createElement(\"div\");\n\n                        item.className=\n                            \"batch-result-item\";\n\n                        var url=URL.createObjectURL(blob);\n                        resultURLs.push(url);\n\n                        item.innerHTML=\n                            '<img decoding=\"async\" src=\"'+url+'\" alt=\"\">'+\n                            \"<strong>\"+\n                            escapeHtml(name)+\n                            \"<\/strong><br>\"+\n                            '<span class=\"file-size\">'+\n                            formatBytes(blob.size)+\n                            \"<\/span><br>\";\n\n                        var link=\n                            document.createElement(\"a\");\n\n                        link.className=\n                            \"download-link\";\n\n                        link.textContent=\n                            \"\u2b07\ufe0f T\u1ea3i\";\n\n                        link.href=url;\n                        link.download=name;\n\n                        item.appendChild(link);\n                        grid.appendChild(item);\n\n                        out.width=1;\n                        out.height=1;\n\n                    }catch(e){\n\n                        var err=\n                            document.createElement(\"div\");\n\n                        err.className=\n                            \"batch-result-item\";\n\n                        err.textContent=\n                            \"L\u1ed7i: \"+file.name;\n\n                        grid.appendChild(err);\n\n                        console.error(e);\n                    }\n\n                    await sleep(0);\n                }\n\n                message.textContent=\n                    \"\u0110\u00e3 x\u1eed l\u00fd xong \"+images.length+\" \u1ea3nh. \"+\n                    \"B\u1ea1n c\u00f3 th\u1ec3 t\u1ea3i t\u1eebng \u1ea3nh \u1edf ph\u00eda tr\u00ean.\";\n\n            }catch(e){\n\n                console.error(e);\n\n                message.textContent=\n                    e.message||\n                    \"Kh\u00f4ng th\u1ec3 x\u1eed l\u00fd h\u00e0ng lo\u1ea1t.\";\n\n            }finally{\n                button.disabled=false;\n            }\n        }\n    );\n\n    setupDragDrop(\n        imagesInput.closest(\".itool-upload\")\n    );\n\n    setupDragDrop(\n        logoInput.closest(\".itool-upload\")\n    );\n}\n\n\n\/* =========================================================\n   INITIALIZE\n   ========================================================= *\/\n\nfunction initialize(){\n\n    document.querySelectorAll(\n        \".itools-wrapper\"\n    ).forEach(function(wrapper){\n\n        if(wrapper.dataset.initialized===\"1\"){\n            return;\n        }\n\n        wrapper.dataset.initialized=\"1\";\n\n        initTabs(wrapper);\n\n        wrapper.querySelectorAll(\n            \"[data-module]\"\n        ).forEach(function(box){\n\n            var m=box.dataset.module;\n\n            if(m===\"compressor\"){\n                initCompressor(box);\n            }\n\n            if(m===\"cropper\"){\n                initCropper(box);\n            }\n\n            if(m===\"converter\"){\n                initConverter(box);\n            }\n\n            if(m===\"watermark\"){\n                initWatermark(box);\n            }\n        });\n    });\n}\n\nif(document.readyState===\"loading\"){\n    document.addEventListener(\n        \"DOMContentLoaded\",\n        initialize\n    );\n}else{\n    initialize();\n}\n\n})();\n<\/script>\n        \n<p><strong>\u1ee8ng d\u1ee5ng n\u00e9n h\u00ecnh \u1ea3nh online<\/strong> gi\u00fap b\u1ea1n gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh tr\u1ef1c ti\u1ebfp tr\u00ean tr\u00ecnh duy\u1ec7t m\u00e0 kh\u00f4ng c\u1ea7n c\u00e0i \u0111\u1eb7t ph\u1ea7n m\u1ec1m. C\u00f4ng c\u1ee5 ph\u00f9 h\u1ee3p \u0111\u1ec3 x\u1eed l\u00fd c\u00e1c h\u00ecnh \u1ea3nh c\u00f3 k\u00edch th\u01b0\u1edbc l\u1edbn tr\u01b0\u1edbc khi t\u1ea3i l\u00ean website, g\u1eedi qua email, chia s\u1ebb tr\u00ean m\u1ea1ng x\u00e3 h\u1ed9i ho\u1eb7c l\u01b0u tr\u1eef tr\u00ean thi\u1ebft b\u1ecb.<\/p>\n<p>Ch\u1ec9 v\u1edbi v\u00e0i thao t\u00e1c \u0111\u01a1n gi\u1ea3n, b\u1ea1n c\u00f3 th\u1ec3 t\u1ea3i h\u00ecnh \u1ea3nh l\u00ean v\u00e0 ti\u1ebfn h\u00e0nh n\u00e9n \u1ea3nh online nhanh ch\u00f3ng. H\u1ec7 th\u1ed1ng s\u1ebd t\u1ed1i \u01b0u dung l\u01b0\u1ee3ng file, gi\u00fap <a href=\"https:\/\/app.mitunet.com\/home\/chinh-sua-anh\/\">ch\u1ec9nh s\u1eeda h\u00ecnh \u1ea3nh<\/a> cho nh\u1eb9 h\u01a1n nh\u01b0ng v\u1eabn c\u1ed1 g\u1eafng duy tr\u00ec ch\u1ea5t l\u01b0\u1ee3ng hi\u1ec3n th\u1ecb t\u1ed1t. T\u00f9y nhu c\u1ea7u, b\u1ea1n c\u00f3 th\u1ec3 s\u1eed d\u1ee5ng c\u00f4ng c\u1ee5 \u0111\u1ec3 n\u00e9n c\u00e1c \u0111\u1ecbnh d\u1ea1ng \u1ea3nh ph\u1ed5 bi\u1ebfn nh\u01b0 JPG, JPEG, PNG ho\u1eb7c WebP.<\/p>\n<p>Vi\u1ec7c <strong>gi\u1ea3m dung l\u01b0\u1ee3ng h\u00ecnh \u1ea3nh online<\/strong> kh\u00f4ng ch\u1ec9 gi\u00fap ti\u1ebft ki\u1ec7m kh\u00f4ng gian l\u01b0u tr\u1eef m\u00e0 c\u00f2n \u0111\u1eb7c bi\u1ec7t h\u1eefu \u00edch \u0111\u1ed1i v\u1edbi website. H\u00ecnh \u1ea3nh c\u00f3 dung l\u01b0\u1ee3ng th\u1ea5p s\u1ebd gi\u00fap gi\u1ea3m l\u01b0\u1ee3ng d\u1eef li\u1ec7u c\u1ea7n t\u1ea3i xu\u1ed1ng, t\u1eeb \u0111\u00f3 h\u1ed7 tr\u1ee3 c\u1ea3i thi\u1ec7n t\u1ed1c \u0111\u1ed9 t\u1ea3i trang v\u00e0 mang l\u1ea1i tr\u1ea3i nghi\u1ec7m t\u1ed1t h\u01a1n cho ng\u01b0\u1eddi d\u00f9ng. \u0110\u00e2y c\u0169ng l\u00e0 m\u1ed9t trong nh\u1eefng b\u01b0\u1edbc quan tr\u1ecdng khi t\u1ed1i \u01b0u h\u00ecnh \u1ea3nh cho website v\u00e0 SEO.<\/p>\n<p>V\u1edbi \u1ee9ng d\u1ee5ng <strong>n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed<\/strong>, b\u1ea1n c\u00f3 th\u1ec3 x\u1eed l\u00fd \u1ea3nh m\u1ecdi l\u00fac tr\u00ean m\u00e1y t\u00ednh, \u0111i\u1ec7n tho\u1ea1i ho\u1eb7c m\u00e1y t\u00ednh b\u1ea3ng m\u00e0 kh\u00f4ng c\u1ea7n c\u00e0i \u0111\u1eb7t th\u00eam ch\u01b0\u01a1ng tr\u00ecnh. H\u00e3y t\u1ea3i h\u00ecnh \u1ea3nh l\u00ean c\u00f4ng c\u1ee5, ch\u1ecdn m\u1ee9c \u0111\u1ed9 n\u00e9n ph\u00f9 h\u1ee3p v\u00e0 t\u1ea3i xu\u1ed1ng file \u0111\u00e3 \u0111\u01b0\u1ee3c t\u1ed1i \u01b0u \u0111\u1ec3 s\u1eed d\u1ee5ng ngay.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u1ee8ng d\u1ee5ng n\u00e9n \u1ea3nh online n\u00e0y gi\u00fap b\u1ea1n gi\u1ea3m dung l\u01b0\u1ee3ng h\u00ecnh \u1ea3nh nhanh ch\u00f3ng m\u00e0 kh\u00f4ng c\u1ea7n c\u00e0i \u0111\u1eb7t ph\u1ea7n m\u1ec1m tr\u00ean m\u00e1y t\u00ednh. Ch\u1ec9 v\u1edbi v\u00e0i thao t\u00e1c \u0111\u01a1n gi\u1ea3n, b\u1ea1n c\u00f3 th\u1ec3 n\u00e9n \u1ea3nh JPG, PNG, WebP v\u00e0 nhi\u1ec1u \u0111\u1ecbnh d\u1ea1ng ph\u1ed5 bi\u1ebfn kh\u00e1c, \u0111\u1ed3ng th\u1eddi gi\u1eef ch\u1ea5t l\u01b0\u1ee3ng h\u00ecnh \u1ea3nh \u1edf [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":540,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[27],"class_list":["post-534","post","type-post","status-publish","format-standard","has-post-thumbnail","category-tien-ich","tag-hinh-anh"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh | App Mitunet<\/title>\n<meta name=\"description\" content=\"C\u00f4ng c\u1ee5 n\u00e9n \u1ea3nh online mi\u1ec5n ph\u00ed, ti\u1ec7n \u00edch n\u00e9n h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn, gi\u1ea3i ph\u00e1p n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn mi\u1ec5n ph\u00ed t\u1ed1t nh\u1ea5t, c\u00e1ch n\u00e9n images...\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/app.mitunet.com\/home\/nen-anh-online\/\" \/>\n<meta property=\"og:locale\" content=\"vi_VN\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh | App Mitunet\" \/>\n<meta property=\"og:description\" content=\"C\u00f4ng c\u1ee5 n\u00e9n \u1ea3nh online mi\u1ec5n ph\u00ed, ti\u1ec7n \u00edch n\u00e9n h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn, gi\u1ea3i ph\u00e1p n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn mi\u1ec5n ph\u00ed t\u1ed1t nh\u1ea5t, c\u00e1ch n\u00e9n images...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/app.mitunet.com\/home\/nen-anh-online\/\" \/>\n<meta property=\"og:site_name\" content=\"App Mitunet\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-10T14:26:18+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-08-10T14:43:23+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/app.mitunet.com\/home\/wp-content\/uploads\/2026\/08\/nen-anh-online.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"800\" \/>\n\t<meta property=\"og:image:height\" content=\"600\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Ho\u00e0ng Nguy\u1ec5n\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u0110\u01b0\u1ee3c vi\u1ebft b\u1edfi\" \/>\n\t<meta name=\"twitter:data1\" content=\"Ho\u00e0ng Nguy\u1ec5n\" \/>\n\t<meta name=\"twitter:label2\" content=\"\u01af\u1edbc t\u00ednh th\u1eddi gian \u0111\u1ecdc\" \/>\n\t<meta name=\"twitter:data2\" content=\"3 ph\u00fat\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/nen-anh-online\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/nen-anh-online\\\/\"},\"author\":{\"name\":\"Ho\u00e0ng Nguy\u1ec5n\",\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/#\\\/schema\\\/person\\\/f50dd8aac6139374e226f02e17793e76\"},\"headline\":\"C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh\",\"datePublished\":\"2026-08-10T14:26:18+00:00\",\"dateModified\":\"2026-08-10T14:43:23+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/nen-anh-online\\\/\"},\"wordCount\":567,\"image\":{\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/nen-anh-online\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/nen-anh-online.jpg\",\"keywords\":[\"H\u00ecnh \u1ea3nh\"],\"articleSection\":[\"Ti\u1ec7n \u00edch\"],\"inLanguage\":\"vi\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/nen-anh-online\\\/\",\"url\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/nen-anh-online\\\/\",\"name\":\"C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh | App Mitunet\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/nen-anh-online\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/nen-anh-online\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/nen-anh-online.jpg\",\"datePublished\":\"2026-08-10T14:26:18+00:00\",\"dateModified\":\"2026-08-10T14:43:23+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/#\\\/schema\\\/person\\\/f50dd8aac6139374e226f02e17793e76\"},\"description\":\"C\u00f4ng c\u1ee5 n\u00e9n \u1ea3nh online mi\u1ec5n ph\u00ed, ti\u1ec7n \u00edch n\u00e9n h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn, gi\u1ea3i ph\u00e1p n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn mi\u1ec5n ph\u00ed t\u1ed1t nh\u1ea5t, c\u00e1ch n\u00e9n images...\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/nen-anh-online\\\/#breadcrumb\"},\"inLanguage\":\"vi\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/app.mitunet.com\\\/home\\\/nen-anh-online\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"vi\",\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/nen-anh-online\\\/#primaryimage\",\"url\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/nen-anh-online.jpg\",\"contentUrl\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/nen-anh-online.jpg\",\"width\":800,\"height\":600,\"caption\":\"C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/nen-anh-online\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Trang ch\u1ee7\",\"item\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/#website\",\"url\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/\",\"name\":\"App Mitunet\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"vi\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/app.mitunet.com\\\/home\\\/#\\\/schema\\\/person\\\/f50dd8aac6139374e226f02e17793e76\",\"name\":\"Ho\u00e0ng Nguy\u1ec5n\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"vi\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f94a5ef7ba48bd05196f8ba8340b297655b9adc8975f99cf481c8e14dea06e48?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f94a5ef7ba48bd05196f8ba8340b297655b9adc8975f99cf481c8e14dea06e48?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f94a5ef7ba48bd05196f8ba8340b297655b9adc8975f99cf481c8e14dea06e48?s=96&d=mm&r=g\",\"caption\":\"Ho\u00e0ng Nguy\u1ec5n\"},\"description\":\"Hi, m\u00ecnh l\u00e0 Ho\u00e0ng Nguy\u1ec5n, \u0111\u00e3 t\u1eebng c\u00f3 h\u01a1n 10 n\u0103m kinh nghi\u1ec7m trong l\u0129nh v\u1ef1c Marketing Online nh\u01b0 SEO, qu\u1ea3ng c\u00e1o, content, qu\u1ea3n tr\u1ecb web, ph\u00e1t tri\u1ec5n website... Hy v\u1ecdng v\u1edbi nh\u1eefng n\u1ed9i dung c\u1ee7a m\u00ecnh s\u1ebb gi\u00fap b\u1ea1n hi\u1ec7u qu\u1ea3 h\u01a1n trong vi\u1ec7c marketing online.\",\"sameAs\":[\"https:\\\/\\\/app.mitunet.com\\\/home\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh | App Mitunet","description":"C\u00f4ng c\u1ee5 n\u00e9n \u1ea3nh online mi\u1ec5n ph\u00ed, ti\u1ec7n \u00edch n\u00e9n h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn, gi\u1ea3i ph\u00e1p n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn mi\u1ec5n ph\u00ed t\u1ed1t nh\u1ea5t, c\u00e1ch n\u00e9n images...","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:\/\/app.mitunet.com\/home\/nen-anh-online\/","og_locale":"vi_VN","og_type":"article","og_title":"C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh | App Mitunet","og_description":"C\u00f4ng c\u1ee5 n\u00e9n \u1ea3nh online mi\u1ec5n ph\u00ed, ti\u1ec7n \u00edch n\u00e9n h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn, gi\u1ea3i ph\u00e1p n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn mi\u1ec5n ph\u00ed t\u1ed1t nh\u1ea5t, c\u00e1ch n\u00e9n images...","og_url":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/","og_site_name":"App Mitunet","article_published_time":"2026-08-10T14:26:18+00:00","article_modified_time":"2026-08-10T14:43:23+00:00","og_image":[{"width":800,"height":600,"url":"https:\/\/app.mitunet.com\/home\/wp-content\/uploads\/2026\/08\/nen-anh-online.jpg","type":"image\/jpeg"}],"author":"Ho\u00e0ng Nguy\u1ec5n","twitter_card":"summary_large_image","twitter_misc":{"\u0110\u01b0\u1ee3c vi\u1ebft b\u1edfi":"Ho\u00e0ng Nguy\u1ec5n","\u01af\u1edbc t\u00ednh th\u1eddi gian \u0111\u1ecdc":"3 ph\u00fat"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/#article","isPartOf":{"@id":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/"},"author":{"name":"Ho\u00e0ng Nguy\u1ec5n","@id":"https:\/\/app.mitunet.com\/home\/#\/schema\/person\/f50dd8aac6139374e226f02e17793e76"},"headline":"C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh","datePublished":"2026-08-10T14:26:18+00:00","dateModified":"2026-08-10T14:43:23+00:00","mainEntityOfPage":{"@id":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/"},"wordCount":567,"image":{"@id":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/#primaryimage"},"thumbnailUrl":"https:\/\/app.mitunet.com\/home\/wp-content\/uploads\/2026\/08\/nen-anh-online.jpg","keywords":["H\u00ecnh \u1ea3nh"],"articleSection":["Ti\u1ec7n \u00edch"],"inLanguage":"vi"},{"@type":"WebPage","@id":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/","url":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/","name":"C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh | App Mitunet","isPartOf":{"@id":"https:\/\/app.mitunet.com\/home\/#website"},"primaryImageOfPage":{"@id":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/#primaryimage"},"image":{"@id":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/#primaryimage"},"thumbnailUrl":"https:\/\/app.mitunet.com\/home\/wp-content\/uploads\/2026\/08\/nen-anh-online.jpg","datePublished":"2026-08-10T14:26:18+00:00","dateModified":"2026-08-10T14:43:23+00:00","author":{"@id":"https:\/\/app.mitunet.com\/home\/#\/schema\/person\/f50dd8aac6139374e226f02e17793e76"},"description":"C\u00f4ng c\u1ee5 n\u00e9n \u1ea3nh online mi\u1ec5n ph\u00ed, ti\u1ec7n \u00edch n\u00e9n h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn, gi\u1ea3i ph\u00e1p n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng h\u00ecnh \u1ea3nh tr\u1ef1c tuy\u1ebfn mi\u1ec5n ph\u00ed t\u1ed1t nh\u1ea5t, c\u00e1ch n\u00e9n images...","breadcrumb":{"@id":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/#breadcrumb"},"inLanguage":"vi","potentialAction":[{"@type":"ReadAction","target":["https:\/\/app.mitunet.com\/home\/nen-anh-online\/"]}]},{"@type":"ImageObject","inLanguage":"vi","@id":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/#primaryimage","url":"https:\/\/app.mitunet.com\/home\/wp-content\/uploads\/2026\/08\/nen-anh-online.jpg","contentUrl":"https:\/\/app.mitunet.com\/home\/wp-content\/uploads\/2026\/08\/nen-anh-online.jpg","width":800,"height":600,"caption":"C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed"},{"@type":"BreadcrumbList","@id":"https:\/\/app.mitunet.com\/home\/nen-anh-online\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Trang ch\u1ee7","item":"https:\/\/app.mitunet.com\/home\/"},{"@type":"ListItem","position":2,"name":"C\u00f4ng c\u1ee5 n\u00e9n h\u00ecnh \u1ea3nh online mi\u1ec5n ph\u00ed, \u1ee9ng d\u1ee5ng n\u00e9n gi\u1ea3m dung l\u01b0\u1ee3ng \u1ea3nh"}]},{"@type":"WebSite","@id":"https:\/\/app.mitunet.com\/home\/#website","url":"https:\/\/app.mitunet.com\/home\/","name":"App Mitunet","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/app.mitunet.com\/home\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"vi"},{"@type":"Person","@id":"https:\/\/app.mitunet.com\/home\/#\/schema\/person\/f50dd8aac6139374e226f02e17793e76","name":"Ho\u00e0ng Nguy\u1ec5n","image":{"@type":"ImageObject","inLanguage":"vi","@id":"https:\/\/secure.gravatar.com\/avatar\/f94a5ef7ba48bd05196f8ba8340b297655b9adc8975f99cf481c8e14dea06e48?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/f94a5ef7ba48bd05196f8ba8340b297655b9adc8975f99cf481c8e14dea06e48?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f94a5ef7ba48bd05196f8ba8340b297655b9adc8975f99cf481c8e14dea06e48?s=96&d=mm&r=g","caption":"Ho\u00e0ng Nguy\u1ec5n"},"description":"Hi, m\u00ecnh l\u00e0 Ho\u00e0ng Nguy\u1ec5n, \u0111\u00e3 t\u1eebng c\u00f3 h\u01a1n 10 n\u0103m kinh nghi\u1ec7m trong l\u0129nh v\u1ef1c Marketing Online nh\u01b0 SEO, qu\u1ea3ng c\u00e1o, content, qu\u1ea3n tr\u1ecb web, ph\u00e1t tri\u1ec5n website... Hy v\u1ecdng v\u1edbi nh\u1eefng n\u1ed9i dung c\u1ee7a m\u00ecnh s\u1ebb gi\u00fap b\u1ea1n hi\u1ec7u qu\u1ea3 h\u01a1n trong vi\u1ec7c marketing online.","sameAs":["https:\/\/app.mitunet.com\/home"]}]}},"_links":{"self":[{"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/posts\/534","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/comments?post=534"}],"version-history":[{"count":6,"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/posts\/534\/revisions"}],"predecessor-version":[{"id":541,"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/posts\/534\/revisions\/541"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/media\/540"}],"wp:attachment":[{"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/media?parent=534"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/categories?post=534"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/app.mitunet.com\/home\/wp-json\/wp\/v2\/tags?post=534"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}