PageSourceSearch

https://laughingprofessor.net/product-designer-tshirt/

html laughingprofessor.net collected 2026-10-01 23:52:55 UTC 58,293 bytes, 1,087 lines download raw bytes

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 &amp; 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 &amp; 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 &amp; 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 &amp; 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>&nbsp;</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.