1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> 8 <meta name="robots" content="index, follow, max-snippet:170, max-image-preview:standard"> 9 <meta name="rating" content="General"> 10 <meta name="revisit-after" content="7 days"> 11 <meta name="distribution" content="Global"> 12<link rel="alternate" href="https://laughingprofessor.net/product-designer-tshirt/" hreflang="x-default" /> 13<link rel="canonical" href="https://laughingprofessor.net/product-designer-tshirt/"> 14 <link rel="stylesheet" href="https://laughingprofessor.net/seo-agency-toolbox/style2.css"> 15 <link rel="icon" type="image/png" href="/favicon.png"> 16 <link rel="apple-touch-icon" href="/favicon.png"> 17 <link rel="apple-touch-icon" sizes="76x76" href="/favicon.png"> 18 <link rel="apple-touch-icon" sizes="120x120" href="/favicon.png"> 19 <link rel="apple-touch-icon" sizes="152x152" href="/favicon.png"> 20 <meta name="dcterms.created" content="The Laughing Professor"> 21 <meta name="author" content="The Professor, Paul Griffiths"> 22 <meta name="publisher" content="The Laughing Professor"> 23 <meta name="copyright" content="Copyright 2026"> 24 <meta property="og:updated_time" content="2026-02-22T08:00:00+00:00"> 25<!-- Pingback: https://laughingprofessor.net/pro-generator/llms.txt --> 26<!-- Generated by The Laughing Professor's OG Meta Tag Tool --> 27<meta name="made-by" content="The Professor OG Meta Tag Generator"> 28 <!-- Facebook Meta Tags --> 29 <meta name="facebook-domain-verification" content="k5ffz73klofie45e9rdzyi90754w5d"> 30 <meta property="fb:page_id" content="109516727266667"> 31 <meta property="fb:app_id" content="2778978168891853"> 32 <meta property="fb:admins" content="LaughingProf"> 33 <meta property="og:title" content="The Laughing Professor On-line product t-shirt mockup designer"> 34 <meta property="og:image:type" content="image/png"> 35 <meta property="og:image" content="https://laughingprofessor.net/product-designer-tshirt/og-page-mockup.png"> 36 <meta property="og:image:url" content="https://laughingprofessor.net/product-designer-tshirt/og-page-mockup.png"> 37 <meta property="og:image:secure_url" content="https://laughingprofessor.net/product-designer-tshirt/og-page-mockup.png"> 38 <meta property="og:image:alt" content="Free SEO Tools & Professional SEO Services"> 39 <meta property="og:image:width" content="1200"> 40 <meta property="og:image:height" content="630"> 41 <meta property="og:description" content="Your Multi-Design & Position Product Mockup Designer"> 42 <meta property="og:url" content="https://laughingprofessor.net/product-designer-tshirt/"> 43 <meta property="og:site_name" content="The Laughing Professor Free SEO Tools & Professional SEO Services"> 44 <meta property="og:type" content="website"> 45 <meta property="og:price:currency" content="USD"> 46 <meta property="og:locale" content="en_US"> 47 <!-- Twitter Meta Tags --> 48 <meta name="twitter:card" content="summary_large_image"> 49 <meta name="twitter:title" content="The Laughing Professor On-line product t-shirt mockup designer"> 50 <meta name="twitter:description" content="Your Multi-Design & Position Product Mockup Designer"> 51 <meta name="twitter:image" content="https://laughingprofessor.net/product-designer-tshirt/og-page-mockup.png">
52<script src="/user-tracking/tracker.js"></script>
vendor: 66 bytes, lines 52-53
52 53 <script async src="https://www.googletagmanager.com/gtag/js?id=
53G-SSN64XWYYC
vendor: 14 bytes, lines 53-54
53"></script> 54
54<script> 55
vendor: 142 bytes, lines 55-58
55window.dataLayer = window.dataLayer || []; 56 function gtag(){dataLayer.push(arguments);} 57 gtag('js', new Date()); 58 gtag('config', '
58G-SSN64XWYYC
vendor: 6 bytes, lines 58-59
58'); 59
59</script>
59 60 <title>On-line product t-shirt mockup designer</title> 61 <meta name="description" content="Your Multi-Design & Position Product Mockup Designer. Seemlessly intergrated into your own website to boost customer engagment and seo rankings">
62<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
62 63<link rel="preconnect" href="https://fonts.googleapis.com"> 64<link href="https://fonts.googleapis.com/css2?family=Anton&family=Bangers&family=Dancing+Script:wght@700&family=Lobster&family=Montserrat:wght@700&family=Oswald:wght@700&family=Pacifico&family=Permanent+Marker&family=Press+Start+2P&family=Raleway:wght@800&family=Roboto+Condensed:wght@700&family=Russo+One&family=Ultra&family=Satisfy&display=swap" rel="stylesheet"> 65<style> 66*,*::before,*::after{box-sizing:border-box;margin:0;padding:0} 67:root{ 68 --glass:rgba(255,255,255,0.07); 69 --gb:rgba(255,255,255,0.15); 70 --accent:#f97316; 71 --accent2:#6366f1; 72 --bg:#0f0f1a; 73 --text:#e2e8f0; 74 --muted:#94a3b8; 75 --r:14px; 76} 77html,body{width:100%;overflow-x:hidden} 78body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--text);padding:12px} 79 80.sub{text-align:center;color:var(--muted);font-size:.8rem;margin-bottom:12px} 81 82.app{display:flex;gap:10px;width:100%;max-width:100%;align-items:flex-start} 83.sidebar{flex:0 0 190px;min-width:0;display:flex;flex-direction:column;gap:10px} 84.centre{flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:10px} 85 86.panel{background:var(--glass);border:1px solid var(--gb);border-radius:var(--r);padding:12px;width:100%} 87.panel h3{font-size:.72rem;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);margin-bottom:8px} 88 89/* canvas area two shirts side by side */ 90.canvas-wrap{background:var(--glass);border:1px solid var(--gb);border-radius:var(--r);padding:12px;width:100%;overflow:hidden} 91.shirts-row{display:flex;gap:10px;justify-content:center;align-items:flex-start;transition:all .35s ease} 92 93/* each shirt stage */ 94.stage-wrap{flex:1 1 0;min-width:0;display:flex;flex-direction:column;align-items:center;gap:6px;transition:all .35s ease} 95.stage-label{font-size:.72rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em} 96.stage-label span{color:var(--accent);font-weight:700} 97 98.stage{position:relative;width:100%;max-width:320px;aspect-ratio:1/1.1} 99.stage img.shirt{width:100%;height:100%;object-fit:contain;display:block;border-radius:6px} 100 101/* focused = full width, other hidden */ 102.stage-wrap.focused{flex:0 0 min(360px,90%)} 103.stage-wrap.dimmed{flex:0 0 0;overflow:hidden;opacity:0;pointer-events:none;min-width:0} 104 105.overlay-img{position:absolute;cursor:move;user-select:none;touch-action:none} 106.overlay-img.ring{outline:2px solid var(--accent);outline-offset:2px} 107.text-overlay{position:absolute;cursor:move;user-select:none;touch-action:none;white-space:nowrap;line-height:1.2;z-index:50} 108.text-overlay.ring{outline:2px dashed var(--accent2);outline-offset:3px} 109 110/* colour swatches */ 111.color-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px} 112.cswatch{width:100%;aspect-ratio:1;border-radius:50%;cursor:pointer;border:2px solid transparent;transition:transform .2s,border-color .2s;position:relative} 113.cswatch:hover,.cswatch.on{transform:scale(1.15);border-color:#fff} 114.tip{display:none;position:absolute;bottom:115%;left:50%;transform:translateX(-50%);background:#1e1e2e;color:#fff;font-size:.65rem;white-space:nowrap;padding:2px 6px;border-radius:5px;pointer-events:none;z-index:99} 115.cswatch:hover .tip{display:block} 116 117.tabs{display:flex;gap:5px;margin-bottom:10px;flex-wrap:wrap} 118.tab{flex:1;padding:5px 3px;font-size:.72rem;border-radius:8px;border:1px solid var(--gb);background:transparent;color:var(--muted);cursor:pointer;transition:all .2s;white-space:nowrap;min-width:0} 119.tab.on{background:var(--accent2);color:#fff;border-color:var(--accent2)} 120 121.cg{margin-bottom:8px} 122.cg label{font-size:.7rem;color:var(--muted);display:flex;justify-content:space-between;margin-bottom:2px} 123input[type=range]{width:100%;accent-color:var(--accent);cursor:pointer} 124 125.grow{display:flex;gap:7px;overflow-x:auto;padding-bottom:5px} 126.grow::-webkit-scrollbar{height:4px} 127.grow::-webkit-scrollbar-thumb{background:rgba(255,255,255,.2);border-radius:4px} 128.dsw{flex-shrink:0;width:62px;height:62px;border-radius:9px;overflow:hidden;border:2px solid var(--gb);cursor:pointer;background:rgba(255,255,255,.05);transition:border-color .2s,transform .2s;
128display:flex;align-items:center;justify-content:center} 129.dsw:hover{border-color:var(--accent);transform:scale(1.05)} 130.dsw img{width:100%;height:100%;object-fit:contain} 131.dsw.up{flex-direction:column;gap:3px;font-size:.62rem;color:var(--muted);border-style:dashed} 132.dsw.up svg{width:20px;height:20px;opacity:.6} 133input[type=file]{display:none} 134 135#textInput{width:100%;background:rgba(255,255,255,.07);border:1px solid var(--gb);border-radius:8px;color:var(--text);padding:7px 9px;font-size:.85rem;font-family:inherit;margin-bottom:8px} 136#textInput:focus{outline:1px solid var(--accent2)} 137 138/* which shirt does the text go on */ 139.shirt-target-row{display:flex;gap:6px;margin-bottom:8px} 140.stt{flex:1;padding:5px;font-size:.73rem;border-radius:8px;border:1px solid var(--gb);background:transparent;color:var(--muted);cursor:pointer;transition:all .2s;text-align:center} 141.stt.on{background:var(--accent);color:#fff;border-color:var(--accent)} 142 143.font-grid{display:grid;grid-template-columns:1fr 1fr;gap:5px;margin-bottom:8px;max-height:130px;overflow-y:auto} 144.font-grid::-webkit-scrollbar{width:3px} 145.font-grid::-webkit-scrollbar-thumb{background:rgba(255,255,255,.2);border-radius:3px} 146.fopt{padding:5px;border-radius:7px;border:1px solid var(--gb);cursor:pointer;font-size:.8rem;background:transparent;color:var(--text);transition:all .2s;text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} 147.fopt:hover{border-color:var(--accent2)} 148.fopt.on{background:var(--accent2);border-color:var(--accent2);color:#fff} 149 150.cstrip{display:flex;flex-wrap:wrap;gap:5px;margin-bottom:8px} 151.tcsw{width:22px;height:22px;border-radius:50%;cursor:pointer;border:2px solid transparent;transition:transform .2s,border-color .2s;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:.6rem} 152.tcsw:hover,.tcsw.on{transform:scale(1.2);border-color:#fff} 153 154.tlist{display:flex;flex-direction:column;gap:4px;max-height:100px;overflow-y:auto;margin-top:6px} 155.tentry{display:flex;align-items:center;padding:4px 7px;background:rgba(255,255,255,.05);border-radius:7px;font-size:.73rem;gap:5px} 156.tentry span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} 157.tentry button{background:none;border:none;color:var(--muted);cursor:pointer;padding:0 2px;font-size:.85rem} 158.tentry button:hover{color:#f87171} 159.tentry .eb:hover{color:var(--accent2)} 160 161.bbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center;background:var(--glass);border:1px solid var(--gb);border-radius:var(--r);padding:10px 14px;width:100%} 162.btn{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:9px;font-size:.8rem;font-weight:600;cursor:pointer;border:none;transition:all .22s;white-space:nowrap} 163.bp{background:var(--accent);color:#fff} 164.bp:hover{background:#ea6c10;transform:translateY(-1px);box-shadow:0 4px 12px rgba(249,115,22,.4)} 165.bs{background:var(--accent2);color:#fff} 166.bs:hover{background:#4f52d8;transform:translateY(-1px);box-shadow:0 4px 12px rgba(99,102,241,.4)} 167.bg{background:var(--glass);color:var(--text);border:1px solid var(--gb)} 168.bg:hover{background:rgba(255,255,255,.12);transform:translateY(-1px)} 169.btn svg{width:14px;height:14px;flex-shrink:0} 170.cstatus{margin-left:auto;font-size:.75rem;color:var(--muted);display:flex;align-items:center;gap:7px} 171#colorDot{width:13px;height:13px;border-radius:50%;background:#C9C9C9;border:2px solid rgba(255,255,255,.3);flex-shrink:0} 172 173hr.div{border:none;border-top:1px solid var(--gb);margin:8px 0} 174 175.mbg{display:none;position:fixed;inset:0;background:rgba(0,0,0,.65);backdrop-filter:blur(4px);z-index:200;align-items:center;justify-content:center} 176.mbg.open{display:flex} 177.modal{background:#1e1e30;border:1px solid var(--gb);border-radius:var(--r);padding:22px;width:340px;max-width:95vw} 178.modal h2{font-size:.95rem;margin-bottom:12px} 179.modal input,.modal textarea{width:100%;background:rgba(255,255,255,.07);border:1px solid var(--gb);border-radius:8px;color:var(--text);padding:8px 11px;font-size:.82rem;margin-bottom:9px;font-family:inherit} 180.modal textarea{resize:vertical;min-height:75px} 181.modal .row{display:flex;gap:7px} 182 183#notif{position:fixed;bottom:20px;left:50%;transform:translateX(-50%) translateY(80px);background:#22c55e;color:#fff;padding:9px 18px;border-radius:9px;font-size:.82rem;font-weight:600;transition:transform .4s;z-index:300;pointer-events:none} 184#notif.show{transform:translateX(-50%) translateY(0)} 185 186@media(max-width:700px){ 187 .app{flex-direction:column} 188 .sidebar{flex:none;width:100%} 189 .color-grid{grid-template-columns:repeat(6,1fr)} 190} 191#note-box { width: 100%; max-width: 300px; 192 margin: 8px auto; padding: 8px; text-align: center; 193 font-size: 0.9em; font-weight: normal;
193 color: yellow; 194 background-image: radial-gradient(ellipse at top left, #6699FF, #6666CC, #00CCFF); 195 border: 2px solid #0E5077; border-radius: 20px; } 196 197#SPONSOR-AD { width: 100%; 198 max-width: 200px; height: 70px; 199 margin: 8px auto; cursor: pointer; background: white; 200 box-shadow: 5px 5px 10px 3px rgba(0, 0, 0, 0.5); border-radius: 8px; } 201#SPONSOR-AD:hover { transform: scale(1.1); } 202#SPONSOR-AD img { width: 100%; height: 100%; object-fit: contain; border-radius: 8px; } 203.custom-share { display: flex; gap: 12px; justify-content: right;} 204.custom-share a { display: inline-block; width: 30px; height: 30px; } 205.custom-share a img { width: 100%; height: 100%; object-fit: contain; } 206.breadcrumb {background: lightgrey;} 207</style> 208</head> 209<!-- 210 ____ __ 211 | _ \ _ __ ___ / _| ___ ___ ___ ___ _ __ 212 | |_) | '__/ _ \| |_ / _ \/ __/ __|/ _ \| '__| 213 | __/| | | (_) | _| __/\__ \__ \ (_) | | 214 |_| |_| \___/|_| \___||___/___/\___/|_| 215 216 The Laughing Professor White Label SEO Toolbox Built for AI, SEO & You. 217--> 218<body> 219<div class="floating-tooltip"> 220 <a href="https://laughingprofessor.net/product-designer-tshirt/" target="_parent"> 221 <span class="tooltip-icon"><i class="fa fa-expand" aria-hidden="true"></i></span> 222 <span class="tooltip-text"> 223 Goto full web page. 224 </span></a> 225</div> 226 <header class="header" role="banner"> 227 <h1> 228 On-line product t-shirt mockup designer 229 </h1> 230<p>Seemlessly intergrated into your own website to boost customer engagment and seo rankings</p> 231 <div style="display: flex; justify-content: center;"> 232 <a href="https://laughingprofessor.net/customer-engagement-widgets" 233 target="_parent" 234 class="cta-seo" 235 aria-label="Browse customer engaging widgets"> 236 <i class="fa fa-info-circle" aria-hidden="true" style="padding-right: 10px;"></i> 237 Customer Engaging Widgets 238 </a> 239 <a href="https://laughingprofessor.net/on-page-seo" 240 target="_parent" 241 class="cta-white" 242 aria-label="On Page SEO Services"> 243 <i class="fa fa-line-chart" aria-hidden="true" style="padding-right: 10px;"></i> 244 Module SEO Services 245 </a> 246 </div> 247 </header> 248 <main id="main-content" role="main"> 249 250 <nav class="breadcrumb" role="navigation" aria-label="Breadcrumb"> 251 <a href="/" title="Site home page" target="_parent" aria-label="Home"> 252 <i class="fa fa-home" aria-hidden="true"></i> 253 <span class="sr-only">Home</span> 254 </a> 255 / <a href="/printshop-tools/" target="_parent">Print Shop Tools</a> 256 / <a href="/seo-agency-toolbox/#design:designer" target="_parent" aria-current="page">T-Shirt Mock-up Designer</a> 257 </nav> 258 259<h2>T-Shirt Mockup Designer</h2> 260<p class="sub">Front & back - multi-design & text - fully interactive</p> 261 262<div class="app"> 263 264 <!-- LEFT sidebar --> 265 <div class="sidebar"> 266 267 <div class="panel"> 268 <h3>Product Colour</h3> 269 <div class="color-grid" id="colorGrid"></div> 270 <p style="font-size:.65rem;color:var(--muted);margin-top:8px;text-align:center" id="colorLabel">Silver</p> 271 </div> 272 273 <div class="panel"> 274 <h3>View / Edit</h3> 275 <div class="tabs" style="margin-bottom:6px">
276 <button class="tab on" id="viewBoth" onclick="setView('both')">Both</button> 277 <button class="tab" id="viewFront" onclick="setView('front')">Front</button> 278 <button class="tab" id="viewBack" onclick="setView('back')">Back</button> 279 </div> 280 <p id="viewNote" style="font-size:.68rem;color:var(--muted);text-align:center">Showing front & back</p> 281 </div> 282 283 <div class="panel"> 284 <h3>Controls</h3> 285 <div class="tabs" id="mainTabs"> 286 <button class="tab on" onclick="switchMain('img1')">Design 1</button> 287 <button class="tab" onclick="switchMain('img2')">Design 2</button> 288 <button class="tab" onclick="switchMain('text')"><i class="fa fa-pencil" aria-hidden="true"></i> Text</button> 289 </div> 290 291 <!-- Design 1 (front shirt) --> 292 <div id="pane-img1"> 293 <p style="font-size:.68rem;color:var(--accent);margin-bottom:6px">Placed on Front shirt</p> 294 <div class="cg"><label>Width <span id="v1w">90</span>px</label><input type="range" min="20" max="300" value="90" oninput="updateOv(1,'w',this.value)"></div> 295 <div class="cg"><label>Height <span id="v1h">90</span>px</label><input type="range" min="20" max="300" value="90" oninput="updateOv(1,'h',this.value)"></div> 296 <div class="cg"><label>Left/Right <span id="v1x">80</span></label><input type="range" min="0" max="280" value="80" oninput="updateOv(1,'x',this.value)"></div> 297 <div class="cg"><label>Up/Down <span id="v1y">90</span></label><input type="range" min="0" max="310" value="90" oninput="updateOv(1,'y',this.value)"></div> 298 <div class="cg"><label>Rotate <span id="v1r">0</span>°</label><input type="range" min="0" max="360" value="0" oninput="updateOv(1,'r',this.value)"></div> 299 <div class="cg"><label>Opacity <span id="v1o">100</span>%</label><input type="range" min="10" max="100" value="100" oninput="updateOv(1,'o',this.value)"></div> 300 <button class="btn bg" style="width:100%;justify-content:center;font-size:.75rem" onclick="resetImg(1)"><i class="fa fa-refresh" aria-hidden="true"></i> Reset</button> 301 </div> 302 303 <!-- Design 2 (back shirt) --> 304 <div id="pane-img2" style="display:none"> 305 <p style="font-size:.68rem;color:var(--accent2);margin-bottom:6px">Placed on Back shirt</p> 306 <div class="cg"><label>Width <span id="v2w">90</span>px</label><input type="range" min="20" max="300" value="90" oninput="updateOv(2,'w',this.value)"></div> 307 <div class="cg"><label>Height <span id="v2h">90</span>px</label><input type="range" min="20" max="300" value="90" oninput="updateOv(2,'h',this.value)"></div> 308 <div class="cg"><label>Left/Right <span id="v2x">80</span></label><input type="range" min="0" max="280" value="80" oninput="updateOv(2,'x',this.value)"></div> 309 <div class="cg"><label>Up/Down <span id="v2y">90</span></label><input type="range" min="0" max="310" value="90" oninput="updateOv(2,'y',this.value)"></div> 310 <div class="cg"><label>Rotate <span id="v2r">0</span>°</label><input type="range" min="0" max="360" value="0" oninput="updateOv(2,'r',this.value)"></div> 311 <div class="cg"><label>Opacity <span id="v2o">100</span>%</label><input type="range" min="10" max="100" value="100" oninput="updateOv(2,'o',this.value)"></div> 312 <button class="btn bg" style="width:100%;justify-content:center;font-size:.75rem" onclick="resetImg(2)"><i class="fa fa-refresh" aria-hidden="true"></i> Reset</button> 313 </div> 314 315 <!-- Text --> 316 <div id="pane-text" style="display:none"> 317 <input type="text" id="textInput" placeholder="Type your text..." maxlength="40"> 318 <h3 style="margin-bottom:5px">Place on</h3> 319 <div class="shirt-target-row"> 320 <button class="stt on" id="sttF" onclick="setTextTarget('front')">Front</button> 321 <button class="stt" id="sttB" onclick="setTextTarget('back')">Back</button> 322 </div> 323 <h3 style="margin-bottom:5px">Font</h3> 324 <div class="font-grid" id="fontGrid"></div> 325 <div class="cg"><label>
325Size <span id="vFS">28</span>px</label> 326 <input type="range" min="10" max="90" value="28" id="slFS" oninput="document.getElementById('vFS').textContent=this.value;applyPreview()"> 327 </div> 328 <h3 style="margin-bottom:5px">Text Colour</h3> 329 <div class="cstrip" id="tcStrip"></div> 330 <div style="display:flex;align-items:center;gap:7px;margin-bottom:8px"> 331 <span style="font-size:.7rem;color:var(--muted)">Custom:</span> 332 <input type="color" id="tcPicker" value="#ffffff" style="width:32px;height:26px;border:none;background:none;cursor:pointer" oninput="setTC(this.value)"> 333 </div> 334 <h3 style="margin-bottom:5px">Outline</h3> 335 <div class="cstrip" id="scStrip"></div> 336 <div style="display:flex;align-items:center;gap:7px;margin-bottom:6px"> 337 <span style="font-size:.7rem;color:var(--muted)">Custom:</span> 338 <input type="color" id="scPicker" value="#000000" style="width:32px;height:26px;border:none;background:none;cursor:pointer" oninput="setSC(this.value)"> 339 </div> 340 <div class="cg"><label>Outline width <span id="vSW">0</span>px</label> 341 <input type="range" min="0" max="8" value="0" id="slSW" oninput="document.getElementById('vSW').textContent=this.value;applyPreview()"> 342 </div> 343 <div class="cg"><label>Rotate <span id="vTR">0</span>°</label> 344 <input type="range" min="-180" max="180" value="0" id="slTR" oninput="document.getElementById('vTR').textContent=this.value;applyPreview()"> 345 </div> 346 <button class="btn bs" style="width:100%;justify-content:center;margin-bottom:6px" onclick="addText()">+ Add Text to Shirt</button> 347 <hr class="div"> 348 <h3>Text Layers</h3> 349 <div class="tlist" id="tlist"><p style="font-size:.7rem;color:var(--muted);text-align:center">No text yet</p></div> 350 </div> 351 </div> 352 353 </div><!-- /sidebar --> 354 355 <!-- CENTRE --> 356 <div class="centre"> 357 <div class="canvas-wrap"> 358 <div class="shirts-row" id="shirtsRow"> 359 360 <!-- FRONT stage --> 361 <div class="stage-wrap" id="wrapFront"> 362 <div class="stage-label">Front <span>Design 1</span></div> 363 <div class="stage" id="stageFront"> 364 <img class="shirt" id="imgFront" src="https://laughingprofessor.net/product-designer-tshirt/image/silver-shirt-mockup-front.png" alt="Front" crossorigin="anonymous"> 365 <img id="ov1" class="overlay-img" src="https://laughingprofessor.net/product-designer-tshirt/image/design2.png" 366 style="width:90px;height:90px;left:80px;top:90px" crossorigin="anonymous" alt="Design 1"> 367 </div> 368 </div> 369 370 <!-- BACK stage --> 371 <div class="stage-wrap" id="wrapBack"> 372 <div class="stage-label">Back <span>Design 2</span></div> 373 <div class="stage" id="stageBack"> 374 <img class="shirt" id="imgBack" src="https://laughingprofessor.net/product-designer-tshirt/image/silver-shirt-mockup-back.png" alt="Back" crossorigin="anonymous"> 375 <img id="ov2" class="overlay-img" src="https://laughingprofessor.net/product-designer-tshirt/image/no-image.png" 376 style="width:90px;height:90px;left:80px;top:90px;display:none" crossorigin="anonymous" alt="Design 2"> 377 </div> 378 </div> 379 380 </div> 381 </div> 382 383 <div class="panel"> 384 <h3>Design 1 - Front shirt</h3> 385 <div class="grow" id="gallery1"></div> 386 </div> 387 <div class="panel"> 388 <h3>Design 2 - Back shirt</h3> 389 <div class="grow" id="gallery2"></div> 390 </div> 391 392 <div class="bbar"> 393 <button class="btn bp" onclick="doDownload()"> 394 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg> 395 Download Both 396 </button> 397 <button class="btn bp" onclick="doDownloadSingle('front')"><i class="fa fa-arrow-down" aria-hidden="true"></i> Front</button> 398 <button class="btn bp" onclick="doDownloadSingle('back')"><i class="fa fa-arrow-down" aria-hidden="true"></i> Back</button> 399 <button class="btn bs" onclick="openEmail()"> 400 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg> 401 Email 402 </button> 403 <button class="btn bg" onclick="copyLink()"> 404 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> 405 Copy Link 406 </button> 407 <div class="cstatus"><div id="colorDot"></div><span id="colorStatus">Silver</span></div> 408 </div> 409 </div> 410 411</div> 412
413<!-- Email Modal --> 414<div class="mbg" id="emailModal"> 415 <div class="modal"> 416 <h2><i class="fa fa-envelope-o" aria-hidden="true"></i> Email Your Mockup</h2> 417 <p style="font-size:.78rem;color:var(--muted);margin-bottom:10px">Mockup downloads first - attach it to the email that opens.</p> 418 <input type="email" id="eTo" placeholder="Recipient email"> 419 <input type="text" id="eSubj" value="My Custom T-Shirt Mockup"> 420 <textarea id="eBody">Hi, please find my custom t-shirt mockup attached! 421 422Created with The Laughing Professor Mockup Designer.</textarea> 423 <div class="row"> 424 <button class="btn bp" style="flex:1;justify-content:center" onclick="sendEmail()">Send</button> 425 <button class="btn bg" style="flex:1;justify-content:center" onclick="closeEmail()">Cancel</button> 426 </div> 427 </div> 428</div> 429 430<div id="notif"><i class="fa fa-check" aria-hidden="true"></i> Done!</div> 431<div style="display: flex; flex-direction: row;"> 432<!-- Share this Page --> 433<div class="custom-share"> 434<strong>Share this Page: </strong> 435 <a href="#" class="share-button" data-service="facebook" aria-label="Share on Facebook"> 436 <img src="https://laughingprofessor.net/image/catalog/social-share/facebook-icon.png" alt="Share on Facebook"> 437 </a> 438 <a href="#" class="share-button" data-service="x" aria-label="Share on X"> 439 <img src="https://laughingprofessor.net/image/catalog/social-share/x-icon.png" alt="Share on X"> 440 </a> 441 <a href="#" class="share-button" data-service="linkedin" aria-label="Share on LinkedIn"> 442 <img src="https://laughingprofessor.net/image/catalog/social-share/linkedin-icon.png" alt="Share on LinkedIn"> 443 </a> 444 <a href="#" class="share-button" data-service="whatsapp" aria-label="Share on WhatsApp"> 445 <img src="https://laughingprofessor.net/image/catalog/social-share/whatsapp-icon.png" alt="Share on WhatsApp"> 446 </a> 447</div> 448<div class="note-box" id="note-box"> 449<label>Embedded in to any web page with your very own 'product / color and design choices' and includes 450your own message here.</label></div> 451<div class="SPONSOR-AD" id="SPONSOR-AD"> 452 <!-- Sponsor ad --> 453<div class="sponsor-ad" id="sponsor-ad"> 454<a id="sponsor-link" href="https://caszkins.com/" target="_blank" rel="noopener"> 455<img id="sponsor-img" src="https://laughingprofessor.net/image/catalog/artwork/caszkins-clothing-co-banner.png" alt="Caszkins Clothing Co"></a> 456</div></div> 457</div> 458<h3><b>Product Mockup Tool - Edit Designs & Customize Colors 459for Your Products</b></h3><h4>Such a cool interactive feature to benefit your 460custom design/product website store.</h4><p>Here your customers are able to preview 461different product options and design position preferences. Both of which are defined 462by you - from any clothing or other product choices that you wish to provide to 463your customers.</p> 464<br> 465 466<h2>Stand-Alone Online T-Shirt Mockup Product Designer</h2><p>Welcome 467to our <em>Stand-Alone Customer Online T-Shirt Mockup Product Designer</em>-the 468perfect tool to create your own custom designs for various products! Whether you're 469looking to design a t-shirt, mug, tumbler, or any other specific product, our 470interactive mockup tool gives you complete control over how your product looks 471before purchase.</p> 472<p>Our online product designer allows you to <strong>add up 473to two unique designs and placements</strong>, something you won't find in most 474other mockup tools! You can position your designs just the way you like, ensuring 475the final product perfectly matches your vision.</p> 476<br> 477<h4>Why Choose Our Mockup Designer?</h4> 478<ul> 479<li><strong>Interactive & Easy to Use:</strong> Our user-friendly 480interface is designed with simplicity in mind, so anyone can create professional-looking 481product mockups in minutes.</li> 482<li><strong>Multiple Product Types:</strong> In 483addition to <strong>t-shirts</strong>, you can create custom designs for <strong>mugs</strong>, 484<strong>tumblers</strong>, and more. We can also accommodate specific product 485types-just let us know your requirements!</li> 486<li><strong>Flexible Design Placement:</strong> 487Most tools limit you to one design placement, but with us, you can <strong>add 488two different designs</strong> in multiple areas on the same product.</li> 489<li><strong>Custom 490Color Options:</strong> Customize your mockup by selecting from a wide range of 491<strong>
491product colors</strong>. Each product can be viewed in various color options, 492ensuring the final result matches your brand or style.</li> 493<li><strong>Embeddable 494on Your Website:</strong> Our designer can be <strong>embedded directly into your 495website</strong>, allowing you to offer customers a seamless design experience. 496Control the look and feel by choosing your preferred colors and image options 497for the products.</li> 498</ul> 499<br> 500<hr> 501<br> 502<h3>What Can You Design?</h3> 503<p>With our stand-alone online product designer, you can create mockups for:</p> 504<ul> 505<li><strong>Custom T-Shirts</strong> 506<br>Design t-shirts with up to two design placements, available 507in various sizes and colors.</li> 508<li><p><strong>Mugs and Tumblers</strong> 509<br>Create 510personalized drinkware with your designs, perfectly placed to your liking.</p></li><li><p><strong>Any 511Custom Product</strong> 512<br>Have something unique in mind? We offer flexible design 513options for <strong>any specific product</strong>. Just provide us with the details, 514and we'll help you bring it to life.</p></li></ul> 515<br> 516<hr> 517<br> 518<h3>How It Works:</h3><ul> 519<li><strong>Choose 520a Product:</strong> Select the product you want to design (e.g., t-shirt, mug, 521tumbler).</li> 522<li><strong>Upload Your Designs:</strong> Upload up to two images 523or designs to be applied to different areas of the product.</li> 524<li><strong>Adjust 525Placement:</strong> Use the interactive tool to position the designs exactly where 526you want them.</li> 527<li><strong>Select Colors:</strong> Customize the product's color from a wide selection of options.</li> 528<li><strong>Preview & Save:</strong> 529Instantly preview your final mockup, save it, or proceed with your order!</li> 530</ul> 531<br> 532<hr> 533<br> 534<h3>Fully Customizable and Integrated into Your Website</h3> 535<p>This tool can be seamlessly 536<strong>embedded into your website</strong>, allowing you to offer customers the 537ability to design and visualize products before they buy. As a site owner, you 538can choose which products and colors to offer, giving you full control over the 539experience.</p> 540<br> 541<hr> 542<br> 543<h3>Start Creating Your Custom Designs Today!</h3> 544<p>Our <em>Stand-Alone 545Customer Online T-Shirt Mockup Product Designer</em> is the most flexible and 546user-friendly tool available, offering a unique, multi-design placement feature. 547Whether you're designing for apparel or drinkware, our tool provides everything 548you need to create stunning custom products.</p> 549<p>Get started today and bring your product designs to life!</p> 550<p><b>Product Mockup Tool</b>, the ultimate solution 551for custom design and printing store owners who want to take their products to 552the next level! Whether you're selling custom t-shirts, mugs, phone cases, or 553any other product, our tool allows you to <b>edit product designs, select colors</b>, 554and adjust design positioning with ease. Create the perfect product mockup for 555your store and give your customers the power to visualize their designs exactly 556how they want them.</p> 557<br> 558<h4>Why Use the Product Mockup Tool?</h4> 559<p><b>Complete Customization at Your Fingertips</b> Our intuitive Product Mockup Tool gives you 560complete control over every aspect of your product design. Adjust the <strong>design 561placement</strong>, <strong>size</strong>, and overlay <strong>position</strong> to match your exact vision. 562Select colors from a variety of swatches to perfectly match your products with 563your branding or the customer's preference.</p> 564<br> 565<h4>Boost Customer Engagement</h4> 566Allow your customers to experiment with different design placements and color choices 567before placing an order. By providing them with an interactive mockup experience, 568you increase engagement, leading to more personalized designs and higher sales. 569Customers can visualize their custom products and feel more confident in their 570choices. 571<br> 572<h4>Enhanced Design Control for Printing Accuracy</h4>The tool ensures 573that your customers' designs are correctly positioned and sized for printing. 574Whether it's a t-shirt, mug, or any other item, adjusting the <b>design positioning</b> 575is simple. This leads to more accurate prints and better results, minimizing potential 576errors in production. 577<br> 578<h4>Real-Time Previews for Better Product Selection</h4>No 579more guessing how a design will look once printed! Customers can see real-time 580changes as they modify the color or reposition the design, helping them make informed 581decisions. With live previews, they know exactly how their custom product will 582appear before purchasing. 583<br> 584<h4>Perfect for Custom Print-on-Demand Stores</h4>Our 585Product Mockup Tool is an ideal solution for <b>print-on-demand</b> store owners 586who need a quick, easy, and reliable way to showcase their products. Whether you're 587selling custom apparel, accessories, or gifts, this tool integrates seamlessly 588into your store, enhancing the user experience and driving more sales.<hr> 589<br> 590<h4>Key Features of the Product Mockup Tool:</h4> 591<b>Color Selection:</b> Choose from a 592wide range of colors to match your product's look. Ensure your design blends perfectly 593with the color options available for each product.<br><b>Design Positioning:</b> 594Adjust and move your design elements on the product mockup to achieve the ideal 595look. Fine-tune the size, position, and rotation of your design.<br> 596<b>User-Friendly 597Interface:</b> The easy-to-use interface ensures that store owners and customers 598alike can create the perfect product mockup with minimal effort.<br> 599<b>Responsive 600Design</b>
600: Whether your customers are shopping on desktop or mobile, the mockup 601tool works flawlessly on all devices, ensuring a smooth and seamless experience.<br> 602<b>Multiple 603Product Mockups:</b> Our tool supports a variety of products, so whether you're 604working on a t-shirt, mug, or other custom items, you can create high-quality 605mockups that reflect your product range. 606<br> 607<h4>How It Benefits Store Owners</h4> 608As a store owner, offering a <b>custom product mockup tool</b> improves your store's 609offerings and drives sales by providing customers with a fully interactive shopping 610experience. Here's why you should integrate the tool into your custom design business:<ul><li>Increase 611Conversion Rates: Customers are more likely to make a purchase when they can see 612their design on the product. The mockup tool removes doubt and helps them visualize 613the final product.</li> 614<li>Streamline Design Process: Rather than dealing with 615constant revision requests, customers can modify their design in real-time. You'll 616save time and reduce errors.</li><li>Attract More Customers: Interactive tools 617create a fun and engaging shopping experience, encouraging customers to spend 618more time on your website, exploring different designs and products.</li> 619<li>Minimize 620Return Rates: By allowing customers to get an accurate preview of their design, 621you reduce the chances of returns or dissatisfaction, ensuring a higher level 622of customer satisfaction.</li></ul> 623<br> 624<h4>Get Started with Our Product Mockup Tool Today!</h4> 625Don't miss out on the opportunity to provide your customers with the 626best possible shopping experience. Our <b>Product Mockup Tool</b> is simple to 627integrate into your existing store and enhances both your customer service and 628your bottom line.<br> 629Whether you're a small business or an established custom 630printing store, this tool will take your designs to the next level. <b>Start offering 631customized mockups</b> to your customers today and see the difference in your 632sales! 633<h4>Ready to revolutionize your product mockups?</h4> 634Get Started Now and 635explore the full potential of our tool for your custom printing business!<p></p><h3> </h3> 636 637 <blockquote> 638This tool is part of The Laughing Professor <a href="/seo-agency-toolbox/" target="_parent" aria-label="view the seo toolbox dashboard">On Page SEO Toolbox</a>, where multiple diagnostics and generators are combined into a single workflow for site optimization. 639</blockquote> 640 <footer class="legal"> 641 <span class="updated-date"> 642 <small> 643 <i class="fa fa-clock-o" aria-hidden="true"></i> 644 <time datetime="2026-02-22">Last updated: 22nd Feb 2026</time> - 645 <a href="https://laughingprofessor.net" target="_parent">Back to the Main Site</a> / 646 <a href="https://laughingprofessor.net/sitemap.html" title="domain html sitemap" target="_parent">Sitemap</a> 647 </small> 648 </span> 649 </footer>
650<script> 651 window.onload = function() { 652 const sponsors = [ 653 { 654 src: 'https://laughingprofessor.net/image/catalog/artwork/caszkins-clothing-co-banner.png', 655 alt: 'Caszkins Clothing Co', 656 title: 'Caszkins Clothing Co', 657 href: 'https://caszkins.com/' 658 }, 659 { 660 src: 'https://laughingprofessor.net/image/catalog/artwork/ict-sponsor.png', 661 alt: 'ICT Keepers', 662 title: 'ICT Keepers', 663 href: 'https://ictkeepers.com/' 664 }, 665 // Add more sponsors as needed 666 ]; 667 668 let currentSponsor = 0; 669 670 function changeSponsor() { 671 const imgElement = document.getElementById('sponsor-img'); 672 const linkElement = document.getElementById('sponsor-link'); 673 674 imgElement.src = sponsors[currentSponsor].src; 675 imgElement.alt = sponsors[currentSponsor].alt; 676 imgElement.title = sponsors[currentSponsor].title; 677 linkElement.href = sponsors[currentSponsor].href; 678 679 currentSponsor = (currentSponsor + 1) % sponsors.length; 680 } 681 682 setInterval(changeSponsor, 3000); 683 }; 684</script>
vendor: 1 bytes, line 684
684
685<script> 686 687const B = 'https://laughingprofessor.net/product-designer-tshirt/image/'; 688 689const COLORS = [ 690 {name:'Silver', hex:'#C9C9C9', front:'silver-shirt-mockup-front.png', back:'silver-shirt-mockup-back.png'}, 691 {name:'White', hex:'#F5F5F5', front:'white-shirt-mockup-front.png', back:'white-shirt-mockup-back.png'}, 692 {name:'Baby Blue', hex:'#BAD0DF', front:'baby-blue-shirt-mockup-front.png', back:'baby-blue-shirt-mockup-back.png'}, 693 {name:'Red', hex:'#A6342C', front:'red-shirt-mockup-front.png', back:'red-shirt-mockup-back.png'}, 694 {name:'Navy', hex:'#003399', front:'blue-shirt-mockup-front.png', back:'blue-shirt-mockup-back.png'}, 695 {name:'Green', hex:'#4C7C46', front:'green-shirt-mockup-front.png', back:'green-shirt-mockup-back.png'}, 696 {name:'Grass', hex:'#7E9D5E', front:'grass-shirt-mockup-front.png', back:'grass-shirt-mockup-back.png'}, 697 {name:'Asphalt', hex:'#615F5C', front:'asphalt-shirt-mockup-front.png', back:'asphalt-shirt-mockup-back.png'}, 698 {name:'Dk Heather', hex:'#525457', front:'dark-heather-shirt-mockup-front.png',back:'dark-heather-shirt-mockup-back.png'}, 699 {name:'Black', hex:'#111111', front:'black-shirt-mockup-front.png', back:'black-shirt-mockup-back.png'}, 700 {name:'Brown', hex:'#40352D', front:'brown-shirt-mockup-front.png', back:'brown-shirt-mockup-back.png'}, 701]; 702 703const D1 = [ 704 {s:B+'no-image.png',a:'None'},{s:B+'design1.png',a:'Design 1'},{s:B+'design2.png',a:'Design 2'}, 705 {s:B+'lucky-duck.png',a:'Lucky Duck'},{s:B+'relax-karen.png',a:'Relax Karen'}, 706 {s:B+'mom-heart-love.png',a:'Mom Heart'},{s:B+'design6.png',a:'Design 6'}, 707 {s:B+'cherub.png',a:'Cherub'},{s:B+'design8.png',a:'Design 8'}, 708 {s:B+'design9.png',a:'Design 9'},{s:B+'design10.png',a:'Design 10'}, 709 {s:B+'warning-may-do-something-stupid.png',a:'Warning'}, 710 {s:B+'valentines-day-shirt-not-flowers-sml.png',a:'Valentines'}, 711 {s:B+'living-the-dream.png',a:'Living Dream'},{s:B+'stabbed-in-the-back-again-sml.png',a:'Stabbed'}, 712]; 713const D2 = [ 714 {s:B+'no-image.png',a:'None'},{s:B+'us-flag.png',a:'US Flag'}, 715 {s:B+'wanker-w-anchor.png',a:'W Anchor'},{s:B+'teach-peace.png',a:'Teach Peace'}, 716 {s:B+'lgbt-liberty-guns-beer-tits.png',a:'LGBT'},{s:B+'mom-heart-love.png',a:'Mom Heart'}, 717 {s:B+'design6.png',a:'Design 6'},{s:B+'cherub.png',a:'Cherub'}, 718 {s:B+'design8.png',a:'Design 8'},{s:B+'design9.png',a:'Design 9'},{s:B+'design10.png',a:'Design 10'}, 719]; 720 721const FONTS = ['Anton','Bangers','Oswald','Russo One','Ultra','Permanent Marker','Pacifico','Lobster','Dancing Script','Satisfy','Montserrat','Raleway','Press Start 2P','Roboto Condensed']; 722const FLABELS = ['Anton','Bangers','Oswald','Russo One','Ultra','Marker','Pacifico','Lobster','Dancing','Satisfy','Montserrat','Raleway','Pixel','Condensed']; 723const TC = ['#ffffff','#000000','#f97316','#6366f1','#22c55e','#ef4444','#facc15','#06b6d4','#ec4899','#a78bfa']; 724const SC = ['none','#000000','#ffffff','#f97316','#6366f1','#ef4444']; 725 726// state 727const IS = {1:{w:90,h:90,x:80,y:90,r:0,o:100}, 2:{w:90,h:90,x:80,y:90,r:0,o:100}}; 728let curColor=COLORS[0], curView='both'; 729let selFont='Anton', selTC='#ffffff', selSC='none', selTextTarget='front'; 730let layers=[], lid=0, editId=null; 731 732// -- Colour swatches ----------------------------------------------------------- 733const cg=document.getElementById('colorGrid'); 734COLORS.forEach(c=>{ 735 const d=document.createElement('div'); 736 d.className='cswatch'+(c===curColor?' on':''); 737 d.style.background=c.hex; 738 d.innerHTML=`<span class="tip">${c.name}</span>`; 739 d.onclick=()=>{ 740 curColor=c; 741 document.getElementById('imgFront').src=B+c.front; 742 document.getElementById('imgBack').src=B+c.back; 743 document.getElementById('colorDot').style.background=c.hex; 744 document.getElementById('colorStatus').textContent=c.name; 745 document.getElementById('colorLabel').textContent=c.name; 746 cg.querySelectorAll('.cswatch').forEach(e=>e.classList.remove('on')); 747 d.classList.add('on'); 748 }; 749 cg.appendChild(d); 750}); 751 752// -- View toggle focuses one stage or shows both ----------------------------- 753function setView(v){ 754 curView=v; 755 const wF=document.getElementById('wrapFront'), wB=document.getElementById('wrapBack'); 756 wF.classList.remove('focused','dimmed'); 757 wB.classList.remove('focused','dimmed'); 758 if(v==='front'){ wF.classList.add('focused'); wB.classList.add('dimmed'); } 759 else if(v==='back'){ wB.classList.add('focused'); wF.classList.add('dimmed'); } 760 // auto-switch controls to match 761 if(v==='front') switchMain('img1'); 762 if(v==='back') switchMain('img2'); 763 document.getElementById('viewBoth').classList.toggle('on',v==='both'); 764 document.getElementById('viewFront').classList.toggle('on',v==='front'); 765 document.getElementById('viewBack').classList.toggle('on',v==='back'); 766 const notes={both:'Showing front & back',front:'Front focused - drag designs freely',back:'Back focused - drag designs freely'}; 767 document.getElementById('viewNote').textContent=notes[v]; 768} 769 770// -- Galleries ----------------------------------------------------------------- 771function buildGallery(gid, items, ovId){ 772 const g=document.getElementById(gid), uid='up-'+ovId; 773 g.innerHTML=` 774 <label class="dsw up" for="${uid}"> 775 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 776 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> 777 <polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/> 778 </svg>Upload 779 </label> 780 <input type="file" id="${uid}" accept="image/*">`; 781 document.getElementById(uid).onchange=e=>{ 782 const f=e.target.files[0]; if(!f) return; 783 const el=document.getElementById(ovId); 784 el.src=URL.createObjectURL(f); el.style.display=''; showN('Design updated!'); 785 };
786 items.forEach(d=>{ 787 const el=document.createElement('div'); el.className='dsw'; 788 el.innerHTML=`<img src="${d.s}" alt="${d.a}" crossorigin="anonymous">`; 789 el.onclick=()=>{ 790 const ov=document.getElementById(ovId); 791 ov.src=d.s; ov.style.display=d.s.includes('no-image')?'none':''; 792 }; 793 g.appendChild(el); 794 }); 795} 796buildGallery('gallery1',D1,'ov1'); 797buildGallery('gallery2',D2,'ov2'); 798 799// -- Image overlay controls ---------------------------------------------------- 800function updateOv(n,p,v){ 801 IS[n][p]=+v; 802 const lbl=document.getElementById(`v${n}${p}`); if(lbl) lbl.textContent=v; 803 applyOv(n); 804} 805function applyOv(n){ 806 const s=IS[n], el=document.getElementById('ov'+n); 807 el.style.width=s.w+'px'; el.style.height=s.h+'px'; 808 el.style.left=s.x+'px'; el.style.top=s.y+'px'; 809 el.style.opacity=s.o/100; el.style.transform=`rotate(${s.r}deg)`; 810} 811function resetImg(n){ 812 const def={w:90,h:90,x:80,y:90,r:0,o:100}; 813 Object.assign(IS[n],def); 814 const keys=['w','h','x','y','r','o']; 815 document.getElementById('pane-img'+n).querySelectorAll('input[type=range]').forEach((sl,i)=>sl.value=def[keys[i]]); 816 keys.forEach(k=>{ const e=document.getElementById(`v${n}${k}`); if(e) e.textContent=def[k]; }); 817 applyOv(n); 818} 819 820// -- Image dragging ------------------------------------------------------------ 821[1,2].forEach(n=>{ 822 const el=document.getElementById('ov'+n); 823 const stageId=n===1?'stageFront':'stageBack'; 824 let drag=false,sx=0,sy=0,ox=0,oy=0; 825 const maxX=()=>document.getElementById(stageId).offsetWidth-IS[n].w; 826 const maxY=()=>document.getElementById(stageId).offsetHeight-IS[n].h; 827 const start=(cx,cy)=>{ drag=true; sx=cx; sy=cy; ox=IS[n].x; oy=IS[n].y; switchMain('img'+n); }; 828 const move=(cx,cy)=>{ 829 if(!drag) return; 830 IS[n].x=Math.max(0,Math.min(maxX(),ox+(cx-sx))); 831 IS[n].y=Math.max(0,Math.min(maxY(),oy+(cy-sy))); 832 applyOv(n); 833 const sl=document.getElementById('pane-img'+n).querySelectorAll('input[type=range]'); 834 sl[2].value=IS[n].x; sl[3].value=IS[n].y; 835 document.getElementById(`v${n}x`).textContent=Math.round(IS[n].x); 836 document.getElementById(`v${n}y`).textContent=Math.round(IS[n].y); 837 }; 838 const end=()=>drag=false; 839 el.addEventListener('mousedown',e=>{ start(e.clientX,e.clientY); e.preventDefault(); }); 840 window.addEventListener('mousemove',e=>move(e.clientX,e.clientY)); 841 window.addEventListener('mouseup',end); 842 el.addEventListener('touchstart',e=>{ start(e.touches[0].clientX,e.touches[0].clientY); e.preventDefault(); },{passive:false}); 843 window.addEventListener('touchmove',e=>{ if(drag) move(e.touches[0].clientX,e.touches[0].clientY); },{passive:false}); 844 window.addEventListener('touchend',end); 845}); 846 847// -- Main tab ------------------------------------------------------------------ 848function switchMain(p){ 849 ['img1','img2','text'].forEach(x=>document.getElementById('pane-'+x).style.display=x===p?'':'none'); 850 document.querySelectorAll('#mainTabs .tab').forEach((t,i)=>t.classList.toggle('on',['img1','img2','text'][i]===p)); 851} 852 853// -- Font grid ----------------------------------------------------------------- 854const fg=document.getElementById('fontGrid'); 855FONTS.forEach((f,i)=>{ 856 const b=document.createElement('button'); 857 b.className='fopt'+(f===selFont?' on':''); 858 b.style.fontFamily=f; b.textContent=FLABELS[i]; 859 b.onclick=()=>{ selFont=f; fg.querySelectorAll('.fopt').forEach(e=>e.classList.remove('on')); b.classList.add('on'); applyPreview(); }; 860 fg.appendChild(b); 861}); 862 863// -- Text colour --------------------------------------------------------------- 864const tcs=document.getElementById('tcStrip');
865TC.forEach(c=>{ 866 const d=document.createElement('div'); 867 d.className='tcsw'+(c===selTC?' on':''); 868 d.style.background=c; if(c==='#ffffff') d.style.border='2px solid #555'; 869 d.onclick=()=>{ setTC(c); tcs.querySelectorAll('.tcsw').forEach(e=>e.classList.remove('on')); d.classList.add('on'); }; 870 tcs.appendChild(d); 871}); 872function setTC(c){ selTC=c; document.getElementById('tcPicker').value=c; applyPreview(); } 873 874// -- Stroke colour ------------------------------------------------------------- 875const scs=document.getElementById('scStrip'); 876SC.forEach(c=>{ 877 const d=document.createElement('div'); 878 d.className='tcsw'+(c==='none'?' on':''); 879 d.style.background=c==='none'?'transparent':c; 880 if(c==='none'){ d.style.border='2px dashed #666'; d.textContent='?'; } 881 else if(c==='#ffffff') d.style.border='2px solid #444'; 882 d.onclick=()=>{ setSC(c); scs.querySelectorAll('.tcsw').forEach(e=>e.classList.remove('on')); d.classList.add('on'); }; 883 scs.appendChild(d); 884}); 885function setSC(c){ selSC=c; if(c!=='none') document.getElementById('scPicker').value=c; applyPreview(); } 886 887// -- Text target (front/back shirt) -------------------------------------------- 888function setTextTarget(t){ 889 selTextTarget=t; 890 document.getElementById('sttF').classList.toggle('on',t==='front'); 891 document.getElementById('sttB').classList.toggle('on',t==='back'); 892} 893 894// -- Text preview (updates selected layer) ------------------------------------ 895function applyPreview(){ 896 if(editId===null) return; 897 const l=layers.find(x=>x.id===editId); if(!l) return; 898 l.font=selFont; l.color=selTC; l.stroke=selSC; 899 l.size=+document.getElementById('slFS').value; 900 l.rot=+document.getElementById('slTR').value; 901 l.strokeW=+document.getElementById('slSW').value; 902 renderLayer(l); renderList(); 903} 904 905// -- Add text ------------------------------------------------------------------ 906function addText(){ 907 const txt=document.getElementById('textInput').value.trim(); 908 if(!txt){ showN('? Enter some text first!'); return; } 909 const id=++lid; 910 const el=document.createElement('div'); 911 el.className='text-overlay ring'; el.id='tov-'+id; 912 // attach to correct stage 913 const stageId=selTextTarget==='front'?'stageFront':'stageBack'; 914 document.getElementById(stageId).appendChild(el); 915 const l={id,text:txt,font:selFont,size:+document.getElementById('slFS').value, 916 color:selTC,stroke:selSC,strokeW:+document.getElementById('slSW').value, 917 x:60,y:150,rot:+document.getElementById('slTR').value, 918 target:selTextTarget, el}; 919 layers.push(l); 920 editId=id; 921 renderLayer(l); makeTextDrag(l); renderList(); 922 document.getElementById('textInput').value=''; 923 showN(`? Text added to ${selTextTarget} drag to position!`); 924} 925 926function renderLayer(l){ 927 const sw=l.strokeW, sc=l.stroke; 928 const shadow=(sw>0&&sc!=='none') 929 ?`${sw}px ${sw}px 0 ${sc},-${sw}px ${sw}px 0 ${sc},${sw}px -${sw}px 0 ${sc},-${sw}px -${sw}px 0 ${sc}` 930 :'none'; 931 l.el.style.cssText=` 932 position:absolute;cursor:move;user-select:none;touch-action:none; 933 left:${l.x}px;top:${l.y}px; 934 font-family:'${l.font}',sans-serif;font-size:${l.size}px; 935 color:${l.color};text-shadow:${shadow}; 936 transform:rotate(${l.rot}deg); 937 white-space:nowrap;line-height:1.2;padding:2px 4px;z-index:50; 938 outline:${editId===l.id?'2px dashed #6366f1':'none'};outline-offset:3px; 939 `; 940 l.el.textContent=l.text; 941} 942 943function makeTextDrag(l){ 944 const el=l.el; let drag=false,sx=0,sy=0,ox=0,oy=0; 945 const start=(cx,cy)=>{ drag=true; sx=cx; sy=cy; ox=l.x; oy=l.y; selLayer(l.id); }; 946 const move=(cx,cy)=>{ if(!drag) return; l.x=ox+(cx-sx); l.y=oy+(cy-sy); renderLayer(l); }; 947 const end=()=>drag=false; 948 el.addEventListener('mousedown',e=>{ start(e.clientX,e.clientY); e.preventDefault(); }); 949 window.addEventListener('mousemove',e=>move(e.clientX,e.clientY)); 950 window.addEventListener('mouseup',end); 951 el.addEventListener('touchstart',e=>{ start(e.touches[0].clientX,e.touches[0].clientY); e.preventDefault(); },{passive:false}); 952 window.addEventListener('touchmove',e=>{ if(drag) move(e.touches[0].clientX,e.touches[0].clientY); },{passive:false}); 953 window.addEventListener('touchend',end); 954} 955 956function selLayer(id){ 957 editId=id; 958 const l=layers.find(x=>x.id===id); if(!l) return; 959 selFont=l.font; selTC=l.color; selSC=l.stroke; 960 document.getElementById('textInput').value=l.text; 961 document.getElementById('slFS').value=l.size; document.getElementById('vFS').textContent=l.size; 962 document.getElementById('slTR').value=l.rot; document.getElementById('vTR').textContent=l.rot; 963 document.getElementById('slSW').value=l.strokeW; document.getElementById('vSW').textContent=l.strokeW; 964 fg.querySelectorAll('.fopt').forEach(b=>b.classList.toggle('on',b.style.fontFamily.replace(/'/g,'')===l.font)); 965 setTextTarget(l.target);
966 layers.forEach(x=>renderLayer(x)); renderList(); 967 switchMain('text'); 968} 969 970function removeLayer(id){ 971 const i=layers.findIndex(x=>x.id===id); if(i<0) return; 972 layers[i].el.remove(); layers.splice(i,1); 973 if(editId===id) editId=null; 974 renderList(); 975} 976 977function renderList(){ 978 const el=document.getElementById('tlist'); 979 if(!layers.length){ el.innerHTML='<p style="font-size:.7rem;color:var(--muted);text-align:center">No text yet</p>'; return; } 980 el.innerHTML=layers.map(l=>` 981 <div class="tentry" style="${editId===l.id?'border:1px solid #6366f1':''}"> 982 <span style="font-family:'${l.font}';color:${l.color}">${l.text}</span> 983 <span style="font-size:.6rem;color:var(--muted);flex-shrink:0">${l.target}</span> 984 <button class="eb" onclick="selLayer(${l.id})" title="Edit">??</button> 985 <button onclick="removeLayer(${l.id})" title="Remove">?</button> 986 </div>`).join(''); 987} 988 989// -- Download ------------------------------------------------------------------ 990function stripOutlines(){ layers.forEach(l=>{ l.el.style.outline='none'; }); } 991function restoreOutlines(){ layers.forEach(l=>renderLayer(l)); } 992 993function doDownload(){ 994 stripOutlines(); 995 showN('Preparing '); 996 html2canvas(document.getElementById('shirtsRow'),{useCORS:true,allowTaint:false,backgroundColor:null}) 997 .then(canvas=>{ 998 const a=document.createElement('a'); a.href=canvas.toDataURL('image/png'); a.download='tshirt-mockup-both.png'; a.click(); 999 showN('? Downloaded!'); 1000 }).catch(()=>showN('? Try again')).finally(restoreOutlines); 1001} 1002 1003function doDownloadSingle(side){ 1004 stripOutlines(); 1005 const stageId=side==='front'?'stageFront':'stageBack'; 1006 html2canvas(document.getElementById(stageId),{useCORS:true,allowTaint:false,backgroundColor:null}) 1007 .then(canvas=>{ 1008 const a=document.createElement('a'); a.href=canvas.toDataURL('image/png'); a.download=`tshirt-${side}.png`; a.click(); 1009 showN(`? ${side} downloaded!`); 1010 }).catch(()=>showN('? Try again')).finally(restoreOutlines); 1011} 1012 1013// -- Email --------------------------------------------------------------------- 1014function openEmail(){ document.getElementById('emailModal').classList.add('open'); } 1015function closeEmail(){ document.getElementById('emailModal').classList.remove('open'); } 1016function sendEmail(){ 1017 const to=document.getElementById('eTo').value.trim(); 1018 const subj=encodeURIComponent(document.getElementById('eSubj').value); 1019 const body=encodeURIComponent(document.getElementById('eBody').value); 1020 doDownload(); 1021 setTimeout(()=>{ window.location.href=`mailto:${to}?subject=${subj}&body=${body}`; },1400); 1022 closeEmail(); 1023} 1024function copyLink(){ navigator.clipboard.writeText(window.location.href).then(()=>showN('? Link copied!')).catch(()=>showN('Could not copy')); } 1025function showN(msg){ const n=document.getElementById('notif'); n.textContent=msg; n.classList.add('show'); setTimeout(()=>n.classList.remove('show'),2400); } 1026</script>
vendor: 1 bytes, line 1026
1026
1027<script> 1028document.addEventListener('DOMContentLoaded', function () { 1029 const shareButtons = document.querySelectorAll('.share-button'); 1030 1031 shareButtons.forEach(function (button) { 1032 button.addEventListener('click', function (event) { 1033 event.preventDefault(); 1034 1035 var service = this.getAttribute('data-service'); 1036 var url = encodeURIComponent(window.location.href); 1037 var text = encodeURIComponent(document.title); 1038 var shareUrl = ''; 1039 1040 switch (service) { 1041 case 'facebook': 1042 shareUrl = 'https://www.facebook.com/sharer/sharer.php?u=' + url; 1043 break; 1044 case 'x': 1045 shareUrl = 'https://twitter.com/intent/tweet?url=' + url + '&text=' + text; 1046 break; 1047 case 'linkedin': 1048 shareUrl = 'https://www.linkedin.com/shareArticle?min=true&url=' + url + '&title=' + text; 1049 break; 1050 case 'whatsapp': 1051 shareUrl = 'https://api.whatsapp.com/send?text=' + text + '%20' + url; 1052 break; 1053 } 1054 1055 if (shareUrl) { 1056 window.open(shareUrl, '_blank', 'width=600,height=400'); 1057 } 1058 }); 1059 }); 1060}); 1061</script>
1061 1062 1063
1063<script type="application/ld+json"> 1064{ 1065 "@context": "https://schema.org/", 1066 "@type": "BreadcrumbList", 1067 "itemListElement": [{ 1068 "@type": "ListItem", 1069 "position": 1, 1070 "name": "The Laughing Professor", 1071 "item": "https://laughingprofessor.net/" 1072 },{ 1073 "@type": "ListItem", 1074 "position": 2, 1075 "name": "Print Shop Tools", 1076 "item": "https://laughingprofessor.net/printshop-tools/" 1077 },{ 1078 "@type": "ListItem", 1079 "position": 3, 1080 "name": "On-line product t-shirt mockup designer", 1081 "item": "https://laughingprofessor.net/product-designer-tshirt/" 1082 }] 1083} 1084</script>
1084 1085 1086</body> 1087</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.