Changeset 3783861
- Timestamp:
- 11/04/2026 12:01:29 PM (38 hours ago)
- Location:
- awosign
- Files:
-
- 17 added
- 7 edited
-
assets/banner-1544x500.png (modified) ( previous)
-
assets/banner-772x250.png (modified) ( previous)
-
assets/blueprints (added)
-
assets/blueprints/blueprint.json (added)
-
assets/screenshot-1.png (added)
-
assets/screenshot-2.png (added)
-
assets/screenshot-3.png (added)
-
tags/1.3.0 (added)
-
tags/1.3.0/LICENSE.txt (added)
-
tags/1.3.0/assets (added)
-
tags/1.3.0/assets/designer.css (added)
-
tags/1.3.0/assets/designer.js (added)
-
tags/1.3.0/assets/qrcode-LICENSE.txt (added)
-
tags/1.3.0/assets/qrcode.js (added)
-
tags/1.3.0/awosign.php (added)
-
tags/1.3.0/designer-view.php (added)
-
tags/1.3.0/readme.txt (added)
-
tags/1.3.0/uninstall.php (added)
-
trunk/assets/designer.css (modified) (1 diff)
-
trunk/assets/designer.js (modified) (6 diffs)
-
trunk/awosign.php (modified) (3 diffs)
-
trunk/designer-view.php (modified) (1 diff)
-
trunk/readme.txt (modified) (1 diff)
-
trunk/uninstall.php (added)
Legend:
- Unmodified
- Added
- Removed
-
awosign/trunk/assets/designer.css
r3783841 r3783861 1 /* AwoSign v1.3.0 — GPL-2.0-or-later */ 1 2 #awosign-root { 2 font-family:Arial,Helvetica,sans-serif; 3 color-scheme:dark; 4 --brand:#172e49; 5 --accent:#e7b52e 6 } 3 --brand: #172e49; 4 --accent: #e7b52e; 5 --as-bg: #09131f; 6 --as-panel: #182a3e; 7 --as-panel-border: #304963; 8 --as-stage: #101e2e; 9 --as-stage-border: #4c6680; 10 --as-input-bg: #102032; 11 --as-input-border: #47617a; 12 --as-text: #eaf2fc; 13 --as-muted: #91abc5; 14 --as-hint: #b9cee3; 15 --as-header-bg: #172b43; 16 font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; 17 color-scheme: dark; 18 margin: 20px 20px 20px 0; 19 min-height: 80vh; 20 background: var(--as-bg); 21 color: var(--as-text); 22 border-radius: 14px; 23 overflow: hidden; 24 transition: background-color 0.2s ease, color 0.2s ease; 25 } 26 27 /* Light Mode: Explicit or Automatic via System/Page */ 28 #awosign-root[data-theme="light"], 29 html:not(.dark):not([data-theme="dark"]) body:not(.dark):not(.dark-mode):not(.wp-dark-mode-active) #awosign-root[data-theme="auto"] { 30 --as-bg: #edf2f8; 31 --as-panel: #ffffff; 32 --as-panel-border: #d2dfed; 33 --as-stage: #f3f6fa; 34 --as-stage-border: #b8c9dc; 35 --as-input-bg: #ffffff; 36 --as-input-border: #bdcad9; 37 --as-text: #172e49; 38 --as-muted: #526477; 39 --as-hint: #3b536e; 40 --as-header-bg: #102438; 41 color-scheme: light; 42 } 43 44 @media (prefers-color-scheme: dark) { 45 #awosign-root[data-theme="auto"] { 46 --as-bg: #09131f; 47 --as-panel: #182a3e; 48 --as-panel-border: #304963; 49 --as-stage: #101e2e; 50 --as-stage-border: #4c6680; 51 --as-input-bg: #102032; 52 --as-input-border: #47617a; 53 --as-text: #eaf2fc; 54 --as-muted: #91abc5; 55 --as-hint: #b9cee3; 56 --as-header-bg: #172b43; 57 color-scheme: dark; 58 } 59 } 60 7 61 #awosign-root * { 8 box-sizing:border-box 9 } 10 #awosign-root { 11 margin:0; 12 background:#09131f; 13 color:#eaf2fc 14 } 62 box-sizing: border-box; 63 } 64 15 65 #awosign-root header { 16 background:#172b43; 17 padding:18px 26px; 18 display:flex; 19 align-items:center; 20 justify-content:space-between; 21 gap:16px; 22 flex-wrap:wrap 23 } 66 background: var(--as-header-bg); 67 color: #ffffff; 68 padding: 18px 26px; 69 display: flex; 70 align-items: center; 71 justify-content: space-between; 72 gap: 16px; 73 flex-wrap: wrap; 74 } 75 24 76 #awosign-root header h1 { 25 margin:0; 26 font-size:25px 27 } 77 margin: 0; 78 font-size: 24px; 79 color: #ffffff; 80 } 81 28 82 #awosign-root header small { 29 color:#b8c8dc 30 } 83 color: #b8c8dc; 84 display: block; 85 margin-top: 4px; 86 } 87 88 #awosign-root header label { 89 color: #ffffff; 90 margin-top: 0; 91 } 92 93 #awosign-root header select { 94 background: #102032; 95 color: #ffffff; 96 border-color: #47617a; 97 } 98 31 99 #awosign-root main { 32 padding:22px; 33 display:grid; 34 grid-template-columns:minmax(290px,390px) minmax(0,1fr); 35 gap:20px 36 } 100 padding: 22px; 101 display: grid; 102 grid-template-columns: minmax(300px, 400px) minmax(0, 1fr); 103 gap: 20px; 104 align-items: start; 105 } 106 37 107 #awosign-root .panel { 38 background:#182a3e; 39 border:1px solid #304963; 40 border-radius:14px; 41 padding:22px 42 } 108 background: var(--as-panel); 109 border: 1px solid var(--as-panel-border); 110 border-radius: 14px; 111 padding: 22px; 112 } 113 43 114 #awosign-root h2 { 44 font-size:18px; 45 margin:0 0 17px 46 } 115 font-size: 18px; 116 margin: 0 0 14px; 117 color: inherit; 118 } 119 47 120 #awosign-root h3 { 48 font-size:14px; 49 margin:18px 0 9px 50 } 121 font-size: 14px; 122 margin: 16px 0 9px; 123 color: inherit; 124 } 125 51 126 #awosign-root label { 52 display:block; 53 font-size:12px; 54 font-weight:bold; 55 margin:12px 0 5px 56 } 57 #awosign-root input,#awosign-root select,#awosign-root textarea { 58 width:100%; 59 border:1px solid #47617a; 60 border-radius:7px; 61 background:#102032; 62 color:#fff; 63 padding:10px; 64 font:inherit; 65 font-size:13px 66 } 67 #awosign-root input[type=color] { 68 height:40px; 69 padding:3px 70 } 71 #awosign-root input[type=checkbox] { 72 width:auto; 73 accent-color:#e7b52e 74 } 75 #awosign-root input[type=file] { 76 font-size:12px 77 } 127 display: block; 128 font-size: 12.5px; 129 font-weight: 600; 130 margin: 11px 0 5px; 131 } 132 133 #awosign-root .as-inline-toggle { 134 display: flex; 135 align-items: center; 136 gap: 8px; 137 cursor: pointer; 138 margin: 10px 0; 139 } 140 141 #awosign-root input, 142 #awosign-root select, 143 #awosign-root textarea { 144 width: 100%; 145 border: 1px solid var(--as-input-border); 146 border-radius: 7px; 147 background: var(--as-input-bg); 148 color: var(--as-text); 149 padding: 9px 10px; 150 font: inherit; 151 font-size: 13px; 152 } 153 154 #awosign-root input[type="color"] { 155 height: 40px; 156 padding: 3px; 157 cursor: pointer; 158 } 159 160 #awosign-root input[type="checkbox"] { 161 width: auto; 162 accent-color: #e7b52e; 163 } 164 165 #awosign-root input[type="file"] { 166 font-size: 12px; 167 } 168 78 169 #awosign-root .two { 79 display:grid; 80 grid-template-columns:1fr 1fr; 81 gap:12px 82 } 170 display: grid; 171 grid-template-columns: 1fr 1fr; 172 gap: 12px; 173 } 174 83 175 #awosign-root .social { 84 display:grid; 85 grid-template-columns:repeat(2,minmax(0,1fr)); 86 gap:8px 87 } 176 display: grid; 177 grid-template-columns: repeat(2, minmax(0, 1fr)); 178 gap: 8px; 179 } 180 88 181 #awosign-root .social label { 89 display:flex; 90 align-items:center; 91 gap:7px; 92 font-size:12px; 93 margin:0; 94 background:#102032; 95 border-radius:7px; 96 padding:9px 97 } 182 display: flex; 183 align-items: center; 184 gap: 7px; 185 font-size: 12px; 186 margin: 0; 187 background: var(--as-input-bg); 188 border: 1px solid var(--as-input-border); 189 border-radius: 7px; 190 padding: 8px 10px; 191 cursor: pointer; 192 } 193 98 194 #awosign-root .tabs { 99 display:flex; 100 flex-wrap:wrap; 101 gap:8px; 102 margin-bottom:18px 103 } 195 display: flex; 196 flex-wrap: wrap; 197 gap: 8px; 198 margin-bottom: 18px; 199 } 200 104 201 #awosign-root button { 105 border:1px solid #54708e; 106 background:#25486d; 107 color:white; 108 padding:10px 13px; 109 border-radius:8px; 110 cursor:pointer; 111 font-weight:bold 112 } 202 border: 1px solid #415e7c; 203 background: #1d3b5a; 204 color: #ffffff; 205 padding: 9px 14px; 206 border-radius: 8px; 207 cursor: pointer; 208 font-weight: 600; 209 font-size: 13px; 210 transition: background 0.15s ease; 211 } 212 213 #awosign-root button:hover { 214 background: #2b527a; 215 } 216 217 #awosign-root button.as-btn-primary { 218 background: #1a633f; 219 border-color: #237d50; 220 } 221 222 #awosign-root button.as-btn-primary:hover { 223 background: #237d50; 224 } 225 226 #awosign-root button.as-btn-accent { 227 background: #9a6f15; 228 border-color: #b5831a; 229 } 230 231 #awosign-root button.as-btn-accent:hover { 232 background: #b5831a; 233 } 234 113 235 #awosign-root button.active { 114 background:#e7b52e; 115 color:#102032; 116 border-color:#e7b52e 117 } 236 background: #e7b52e; 237 color: #102032; 238 border-color: #e7b52e; 239 } 240 241 #awosign-root button:focus-visible, 242 #awosign-root input:focus-visible, 243 #awosign-root select:focus-visible, 244 #awosign-root textarea:focus-visible { 245 outline: 3px solid #e7b52e; 246 outline-offset: 2px; 247 } 248 118 249 #awosign-root .stage { 119 background:#101e2e; 120 border:1px dashed #4c6680; 121 border-radius:12px; 122 padding:24px; 123 overflow:auto 124 } 250 background: var(--as-stage); 251 border: 1px dashed var(--as-stage-border); 252 border-radius: 12px; 253 padding: 24px; 254 overflow: auto; 255 } 256 125 257 #awosign-root .stage h3 { 126 margin: 0 0 13px;127 color:#c8d9ee 128 } 258 margin: 0 0 13px; 259 } 260 129 261 #awosign-root .signature { 130 background:white; 131 color:#15263c; 132 width:600px; 133 max-width:100%; 134 padding:18px; 135 border-radius:2px; 136 font-family:Arial,sans-serif 137 } 262 background: #ffffff; 263 color: #15263c; 264 width: 600px; 265 max-width: 100%; 266 padding: 18px; 267 border-radius: 4px; 268 font-family: Arial, Helvetica, sans-serif; 269 box-shadow: 0 4px 18px rgba(0, 0, 0, 0.12); 270 } 271 138 272 #awosign-root .sigrow { 139 display:flex; 140 align-items:center; 141 gap:20px 142 } 273 display: flex; 274 align-items: center; 275 gap: 20px; 276 } 277 143 278 #awosign-root .siglogo { 144 width:170px; 145 min-width:170px; 146 text-align:center 147 } 279 width: 170px; 280 min-width: 170px; 281 text-align: center; 282 } 283 148 284 #awosign-root .siglogo img { 149 max-width:100%; 150 max-height:110px; 151 object-fit:contain 152 } 285 max-width: 100%; 286 max-height: 110px; 287 object-fit: contain; 288 } 289 290 #awosign-root .siglogo div { 291 overflow-wrap: anywhere; 292 } 293 153 294 #awosign-root .siginfo { 154 border-left:2px solid var(--accent); 155 padding-left:17px; 156 line-height:1.55; 157 font-size:12px 158 } 295 border-left: 2px solid var(--accent); 296 padding-left: 17px; 297 line-height: 1.55; 298 font-size: 12px; 299 } 300 159 301 #awosign-root .siginfo strong { 160 font-size:19px 161 } 302 font-size: 19px; 303 } 304 162 305 #awosign-root .sigaddress { 163 font-size:11px; 164 margin-top:12px; 165 border-top:1px solid #ddd; 166 padding-top:9px 167 } 306 font-size: 11px; 307 margin-top: 12px; 308 border-top: 1px solid #dddddd; 309 padding-top: 9px; 310 } 311 312 /* 85 x 55 mm Exact Business Cards (Front & Back) */ 168 313 #awosign-root .cards { 169 display:flex; 170 gap:20px; 171 flex-wrap:wrap 172 } 314 display: flex; 315 gap: 24px; 316 flex-wrap: wrap; 317 align-items: flex-start; 318 } 319 173 320 #awosign-root .cardwrap { 174 width:min(100%,460px) 175 } 176 #awosign-root .card { 177 aspect-ratio:85/55; 178 border-radius:9px; 179 overflow:hidden; 180 padding:24px; 181 position:relative 182 } 183 #awosign-root .front { 184 background:var(--brand); 185 color:white; 186 display:flex; 187 flex-direction:column; 188 align-items:center; 189 justify-content:center; 190 text-align:center; 191 border-top:5px solid var(--accent) 192 } 193 #awosign-root .front img { 194 max-width:55%; 195 max-height:95px; 196 object-fit:contain 197 } 198 #awosign-root .front strong { 199 font-size:22px; 200 margin-top:12px 201 } 202 #awosign-root .back { 203 background:white; 204 color:#15263c; 205 border-left:6px solid var(--accent); 206 display:flex; 207 align-items:center; 208 justify-content:space-between; 209 gap:12px 210 } 211 #awosign-root .back .details { 212 min-width:0; 213 overflow-wrap:anywhere; 214 font-size:12px; 215 line-height:1.6 216 } 217 #awosign-root .back strong { 218 font-size:20px 219 } 220 #awosign-root .qr { 221 background:white; 222 padding:5px; 223 border-radius:5px; 224 width:105px; 225 height:105px; 226 object-fit:contain; 227 flex-shrink:0 228 } 229 #awosign-root .socialicons { 230 display:flex; 231 gap:5px; 232 flex-wrap:wrap; 233 margin-top:8px 234 } 235 #awosign-root .socialicons span { 236 background:var(--brand); 237 color:#fff; 238 font-size:10px; 239 padding:5px 7px; 240 border-radius:4px; 241 font-weight:bold 242 } 321 display: flex; 322 flex-direction: column; 323 gap: 8px; 324 } 325 326 #awosign-root .card, 327 #as-print-host .card { 328 width: 85mm; 329 height: 55mm; 330 box-sizing: border-box; 331 border-radius: 0; 332 overflow: hidden; 333 padding: 5.5mm; 334 position: relative; 335 font-family: Arial, Helvetica, sans-serif; 336 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.18); 337 } 338 339 #awosign-root .card.show-crop-guide { 340 outline: 1px dashed #7f95ab; 341 outline-offset: -0.2mm; 342 } 343 344 #awosign-root .front, 345 #as-print-host .front { 346 background: var(--brand, #172e49); 347 color: #ffffff; 348 display: flex; 349 flex-direction: column; 350 align-items: center; 351 justify-content: center; 352 text-align: center; 353 border-top: 1.5mm solid var(--accent, #e7b52e); 354 } 355 356 #awosign-root .front img, 357 #as-print-host .front img { 358 max-width: 46mm; 359 max-height: 22mm; 360 object-fit: contain; 361 } 362 363 #awosign-root .front strong, 364 #as-print-host .front strong { 365 font-size: 13pt; 366 margin-top: 2.5mm; 367 } 368 369 #awosign-root .front small, 370 #as-print-host .front small { 371 font-size: 7.5pt; 372 color: #dbe7f5; 373 margin-top: 1mm; 374 } 375 376 #awosign-root .back, 377 #as-print-host .back { 378 background: #ffffff; 379 color: #15263c; 380 border-left: 1.8mm solid var(--accent, #e7b52e); 381 display: flex; 382 align-items: center; 383 justify-content: space-between; 384 gap: 3mm; 385 } 386 387 #awosign-root .back .details, 388 #as-print-host .back .details { 389 min-width: 0; 390 overflow-wrap: anywhere; 391 font-size: 7.4pt; 392 line-height: 1.38; 393 } 394 395 #awosign-root .back strong, 396 #as-print-host .back strong { 397 font-size: 11.5pt; 398 color: #102032; 399 } 400 401 #awosign-root .qr, 402 #as-print-host .qr { 403 background: #ffffff; 404 padding: 1mm; 405 border-radius: 1mm; 406 border: 0.2mm solid #d8e0e8; 407 width: 22mm; 408 height: 22mm; 409 object-fit: contain; 410 flex-shrink: 0; 411 } 412 413 #awosign-root .socialicons, 414 #as-print-host .socialicons { 415 display: flex; 416 gap: 1.2mm; 417 flex-wrap: wrap; 418 margin-top: 1.8mm; 419 } 420 421 #awosign-root .socialicons span, 422 #as-print-host .socialicons span { 423 background: var(--brand, #172e49); 424 color: #ffffff; 425 font-size: 6.5pt; 426 padding: 0.8mm 1.5mm; 427 border-radius: 0.8mm; 428 font-weight: bold; 429 display: inline-block; 430 } 431 243 432 #awosign-root .digital { 244 width:540px; 245 max-width:100%; 246 background:var(--brand); 247 color:white; 248 border-top:5px solid var(--accent); 249 padding:24px; 250 border-radius:12px; 251 display:flex; 252 justify-content:space-between; 253 align-items:center; 254 gap:12px; 255 min-height:310px 256 } 433 width: 540px; 434 max-width: 100%; 435 background: var(--brand); 436 color: #ffffff; 437 border-top: 5px solid var(--accent); 438 padding: 24px; 439 border-radius: 12px; 440 display: flex; 441 justify-content: space-between; 442 align-items: center; 443 gap: 16px; 444 min-height: 290px; 445 box-shadow: 0 6px 24px rgba(0, 0, 0, 0.2); 446 } 447 257 448 #awosign-root .digital img.logo { 258 width:100px; 259 max-height:70px; 260 object-fit:contain 261 } 449 width: 110px; 450 max-height: 70px; 451 object-fit: contain; 452 } 453 262 454 #awosign-root .digital strong { 263 font-size:22px 264 } 455 font-size: 22px; 456 } 457 265 458 #awosign-root .digital .details { 266 line-height:1.7; 267 font-size:13px; 268 overflow-wrap:anywhere 269 } 459 line-height: 1.65; 460 font-size: 13px; 461 overflow-wrap: anywhere; 462 } 463 464 #awosign-root .digital .qr { 465 width: 108px; 466 height: 108px; 467 } 468 469 /* Print & Bulk Duplication Panel */ 470 #awosign-root .as-staff-bar { 471 background: var(--as-input-bg); 472 border: 1px solid var(--as-input-border); 473 border-radius: 10px; 474 padding: 14px; 475 margin-bottom: 14px; 476 } 477 478 #awosign-root .as-staff-header { 479 display: flex; 480 align-items: center; 481 justify-content: space-between; 482 gap: 8px; 483 margin-bottom: 6px; 484 } 485 486 #awosign-root .as-staff-header h2 { 487 margin: 0; 488 font-size: 15px; 489 } 490 491 #awosign-root .as-badge { 492 background: #e7b52e; 493 color: #102032; 494 font-size: 11px; 495 font-weight: 700; 496 padding: 3px 9px; 497 border-radius: 20px; 498 } 499 500 #awosign-root .as-shortcode-box { 501 margin-top: 10px; 502 padding-top: 10px; 503 border-top: 1px dashed var(--as-input-border); 504 } 505 506 #awosign-root .as-shortcode-row, 507 #awosign-root .as-logo-row { 508 display: flex; 509 gap: 8px; 510 align-items: center; 511 } 512 513 #awosign-root .as-shortcode-row input { 514 font-family: monospace; 515 font-size: 12px; 516 } 517 518 #awosign-root .as-logo-row button, 519 #awosign-root .as-shortcode-row button { 520 white-space: nowrap; 521 flex-shrink: 0; 522 } 523 524 #awosign-root .as-file-btn { 525 display: inline-flex; 526 align-items: center; 527 border: 1px solid #415e7c; 528 background: #1d3b5a; 529 color: #ffffff; 530 padding: 9px 14px; 531 border-radius: 8px; 532 cursor: pointer; 533 font-weight: 600; 534 font-size: 13px; 535 margin: 0; 536 } 537 538 #awosign-root .as-print-panel { 539 background: var(--as-input-bg); 540 border: 1px solid var(--as-input-border); 541 border-radius: 10px; 542 padding: 14px 16px; 543 margin-top: 16px; 544 } 545 546 #awosign-root .as-print-panel h4 { 547 margin: 0 0 10px; 548 font-size: 14px; 549 } 550 551 #awosign-root .as-print-grid { 552 display: grid; 553 grid-template-columns: minmax(220px, 1.6fr) minmax(130px, 1fr); 554 gap: 12px; 555 align-items: end; 556 } 557 270 558 #awosign-root .hint { 271 font-size:12px; 272 color:#b9cee3; 273 margin-top:12px 274 } 559 font-size: 12px; 560 color: var(--as-hint); 561 margin-top: 10px; 562 } 563 275 564 #awosign-root .muted { 276 color:#91abc5; 277 font-size:12px 278 } 565 color: var(--as-muted); 566 font-size: 12px; 567 margin-top: 14px; 568 } 569 570 #awosign-root #as-status { 571 font-size: 13px; 572 font-weight: 600; 573 min-height: 18px; 574 margin-top: 10px; 575 color: #e7b52e; 576 } 577 279 578 #awosign-root .hidden { 280 display:none!important 281 } 579 display: none !important; 580 } 581 282 582 #awosign-root .buttons { 283 display:flex; 284 gap:8px; 285 flex-wrap:wrap; 286 margin-top:15px 287 } 583 display: flex; 584 gap: 8px; 585 flex-wrap: wrap; 586 margin-top: 15px; 587 } 588 288 589 #awosign-root a { 289 color:inherit 290 } 291 #awosign-root.light { 292 background:#edf2f8; 293 color:#172e49; 294 color-scheme:light 295 } 296 #awosign-root.light .panel { 297 background:white; 298 border-color:#d2dfed 299 } 300 #awosign-root.light .stage { 301 background:#f3f6fa 302 } 303 #awosign-root.light input,#awosign-root.light select,#awosign-root.light textarea { 304 background:white; 305 color:#172e49; 306 border-color:#bdcad9 307 } 308 #awosign-root.light .social label { 309 background:#edf2f8 310 } 311 #awosign-root.light .stage h3 { 312 color:#243f5d 313 } 314 @media(max-width:950px) { 590 color: inherit; 591 } 592 593 #awosign-root .privacy { 594 padding: 12px 22px; 595 background: #102032; 596 color: #d7e4f5; 597 margin: 0; 598 font-size: 12.5px; 599 } 600 601 /* In-Page Print Host (Replaces popup window.open) */ 602 #as-print-host { 603 display: none; 604 } 605 606 #as-print-host .as-a4-sheet { 607 width: 210mm; 608 height: 297mm; 609 box-sizing: border-box; 610 padding: 11mm 20mm; 611 background: #ffffff; 612 display: grid; 613 grid-template-columns: repeat(2, 85mm); 614 grid-template-rows: repeat(5, 55mm); 615 gap: 0; 616 break-after: page; 617 page-break-after: always; 618 -webkit-print-color-adjust: exact; 619 print-color-adjust: exact; 620 } 621 622 #as-print-host .as-a4-sheet:last-child { 623 break-after: auto; 624 page-break-after: auto; 625 } 626 627 #as-print-host .as-a4-cell { 628 width: 85mm; 629 height: 55mm; 630 position: relative; 631 box-sizing: border-box; 632 } 633 634 #as-print-host .as-a4-cell.has-crop-lines { 635 outline: 0.15mm dashed #9aa8b6; 636 } 637 638 #as-print-host .as-bleed-sheet { 639 width: 97mm; 640 height: 67mm; 641 padding: 6mm; 642 box-sizing: border-box; 643 background: #ffffff; 644 position: relative; 645 break-after: page; 646 page-break-after: always; 647 -webkit-print-color-adjust: exact; 648 print-color-adjust: exact; 649 } 650 651 #as-print-host .as-bleed-sheet:last-child { 652 break-after: auto; 653 page-break-after: auto; 654 } 655 656 #as-print-host .as-bleed-sheet .card { 657 position: relative; 658 z-index: 2; 659 box-shadow: none !important; 660 } 661 662 #as-print-host .as-bleed-bg-front { 663 position: absolute; 664 top: 3mm; 665 left: 3mm; 666 width: 91mm; 667 height: 61mm; 668 background: var(--brand, #172e49); 669 border-top: 4.5mm solid var(--accent, #e7b52e); 670 z-index: 1; 671 } 672 673 #as-print-host .as-bleed-bg-back { 674 position: absolute; 675 top: 3mm; 676 left: 3mm; 677 width: 91mm; 678 height: 61mm; 679 background: #ffffff; 680 border-left: 4.8mm solid var(--accent, #e7b52e); 681 z-index: 1; 682 } 683 684 #as-print-host .as-crop-mark { 685 position: absolute; 686 background: #000000; 687 z-index: 3; 688 } 689 690 #as-print-host .as-crop-tl-h { top: 6mm; left: 0.5mm; width: 4mm; height: 0.2mm; } 691 #as-print-host .as-crop-tr-h { top: 6mm; right: 0.5mm; width: 4mm; height: 0.2mm; } 692 #as-print-host .as-crop-bl-h { bottom: 6mm; left: 0.5mm; width: 4mm; height: 0.2mm; } 693 #as-print-host .as-crop-br-h { bottom: 6mm; right: 0.5mm; width: 4mm; height: 0.2mm; } 694 695 #as-print-host .as-crop-tl-v { top: 0.5mm; left: 6mm; width: 0.2mm; height: 4mm; } 696 #as-print-host .as-crop-tr-v { top: 0.5mm; right: 6mm; width: 0.2mm; height: 4mm; } 697 #as-print-host .as-crop-bl-v { bottom: 0.5mm; left: 6mm; width: 0.2mm; height: 4mm; } 698 #as-print-host .as-crop-br-v { bottom: 0.5mm; right: 6mm; width: 0.2mm; height: 4mm; } 699 700 @media (max-width: 980px) { 315 701 #awosign-root main { 316 grid-template-columns:1fr 317 } 318 #awosign-root .panel { 319 min-width:0 320 } 321 } 322 @media(max-width:560px) { 323 #awosign-root main { 324 padding:10px 325 } 326 #awosign-root .panel { 327 padding:14px 328 } 329 #awosign-root .stage { 330 padding:12px 331 } 332 #awosign-root .two { 333 grid-template-columns:1fr 334 } 335 #awosign-root .sigrow { 336 gap:8px 337 } 338 #awosign-root .siglogo { 339 width:32%; 340 min-width:0 341 } 342 #awosign-root .siginfo { 343 padding-left:9px; 344 font-size:10px 345 } 346 #awosign-root .siginfo strong { 347 font-size:15px 348 } 349 #awosign-root .card { 350 padding:15px 351 } 352 #awosign-root .back strong { 353 font-size:16px 354 } 355 #awosign-root .back .details { 356 font-size:10px 357 } 358 #awosign-root .qr { 359 width:80px; 360 height:80px 361 } 362 #awosign-root .digital { 363 padding:16px; 364 min-height:260px 365 } 366 #awosign-root .digital strong { 367 font-size:18px 368 } 369 } 370 371 372 #awosign-root { 373 margin:20px 20px 20px 0; 374 min-height:80vh 375 } 376 #awosign-root button:focus-visible,#awosign-root input:focus-visible { 377 outline:3px solid #e7b52e; 378 outline-offset:3px 379 } 380 #awosign-root .signature { 381 box-sizing:border-box 382 } 383 #awosign-root p { 384 font-size:13px 385 } 386 #awosign-root .privacy { 387 padding:12px 22px; 388 background:#102032; 389 color:#d7e4f5 390 } 391 #awosign-root h1,#awosign-root h2,#awosign-root h3 { 392 color:inherit 393 } 394 #awosign-root .siglogo div { 395 overflow-wrap:anywhere 396 } 397 @media(max-width:560px) { 398 #awosign-root .sigrow { 399 display:block 400 } 401 #awosign-root .siglogo { 402 width:100%; 403 text-align:left; 404 margin-bottom:12px 405 } 406 #awosign-root .siginfo { 407 border-left:0; 408 border-top:2px solid var(--accent); 409 padding:10px 0 0; 410 font-size:12px 411 } 412 #awosign-root .siginfo strong { 413 font-size:18px 414 } 415 } 702 grid-template-columns: 1fr; 703 } 704 #awosign-root .as-print-grid { 705 grid-template-columns: 1fr; 706 } 707 } 708 709 @media print { 710 html, 711 body { 712 margin: 0 !important; 713 padding: 0 !important; 714 background: #ffffff !important; 715 } 716 717 body > *:not(#as-print-host), 718 #wpadminbar, 719 #adminmenumain, 720 #adminmenuback, 721 #adminmenuwrap, 722 #wpfooter { 723 display: none !important; 724 } 725 726 body #as-print-host { 727 display: block !important; 728 margin: 0 !important; 729 padding: 0 !important; 730 } 731 732 #as-print-host > .card { 733 width: 85mm; 734 height: 55mm; 735 margin: 0; 736 box-shadow: none !important; 737 break-after: page; 738 page-break-after: always; 739 -webkit-print-color-adjust: exact; 740 print-color-adjust: exact; 741 } 742 743 #as-print-host > .card:last-child { 744 break-after: auto; 745 page-break-after: auto; 746 } 747 748 #as-print-host .card { 749 box-shadow: none !important; 750 -webkit-print-color-adjust: exact; 751 print-color-adjust: exact; 752 } 753 } -
awosign/trunk/assets/designer.js
r3783841 r3783861 1 /* AwoSign Free 1. 0.1— GPL-2.0-or-later */1 /* AwoSign Free 1.3.0 — GPL-2.0-or-later */ 2 2 (function () { 3 3 "use strict"; … … 5 5 const root = document.getElementById('awosign-root'); 6 6 if (!root) return; 7 const $ = id => root.querySelector('[id="' + id + '"]'), 8 fields = ['company', 'person', 'job', 'phone', 'email', 'website', 'address', 'qrUrl']; 7 8 const $ = id => root.querySelector('[id="' + id + '"]'); 9 const brandFields = ['company', 'brand', 'accent', 'website', 'address', 'logoUrl', 'sigTemplate', 'language', 'theme']; 10 const staffFields = ['person', 'job', 'phone', 'email', 'qrMode', 'qrUrl']; 11 const STORAGE_KEY = 'awosign_workspace_v130'; 12 9 13 let logo = ''; 10 const platforms = [['LinkedIn', 'in', 'https://www.linkedin.com/in/'], ['Instagram', '◎', 'https://www.instagram.com/'], ['Facebook', 'f', 'https://www.facebook.com/'], ['X', 'X', 'https://x.com/'], ['YouTube', '▶', 'https://www.youtube.com/@'], ['WhatsApp', '☎', 'https://wa.me/'], ['TikTok', '♪', 'https://www.tiktok.com/@'], ['Pinterest', 'P', 'https://www.pinterest.com/'], ['GitHub', 'GH', 'https://github.com/'], ['Telegram', '✈', 'https://t.me/']];11 14 let active = 'Signature'; 12 // Escape contact text before inserting it into preview or exported HTML. 15 16 const platforms = [ 17 ['LinkedIn', 'in', 'https://www.linkedin.com/in/'], 18 ['Instagram', '◎', 'https://www.instagram.com/'], 19 ['Facebook', 'f', 'https://www.facebook.com/'], 20 ['X', 'X', 'https://x.com/'], 21 ['YouTube', '▶', 'https://www.youtube.com/@'], 22 ['WhatsApp', '☎', 'https://wa.me/'], 23 ['TikTok', '♪', 'https://www.tiktok.com/@'], 24 ['Pinterest', 'P', 'https://www.pinterest.com/'], 25 ['GitHub', 'GH', 'https://github.com/'], 26 ['Telegram', '✈', 'https://t.me/'] 27 ]; 28 29 let workspace = { 30 version: 3, 31 activeProfileId: 'prof_1', 32 brand: { 33 company: 'Example Studio', 34 brand: '#172e49', 35 accent: '#e7b52e', 36 website: 'https://example.com', 37 address: '123 Example Street, Istanbul, TR', 38 logo: '', 39 logoUrl: '', 40 sigTemplate: 'classic', 41 language: 'tr', 42 theme: 'auto' 43 }, 44 profiles: [ 45 { 46 id: 'prof_1', 47 person: 'Alex Morgan', 48 job: 'Creative Director', 49 phone: '+90 555 123 4567', 50 email: 'alex@example.com', 51 qrMode: 'url', 52 qrUrl: '', 53 social: { 54 LinkedIn: { enabled: true, value: 'example' }, 55 Instagram: { enabled: true, value: 'example' }, 56 Facebook: { enabled: true, value: 'example' } 57 } 58 } 59 ] 60 }; 61 13 62 function esc(v) { 14 63 return String(v || '').replace(/[&<>"']/g, c => ({ … … 20 69 })[c]); 21 70 } 71 22 72 function val(k) { 23 return $(k).value.trim(); 24 } 25 // Only HTTP(S) links are accepted for profiles and QR destinations. 73 const el = $(k); 74 return el ? el.value.trim() : ''; 75 } 76 77 function isShowCrop() { 78 const el = $('cropMarks'); 79 if (!el) return true; 80 if (el.type === 'checkbox') return el.checked; 81 return el.value !== '0'; 82 } 83 84 function setStatus(msg) { 85 const s = $('as-status'); 86 if (s) s.textContent = msg || ''; 87 } 88 26 89 function validUrl(s) { 27 90 try { 28 let u = new URL(s);91 const u = new URL(s); 29 92 return ['https:', 'http:'].includes(u.protocol) ? u.href : ''; 30 93 } catch { … … 32 95 } 33 96 } 97 34 98 function profile(p) { 35 let s = $('soc_' + p[0]).value.trim(); 99 const inputEl = $('soc_' + p[0]); 100 let s = inputEl ? inputEl.value.trim() : ''; 36 101 if (!s) return ''; 37 102 if (/^https?:\/\//i.test(s)) return validUrl(s); … … 39 104 return validUrl(p[2] + encodeURIComponent(s.replace(/^@/, ''))); 40 105 } 106 41 107 function socialHtml() { 42 return platforms.filter(p => $('check_' + p[0]).checked && profile(p)).map(p => '<a href="' + esc(profile(p)) + '" target="_blank" rel="noopener noreferrer" style="text-decoration:none"><span>' + esc(p[1]) + '</span></a>').join(''); 43 } 44 function logoHtml(style) { 45 return logo ? '<img src="' + logo + '" alt="Company logo" style="' + style + '">' : '<div style="font-size:24px;font-weight:bold">' + esc(val('company')) + '</div>'; 46 } 47 let qrKey = '', 48 qrMarkup = ''; 49 // Generate and cache QR images locally using the bundled MIT library. 108 return platforms 109 .filter(p => $('check_' + p[0]) && $('check_' + p[0]).checked && profile(p)) 110 .map(p => '<a href="' + esc(profile(p)) + '" target="_blank" rel="noopener noreferrer" style="text-decoration:none"><span>' + esc(p[1]) + '</span></a>') 111 .join(''); 112 } 113 114 function getSignatureLogoSrc() { 115 return validUrl(val('logoUrl')) || logo || ''; 116 } 117 118 function logoHtml(style, preferUrlForEmail) { 119 const src = preferUrlForEmail ? getSignatureLogoSrc() : (logo || validUrl(val('logoUrl'))); 120 return src 121 ? '<img src="' + esc(src) + '" alt="Company logo" style="' + style + '">' 122 : '<div style="font-size:22px;font-weight:bold">' + esc(val('company')) + '</div>'; 123 } 124 125 function getActiveProfile() { 126 let found = workspace.profiles.find(p => p.id === workspace.activeProfileId); 127 if (!found) { 128 found = workspace.profiles[0]; 129 workspace.activeProfileId = found.id; 130 } 131 return found; 132 } 133 134 function syncFormToWorkspace() { 135 brandFields.forEach(k => { 136 if ($(k)) workspace.brand[k] = val(k); 137 }); 138 workspace.brand.logo = logo; 139 140 const current = getActiveProfile(); 141 if (current) { 142 staffFields.forEach(k => { 143 if ($(k)) current[k] = val(k); 144 }); 145 current.social = {}; 146 platforms.forEach(p => { 147 const chk = $('check_' + p[0]); 148 const inp = $('soc_' + p[0]); 149 current.social[p[0]] = { 150 enabled: chk ? chk.checked : false, 151 value: inp ? inp.value.trim() : '' 152 }; 153 }); 154 } 155 refreshProfileSelectLabels(); 156 try { 157 localStorage.setItem(STORAGE_KEY, JSON.stringify(workspace)); 158 } catch (_e) {} 159 } 160 161 function loadWorkspaceIntoForm() { 162 if (workspace.brand) { 163 brandFields.forEach(k => { 164 if ($(k) && typeof workspace.brand[k] === 'string') { 165 $(k).value = workspace.brand[k]; 166 } 167 }); 168 logo = workspace.brand.logo || ''; 169 } 170 const current = getActiveProfile(); 171 if (current) { 172 staffFields.forEach(k => { 173 if ($(k)) $(k).value = current[k] || (k === 'qrMode' ? 'url' : ''); 174 }); 175 platforms.forEach(p => { 176 const s = current.social && current.social[p[0]]; 177 if ($('check_' + p[0])) $('check_' + p[0]).checked = !!(s && s.enabled); 178 if ($('soc_' + p[0])) $('soc_' + p[0]).value = s && typeof s.value === 'string' ? s.value : ''; 179 }); 180 } 181 rebuildProfileSelect(); 182 applyLanguageText(); 183 applyTheme(); 184 } 185 186 function rebuildProfileSelect() { 187 const sel = $('profileSelect'); 188 if (!sel) return; 189 sel.innerHTML = ''; 190 workspace.profiles.forEach((p, idx) => { 191 const opt = document.createElement('option'); 192 opt.value = p.id; 193 opt.textContent = (p.person || ('Personel #' + (idx + 1))) + (p.job ? ' — ' + p.job : ''); 194 if (p.id === workspace.activeProfileId) opt.selected = true; 195 sel.appendChild(opt); 196 }); 197 const badge = $('profileCountBadge'); 198 if (badge) { 199 const count = workspace.profiles.length; 200 badge.textContent = val('language') === 'en' ? count + (count === 1 ? ' Profile' : ' Profiles') : count + ' Profil'; 201 } 202 const sc = $('shortcodeDisplay'); 203 if (sc) { 204 sc.value = '[awosign id="' + workspace.activeProfileId + '"]'; 205 } 206 if ($('deleteProfile')) { 207 $('deleteProfile').disabled = workspace.profiles.length <= 1; 208 } 209 } 210 211 function refreshProfileSelectLabels() { 212 const sel = $('profileSelect'); 213 if (!sel) return; 214 Array.from(sel.options).forEach((opt, idx) => { 215 const p = workspace.profiles[idx]; 216 if (p) { 217 opt.textContent = (p.person || ('Personel #' + (idx + 1))) + (p.job ? ' — ' + p.job : ''); 218 } 219 }); 220 const sc = $('shortcodeDisplay'); 221 if (sc) { 222 sc.value = '[awosign id="' + workspace.activeProfileId + '"]'; 223 } 224 } 225 226 function buildVCardPayload(compactForQr) { 227 const clean = v => 228 String(v || '') 229 .replace(/\\/g, '\\\\') 230 .replace(/\r\n|\r|\n/g, '\\n') 231 .replace(/[,;]/g, c => '\\' + c); 232 233 const lines = [ 234 'BEGIN:VCARD', 235 'VERSION:3.0', 236 'FN:' + clean(val('person')), 237 'ORG:' + clean(val('company')), 238 'TITLE:' + clean(val('job')), 239 val('phone') ? 'TEL;TYPE=WORK,VOICE:' + clean(val('phone')) : '', 240 val('email') ? 'EMAIL:' + clean(val('email')) : '', 241 validUrl(val('website')) ? 'URL:' + clean(validUrl(val('website'))) : '', 242 !compactForQr && val('address') ? 'ADR;TYPE=WORK:;;' + clean(val('address')) + ';;;;' : '', 243 'END:VCARD' 244 ].filter(Boolean); 245 return lines.join('\r\n'); 246 } 247 248 let qrKey = ''; 249 let qrMarkup = ''; 250 let lastQrDataUrl = ''; 251 50 252 function qrHtml() { 51 const url = validUrl(val('qrUrl')) || validUrl(val('website')); 52 if (url === qrKey && qrMarkup) return qrMarkup; 53 qrKey = url; 253 const qrMode = val('qrMode') || 'url'; 254 const payload = 255 qrMode === 'vcard' 256 ? buildVCardPayload(true) 257 : (validUrl(val('qrUrl')) || validUrl(val('website'))); 258 259 const cacheKey = qrMode + ':' + payload; 260 if (cacheKey === qrKey && qrMarkup) return qrMarkup; 261 qrKey = cacheKey; 262 54 263 try { 55 if (! url) throw new Error('URLrequired');56 const code = qrcode(0, ' M');57 code.addData( url, 'Byte');264 if (!payload) throw new Error('Payload required'); 265 const code = qrcode(0, 'L'); 266 code.addData(payload, 'Byte'); 58 267 code.make(); 59 qrMarkup = '<img class="qr" src="' + code.createDataURL(5, 8) + '" alt="QR code">'; 60 } catch (e) { 61 qrMarkup = '<span class="qr" style="color:#333;font-size:11px">' + (val('language') === 'en' ? 'QR unavailable: check URL' : 'QR oluşturulamadı: adresi kontrol edin') + '</span>'; 268 lastQrDataUrl = code.createDataURL(5, 6); 269 qrMarkup = '<img class="qr" src="' + lastQrDataUrl + '" alt="QR code">'; 270 } catch (_e) { 271 lastQrDataUrl = ''; 272 qrMarkup = 273 '<span class="qr" style="color:#333;font-size:10px;display:flex;align-items:center;justify-content:center;text-align:center">' + 274 (val('language') === 'en' ? 'QR unavailable: check URL' : 'QR oluşturulamadı: adresi kontrol edin') + 275 '</span>'; 62 276 } 63 277 return qrMarkup; 64 278 } 65 // Update the signature, both print faces, and digital contact preview. 279 280 function applyTheme() { 281 const tMode = val('theme') || 'auto'; 282 root.setAttribute('data-theme', tMode); 283 } 284 285 function applyLanguageText() { 286 const l = val('language') || 'tr'; 287 root.lang = l; 288 root.querySelectorAll('[data-tr]').forEach(x => { 289 x.textContent = x.dataset[l]; 290 }); 291 } 292 293 /** 294 * Build Table-based Email Signature HTML for Selected Template (classic | compact | banner) 295 */ 296 function signatureHtml() { 297 const brand = val('brand') || '#172e49'; 298 const accent = val('accent') || '#e7b52e'; 299 const tpl = val('sigTemplate') || 'classic'; 300 const name = esc(val('person')); 301 const job = esc(val('job')); 302 const company = esc(val('company')); 303 const phone = esc(val('phone')); 304 const email = esc(val('email')); 305 const web = esc(val('website')); 306 const addr = esc(val('address')); 307 const badges = socialHtml().replace( 308 /<span>/g, 309 '<span style="display:inline-block;margin:2px;padding:4px 6px;border-radius:3px;background:' + 310 brand + 311 ';color:white;font-size:11px;font-weight:bold">' 312 ); 313 314 if (tpl === 'compact') { 315 return ( 316 '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:520px;max-width:100%;font-family:Arial,Helvetica,sans-serif;color:#15263c;background:#fff;border-top:4px solid ' + 317 brand + 318 ';border-bottom:2px solid ' + 319 accent + 320 '"><tr><td style="padding:14px 16px;vertical-align:middle;width:130px">' + 321 logoHtml('max-width:115px;max-height:70px;width:auto;height:auto', true) + 322 '</td><td style="padding:14px 16px;font-size:12px;line-height:1.5"><strong style="font-size:16px;color:' + 323 brand + 324 '">' + 325 name + 326 '</strong> · <span style="color:#475569;font-weight:bold">' + 327 job + 328 '</span><br><span style="color:' + 329 brand + 330 ';font-weight:bold">' + 331 company + 332 '</span><br>' + 333 phone + 334 ' · ' + 335 email + 336 '<br>' + 337 web + 338 '<div style="margin-top:6px">' + 339 badges + 340 '</div></td></tr></table>' 341 ); 342 } 343 344 if (tpl === 'banner') { 345 const qrImg = lastQrDataUrl 346 ? '<img src="' + lastQrDataUrl + '" alt="QR" style="width:78px;height:78px;display:block;border:1px solid #e2e8f0;padding:3px;border-radius:4px">' 347 : ''; 348 return ( 349 '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:100%;font-family:Arial,Helvetica,sans-serif;color:#15263c;background:#fff;border:1px solid #dbe3ec;border-radius:6px;overflow:hidden"><tr style="background:' + 350 brand + 351 ';color:#fff"><td colspan="2" style="padding:12px 18px;border-bottom:3px solid ' + 352 accent + 353 '"><table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td><strong style="font-size:18px;color:#fff">' + 354 name + 355 '</strong><br><span style="font-size:12px;color:#dbe7f5">' + 356 job + 357 ' · ' + 358 company + 359 '</span></td><td align="right">' + 360 logoHtml('max-width:110px;max-height:46px;width:auto;height:auto', true) + 361 '</td></tr></table></td></tr><tr><td style="padding:14px 18px;font-size:12px;line-height:1.6;vertical-align:middle">' + 362 '📞 ' + 363 phone + 364 '<br>✉️ ' + 365 email + 366 '<br>🌐 ' + 367 web + 368 '<br>📍 ' + 369 addr + 370 '<div style="margin-top:8px">' + 371 badges + 372 '</div></td><td style="padding:14px 18px;width:96px;vertical-align:middle" align="right">' + 373 qrImg + 374 '</td></tr></table>' 375 ); 376 } 377 378 return ( 379 '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:100%;font-family:Arial,Helvetica,sans-serif;color:#15263c;background:#fff"><tr><td style="width:170px;padding:18px;vertical-align:middle">' + 380 logoHtml('max-width:150px;max-height:100px;width:auto;height:auto', true) + 381 '</td><td style="padding:18px;border-left:2px solid ' + 382 accent + 383 ';font-size:12px;line-height:1.6"><strong style="font-size:19px">' + 384 name + 385 '</strong><br>' + 386 job + 387 '<br>' + 388 company + 389 '<br>' + 390 phone + 391 '<br>' + 392 email + 393 '<br>' + 394 web + 395 '<div style="margin-top:8px">' + 396 badges + 397 '</div></td></tr><tr><td colspan="2" style="padding:12px 18px;border-top:1px solid #ddd;font-size:11px">' + 398 addr + 399 '</td></tr></table>' 400 ); 401 } 402 66 403 function render() { 67 root.style.setProperty('--brand', $('brand').value); 68 root.style.setProperty('--accent', $('accent').value); 69 let name = esc(val('person')), 70 company = esc(val('company')), 71 job = esc(val('job')), 72 phone = esc(val('phone')), 73 email = esc(val('email')), 74 web = esc(val('website')), 75 address = esc(val('address')); 76 let icons = socialHtml(); 77 $('sig').innerHTML = '<div class="sigrow"><div class="siglogo">' + logoHtml('max-width:100%;max-height:100px;object-fit:contain') + '</div><div class="siginfo"><strong>' + name + '</strong><br>' + job + '<br>' + company + '<br>' + phone + '<br>' + email + '<br>' + web + '<div class="socialicons">' + icons + '</div></div></div><div class="sigaddress">' + address + '</div>'; 78 $('front').innerHTML = logoHtml('max-width:55%;max-height:95px;object-fit:contain') + '<strong>' + company + '</strong><small>' + web + '</small>'; 79 $('back').innerHTML = '<div class="details"><strong>' + name + '</strong><br>' + job + '<br>' + company + '<br>' + phone + '<br>' + email + '<br>' + web + '<br>' + address + '<div class="socialicons">' + icons + '</div></div>' + qrHtml(); 80 $('digital').innerHTML = '<div class="details">' + logoHtml('max-width:100px;max-height:70px;object-fit:contain') + '<br><strong>' + name + '</strong><br>' + job + '<br>' + company + '<br>' + phone + '<br>' + email + '<br>' + web + '<div class="socialicons">' + icons + '</div></div>' + qrHtml(); 81 } 404 syncFormToWorkspace(); 405 applyTheme(); 406 root.style.setProperty('--brand', val('brand') || '#172e49'); 407 root.style.setProperty('--accent', val('accent') || '#e7b52e'); 408 409 const name = esc(val('person')); 410 const company = esc(val('company')); 411 const job = esc(val('job')); 412 const phone = esc(val('phone')); 413 const email = esc(val('email')); 414 const web = esc(val('website')); 415 const address = esc(val('address')); 416 const icons = socialHtml(); 417 const showCrop = isShowCrop(); 418 419 // Ensure QR cache is up-to-date before signatureHtml() uses lastQrDataUrl 420 const qrMarkupNow = qrHtml(); 421 422 $('sig').innerHTML = signatureHtml(); 423 424 $('front').innerHTML = 425 logoHtml('max-width:55%;max-height:85px;object-fit:contain', false) + 426 '<strong>' + 427 company + 428 '</strong><small>' + 429 web + 430 '</small>'; 431 432 $('back').innerHTML = 433 '<div class="details"><strong>' + 434 name + 435 '</strong><br>' + 436 job + 437 '<br>' + 438 company + 439 '<br>' + 440 phone + 441 '<br>' + 442 email + 443 '<br>' + 444 web + 445 '<br>' + 446 address + 447 '<div class="socialicons">' + 448 icons + 449 '</div></div>' + 450 qrMarkupNow; 451 452 $('front').classList.toggle('show-crop-guide', showCrop); 453 $('back').classList.toggle('show-crop-guide', showCrop); 454 455 $('digital').innerHTML = 456 '<div class="details">' + 457 logoHtml('max-width:100px;max-height:70px;object-fit:contain', false) + 458 '<br><strong>' + 459 name + 460 '</strong><br>' + 461 job + 462 '<br>' + 463 company + 464 '<br>' + 465 phone + 466 '<br>' + 467 email + 468 '<br>' + 469 web + 470 '<div class="socialicons">' + 471 icons + 472 '</div></div>' + 473 qrMarkupNow; 474 } 475 82 476 function download(name, content, type) { 83 let b = new Blob([content], { 84 type 85 }); 86 let a = document.createElement('a'); 477 const b = content instanceof Blob ? content : new Blob([content], { type }); 478 const a = document.createElement('a'); 87 479 a.href = URL.createObjectURL(b); 88 480 a.download = name; 481 document.body.appendChild(a); 89 482 a.click(); 483 a.remove(); 90 484 setTimeout(() => URL.revokeObjectURL(a.href), 1000); 91 485 } 92 // Export vCard 3.0 with escaped field separators and line breaks. 486 487 function loadImage(src) { 488 return new Promise((resolve, reject) => { 489 const i = new Image(); 490 i.crossOrigin = 'anonymous'; 491 i.onload = () => resolve(i); 492 i.onerror = reject; 493 i.src = src; 494 }); 495 } 496 93 497 function vcf() { 94 const clean = v => String(v).replace(/\\/g, '\\\\').replace(/\r\n|\r|\n/g, '\\n').replace(/[,;]/g, c => '\\' + c); 95 download('awosign-contact.vcf', ['BEGIN:VCARD', 'VERSION:3.0', 'FN:' + clean(val('person')), 'ORG:' + clean(val('company')), 'TITLE:' + clean(val('job')), 'TEL;TYPE=WORK,VOICE:' + clean(val('phone')), 'EMAIL:' + clean(val('email')), 'URL:' + clean(validUrl(val('website'))), 'ADR;TYPE=WORK:;;' + clean(val('address')) + ';;;;', 'END:VCARD'].join('\r\n'), 'text/vcard;charset=utf-8'); 96 } 498 download('awosign-contact.vcf', buildVCardPayload(false), 'text/vcard;charset=utf-8'); 499 setStatus(val('language') === 'en' ? 'VCF contact card downloaded.' : 'VCF rehber kartı indirildi.'); 500 } 501 502 /** 503 * High-DPI Canvas Renderer for Front & Back Print Cards (300 PPI or 600 DPI with 3mm Bleed & Crop Marks) 504 */ 505 async function renderPrintFaceToPngBlob(face, withBleedAndCrop) { 506 const scale = withBleedAndCrop ? 2 : 1; 507 const trimW = 1004 * scale; 508 const trimH = 650 * scale; 509 const marginPx = withBleedAndCrop ? Math.round(71 * scale) : 0; 510 const bleedPx = withBleedAndCrop ? Math.round(35.4 * scale) : 0; 511 512 const canvas = document.createElement('canvas'); 513 canvas.width = trimW + marginPx * 2; 514 canvas.height = trimH + marginPx * 2; 515 const ctx = canvas.getContext('2d'); 516 517 const brand = val('brand') || '#172e49'; 518 const accent = val('accent') || '#e7b52e'; 519 const originX = marginPx; 520 const originY = marginPx; 521 522 ctx.fillStyle = '#ffffff'; 523 ctx.fillRect(0, 0, canvas.width, canvas.height); 524 525 if (face === 'front') { 526 ctx.fillStyle = brand; 527 ctx.fillRect( 528 originX - bleedPx, 529 originY - bleedPx, 530 trimW + bleedPx * 2, 531 trimH + bleedPx * 2 532 ); 533 ctx.fillStyle = accent; 534 ctx.fillRect( 535 originX - bleedPx, 536 originY - bleedPx, 537 trimW + bleedPx * 2, 538 Math.round(18 * scale) + bleedPx 539 ); 540 541 let centerY = originY + Math.round(trimH * 0.42); 542 const activeLogo = logo || validUrl(val('logoUrl')); 543 if (activeLogo) { 544 try { 545 const img = await loadImage(activeLogo); 546 const maxW = 520 * scale; 547 const maxH = 220 * scale; 548 const ratio = Math.min(maxW / img.width, maxH / img.height, 1); 549 const drawW = img.width * ratio; 550 const drawH = img.height * ratio; 551 ctx.drawImage( 552 img, 553 originX + (trimW - drawW) / 2, 554 originY + Math.round(135 * scale), 555 drawW, 556 drawH 557 ); 558 centerY = originY + Math.round(155 * scale) + drawH + Math.round(55 * scale); 559 } catch (_e) {} 560 } 561 562 ctx.fillStyle = '#ffffff'; 563 ctx.textAlign = 'center'; 564 ctx.font = `bold ${Math.round(52 * scale)}px Arial, sans-serif`; 565 ctx.fillText(val('company'), originX + trimW / 2, centerY); 566 567 ctx.fillStyle = '#dbe7f5'; 568 ctx.font = `${Math.round(28 * scale)}px Arial, sans-serif`; 569 ctx.fillText(val('website'), originX + trimW / 2, centerY + Math.round(48 * scale)); 570 } else { 571 ctx.fillStyle = '#ffffff'; 572 ctx.fillRect( 573 originX - bleedPx, 574 originY - bleedPx, 575 trimW + bleedPx * 2, 576 trimH + bleedPx * 2 577 ); 578 ctx.fillStyle = accent; 579 ctx.fillRect( 580 originX - bleedPx, 581 originY - bleedPx, 582 Math.round(22 * scale) + bleedPx, 583 trimH + bleedPx * 2 584 ); 585 586 const padX = originX + Math.round(68 * scale); 587 let curY = originY + Math.round(115 * scale); 588 589 ctx.textAlign = 'left'; 590 ctx.fillStyle = '#102032'; 591 ctx.font = `bold ${Math.round(46 * scale)}px Arial, sans-serif`; 592 ctx.fillText(val('person'), padX, curY); 593 594 curY += Math.round(42 * scale); 595 ctx.fillStyle = '#243f5d'; 596 ctx.font = `bold ${Math.round(29 * scale)}px Arial, sans-serif`; 597 ctx.fillText(val('job') + (val('company') ? ' · ' + val('company') : ''), padX, curY); 598 599 curY += Math.round(46 * scale); 600 ctx.fillStyle = '#15263c'; 601 ctx.font = `${Math.round(27 * scale)}px Arial, sans-serif`; 602 [val('phone'), val('email'), val('website'), val('address')].filter(Boolean).forEach(line => { 603 ctx.fillText(line.slice(0, 52), padX, curY); 604 curY += Math.round(38 * scale); 605 }); 606 607 const activeSocials = platforms.filter(p => $('check_' + p[0]) && $('check_' + p[0]).checked && profile(p)); 608 if (activeSocials.length) { 609 let socX = padX; 610 const socY = originY + trimH - Math.round(75 * scale); 611 ctx.font = `bold ${Math.round(22 * scale)}px Arial, sans-serif`; 612 activeSocials.forEach(p => { 613 const badge = p[1]; 614 const w = Math.max(Math.round(42 * scale), ctx.measureText(badge).width + Math.round(24 * scale)); 615 ctx.fillStyle = brand; 616 ctx.fillRect(socX, socY, w, Math.round(38 * scale)); 617 ctx.fillStyle = '#ffffff'; 618 ctx.fillText(badge, socX + Math.round(11 * scale), socY + Math.round(26 * scale)); 619 socX += w + Math.round(10 * scale); 620 }); 621 } 622 623 if (lastQrDataUrl) { 624 try { 625 const qImg = await loadImage(lastQrDataUrl); 626 const qSize = Math.round(255 * scale); 627 ctx.drawImage( 628 qImg, 629 originX + trimW - Math.round(55 * scale) - qSize, 630 originY + (trimH - qSize) / 2, 631 qSize, 632 qSize 633 ); 634 } catch (_e) {} 635 } 636 } 637 638 if (withBleedAndCrop) { 639 ctx.strokeStyle = '#000000'; 640 ctx.lineWidth = Math.max(1, Math.round(1.5 * scale)); 641 const markLen = Math.round(42 * scale); 642 const markGap = Math.round(18 * scale); 643 const drawCorner = (cx, cy, dx, dy) => { 644 ctx.beginPath(); 645 ctx.moveTo(cx + dx * markGap, cy); 646 ctx.lineTo(cx + dx * (markGap + markLen), cy); 647 ctx.moveTo(cx, cy + dy * markGap); 648 ctx.lineTo(cx, cy + dy * (markGap + markLen)); 649 ctx.stroke(); 650 }; 651 drawCorner(originX, originY, -1, -1); 652 drawCorner(originX + trimW, originY, 1, -1); 653 drawCorner(originX, originY + trimH, -1, 1); 654 drawCorner(originX + trimW, originY + trimH, 1, 1); 655 } 656 657 return new Promise(resolve => canvas.toBlob(resolve, 'image/png')); 658 } 659 660 function applyDynamicPageSize(sizeCss) { 661 let styleEl = document.getElementById('as-dynamic-page-style'); 662 if (!styleEl) { 663 styleEl = document.createElement('style'); 664 styleEl.id = 'as-dynamic-page-style'; 665 document.head.appendChild(styleEl); 666 } 667 styleEl.textContent = `@page { size: ${sizeCss}; margin: 0; }`; 668 } 669 670 function buildBleedSheet(cardEl, isFront) { 671 const sheet = document.createElement('div'); 672 sheet.className = 'as-bleed-sheet'; 673 674 const bg = document.createElement('div'); 675 bg.className = isFront ? 'as-bleed-bg-front' : 'as-bleed-bg-back'; 676 sheet.appendChild(bg); 677 678 const clone = cardEl.cloneNode(true); 679 clone.classList.remove('show-crop-guide'); 680 sheet.appendChild(clone); 681 682 [ 683 'as-crop-tl-h', 'as-crop-tl-v', 684 'as-crop-tr-h', 'as-crop-tr-v', 685 'as-crop-bl-h', 'as-crop-bl-v', 686 'as-crop-br-h', 'as-crop-br-v' 687 ].forEach(cls => { 688 const m = document.createElement('div'); 689 m.className = 'as-crop-mark ' + cls; 690 sheet.appendChild(m); 691 }); 692 return sheet; 693 } 694 695 function printBusinessCards() { 696 let host = document.getElementById('as-print-host'); 697 if (!host) { 698 host = document.createElement('div'); 699 host.id = 'as-print-host'; 700 document.body.appendChild(host); 701 } 702 host.style.setProperty('--brand', val('brand') || '#172e49'); 703 host.style.setProperty('--accent', val('accent') || '#e7b52e'); 704 705 const layout = val('printLayout') || 'a4_10up_duplex'; 706 const copies = Math.max(1, Math.min(20, Number(val('printCopies')) || 1)); 707 const showCrop = isShowCrop(); 708 const frontEl = $('front'); 709 const backEl = $('back'); 710 const nodes = []; 711 712 const makeA4SheetOf = cardEl => { 713 const sheet = document.createElement('div'); 714 sheet.className = 'as-a4-sheet'; 715 for (let i = 0; i < 10; i++) { 716 const cell = document.createElement('div'); 717 cell.className = 'as-a4-cell' + (showCrop ? ' has-crop-lines' : ''); 718 const clone = cardEl.cloneNode(true); 719 clone.classList.remove('show-crop-guide'); 720 cell.appendChild(clone); 721 sheet.appendChild(cell); 722 } 723 return sheet; 724 }; 725 726 if (layout === 'a4_10up_duplex') { 727 applyDynamicPageSize('210mm 297mm'); 728 for (let c = 0; c < copies; c++) { 729 nodes.push(makeA4SheetOf(frontEl)); 730 nodes.push(makeA4SheetOf(backEl)); 731 } 732 } else if (layout === 'a4_10up_front') { 733 applyDynamicPageSize('210mm 297mm'); 734 for (let c = 0; c < copies; c++) { 735 nodes.push(makeA4SheetOf(frontEl)); 736 } 737 } else if (layout === 'a4_10up_back') { 738 applyDynamicPageSize('210mm 297mm'); 739 for (let c = 0; c < copies; c++) { 740 nodes.push(makeA4SheetOf(backEl)); 741 } 742 } else if (layout === 'single_bleed' || layout === 'bleed_91x61') { 743 applyDynamicPageSize('97mm 67mm'); 744 for (let c = 0; c < copies; c++) { 745 nodes.push(buildBleedSheet(frontEl, true)); 746 nodes.push(buildBleedSheet(backEl, false)); 747 } 748 } else { 749 applyDynamicPageSize('85mm 55mm'); 750 for (let c = 0; c < copies; c++) { 751 const fCopy = frontEl.cloneNode(true); 752 const bCopy = backEl.cloneNode(true); 753 fCopy.classList.remove('show-crop-guide'); 754 bCopy.classList.remove('show-crop-guide'); 755 nodes.push(fCopy, bCopy); 756 } 757 } 758 759 host.replaceChildren(...nodes); 760 window.print(); 761 } 762 97 763 platforms.forEach((p, i) => { 98 $('socials').insertAdjacentHTML('beforeend', '<label><input type="checkbox" id="check_' + p[0] + '" ' + (i < 3 ? 'checked' : '') + '>' + p[0] + '</label>'); 99 $('socialInputs').insertAdjacentHTML('beforeend', '<label>' + p[0] + ' — ' + p[2] + '</label><input id="soc_' + p[0] + '" placeholder="username / URL" value="' + (i < 3 ? 'example' : '') + '">'); 764 $('socials').insertAdjacentHTML( 765 'beforeend', 766 '<label><input type="checkbox" id="check_' + p[0] + '" ' + (i < 3 ? 'checked' : '') + '>' + p[0] + '</label>' 767 ); 768 $('socialInputs').insertAdjacentHTML( 769 'beforeend', 770 '<label>' + p[0] + ' — ' + p[2] + '</label><input id="soc_' + p[0] + '" placeholder="username / URL" value="' + (i < 3 ? 'example' : '') + '">' 771 ); 100 772 }); 101 root.querySelectorAll('input,textarea,select').forEach(el => el.addEventListener('input', render)); 773 774 if (typeof window.awosignWpConfig === 'object' && window.awosignWpConfig && window.awosignWpConfig.workspace) { 775 workspace = window.awosignWpConfig.workspace; 776 } else { 777 try { 778 const savedLocal = localStorage.getItem(STORAGE_KEY) || localStorage.getItem('awosign_workspace_v120'); 779 if (savedLocal) { 780 const parsed = JSON.parse(savedLocal); 781 if (parsed && Array.isArray(parsed.profiles) && parsed.profiles.length) { 782 workspace = parsed; 783 } 784 } 785 } catch (_e) {} 786 } 787 788 loadWorkspaceIntoForm(); 789 790 root.querySelectorAll('input,textarea,select').forEach(el => { 791 if (el.id === 'profileSelect' || el.id === 'loadProject' || el.id === 'logoFile') return; 792 el.addEventListener('input', render); 793 }); 794 root.querySelectorAll('select,input[type="checkbox"]').forEach(el => { 795 if (el.id === 'profileSelect') return; 796 el.addEventListener('change', render); 797 }); 798 799 if ($('profileSelect')) { 800 $('profileSelect').onchange = e => { 801 workspace.activeProfileId = e.target.value; 802 loadWorkspaceIntoForm(); 803 render(); 804 setStatus( 805 val('language') === 'en' 806 ? 'Switched to profile: ' + val('person') 807 : 'Aktif personel değiştirildi: ' + val('person') 808 ); 809 }; 810 } 811 812 if ($('addProfile')) { 813 $('addProfile').onclick = () => { 814 syncFormToWorkspace(); 815 const newId = 'prof_' + Date.now().toString(36); 816 const count = workspace.profiles.length + 1; 817 workspace.profiles.push({ 818 id: newId, 819 person: val('language') === 'en' ? 'New Staff #' + count : 'Yeni Personel #' + count, 820 job: val('language') === 'en' ? 'Team Member' : 'Uzman / Yetkili', 821 phone: val('phone') || '+90 555 000 0000', 822 email: 823 'personel' + 824 count + 825 '@' + 826 ((val('website') || 'example.com').replace(/^https?:\/\/(www\.)?/i, '').split('/')[0] || 'example.com'), 827 qrMode: 'url', 828 qrUrl: '', 829 social: {} 830 }); 831 workspace.activeProfileId = newId; 832 loadWorkspaceIntoForm(); 833 render(); 834 setStatus(val('language') === 'en' ? 'New staff profile added.' : 'Yeni personel profili oluşturuldu.'); 835 }; 836 } 837 838 if ($('deleteProfile')) { 839 $('deleteProfile').onclick = () => { 840 if (workspace.profiles.length <= 1) return; 841 workspace.profiles = workspace.profiles.filter(p => p.id !== workspace.activeProfileId); 842 workspace.activeProfileId = workspace.profiles[0].id; 843 loadWorkspaceIntoForm(); 844 render(); 845 setStatus(val('language') === 'en' ? 'Staff profile deleted.' : 'Personel profili silindi.'); 846 }; 847 } 848 849 if ($('copyShortcode')) { 850 $('copyShortcode').onclick = async () => { 851 const code = $('shortcodeDisplay') ? $('shortcodeDisplay').value : '[awosign]'; 852 try { 853 await navigator.clipboard.writeText(code); 854 setStatus(val('language') === 'en' ? 'Shortcode copied: ' + code : 'Kısa kod kopyalandı: ' + code); 855 } catch (_e) { 856 if ($('shortcodeDisplay')) $('shortcodeDisplay').select(); 857 setStatus(code); 858 } 859 }; 860 } 861 862 if ($('pickWpMediaLogo')) { 863 $('pickWpMediaLogo').onclick = () => { 864 if (typeof window.wp !== 'undefined' && window.wp && window.wp.media) { 865 const frame = window.wp.media({ 866 title: val('language') === 'en' ? 'Select Company Logo' : 'Şirket Logosu Seçin', 867 multiple: false, 868 library: { type: 'image' } 869 }); 870 frame.on('select', () => { 871 const attachment = frame.state().get('selection').first().toJSON(); 872 if (attachment && attachment.url) { 873 if ($('logoUrl')) $('logoUrl').value = attachment.url; 874 render(); 875 setStatus(val('language') === 'en' ? 'Media library logo selected.' : 'Medya kütüphanesi logosu seçildi.'); 876 } 877 }); 878 frame.open(); 879 } else { 880 setStatus( 881 val('language') === 'en' 882 ? 'WP Media Library is available inside WordPress admin. Paste any image URL in the field.' 883 : 'WP Medya Kütüphanesi WordPress yönetim panelinde aktif olur. Kutuya doğrudan görsel URL yapıştırabilirsiniz.' 884 ); 885 } 886 }; 887 } 888 889 if ($('shareWhatsapp')) { 890 $('shareWhatsapp').onclick = () => { 891 const text = [ 892 '📇 *' + val('person') + '*', 893 val('job') + (val('company') ? ' · ' + val('company') : ''), 894 val('phone') ? '📞 ' + val('phone') : '', 895 val('email') ? '✉️ ' + val('email') : '', 896 val('website') ? '🌐 ' + val('website') : '', 897 val('address') ? '📍 ' + val('address') : '' 898 ] 899 .filter(Boolean) 900 .join('\n'); 901 window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank', 'noopener,noreferrer'); 902 }; 903 } 904 905 if ($('saveWpDb')) { 906 $('saveWpDb').onclick = async () => { 907 syncFormToWorkspace(); 908 if (typeof window.awosignWpConfig === 'object' && window.awosignWpConfig && window.awosignWpConfig.ajaxUrl) { 909 try { 910 setStatus(val('language') === 'en' ? 'Saving to WordPress...' : 'WordPress veritabanına kaydediliyor...'); 911 const body = new URLSearchParams(); 912 body.set('action', 'awosign_save_workspace'); 913 body.set('nonce', window.awosignWpConfig.nonce || ''); 914 body.set('workspace', JSON.stringify(workspace)); 915 const res = await fetch(window.awosignWpConfig.ajaxUrl, { 916 method: 'POST', 917 headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, 918 body: body.toString() 919 }); 920 const json = await res.json(); 921 if (json && json.success) { 922 setStatus( 923 val('language') === 'en' 924 ? 'Saved to WordPress database (' + workspace.profiles.length + ' profiles).' 925 : 'WordPress veritabanına kaydedildi (' + workspace.profiles.length + ' personel profili).' 926 ); 927 } else { 928 throw new Error((json && json.data && json.data.message) || 'Save error'); 929 } 930 } catch (err) { 931 setStatus( 932 val('language') === 'en' 933 ? 'WordPress save failed: ' + err.message 934 : 'WordPress kaydı başarısız: ' + err.message 935 ); 936 } 937 } else { 938 setStatus( 939 val('language') === 'en' 940 ? 'Saved in browser storage (Preview mode). In WordPress this saves to wp_options.' 941 : 'Tarayıcı belleğine kaydedildi (Önizleme modu). WordPress içinde wp_options veritabanına kaydedilir.' 942 ); 943 } 944 }; 945 } 946 947 if ($('useSiteLogo')) { 948 $('useSiteLogo').onclick = async () => { 949 const wpLogo = 950 typeof window.awosignWpConfig === 'object' && 951 window.awosignWpConfig && 952 window.awosignWpConfig.siteLogoUrl 953 ? window.awosignWpConfig.siteLogoUrl 954 : ''; 955 if (wpLogo) { 956 if ($('logoUrl') && !$('logoUrl').value) $('logoUrl').value = wpLogo; 957 try { 958 const img = await loadImage(wpLogo); 959 const c = document.createElement('canvas'); 960 c.width = img.naturalWidth || img.width || 300; 961 c.height = img.naturalHeight || img.height || 120; 962 c.getContext('2d').drawImage(img, 0, 0); 963 logo = c.toDataURL('image/png'); 964 } catch (_e) { 965 logo = wpLogo; 966 } 967 render(); 968 setStatus(val('language') === 'en' ? 'WordPress site logo loaded.' : 'WordPress site logosu yüklendi.'); 969 return; 970 } 971 const comp = val('company') || 'AWO'; 972 const initials = 973 comp 974 .split(/\s+/) 975 .filter(Boolean) 976 .slice(0, 2) 977 .map(w => w[0].toUpperCase()) 978 .join('') || 'AW'; 979 const c = document.createElement('canvas'); 980 c.width = 360; 981 c.height = 140; 982 const ctx = c.getContext('2d'); 983 ctx.fillStyle = val('accent') || '#e7b52e'; 984 ctx.beginPath(); 985 ctx.roundRect(10, 15, 110, 110, 22); 986 ctx.fill(); 987 ctx.fillStyle = val('brand') || '#172e49'; 988 ctx.font = 'bold 50px Arial, sans-serif'; 989 ctx.textAlign = 'center'; 990 ctx.textBaseline = 'middle'; 991 ctx.fillText(initials, 65, 72); 992 ctx.fillStyle = '#ffffff'; 993 ctx.font = 'bold 34px Arial, sans-serif'; 994 ctx.textAlign = 'left'; 995 ctx.fillText(comp.slice(0, 14), 138, 72); 996 logo = c.toDataURL('image/png'); 997 render(); 998 setStatus( 999 val('language') === 'en' 1000 ? 'Generated company emblem logo.' 1001 : 'Kurumsal amblem logosu otomatik oluşturuldu.' 1002 ); 1003 }; 1004 } 1005 102 1006 $('logoFile').addEventListener('change', e => { 103 let f = e.target.files[0];1007 const f = e.target.files[0]; 104 1008 if (!f) return; 105 1009 if (!['image/png', 'image/jpeg'].includes(f.type) || f.size > 3e6) { 106 alert('PNG veya JPG · en fazla 3 MB');1010 setStatus(val('language') === 'en' ? 'Choose PNG/JPG up to 3 MB.' : 'En çok 3 MB PNG veya JPG seçin.'); 107 1011 return; 108 1012 } 109 let r = new FileReader();1013 const r = new FileReader(); 110 1014 r.onload = () => { 111 1015 logo = r.result; 112 1016 render(); 1017 setStatus(val('language') === 'en' ? 'Logo updated.' : 'Logo güncellendi.'); 113 1018 }; 114 1019 r.readAsDataURL(f); 115 1020 }); 116 root.querySelectorAll('[data-tab]').forEach(b => b.onclick = () => { 117 active = b.dataset.tab; 118 root.querySelectorAll('[data-tab]').forEach(x => x.classList.toggle('active', x === b)); 119 ['Signature', 'Digital', 'Print'].forEach(t => $('view' + t).classList.toggle('hidden', t !== active)); 1021 1022 root.querySelectorAll('[data-tab]').forEach(b => { 1023 b.onclick = () => { 1024 active = b.dataset.tab; 1025 root.querySelectorAll('[data-tab]').forEach(x => x.classList.toggle('active', x === b)); 1026 ['Signature', 'Digital', 'Print'].forEach(t => $('view' + t).classList.toggle('hidden', t !== active)); 1027 }; 120 1028 }); 1029 121 1030 $('language').onchange = () => { 122 let l = val('language'); 123 root.lang = l; 124 root.querySelectorAll('[data-tr]').forEach(x => x.textContent = x.dataset[l]); 1031 applyLanguageText(); 1032 rebuildProfileSelect(); 125 1033 render(); 126 1034 }; 127 $('theme').onchange = () => root.classList.toggle('light', val('theme') === 'light'); 1035 1036 $('theme').onchange = () => { 1037 applyTheme(); 1038 syncFormToWorkspace(); 1039 }; 128 1040 $('vcf').onclick = vcf; 129 1041 $('vcf2').onclick = vcf; 130 $('print').onclick = () => { 131 const w = window.open('', '_blank'); 132 if (!w) { 133 alert('Please allow popups for printing.'); 134 return; 135 } 136 w.opener = null; 137 w.document.write('<!doctype html><html><head><meta charset="utf-8"><title>AwoSign Print</title><style>@page{size:91mm 61mm;margin:0}*{box-sizing:border-box}body{margin:0;font-family:Arial,sans-serif}.card{width:91mm;height:61mm;padding:9mm;overflow:hidden;break-after:page;print-color-adjust:exact;-webkit-print-color-adjust:exact}.front{background:' + val('brand') + ';color:white;display:flex;flex-direction:column;align-items:center;justify-content:center;border-top:3mm solid ' + val('accent') + '}.back{background:white;color:#15263c;display:flex;align-items:center;justify-content:space-between;border-left:3mm solid ' + val('accent') + ';break-after:auto}.details{font-size:9pt;line-height:1.4;overflow-wrap:anywhere}.qr{width:22mm;height:22mm}.socialicons{display:flex;gap:3px}.socialicons span{font-size:8pt}small{font-size:8pt}strong{font-size:14pt}</style></head><body>' + document.getElementById('front').outerHTML + document.getElementById('back').outerHTML + '</body></html>'); 138 w.document.close(); 139 const imgs = Array.from(w.document.images); 140 Promise.all(imgs.map(img => img.complete ? Promise.resolve() : new Promise(resolve => { 141 img.onload = resolve; 142 img.onerror = resolve; 143 }))).then(() => { 144 w.focus(); 145 w.print(); 146 }); 1042 $('print').onclick = printBusinessCards; 1043 1044 if ($('downloadPrintPng')) { 1045 $('downloadPrintPng').onclick = async () => { 1046 const fBlob = await renderPrintFaceToPngBlob('front', true); 1047 const bBlob = await renderPrintFaceToPngBlob('back', true); 1048 if (fBlob) download('awosign-on-yuz-600dpi-kroslu.png', fBlob, 'image/png'); 1049 if (bBlob) download('awosign-arka-yuz-600dpi-kroslu.png', bBlob, 'image/png'); 1050 setStatus( 1051 val('language') === 'en' 1052 ? 'Exported 600 DPI Front & Back PNGs with 3 mm bleed & crop marks.' 1053 : 'Ön ve arka yüz 600 DPI matbaa PNG kalıpları (3 mm taşma + kroslu) indirildi.' 1054 ); 1055 }; 1056 } 1057 1058 if ($('downloadStandardPng')) { 1059 $('downloadStandardPng').onclick = async () => { 1060 const fBlob = await renderPrintFaceToPngBlob('front', false); 1061 const bBlob = await renderPrintFaceToPngBlob('back', false); 1062 if (fBlob) download('awosign-on-yuz-300ppi.png', fBlob, 'image/png'); 1063 if (bBlob) download('awosign-arka-yuz-300ppi.png', bBlob, 'image/png'); 1064 setStatus( 1065 val('language') === 'en' 1066 ? 'Exported 300 PPI Front & Back PNG cards.' 1067 : 'Ön ve arka yüz 300 PPI PNG kartvizitler indirildi.' 1068 ); 1069 }; 1070 } 1071 1072 if ($('downloadDigitalPng')) { 1073 $('downloadDigitalPng').onclick = async () => { 1074 const bBlob = await renderPrintFaceToPngBlob('back', false); 1075 if (bBlob) download('awosign-dijital-kart.png', bBlob, 'image/png'); 1076 setStatus( 1077 val('language') === 'en' 1078 ? 'Digital business card PNG downloaded.' 1079 : 'Dijital kartvizit PNG olarak indirildi.' 1080 ); 1081 }; 1082 } 1083 1084 $('downloadSig').onclick = () => { 1085 download( 1086 'awosign-signature.html', 1087 '<!doctype html><html><head><meta charset="utf-8"><title>AwoSign signature</title></head><body>' + 1088 signatureHtml() + 1089 '</body></html>', 1090 'text/html;charset=utf-8' 1091 ); 1092 setStatus(val('language') === 'en' ? 'Signature HTML downloaded.' : 'İmza HTML dosyası indirildi.'); 147 1093 }; 148 function signatureHtml() { 149 const brand = val('brand'), 150 accent = val('accent'); 151 return '<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:100%;font-family:Arial,Helvetica,sans-serif;color:#15263c;background:#fff"><tr><td style="width:170px;padding:18px;vertical-align:middle">' + logoHtml('max-width:150px;max-height:100px;width:auto;height:auto') + '</td><td style="padding:18px;border-left:2px solid ' + accent + ';font-size:12px;line-height:1.6"><strong style="font-size:19px">' + esc(val('person')) + '</strong><br>' + esc(val('job')) + '<br>' + esc(val('company')) + '<br>' + esc(val('phone')) + '<br>' + esc(val('email')) + '<br>' + esc(val('website')) + '<div style="margin-top:8px">' + socialHtml().replace(/<span>/g, '<span style="display:inline-block;margin:2px;padding:4px;background:' + brand + ';color:white;font-size:11px">') + '</div></td></tr><tr><td colspan="2" style="padding:12px 18px;border-top:1px solid #ddd;font-size:11px">' + esc(val('address')) + '</td></tr></table>'; 152 } 153 $('downloadSig').onclick = () => download('awosign-signature.html', '<!doctype html><html><head><meta charset="utf-8"><title>AwoSign signature</title></head><body>' + signatureHtml() + '</body></html>', 'text/html;charset=utf-8'); 1094 154 1095 $('copySig').onclick = async () => { 155 1096 try { 156 await navigator.clipboard.write([new ClipboardItem({ 157 'text/html': new Blob([signatureHtml()], { 158 type: 'text/html' 159 }), 160 'text/plain': new Blob([$('sig').innerText], { 161 type: 'text/plain' 1097 await navigator.clipboard.write([ 1098 new ClipboardItem({ 1099 'text/html': new Blob([signatureHtml()], { type: 'text/html' }), 1100 'text/plain': new Blob([$('sig').innerText], { type: 'text/plain' }) 162 1101 }) 163 })]);164 alert(val('language') === 'en' ? 'Copied' : 'Kopyalandı');1102 ]); 1103 setStatus(val('language') === 'en' ? 'Signature copied to clipboard.' : 'E-posta imzası panoya kopyalandı.'); 165 1104 } catch { 166 alert(val('language') === 'en' ? 'Clipboard blocked. Use Download HTML.' : 'Kopyalama engellendi. HTML indir seçeneğini kullanın.'); 1105 setStatus( 1106 val('language') === 'en' 1107 ? 'Clipboard blocked. Use Download HTML.' 1108 : 'Kopyalama engellendi. HTML indir seçeneğini kullanın.' 1109 ); 167 1110 } 168 1111 }; 1112 169 1113 $('saveProject').onclick = () => { 170 const data = { 171 version: 1, 172 logo, 173 fields: {}, 174 social: {} 175 }; 176 [...fields, 'brand', 'accent', 'language', 'theme'].forEach(k => data.fields[k] = val(k)); 177 platforms.forEach(p => data.social[p[0]] = { 178 enabled: $('check_' + p[0]).checked, 179 value: $('soc_' + p[0]).value 180 }); 181 download('awosign-project.json', JSON.stringify(data, null, 2), 'application/json'); 1114 syncFormToWorkspace(); 1115 download('awosign-workspace.json', JSON.stringify(workspace, null, 2), 'application/json'); 1116 setStatus(val('language') === 'en' ? 'Workspace JSON downloaded.' : 'Tüm profiller JSON olarak indirildi.'); 182 1117 }; 183 // Validate imported project fields; accept PNG/JPEG data URLs only. 1118 184 1119 $('loadProject').onchange = async e => { 185 1120 try { … … 187 1122 if (!f || f.size > 5000000) throw Error('Invalid file'); 188 1123 const data = JSON.parse(await f.text()); 189 if (data.version !== 1 || !data.fields) throw Error('Invalid format'); 190 [...fields, 'brand', 'accent', 'language', 'theme'].forEach(k => { 191 if (typeof data.fields[k] === 'string') { 192 if (['brand', 'accent'].includes(k) && !/^#[0-9a-f]{6}$/i.test(data.fields[k])) return; 193 $(k).value = data.fields[k].slice(0, 5000); 194 } 195 }); 196 logo = typeof data.logo === 'string' && /^data:image\/(png|jpeg);base64,[a-z0-9+/=]+$/i.test(data.logo) ? data.logo : ''; 197 platforms.forEach(p => { 198 const v = data.social && data.social[p[0]]; 199 if (v) { 200 $('check_' + p[0]).checked = v.enabled === true; 201 $('soc_' + p[0]).value = typeof v.value === 'string' ? v.value.slice(0, 2000) : ''; 202 } 203 }); 204 $('language').onchange(); 205 $('theme').onchange(); 1124 if ((data.version === 2 || data.version === 3) && Array.isArray(data.profiles) && data.profiles.length) { 1125 workspace = data; 1126 workspace.version = 3; 1127 } else if (data.version === 1 && data.fields) { 1128 workspace = { 1129 version: 3, 1130 activeProfileId: 'prof_1', 1131 brand: { 1132 company: data.fields.company || 'Example Studio', 1133 brand: data.fields.brand || '#172e49', 1134 accent: data.fields.accent || '#e7b52e', 1135 website: data.fields.website || '', 1136 address: data.fields.address || '', 1137 logo: typeof data.logo === 'string' ? data.logo : '', 1138 logoUrl: '', 1139 sigTemplate: 'classic', 1140 language: data.fields.language || 'tr', 1141 theme: data.fields.theme || 'auto' 1142 }, 1143 profiles: [ 1144 { 1145 id: 'prof_1', 1146 person: data.fields.person || '', 1147 job: data.fields.job || '', 1148 phone: data.fields.phone || '', 1149 email: data.fields.email || '', 1150 qrMode: data.fields.qrMode || 'url', 1151 qrUrl: data.fields.qrUrl || '', 1152 social: data.social || {} 1153 } 1154 ] 1155 }; 1156 } else { 1157 throw Error('Invalid format'); 1158 } 1159 loadWorkspaceIntoForm(); 206 1160 render(); 1161 setStatus(val('language') === 'en' ? 'Workspace loaded.' : 'Proje ve personel profilleri yüklendi.'); 207 1162 } catch { 208 alert('Invalid AwoSign JSON / Geçersiz proje dosyası');1163 setStatus(val('language') === 'en' ? 'Invalid AwoSign JSON file.' : 'Geçersiz AwoSign proje dosyası.'); 209 1164 } 210 1165 e.target.value = ''; 211 1166 }; 212 // Associate existing visible labels with their fields. 1167 213 1168 root.querySelectorAll('input,select,textarea').forEach(el => { 214 1169 const label = el.previousElementSibling; 215 1170 if (label && label.tagName === 'LABEL' && el.id) label.htmlFor = el.id; 216 1171 }); 1172 217 1173 render(); 218 1174 })(); -
awosign/trunk/awosign.php
r3783841 r3783861 2 2 /** 3 3 * Plugin Name: AwoSign 4 * Description: Free private workspace for email signatures, digital contact cards and two-sidedprintable business cards.5 * Version: 1. 0.14 * Description: Free workspace for multi-template email signatures, digital contact cards, multi-staff profiles, [awosign] shortcode, and 600 DPI printable business cards. 5 * Version: 1.3.0 6 6 * Requires at least: 6.4 7 7 * Requires PHP: 7.4 … … 11 11 * Text Domain: awosign 12 12 */ 13 if ( ! defined( 'ABSPATH' ) ) { exit; } 13 if ( ! defined( 'ABSPATH' ) ) { 14 exit; 15 } 16 17 define( 'AWOSIGN_VERSION', '1.3.0' ); 18 define( 'AWOSIGN_OPTION_KEY', 'awosign_workspace_data_v1' ); 19 20 /** 21 * Resolve WordPress Custom Logo or Site Icon URL if available. 22 */ 23 function awosign_get_wp_site_logo_url() { 24 $custom_logo_id = get_theme_mod( 'custom_logo' ); 25 if ( $custom_logo_id ) { 26 $url = wp_get_attachment_image_url( $custom_logo_id, 'full' ); 27 if ( $url ) { 28 return $url; 29 } 30 } 31 $site_icon = get_site_icon_url( 512 ); 32 return $site_icon ? $site_icon : ''; 33 } 34 35 /** 36 * Default workspace state structure. 37 */ 38 function awosign_get_default_workspace() { 39 $site_name = get_bloginfo( 'name' ); 40 $site_url = home_url( '/' ); 41 return array( 42 'version' => 3, 43 'activeProfileId' => 'prof_1', 44 'brand' => array( 45 'company' => $site_name ? $site_name : 'Example Studio', 46 'brand' => '#172e49', 47 'accent' => '#e7b52e', 48 'website' => $site_url ? $site_url : 'https://example.com', 49 'address' => '123 Example Street, Istanbul, TR', 50 'logo' => '', 51 'logoUrl' => awosign_get_wp_site_logo_url(), 52 'sigTemplate' => 'classic', 53 'language' => 'tr', 54 'theme' => 'auto', 55 ), 56 'profiles' => array( 57 array( 58 'id' => 'prof_1', 59 'person' => 'Alex Morgan', 60 'job' => 'Creative Director', 61 'phone' => '+90 555 123 4567', 62 'email' => 'alex@example.com', 63 'qrMode' => 'url', 64 'qrUrl' => '', 65 'social' => array( 66 'LinkedIn' => array( 'enabled' => true, 'value' => 'example' ), 67 'Instagram' => array( 'enabled' => true, 'value' => 'example' ), 68 'Facebook' => array( 'enabled' => true, 'value' => 'example' ), 69 ), 70 ), 71 ), 72 ); 73 } 74 75 /** 76 * Retrieve saved workspace or defaults. 77 */ 78 function awosign_get_workspace() { 79 $saved = get_option( AWOSIGN_OPTION_KEY, null ); 80 if ( ! is_array( $saved ) || empty( $saved['profiles'] ) || ! is_array( $saved['profiles'] ) ) { 81 return awosign_get_default_workspace(); 82 } 83 return $saved; 84 } 85 86 /** 87 * Register WP Admin Menu. 88 */ 14 89 function awosign_register_menu() { 15 add_menu_page( 'AwoSign', 'AwoSign', 'manage_options', 'awosign', 'awosign_render_page', 'dashicons-id-alt', 80 ); 90 add_menu_page( 91 'AwoSign', 92 'AwoSign', 93 'manage_options', 94 'awosign', 95 'awosign_render_page', 96 'dashicons-id-alt', 97 80 98 ); 16 99 } 17 100 add_action( 'admin_menu', 'awosign_register_menu' ); 101 102 /** 103 * Enqueue Admin Designer Assets + WP Media Library + Localize Saved Workspace & Nonce. 104 */ 18 105 function awosign_enqueue_assets( $hook ) { 19 if ( 'toplevel_page_awosign' !== $hook || ! current_user_can( 'manage_options' ) ) { return; } 20 wp_enqueue_style( 'awosign-designer', plugins_url( 'assets/designer.css', __FILE__ ), array(), '1.0.1' ); 106 if ( 'toplevel_page_awosign' !== $hook || ! current_user_can( 'manage_options' ) ) { 107 return; 108 } 109 wp_enqueue_media(); 110 wp_enqueue_style( 'awosign-designer', plugins_url( 'assets/designer.css', __FILE__ ), array(), AWOSIGN_VERSION ); 21 111 wp_enqueue_script( 'awosign-qrcode', plugins_url( 'assets/qrcode.js', __FILE__ ), array(), '1.4.4', true ); 22 wp_enqueue_script( 'awosign-designer', plugins_url( 'assets/designer.js', __FILE__ ), array( 'awosign-qrcode' ), '1.0.1', true ); 112 wp_enqueue_script( 'awosign-designer', plugins_url( 'assets/designer.js', __FILE__ ), array( 'awosign-qrcode' ), AWOSIGN_VERSION, true ); 113 114 wp_localize_script( 115 'awosign-designer', 116 'awosignWpConfig', 117 array( 118 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 119 'nonce' => wp_create_nonce( 'awosign_save_workspace_nonce' ), 120 'siteLogoUrl' => esc_url_raw( awosign_get_wp_site_logo_url() ), 121 'siteName' => sanitize_text_field( get_bloginfo( 'name' ) ), 122 'siteUrl' => esc_url_raw( home_url( '/' ) ), 123 'workspace' => awosign_get_workspace(), 124 ) 125 ); 23 126 } 24 127 add_action( 'admin_enqueue_scripts', 'awosign_enqueue_assets' ); 128 129 /** 130 * Render Admin Page. 131 */ 25 132 function awosign_render_page() { 26 133 if ( ! current_user_can( 'manage_options' ) ) { … … 29 136 require __DIR__ . '/designer-view.php'; 30 137 } 138 139 /** 140 * Sanitize and Save Workspace via AJAX (manage_options + Nonce protected). 141 */ 142 function awosign_ajax_save_workspace() { 143 if ( ! current_user_can( 'manage_options' ) ) { 144 wp_send_json_error( array( 'message' => 'Yetkisiz işlem (manage_options gerekli).' ), 403 ); 145 } 146 check_ajax_referer( 'awosign_save_workspace_nonce', 'nonce' ); 147 148 $raw_json = isset( $_POST['workspace'] ) ? wp_unslash( $_POST['workspace'] ) : ''; 149 $decoded = json_decode( $raw_json, true ); 150 if ( ! is_array( $decoded ) || empty( $decoded['profiles'] ) || ! is_array( $decoded['profiles'] ) ) { 151 wp_send_json_error( array( 'message' => 'Geçersiz veri formatı.' ), 400 ); 152 } 153 154 $brand_in = isset( $decoded['brand'] ) && is_array( $decoded['brand'] ) ? $decoded['brand'] : array(); 155 $brand_color = isset( $brand_in['brand'] ) && preg_match( '/^#[0-9a-fA-F]{6}$/', $brand_in['brand'] ) ? $brand_in['brand'] : '#172e49'; 156 $accent_color = isset( $brand_in['accent'] ) && preg_match( '/^#[0-9a-fA-F]{6}$/', $brand_in['accent'] ) ? $brand_in['accent'] : '#e7b52e'; 157 158 $logo_raw = isset( $brand_in['logo'] ) ? (string) $brand_in['logo'] : ''; 159 $logo_clean = ''; 160 if ( preg_match( '/^data:image\/(png|jpeg|webp|svg\+xml);base64,[a-zA-Z0-9+\/=]+$/', $logo_raw ) && strlen( $logo_raw ) < 4500000 ) { 161 $logo_clean = $logo_raw; 162 } elseif ( filter_var( $logo_raw, FILTER_VALIDATE_URL ) ) { 163 $logo_clean = esc_url_raw( $logo_raw ); 164 } 165 166 $sig_tpl = isset( $brand_in['sigTemplate'] ) && in_array( $brand_in['sigTemplate'], array( 'classic', 'compact', 'banner' ), true ) 167 ? $brand_in['sigTemplate'] 168 : 'classic'; 169 170 $clean_brand = array( 171 'company' => sanitize_text_field( isset( $brand_in['company'] ) ? $brand_in['company'] : '' ), 172 'brand' => $brand_color, 173 'accent' => $accent_color, 174 'website' => esc_url_raw( isset( $brand_in['website'] ) ? $brand_in['website'] : '' ), 175 'address' => sanitize_textarea_field( isset( $brand_in['address'] ) ? $brand_in['address'] : '' ), 176 'logo' => $logo_clean, 177 'logoUrl' => esc_url_raw( isset( $brand_in['logoUrl'] ) ? $brand_in['logoUrl'] : '' ), 178 'sigTemplate' => $sig_tpl, 179 'language' => ( isset( $brand_in['language'] ) && 'en' === $brand_in['language'] ) ? 'en' : 'tr', 180 'theme' => ( isset( $brand_in['theme'] ) && in_array( $brand_in['theme'], array( 'auto', 'dark', 'light' ), true ) ) ? $brand_in['theme'] : 'auto', 181 ); 182 183 $clean_profiles = array(); 184 foreach ( array_slice( $decoded['profiles'], 0, 50 ) as $idx => $p ) { 185 if ( ! is_array( $p ) ) { 186 continue; 187 } 188 $pid = isset( $p['id'] ) ? preg_replace( '/[^a-zA-Z0-9_-]/', '', (string) $p['id'] ) : ''; 189 if ( ! $pid ) { 190 $pid = 'prof_' . ( $idx + 1 ); 191 } 192 $social_in = isset( $p['social'] ) && is_array( $p['social'] ) ? $p['social'] : array(); 193 $clean_social = array(); 194 foreach ( $social_in as $plat => $sdata ) { 195 $plat_key = preg_replace( '/[^a-zA-Z0-9]/', '', (string) $plat ); 196 if ( $plat_key && is_array( $sdata ) ) { 197 $clean_social[ $plat_key ] = array( 198 'enabled' => ! empty( $sdata['enabled'] ), 199 'value' => sanitize_text_field( isset( $sdata['value'] ) ? $sdata['value'] : '' ), 200 ); 201 } 202 } 203 $clean_profiles[] = array( 204 'id' => $pid, 205 'person' => sanitize_text_field( isset( $p['person'] ) ? $p['person'] : '' ), 206 'job' => sanitize_text_field( isset( $p['job'] ) ? $p['job'] : '' ), 207 'phone' => sanitize_text_field( isset( $p['phone'] ) ? $p['phone'] : '' ), 208 'email' => sanitize_email( isset( $p['email'] ) ? $p['email'] : '' ), 209 'qrMode' => ( isset( $p['qrMode'] ) && 'vcard' === $p['qrMode'] ) ? 'vcard' : 'url', 210 'qrUrl' => esc_url_raw( isset( $p['qrUrl'] ) ? $p['qrUrl'] : '' ), 211 'social' => $clean_social, 212 ); 213 } 214 215 if ( empty( $clean_profiles ) ) { 216 $clean_profiles = awosign_get_default_workspace()['profiles']; 217 } 218 219 $active_id = isset( $decoded['activeProfileId'] ) ? preg_replace( '/[^a-zA-Z0-9_-]/', '', (string) $decoded['activeProfileId'] ) : $clean_profiles[0]['id']; 220 221 $to_save = array( 222 'version' => 3, 223 'activeProfileId' => $active_id, 224 'brand' => $clean_brand, 225 'profiles' => $clean_profiles, 226 'updatedAt' => current_time( 'mysql' ), 227 ); 228 229 update_option( AWOSIGN_OPTION_KEY, $to_save, false ); 230 231 wp_send_json_success( 232 array( 233 'message' => 'AwoSign profilleri WordPress veritabanına kaydedildi.', 234 'workspace' => $to_save, 235 ) 236 ); 237 } 238 add_action( 'wp_ajax_awosign_save_workspace', 'awosign_ajax_save_workspace' ); 239 240 /** 241 * Frontend Shortcode: [awosign id="prof_1" theme="auto"] 242 */ 243 function awosign_render_shortcode( $atts ) { 244 $atts = shortcode_atts( 245 array( 246 'id' => '', 247 'theme' => 'auto', 248 ), 249 $atts, 250 'awosign' 251 ); 252 253 $workspace = awosign_get_workspace(); 254 $brand = isset( $workspace['brand'] ) ? $workspace['brand'] : array(); 255 $profiles = isset( $workspace['profiles'] ) && is_array( $workspace['profiles'] ) ? $workspace['profiles'] : array(); 256 if ( empty( $profiles ) ) { 257 return ''; 258 } 259 260 $target_id = $atts['id'] ? $atts['id'] : ( isset( $workspace['activeProfileId'] ) ? $workspace['activeProfileId'] : '' ); 261 $profile = $profiles[0]; 262 foreach ( $profiles as $candidate ) { 263 if ( isset( $candidate['id'] ) && $candidate['id'] === $target_id ) { 264 $profile = $candidate; 265 break; 266 } 267 } 268 269 wp_enqueue_style( 'awosign-designer', plugins_url( 'assets/designer.css', __FILE__ ), array(), AWOSIGN_VERSION ); 270 wp_enqueue_script( 'awosign-qrcode', plugins_url( 'assets/qrcode.js', __FILE__ ), array(), '1.4.4', true ); 271 272 $company = isset( $brand['company'] ) ? $brand['company'] : ''; 273 $brand_color = isset( $brand['brand'] ) ? $brand['brand'] : '#172e49'; 274 $accent_color = isset( $brand['accent'] ) ? $brand['accent'] : '#e7b52e'; 275 $website = isset( $brand['website'] ) ? $brand['website'] : ''; 276 $address = isset( $brand['address'] ) ? $brand['address'] : ''; 277 $logo = ! empty( $brand['logoUrl'] ) ? $brand['logoUrl'] : ( isset( $brand['logo'] ) ? $brand['logo'] : '' ); 278 279 $person = isset( $profile['person'] ) ? $profile['person'] : ''; 280 $job = isset( $profile['job'] ) ? $profile['job'] : ''; 281 $phone = isset( $profile['phone'] ) ? $profile['phone'] : ''; 282 $email = isset( $profile['email'] ) ? $profile['email'] : ''; 283 $qr_mode = isset( $profile['qrMode'] ) ? $profile['qrMode'] : 'url'; 284 $qr_url = ! empty( $profile['qrUrl'] ) ? $profile['qrUrl'] : $website; 285 286 $vcard_lines = array( 287 'BEGIN:VCARD', 288 'VERSION:3.0', 289 'FN:' . $person, 290 'ORG:' . $company, 291 'TITLE:' . $job, 292 ); 293 if ( $phone ) { 294 $vcard_lines[] = 'TEL;TYPE=WORK,VOICE:' . $phone; 295 } 296 if ( $email ) { 297 $vcard_lines[] = 'EMAIL:' . $email; 298 } 299 if ( $website ) { 300 $vcard_lines[] = 'URL:' . $website; 301 } 302 if ( $address ) { 303 $vcard_lines[] = 'ADR;TYPE=WORK:;;' . str_replace( array( "\r\n", "\r", "\n" ), ' ', $address ) . ';;;;'; 304 } 305 $vcard_lines[] = 'END:VCARD'; 306 $vcard_payload = implode( "\r\n", $vcard_lines ); 307 308 $qr_payload = ( 'vcard' === $qr_mode ) ? $vcard_payload : $qr_url; 309 $uid = 'awosign-sc-' . wp_unique_id(); 310 $theme_attr = in_array( $atts['theme'], array( 'auto', 'dark', 'light' ), true ) ? $atts['theme'] : 'auto'; 311 $wa_text = rawurlencode( trim( $person . ' — ' . $job . ' (' . $company . ')' . "\n" . $phone . "\n" . $email . "\n" . $website ) ); 312 313 ob_start(); 314 ?> 315 <div id="<?php echo esc_attr( $uid ); ?>" class="awosign-shortcode-card" data-theme="<?php echo esc_attr( $theme_attr ); ?>" style="--brand:<?php echo esc_attr( $brand_color ); ?>;--accent:<?php echo esc_attr( $accent_color ); ?>;max-width:540px;margin:16px auto;font-family:system-ui,-apple-system,Segoe UI,Arial,sans-serif;"> 316 <div class="digital" style="width:100%;background:var(--brand);color:#fff;border-top:5px solid var(--accent);padding:24px;border-radius:12px;display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;box-shadow:0 6px 24px rgba(0,0,0,0.18);"> 317 <div class="details" style="line-height:1.65;font-size:13px;flex:1;min-width:210px;"> 318 <?php if ( $logo ) : ?> 319 <img src="<?php echo esc_url( $logo ); ?>" alt="<?php echo esc_attr( $company ); ?>" style="max-width:110px;max-height:70px;object-fit:contain;display:block;margin-bottom:8px;"> 320 <?php else : ?> 321 <div style="font-size:20px;font-weight:700;margin-bottom:6px;"><?php echo esc_html( $company ); ?></div> 322 <?php endif; ?> 323 <strong style="font-size:20px;display:block;"><?php echo esc_html( $person ); ?></strong> 324 <span><?php echo esc_html( $job ); ?><?php echo $company ? ' · ' . esc_html( $company ) : ''; ?></span><br> 325 <?php if ( $phone ) : ?> 326 <a href="tel:<?php echo esc_attr( preg_replace( '/[^0-9+]/', '', $phone ) ); ?>" style="color:#fff;text-decoration:none;"><?php echo esc_html( $phone ); ?></a><br> 327 <?php endif; ?> 328 <?php if ( $email ) : ?> 329 <a href="mailto:<?php echo esc_attr( $email ); ?>" style="color:#fff;text-decoration:none;"><?php echo esc_html( $email ); ?></a><br> 330 <?php endif; ?> 331 <?php if ( $website ) : ?> 332 <a href="<?php echo esc_url( $website ); ?>" target="_blank" rel="noopener noreferrer" style="color:#dbe7f5;text-decoration:none;"><?php echo esc_html( $website ); ?></a><br> 333 <?php endif; ?> 334 <?php if ( $address ) : ?> 335 <span style="font-size:12px;color:#cbd9ea;"><?php echo esc_html( $address ); ?></span> 336 <?php endif; ?> 337 <div style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap;"> 338 <button type="button" class="awosign-sc-vcf-btn" style="background:var(--accent);color:#102032;border:none;padding:7px 13px;border-radius:6px;font-weight:700;font-size:12px;cursor:pointer;"> 339 📇 Rehbere Ekle (VCF) 340 </button> 341 <a href="https://wa.me/?text=<?php echo esc_attr( $wa_text ); ?>" target="_blank" rel="noopener noreferrer" style="background:#25D366;color:#fff;text-decoration:none;padding:7px 13px;border-radius:6px;font-weight:700;font-size:12px;display:inline-block;"> 342 💬 WhatsApp Paylaş 343 </a> 344 </div> 345 </div> 346 <div class="awosign-sc-qr" data-qr="<?php echo esc_attr( $qr_payload ); ?>" style="background:#fff;padding:6px;border-radius:8px;width:108px;height:108px;display:flex;align-items:center;justify-content:center;flex-shrink:0;"></div> 347 </div> 348 </div> 349 <script> 350 (function(){ 351 var wrap = document.getElementById(<?php echo wp_json_encode( $uid ); ?>); 352 if (!wrap) return; 353 var qrBox = wrap.querySelector('.awosign-sc-qr'); 354 if (qrBox && typeof qrcode === 'function') { 355 try { 356 var q = qrcode(0, 'L'); 357 q.addData(qrBox.getAttribute('data-qr') || '', 'Byte'); 358 q.make(); 359 qrBox.innerHTML = '<img src="' + q.createDataURL(4, 4) + '" alt="QR" style="width:96px;height:96px;object-fit:contain;">'; 360 } catch (e) {} 361 } 362 var btn = wrap.querySelector('.awosign-sc-vcf-btn'); 363 if (btn) { 364 btn.addEventListener('click', function(){ 365 var vcfData = <?php echo wp_json_encode( $vcard_payload ); ?>; 366 var blob = new Blob([vcfData], {type: 'text/vcard;charset=utf-8'}); 367 var a = document.createElement('a'); 368 a.href = URL.createObjectURL(blob); 369 a.download = <?php echo wp_json_encode( sanitize_title( $person ? $person : 'contact' ) . '.vcf' ); ?>; 370 document.body.appendChild(a); 371 a.click(); 372 a.remove(); 373 }); 374 } 375 })(); 376 </script> 377 <?php 378 return ob_get_clean(); 379 } 380 add_shortcode( 'awosign', 'awosign_render_shortcode' ); -
awosign/trunk/designer-view.php
r3783841 r3783861 2 2 if ( ! defined( 'ABSPATH' ) ) { exit; } 3 3 ?> 4 <div id="awosign-root"><header><div><h1>AwoSign <span style="color:#e7b52e">Free 1.0.1</span> — Kurumsal Kimlik</h1><small>Tek marka kimliği · E-posta imzası · Dijital kartvizit · Baskı kartviziti · WordPress Free</small></div><div class="two" style="min-width:245px"><div><label for="language">Dil / Language</label><select id="language"><option value="tr">Türkçe</option><option value="en">English</option></select></div><div><label for="theme">Görünüm</label><select id="theme"><option value="dark">Koyu</option><option value="light">Açık</option></select></div></div></header><main><section class="panel editor"><h2 data-tr="Kurumsal kimlik ve iletişim" data-en="Brand identity and contact">Kurumsal kimlik ve iletişim</h2><label data-tr="Şirket logosu (PNG / JPG)" data-en="Company logo (PNG / JPG)">Şirket logosu (PNG / JPG)</label><input id="logoFile" type="file" accept="image/png,image/jpeg"><div class="two"><div><label data-tr="Ana renk" data-en="Primary color">Ana renk</label><input type="color" id="brand" value="#172e49"></div><div><label data-tr="Vurgu rengi" data-en="Accent color">Vurgu rengi</label><input type="color" id="accent" value="#e7b52e"></div></div><label data-tr="Şirket adı" data-en="Company name">Şirket adı</label><input id="company" value="Example Studio"><div class="two"><div><label data-tr="Ad soyad" data-en="Full name">Ad soyad</label><input id="person" value="Alex Morgan"></div><div><label data-tr="Unvan" data-en="Job title">Unvan</label><input id="job" value="Creative Director"></div></div><div class="two"><div><label data-tr="Telefon (ülke koduyla)" data-en="Phone (with country code)">Telefon (ülke koduyla)</label><input type="tel" id="phone" value="+44 555 123 4567"></div><div><label>E-posta / Email</label><input type="email" id="email" value="alex@example.com"></div></div><label data-tr="Web sitesi" data-en="Website">Web sitesi</label><input id="website" value="https://example.com"><label data-tr="Adres" data-en="Address">Adres</label><textarea id="address" rows="2">123 Example Street, London, UK</textarea><label data-tr="QR hedef adresi (boşsa web sitesi)" data-en="QR destination (defaults to website)">QR hedef adresi (boşsa web sitesi)</label><input id="qrUrl" placeholder="https://..."><h3 data-tr="Gösterilecek sosyal medya ikonları" data-en="Social icons to display">Gösterilecek sosyal medya ikonları</h3><div class="social" id="socials"></div><p class="hint" data-tr="Tüm materyaller aynı logo ve renkleri kullanır. Sosyal bağlantılar için platform adresi + kullanıcı adı girilir." data-en="All materials share one logo and color palette. Enter a username or full URL for social profiles.">Tüm materyaller aynı logo ve renkleri kullanır. Sosyal bağlantılar için platform adresi + kullanıcı adı girilir.</p><div id="socialInputs"></div><div class="buttons"><button id="saveProject" data-tr="Proje indir (JSON)" data-en="Download project (JSON)">Proje indir (JSON)</button><label for="loadProject" data-tr="Proje yükle" data-en="Load project">Proje yükle</label><input id="loadProject" type="file" accept="application/json,.json"></div></section><section class="panel"><div class="tabs"><button class="active" data-tab="Signature" data-tr="E-posta imzası" data-en="Email signature">E-posta imzası</button><button data-tab="Digital" data-tr="Dijital kartvizit" data-en="Digital card">Dijital kartvizit</button><button data-tab="Print" data-tr="Baskı kartviziti (iki yüz)" data-en="Print card (two sides)">Baskı kartviziti (iki yüz)</button></div><div id="viewSignature" class="stage"><h3 data-tr="E-posta imzası — 600 px" data-en="Email signature — 600 px">E-posta imzası — 600 px</h3><div id="sig" class="signature"></div><div class="buttons"><button id="copySig" data-tr="İmzayı kopyala" data-en="Copy signature">İmzayı kopyala</button><button id="downloadSig" data-tr="HTML indir" data-en="Download HTML">HTML indir</button></div></div><div id="viewDigital" class="stage hidden"><h3 data-tr="Dijital kartvizit — 540 px" data-en="Digital business card — 540 px">Dijital kartvizit — 540 px</h3><div id="digital" class="digital"></div><div class="buttons"><button id="vcf" data-tr="VCF indir" data-en="Download VCF">VCF indir</button></div></div><div id="viewPrint" class="stage hidden"><h3 data-tr="Baskı kartviziti — ön ve arka yüz" data-en="Print business card — front and back">Baskı kartviziti — ön ve arka yüz</h3><div class="cards"><div class="cardwrap"><h3 data-tr="ÖN YÜZ" data-en="FRONT">ÖN YÜZ</h3><div id="front" class="card front"></div></div><div class="cardwrap"><h3 data-tr="ARKA YÜZ" data-en="BACK">ARKA YÜZ</h3><div id="back" class="card back"></div></div></div><div class="buttons"><button id="print" data-tr="İki yüzü PDF olarak yazdır" data-en="Print both sides to PDF">İki yüzü PDF olarak yazdır</button><button id="vcf2" data-tr="VCF indir" data-en="Download VCF">VCF indir</button></div><p class="hint" data-tr="Baskı prototipi: 85 × 55 mm bitmiş ölçü + 3 mm taşma payı. PDF renk ayrımı, kesim işaretleri ve QR baskı kalitesi henüz matbaa onaylı değildir." data-en="Print prototype: 85 × 55 mm trim + 3 mm bleed. Color separation, crop marks and QR print quality are not press-validated.">Baskı prototipi: 85 × 55 mm bitmiş ölçü + 3 mm taşma payı. PDF renk ayrımı, kesim işaretleri ve QR baskı kalitesi henüz matbaa onaylı değildir.</p></div><p class="muted" data-tr="QR kodları cihazınızda üretilir. Bilgiler WordPress veritabanına kaydedilmez. E-posta istemcileri gömülü logoları engelleyebilir; imzayı kendi istemcinizde test edin." data-en="QR codes are generated on your device. Details are not saved to the WordPress database. Email clients may block embedded logos; test your signature in your email client.">QR kodları cihazınızda üretilir. Bilgiler WordPress veritabanına kaydedilmez. E-posta istemcileri gömülü logoları engelleyebilir; imzayı kendi istemcinizde test edin.</p></section></main><p class="privacy" data-tr="Ücretsiz · Hesap veya API anahtarı gerekmez · Form verileri sunucuya gönderilmez. Dış profil bağlantıları yalnız tıklandığında açılır." data-en="Free · No account or API key required · Form data is not sent to the server. External profile links open only when clicked.">Ücretsiz · Hesap veya API anahtarı gerekmez · Form verileri sunucuya gönderilmez. Dış profil bağlantıları yalnız tıklandığında açılır.</p></div> 4 <div id="awosign-root" data-theme="auto"> 5 <header> 6 <div> 7 <h1>AwoSign <span style="color:#e7b52e">Free 1.3.0</span> — Kurumsal Kimlik, Çoklu İmza Şablonu & Matbaa Stüdyosu</h1> 8 <small data-tr="3 E-posta imza şablonu · Medya Kütüphanesi URL logosu · Çoklu personel · WhatsApp paylaşım · A4 10'lu & 600 DPI matbaa kartviziti" data-en="3 Email signature templates · Media Library URL logo · Multi-staff · WhatsApp share · A4 10-up & 600 DPI print-shop card">3 E-posta imza şablonu · Medya Kütüphanesi URL logosu · Çoklu personel · WhatsApp paylaşım · A4 10'lu & 600 DPI matbaa kartviziti</small> 9 </div> 10 <div class="two" style="min-width:290px"> 11 <div> 12 <label for="language">Dil / Language</label> 13 <select id="language"> 14 <option value="tr">Türkçe</option> 15 <option value="en">English</option> 16 </select> 17 </div> 18 <div> 19 <label for="theme" data-tr="Görünüm (Day/Night)" data-en="Appearance (Day/Night)">Görünüm (Day/Night)</label> 20 <select id="theme"> 21 <option value="auto" data-tr="◐ Otomatik (Sistem/Sayfa)" data-en="◐ Auto (System/Page)">◐ Otomatik (Sistem/Sayfa)</option> 22 <option value="dark" data-tr="☾ Gece (Koyu)" data-en="☾ Night (Dark)">☾ Gece (Koyu)</option> 23 <option value="light" data-tr="☀ Gündüz (Açık)" data-en="☀ Day (Light)">☀ Gündüz (Açık)</option> 24 </select> 25 </div> 26 </div> 27 </header> 28 <main> 29 <section class="panel editor"> 30 <div class="as-staff-bar"> 31 <div class="as-staff-header"> 32 <h2 data-tr="👥 Personel Profilleri & WordPress Kaydı" data-en="👥 Staff Profiles & WordPress Storage">👥 Personel Profilleri & WordPress Kaydı</h2> 33 <span class="as-badge" id="profileCountBadge">1 Profil</span> 34 </div> 35 <label for="profileSelect" data-tr="Aktif Personel Profili" data-en="Active Staff Profile">Aktif Personel Profili</label> 36 <select id="profileSelect"></select> 37 <div class="buttons" style="margin-top:10px"> 38 <button type="button" id="addProfile" data-tr="➕ Yeni Personel" data-en="➕ New Staff">➕ Yeni Personel</button> 39 <button type="button" id="deleteProfile" data-tr="🗑️ Profili Sil" data-en="🗑️ Delete Profile">🗑️ Profili Sil</button> 40 <button type="button" id="saveWpDb" class="as-btn-primary" data-tr="💾 WordPress'e Kaydet" data-en="💾 Save to WordPress">💾 WordPress'e Kaydet</button> 41 </div> 42 <div class="as-shortcode-box"> 43 <label for="shortcodeDisplay" data-tr="Sayfa / Yazı Kısa Kodu (Shortcode)" data-en="Page / Post Shortcode">Sayfa / Yazı Kısa Kodu (Shortcode)</label> 44 <div class="as-shortcode-row"> 45 <input id="shortcodeDisplay" readonly value='[awosign id="prof_1"]'> 46 <button type="button" id="copyShortcode" data-tr="📋 Kopyala" data-en="📋 Copy">📋 Kopyala</button> 47 </div> 48 </div> 49 </div> 50 51 <h2 data-tr="🏢 Ortak Kurumsal Kimlik (Tüm Personeller)" data-en="🏢 Shared Brand Identity (All Staff)" style="margin-top:18px">🏢 Ortak Kurumsal Kimlik (Tüm Personeller)</h2> 52 <label data-tr="Şirket logosu dosyası (Baskı / PNG için)" data-en="Company logo file (For Print / PNG)">Şirket logosu dosyası (Baskı / PNG için)</label> 53 <div class="as-logo-row"> 54 <input id="logoFile" type="file" accept="image/png,image/jpeg"> 55 <button type="button" id="useSiteLogo" data-tr="🌐 Site Logosunu Çek" data-en="🌐 Use Site Logo">🌐 Site Logosunu Çek</button> 56 </div> 57 <label for="logoUrl" data-tr="E-posta imzası için Logo URL (Gmail/Outlook Base64 engeline takılmaz)" data-en="Logo URL for Email Signatures (Avoids Gmail/Outlook Base64 block)">E-posta imzası için Logo URL (Gmail/Outlook Base64 engeline takılmaz)</label> 58 <div class="as-logo-row"> 59 <input id="logoUrl" placeholder="https://siteniz.com/wp-content/uploads/logo.png"> 60 <button type="button" id="pickWpMediaLogo" data-tr="🖼️ Medya Seç" data-en="🖼️ Media Library">🖼️ Medya Seç</button> 61 </div> 62 <div class="two"> 63 <div> 64 <label data-tr="Ana renk" data-en="Primary color">Ana renk</label> 65 <input type="color" id="brand" value="#172e49"> 66 </div> 67 <div> 68 <label data-tr="Vurgu rengi" data-en="Accent color">Vurgu rengi</label> 69 <input type="color" id="accent" value="#e7b52e"> 70 </div> 71 </div> 72 <label data-tr="Şirket adı" data-en="Company name">Şirket adı</label> 73 <input id="company" value="Example Studio"> 74 <label data-tr="Web sitesi" data-en="Website">Web sitesi</label> 75 <input id="website" value="https://example.com"> 76 <label data-tr="Adres" data-en="Address">Adres</label> 77 <textarea id="address" rows="2">123 Example Street, Istanbul, TR</textarea> 78 79 <h2 data-tr="👤 Seçili Personel Bilgileri" data-en="👤 Selected Staff Details" style="margin-top:18px">👤 Seçili Personel Bilgileri</h2> 80 <div class="two"> 81 <div> 82 <label data-tr="Ad soyad" data-en="Full name">Ad soyad</label> 83 <input id="person" value="Alex Morgan"> 84 </div> 85 <div> 86 <label data-tr="Unvan" data-en="Job title">Unvan</label> 87 <input id="job" value="Creative Director"> 88 </div> 89 </div> 90 <div class="two"> 91 <div> 92 <label data-tr="Telefon (ülke koduyla)" data-en="Phone (with country code)">Telefon (ülke koduyla)</label> 93 <input type="tel" id="phone" value="+90 555 123 4567"> 94 </div> 95 <div> 96 <label>E-posta / Email</label> 97 <input type="email" id="email" value="alex@example.com"> 98 </div> 99 </div> 100 <div class="two"> 101 <div> 102 <label for="qrMode" data-tr="QR Kod İçeriği" data-en="QR Code Content">QR Kod İçeriği</label> 103 <select id="qrMode"> 104 <option value="url" data-tr="Web sitesi / Özel URL" data-en="Website / Custom URL">Web sitesi / Özel URL</option> 105 <option value="vcard" data-tr="vCard Rehber (Kamerayla Rehbere Ekle)" data-en="vCard Contact (Scan to Save)">vCard Rehber (Kamerayla Rehbere Ekle)</option> 106 </select> 107 </div> 108 <div> 109 <label data-tr="QR hedef URL (boşsa web sitesi)" data-en="QR destination (defaults to website)">QR hedef URL (boşsa web sitesi)</label> 110 <input id="qrUrl" placeholder="https://..."> 111 </div> 112 </div> 113 <h3 data-tr="Gösterilecek sosyal medya ikonları" data-en="Social icons to display">Gösterilecek sosyal medya ikonları</h3> 114 <div class="social" id="socials"></div> 115 <p class="hint" data-tr="Tüm personeller aynı şirket logosu ve renklerini paylaşır. Sosyal bağlantılar seçili personele özeldir." data-en="All staff members share the company logo and color palette. Social links belong to the selected staff profile.">Tüm personeller aynı şirket logosu ve renklerini paylaşır. Sosyal bağlantılar seçili personele özeldir.</p> 116 <div id="socialInputs"></div> 117 <div class="buttons"> 118 <button type="button" id="saveProject" data-tr="📦 Tüm Profilleri İndir (JSON)" data-en="📦 Export All Profiles (JSON)">📦 Tüm Profilleri İndir (JSON)</button> 119 <label for="loadProject" class="as-file-btn" data-tr="📂 JSON Yükle" data-en="📂 Import JSON">📂 JSON Yükle</label> 120 <input id="loadProject" type="file" accept="application/json,.json" class="hidden"> 121 </div> 122 </section> 123 <section class="panel"> 124 <div class="tabs"> 125 <button class="active" data-tab="Signature" data-tr="E-posta imzası" data-en="Email signature">E-posta imzası</button> 126 <button data-tab="Digital" data-tr="Dijital kartvizit" data-en="Digital card">Dijital kartvizit</button> 127 <button data-tab="Print" data-tr="Baskı & Çoğaltma (İki Yüz)" data-en="Print & Duplication (Two Sides)">Baskı & Çoğaltma (İki Yüz)</button> 128 </div> 129 <div id="viewSignature" class="stage"> 130 <div style="display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:14px;"> 131 <h3 style="margin:0" data-tr="E-posta imzası — 600 px" data-en="Email signature — 600 px">E-posta imzası — 600 px</h3> 132 <div style="min-width:250px;"> 133 <select id="sigTemplate" aria-label="E-posta imza şablonu"> 134 <option value="classic" data-tr="Şablon 1: Klasik Sol Logo (600 px)" data-en="Template 1: Classic Left Logo (600 px)">Şablon 1: Klasik Sol Logo (600 px)</option> 135 <option value="compact" data-tr="Şablon 2: Modern Kompakt Şerit (520 px)" data-en="Template 2: Modern Compact Bar (520 px)">Şablon 2: Modern Kompakt Şerit (520 px)</option> 136 <option value="banner" data-tr="Şablon 3: Kurumsal Başlık & QR (600 px)" data-en="Template 3: Corporate Banner & QR (600 px)">Şablon 3: Kurumsal Başlık & QR (600 px)</option> 137 </select> 138 </div> 139 </div> 140 <div id="sig" class="signature"></div> 141 <div class="buttons"> 142 <button id="copySig" data-tr="📋 İmzayı Kopyala" data-en="📋 Copy Signature">📋 İmzayı Kopyala</button> 143 <button id="downloadSig" data-tr="📄 HTML İndir" data-en="📄 Download HTML">📄 HTML İndir</button> 144 </div> 145 </div> 146 <div id="viewDigital" class="stage hidden"> 147 <h3 data-tr="Dijital kartvizit — 540 px" data-en="Digital business card — 540 px">Dijital kartvizit — 540 px</h3> 148 <div id="digital" class="digital"></div> 149 <div class="buttons"> 150 <button id="downloadDigitalPng" data-tr="🖼️ Dijital Kartvizit PNG İndir" data-en="🖼️ Download Digital Card PNG">🖼️ Dijital Kartvizit PNG İndir</button> 151 <button id="vcf" class="btn-secondary" data-tr="📇 VCF Rehber İndir" data-en="📇 Download VCF">📇 VCF Rehber İndir</button> 152 <button id="shareWhatsapp" class="as-btn-primary" data-tr="💬 WhatsApp ile Paylaş" data-en="💬 Share via WhatsApp">💬 WhatsApp ile Paylaş</button> 153 </div> 154 </div> 155 <div id="viewPrint" class="stage hidden"> 156 <h3 data-tr="Baskı kartviziti — 85 × 55 mm ön ve arka yüz" data-en="Print business card — 85 × 55 mm front and back">Baskı kartviziti — 85 × 55 mm ön ve arka yüz</h3> 157 <div class="cards"> 158 <div class="cardwrap"> 159 <h3 data-tr="ÖN YÜZ (85 × 55 mm)" data-en="FRONT (85 × 55 mm)">ÖN YÜZ (85 × 55 mm)</h3> 160 <div id="front" class="card front"></div> 161 </div> 162 <div class="cardwrap"> 163 <h3 data-tr="ARKA YÜZ (85 × 55 mm)" data-en="BACK (85 × 55 mm)">ARKA YÜZ (85 × 55 mm)</h3> 164 <div id="back" class="card back"></div> 165 </div> 166 </div> 167 <div class="as-print-panel"> 168 <h4 data-tr="🖨️ Matbaa Baskı & Çoklu Çoğaltma Ayarları" data-en="🖨️ Print-Shop & Bulk Duplication Settings">🖨️ Matbaa Baskı & Çoklu Çoğaltma Ayarları</h4> 169 <div class="as-print-grid"> 170 <div> 171 <label for="printLayout" data-tr="Sayfa Düzeni / Çoğaltma Modu" data-en="Page Layout / Duplication Mode">Sayfa Düzeni / Çoğaltma Modu</label> 172 <select id="printLayout"> 173 <option value="a4_10up_duplex" data-tr="A4 Çift Yön Çoklu (2×5 = 10 Kart/Sayfa Ön+Arka)" data-en="A4 Duplex Sheet (2×5 = 10 Cards/Sheet Front+Back)">A4 Çift Yön Çoklu (2×5 = 10 Kart/Sayfa Ön+Arka)</option> 174 <option value="a4_10up_front" data-tr="A4 Tek Yön Çoklu — Sadece Ön Yüz (10 Kart/Sayfa)" data-en="A4 Single Side — Front Only (10 Cards/Sheet)">A4 Tek Yön Çoklu — Sadece Ön Yüz (10 Kart/Sayfa)</option> 175 <option value="a4_10up_back" data-tr="A4 Tek Yön Çoklu — Sadece Arka Yüz (10 Kart/Sayfa)" data-en="A4 Single Side — Back Only (10 Cards/Sheet)">A4 Tek Yön Çoklu — Sadece Arka Yüz (10 Kart/Sayfa)</option> 176 <option value="single_bleed" data-tr="Matbaa Tekli Kalıp (91×61 mm — 3 mm Taşma Paylı + Kroslu)" data-en="Print-Shop Single Master (91×61 mm — 3 mm Bleed + Crop Marks)">Matbaa Tekli Kalıp (91×61 mm — 3 mm Taşma Paylı + Kroslu)</option> 177 </select> 178 </div> 179 <div> 180 <label for="printCopies" data-tr="Çoğaltma Adedi (A4 Set Sayısı)" data-en="Copy Multiplier (A4 Sheet Sets)">Çoğaltma Adedi (A4 Set Sayısı)</label> 181 <select id="printCopies"> 182 <option value="1" data-tr="1 Set (10 Adet Kartvizit)" data-en="1 Set (10 Business Cards)">1 Set (10 Adet Kartvizit)</option> 183 <option value="2" data-tr="2 Set (20 Adet Kartvizit)" data-en="2 Sets (20 Business Cards)">2 Set (20 Adet Kartvizit)</option> 184 <option value="5" data-tr="5 Set (50 Adet Kartvizit)" data-en="5 Sets (50 Business Cards)">5 Set (50 Adet Kartvizit)</option> 185 <option value="10" data-tr="10 Set (100 Adet Kartvizit)" data-en="10 Sets (100 Business Cards)">10 Set (100 Adet Kartvizit)</option> 186 </select> 187 </div> 188 <div> 189 <label for="cropMarks" data-tr="Kesim Çizgileri (Kros)" data-en="Crop Marks / Cut Guides">Kesim Çizgileri (Kros)</label> 190 <select id="cropMarks"> 191 <option value="1" data-tr="Göster (Giyotin / Maket Bıçağı Kılavuzu)" data-en="Show (Guillotine / Cut Guides)">Göster (Giyotin / Maket Bıçağı Kılavuzu)</option> 192 <option value="0" data-tr="Gizle (Tam Sıfır Kenar)" data-en="Hide (Zero Border)">Gizle (Tam Sıfır Kenar)</option> 193 </select> 194 </div> 195 </div> 196 <div class="buttons"> 197 <button id="print" data-tr="🖨️ Seçili Düzeni Yazdır / PDF Kaydet" data-en="🖨️ Print Selected Layout / Save PDF">🖨️ Seçili Düzeni Yazdır / PDF Kaydet</button> 198 <button id="downloadPrintPng" class="btn-secondary" data-tr="📐 600 DPI Matbaa PNG (Ön+Arka Kroslu)" data-en="📐 600 DPI Print-Shop PNG (Front+Back w/ Bleed)">📐 600 DPI Matbaa PNG (Ön+Arka Kroslu)</button> 199 <button id="downloadStandardPng" class="btn-secondary" data-tr="🖼️ 300 PPI Standart PNG (Ön+Arka)" data-en="🖼️ 300 PPI Standard PNG (Front+Back)">🖼️ 300 PPI Standart PNG (Ön+Arka)</button> 200 <button id="vcf2" class="btn-secondary" data-tr="📇 VCF İndir" data-en="📇 Download VCF">📇 VCF İndir</button> 201 </div> 202 </div> 203 <p class="hint" data-tr="Baskı İpucu: Yazdırma penceresinde 'Kenar Boşlukları: Yok / Sıfır', 'Ölçek: %100' ve 'Arka Plan Grafikleri: Açık' seçin. Çift yönlü A4 baskıda yazıcınızın 'Uzun Kenardan Çevir (Flip on Long Edge)' ayarını kullanın." data-en="Print Tip: Select 'Margins: None', 'Scale: 100%', and 'Background graphics: On' in the print dialog. For duplex A4 printing, use 'Flip on Long Edge'.">Baskı İpucu: Yazdırma penceresinde 'Kenar Boşlukları: Yok / Sıfır', 'Ölçek: %100' ve 'Arka Plan Grafikleri: Açık' seçin. Çift yönlü A4 baskıda yazıcınızın 'Uzun Kenardan Çevir (Flip on Long Edge)' ayarını kullanın.</p> 204 </div> 205 <p id="as-status" class="as-status" role="status" aria-live="polite"></p> 206 <p class="muted" data-tr="QR kodları cihazınızda üretilir. E-posta imzalarında Gmail/Outlook uyumluluğu için 'Logo URL' alanına medya kütüphanesi adresinizi ekleyebilirsiniz." data-en="QR codes are generated on your device. For Gmail/Outlook signature compatibility, provide a public Media Library URL in the 'Logo URL' field.">QR kodları cihazınızda üretilir. E-posta imzalarında Gmail/Outlook uyumluluğu için 'Logo URL' alanına medya kütüphanesi adresinizi ekleyebilirsiniz.</p> 207 </section> 208 </main> 209 <p class="privacy" data-tr="Ücretsiz · Hesap veya API anahtarı gerekmez · Veriler yalnız sizin WordPress sitenizde veya indirdiğiniz JSON dosyasında tutulur." data-en="Free · No account or API key required · Data is stored only on your own WordPress site or exported JSON file.">Ücretsiz · Hesap veya API anahtarı gerekmez · Veriler yalnız sizin WordPress sitenizde veya indirdiğiniz JSON dosyasında tutulur.</p> 210 </div> -
awosign/trunk/readme.txt
r3783841 r3783861 1 === AwoSign===1 === AwoSign — Email Signature, Digital Contact Card & Printable Business Card Studio === 2 2 Contributors: awoscan 3 Tags: email signature, business card, vcard, qr code, digital card 4 Requires at least: 6.4 3 5 Tested up to: 6.7 4 Tags: email signature, business card, branding, vcard, qr code5 Requires at least: 6.46 6 Requires PHP: 7.4 7 Stable tag: 1. 0.17 Stable tag: 1.3.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 10 11 Free brand identity workspace: email signatures, digital card previews, printable business cards and vCard downloads.11 Multi-template HTML email signature generator, digital contact card with `[awosign]` shortcode, local vCard/URL QR code, and 600 DPI printable business card studio. 12 12 13 13 == Description == 14 AwoSign turns a shared logo and color palette into an email signature, digital contact card preview and two-sided business card. Free, no account, subscription, trial limit or API key.14 **AwoSign** turns your company logo and brand palette into a professional **HTML Email Signature**, an interactive **Digital Contact Card (`[awosign]`)**, and a **Two-Sided 85x55mm Printable Business Card** directly inside WordPress. 15 15 16 * Turkish and English editor; light and dark themes. 17 * PNG/JPEG logos up to 3 MB. 18 * Ten selectable social profile links with compact text/symbol badges. 19 * Local QR generation; no third-party QR service. 20 * Table-based, inline-styled signature HTML export and rich clipboard copy when supported. 21 * vCard 3.0 export and two-page browser print/PDF output. 22 * Explicit JSON project download/import; no automatic persistence. 16 100% free, self-contained, and privacy-first: no external QR APIs, no tracking, and no account required. 23 17 24 Digital cards are private previews, not published public profile pages. Social badges are text/symbol labels, not official brand pictograms. Print output is RGB browser output: 85 x 55 mm trim plus 3 mm bleed. It does not include crop marks or certified press/color separation. Email compatibility varies; test exported signatures, especially embedded logo images, in your email clients. 18 = Key Features (English) = 19 * **3 HTML Email Signature Templates:** Switch instantly between *Classic (Left Logo)*, *Compact (Horizontal Bar)*, and *Corporate Banner (with QR Code)* — built with inline table styles for Gmail, Outlook, Apple Mail, and Thunderbird. 20 * **Multi-Staff Profile Management:** Manage up to 50 team members under a shared company brand identity and save directly to the WordPress database (`wp_options`) or export/import portable JSON backups. 21 * **`[awosign]` Frontend Shortcode:** Embed interactive digital business cards on any page or post (`[awosign]` or `[awosign id="prof_1"]`) complete with live QR code, one-click **vCard 3.0 (`.vcf`)** download, and **WhatsApp Share**. 22 * **Dual QR Code Engine (100% Offline):** Generate QR codes locally for either a **Website/Custom URL** or a full **vCard 3.0 Contact** (scan with phone camera to add directly to contacts). 23 * **600 DPI Print-Shop PNG & A4 10-Up Print Engine:** Print single 85x55mm cards with 3mm bleed and corner crop marks, or print **A4 10-Up (2x5) front/back duplex sheets** (`10` to `100` cards) and export **600 DPI (`2150x1441 px`)** print-ready PNGs. 24 * **Bilingual & Adaptive Theme:** Full **Turkish & English** interface with Automatic (System/Page), Light, and Dark mode support. 25 26 = Başlıca Özellikler (Türkçe) = 27 * **3 Kurumsal E-Posta İmzası Şablonu:** Klasik Sol Logo, Kompakt Yatay Bar ve QR Kodlu Kurumsal Banner şablonları (Gmail, Outlook, Apple Mail ve Thunderbird uyumlu tablo tabanlı HTML). 28 * **Çoklu Personel Profili & WordPress Veritabanı Kaydı:** Tek kurumsal kimlik altında tüm ekip profillerini yönetin, WordPress Medya Kütüphanesinden logo seçin. 29 * **`[awosign]` Ön Yüz Kısa Kodu:** Dijital kartvizitinizi QR kod, tek tıkla Rehbere Ekle (`.vcf`) ve WhatsApp paylaşım butonuyla sitenizin istediğiniz sayfasına yerleştirin. 30 * **600 DPI Matbaa Baskı & A4 10'lu Kartvizit Dizgisi:** 85x55mm + 3mm taşma payı (bleed) ve kesim kroslarıyla tekli veya A4 sayfasına 10'lu (10–100 adet) çift yönlü kartvizit baskısı alın. 25 31 26 32 == Installation == 27 1. Upload the awosign folder to /wp-content/plugins/ or upload the ZIP through Plugins > Add New > Upload Plugin. 28 2. Activate AwoSign. 29 3. Administrators: open AwoSign in the dashboard menu. 30 4. Customize the fields and export. Download a project JSON to keep your work before closing or reloading the page. 31 32 == Privacy == 33 No form values or logos are sent to WordPress or third-party services by the plugin. No telemetry, cookies, database options, REST endpoints or external asset CDNs. Editing state is lost on reload unless exported. Project JSON includes contact information and embedded logos; keep it private. Clicking a social link visits that provider. Exported documents may include those links. The plugin only loads assets on its own admin page and requires manage_options. No public shortcode is registered. 33 1. Upload the `awosign` folder to `/wp-content/plugins/` or install directly via **Plugins > Add New** in WordPress. 34 2. Activate **AwoSign**. 35 3. Open **AwoSign** in the WordPress admin sidebar to design signatures and business cards, or place `[awosign]` on any page. 34 36 35 37 == Frequently Asked Questions == 36 = Does it save company data in WordPress? = 37 No. Download/import a project JSON file for portable storage. 38 = Does it require a QR service? = 39 No. QR Code Generator by Kazuhiko Arase is bundled locally under the MIT license. Source: https://github.com/kazuhikoarase/qrcode-generator (JavaScript 1.4.4). 40 = Does it include invoice, accounting or stationery functions? = 41 No. This release follows the supplied signature/card design. It does not include e-invoicing, invoice cards, expense OCR, letterhead or envelopes. 38 = Does AwoSign send any data to external servers or third-party QR APIs? = 39 No. All QR codes, vCard files, HTML signatures, and 600 DPI PNG business cards are generated 100% locally inside your browser and WordPress site. 40 41 = How do I display a digital business card on a public WordPress page? = 42 Save your workspace in **AwoSign** and add the `[awosign]` shortcode (or `[awosign id="prof_1"]` for a specific team member) to any page, post, or widget. 43 44 = Why does Gmail or Outlook block Base64 logos in email signatures? = 45 Email clients often block inline `data:image/...` strings. Use the **WordPress Media Library Logo Picker** (`#logoUrl`) in AwoSign so your email signature uses a standard `https://...` image URL. 46 47 == Screenshots == 48 1. Multi-Template HTML Email Signature Studio (Classic, Compact & Banner with QR Code + Media Library Logo Picker). 49 2. Interactive Digital Contact Card & `[awosign]` Frontend Shortcode with vCard (.VCF) download and WhatsApp sharing. 50 3. 600 DPI Print-Shop PNG Exporter & A4 10-Up Duplex Printable Business Card Studio (85x55mm + 3mm bleed & crop marks). 42 51 43 52 == Source code == 44 The plugin includes the readable JavaScript and CSS source files that it runs: 45 46 * assets/designer.js - AwoSign editor, previews, local exports and project import. 47 * assets/designer.css - Editor and preview styles, scoped to the admin workspace. 48 * assets/qrcode.js - Readable QR Code Generator 1.4.4 source by Kazuhiko Arase (MIT). Upstream: https://github.com/kazuhikoarase/qrcode-generator 49 50 No build step, package manager, transpilation or minification is required. Edit these files directly; WordPress loads them as shipped. The QR library license is included in assets/qrcode-LICENSE.txt; AwoSign is GPL-2.0-or-later. 53 The plugin ships with human-readable JavaScript and CSS directly (no build or minification step required): 54 * `assets/designer.js` — AwoSign studio, live previews, QR/vCard/PNG exporters, and AJAX persistence. 55 * `assets/designer.css` — Scoped studio and print layout styles. 56 * `assets/qrcode.js` — QR Code Generator 1.4.4 by Kazuhiko Arase (MIT License, see `assets/qrcode-LICENSE.txt`). 51 57 52 58 == Changelog == 59 = 1.3.0 = 60 * Added 3 email signature layout templates (`classic` Left Logo, `compact` Horizontal Bar, and `banner` Corporate Header with QR). 61 * Added WordPress Media Library Logo URL support (`#logoUrl` + `wp.media` picker) for full Gmail/Outlook compatibility. 62 * Added one-click WhatsApp card sharing button in the digital card view and `[awosign]` frontend shortcode. 63 64 = 1.2.0 = 65 * Added multi-staff profile management under a shared company brand identity. 66 * Added WordPress database storage (`wp_options` via `awosign_workspace_data_v1`) protected by `manage_options` capability and AJAX nonce, plus `uninstall.php` cleanup. 67 * Added `[awosign]` frontend shortcode to embed interactive digital business cards with QR and VCF download. 68 69 = 1.1.0 = 70 * Added popup-free in-page `@media print` engine (`#as-print-host`) with A4 10-up (2x5) front/back duplex duplication and 600 DPI Print-Shop PNG export (2150x1441 px with 3 mm bleed and corner crop marks). 71 * Added QR Code mode selector: Website/Custom URL or vCard 3.0 Contact QR. 72 * Added Automatic (System/Page Day-Night), Light, and Dark theme selector. 73 53 74 = 1.0.1 = 54 Provide readable designer JavaScript and CSS directly and document the bundled source files.75 * Provide readable designer JavaScript and CSS directly and document the bundled source files. 55 76 56 77 = 1.0.0 = 57 Initial WordPress adaptation of the AwoSign V2.2 HTML design. Local QR, scoped assets, table signature export, project files and private admin access. 58 59 == Third-party licenses == 60 assets/qrcode.js: QR Code Generator, Copyright (c) 2009 Kazuhiko Arase; MIT license, see assets/qrcode-LICENSE.txt. Other AwoSign code: GPL-2.0-or-later. 61 78 * Initial WordPress release of the AwoSign studio.
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)