opentwig 1.0.0

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,313 @@
1
+ /* Import Google Fonts */
2
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
3
+
4
+ /* css reset */
5
+ * {
6
+ margin: 0;
7
+ padding: 0;
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ /* Dark theme background */
12
+ body {
13
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
14
+ min-height: 100vh;
15
+ font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
16
+ color: #e8e8e8;
17
+ }
18
+
19
+ .app-bg {
20
+ min-height: 100vh;
21
+ display: flex;
22
+ align-items: stretch;
23
+ justify-content: center;
24
+ padding: 28px 16px;
25
+ }
26
+
27
+ .card {
28
+ width: 100%;
29
+ max-width: 480px;
30
+ background: rgba(30, 30, 46, 0.95);
31
+ backdrop-filter: blur(20px);
32
+ border-radius: 24px;
33
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1);
34
+ padding-bottom: 28px;
35
+ min-height: calc(100vh - 56px);
36
+ display: flex;
37
+ flex-direction: column;
38
+ }
39
+
40
+ .top-actions {
41
+ display: flex;
42
+ justify-content: flex-end;
43
+ padding: 12px 16px;
44
+ }
45
+
46
+ .icon-btn {
47
+ width: 36px;
48
+ height: 36px;
49
+ border: 1px solid rgba(255, 255, 255, 0.2);
50
+ background: rgba(255, 255, 255, 0.1);
51
+ border-radius: 9999px;
52
+ display: inline-flex;
53
+ align-items: center;
54
+ justify-content: center;
55
+ cursor: pointer;
56
+ transition: all 0.2s ease;
57
+ }
58
+
59
+ .icon-btn:hover {
60
+ background: rgba(255, 255, 255, 0.2);
61
+ border-color: rgba(255, 255, 255, 0.3);
62
+ transform: translateY(-1px);
63
+ }
64
+
65
+ .icon-btn svg {
66
+ stroke: white;
67
+ }
68
+
69
+ .profile {
70
+ display: flex;
71
+ align-items: center;
72
+ flex-direction: column;
73
+ text-align: center;
74
+ padding: 8px 24px 16px;
75
+ }
76
+
77
+ .avatar {
78
+ width: 96px;
79
+ height: 96px;
80
+ border-radius: 9999px;
81
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
82
+ border: 3px solid rgba(255, 255, 255, 0.2);
83
+ box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
84
+ }
85
+
86
+ .avatar img {
87
+ width: 100%;
88
+ height: 100%;
89
+ object-fit: cover;
90
+ border-radius: 9999px;
91
+ }
92
+
93
+ .name {
94
+ font-weight: 700;
95
+ margin-top: 12px;
96
+ font-size: 22px;
97
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
98
+ -webkit-background-clip: text;
99
+ -webkit-text-fill-color: transparent;
100
+ background-clip: text;
101
+ }
102
+
103
+ .tagline {
104
+ margin-top: 4px;
105
+ color: #a0a0a0;
106
+ font-size: 14px;
107
+ }
108
+
109
+ .links {
110
+ padding: 16px;
111
+ display: grid;
112
+ gap: 12px;
113
+ }
114
+
115
+ .link-item {
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: space-between;
119
+ text-decoration: none;
120
+ background: rgba(255, 255, 255, 0.05);
121
+ color: inherit;
122
+ border-radius: 16px;
123
+ border: 1px solid rgba(255, 255, 255, 0.1);
124
+ padding: 16px;
125
+ transition: all 0.3s ease;
126
+ backdrop-filter: blur(10px);
127
+ }
128
+
129
+ .link-item:hover {
130
+ transform: translateY(-2px);
131
+ box-shadow: 0 12px 24px rgba(102, 126, 234, 0.2);
132
+ background: rgba(255, 255, 255, 0.1);
133
+ border-color: rgba(102, 126, 234, 0.3);
134
+ }
135
+
136
+ .footer-links {
137
+ display: flex;
138
+ justify-content: center;
139
+ gap: 10px;
140
+ color: #888;
141
+ font-size: 12px;
142
+ margin-top: auto;
143
+ padding-top: 18px;
144
+ }
145
+
146
+ .footer-links a {
147
+ color: inherit;
148
+ text-decoration: none;
149
+ transition: color 0.2s ease;
150
+ }
151
+
152
+ .footer-links a:hover {
153
+ color: #667eea;
154
+ text-decoration: underline;
155
+ }
156
+
157
+ /* QR Code - Desktop Only */
158
+ .qr-desktop {
159
+ display: none;
160
+ }
161
+
162
+ @media (min-width: 1022px) {
163
+ .qr-desktop {
164
+ display: block;
165
+ position: fixed;
166
+ bottom: 20px;
167
+ right: 20px;
168
+ z-index: 1000;
169
+ }
170
+
171
+ .qr-content {
172
+ text-align: center;
173
+ background: rgba(30, 30, 46, 0.95);
174
+ backdrop-filter: blur(20px);
175
+ border-radius: 16px;
176
+ padding: 16px;
177
+ border: 1px solid rgba(255, 255, 255, 0.1);
178
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
179
+ }
180
+
181
+ .qr-content h3 {
182
+ font-size: 14px;
183
+ font-weight: 500;
184
+ color: #e8e8e8;
185
+ margin-bottom: -10px;
186
+ }
187
+
188
+ .qr-content img {
189
+ width: 140px;
190
+ height: 140px;
191
+ border-radius: 8px;
192
+ }
193
+ }
194
+
195
+ /* Modal Dialog Styles */
196
+ .modal-dialog {
197
+ position: fixed;
198
+ top: 0;
199
+ left: 0;
200
+ width: 100%;
201
+ height: 100%;
202
+ max-width: 100%;
203
+ max-height: 100%;
204
+ background: rgba(0, 0, 0, 0.7);
205
+ border: none;
206
+ padding: 0;
207
+ margin: 0;
208
+ z-index: 1000;
209
+ display: none;
210
+ align-items: center;
211
+ justify-content: center;
212
+ }
213
+
214
+ .modal-dialog[open] {
215
+ display: flex;
216
+ }
217
+
218
+ .modal-dialog::backdrop {
219
+ background: rgba(0, 0, 0, 0.7);
220
+ }
221
+
222
+ .modal-content {
223
+ background: rgba(30, 30, 46, 0.95);
224
+ backdrop-filter: blur(20px);
225
+ border-radius: 16px;
226
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1);
227
+ max-width: 90vw;
228
+ max-height: 90vh;
229
+ width: 100%;
230
+ max-width: 500px;
231
+ display: flex;
232
+ flex-direction: column;
233
+ overflow: hidden;
234
+ }
235
+
236
+ .modal-header {
237
+ display: flex;
238
+ align-items: center;
239
+ justify-content: space-between;
240
+ padding: 20px 24px 16px;
241
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
242
+ }
243
+
244
+ .modal-header h3 {
245
+ font-size: 18px;
246
+ font-weight: 600;
247
+ color: #e8e8e8;
248
+ margin: 0;
249
+ }
250
+
251
+ .modal-close {
252
+ background: none;
253
+ border: none;
254
+ cursor: pointer;
255
+ padding: 8px;
256
+ border-radius: 6px;
257
+ color: #a0a0a0;
258
+ display: flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ transition: all 0.2s ease;
262
+ }
263
+
264
+ .modal-close:hover {
265
+ background: rgba(255, 255, 255, 0.1);
266
+ color: #e8e8e8;
267
+ }
268
+
269
+ .modal-close:active {
270
+ transform: scale(0.95);
271
+ }
272
+
273
+ .modal-body {
274
+ padding: 20px 24px 24px;
275
+ overflow-y: auto;
276
+ flex: 1;
277
+ }
278
+
279
+ .modal-body p {
280
+ margin: 0;
281
+ line-height: 1.6;
282
+ color: #c0c0c0;
283
+ font-size: 14px;
284
+ }
285
+
286
+ /* Animation for modal appearance */
287
+ .modal-dialog[open] {
288
+ animation: modalFadeIn 0.2s ease-out;
289
+ }
290
+
291
+ .modal-content {
292
+ animation: modalSlideIn 0.2s ease-out;
293
+ }
294
+
295
+ @keyframes modalFadeIn {
296
+ from {
297
+ opacity: 0;
298
+ }
299
+ to {
300
+ opacity: 1;
301
+ }
302
+ }
303
+
304
+ @keyframes modalSlideIn {
305
+ from {
306
+ transform: scale(0.9) translateY(-20px);
307
+ opacity: 0;
308
+ }
309
+ to {
310
+ transform: scale(1) translateY(0);
311
+ opacity: 1;
312
+ }
313
+ }
@@ -0,0 +1,7 @@
1
+ module.exports = function() {
2
+ return `
3
+ <div class="avatar">
4
+ <img src="./avatar.png" alt="Avatar" />
5
+ </div>
6
+ `;
7
+ }
@@ -0,0 +1,20 @@
1
+ module.exports = function(link, index) {
2
+ return `
3
+ <a href="#" onclick="event.preventDefault(); document.getElementById('dialog${index}').showModal(); return false;">${link.title}</a>
4
+ <dialog id="dialog${index}" class="modal-dialog">
5
+ <div class="modal-content" onclick="event.stopPropagation();">
6
+ <div class="modal-header">
7
+ <h3>${link.title}</h3>
8
+ <button class="modal-close" onclick="document.getElementById('dialog${index}').close(); return false;" aria-label="Close dialog">
9
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
10
+ <path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
11
+ </svg>
12
+ </button>
13
+ </div>
14
+ <div class="modal-body">
15
+ <p>${link.content}</p>
16
+ </div>
17
+ </div>
18
+ </dialog>
19
+ `;
20
+ }
@@ -0,0 +1,5 @@
1
+ module.exports = function(link) {
2
+ return `
3
+ <a href="${link.url}">${link.title}</a>
4
+ `;
5
+ }
@@ -0,0 +1,7 @@
1
+ module.exports = function({link}) {
2
+ return `
3
+ <a class="link-item" href="${link.url}" target="_blank" rel="noopener">
4
+ <span>${link.title}</span>
5
+ </a>
6
+ `;
7
+ }
@@ -0,0 +1,10 @@
1
+ module.exports = function() {
2
+ return `
3
+ <div class="qr-desktop">
4
+ <div class="qr-content">
5
+ <h3>View on mobile</h3>
6
+ <img src="./qr.svg" alt="QR Code" />
7
+ </div>
8
+ </div>
9
+ `;
10
+ }
@@ -0,0 +1,11 @@
1
+ module.exports = function({share}) {
2
+ if (!share) {
3
+ return '';
4
+ }
5
+
6
+ return `
7
+ <button class="icon-btn" aria-label="Share" onclick="() => navigator.share({ url: location.href, title:'${share.title}', text:'${share.text}' })">
8
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12v7a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-7"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>
9
+ </button>
10
+ `;
11
+ }
@@ -0,0 +1,53 @@
1
+ const avatarComponent = require('./components/avatar');
2
+ const linkComponent = require('./components/link');
3
+ const footerLinkComponent = require('./components/footer-link');
4
+ const shareButtonComponent = require('./components/share-button');
5
+ const qrComponent = require('./components/qr');
6
+ const dialogComponent = require('./components/dialog');
7
+
8
+ module.exports = function({title, url, name, content, avatar, links, footerLinks, share}) {
9
+ return `<!DOCTYPE html>
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="UTF-8" />
13
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
14
+ <title>${title}</title>
15
+ <meta name="description" content="${content}">
16
+ <link rel="stylesheet" href="./style.css">
17
+ <meta property="og:title" content="tufantunc | Twitter | Linktree"/>
18
+ <meta property="og:description" content="Merhaba."/>
19
+ <meta property="og:url" content="${url}"/>
20
+ <meta property="og:image" content="${url}/og-image.png"/>
21
+ </head>
22
+ <body>
23
+ <div class="app-bg">
24
+ <div class="card">
25
+ <div class="top-actions">
26
+ ${shareButtonComponent({share})}
27
+ </div>
28
+
29
+ <div class="profile">
30
+ ${avatarComponent({avatar})}
31
+ <div class="name">${name}</div>
32
+ <div class="tagline">${content}</div>
33
+ </div>
34
+
35
+ <div class="links">
36
+ ${links.map(link => linkComponent({link})).join('')}
37
+ </div>
38
+
39
+ <div class="footer-links">
40
+ ${footerLinks.map((link, index) => {
41
+ if(link.hasOwnProperty('content')) {
42
+ return dialogComponent(link, index);
43
+ } else {
44
+ return footerLinkComponent(link);
45
+ }
46
+ }).join('<span class="dot">•</span>')}
47
+ </div>
48
+ </div>
49
+ ${qrComponent()}
50
+ </div>
51
+ </body>
52
+ </html>`;
53
+ };
@@ -0,0 +1,277 @@
1
+ /* css reset */
2
+ * {
3
+ margin: 0;
4
+ padding: 0;
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ /* background like Linktree preview */
9
+ body {
10
+ background: #c7c9cb;
11
+ min-height: 100vh;
12
+ font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica Neue, Arial, "Apple Color Emoji", "Segoe UI Emoji";
13
+ color: #0a0a0a;
14
+ }
15
+
16
+ .app-bg {
17
+ min-height: 100vh;
18
+ display: flex;
19
+ align-items: stretch;
20
+ justify-content: center;
21
+ padding: 28px 16px;
22
+ }
23
+
24
+ .card {
25
+ width: 100%;
26
+ max-width: 480px;
27
+ background: #eceff3;
28
+ border-radius: 20px;
29
+ box-shadow: 0 8px 30px rgba(0,0,0,0.12);
30
+ padding-bottom: 28px;
31
+ min-height: calc(100vh - 56px);
32
+ display: flex;
33
+ flex-direction: column;
34
+ }
35
+
36
+ .top-actions {
37
+ display: flex;
38
+ justify-content: flex-end;
39
+ padding: 12px 16px;
40
+ }
41
+
42
+ .icon-btn {
43
+ width: 36px;
44
+ height: 36px;
45
+ border: 1px solid rgba(0,0,0,0.15);
46
+ background: #fff;
47
+ border-radius: 9999px;
48
+ display: inline-flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ cursor: pointer;
52
+ }
53
+
54
+ .profile {
55
+ display: flex;
56
+ align-items: center;
57
+ flex-direction: column;
58
+ text-align: center;
59
+ padding: 8px 24px 16px;
60
+ }
61
+
62
+ .avatar {
63
+ width: 96px;
64
+ height: 96px;
65
+ border-radius: 9999px;
66
+ background: #dedede;
67
+ }
68
+
69
+ .avatar img {
70
+ width: 100%;
71
+ height: 100%;
72
+ object-fit: cover;
73
+ border-radius: 9999px;
74
+ }
75
+
76
+ .name {
77
+ font-weight: 700;
78
+ margin-top: 12px;
79
+ font-size: 22px;
80
+ }
81
+
82
+ .tagline {
83
+ margin-top: 4px;
84
+ color: #5a5a5a;
85
+ font-size: 14px;
86
+ }
87
+
88
+ .links {
89
+ padding: 16px;
90
+ display: grid;
91
+ gap: 12px;
92
+ }
93
+
94
+ .link-item {
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: space-between;
98
+ text-decoration: none;
99
+ background: #fff;
100
+ color: inherit;
101
+ border-radius: 12px;
102
+ border: 1px solid rgba(0,0,0,0.1);
103
+ padding: 16px;
104
+ transition: transform .06s ease, box-shadow .12s ease;
105
+ }
106
+
107
+ .link-item:hover {
108
+ transform: translateY(-1px);
109
+ box-shadow: 0 6px 16px rgba(0,0,0,0.08);
110
+ }
111
+
112
+ .footer-links {
113
+ display: flex;
114
+ justify-content: center;
115
+ gap: 10px;
116
+ color: #5a5a5a;
117
+ font-size: 12px;
118
+ margin-top: auto;
119
+ padding-top: 18px;
120
+ }
121
+
122
+ .footer-links a {
123
+ color: inherit;
124
+ text-decoration: none;
125
+ }
126
+
127
+ .footer-links a:hover { text-decoration: underline; }
128
+
129
+ /* QR Code - Desktop Only */
130
+ .qr-desktop {
131
+ display: none;
132
+ }
133
+
134
+ @media (min-width: 1022px) {
135
+ .qr-desktop {
136
+ display: block;
137
+ position: fixed;
138
+ bottom: 20px;
139
+ right: 20px;
140
+ z-index: 1000;
141
+ }
142
+
143
+ .qr-content {
144
+ text-align: center;
145
+ }
146
+
147
+ .qr-content h3 {
148
+ font-size: 14px;
149
+ font-weight: 500;
150
+ color: #0a0a0a;
151
+ margin-bottom: -10px;
152
+ }
153
+
154
+ .qr-content img {
155
+ width: 140px;
156
+ height: 140px;
157
+ }
158
+ }
159
+
160
+ /* Modal Dialog Styles */
161
+ .modal-dialog {
162
+ position: fixed;
163
+ top: 0;
164
+ left: 0;
165
+ width: 100%;
166
+ height: 100%;
167
+ max-width: 100%;
168
+ max-height: 100%;
169
+ background: rgba(0, 0, 0, 0.5);
170
+ border: none;
171
+ padding: 0;
172
+ margin: 0;
173
+ z-index: 1000;
174
+ display: none;
175
+ align-items: center;
176
+ justify-content: center;
177
+ }
178
+
179
+ .modal-dialog[open] {
180
+ display: flex;
181
+ }
182
+
183
+ .modal-dialog::backdrop {
184
+ background: rgba(0, 0, 0, 0.5);
185
+ }
186
+
187
+ .modal-content {
188
+ background: #fff;
189
+ border-radius: 12px;
190
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
191
+ max-width: 90vw;
192
+ max-height: 90vh;
193
+ width: 100%;
194
+ max-width: 500px;
195
+ display: flex;
196
+ flex-direction: column;
197
+ overflow: hidden;
198
+ }
199
+
200
+ .modal-header {
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: space-between;
204
+ padding: 20px 24px 16px;
205
+ border-bottom: 1px solid #e5e5e5;
206
+ }
207
+
208
+ .modal-header h3 {
209
+ font-size: 18px;
210
+ font-weight: 600;
211
+ color: #0a0a0a;
212
+ margin: 0;
213
+ }
214
+
215
+ .modal-close {
216
+ background: none;
217
+ border: none;
218
+ cursor: pointer;
219
+ padding: 8px;
220
+ border-radius: 6px;
221
+ color: #666;
222
+ display: flex;
223
+ align-items: center;
224
+ justify-content: center;
225
+ transition: all 0.2s ease;
226
+ }
227
+
228
+ .modal-close:hover {
229
+ background: #f5f5f5;
230
+ color: #333;
231
+ }
232
+
233
+ .modal-close:active {
234
+ transform: scale(0.95);
235
+ }
236
+
237
+ .modal-body {
238
+ padding: 20px 24px 24px;
239
+ overflow-y: auto;
240
+ flex: 1;
241
+ }
242
+
243
+ .modal-body p {
244
+ margin: 0;
245
+ line-height: 1.6;
246
+ color: #333;
247
+ font-size: 14px;
248
+ }
249
+
250
+ /* Animation for modal appearance */
251
+ .modal-dialog[open] {
252
+ animation: modalFadeIn 0.2s ease-out;
253
+ }
254
+
255
+ .modal-content {
256
+ animation: modalSlideIn 0.2s ease-out;
257
+ }
258
+
259
+ @keyframes modalFadeIn {
260
+ from {
261
+ opacity: 0;
262
+ }
263
+ to {
264
+ opacity: 1;
265
+ }
266
+ }
267
+
268
+ @keyframes modalSlideIn {
269
+ from {
270
+ transform: scale(0.9) translateY(-20px);
271
+ opacity: 0;
272
+ }
273
+ to {
274
+ transform: scale(1) translateY(0);
275
+ opacity: 1;
276
+ }
277
+ }
@@ -0,0 +1,4 @@
1
+ // Minimal theme - uses default theme completely
2
+ const defaultTheme = require('../default/index.js');
3
+
4
+ module.exports = defaultTheme;