@spark-apps/piclet 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,180 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-piclet data-width="400" data-height="440">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Filter</title>
7
+ <link rel="stylesheet" href="/css/theme.css">
8
+ <script src="/js/piclet.js"></script>
9
+ <style>
10
+ .preview-area{flex:1;display:flex;align-items:center;justify-content:center;background:var(--bg2);border-radius:6px;min-height:140px;overflow:hidden;position:relative}
11
+ .preview-area::before{content:'';position:absolute;inset:0;background:repeating-conic-gradient(#1a1a1d 0% 25%, #222 0% 50%) 50%/16px 16px;z-index:0}
12
+ .preview-area img{max-width:100%;max-height:100%;object-fit:contain;position:relative;z-index:1}
13
+ .preview-area .placeholder{position:relative;z-index:1;font-size:11px;color:var(--txt3)}
14
+ .preview-area .mini-sp{width:16px;height:16px;border:2px solid var(--bg3);border-top-color:var(--acc);border-radius:50%;animation:s .5s linear infinite;position:relative;z-index:1}
15
+ .preview-info{font-size:10px;color:var(--txt3);text-align:center;margin-top:4px;height:14px}
16
+ .filter-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;padding:4px 0}
17
+ .filter-btn{display:flex;flex-direction:column;align-items:center;gap:4px;padding:10px 8px;background:var(--bg2);border:1px solid var(--brd);border-radius:6px;color:var(--txt2);font:inherit;font-size:11px;cursor:pointer;transition:all .15s}
18
+ .filter-btn:hover{background:var(--bg3);color:var(--txt)}
19
+ .filter-btn.active{background:var(--acc);border-color:var(--acc);color:#000;font-weight:500}
20
+ .filter-icon{width:24px;height:24px;border-radius:4px;border:1px solid var(--brd)}
21
+ .filter-btn.active .filter-icon{border-color:#000}
22
+ .filter-icon.gray{background:linear-gradient(135deg,#888,#333)}
23
+ .filter-icon.sepia{background:linear-gradient(135deg,#d4a574,#8b6914)}
24
+ .filter-icon.invert{background:linear-gradient(135deg,#fff,#000)}
25
+ .filter-icon.vintage{background:linear-gradient(135deg,#a89078,#5c4a3a)}
26
+ .filter-icon.vivid{background:linear-gradient(135deg,#ff6b6b,#4ecdc4)}
27
+ </style>
28
+ </head>
29
+ <body>
30
+ <div class="app">
31
+ <div class="hd"><b>PicLet</b><span>Filter</span></div>
32
+ <div class="meta">
33
+ <div>File<b id="fn">-</b></div>
34
+ <div>Size<b id="sz">-</b></div>
35
+ </div>
36
+ <!-- Form -->
37
+ <div id="F" class="form">
38
+ <div class="preview-area" id="pA">
39
+ <span class="placeholder">Select filter to preview</span>
40
+ </div>
41
+ <div class="preview-info" id="pI"></div>
42
+ <div class="filter-grid">
43
+ <button class="filter-btn active" data-filter="grayscale" onclick="selectFilter('grayscale')">
44
+ <div class="filter-icon gray"></div>
45
+ Grayscale
46
+ </button>
47
+ <button class="filter-btn" data-filter="sepia" onclick="selectFilter('sepia')">
48
+ <div class="filter-icon sepia"></div>
49
+ Sepia
50
+ </button>
51
+ <button class="filter-btn" data-filter="invert" onclick="selectFilter('invert')">
52
+ <div class="filter-icon invert"></div>
53
+ Invert
54
+ </button>
55
+ <button class="filter-btn" data-filter="vintage" onclick="selectFilter('vintage')">
56
+ <div class="filter-icon vintage"></div>
57
+ Vintage
58
+ </button>
59
+ <button class="filter-btn" data-filter="vivid" onclick="selectFilter('vivid')">
60
+ <div class="filter-icon vivid"></div>
61
+ Vivid
62
+ </button>
63
+ </div>
64
+ <div class="btns">
65
+ <button class="btn btn-g" onclick="PicLet.close()">Cancel</button>
66
+ <button class="btn btn-p" onclick="apply()">Apply</button>
67
+ </div>
68
+ </div>
69
+ <!-- Loading state -->
70
+ <div class="ld" id="L"><div class="sp"></div><span id="lT">Applying filter...</span></div>
71
+ <!-- Log -->
72
+ <div class="log" id="G"></div>
73
+ <!-- Done state -->
74
+ <div class="dn" id="D">
75
+ <h4 id="dT"></h4>
76
+ <p id="dM"></p>
77
+ <div class="btns" style="width:100%;margin-top:8px">
78
+ <button class="btn btn-p" onclick="PicLet.close()">Done</button>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ <script>
83
+ const { $, log, fetchJson, postJson } = PicLet;
84
+ const pA = $('pA'), pI = $('pI');
85
+
86
+ let currentFilter = 'grayscale';
87
+ let previewTimeout = null;
88
+ let lastPreviewOpts = null;
89
+
90
+ // Select filter
91
+ function selectFilter(type) {
92
+ currentFilter = type;
93
+ document.querySelectorAll('.filter-btn').forEach(btn => {
94
+ btn.classList.toggle('active', btn.dataset.filter === type);
95
+ });
96
+ schedulePreview();
97
+ }
98
+
99
+ // Get current options
100
+ function getOptions() {
101
+ return { filter: currentFilter };
102
+ }
103
+
104
+ // Check if options changed
105
+ function optionsChanged() {
106
+ const current = JSON.stringify(getOptions());
107
+ if (current === lastPreviewOpts) return false;
108
+ lastPreviewOpts = current;
109
+ return true;
110
+ }
111
+
112
+ // Schedule preview
113
+ function schedulePreview() {
114
+ if (previewTimeout) clearTimeout(previewTimeout);
115
+ previewTimeout = setTimeout(() => {
116
+ if (optionsChanged()) {
117
+ generatePreview();
118
+ }
119
+ }, 200);
120
+ }
121
+
122
+ // Generate preview
123
+ async function generatePreview() {
124
+ pA.innerHTML = '<div class="mini-sp"></div>';
125
+ pI.textContent = '';
126
+
127
+ try {
128
+ const result = await postJson('/api/preview', getOptions());
129
+
130
+ if (result.success && result.imageData) {
131
+ const img = document.createElement('img');
132
+ img.src = result.imageData;
133
+ img.alt = 'Preview';
134
+ pA.innerHTML = '';
135
+ pA.appendChild(img);
136
+ pI.textContent = result.width && result.height ? `${result.width}×${result.height}` : '';
137
+ } else {
138
+ pA.innerHTML = '<span class="placeholder">' + (result.error || 'Preview failed') + '</span>';
139
+ pI.textContent = '';
140
+ }
141
+ } catch (e) {
142
+ pA.innerHTML = '<span class="placeholder">Preview error</span>';
143
+ pI.textContent = '';
144
+ }
145
+ }
146
+
147
+ // Load initial data
148
+ fetchJson('/api/info').then(d => {
149
+ $('fn').textContent = d.fileName;
150
+ $('sz').textContent = d.width + '×' + d.height;
151
+ setTimeout(generatePreview, 100);
152
+ });
153
+
154
+ // Apply
155
+ async function apply() {
156
+ $('F').classList.add('hide');
157
+ $('L').classList.add('on');
158
+ $('G').classList.add('on');
159
+
160
+ try {
161
+ const result = await postJson('/api/process', getOptions());
162
+ if (result.logs) {
163
+ result.logs.forEach(l => log('G', l.type[0], l.message));
164
+ }
165
+
166
+ $('L').classList.remove('on');
167
+ $('D').classList.add('on', result.success ? 'ok' : 'err');
168
+ $('dT').textContent = result.success ? 'Done' : 'Failed';
169
+ $('dM').textContent = result.success ? result.output : result.error;
170
+ } catch (e) {
171
+ log('G', 'e', e.message);
172
+ $('L').classList.remove('on');
173
+ $('D').classList.add('on', 'err');
174
+ $('dT').textContent = 'Error';
175
+ $('dM').textContent = e.message;
176
+ }
177
+ }
178
+ </script>
179
+ </body>
180
+ </html>
@@ -1,113 +1,113 @@
1
- <!DOCTYPE html>
2
- <html data-piclet data-width="340" data-height="310">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <title>Icon Pack</title>
7
- <link rel="stylesheet" href="/css/theme.css">
8
- <script src="/js/piclet.js"></script>
9
- <style>
10
- .platforms{display:flex;flex-direction:column;gap:4px;flex:1}
11
- .platforms .opt{padding:8px 10px;background:var(--bg2);border-radius:6px;border:1px solid var(--brd)}
12
- .platforms .opt:hover{border-color:var(--acc)}
13
- .platform-info{font-size:10px;color:var(--txt3);margin-left:22px;margin-top:2px}
14
- </style>
15
- </head>
16
- <body>
17
- <div class="app">
18
- <div class="hd"><b>PicLet</b><span>Icon Pack Generator</span></div>
19
- <div class="meta">
20
- <div>File<b id="fn">-</b></div>
21
- <div>Size<b id="sz">-</b></div>
22
- </div>
23
- <!-- Form state -->
24
- <div id="F" class="form">
25
- <div class="platforms">
26
- <label class="opt">
27
- <input type="checkbox" id="cW" checked>
28
- <span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>
29
- Web
30
- <div class="platform-info">favicon, PWA, apple-touch-icon</div>
31
- </label>
32
- <label class="opt">
33
- <input type="checkbox" id="cA" checked>
34
- <span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>
35
- Android
36
- <div class="platform-info">mipmap folders, Play Store icon</div>
37
- </label>
38
- <label class="opt">
39
- <input type="checkbox" id="cI" checked>
40
- <span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>
41
- iOS
42
- <div class="platform-info">App icons, App Store icon</div>
43
- </label>
44
- </div>
45
- <div class="warn-box" id="wB"></div>
46
- <div class="btns">
47
- <button class="btn btn-g" onclick="PicLet.close()">Cancel</button>
48
- <button class="btn btn-p" onclick="generate()">Generate</button>
49
- </div>
50
- </div>
51
- <!-- Loading state -->
52
- <div class="ld" id="L"><div class="sp"></div><span id="lT">Generating icons...</span></div>
53
- <!-- Log -->
54
- <div class="log" id="G"></div>
55
- <!-- Done state -->
56
- <div class="dn" id="D">
57
- <h4 id="dT"></h4>
58
- <p id="dM"></p>
59
- <div class="btns" style="width:100%;margin-top:8px">
60
- <button class="btn btn-p" onclick="PicLet.close()">Done</button>
61
- </div>
62
- </div>
63
- </div>
64
- <script>
65
- const { $, log, fetchJson, postJson } = PicLet;
66
-
67
- // Load initial data and check for warnings
68
- fetchJson('/api/info').then(d => {
69
- $('fn').textContent = d.fileName;
70
- $('sz').textContent = d.width + '×' + d.height;
71
- if (d.width < 1024 || d.height < 1024) {
72
- $('wB').textContent = 'Source < 1024px. Larger images produce better quality.';
73
- $('wB').classList.add('on');
74
- }
75
- });
76
-
77
- // Generate icons
78
- async function generate() {
79
- const web = $('cW').checked;
80
- const android = $('cA').checked;
81
- const ios = $('cI').checked;
82
-
83
- if (!web && !android && !ios) {
84
- alert('Please select at least one platform');
85
- return;
86
- }
87
-
88
- $('F').classList.add('hide');
89
- $('L').classList.add('on');
90
- $('G').classList.add('on');
91
-
92
- try {
93
- const result = await postJson('/api/process', { web, android, ios });
94
-
95
- if (result.logs) {
96
- result.logs.forEach(l => log('G', l.type[0], l.message));
97
- }
98
-
99
- $('L').classList.remove('on');
100
- $('D').classList.add('on', result.success ? 'ok' : 'err');
101
- $('dT').textContent = result.success ? 'Done' : 'Failed';
102
- $('dM').textContent = result.success ? result.output : result.error;
103
- } catch (e) {
104
- log('G', 'e', e.message);
105
- $('L').classList.remove('on');
106
- $('D').classList.add('on', 'err');
107
- $('dT').textContent = 'Error';
108
- $('dM').textContent = e.message;
109
- }
110
- }
111
- </script>
112
- </body>
113
- </html>
1
+ <!DOCTYPE html>
2
+ <html data-piclet data-width="340" data-height="310">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Icon Pack</title>
7
+ <link rel="stylesheet" href="/css/theme.css">
8
+ <script src="/js/piclet.js"></script>
9
+ <style>
10
+ .platforms{display:flex;flex-direction:column;gap:4px;flex:1}
11
+ .platforms .opt{padding:8px 10px;background:var(--bg2);border-radius:6px;border:1px solid var(--brd)}
12
+ .platforms .opt:hover{border-color:var(--acc)}
13
+ .platform-info{font-size:10px;color:var(--txt3);margin-left:22px;margin-top:2px}
14
+ </style>
15
+ </head>
16
+ <body>
17
+ <div class="app">
18
+ <div class="hd"><b>PicLet</b><span>Icon Pack Generator</span></div>
19
+ <div class="meta">
20
+ <div>File<b id="fn">-</b></div>
21
+ <div>Size<b id="sz">-</b></div>
22
+ </div>
23
+ <!-- Form state -->
24
+ <div id="F" class="form">
25
+ <div class="platforms">
26
+ <label class="opt">
27
+ <input type="checkbox" id="cW" checked>
28
+ <span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>
29
+ Web
30
+ <div class="platform-info">favicon, PWA, apple-touch-icon</div>
31
+ </label>
32
+ <label class="opt">
33
+ <input type="checkbox" id="cA" checked>
34
+ <span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>
35
+ Android
36
+ <div class="platform-info">mipmap folders, Play Store icon</div>
37
+ </label>
38
+ <label class="opt">
39
+ <input type="checkbox" id="cI" checked>
40
+ <span class="box"><svg viewBox="0 0 12 12"><polyline points="2,6 5,9 10,3"/></svg></span>
41
+ iOS
42
+ <div class="platform-info">App icons, App Store icon</div>
43
+ </label>
44
+ </div>
45
+ <div class="warn-box" id="wB"></div>
46
+ <div class="btns">
47
+ <button class="btn btn-g" onclick="PicLet.close()">Cancel</button>
48
+ <button class="btn btn-p" onclick="generate()">Generate</button>
49
+ </div>
50
+ </div>
51
+ <!-- Loading state -->
52
+ <div class="ld" id="L"><div class="sp"></div><span id="lT">Generating icons...</span></div>
53
+ <!-- Log -->
54
+ <div class="log" id="G"></div>
55
+ <!-- Done state -->
56
+ <div class="dn" id="D">
57
+ <h4 id="dT"></h4>
58
+ <p id="dM"></p>
59
+ <div class="btns" style="width:100%;margin-top:8px">
60
+ <button class="btn btn-p" onclick="PicLet.close()">Done</button>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ <script>
65
+ const { $, log, fetchJson, postJson } = PicLet;
66
+
67
+ // Load initial data and check for warnings
68
+ fetchJson('/api/info').then(d => {
69
+ $('fn').textContent = d.fileName;
70
+ $('sz').textContent = d.width + '×' + d.height;
71
+ if (d.width < 1024 || d.height < 1024) {
72
+ $('wB').textContent = 'Source < 1024px. Larger images produce better quality.';
73
+ $('wB').classList.add('on');
74
+ }
75
+ });
76
+
77
+ // Generate icons
78
+ async function generate() {
79
+ const web = $('cW').checked;
80
+ const android = $('cA').checked;
81
+ const ios = $('cI').checked;
82
+
83
+ if (!web && !android && !ios) {
84
+ alert('Please select at least one platform');
85
+ return;
86
+ }
87
+
88
+ $('F').classList.add('hide');
89
+ $('L').classList.add('on');
90
+ $('G').classList.add('on');
91
+
92
+ try {
93
+ const result = await postJson('/api/process', { web, android, ios });
94
+
95
+ if (result.logs) {
96
+ result.logs.forEach(l => log('G', l.type[0], l.message));
97
+ }
98
+
99
+ $('L').classList.remove('on');
100
+ $('D').classList.add('on', result.success ? 'ok' : 'err');
101
+ $('dT').textContent = result.success ? 'Done' : 'Failed';
102
+ $('dM').textContent = result.success ? result.output : result.error;
103
+ } catch (e) {
104
+ log('G', 'e', e.message);
105
+ $('L').classList.remove('on');
106
+ $('D').classList.add('on', 'err');
107
+ $('dT').textContent = 'Error';
108
+ $('dM').textContent = e.message;
109
+ }
110
+ }
111
+ </script>
112
+ </body>
113
+ </html>
@@ -41,6 +41,15 @@
41
41
  fetch('/api/close', { method: 'POST' }).finally(() => window.close());
42
42
  }
43
43
 
44
+ // Open URL in default browser
45
+ function openUrl(url) {
46
+ fetch('/api/open-url', {
47
+ method: 'POST',
48
+ headers: { 'Content-Type': 'application/json' },
49
+ body: JSON.stringify({ url })
50
+ });
51
+ }
52
+
44
53
  // Initialize window
45
54
  function init(opts = {}) {
46
55
  const w = opts.width || 320;
@@ -60,7 +69,7 @@
60
69
  }
61
70
 
62
71
  // Export to global
63
- window.PicLet = { $, log, fetchJson, postJson, close, init };
72
+ window.PicLet = { $, log, fetchJson, postJson, close, openUrl, init };
64
73
 
65
74
  // Auto-init on DOMContentLoaded if data attributes present
66
75
  document.addEventListener('DOMContentLoaded', () => {
@@ -20,26 +20,21 @@
20
20
  />
21
21
  <style>
22
22
  *{margin:0;padding:0}
23
- html,body{height:100%;overflow:hidden;background:linear-gradient(180deg,#0a0a0c 0%,#111113 100%);transition:opacity .4s ease-out}
24
- body.fade-out{opacity:0}
23
+ html,body{height:100%;overflow:hidden;background:linear-gradient(180deg,#0a0a0c 0%,#111113 100%)}
25
24
  body{font-family:Segoe UI,sans-serif;color:#e4e4e7;text-align:center;padding:24px}
26
25
  .logo{width:80px;height:80px;margin-bottom:8px}
27
- .title{font-size:28px;font-weight:700;background:linear-gradient(135deg,#eab308 0%,#fbbf24 50%,#f59e0b 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-bottom:2px}
26
+ .title{font-size:28px;font-weight:700;color:#eab308;margin-bottom:2px}
28
27
  .tagline{font-size:10px;color:#fcd34d;font-weight:600;letter-spacing:1px;text-transform:uppercase;margin-bottom:4px}
29
28
  .desc{font-size:10px;color:#636366;margin-bottom:14px}
30
29
  .loading{font-size:12px;color:#636366;margin-bottom:6px}
31
30
  .dots{margin-bottom:20px}
32
31
  .dot{display:inline-block;width:6px;height:6px;background:#eab308;border-radius:50%;margin:0 3px}
33
32
  .divider{width:80%;height:1px;background:#222;margin:0 auto 14px auto}
34
- .website{display:inline-block;font-size:11px;color:#636366;text-decoration:none;padding:6px 14px;background:#111113;border:1px solid #222;border-radius:5px;margin-bottom:12px;transition:all .2s}
35
- .website:hover{color:#eab308;border-color:#eab308;background:rgba(234,179,8,0.05)}
36
- .promo{font-size:9px;color:#4a4a4f;margin-bottom:4px}
37
- .brand{font-size:11px;color:#636366;font-weight:500;text-decoration:none}
38
- .brand:hover{color:#fcd34d}
39
- .sub{font-size:9px;color:#4a4a4f;margin-top:2px}
33
+ .website{font-size:11px;color:#eab308;text-decoration:none;transition:color .2s}
34
+ .website:hover{color:#fcd34d}
40
35
  </style>
41
36
  <script language="javascript">
42
- var w = 280, h = 385;
37
+ var w = 280, h = 360;
43
38
  window.resizeTo(w, h);
44
39
  window.moveTo((screen.availWidth - w) / 2, (screen.availHeight - h) / 2);
45
40
 
@@ -55,10 +50,9 @@
55
50
  if (!closing && fso.FileExists(readyFile)) {
56
51
  closing = true;
57
52
  try { fso.DeleteFile(readyFile); } catch(e) {}
58
- document.body.className = 'fade-out';
59
- setTimeout(function() { window.close(); }, 400);
53
+ window.close();
60
54
  }
61
- }, 200);
55
+ }, 50);
62
56
 
63
57
  // Animate dots
64
58
  var idx = 0;
@@ -88,9 +82,6 @@
88
82
  <div id="d3" class="dot"></div>
89
83
  </div>
90
84
  <div class="divider"></div>
91
- <a class="website" href="https://piclet.app" target="_blank">piclet.app</a>
92
- <div class="promo">A free tool by</div>
93
- <a class="brand" href="https://spark-games.co.uk" target="_blank">spark-games.co.uk</a>
94
- <div class="sub">Game Dev Tools & Resources</div>
85
+ <a class="website" href="#" onclick="shell.Run('https://piclet.app');return false">piclet.app</a>
95
86
  </body>
96
87
  </html>