{"id":956,"date":"2025-06-22T15:03:42","date_gmt":"2025-06-22T22:03:42","guid":{"rendered":"https:\/\/www.sandcomp.com\/blog\/?page_id=956"},"modified":"2025-07-21T18:22:40","modified_gmt":"2025-07-22T01:22:40","slug":"sandphoto-en","status":"publish","type":"page","link":"https:\/\/www.sandcomp.com\/blog\/en\/sandphoto-en\/","title":{"rendered":"Online ID Photo Layout Generator 2.0"},"content":{"rendered":"\n<p>  <!-- HFCM by 99 Robots - Snippet # 4: sandphoto-js-scripts -->\n<script data-jetpack-boost=\"ignore\" type=\"text\/javascript\" src=\"https:\/\/www.sandcomp.com\/sandphoto-js\/phototypes.js?v=20250715\"><\/script>\r\n<script data-jetpack-boost=\"ignore\" type=\"text\/javascript\" src=\"https:\/\/www.sandcomp.com\/sandphoto-js\/sandphoto.js?v=20250715\"><\/script>\r\n<script data-jetpack-boost=\"ignore\" type=\"text\/javascript\" src=\"https:\/\/www.sandcomp.com\/sandphoto-js\/ui-generator.js?v=20250715\"><\/script>\r\n<script data-jetpack-boost=\"ignore\" type=\"text\/javascript\" src=\"https:\/\/www.sandcomp.com\/sandphoto-js\/app.js?v=20250715\"><\/script>\n<!-- \/end HFCM by 99 Robots -->\n <!-- HFCM by 99 Robots - Snippet # 3: sandphoto-js-en -->\n<script data-jetpack-boost=\"ignore\">\r\n        \/\/ Set your language here (only one place)\r\n        const language = 'en'; \/\/ Change to 'zh', 'es', etc. as needed\r\n\r\n        document.addEventListener('DOMContentLoaded', () => {\r\n            \/\/ Get the config for the selected language\r\n            const langConfig = UIGenerator.getLanguageConfig(language);\r\n            const uiConfig = {\r\n                ...langConfig,\r\n                previewType: 'canvas'\r\n            };\r\n            const uiGenerator = new UIGenerator(uiConfig);\r\n\r\n            \/\/ Generate the complete form\r\n            const form = uiGenerator.generateCompleteForm('sandphoto-form');\r\n            const container = document.getElementById('sandphoto-form-container');\r\n            container.appendChild(form);\r\n\r\n            \/\/ Create app with main page configuration\r\n            const mainConfig = {\r\n                language: language,\r\n                elementIds: {\r\n                    uploadArea: 'uploadArea',\r\n                    photoInput: 'filename',\r\n                    targetTypeSelect: 'targetType',\r\n                    containerTypeSelect: 'containerType',\r\n                    bgColorSelect: 'bgColor', \/\/ Use dropdown\r\n                    previewSection: 'previewContainer',\r\n                    previewCanvas: 'previewCanvas',\r\n                    photoCount: 'count',\r\n                    downloadBtn: 'downloadBtn',\r\n                    customSizeGroup: 'customSizeSection',\r\n                    customSizeGroup2: null,\r\n                    customWidthInput: 'customWidth',\r\n                    customHeightInput: 'customHeight',\r\n                    photoCountSelect: 'photoCountSelect',\r\n                    customCountGroup: 'photoCountSection',\r\n                    customPhotoCountInput: 'customPhotoCount',\r\n\t\t\t\t\t\t\t\t\t  gapInputMm: 'gapMm',\r\n                    \/\/ Multi-photo elements\r\n                    singleUploadArea: 'singleUploadArea',\r\n                    multiUploadArea: 'multiUploadArea',\r\n                    multiFileInput: 'multiFilename',\r\n                    photoListContainer: 'photoListContainer',\r\n                    photoList: 'photoList',\r\n                    uploadModeRadios: 'uploadMode'\r\n                },\r\n                texts: langConfig.texts\r\n            };\r\n\r\n            new SandPhotoApp(mainConfig);\r\n        });\r\n<\/script>\n<!-- \/end HFCM by 99 Robots -->\n<!-- HFCM by 99 Robots - Snippet # 2: sandphoto-js style -->\n<style>\r\n\r\n.container {\r\n    max-width: 1200px;\r\n    margin: 0 auto;\r\n    padding: 20px;\r\n}\r\n\r\n.upload-section {\r\n    margin-bottom: 40px;\r\n}\r\n\r\n.upload-area {\r\n    border: 3px dashed #ddd;\r\n    border-radius: 15px;\r\n    padding: 60px 20px;\r\n    text-align: center;\r\n    transition: all 0.3s ease;\r\n    background: #fafafa;\r\n    cursor: pointer;\r\n}\r\n\r\n.upload-area:hover {\r\n    border-color: #667eea;\r\n    background: #f0f4ff;\r\n}\r\n\r\n.upload-area.dragover {\r\n    border-color: #667eea;\r\n    background: #e8f0ff;\r\n    transform: scale(1.02);\r\n}\r\n\r\n.upload-icon {\r\n    margin-bottom: 20px;\r\n}\r\n\r\n.upload-content h3 {\r\n    margin-bottom: 10px;\r\n    color: #333;\r\n}\r\n\r\n.upload-content p {\r\n    color: #666;\r\n    margin-bottom: 20px;\r\n}\r\n\r\n.upload-btn {\r\n    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n    color: white;\r\n    border: none;\r\n    padding: 12px 30px;\r\n    border-radius: 25px;\r\n    font-size: 1rem;\r\n    cursor: pointer;\r\n    transition: all 0.3s ease;\r\n    box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);\r\n}\r\n\r\n.upload-btn:hover {\r\n    transform: translateY(-2px);\r\n    box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);\r\n}\r\n\r\n.settings-section {\r\n    margin-bottom: 40px;\r\n}\r\n\r\n.settings-grid {\r\n    display: grid;\r\n    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\r\n    gap: 20px;\r\n}\r\n\r\n.setting-group {\r\n    display: flex;\r\n    flex-direction: column;\r\n}\r\n\r\n.setting-group label {\r\n    margin-bottom: 8px;\r\n    color: #333;\r\n}\r\n\r\n.form-control {\r\n    padding: 12px;\r\n    border: 2px solid #ddd;\r\n    border-radius: 8px;\r\n    transition: border-color 0.3s ease;\r\n    background: white;\r\n}\r\n\r\n.form-control:focus {\r\n    outline: none;\r\n    border-color: #667eea;\r\n    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);\r\n}\r\n\r\n\/* Custom size input styling *\/\r\n#customSizeGroup,\r\n#customSizeGroup2,\r\n#customCountGroup {\r\n    transition: all 0.3s ease;\r\n}\r\n\r\n#customSizeGroup.show,\r\n#customSizeGroup2.show,\r\n#customCountGroup.show {\r\n    animation: slideDown 0.3s ease;\r\n}\r\n\r\n@keyframes slideDown {\r\n    from {\r\n        opacity: 0;\r\n        transform: translateY(-10px);\r\n    }\r\n    to {\r\n        opacity: 1;\r\n        transform: translateY(0);\r\n    }\r\n}\r\n\r\n\/* Number input specific styling *\/\r\ninput[type=\"number\"].form-control {\r\n    -moz-appearance: textfield;\r\n}\r\n\r\ninput[type=\"number\"].form-control::-webkit-outer-spin-button,\r\ninput[type=\"number\"].form-control::-webkit-inner-spin-button {\r\n    -webkit-appearance: none;\r\n    margin: 0;\r\n}\r\n\r\n.preview-section {\r\n    border-top: 2px solid #eee;\r\n    padding-top: 30px;\r\n}\r\n\r\n.preview-section h3 {\r\n    text-align: center;\r\n    margin-bottom: 20px;\r\n    color: #333;\r\n}\r\n\r\n.preview-container {\r\n    display: flex;\r\n    justify-content: center;\r\n    margin-bottom: 20px;\r\n}\r\n\r\n#previewCanvas {\r\n    border: 2px solid #ddd;\r\n    border-radius: 10px;\r\n    max-width: 100%;\r\n    height: auto;\r\n    box-shadow: 0 4px 15px rgba(0,0,0,0.1);\r\n}\r\n\r\n.preview-info {\r\n    text-align: center;\r\n}\r\n\r\n#photoCount {\r\n    margin-bottom: 15px;\r\n    color: #666;\r\n}\r\n\r\n#photoCount span {\r\n    font-weight: bold;\r\n    color: #667eea;\r\n}\r\n\r\n.download-btn {\r\n    background: linear-gradient(135deg, #28a745 0%, #20c997 100%);\r\n    color: white;\r\n    border: none;\r\n    padding: 15px 40px;\r\n    border-radius: 25px;\r\n    cursor: pointer;\r\n    transition: all 0.3s ease;\r\n    box-shadow: 0 4px 15px rgba(40, 167, 69, 0.4);\r\n}\r\n\r\n.download-btn:hover {\r\n    transform: translateY(-2px);\r\n    box-shadow: 0 6px 20px rgba(40, 167, 69, 0.6);\r\n}\r\n\r\n.download-btn:disabled {\r\n    background: #ccc;\r\n    cursor: not-allowed;\r\n    transform: none;\r\n    box-shadow: none;\r\n}\r\n\r\n\r\n\r\n\/* Loading animation *\/\r\n.loading {\r\n    display: inline-block;\r\n    width: 20px;\r\n    height: 20px;\r\n    border: 3px solid #f3f3f3;\r\n    border-top: 3px solid #667eea;\r\n    border-radius: 50%;\r\n    animation: spin 1s linear infinite;\r\n}\r\n\r\n@keyframes spin {\r\n    0% { transform: rotate(0deg); }\r\n    100% { transform: rotate(360deg); }\r\n}\r\n\r\n\/* Responsive design *\/\r\n@media (max-width: 768px) {\r\n    .container {\r\n        padding: 10px;\r\n    }\r\n    \r\n    main {\r\n        padding: 20px;\r\n    }\r\n    \r\n    header h1 {\r\n    }\r\n    \r\n    .settings-grid {\r\n        grid-template-columns: 1fr;\r\n    }\r\n    \r\n    .upload-area {\r\n        padding: 40px 20px;\r\n    }\r\n    \r\n    #previewCanvas {\r\n        width: 100%;\r\n        height: auto;\r\n    }\r\n} \r\n<\/style>\n<!-- \/end HFCM by 99 Robots -->\n<\/p>\n\n\n\n<p>[Updated on July 14, 2025] New update:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Pure client-side implementation \u2014 your photos never leave your device<\/strong>! <\/li>\n\n\n\n<li>You can choose the number of photos, you can choose only one photo!<\/li>\n\n\n\n<li>If you encounter any issues, you can use the <a href=\"https:\/\/www.sandcomp.com\/blog\/en\/online-id-photo-layout-generator-old-version\/\" title=\"\">old version<\/a> and leave feedback. Thank you!<\/li>\n<\/ul>\n\n\n\n<p>Do you find printing ID photos too expensive? Here&#8217;s a money-saving trick: Arrange multiple ID photos on a single 6-inch photo, and you can print a bunch of ID photos for just a few cents.<br>What? You don&#8217;t know how to lay them out with Photoshop? Or you&#8217;re too lazy to do it? No worries \u2014 I got tired of using Photoshop every time too, so I wrote a little program. Give it a try!<\/p>\n\n\n\n<p>By the way, I\u2019ve open-sourced the code and uploaded it to <a href=\"https:\/\/github.com\/gmajian\/sandphoto\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"github\">GitH<\/a><a href=\"https:\/\/github.com\/gmajian\/sandphoto-js\" target=\"_blank\" rel=\"noopener\" title=\"github\">u<\/a><a href=\"https:\/\/github.com\/gmajian\/sandphoto\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"github\">b<\/a>. Feel free to check it out.<br>&nbsp;<\/p>\n\n\n\n<div id=\"sandphoto-form-container\"><!-- Form will be generated here by JavaScript --><\/div>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p>If you have any suggestions, feel free to leave a comment. You&#8217;re also welcome to help spread the word. The site address is: <a href=\"https:\/\/www.sandcomp.com\/blog\/en\/sandphoto-en\/\" title=\"\">https:\/\/www.sandcomp.com\/blog\/en\/sandphoto\/<\/a><\/p>\n\n\n\n<div class=\"wp-block-jetpack-donations\"><h4>\u5355\u6b21\u6350\u8d60<\/h4><p>Thank your for your dontation!<\/p><a class=\"jetpack-donations-fallback-link\" rel=\"noopener noreferrer noamphtml\" target=\"_blank\">\u6350\u8d60<\/a><\/div>\n","protected":false},"excerpt":{"rendered":"<p>[Updated on July 14, 2025] New update: D &hellip; <a href=\"https:\/\/www.sandcomp.com\/blog\/en\/sandphoto-en\/\">\u7ee7\u7eed\u9605\u8bfb <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"open","ping_status":"closed","template":"","meta":{"jetpack_post_was_ever_published":false,"footnotes":""},"class_list":["post-956","page","type-page","status-publish","hentry"],"aioseo_notices":[],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.sandcomp.com\/blog\/wp-json\/wp\/v2\/pages\/956","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sandcomp.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.sandcomp.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.sandcomp.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sandcomp.com\/blog\/wp-json\/wp\/v2\/comments?post=956"}],"version-history":[{"count":9,"href":"https:\/\/www.sandcomp.com\/blog\/wp-json\/wp\/v2\/pages\/956\/revisions"}],"predecessor-version":[{"id":1061,"href":"https:\/\/www.sandcomp.com\/blog\/wp-json\/wp\/v2\/pages\/956\/revisions\/1061"}],"wp:attachment":[{"href":"https:\/\/www.sandcomp.com\/blog\/wp-json\/wp\/v2\/media?parent=956"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}