Category: image

  • stickers(gif) – beautiful frustration

    #bookmark

    (function() {
        'use strict';
        const sub = window.SITE_SUBDOMAIN;
    
        // 1. UI INJECTION (All tools included)
        const uiWrap = document.createElement('div');
        uiWrap.id = "cms-editor-wrapper";
        uiWrap.innerHTML = `
            <div id="sel-tb" class="sel-tb">
                <button data-cmd="bold"><span class="material-icons">format_bold</span></button>
                <button data-cmd="italic"><span class="material-icons">format_italic</span></button>
                <button data-cmd="underline"><span class="material-icons">format_underlined</span></button>
                <button id="add-lnk"><span class="material-icons">link</span></button>
                <button id="add-id"><span class="material-icons">fingerprint</span></button>
                <div class="ctrl-grp"><input type="color" id="clr-txt" value="#000000"></div>
                <div class="ctrl-grp"><input type="color" id="clr-bg" value="#ffff00"></div>
            </div>
            <div id="plus-btn"><span class="material-icons">add</span></div>
            <div id="ins-m" class="ins-m">
                <div class="group"><div class="g-title">Text Blocks</div><div class="g-row">
                    <div data-b="H1"><span class="material-icons">looks_one</span><label>H1</label></div>
                    <div data-b="H2"><span class="material-icons">looks_two</span><label>H2</label></div>
                    <div data-b="H3"><span class="material-icons">looks_3</span><label>H3</label></div>
                    <div data-b="H4"><span class="material-icons">looks_4</span><label>H4</label></div>
                    <div data-b="P"><span class="material-icons">subject</span><label>P</label></div>
                    <div id="do-ul"><span class="material-icons">format_list_bulleted</span><label>List</label></div>
                </div></div>
                <div class="group"><div class="g-title">Media</div><div class="g-row">
                    <div id="do-img"><span class="material-icons">image</span><label>Img/Sticker</label></div>
                    <div id="do-vid"><span class="material-icons">smart_display</span><label>Video</label></div>
                </div></div>
                <div class="group"><div class="g-title">Layout</div><div class="g-row">
                    <div data-g="2"><span class="material-icons">view_week</span><label>2 Col</label></div>
                    <div data-g="3"><span class="material-icons">view_column</span><label>3 Col</label></div>
                    <div data-g="4"><span class="material-icons">view_module</span><label>4 Col</label></div>
                    <div data-g="6"><span class="material-icons">grid_view</span><label>6 Col</label></div>
                    <div data-g="8"><span class="material-icons">grid_on</span><label>8 Col</label></div>
                </div></div>
            </div>
            <input type="file" id="f-sys" style="display:none" accept="image/*,image/avif">
        `;
        document.body.appendChild(uiWrap);
    
        const viewport = document.querySelector('.viewport') || document.querySelector('.editor');
        const selTb = document.getElementById('sel-tb');
        const plusBtn = document.getElementById('plus-btn');
        const insMenu = document.getElementById('ins-m');
        const fSys = document.getElementById('f-sys');
    
        let activeRange = null, activeBlock = null;
        const autoSave = () => { if(window.performAutoSave) window.performAutoSave(); };
    
        // --- UPLOAD HELPER ---
        async function uploadToServer(file) {
            const fd = new FormData(); fd.append('file', file);
            try {
                const r = await fetch(`https://madenp.com/upload.php?action=upload&subdomain=${sub}`, { method: 'POST', body: fd, credentials: 'include' });
                const d = await r.json();
                return d.success ? `https://madenp.com/media.php?s=${sub}&f=${d.filename}` : null;
            } catch (e) { return null; }
        }
    
        // --- RESPONSIVE TRANSFORM ENGINE ---
        const applyTransform = (el) => {
            el.style.position = 'absolute';
            el.style.left = el.dataset.x + "%";
            el.style.top = el.dataset.y + "%";
            // translate(-50%, -50%) ensures the CENTER of the image stays on the % coordinate
            el.style.transform = `translate(-50%, -50%) scale(${el.dataset.scale}) rotate(${el.dataset.rotate}deg)`;
            el.style.margin = "0";
            el.classList.add('is-active');
        };
    
        const activateSticker = (sticker) => {
            if (sticker.classList.contains('is-active')) return;
    
            // Find the nearest parent section/article to anchor to
            const parent = sticker.closest('section, article, .row, .viewport, .editor');
            parent.style.position = 'relative';
    
            const pRect = parent.getBoundingClientRect();
            const sRect = sticker.getBoundingClientRect();
    
            // Calculate center relative to parent in percentages
            const centerX = (sRect.left + sRect.width / 2) - pRect.left;
            const centerY = (sRect.top + sRect.height / 2) - pRect.top;
    
            sticker.dataset.x = ((centerX / pRect.width) * 100).toFixed(2);
            sticker.dataset.y = ((centerY / pRect.height) * 100).toFixed(2);
            sticker.dataset.scale = sticker.dataset.scale || "1";
            sticker.dataset.rotate = sticker.dataset.rotate || "0";
    
            applyTransform(sticker);
        };
    
        document.addEventListener('mousedown', (e) => {
            const sticker = e.target.closest('.gif-sticker');
            if (!sticker) return;
    
            activateSticker(sticker);
    
            const parent = sticker.parentElement;
            const isResizer = e.target.classList.contains('resizer');
            const isRotator = e.target.closest('.rotator');
    
            if (isResizer) {
                const onResize = (ev) => {
                    let scale = parseFloat(sticker.dataset.scale);
                    scale += ev.movementX * 0.005;
                    sticker.dataset.scale = Math.max(0.1, scale).toFixed(3);
                    applyTransform(sticker);
                };
                document.addEventListener('mousemove', onResize);
                document.addEventListener('mouseup', () => { document.removeEventListener('mousemove', onResize); autoSave(); }, {once:true});
            }
            else if (isRotator) {
                const onRotate = (ev) => {
                    let r = parseFloat(sticker.dataset.rotate);
                    r += ev.movementX;
                    sticker.dataset.rotate = r.toFixed(1);
                    applyTransform(sticker);
                };
                document.addEventListener('mousemove', onRotate);
                document.addEventListener('mouseup', () => { document.removeEventListener('mousemove', onRotate); autoSave(); }, {once:true});
            }
            else {
                const onMove = (ev) => {
                    const pRect = parent.getBoundingClientRect();
                    let x = ((ev.clientX - pRect.left) / pRect.width) * 100;
                    let y = ((ev.clientY - pRect.top) / pRect.height) * 100;
                    sticker.dataset.x = x.toFixed(2);
                    sticker.dataset.y = y.toFixed(2);
                    applyTransform(sticker);
                };
                document.addEventListener('mousemove', onMove);
                document.addEventListener('mouseup', () => { document.removeEventListener('mousemove', onMove); autoSave(); }, {once:true});
            }
            e.preventDefault();
        });
    
        // --- EDITOR UI LOGIC ---
        viewport.addEventListener('mousemove', (e) => {
            const block = e.target.closest('.viewport > *, .col > *, h1, h2, h3, h4, p, img, .row, section, article');
            if (block && viewport.contains(block) && !block.classList.contains('gif-sticker')) {
                activeBlock = block;
                const rect = block.getBoundingClientRect();
                plusBtn.style.display = 'flex';
                plusBtn.style.top = `${rect.bottom + window.scrollY - 15}px`;
                plusBtn.style.left = `${rect.left + window.scrollX + (rect.width / 2)}px`;
            }
        });
    
        plusBtn.onclick = (e) => {
            e.stopPropagation();
            insMenu.style.display = 'block';
            insMenu.style.left = plusBtn.style.left; insMenu.style.top = plusBtn.style.top;
        };
    
        const insertAfter = (html) => {
            if (activeBlock) activeBlock.insertAdjacentHTML('afterend', html);
            else viewport.insertAdjacentHTML('beforeend', html);
            insMenu.style.display = 'none'; autoSave();
        };
    
        // --- TOOL ACTIONS ---
        insMenu.querySelectorAll('[data-b]').forEach(i => {
            i.onclick = () => insertAfter(`<${i.dataset.b.toLowerCase()}>New Block</${i.dataset.b.toLowerCase()}>`);
        });
    
        document.getElementById('do-ul').onclick = () => insertAfter('<ul><li>List Item</li></ul>');
    
        insMenu.querySelectorAll('[data-g]').forEach(i => {
            i.onclick = () => {
                const c = parseInt(i.dataset.g);
                let h = `<div class="row ${c > 4 ? 'mobile-overflow' : ''}" contenteditable="false">`;
                for(let j=0; j<c; j++) h += `<div class="col" contenteditable="true"><p>Column ${j+1}</p></div>`;
                h += `</div><p><br></p>`;
                insertAfter(h);
            };
        });
    
        // --- MEDIA ---
        document.getElementById('do-img').onclick = () => fSys.click();
        fSys.onchange = async function() {
            const file = this.files[0]; if(!file) return;
            const url = await uploadToServer(file);
            if(!url) return;
    
            const isSticker = file.name.match(/\.(gif|avif)$/i) || file.type.match(/(gif|avif)/i);
    
            if(isSticker) {
                // STEP 1: Insert as normal block (width is set smaller to start)
                insertAfter(`
                    <div class="gif-sticker" style="position:relative; display:block; margin: 20px auto; width: 180px; cursor: pointer; z-index:10;" contenteditable="false">
                        <img src="${url}" style="width:100%; display:block; pointer-events:none;">
                        <div class="gif-handle rotator"><span class="material-icons">cached</span></div>
                        <div class="gif-handle resizer"></div>
                    </div>
                `);
            } else {
                insertAfter(`<img src="${url}" class="standard-img">`);
            }
            this.value = ''; autoSave();
        };
    
        document.getElementById('do-vid').onclick = () => {
            const url = prompt("YouTube URL:"); if (!url) return;
            let id = url.includes('v=') ? url.split('v=')[1].split('&')[0] : url.split('/').pop();
            if (id) insertAfter(`<div class="v-w" contenteditable="false"><iframe src="https://www.youtube.com/embed/${id}" allowfullscreen></iframe></div><p><br></p>`);
        };
    
        const run = (c, v = null) => {
            if (activeRange) { const s = window.getSelection(); s.removeAllRanges(); s.addRange(activeRange); }
            document.execCommand(c, false, v); autoSave();
        };
    
        document.addEventListener('selectionchange', () => {
            const sel = window.getSelection();
            if (sel.rangeCount > 0 && !sel.isCollapsed && viewport.contains(sel.anchorNode)) {
                activeRange = sel.getRangeAt(0).cloneRange();
                const rect = activeRange.getBoundingClientRect();
                selTb.classList.add('active');
                selTb.style.left = `${rect.left + window.scrollX}px`;
                selTb.style.top = `${rect.top + window.scrollY - 50}px`;
            } else if (!selTb.matches(':hover')) selTb.classList.remove('active');
        });
    
        selTb.querySelectorAll('[data-cmd]').forEach(b => b.onmousedown = (e) => { e.preventDefault(); run(b.dataset.cmd); });
        document.getElementById('add-lnk').onmousedown = (e) => { e.preventDefault(); const u = prompt("URL:"); if(u) run('createLink', u); };
        document.getElementById('add-id').onmousedown = (e) => { e.preventDefault(); const id = prompt("Block ID:"); if (id && activeBlock) activeBlock.id = id; };
        document.getElementById('clr-txt').oninput = (e) => run('foreColor', e.target.value);
        document.getElementById('clr-bg').oninput = (e) => run('hiliteColor', e.target.value);
    
        document.addEventListener('mousedown', (e) => { if (!insMenu.contains(e.target) && e.target !== plusBtn) insMenu.style.display = 'none'; });
        if(viewport) viewport.contentEditable = "true";
    })();
    

    Summary: Building a Responsive “Adaptive Sticker” Engine

    In this session, we evolved a standard WYSIWYG editor into a high-end layout builder by solving the biggest problem with “draggable” elements: Responsive Breakdown.

    1. The “Two-Step Activation” Workflow (UX)

    Standard editors either force an image to be a “block” (static) or a “sticker” (floating). We created a hybrid approach:

    • Step 1 (The Ghost Block): When a user clicks the “Plus” button, the GIF is inserted as a standard HTML element in the natural flow of the document (inside the current <section> or <article>). This ensures the content doesn’t “jump” and stays exactly where the user intended.
    • Step 2 (The Activation): The element only becomes “Absolute” the moment the user interacts with it (Drag, Resize, or Rotate). This keeps the DOM clean and prevents layout shifts during the initial build.

    2. Section-Bound Containment (Clean HTML Structure)

    One major challenge was preventing absolute-positioned stickers from flying out of their parent containers.

    • The Logic: Upon activation, the engine identifies the nearest parent <section> or <article> and sets it to position: relative.
    • The Result: The sticker is now “trapped” within its specific section. If you move a section up or down in your CMS, the sticker travels with its section because its coordinates are local to that parent, not the whole page.

    3. Percentage-Based Geometry (100% Responsive)

    Pixel-based positioning (top: 200px) is the enemy of mobile design. We moved to a Relative Coordinate System:

    • Storage: We store the position as data-x and data-y in percentages (0% to 100%).
    • Visual Consistency: Whether the screen is 1920px wide or 375px wide, the sticker stays at the exact same relative spot (e.g., “Top Right of Section 2”).
    • The Math: By using translate(-50%, -50%), we moved the “Pivot Point” to the true center of the image. This makes rotation and scaling mathematically perfect regardless of the image aspect ratio.

    4. The Transformation Engine

    We built a unified applyTransform function that handles three complex states simultaneously:

    1. Scale: Uses multipliers instead of width/height px to preserve image quality.
    2. Rotation: Maintains degrees even after the mouse is released.
    3. Translation: Keeps the image centered on its coordinate.

    🧠 The Technical “Aha!” Moment:

    Instead of trying to make the editor behave like a Word document, we made it behave like a Design Canvas. By calculating coordinates as (Mouse Position – Parent Offset) / Parent Width, we created a builder that looks the same on a 4K monitor as it does on an iPhone.

  • background – engine.js

    Here is the summary of the Background Transparency code you need for any engine.js file, and a prompt checklist for future use.

    1. The 3 Background Code Blocks

    A. The CSS (The “Wall” removal)

    The Code:

    html, body, #webgl-container { 
        background: transparent !important; 
    }
    • Why: Standard websites have a white or black “wall” (the body background). This line knocks that wall down so you can see the cms-bg-layer sitting behind the website.

    B. The Renderer (The “Glass” canvas)

    The Code:

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setClearColor(0x000000, 0); 
    • Why:
      • alpha: true: This turns the 3D canvas into a sheet of glass instead of a solid box.
      • setClearColor(..., 0): This makes the “clear space” in the 3D world 100% transparent.

    C. The Scene (The “Sky” removal)

    The Code:

    scene.background = null;
    • Why: Even if the canvas is glass, if you paint the “sky” (scene background) white, you won’t see through it. Setting it to null removes the paint.

    2. Checklist for Future Implementation

    If you get a new engine.js and want to add the background features, follow this prompt:

    StepCheck (A)Add/Change (B)
    1Look for style.innerHTMLAdd background: transparent !important; to html, body.
    2Look for new THREE.WebGLRendererAdd alpha: true inside the { }.
    3Under the renderer definitionAdd renderer.setClearColor(0x000000, 0);
    4Look for scene.background = ...Change the value to null.
    5Look for mousedown listenersAdd if (e.target.closest('#cms-bg-modal')) return; to prevent the 3D scene from moving while you use the background menu.

    3. Quick “Genie” Note

    To keep the Apple macOS Genie effect working in any new script, you must always include:

    1. The function: getScreenXY(mesh) (This finds the pixels on the screen).
    2. The GSAP animation: Inside the performRaycast (click) function, use gsap.fromTo to animate the scale from 0 to 1 using the coordinates from the function.
  • image Size, Compression and WebP

    what is normal good image size width and height for website background 

    There isn’t one fixed “perfect” size, but there are standard safe ranges used in real websites so images look good on all screens without becoming too heavy.

    🎯 Good background image size (modern websites)

    ✅ Recommended resolution

    • 1920 × 1080 px (Full HD) → most common “safe default”
    • 2560 × 1440 px (2K) → sharper for large screens
    • 3840 × 2160 px (4K) → only if you really need ultra high quality

    🧠 What you should actually use (practical advice)

    For your CMS / multi-website system:

    👉 Best balance:

    • Width: 1920 px
    • Height: 1080–1200 px

    This works for:

    • laptops
    • desktops
    • tablets
    • most mobile scaling (cropped automatically)

    📱 Important concept (very important)

    Background images are usually:

    • Responsive (cropped, not stretched)
    • Controlled using CSS like:
      • background-size: cover;
      • background-position: center;

    So you don’t need perfect height — only good width matters more.


    📦 File size target (important for your 10 GB system)

    Try to keep:

    • Good quality JPG/WebP: 100–300 KB
    • Max acceptable: 500 KB (if really needed)

    👉 Best format:

    • WebP (best choice)
    • JPG (fallback)

    🚀 Simple rule you can follow

    “1920px wide, compressed under 200 KB”

    That’s the sweet spot for:

    • fast loading
    • good quality
    • scalable system (like your 200 websites plan)

    If you want, I can give you a full CMS image standard (product images, thumbnails, banners, background rules)so your whole system stays inside 10 GB cleanly.

    2560 best of most of average website ??

    Short answer: No — 2560 is not the “best for most websites.” It’s more like high-end optional, not standard.


    🎯 What most websites actually use

    ✅ Standard (most common today)

    • 1920 × 1080 (Full HD)
      👉 Used by probably 80–90% of websites

    Why:

    • Good on laptops + desktops
    • Lightweight enough for speed
    • Easy to compress under 200–300 KB

    🟡 High-end / premium websites

    • 2560 × 1440
      👉 Used for:
    • photography sites
    • luxury brands
    • design portfolios
    • big hero backgrounds

    Downside:

    • Bigger file size
    • Slower loading if not optimized
    • Often unnecessary because browsers still show it scaled down

    🔴 4K (3840 × 2160)

    • Only for:
      • cinematic landing pages
      • very high-end visuals
        👉 Not practical for your CMS idea

    🧠 Real-world truth

    Even on a 4K screen, most websites:

    • still display a 1920px or smaller image
    • just scaled up by CSS

    So 2560 often gives no real visible benefit, only heavier files.


    🚀 Best recommendation for YOU (important)

    For your 10 GB / 200 websites system:

    👉 Use:

    • 1920 × 1080 or 1920 × 1200
    • format: WebP
    • size target: 100–250 KB

    💡 Simple rule

    “1920px is the sweet spot. 2560px is optional luxury.”


    If you want, I can design a full image policy for your CMS (background, product, thumbnails, logos) so everything stays fast + within storage limits.

    can I limit when some upload image big size and auto Lowe down WebP + 1920

    Yes — and this is actually the correct way to build a modern CMS 👍

    You can absolutely:

    Auto-detect big uploads → resize → convert to WebP → cap width to 1920px


    🧠 What you want is called:

    👉 Image processing pipeline (server-side)

    It does this automatically:

    1. User uploads image (even 5MB, 4000px wide)
    2. System checks size
    3. If too big:
      • resize to max 1920px width
      • keep aspect ratio
    4. compress quality
    5. convert to WebP
    6. save optimized version

    ⚙️ Recommended rules for your system

    📏 Resize rule

    • Max width: 1920px
    • Max height: auto (keep ratio)

    🧾 Format rule

    • Convert all images to: WebP

    🎚 Compression rule

    • Quality: 70–80% (sweet spot)

    📦 File size rule (optional limit)

    • Warn if > 300–500 KB
    • Hard block if > 1 MB (for your 10GB system)

    🔥 Example result

    User uploads:

    • 4000 × 3000 image, 3MB

    System outputs:

    • 1920 × 1440 WebP
    • ~150–250 KB

    🛠️ How to implement (simple stack options)

    If PHP (very common for your CMS idea)

    • Imagick (best quality)
    • or GD library

    If Node.js

    • sharp (FAST + industry standard)

    Example logic (Node.js idea):

    • resize → sharp().resize(1920)
    • convert → .webp({ quality: 75 })

    🚀 Why this is powerful for your 10GB system

    Without optimization:

    • 200 websites → storage explodes ❌

    With auto WebP + resize:

    • 200 websites becomes realistic ✅
    • consistent ~100–200 KB images
    • predictable storage control

    💡 Simple rule you should enforce

    “All uploads are automatically optimized. No original large images are stored.”


    If you want, I can design your full upload system logic (CMS-ready) including:

    • limits per user/site
    • auto compression rules
    • storage quotas (50MB/site system)
    • folder structure

    That will make your 200-site plan actually scalable 👍