← back to Dw Photo Capture
Crop tool: add aspect-ratio presets (Free / 1:1 / 4:3 / 3:4 / 16:9) — ratio-locked, anchor-based corner resize
3832493175a0fae7019f9eb2d39b6f2f17c42e9f · 2026-06-25 09:53:50 -0700 · steve@designerwallcoverings.com
Files touched
M data/build.jsonM public/index.html
Diff
commit 3832493175a0fae7019f9eb2d39b6f2f17c42e9f
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date: Thu Jun 25 09:53:50 2026 -0700
Crop tool: add aspect-ratio presets (Free / 1:1 / 4:3 / 3:4 / 16:9) — ratio-locked, anchor-based corner resize
---
data/build.json | 5 +++--
public/index.html | 54 ++++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 45 insertions(+), 14 deletions(-)
diff --git a/data/build.json b/data/build.json
index fa23cc5..ae87041 100644
--- a/data/build.json
+++ b/data/build.json
@@ -1,5 +1,5 @@
{
- "next": 33,
+ "next": 34,
"map": {
"63863152": 27,
"578af86f": 2,
@@ -31,6 +31,7 @@
"f4a0bf6d": 29,
"86060b11": 30,
"43b1741c": 31,
- "b2f94d6e": 32
+ "b2f94d6e": 32,
+ "224e5276": 33
}
}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index de92ceb..b667fba 100644
--- a/public/index.html
+++ b/public/index.html
@@ -133,6 +133,9 @@
.cropbox i[data-h=ne]{right:-3px;top:-3px;border-left:0;border-bottom:0;cursor:nesw-resize}
.cropbox i[data-h=sw]{left:-3px;bottom:-3px;border-right:0;border-top:0;cursor:nesw-resize}
.cropbox i[data-h=se]{right:-3px;bottom:-3px;border-left:0;border-top:0;cursor:nwse-resize}
+ .crop-presets{position:fixed;left:50%;top:max(14px,env(safe-area-inset-top));transform:translateX(-50%);display:flex;gap:7px;z-index:121}
+ .crop-presets button{background:rgba(8,7,6,.75);border:1px solid var(--line);color:var(--ink);border-radius:99px;padding:8px 13px;font-size:13px;font-weight:600;cursor:pointer}
+ .crop-presets button.on{background:var(--gold);color:#1b1407;border-color:var(--gold)}
.crop-bar{position:fixed;left:50%;bottom:max(24px,env(safe-area-inset-bottom));transform:translateX(-50%);display:flex;gap:12px;z-index:121}
.crop-bar button{border:none;border-radius:12px;padding:14px 26px;font-weight:700;font-size:15px;cursor:pointer}
#cropCancel{background:#16140f;border:1px solid var(--red);color:var(--red)}
@@ -227,6 +230,13 @@
<!-- crop overlay (Photoshop-style: drag handles, dim outside) -->
<div class="cropov" id="cropov" hidden>
+ <div class="crop-presets" id="cropPresets">
+ <button class="on" data-r="0">Free</button>
+ <button data-r="1">1:1</button>
+ <button data-r="1.3333">4:3</button>
+ <button data-r="0.75">3:4</button>
+ <button data-r="1.7778">16:9</button>
+ </div>
<div class="cropbox" id="cropbox"><i data-h="nw"></i><i data-h="ne"></i><i data-h="sw"></i><i data-h="se"></i></div>
<div class="crop-bar"><button id="cropCancel">✕ Cancel</button><button id="cropApply">✓ Apply Crop</button></div>
</div>
@@ -719,26 +729,46 @@ function exitCrop(){ $('#cropov').hidden=true; CROP=null; }
function setCropBox(x,y,w,h){
const b=$('#cropbox'); b.style.left=x+'px'; b.style.top=y+'px'; b.style.width=Math.max(30,w)+'px'; b.style.height=Math.max(30,h)+'px';
}
+let cropRatio=0; // 0 = free, else width/height
+function setPreset(rat,btn){
+ cropRatio=+rat||0;
+ document.querySelectorAll('#cropPresets button').forEach(b=>b.classList.remove('on')); if(btn)btn.classList.add('on');
+ if(!CROP||!cropRatio)return;
+ const r=CROP.r, bb=$('#cropbox').getBoundingClientRect(), cx=bb.left+bb.width/2, cy=bb.top+bb.height/2;
+ let w=bb.width, ht=w/cropRatio;
+ if(ht>r.height*0.96){ ht=r.height*0.92; w=ht*cropRatio; }
+ if(w>r.width*0.96){ w=r.width*0.92; ht=w/cropRatio; }
+ const x=Math.max(r.left,Math.min(cx-w/2,r.right-w)), y=Math.max(r.top,Math.min(cy-ht/2,r.bottom-ht));
+ setCropBox(x,y,w,ht);
+}
(function cropDrag(){
const box=$('#cropbox'); let mode=null,h=null,sx,sy,ox,oy,ow,oh;
- const clampToCanvas=()=>{ if(!CROP)return; const r=CROP.r,bb=box.getBoundingClientRect();
- let x=Math.max(r.left,Math.min(bb.left,r.right-bb.width)), y=Math.max(r.top,Math.min(bb.top,r.bottom-bb.height));
- let w=Math.min(bb.width,r.right-x), ht=Math.min(bb.height,r.bottom-y); setCropBox(x,y,w,ht); };
- function down(e){ if(!CROP)return; e.preventDefault(); const t=e.touches?e.touches[0]:e; sx=t.clientX; sy=t.clientY;
+ function down(e){ if(!CROP)return; e.preventDefault(); sx=e.clientX; sy=e.clientY;
const bb=box.getBoundingClientRect(); ox=bb.left;oy=bb.top;ow=bb.width;oh=bb.height;
h=e.target.dataset&&e.target.dataset.h; mode=h?'resize':'move';
document.addEventListener('pointermove',move,{passive:false}); document.addEventListener('pointerup',up); }
- function move(e){ if(!mode||!CROP)return; e.preventDefault(); const r=CROP.r; const dx=e.clientX-sx, dy=e.clientY-sy;
- if(mode==='move'){ setCropBox(Math.max(r.left,Math.min(ox+dx,r.right-ow)), Math.max(r.top,Math.min(oy+dy,r.bottom-oh)), ow, oh); }
- else { let x=ox,y=oy,w=ow,ht=oh;
- if(h.includes('w')){ x=ox+dx; w=ow-dx; } if(h.includes('e')){ w=ow+dx; }
- if(h.includes('n')){ y=oy+dy; ht=oh-dy; } if(h.includes('s')){ ht=oh+dy; }
- if(w<30){ if(h.includes('w')) x=ox+ow-30; w=30; } if(ht<30){ if(h.includes('n')) y=oy+oh-30; ht=30; }
- setCropBox(x,y,w,ht); clampToCanvas(); } }
+ function move(e){ if(!mode||!CROP)return; e.preventDefault(); const r=CROP.r;
+ if(mode==='move'){ const dx=e.clientX-sx, dy=e.clientY-sy;
+ setCropBox(Math.max(r.left,Math.min(ox+dx,r.right-ow)), Math.max(r.top,Math.min(oy+dy,r.bottom-oh)), ow, oh); return; }
+ // resize: opposite corner stays anchored
+ const ax = h.includes('w') ? ox+ow : ox, ay = h.includes('n') ? oy+oh : oy;
+ let px=Math.max(r.left,Math.min(e.clientX,r.right)), py=Math.max(r.top,Math.min(e.clientY,r.bottom));
+ let w=Math.abs(px-ax), ht=Math.abs(py-ay);
+ if(cropRatio){
+ if(w/cropRatio>=ht) ht=w/cropRatio; else w=ht*cropRatio;
+ if(h.includes('e')&&ax+w>r.right){ w=r.right-ax; ht=w/cropRatio; }
+ if(h.includes('w')&&ax-w<r.left){ w=ax-r.left; ht=w/cropRatio; }
+ if(h.includes('s')&&ay+ht>r.bottom){ ht=r.bottom-ay; w=ht*cropRatio; }
+ if(h.includes('n')&&ay-ht<r.top){ ht=ay-r.top; w=ht*cropRatio; }
+ }
+ w=Math.max(30,w); ht=Math.max(30,ht);
+ const x=h.includes('w')?ax-w:ax, y=h.includes('n')?ay-ht:ay;
+ setCropBox(x,y,w,ht); }
function up(){ mode=null; document.removeEventListener('pointermove',move); document.removeEventListener('pointerup',up); }
box.addEventListener('pointerdown',down);
})();
-$('#edCrop').addEventListener('click',enterCrop);
+document.querySelectorAll('#cropPresets button').forEach(b=>b.addEventListener('click',()=>setPreset(b.dataset.r,b)));
+$('#edCrop').addEventListener('click',()=>{ enterCrop(); const f=document.querySelector('#cropPresets button[data-r="0"]'); setPreset(0,f); });
$('#cropCancel').addEventListener('click',exitCrop);
$('#cropApply').addEventListener('click',()=>{
if(!CROP||!ES)return; const r=CROP.r, bb=$('#cropbox').getBoundingClientRect();
← f97337a Add ✂️ Crop tool to photo editor (drag corners/move, dim out
·
back to Dw Photo Capture
·
Video uploads: 🎥 Video in gallery → /api/video runs the Sho 7a11139 →