1<!DOCTYPE html> 2<html lang="en"> 3 4<head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>ToyCAD â Subscribe Online & Save 20%</title> 8 9 <!-- BEGIN seo (see scripts/generate_sitemap.py for the sitemap) --> 10 <meta name="description" content="Subscribe to ToyCAD online and save 20% on lifetime or yearly access. Design and 3D print toys from an iPad. No CAD experience needed." /> 11 <link rel="canonical" href="https://toycad.app/subscribe.html" /> 12 13 <meta property="og:type" content="website" /> 14 <meta property="og:site_name" content="ToyCAD" /> 15 <meta property="og:locale" content="en_US" /> 16 <meta property="og:title" content="ToyCAD â Subscribe Online & Save 20%" /> 17 <meta property="og:description" content="Subscribe to ToyCAD online and save 20% on lifetime or yearly access. Design and 3D print toys from an iPad. No CAD experience needed." /> 18 <meta property="og:url" content="https://toycad.app/subscribe.html" /> 19 <meta property="og:image" content="https://toycad.app/img_new/og/toycad.jpg" /> 20 <meta property="og:image:width" content="1200" /> 21 <meta property="og:image:height" content="630" /> 22 <meta property="og:image:alt" content="Toys designed in ToyCAD on an iPad, next to the 3D-printed versions" /> 23 <meta name="twitter:card" content="summary_large_image" /> 24 <meta name="twitter:title" content="ToyCAD â Subscribe Online & Save 20%" /> 25 <meta name="twitter:description" content="Subscribe to ToyCAD online and save 20% on lifetime or yearly access. Design and 3D print toys from an iPad. No CAD experience needed." /> 26 <meta name="twitter:image" content="https://toycad.app/img_new/og/toycad.jpg" /> 27 28 <link rel="icon" href="/favicon.ico" sizes="48x48" /> 29 <link rel="icon" type="image/png" sizes="32x32" href="/img_new/icons/favicon-32.png" /> 30 <link rel="icon" type="image/png" sizes="16x16" href="/img_new/icons/favicon-16.png" /> 31 <link rel="apple-touch-icon" href="/img_new/icons/apple-touch-icon.png" /> 32 <link rel="manifest" href="/site.webmanifest" /> 33 <meta name="theme-color" content="#7b3ff2" /> 34 <meta name="apple-mobile-web-app-title" content="ToyCAD" /> 35 <!-- END seo --> 36<!-- Rewardful Affiliate Tracking --> 37
37<script>(function (w, r) { w._rwq = r; w[r] = w[r] || function () { (w[r].q = w[r].q || []).push(arguments) } })(window, 'rewardful');</script>
37 38
38<script async src="https://r.wdfl.co/rw.js" data-rewardful="052dd7"></script>
38 39 <!-- End Rewardful --> 40 41 <!-- Meta Pixel Code --> 42
42<script>
vendor: 595 bytes, lines 42-54
42 43 !function (f, b, e, v, n, t, s) { 44 if (f.fbq) return; n = f.fbq = function () { 45 n.callMethod ? 46 n.callMethod.apply(n, arguments) : n.queue.push(arguments) 47 }; 48 if (!f._fbq) f._fbq = n; n.push = n; n.loaded = !0; n.version = '2.0'; 49 n.queue = []; t = b.createElement(e); t.async = !0; 50 t.src = v; s = b.getElementsByTagName(e)[0]; 51 s.parentNode.insertBefore(t, s) 52 }(window, document, 'script', 53 'https://connect.facebook.net/en_US/fbevents.js'); 54 fbq('init', '
541373546107509124
vendor: 42 bytes, lines 54-56
54'); 55 fbq('track', 'PageView'); 56
56</script>
56 57 <noscript><img height="1" width="1" style="display:none" 58 src="https://www.facebook.com/tr?id=1373546107509124&ev=PageView&noscript=1" /></noscript> 59 <!-- End Meta Pixel Code --> 60 61 <!-- Google Analytics -->
vendor: 68 bytes, lines 61-62
61 62 <script async src="https://www.googletagmanager.com/gtag/js?id=
62G-NMVRFPGBCN
vendor: 16 bytes, lines 62-63
62"></script> 63
63<script> 64
vendor: 154 bytes, lines 64-67
64window.dataLayer = window.dataLayer || []; 65 function gtag() { dataLayer.push(arguments); } 66 gtag('js', new Date()); 67 gtag('config', '
67G-NMVRFPGBCN
vendor: 8 bytes, lines 67-68
67'); 68
68</script>
68 69 70 <link 71 href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,700;0,9..144,900;1,9..144,400&family=DM+Sans:wght@300;400;500;600&display=swap" 72 rel="stylesheet"> 73 74 <style> 75 :root { 76 --green: #209850; 77 --green-dark: #177a3e; 78 --green-light: #e6f5ec; 79 --orange: #F49100; 80 --yellow: #FFDE06; 81 --ink: #0d1f12; 82 --dark: #0f1a0f; 83 --cream: #f8faf8; 84 --mid: #5a6b5e; 85 --border: rgba(32, 152, 80, 0.15); 86 } 87 88 *, 89 *::before, 90 *::after { 91 box-sizing: border-box; 92 margin: 0; 93 padding: 0; 94 } 95 96 html { 97 scroll-behavior: smooth; 98 } 99 100 body { 101 font-family: 'DM Sans', sans-serif; 102 background: var(--cream); 103 color: var(--ink); 104 overflow-x: hidden; 105 } 106 107 /* ââ NAV ââ */ 108 nav { 109 position: fixed; 110 top: 0; 111 left: 0; 112 right: 0; 113 z-index: 100; 114 display: flex; 115 align-items: center; 116 justify-content: space-between; 117 padding: 14px 24px; 118 background: rgba(13, 26, 15, 0.96); 119 backdrop-filter: blur(12px); 120 border-bottom: 1px solid rgba(255, 255, 255, 0.06); 121 } 122 123 .nav-logo img { 124 height: 34px; 125 display: block; 126 } 127 128 .nav-cta { 129 background: var(--green); 130 color: #fff; 131 border: none; 132 border-radius: 100px; 133 padding: 10px 22px; 134 font-family: 'DM Sans', sans-serif; 135 font-size: 14px; 136 font-weight: 600; 137 cursor: pointer; 138 text-decoration: none; 139 transition: background 0.2s, transform 0.15s; 140 white-space: nowrap; 141 } 142 143 .nav-cta:hover { 144 background: var(--green-dark); 145 transform: translateY(-1px); 146 } 147 148 /* ââ SUCCESS BANNER ââ */ 149 .success-banner { 150 display: none; 151 background: var(--green); 152 color: #fff; 153 padding: 20px 24px; 154 text-align: center; 155 position: relative; 156 z-index: 90; 157 margin-top: 62px; 158 } 159 160 .success-banner-text { 161 font-size: 16px; 162 font-weight: 600; 163 margin-bottom: 16px; 164 } 165 166 .success-banner-actions { 167 display: flex; 168 align-items: center; 169 justify-content: center; 170 flex-wrap: wrap; 171 gap: 12px; 172 } 173 174 .success-banner a { 175 color: var(--yellow); 176 text-decoration: underline; 177 } 178 179 .banner-manage-btn { 180 background: rgba(255, 255, 255, 0.2); 181 color: #fff; 182 border: 1.5px solid rgba(255, 255, 255, 0.5); 183 border-radius: 100px; 184 padding: 9px 20px; 185 font-family: 'DM Sans', sans-serif; 186 font-size: 14px; 187 font-weight: 600; 188 cursor: pointer; 189 transition: background 0.2s; 190 white-space: nowrap; 191 } 192 193 .banner-manage-btn:hover { 194 background: rgba(255, 255, 255, 0.3); 195 } 196 197 /* ââ HERO ââ */ 198 .hero { 199 min-height: 100vh; 200 padding: 80px 24px 60px; 201 display: flex; 202 flex-direction: column; 203 align-items: center; 204 justify-content: center; 205 text-align: center; 206 position: relative; 207 overflow: hidden; 208 background: var(--dark); 209 } 210 211 .hero::before { 212 content: ''; 213 position: absolute; 214 inset: 0; 215 background: radial-gradient(ellipse 70% 50% at 50% 0%, rgba(32, 152, 80, 0.18) 0%, transparent 65%); 216 pointer-events: none; 217 } 218 219 .hero-badge { 220 display: inline-flex; 221 align-items: center; 222 gap: 8px; 223 background: rgba(32, 152, 80, 0.15); 224 color: #4cce7e; 225 border: 1px solid rgba(32, 152, 80, 0.3); 226 border-radius: 100px; 227 padding: 6px 16px; 228 font-size: 13px; 229 font-weight: 600; 230 letter-spacing: 0.3px; 231 margin-bottom: 28px; 232 animation: fadeUp 0.6s ease both; 233 } 234 235 .dot { 236 width: 7px; 237 height: 7px; 238 background: var(--green); 239 border-radius: 50%; 240 animation: pulse 2s infinite; 241 } 242 243 @keyframes pulse { 244 245 0%, 246 100% { 247 opacity: 1; 248 transform: scale(1); 249 } 250 251 50% { 252 opacity: 0.5; 253 transform: scale(1.3); 254 } 255 } 256 257 .hero h1 { 258 font-family: 'Fraunces', serif; 259 font-size: clamp(36px, 7vw, 62px); 260 font-weight: 900; 261 line-height: 1.06; 262 letter-spacing: -2px; 263 max-width: 820px; 264 margin-bottom: 24px; 265 color: #fff; 266 animation: fadeUp 0.7s 0.1s ease both; 267 } 268 269 .hero h1 em { 270 font-style: italic; 271 color: var(--yellow); 272 } 273 274 .hero-sub { 275 font-size: clamp(16px, 2.5vw, 19px); 276 color: rgba(255, 255, 255, 0.6); 277 max-width: 560px; 278 line-height: 1.65; 279 margin-bottom: 40px; 280 animation: fadeUp 0.7s 0.2s ease both; 281 } 282 283 .hero-actions { 284 display: flex; 285 flex-direction: column; 286 align-items: center; 287 gap: 14px; 288 animation: fadeUp 0.7s 0.3s ease both; 289 } 290 291 .btn-primary { 292 background: var(--green); 293 color: #fff; 294 border: none; 295 border-radius: 100px; 296 padding: 18px 44px; 297 font-family: 'DM Sans', sans-serif; 298 font-size: 17px; 299 font-weight: 600; 300 cursor: pointer; 301 text-decoration: none; 302 transition: all 0.2s; 303 box-shadow: 0 4px 32px rgba(32, 152, 80, 0.4); 304 display: inline-block; 305 } 306 307 .btn-primary:hover { 308 background: var(--green-dark); 309 transform: translateY(-2px); 310 box-shadow: 0 8px 40px rgba(32, 152, 80, 0.45); 311 } 312 313 .hero-note { 314 font-size: 13px; 315 color: rgba(255, 255, 255, 0.28); 316 margin-top: 4px; 317 } 318 319 .hero-pricing-preview { 320 display: flex; 321 gap: 24px; 322 margin-top: 52px; 323 flex-wrap: wrap; 324 justify-content: center; 325 animation: fadeUp 0.8s 0.4s ease both; 326 } 327 328 .hero-price-pill { 329 background: rgba(255, 255, 255, 0.06); 330 border: 1px solid rgba(32, 152, 80, 0.3); 331 border-radius: 20px; 332 padding: 20px 28px; 333 text-align: center; 334 min-width: 180px; 335 } 336 337 .hero-price-pill .old-price { 338 font-size: 14px; 339 color: rgba(255, 255, 255, 0.35); 340 text-decoration: line-through; 341 margin-bottom: 4px; 342 } 343 344 .hero-price-pill .new-price { 345 font-family: 'Fraunces', serif; 346 font-size: 32px; 347 font-weight: 900; 348 color: var(--yellow); 349 letter-spacing: -1px; 350 } 351 352 .hero-price-pill .plan-name { 353 font-size: 13px; 354 color: rgba(255, 255, 255, 0.6); 355 margin-top: 4px; 356 } 357 358 .hero-price-pill[onclick] { 359 cursor: pointer; 360 transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease; 361 } 362 363 .hero-price-pill[onclick]:hover { 364 transform: translateY(-3px); 365 border-color: rgba(32, 152, 80, 0.7); 366 box-shadow: 0 8px 32px rgba(32, 152, 80, 0.25); 367 } 368 369 .discount-badge { 370 display: inline-block; 371 background: var(--orange); 372 color: #fff; 373 font-size: 11px; 374 font-weight: 800; 375 letter-spacing: 1px; 376 text-transform: uppercase; 377 padding: 3px 10px; 378 border-radius: 100px; 379 margin-top: 8px; 380 } 381 382 @keyframes fadeUp { 383 from { 384 opacity: 0; 385 transform: translateY(24px); 386 } 387 388 to { 389 opacity: 1; 390 transform: translateY(0); 391 } 392 } 393 394 /* ââ PROOF BAR ââ */ 395 .proof-bar { 396 background: var(--green); 397 color: #fff; 398 padding: 16px 24px; 399 text-align: center; 400 font-size: 14px; 401 font-weight: 500; 402 } 403 404 .proof-bar span { 405 color: var(--yellow); 406 font-weight: 700; 407 } 408 409 /* ââ TOYS / KIDS MADE GRID ââ */ 410 .toys-section { 411 background: var(--ink); 412 color: #fff; 413 text-align: center; 414 } 415 416 .toys-section .section-title { 417 color: #fff; 418 } 419 420 .toys-section .section-sub { 421 color: rgba(255, 255, 255, 0.55); 422 margin: 0 auto 0; 423 } 424 425 .toys-grid { 426 display: flex; 427 justify-content: center; 428 gap: 20px; 429 flex-wrap: wrap; 430 margin-top: 40px; 431 } 432 433 .toy-card { 434 flex: 1 1 220px; 435 max-width: 280px; 436 background: rgba(255, 255, 255, 0.06); 437 border: 1px solid rgba(32, 152, 80, 0.2); 438 border-radius: 20px; 439 overflow: hidden; 440 text-align: center; 441 } 442 443 .toy-card video, 444 .toy-card img { 445 width: 100%; 446 height: auto; 447 display: block; 448 } 449 450 .toy-card h3 { 451 padding: 14px 12px; 452 font-family: 'Fraunces', serif; 453 font-size: 16px; 454 font-weight: 700; 455 color: #fff; 456 } 457 458 /* ââ PARENTS SAY ââ */ 459 .parents-section { 460 background: var(--cream); 461 } 462 463 .parents-grid { 464 display: flex; 465 justify-content: center; 466 gap: 20px; 467 flex-wrap: wrap; 468 margin-top: 40px; 469 } 470 471 .parent-card { 472 flex: 1 1 240px; 473 max-width: 320px; 474 background: #fff; 475 border-radius: 20px; 476 padding: 24px; 477 border: 1px solid var(--border); 478 font-style: italic; 479 font-size: 14px; 480 color: var(--mid); 481 line-height: 1.65; 482 } 483 484 .parent-card strong { 485 font-style: normal; 486 } 487 488 .parent-card .parent-name { 489 display: block; 490 margin-top: 12px; 491 font-style: normal; 492 font-weight: 700; 493 font-size: 13px; 494 color: var(--ink); 495 } 496 497 /* ââ KIDS MADE PHOTO GRID ââ */ 498 .kids-section { 499 background: #fff; 500 } 501 502 /* ââ SECTIONS ââ */ 503 section { 504 padding: 72px 24px; 505 } 506 507 .section-label { 508 font-size: 12px; 509 font-weight: 700; 510 letter-spacing: 2px; 511 text-transform: uppercase; 512 color: var(--green); 513 margin-bottom: 12px; 514 } 515 516 .section-title { 517 font-family: 'Fraunces', serif; 518 font-size: clamp(28px, 5vw, 44px); 519 font-weight: 900; 520 line-height: 1.1; 521 letter-spacing: -1px; 522 margin-bottom: 16px; 523 } 524 525 .section-sub { 526 font-size: 17px; 527 color: var(--mid); 528 line-height: 1.6; 529 max-width: 540px; 530 } 531 532 .container { 533 max-width: 960px; 534 margin: 0 auto; 535 } 536 537 /* ââ PRICING ââ */ 538 .pricing { 539 background: #fff; 540 } 541 542 .pricing-grid { 543 display: grid; 544 grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); 545 gap: 24px; 546 margin-top: 48px; 547 max-width: 1060px; 548 } 549 550 .pricing-card { 551 border-radius: 24px; 552 padding: 32px 28px; 553 border: 2px solid var(--border); 554 position: relative; 555 transition: transform 0.2s; 556 } 557 558 .pricing-card:hover { 559 transform: translateY(-4px); 560 } 561 562 .pricing-card.featured { 563 background: var(--dark); 564 color: #fff; 565 border-color: var(--green); 566 box-shadow: 0 16px 48px rgba(32, 152, 80, 0.25); 567 } 568 569 .pricing-badge { 570 position: absolute; 571 top: -14px; 572 left: 28px; 573 background: var(--orange); 574 color: #fff; 575 font-size: 11px; 576 font-weight: 800; 577 letter-spacing: 1px; 578 text-transform: uppercase; 579 padding: 4px 12px; 580 border-radius: 100px; 581 } 582 583 .pricing-discount-badge { 584 position: absolute; 585 top: -14px; 586 right: 28px; 587 background: var(--green); 588 color: #fff; 589 font-size: 11px; 590 font-weight: 800; 591 letter-spacing: 1px; 592 text-transform: uppercase; 593 padding: 4px 12px; 594 border-radius: 100px; 595 } 596 597 .pricing-card h3 { 598 font-family: 'Fraunces', serif; 599 font-size: 22px; 600 font-weight: 900; 601 margin-bottom: 4px; 602 } 603 604 .price-row { 605 display: flex; 606 align-items: baseline; 607 gap: 10px; 608 margin-bottom: 2px; 609 } 610 611 .price { 612 font-family: 'Fraunces', serif; 613 font-size: 48px; 614 font-weight: 900; 615 letter-spacing: -2px; 616 line-height: 1; 617 } 618 619 .pricing-card.featured .price { 620 color: var(--yellow); 621 } 622 623 .price-was { 624 font-size: 20px; 625 color: rgba(0, 0, 0, 0.3); 626 text-decoration: line-through; 627 font-family: 'Fraunces', serif; 628 font-weight: 700; 629 } 630 631 .pricing-card.featured .price-was { 632 color: rgba(255, 255, 255, 0.25); 633 } 634 635 .price-note { 636 font-size: 13px; 637 opacity: 0.6; 638 margin-bottom: 24px; 639 } 640 641 .pricing-card ul { 642 list-style: none; 643 display: flex; 644 flex-direction: column; 645 gap: 10px; 646 margin-bottom: 28px; 647 } 648 649 .pricing-card ul li { 650 font-size: 14px; 651 display: flex; 652 align-items: flex-start; 653 gap: 8px; 654 line-height: 1.4; 655 } 656 657 .pricing-card ul li::before { 658 content: 'â'; 659 font-weight: 700; 660 color: var(--green); 661 flex-shrink: 0;
662 margin-top: 1px; 663 } 664 665 .btn-outline { 666 display: block; 667 text-align: center; 668 border: 2px solid var(--green); 669 color: var(--green); 670 border-radius: 100px; 671 padding: 14px 24px; 672 font-family: 'DM Sans', sans-serif; 673 font-size: 15px; 674 font-weight: 600; 675 text-decoration: none; 676 cursor: pointer; 677 background: none; 678 transition: all 0.2s; 679 width: 100%; 680 } 681 682 .btn-outline:hover { 683 background: var(--green); 684 color: #fff; 685 } 686 687 .btn-green { 688 display: block; 689 text-align: center; 690 background: var(--green); 691 color: #fff; 692 border-radius: 100px; 693 padding: 14px 24px; 694 font-family: 'DM Sans', sans-serif; 695 font-size: 15px; 696 font-weight: 700; 697 text-decoration: none; 698 cursor: pointer; 699 border: none; 700 transition: all 0.2s; 701 width: 100%; 702 } 703 704 .btn-green:hover { 705 background: var(--green-dark); 706 } 707 708 /* ââ HOW IT WORKS ââ */ 709 .how { 710 background: #fff; 711 } 712 713 .steps { 714 display: grid; 715 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 716 gap: 32px; 717 margin-top: 48px; 718 } 719 720 .step { 721 display: flex; 722 flex-direction: column; 723 gap: 14px; 724 } 725 726 .step-num { 727 width: 44px; 728 height: 44px; 729 background: var(--green-light); 730 color: var(--green); 731 border-radius: 12px; 732 display: flex; 733 align-items: center; 734 justify-content: center; 735 font-family: 'Fraunces', serif; 736 font-size: 22px; 737 font-weight: 900; 738 } 739 740 .step h3 { 741 font-family: 'Fraunces', serif; 742 font-size: 20px; 743 font-weight: 700; 744 letter-spacing: -0.3px; 745 } 746 747 .step p { 748 font-size: 15px; 749 color: var(--mid); 750 line-height: 1.6; 751 } 752 753 /* ââ BENEFITS ââ */ 754 .why { 755 background: var(--cream); 756 } 757 758 .benefits { 759 display: grid; 760 grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); 761 gap: 24px; 762 margin-top: 48px; 763 } 764 765 .benefit-card { 766 background: #fff; 767 border-radius: 20px; 768 padding: 28px 24px; 769 border: 1px solid var(--border); 770 transition: transform 0.2s, box-shadow 0.2s; 771 } 772 773 .benefit-card:hover { 774 transform: translateY(-4px); 775 box-shadow: 0 12px 40px rgba(32, 152, 80, 0.08); 776 } 777 778 .benefit-icon { 779 font-size: 32px; 780 margin-bottom: 14px; 781 } 782 783 .benefit-card h3 { 784 font-family: 'Fraunces', serif; 785 font-size: 19px; 786 font-weight: 700; 787 margin-bottom: 8px; 788 letter-spacing: -0.3px; 789 } 790 791 .benefit-card p { 792 font-size: 14px; 793 color: var(--mid); 794 line-height: 1.6; 795 } 796 797 /* ââ TESTIMONIALS ââ */ 798 .testimonials { 799 background: var(--dark); 800 color: #fff; 801 } 802 803 .testimonials .section-label { 804 color: #4cce7e; 805 } 806 807 .testimonials .section-title { 808 color: #fff; 809 } 810 811 .reviews { 812 display: grid; 813 grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); 814 gap: 20px; 815 margin-top: 48px; 816 } 817 818 .review-card { 819 background: rgba(255, 255, 255, 0.05); 820 border: 1px solid rgba(32, 152, 80, 0.2); 821 border-radius: 20px; 822 padding: 24px; 823 } 824 825 .stars { 826 color: var(--yellow); 827 font-size: 16px; 828 margin-bottom: 12px; 829 } 830 831 .review-text { 832 font-size: 15px; 833 line-height: 1.65; 834 color: rgba(255, 255, 255, 0.8); 835 margin-bottom: 16px; 836 font-style: italic; 837 } 838 839 .review-author { 840 font-size: 13px; 841 font-weight: 600; 842 color: rgba(255, 255, 255, 0.4); 843 text-transform: uppercase; 844 letter-spacing: 0.5px; 845 } 846 847 /* ââ DOWNLOAD SECTION ââ */ 848 .download-section { 849 background: var(--cream); 850 text-align: center; 851 } 852 853 .store-buttons { 854 display: flex; 855 flex-wrap: wrap; 856 gap: 16px; 857 justify-content: center; 858 margin-top: 32px; 859 } 860 861 .store-buttons a img { 862 height: 54px; 863 display: block; 864 } 865 866 .store-buttons a { 867 transition: transform 0.2s; 868 display: block; 869 } 870 871 .store-buttons a:hover { 872 transform: translateY(-2px); 873 } 874 875 /* ââ FAQ ââ */ 876 .faq { 877 background: #fff; 878 } 879 880 .faq-list { 881 margin-top: 48px; 882 display: flex; 883 flex-direction: column; 884 gap: 16px; 885 max-width: 680px; 886 } 887 888 .faq-item { 889 background: var(--cream); 890 border-radius: 16px; 891 border: 1px solid var(--border); 892 overflow: hidden; 893 } 894 895 .faq-q { 896 width: 100%; 897 text-align: left; 898 background: none; 899 border: none; 900 padding: 20px 24px; 901 font-family: 'DM Sans', sans-serif; 902 font-size: 16px; 903 font-weight: 600; 904 color: var(--ink); 905 cursor: pointer; 906 display: flex; 907 align-items: center; 908 justify-content: space-between; 909 gap: 12px; 910 } 911 912 .faq-q .arrow { 913 font-size: 18px; 914 color: var(--green); 915 transition: transform 0.25s; 916 flex-shrink: 0; 917 } 918 919 .faq-item.open .faq-q .arrow { 920 transform: rotate(45deg); 921 } 922 923 .faq-a { 924 max-height: 0; 925 overflow: hidden; 926 transition: max-height 0.3s ease, padding 0.3s; 927 font-size: 15px; 928 color: var(--mid); 929 line-height: 1.65; 930 padding: 0 24px; 931 } 932 933 .faq-item.open .faq-a { 934 max-height: 300px; 935 padding: 0 24px 20px; 936 } 937 938 /* ââ CTA SECTION ââ */ 939 .cta-section { 940 background: var(--dark); 941 color: #fff; 942 text-align: center; 943 padding: 80px 24px; 944 position: relative; 945 overflow: hidden; 946 } 947 948 .cta-section::before { 949 content: ''; 950 position: absolute; 951 inset: 0; 952 background: radial-gradient(ellipse 60% 60% at 50% 100%, rgba(32, 152, 80, 0.2) 0%, transparent 70%); 953 pointer-events: none; 954 } 955 956 .cta-section .section-title { 957 color: #fff; 958 max-width: 600px; 959 margin: 0 auto 16px; 960 position: relative; 961 } 962 963 .cta-section .section-sub { 964 color: rgba(255, 255, 255, 0.55); 965 margin: 0 auto 40px; 966 position: relative; 967 } 968 969 .cta-section .btn-primary { 970 position: relative; 971 } 972 973 .cta-note { 974 margin-top: 16px; 975 font-size: 13px; 976 color: rgba(255, 255, 255, 0.3); 977 position: relative; 978 } 979 980 /* ââ FOOTER ââ */ 981 footer { 982 background: #080f09; 983 color: rgba(255, 255, 255, 0.3); 984 text-align: center; 985 padding: 28px 24px; 986 font-size: 13px; 987 } 988 989 footer a { 990 color: rgba(255, 255, 255, 0.3); 991 text-decoration: none; 992 } 993 994 footer a:hover { 995 color: rgba(255, 255, 255, 0.6); 996 } 997 998 /* ââ MODAL ââ */
999 .modal-overlay { 1000 display: none; 1001 position: fixed; 1002 inset: 0; 1003 z-index: 200; 1004 background: rgba(0, 0, 0, 0.75); 1005 backdrop-filter: blur(6px); 1006 align-items: center; 1007 justify-content: center; 1008 padding: 24px; 1009 } 1010 1011 .modal-overlay.active { 1012 display: flex; 1013 } 1014 1015 .modal { 1016 background: #fff; 1017 border-radius: 28px; 1018 padding: 40px 36px; 1019 max-width: 480px; 1020 width: 100%; 1021 position: relative; 1022 animation: modalIn 0.3s ease; 1023 max-height: 90vh; 1024 overflow-y: auto; 1025 } 1026 1027 @keyframes modalIn { 1028 from { 1029 opacity: 0; 1030 transform: scale(0.95) translateY(16px); 1031 } 1032 1033 to { 1034 opacity: 1; 1035 transform: scale(1) translateY(0); 1036 } 1037 } 1038 1039 .modal-close { 1040 position: absolute; 1041 top: 16px; 1042 right: 20px; 1043 background: none; 1044 border: none; 1045 font-size: 24px; 1046 cursor: pointer; 1047 color: var(--mid); 1048 line-height: 1; 1049 } 1050
1051 .modal-plan-chip { 1052 display: inline-flex; 1053 align-items: center; 1054 gap: 6px; 1055 background: var(--green-light); 1056 color: var(--green); 1057 border: 1px solid rgba(32, 152, 80, 0.2); 1058 border-radius: 100px; 1059 padding: 5px 14px; 1060 font-size: 13px; 1061 font-weight: 700; 1062 margin-bottom: 20px; 1063 } 1064 1065 .modal h2 { 1066 font-family: 'Fraunces', serif; 1067 font-size: 24px; 1068 font-weight: 900; 1069 letter-spacing: -0.5px; 1070 margin-bottom: 6px; 1071 } 1072 1073 .modal .modal-sub { 1074 font-size: 14px; 1075 color: var(--mid); 1076 margin-bottom: 24px; 1077 line-height: 1.5; 1078 } 1079 1080 /* Auth tabs */ 1081 .auth-tabs { 1082 display: flex; 1083 background: var(--cream); 1084 border-radius: 12px; 1085 padding: 4px; 1086 margin-bottom: 24px; 1087 gap: 4px; 1088 } 1089 1090 .auth-tab { 1091 flex: 1; 1092 padding: 10px; 1093 border: none; 1094 background: none; 1095 border-radius: 10px; 1096 font-family: 'DM Sans', sans-serif; 1097 font-size: 14px; 1098 font-weight: 600; 1099 color: var(--mid); 1100 cursor: pointer; 1101 transition: all 0.2s; 1102 } 1103 1104 .auth-tab.active { 1105 background: #fff; 1106 color: var(--ink); 1107 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); 1108 } 1109 1110 .form-group { 1111 margin-bottom: 14px; 1112 } 1113 1114 .form-group label { 1115 display: block; 1116 font-size: 13px; 1117 font-weight: 600; 1118 margin-bottom: 6px; 1119 color: var(--ink); 1120 } 1121 1122 .form-group input { 1123 width: 100%; 1124 border: 1.5px solid var(--border); 1125 border-radius: 12px; 1126 padding: 13px 16px; 1127 font-family: 'DM Sans', sans-serif; 1128 font-size: 15px; 1129 color: var(--ink); 1130 background: var(--cream); 1131 outline: none; 1132 transition: border-color 0.2s; 1133 } 1134 1135 .form-group input:focus { 1136 border-color: var(--green); 1137 } 1138
1139 .modal-submit-btn { 1140 width: 100%; 1141 background: var(--green); 1142 color: #fff; 1143 border: none; 1144 border-radius: 100px; 1145 padding: 16px; 1146 font-family: 'DM Sans', sans-serif; 1147 font-size: 16px; 1148 font-weight: 700; 1149 cursor: pointer; 1150 margin-top: 8px; 1151 transition: background 0.2s; 1152 } 1153 1154 .modal-submit-btn:hover { 1155 background: var(--green-dark); 1156 } 1157 1158 .modal-submit-btn:disabled { 1159 opacity: 0.6; 1160 cursor: default; 1161 } 1162 1163 .modal-link-btn { 1164 background: none; 1165 border: none; 1166 color: var(--green); 1167 font-size: 13px; 1168 font-weight: 600; 1169 cursor: pointer; 1170 padding: 0; 1171 text-decoration: underline; 1172 margin-top: 12px; 1173 display: block; 1174 } 1175 1176 .form-fine { 1177 font-size: 12px; 1178 color: var(--mid); 1179 text-align: center; 1180 margin-top: 12px; 1181 line-height: 1.5; 1182 } 1183
1184 .modal-error { 1185 display: none; 1186 font-size: 14px; 1187 padding: 10px 14px; 1188 border-radius: 10px; 1189 margin-bottom: 14px; 1190 background: #fef2f2; 1191 color: #c0392b; 1192 border: 1px solid #fca5a5; 1193 } 1194 1195 .modal-error.success-msg { 1196 background: var(--green-light); 1197 color: var(--green); 1198 border-color: rgba(32, 152, 80, 0.3); 1199 } 1200 1201 /* Checkout state */ 1202 .checkout-plan-box { 1203 background: var(--dark); 1204 border-radius: 20px; 1205 padding: 24px; 1206 margin-bottom: 24px; 1207 text-align: center; 1208 } 1209 1210 .checkout-plan-box .plan-label { 1211 font-size: 14px; 1212 color: rgba(255, 255, 255, 0.5); 1213 margin-bottom: 4px; 1214 } 1215 1216 .checkout-plan-box .plan-name { 1217 font-family: 'Fraunces', serif; 1218 font-size: 22px; 1219 font-weight: 900; 1220 color: #fff; 1221 margin-bottom: 4px; 1222 } 1223 1224 .checkout-plan-box .plan-price { 1225 font-family: 'Fraunces', serif; 1226 font-size: 38px; 1227 font-weight: 900; 1228 color: var(--yellow); 1229 letter-spacing: -1px; 1230 } 1231 1232 .checkout-plan-box .plan-note { 1233 font-size: 13px; 1234 color: rgba(255, 255, 255, 0.4); 1235 margin-top: 4px; 1236 } 1237 1238 .checkout-user-row { 1239 display: flex; 1240 align-items: center; 1241 gap: 10px; 1242 background: var(--cream); 1243 border-radius: 12px; 1244 padding: 14px 16px; 1245 margin-bottom: 20px; 1246 font-size: 14px; 1247 color: var(--mid); 1248 } 1249 1250 .checkout-user-row strong { 1251 color: var(--ink); 1252 } 1253 1254 /* Success state */
1255 .modal-success-state { 1256 text-align: center; 1257 padding: 12px 0; 1258 } 1259 1260 .success-icon { 1261 font-size: 52px; 1262 margin-bottom: 16px; 1263 } 1264 1265 .modal-success-state h3 { 1266 font-family: 'Fraunces', serif; 1267 font-size: 24px; 1268 font-weight: 900; 1269 margin-bottom: 10px; 1270 } 1271 1272 .modal-success-state p { 1273 font-size: 15px; 1274 color: var(--mid); 1275 line-height: 1.6; 1276 margin-bottom: 16px; 1277 } 1278 1279 .activate-steps { 1280 background: var(--green-light); 1281 border-radius: 16px; 1282 padding: 20px 20px; 1283 text-align: left; 1284 margin-top: 16px; 1285 } 1286 1287 .activate-steps p { 1288 font-size: 14px; 1289 color: var(--ink); 1290 margin-bottom: 8px; 1291 font-weight: 600; 1292 } 1293 1294 .activate-steps ol { 1295 padding-left: 20px; 1296 } 1297 1298 .activate-steps ol li { 1299 font-size: 14px; 1300 color: var(--mid); 1301 line-height: 1.7; 1302 } 1303 1304 /* Loading spinner */ 1305 .loading-state { 1306 text-align: center; 1307 padding: 32px 0; 1308 } 1309 1310 .spinner { 1311 width: 42px; 1312 height: 42px; 1313 border: 4px solid var(--green-light); 1314 border-top-color: var(--green); 1315 border-radius: 50%; 1316 animation: spin 0.8s linear infinite; 1317 margin: 0 auto 16px; 1318 } 1319 1320 @keyframes spin { 1321 to { 1322 transform: rotate(360deg); 1323 } 1324 } 1325 1326 .loading-state p { 1327 font-size: 15px; 1328 color: var(--mid); 1329 } 1330 1331 /* Reveal animation */ 1332 .reveal { 1333 opacity: 0; 1334 transform: translateY(30px); 1335 transition: opacity 0.6s ease, transform 0.6s ease; 1336 } 1337 1338 .reveal.visible { 1339 opacity: 1; 1340 transform: translateY(0); 1341 } 1342 1343 .reveal-delay-1 { 1344 transition-delay: 0.1s; 1345 } 1346 1347 .reveal-delay-2 { 1348 transition-delay: 0.2s; 1349 } 1350 1351 @media (max-width: 600px) { 1352 nav { 1353 padding: 12px 16px; 1354 } 1355 1356 .nav-logo img { 1357 height: 28px; 1358 } 1359 1360 section { 1361 padding: 56px 16px; 1362 } 1363 1364 .modal { 1365 padding: 32px 20px; 1366 } 1367 1368 .pricing-grid { 1369 grid-template-columns: 1fr; 1370 } 1371 1372 .hero-pricing-preview { 1373 gap: 12px; 1374 } 1375 1376 .hero-price-pill { 1377 min-width: 140px; 1378 padding: 16px 20px; 1379 } 1380 } 1381 1382 /* ââ FEATURE SECTIONS ââ */ 1383 .features-section { 1384 background: #fff; 1385 } 1386 1387 .feature-row { 1388 display: flex; 1389 align-items: center; 1390 gap: 48px; 1391 padding: 64px 0; 1392 border-bottom: 1px solid var(--border); 1393 } 1394 1395 .feature-row:last-child { 1396 border-bottom: none; 1397 } 1398 1399 .feature-row.reverse { 1400 flex-direction: row-reverse; 1401 } 1402 1403 .feature-row-text { 1404 flex: 1 1 0; 1405 } 1406 1407 .feature-row-text h2 { 1408 font-family: 'Fraunces', serif; 1409 font-size: clamp(24px, 4vw, 36px); 1410 font-weight: 900; 1411 letter-spacing: -0.5px; 1412 line-height: 1.1; 1413 margin-bottom: 14px; 1414 color: var(--ink); 1415 } 1416 1417 .feature-row-text p { 1418 font-size: 16px; 1419 color: var(--mid); 1420 line-height: 1.7; 1421 } 1422 1423 .feature-row-image { 1424 flex: 1 1 0; 1425 display: flex; 1426 justify-content: center; 1427 } 1428 1429 .feature-row-image img { 1430 width: 100%; 1431 max-width: 420px; 1432 border-radius: 20px; 1433 box-shadow: 0 12px 48px rgba(0, 0, 0, 0.1); 1434 display: block; 1435 } 1436 1437 @media (max-width: 700px) { 1438 1439 .feature-row, 1440 .feature-row.reverse { 1441 flex-direction: column; 1442 gap: 28px; 1443 padding: 40px 0; 1444 } 1445 } 1446 1447 .hero-slider-wrap { 1448 width: 100%; 1449 max-width: 1100px; 1450 margin-top: 0; 1451 margin-bottom: 56px; 1452 border-radius: 20px; 1453 overflow: hidden; 1454 border: 1px solid rgba(32, 152, 80, 0.25); 1455 box-shadow: 0 24px 80px rgba(0, 0, 0, 0.45); 1456 animation: fadeUp 0.9s 0.5s ease both; 1457 position: relative; 1458 z-index: 1; 1459 } 1460 1461 .slider { 1462 position: relative; 1463 width: 100%; 1464 overflow: hidden; 1465 touch-action: pan-y; 1466 } 1467 1468 .slider .slides { 1469 display: flex; 1470 transition: transform 0.6s ease; 1471 will-change: transform; 1472 flex-wrap: nowrap; 1473 align-items: stretch; 1474 } 1475 1476 .slider .slide { 1477 flex: 0 0 100%; 1478 width: 100%; 1479 display: flex; 1480 align-items: center; 1481 justify-content: center; 1482 padding: 0; 1483 } 1484 1485 .slider img, 1486 .slider video { 1487 width: 100%; 1488 max-width: 100%; 1489 height: auto; 1490 display: block; 1491 } 1492 1493 .slider .arrow { 1494 position: absolute; 1495 top: 50%; 1496 transform: translateY(-50%); 1497 background: rgba(32, 152, 80, 0.75); 1498 color: #fff; 1499 border: none; 1500 padding: 10px 14px; 1501 border-radius: 8px; 1502 cursor: pointer; 1503 z-index: 10; 1504 font-size: 22px; 1505 line-height: 1; 1506 transition: background 0.2s; 1507 } 1508 1509 .slider .arrow:hover { 1510 background: var(--green-dark); 1511 } 1512 1513 .slider .arrow.left { 1514 left: 16px; 1515 } 1516 1517 .slider .arrow.right { 1518 right: 16px; 1519 } 1520 1521 /* ââ SUBSCRIPTION MANAGEMENT STATE ââ */ 1522 .sub-info-box { 1523 background: var(--green-light); 1524 border: 1px solid rgba(32, 152, 80, 0.2); 1525 border-radius: 20px; 1526 padding: 24px; 1527 margin-bottom: 20px; 1528 text-align: center; 1529 } 1530 1531 .sub-status-badge { 1532 display: inline-flex; 1533 align-items: center; 1534 gap: 6px; 1535 font-size: 12px; 1536 font-weight: 700; 1537 letter-spacing: 0.5px; 1538 text-transform: uppercase; 1539 padding: 4px 12px; 1540 border-radius: 100px; 1541 margin-bottom: 10px; 1542 } 1543 1544 .sub-status-badge.active { 1545 background: var(--green); 1546 color: #fff; 1547 } 1548 1549 .sub-status-badge.past_due { 1550 background: var(--orange); 1551 color: #fff; 1552 } 1553 1554 .sub-status-badge.cancelled { 1555 background: #e5e7eb; 1556 color: #6b7280; 1557 } 1558 1559 .sub-info-box .sub-plan-name { 1560 font-family: 'Fraunces', serif; 1561 font-size: 22px; 1562 font-weight: 900; 1563 color: var(--ink); 1564 margin-bottom: 6px; 1565 } 1566 1567 .sub-renews { 1568 font-size: 13px; 1569 color: var(--mid); 1570 } 1571 1572 .sub-source-note { 1573 font-size: 14px; 1574 color: var(--mid); 1575 line-height: 1.65; 1576 padding: 16px; 1577 background: var(--cream); 1578 border-radius: 14px; 1579 border: 1px solid var(--border); 1580 margin-bottom: 20px; 1581 } 1582 </style> 1583 1584 <!-- Structured data: the three consumer plans, so search engines and 1585 assistants quote the right prices. Keep in step with the pricing grid 1586 below. Test changes with https://validator.schema.org/ --> 1587
1587<script type="application/ld+json"> 1588 { 1589 "@context": "https://schema.org", 1590 "@graph": [ 1591 { 1592 "@type": "Product", 1593 "@id": "https://toycad.app/subscribe.html#product", 1594 "name": "ToyCAD", 1595 "description": "Full access to ToyCAD, the 3D design app that turns a child's drawing into a toy they can 3D print. Subscribe online and pay 20% less than the in-app price.", 1596 "image": "https://toycad.app/img_new/og/toycad.jpg", 1597 "brand": { "@type": "Brand", "name": "ToyCAD" }, 1598 "offers": [ 1599 { 1600 "@type": "Offer", 1601 "name": "Lifetime licence", 1602 "price": "71.99", 1603 "priceCurrency": "USD", 1604 "url": "https://toycad.app/subscribe.html#pricing", 1605 "availability": "https://schema.org/InStock" 1606 }, 1607 { 1608 "@type": "Offer", 1609 "name": "Yearly subscription", 1610 "price": "31.99", 1611 "priceCurrency": "USD", 1612 "url": "https://toycad.app/subscribe.html#pricing", 1613 "availability": "https://schema.org/InStock" 1614 }, 1615 { 1616 "@type": "Offer", 1617 "name": "Monthly subscription", 1618 "price": "5.59", 1619 "priceCurrency": "USD", 1620 "url": "https://toycad.app/subscribe.html#pricing", 1621 "availability": "https://schema.org/InStock" 1622 } 1623 ] 1624 }, 1625 { 1626 "@type": "WebPage", 1627 "@id": "https://toycad.app/subscribe.html#webpage", 1628 "url": "https://toycad.app/subscribe.html", 1629 "name": "ToyCAD \u2014 Subscribe Online & Save 20%", 1630 "inLanguage": "en-US", 1631 "isPartOf": { "@id": "https://toycad.app/#website" }, 1632 "breadcrumb": { 1633 "@type": "BreadcrumbList", 1634 "itemListElement": [ 1635 { "@type": "ListItem", "position": 1, "name": "ToyCAD", "item": "https://toycad.app/" }, 1636 { "@type": "ListItem", "position": 2, "name": "Plans & Pricing", "item": "https://toycad.app/subscribe.html" } 1637 ] 1638 } 1639 } 1640 ] 1641 } 1642 </script>
1642 1643 1644</head> 1645 1646<body> 1647 1648 <!-- Success banner (shown after returning from Stripe) --> 1649 <div class="success-banner" id="success-banner"> 1650 <div class="success-banner-text">ð You're subscribed! Open the ToyCAD app â Profile tab â sign in with <strong 1651 id="banner-email">your email</strong> to activate your subscription.</div> 1652 <div class="success-banner-actions"> 1653 <a href="https://apps.apple.com/us/app/toycad/id6711330232?mt=8" target="_blank" rel="noopener noreferrer"> 1654 <img src="img_new/appstore.png" alt="Download on App Store" style="height:38px;display:block;"> 1655 </a> 1656 <a href="https://play.google.com/store/apps/details?id=com.toycad.app&hl=en_US" target="_blank" 1657 rel="noopener noreferrer"> 1658 <img src="img_new/playstore.png" alt="Get it on Google Play" style="height:38px;display:block;"> 1659 </a> 1660 <button class="banner-manage-btn" onclick="openModal(null)">Manage Subscription â</button> 1661 </div> 1662 </div> 1663 1664 <nav> 1665 <a href="https://toycad.app" class="nav-logo"> 1666 <img src="img_new/logo.png" alt="ToyCAD"> 1667 </a> 1668 <div id="nav-signed-out"> 1669 <button class="nav-cta" onclick="openModal(null, 'signin')">Sign In</button> 1670 </div> 1671 <div id="nav-signed-in" style="display:none;align-items:center;gap:10px;"> 1672 <span id="nav-user-email" onclick="openModal(null)" 1673 style="font-size:13px;color:rgba(255,255,255,0.55);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;" 1674 title="Manage subscription"></span> 1675 <button class="nav-cta" 1676 style="background:transparent;border:1px solid rgba(255,255,255,0.25);font-size:13px;padding:8px 16px;" 1677 onclick="handleSignOut()">Sign Out</button> 1678 </div> 1679 </nav> 1680 1681 <!-- HERO --> 1682 <section class="hero"> 1683 <!-- HERO SLIDER --> 1684 <div class="hero-slider-wrap"> 1685 <div class="slider" id="heroSlider" aria-roledescription="carousel"> 1686 <div class="slides"> 1687 <div class="slide"> 1688 <picture> 1689 <source srcset="img_new/headerVertical.png" media="(orientation: portrait)"> 1690 <img src="img_new/header.png" alt="ToyCAD Header Image"> 1691 </picture> 1692 </div> 1693 <div class="slide"> 1694 <video id="heroIntroVideo" playsinline muted preload="metadata" 1695 style="width:100%;display:block;"> 1696 <source src="img/getstarted_with_subtitles.mp4" type="video/mp4"> 1697 </video> 1698 </div> 1699 <div class="slide"> 1700 <picture> 1701 <source srcset="img_new/CoverPhotoVertical.png" media="(orientation: portrait)"> 1702 <img src="img_new/CoverPhoto.png" alt="ToyCAD Header Image"> 1703 </picture> 1704 </div> 1705 <div class="slide"> 1706 <picture> 1707 <source srcset="img_new/kid1.png" media="(orientation: portrait)"> 1708 <img src="img_new/kid1expanded.png" alt="ToyCAD Header Image"> 1709 </picture> 1710 </div> 1711 <div class="slide"> 1712 <picture> 1713 <source srcset="img_new/kid2.png" media="(orientation: portrait)"> 1714 <img src="img_new/kid2expanded.png" alt="ToyCAD Header Image"> 1715 </picture> 1716 </div> 1717 <div class="slide"> 1718 <picture> 1719 <source srcset="img_new/kid3.jpg" media="(orientation: portrait)"> 1720 <img src="img_new/kid3expanded.jpg" alt="ToyCAD Header Image"> 1721 </picture> 1722 </div> 1723 </div> 1724 <button class="arrow left" id="sliderPrev" aria-label="Previous">‹</button> 1725 <button class="arrow right" id="sliderNext" aria-label="Next">›</button> 1726 </div> 1727 </div> 1728 1729 <div class="hero-badge"><span class="dot"></span>The Easiest 3D Toy Design App for Kids</div> 1730 <h1>Turn Your Child's Imagination<br>into <em>Real Toys</em> Today!</h1> 1731 <p class="hero-sub">ToyCAD lets kids draw any toy and 3D print it â no CAD experience needed. </p> 1732 <div 1733 style="display:flex;flex-direction:column;align-items:center;gap:32px;margin-top:8px;width:100%;max-width:700px;"> 1734 1735 <!-- Step 1: Try for free --> 1736 <div style="display:flex;flex-direction:column;align-items:center;gap:16px;width:100%;"> 1737 <div style="display:flex;align-items:center;gap:12px;"> 1738 <div 1739 style="width:38px;height:38px;background:rgba(255,255,255,0.12);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;color:rgba(255,255,255,0.5);flex-shrink:0;"> 1740 1</div> 1741 <p style="font-size:19px;font-weight:700;color:rgba(255,255,255,0.7);margin:0;">Try ToyCAD for free
1742 <span 1743 style="background:rgba(32,152,80,0.25);color:#4cce7e;border:1px solid rgba(76,206,126,0.4);border-radius:100px;padding:2px 12px;font-size:14px;font-weight:700;letter-spacing:0.3px;white-space:nowrap;">3-day 1744 free trial</span> 1745 </p> 1746 </div> 1747 <div style="display:flex;align-items:center;gap:14px;flex-wrap:wrap;justify-content:center;"> 1748 <a href="https://apps.apple.com/us/app/toycad/id6711330232?mt=8" target="_blank" 1749 rel="noopener noreferrer"> 1750 <img src="img_new/appstore.png" alt="Download on App Store" 1751 style="width:160px;height:auto;display:block;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.3);"> 1752 </a> 1753 <a href="https://play.google.com/store/apps/details?id=com.toycad.app&hl=en_US" target="_blank" 1754 rel="noopener noreferrer"> 1755 <img src="img_new/playstore.png" alt="Get it on Google Play" 1756 style="width:160px;height:auto;display:block;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.3);"> 1757 </a> 1758 <!-- <a href="https://toycad.online" target="_blank" rel="noopener noreferrer"> 1759 <img src="img_new/webapp.png" alt="Try it on the Web" 1760 style="width:160px;height:auto;display:block;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.3);"> 1761 </a> --> 1762 </div> 1763 </div> 1764 1765 <!-- Divider --> 1766 <div style="display:flex;align-items:center;gap:14px;width:100%;max-width:500px;"> 1767 <div style="flex:1;height:1px;background:rgba(255,255,255,0.1);"></div> 1768 <span style="font-size:13px;color:rgba(255,255,255,0.3);white-space:nowrap;">then, save 20% when you 1769 subscribe online</span> 1770 <div style="flex:1;height:1px;background:rgba(255,255,255,0.1);"></div> 1771 </div> 1772 1773 <!-- Step 2: Subscribe online --> 1774 <div style="display:flex;flex-direction:column;align-items:center;gap:16px;width:100%;"> 1775 <div style="display:flex;align-items:center;gap:12px;"> 1776 <div 1777 style="width:38px;height:38px;background:rgba(32,152,80,0.3);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;color:#4cce7e;flex-shrink:0;"> 1778 2</div> 1779 <p style="font-size:19px;font-weight:700;color:rgba(255,255,255,0.7);margin:0;">Subscribe online â 1780 save 20%</p> 1781 </div> 1782 <div style="display:flex;gap:12px;flex-wrap:wrap;justify-content:center;"> 1783 <button class="btn-primary" 1784 onclick="document.querySelector('#pricing').scrollIntoView({behavior:'smooth'})">See Plans & 1785 Pricing â</button> 1786 <a href="https://toycad.app/subscribeSchools.html#pricing" 1787 style="display:inline-flex;align-items:center;justify-content:center;border:2px solid rgba(76,206,126,0.5);color:#4cce7e;border-radius:100px;padding:14px 24px;font-family:'DM Sans',sans-serif;font-size:15px;font-weight:600;text-decoration:none;white-space:nowrap;transition:border-color 0.15s,background 0.15s;" 1788 onmouseover="this.style.borderColor='#4cce7e';this.style.background='rgba(76,206,126,0.08)'" 1789 onmouseout="this.style.borderColor='rgba(76,206,126,0.5)';this.style.background='transparent'">School 1790 Plans â</a> 1791 </div> 1792 <p class="hero-note">Secure checkout via Stripe · cancel yearly any time</p> 1793 </div> 1794 1795 </div> 1796 1797 </section> 1798 1799 <div class="proof-bar"> 1800 <span>â â â â â </span> "My girls 8 and 6 love it and we printed a couple items they made. I was amazed how 1801 fast they picked it up â especially my 6 yr old!" â Aaron W. 1802 </div> 1803 1804 <!-- FEATURES --> 1805 <section class="features-section"> 1806 <div class="container"> 1807 <div class="feature-row reveal"> 1808 <div class="feature-row-text"> 1809 <div class="section-label">Kid-Friendly Design Tool</div> 1810 <h2>The Easiest Way for Kids<br>to Design Toys</h2> 1811 <p>ToyCAD's intuitive interface seamlessly turns simple drawings into 3D models â perfect for little 1812 creators. It's all about making 3D design a fun, approachable experience for any age.</p> 1813 </div> 1814 <div class="feature-row-image"> 1815 <img src="img_new/kid1.png" alt="Kid drawing in ToyCAD"> 1816 </div> 1817 </div> 1818 <div class="feature-row reverse reveal"> 1819 <div class="feature-row-text"> 1820 <div class="section-label">Educational &
1820 Entertaining</div> 1821 <h2>Builds Creativity &<br>Problem-Solving Skills</h2> 1822 <p>ToyCAD blends education with play. Every design session builds your child's spatial reasoning, 1823 problem-solving, and creative confidence â skills that set them up for future success.</p> 1824 </div> 1825 <div class="feature-row-image"> 1826 <img src="img_new/kid2.png" alt="Child learning with ToyCAD"> 1827 </div> 1828 </div> 1829 <div class="feature-row reveal"> 1830 <div class="feature-row-text"> 1831 <div class="section-label">Design, Print, Play</div> 1832 <h2>From Drawing to Real Toy<br>in Their Hands</h2> 1833 <p>From idea to physical toy â kids see their creations come to life. Holding something they 1834 designed themselves sparks a pride and love for making things that lasts a lifetime.</p> 1835 </div> 1836 <div class="feature-row-image"> 1837 <img src="img_new/kid3.jpg" alt="Kid holding a printed toy"> 1838 </div> 1839 </div> 1840 </div> 1841 </section> 1842 1843 <!-- MAKE ALL KINDS OF TOYS --> 1844 <section class="toys-section"> 1845 <div class="container"> 1846 <div class="section-label" style="color:#4cce7e;">What You Can Make</div> 1847 <h2 class="section-title">Make All Kinds of Toys</h2> 1848 <p class="section-sub">ToyCAD is easy but still powerful! Combining freeform shaping like playdough with 1849 accurate engineering concepts for things like joints.</p> 1850 <div class="toys-grid"> 1851 <div class="toy-card reveal"> 1852 <video autoplay muted loop playsinline> 1853 <source src="img_new/interlocking1.mp4" type="video/mp4"> 1854 </video> 1855 <h3>Interlocking Parts</h3> 1856 </div> 1857 <div class="toy-card reveal reveal-delay-1"> 1858 <video autoplay muted loop playsinline> 1859 <source src="img_new/articulate.mp4" type="video/mp4"> 1860 </video> 1861 <h3>Articulated Toys</h3> 1862 </div> 1863 <div class="toy-card reveal reveal-delay-2"> 1864 <video autoplay muted loop playsinline> 1865 <source src="img_new/magnetic.mp4" type="video/mp4"> 1866 </video> 1867 <h3>Snap-Fit Toys</h3> 1868 </div> 1869 <div class="toy-card reveal"> 1870 <img src="img_new/endless.gif" alt="Character Toys"> 1871 <h3>Character Toys</h3> 1872 </div> 1873 </div> 1874 </div> 1875 </section> 1876 1877 <!-- WHAT PARENTS SAY --> 1878 <section class="parents-section"> 1879 <div class="container"> 1880 <div class="section-label">Real Reviews</div> 1881 <h2 class="section-title">What Parents Say</h2> 1882 <div class="parents-grid"> 1883 <div class="parent-card reveal"> 1884 <p>"I have a 4 year old and she <strong>loves</strong> to draw in this app with me. Watching her 1885 face glow with excitement when a random set of squiggles can become a 3D print she can hold is 1886 amazing for us both. Toycad is worth 5 stars to me."</p> 1887 <span class="parent-name">â Drivenapollo</span> 1888 </div> 1889 <div class="parent-card reveal reveal-delay-1"> 1890 <p>"My daughter and I absolutely <strong>ADORE</strong> the app you've created. It's brilliant! So 1891 intuitive and easy to dive into!"</p> 1892 <span class="parent-name">â Kate O.</span> 1893 </div> 1894 <div class="parent-card reveal reveal-delay-2"> 1895 <p>"My girls 8 and 6 <strong>love</strong> it and we printed a couple items they made. I was amazed 1896 to see how fast they picked it up â especially my 6 yr old!"</p>
1897 <span class="parent-name">â Aaron W.</span> 1898 </div> 1899 <div class="parent-card reveal"> 1900 <p>"My son <strong>loves</strong> it. He's 6 and it's amazing to see the joy in his eyes when I hand 1901 him something he designed himself."</p> 1902 <span class="parent-name">â Michael J.</span> 1903 </div> 1904 <div class="parent-card reveal reveal-delay-1"> 1905 <p>"My 5yo <strong>loves</strong> it and did that all on her own. In fact wouldn't let me help her!" 1906 </p> 1907 <span class="parent-name">â Matt W.</span> 1908 </div> 1909 <div class="parent-card reveal reveal-delay-2"> 1910 <p>"We had a lot of fun designing and printing the first toys for 5 and 6 year old yesterday! Really 1911 <strong>impressed</strong> overall with the program!" 1912 </p> 1913 <span class="parent-name">â Ryan W.</span> 1914 </div> 1915 <div class="parent-card reveal"> 1916 <p>"My kids absolutely <strong>love</strong> the app by the way. I appreciate it! Keep up the good 1917 work!"</p> 1918 <span class="parent-name">â Brandon F.</span> 1919 </div> 1920 <div class="parent-card reveal reveal-delay-1"> 1921 <p>"My daughter just started using the web version and is loving knowing that we can create 1922 <strong>whatever</strong> she is drawing on our printer!" 1923 </p> 1924 <span class="parent-name">â Amanda P.</span> 1925 </div> 1926 </div> 1927 </div> 1928 </section> 1929 1930 <!-- WHAT KIDS MADE --> 1931 <section class="kids-section"> 1932 <div class="container"> 1933 <div class="section-label">Community Gallery</div> 1934 <h2 class="section-title">What Kids Made</h2> 1935 <div class="toys-grid" style="margin-top:40px;"> 1936 <div class="toy-card reveal reveal-delay-1" style="background:var(--cream);border-color:var(--border);"> 1937 <img src="img_new/kids/dinasour.jpg" alt="Kid creation"> 1938 </div> 1939 <div class="toy-card reveal reveal-delay-1" style="background:var(--cream);border-color:var(--border);"> 1940 <img src="img_new/kids/sp_kitty.jpg" alt="Kid creation"> 1941 </div> 1942 <div class="toy-card reveal" style="background:var(--cream);border-color:var(--border);"><img 1943 src="img_new/kids/friends.jpg" alt="Kid creation"></div> 1944 <div class="toy-card reveal reveal-delay-1" style="background:var(--cream);border-color:var(--border);"> 1945 <img src="img_new/kids/0.JPG" alt="Kid creation"> 1946 </div> 1947 <div class="toy-card reveal reveal-delay-2" style="background:var(--cream);border-color:var(--border);"> 1948 <img src="img_new/kids/clicker.jpg" alt="Kid creation"> 1949 </div> 1950 <div class="toy-card reveal reveal-delay-1" style="background:var(--cream);border-color:var(--border);"> 1951 <img src="img_new/kids/1.jpg" alt="Kid creation"> 1952 </div> 1953 <div class="toy-card reveal reveal-delay-2" style="background:var(--cream);border-color:var(--border);"> 1954 <img src="img_new/kids/2.jpg" alt="Kid creation"> 1955 </div> 1956 <div class="toy-card reveal" style="background:var(--cream);border-color:var(--border);"><img 1957 src="img_new/kids/3.jpg" alt="Kid creation"></div> 1958 <div class="toy-card reveal reveal-delay-1" style="background:var(--cream);border-color:var(--border);"> 1959 <img src="img_new/kids/4.jpg" alt="Kid creation"> 1960 </div> 1961 <div class="toy-card reveal reveal-delay-2" style="background:var(--cream);border-color:var(--border);"> 1962 <img src="img_new/kids/5.jpg" alt="Kid creation"> 1963 </div> 1964 <div class="toy-card reveal" style="background:var(--cream);border-color:var(--border);"><img 1965 src="img_new/kids/6.jpg" alt="Kid creation"></div> 1966 <div class="toy-card reveal reveal-delay-1" style="background:var(--cream);border-color:var(--border);"> 1967 <img src="img_new/kids/7.jpg" alt="Kid creation"> 1968 </div> 1969 <div class="toy-card reveal reveal-delay-2" style="background:var(--cream);border-color:var(--border);"> 1970 <img src="img_new/kids/8.jpg" alt="Kid creation"> 1971 </div> 1972 <div class="toy-card reveal" style="background:var(--cream);border-color:var(--border);"><img 1973 src="img_new/kids/10.jpg" alt="Kid creation"></div> 1974 <div class="toy-card reveal reveal-delay-1" style="background:var(--cream);border-color:var(--border);"> 1975 <img src="img_new/kids/11.jpg" alt="Kid creation"> 1976 </div> 1977 <div class="toy-card reveal reveal-delay-2" style="background:var(--cream);border-color:var(--border);"> 1978 <img src="img_new/kids/12.jpg" alt="Kid creation"> 1979 </div> 1980 </div> 1981 </div> 1982 </section> 1983 <section class="pricing" id="pricing"> 1984 <div class="container"> 1985 <div class="section-label">
1985Online Exclusive Pricing</div> 1986 <h2 class="section-title">20% off when you<br>subscribe online.</h2> 1987 <p class="section-sub">Subscribe here to save 20% vs. in-app prices. Full access activates immediately in 1988 the ToyCAD app when you sign in with your account.</p> 1989 <div class="pricing-grid"> 1990 <div class="pricing-card reveal"> 1991 <div class="pricing-discount-badge">20% OFF</div> 1992 <div 1993 style="font-size:11px;font-weight:800;letter-spacing:2px;text-transform:uppercase;color:var(--mid);margin-bottom:14px;"> 1994 Individual</div> 1995 <h3>Lifetime License</h3> 1996 <div class="price-row"> 1997 <div class="price">$71.99</div> 1998 <div class="price-was">$89.99</div> 1999 </div> 2000 <div class="price-note">one-time · use forever · save $18</div> 2001 <ul> 2002 <li>One-time payment â never renews</li> 2003 <li>Full app access forever</li> 2004 <li>All future updates included</li> 2005 <li>Works with any 3D printer</li> 2006 <li>Activate in the app via email sign-in</li> 2007 </ul> 2008 <button class="btn-outline" onclick="openModal('lifetime')">Get Lifetime â $71.99 â</button> 2009 </div> 2010 <div class="pricing-card featured reveal reveal-delay-1"> 2011 <div class="pricing-badge">MOST POPULAR</div> 2012 <div class="pricing-discount-badge">20% OFF</div> 2013 <div 2014 style="font-size:11px;font-weight:800;letter-spacing:2px;text-transform:uppercase;color:rgba(255,255,255,0.45);margin-bottom:14px;"> 2015 Individual</div> 2016 <h3>Yearly Subscription</h3> 2017 <div class="price-row"> 2018 <div class="price">$31.99</div> 2019 <div class="price-was">$39.99</div> 2020 </div> 2021 <div class="price-note">per year · billed annually · save $8/yr</div> 2022 <ul> 2023 <li>Full app access for one year</li> 2024 <li>Auto-renews · cancel any time</li> 2025 <li>All future updates included</li> 2026 <li>Works with any 3D printer</li> 2027 <li>Activate in the app via email sign-in</li> 2028 </ul> 2029 <button class="btn-green" onclick="openModal('yearly')">Get Yearly â $31.99/yr â</button> 2030 </div> 2031 <div class="pricing-card reveal reveal-delay-2"> 2032 <div class="pricing-discount-badge">20% OFF</div> 2033 <div 2034 style="font-size:11px;font-weight:800;letter-spacing:2px;text-transform:uppercase;color:var(--mid);margin-bottom:14px;"> 2035 Individual</div> 2036 <h3>Monthly Subscription</h3> 2037 <div class="price-row"> 2038 <div class="price">$5.59</div> 2039 <div class="price-was">$6.99</div> 2040 </div> 2041 <div class="price-note">per month · billed monthly · save $1.40/mo</div> 2042 <ul> 2043 <li>Full app access each month</li> 2044 <li>Auto-renews · cancel any time</li> 2045 <li>All future updates included</li> 2046 <li>Works with any 3D printer</li> 2047 <li>Activate in the app via email sign-in</li> 2048 </ul> 2049 <button class="btn-outline" onclick="openModal('monthly')">Get Monthly â $5.59/mo â</button> 2050 </div> 2051 <div class="pricing-card reveal"> 2052 <div 2053 style="font-size:11px;font-weight:800;letter-spacing:2px;text-transform:uppercase;
2053color:var(--mid);margin-bottom:14px;"> 2054 Schools</div> 2055 <h3>School Plans</h3> 2056 <div class="price-row"> 2057 <div class="price" style="font-size:36px;">From $9</div> 2058 </div> 2059 <div class="price-note">per month · billed yearly</div> 2060 <ul> 2061 <li>Teacher, Classroom & School tiers</li> 2062 <li>Up to 3, 35, or unlimited students</li> 2063 <li>Class codes students redeem in-app</li> 2064 <li>Manage seats from a teacher dashboard</li> 2065 <li>Cancel any time</li> 2066 </ul> 2067 <a href="https://toycad.app/subscribeSchools.html" class="btn-outline">View School Plans â</a> 2068 </div> 2069 </div> 2070 <p style="margin-top:24px;font-size:13px;color:var(--mid);"> 2071 After subscribing, open ToyCAD â Profile tab â sign in with the same email to activate your subscription 2072 in the app. 2073 </p> 2074 </div> 2075 </section> 2076 2077 <!-- HOW IT WORKS --> 2078 <section class="how" id="how"> 2079 <div class="container"> 2080 <div class="section-label">How It Works</div> 2081 <h2 class="section-title">Subscribe here. Use it<br>in the app. That's it.</h2> 2082 <p class="section-sub">Your subscription activates automatically the moment you sign in with your account 2083 inside the ToyCAD app.</p> 2084 <div class="steps"> 2085 <div class="step reveal"> 2086 <div class="step-num">1</div> 2087 <h3>Subscribe online</h3> 2088 <p>Pick Lifetime, Yearly, or Monthly, create an account, and complete payment via Stripe. Takes 2089 under 2 2090 minutes.</p> 2091 </div> 2092 <div class="step reveal reveal-delay-1"> 2093 <div class="step-num">2</div> 2094 <h3>Open ToyCAD â Profile</h3> 2095 <p>In the app, go to the Profile tab and sign in with the same email and password you used here.</p> 2096 </div> 2097 <div class="step reveal reveal-delay-2"> 2098 <div class="step-num">3</div> 2099 <h3>Subscription activates</h3> 2100 <p>Your subscription is recognized instantly. Full access activated â start designing your first 2101 toy!</p> 2102 </div> 2103 </div> 2104 </div> 2105 </section> 2106 2107 <!-- BENEFITS --> 2108 <section class="why"> 2109 <div class="container"> 2110 <div class="section-label">What You Get</div> 2111 <h2 class="section-title">Full ToyCAD â everything<br>unlocked.</h2> 2112 <p class="section-sub">No feature limits, no watermarks. The complete ToyCAD experience on your iPad.</p> 2113 <div class="benefits"> 2114 <div class="benefit-card reveal"> 2115 <div class="benefit-icon">âï¸</div> 2116 <h3>Draw any toy freehand</h3> 2117 <p>Sketch any shape with your finger or Apple Pencil. ToyCAD converts it to a 3D-printable STL 2118 instantly.</p> 2119 </div> 2120 <div class="benefit-card reveal reveal-delay-1"> 2121 <div class="benefit-icon">ð</div> 2122 <h3>Interlocking parts</h3> 2123 <p>Engineer pieces that snap and fit together. Hinges, connectors, and moving joints â all from a 2124 drawing.</p> 2125 </div> 2126 <div class="benefit-card reveal reveal-delay-2"> 2127 <div class="benefit-icon">ð§²</div> 2128 <h3>Snap-fit & articulated</h3> 2129 <p>Design snap-fit toys and movable articulated figures. Full engineering features, zero 2130 tutorials needed.</p> 2131 </div> 2132 <div class="benefit-card reveal"> 2133 <div class="benefit-icon">ð¨ï¸</div> 2134 <h3>
2134Any 3D printer</h3> 2135 <p>Exports standard STL â works with Bambu Lab, Creality, Prusa, MakerBot and every major brand.</p> 2136 </div> 2137 <div class="benefit-card reveal reveal-delay-1"> 2138 <div class="benefit-icon">ð¦</div> 2139 <h3>All future updates</h3> 2140 <p>New tools, new toy types, new tutorials â all included with your subscription at no extra cost. 2141 </p> 2142 </div> 2143 <div class="benefit-card reveal reveal-delay-2"> 2144 <div class="benefit-icon">ð</div> 2145 <h3>One account, any device</h3> 2146 <p>Your subscription follows your account. Sign into any iPad and your full access is there 2147 instantly.</p> 2148 </div> 2149 </div> 2150 </div> 2151 </section> 2152 2153 <!-- DOWNLOAD / TRY FIRST --> 2154 <section class="download-section"> 2155 <div class="container"> 2156 <div class="section-label">Try Before You Subscribe</div> 2157 <h2 class="section-title">Download free and<br>try your first designs.</h2> 2158 <p class="section-sub" style="margin:0 auto;"> 2159 ToyCAD is free to download. Explore the app and try a few designs â then subscribe online to unlock 2160 everything and save 20%. 2161 </p> 2162 <div class="store-buttons"> 2163 <a href="https://apps.apple.com/us/app/toycad/id6711330232?mt=8" target="_blank"> 2164 <img src="img_new/appstore.png" alt="Download on App Store"> 2165 </a> 2166 <a href="https://play.google.com/store/apps/details?id=com.toycad.app&hl=en_US" target="_blank"> 2167 <img src="img_new/playstore.png" alt="Get it on Google Play"> 2168 </a> 2169 </div> 2170 <p style="margin-top:20px;font-size:13px;color:var(--mid);"> 2171 Remember: subscribe here (not in-app) to get the 20% online discount. 2172 </p> 2173 </div> 2174 </section> 2175 2176 <!-- FAQ --> 2177 <section class="faq"> 2178 <div class="container"> 2179 <div class="section-label">Questions</div> 2180 <h2 class="section-title">Everything you need to know</h2> 2181 <div class="faq-list"> 2182 <div class="faq-item"> 2183 <button class="faq-q" onclick="toggleFaq(this)">How does the 20% online discount work?<span 2184 class="arrow">+</span></button> 2185 <div class="faq-a">Subscribe here on the website instead of through the App Store or Google Play. By 2186 subscribing directly, we skip the app store fee and pass the savings to you â 20% off. The same 2187 full ToyCAD access, just cheaper.</div> 2188 </div> 2189 <div class="faq-item"> 2190 <button class="faq-q" onclick="toggleFaq(this)">How do I activate my subscription in the app?<span 2191 class="arrow">+</span></button> 2192 <div class="faq-a">After subscribing here, open ToyCAD on your iPad and go to the Profile tab. Sign 2193 in with the same email and password you used when subscribing. Your subscription will activate 2194 automatically within seconds.</div> 2195 </div> 2196 <div class="faq-item"> 2197 <button class="faq-q" onclick="toggleFaq(this)">What's the difference between Lifetime and 2198 Yearly?<span class="arrow">+</span></button> 2199 <div class="faq-a">The Lifetime license is a one-time payment of $71.99 â you pay once and have full 2200 access forever, including all future updates. The Yearly subscription is $31.99/year and renews 2201 annually. You can cancel the yearly plan at any time from your account settings.</div> 2202 </div> 2203 <div class="faq-item"> 2204 <button class="faq-q" onclick="toggleFaq(this)">Is my payment secure?<span 2205 class="arrow">+</span></button> 2206 <div class="faq-a">Yes. All payments are processed securely by Stripe, the same payment platform 2207 used by millions of businesses worldwide. We never store your card details.</div> 2208 </div> 2209 <div class="faq-item"> 2210 <button class="faq-q" onclick="toggleFaq(this)">Can I use ToyCAD on multiple devices?<span 2211 class="arrow">+</span></button> 2212 <div class="faq-a">Yes. Your subscription is tied to your account (email). Sign in on any iPad with 2213 the same email and password to activate your subscription on that device.</div> 2214 </div> 2215 <div class="faq-item"> 2216 <button class="faq-q" onclick="toggleFaq(this)">What 3D printers does it work with?<span 2217 class="arrow">+</span></button> 2218 <div class="faq-a">
2218ToyCAD exports standard STL files which work with every major 3D printer 2219 including Bambu Lab, Creality, Prusa, MakerBot, and more.</div> 2220 </div> 2221 </div> 2222 </div> 2223 </section> 2224 2225 <!-- CTA --> 2226 <section class="cta-section"> 2227 <h2 class="section-title">Ready to start designing?</h2> 2228 <p class="section-sub">Subscribe online now and save 20%. Full ToyCAD access â activated instantly in the app. 2229 </p> 2230 <button class="btn-primary" onclick="openModal('lifetime')">Get started â 20% Off â</button> 2231 <p class="cta-note">Secure checkout via Stripe. No hidden fees.</p> 2232 </section> 2233 2234 <footer> 2235 <p>© 2026 ToyCAD LLC · Burlington, MA · <a href="https://toycad.app/contact.html">Contact</a> · <a 2236 href="https://toycad.app/privacyPolicy.html">Privacy Policy</a> · <a 2237 href="https://toycad.app/webTerms.html">Terms</a></p> 2238 </footer> 2239 2240 <!-- SUBSCRIPTION MODAL --> 2241 <div class="modal-overlay" id="sub-modal" onclick="handleModalOverlayClick(event)"> 2242 <div class="modal" id="modal-inner"> 2243 <button class="modal-close" onclick="closeModal()">Ã</button> 2244 2245 <!-- Plan chip (shown in auth + checkout states) --> 2246 <div class="modal-plan-chip" id="modal-plan-chip" style="display:none;"> 2247 ⦠<span id="modal-plan-chip-text">Lifetime â $71.99</span> 2248 </div> 2249 2250 <!-- Error / success message --> 2251 <div class="modal-error" id="modal-error"></div> 2252 2253 <!-- ââ STATE: AUTH ââ --> 2254 <div id="modal-auth-state"> 2255 <h2 id="auth-state-heading">Create your account</h2> 2256 <p class="modal-sub">Create a free account or sign in â then complete your subscription.</p> 2257 2258 <div class="auth-tabs"> 2259 <button class="auth-tab active" id="tab-signup" onclick="setAuthMode('signup')">Sign Up</button> 2260 <button class="auth-tab" id="tab-signin" onclick="setAuthMode('signin')">Log In</button> 2261 </div> 2262 2263 <!-- Sign Up form --> 2264 <div id="auth-signup-form"> 2265 <div class="form-group"> 2266 <label>Email address</label> 2267 <input type="email" id="signup-email" placeholder="[email protected]" autocomplete="email"> 2268 </div> 2269 <div class="form-group"> 2270 <label>Password <span style="color:var(--mid);font-weight:400">(min 6 characters)</span></label> 2271 <input type="password" id="signup-password" placeholder="Create a password" 2272 autocomplete="new-password"> 2273 </div> 2274 <button class="modal-submit-btn" id="signup-btn" onclick="handleSignUp()">Create Account & Continue 2275 â</button> 2276 <p class="form-fine">By continuing you agree to our <a href="https://toycad.app/webTerms.html" 2277 style="color:var(--green)">Terms</a> and <a href="https://toycad.app/privacyPolicy.html" 2278 style="color:var(--green)">Privacy Policy</a>.</p> 2279 </div> 2280 2281 <!-- Sign In form --> 2282 <div id="auth-signin-form" style="display:none;"> 2283 <div class="form-group"> 2284 <label>Email address</label> 2285 <input type="email" id="signin-email" placeholder="[email protected]" autocomplete="email"> 2286 </div> 2287 <div class="form-group"> 2288 <label>Password</label> 2289 <input type="password" id="signin-password" placeholder="Your password" 2290 autocomplete="current-password"> 2291 </div> 2292 <button class="modal-submit-btn" id="signin-btn" onclick="handleSignIn()">Log In & Continue 2293 â</button> 2294 <button class="modal-link-btn" onclick="setAuthMode('forgot')">Forgot password?</button> 2295 </div> 2296 2297 <!-- Forgot Password form --> 2298 <div id="auth-forgot-form" style="display:none;"> 2299 <p class="modal-sub" style="margin-bottom:16px;">Enter your email and we'll send you a reset link. 2300 </p> 2301 <div class="form-group"> 2302 <label>Email address</label> 2303 <input type="email" id="forgot-email" placeholder="[email protected]" autocomplete="email"> 2304 </div> 2305 <button class="modal-submit-btn" id="forgot-btn" onclick="handleForgotPassword()">Send Reset 2306 Email</button> 2307 <button class="modal-link-btn" onclick="setAuthMode('signin')">â Back to Log In</button> 2308 </div> 2309 </div> 2310 2311 <!-- ââ STATE: CHECKOUT ââ --> 2312 <div id="modal-checkout-state" style="display:none;"> 2313 <h2>Complete your subscription</h2> 2314 <p class="modal-sub">You're signed in. Review your plan and proceed to secure checkout.</p> 2315 <div class="checkout-plan-box"> 2316 <div class="plan-label">Your selected plan</div> 2317 <div class="plan-name" id="checkout-plan-name">Lifetime License</div> 2318 <div class="plan-price" id="checkout-plan-price">$71.99</div> 2319 <div class="plan-note" id="checkout-plan-note">one-time · use forever</div> 2320 </div> 2321 <div class="checkout-user-row">
2322 <span>Signed in as:</span> 2323 <strong id="checkout-user-email"></strong> 2324 <button onclick="handleSignOut()" 2325 style="margin-left:auto;background:none;border:none;color:var(--mid);font-size:12px;cursor:pointer;text-decoration:underline;">Sign 2326 out</button> 2327 </div> 2328 <button class="modal-submit-btn" id="checkout-btn" onclick="handleCheckout()">Continue to Secure 2329 Checkout â</button> 2330 <p class="form-fine">Secure payment via Stripe · 20% online discount applied</p> 2331 </div> 2332 2333 <!-- ââ STATE: LOADING ââ --> 2334 <div id="modal-loading-state" style="display:none;"> 2335 <div class="loading-state"> 2336 <div class="spinner"></div> 2337 <p>Preparing secure checkoutâ¦</p> 2338 </div> 2339 </div> 2340 2341 <!-- ââ STATE: SUCCESS ââ --> 2342 <div id="modal-success-state" style="display:none;"> 2343 <div class="modal-success-state"> 2344 <div class="success-icon">ð</div> 2345 <h3>You're subscribed!</h3> 2346 <p>Your <strong id="success-plan-name">ToyCAD</strong> subscription is active. Now activate it in 2347 the app:</p> 2348 <div class="activate-steps"> 2349 <p>To activate in the ToyCAD app:</p> 2350 <ol> 2351 <li>Download ToyCAD from the App Store or Google Play</li> 2352 <li>Open the app and go to the <strong>Profile</strong> tab</li> 2353 <li>Sign in with email: <strong id="success-email"></strong></li> 2354 <li>Your subscription activates automatically â</li> 2355 </ol> 2356 </div> 2357 <div style="display:flex;flex-direction:column;align-items:center;gap:10px;margin-top:20px;"> 2358 <a href="https://apps.apple.com/us/app/toycad/id6711330232?mt=8" target="_blank"> 2359 <img src="img_new/appstore.png" alt="Download on App Store" style="height:44px;"> 2360 </a> 2361 <a href="https://play.google.com/store/apps/details?id=com.toycad.app&hl=en_US" target="_blank"> 2362 <img src="img_new/playstore.png" alt="Get it on Google Play" style="height:44px;"> 2363 </a> 2364 </div> 2365 </div> 2366 </div> 2367 2368 <!-- ââ STATE: SUBSCRIPTION MANAGEMENT ââ --> 2369 <div id="modal-subscription-state" style="display:none;"> 2370 <h2>Your Subscription</h2> 2371 <p class="modal-sub" id="sub-state-user-email" style="margin-bottom:20px;"></p> 2372 <div class="sub-info-box"> 2373 <div id="sub-status-badge" class="sub-status-badge active">â <span 2374 id="sub-status-text">Active</span></div> 2375 <div class="sub-plan-name" id="sub-plan-name">Lifetime License</div> 2376 <div class="sub-renews" id="sub-renews-text"></div> 2377 </div> 2378 <!-- Web (Stripe) subscriber actions --> 2379 <div id="sub-web-actions"> 2380 <button class="modal-submit-btn" id="manage-sub-btn" onclick="handleManageSubscription()">Manage 2381 Subscription â</button> 2382 <p class="form-fine" style="margin-top:10px;">Opens Stripe's secure billing portal â update your 2383 payment method, download invoices, or cancel.</p> 2384 </div> 2385 <!-- In-app subscriber note --> 2386 <div id="sub-inapp-note" style="display:none;"> 2387 <div class="sub-source-note"> 2388 This subscription was purchased through the <strong>App Store or Google Play</strong>. To manage 2389 or cancel it, go to <strong>Settings â [Your Name] â Subscriptions</strong> on your device, or 2390 open the ToyCAD app. 2391 </div> 2392 </div> 2393 <div style="display:flex;flex-direction:column;align-items:center;gap:10px;margin-top:20px;"> 2394 <p style="font-size:13px;
2394color:var(--mid);margin-bottom:4px;">Open the app and sign in to use your 2395 subscription:</p> 2396 <a href="https://apps.apple.com/us/app/toycad/id6711330232?mt=8" target="_blank" 2397 rel="noopener noreferrer"> 2398 <img src="img_new/appstore.png" alt="Download on App Store" style="height:44px;"> 2399 </a> 2400 <a href="https://play.google.com/store/apps/details?id=com.toycad.app&hl=en_US" target="_blank" 2401 rel="noopener noreferrer"> 2402 <img src="img_new/playstore.png" alt="Get it on Google Play" style="height:44px;"> 2403 </a> 2404 </div> 2405 <div style="text-align:center;margin-top:16px;"> 2406 <button onclick="handleSignOut()" 2407 style="background:none;border:none;color:var(--mid);font-size:13px;cursor:pointer;text-decoration:underline;">Sign 2408 out</button> 2409 </div> 2410 </div> 2411 2412 </div> 2413 </div> 2414 2415 <!-- Firebase compat SDK --> 2416
2416<script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js"></script>
2416 2417
2417<script src="https://www.gstatic.com/firebasejs/10.12.0/firebase-auth-compat.js"></script>
2417 2418 2419
2419<script> 2420 // ââ Firebase init ââ 2421 const firebaseConfig = { 2422 apiKey: 'AIzaSyCZgEKh22mtNd5KV3NaoDZ1Ln4HaLB6Tdg', 2423 authDomain: 'dtoymaker.firebaseapp.com', 2424 projectId: 'dtoymaker', 2425 storageBucket: 'dtoymaker.appspot.com', 2426 messagingSenderId: '659050400404', 2427 appId: '1:659050400404:web:86e7804c42d7ef87480281' 2428 }; 2429 firebase.initializeApp(firebaseConfig); 2430 const auth = firebase.auth(); 2431 2432 // ââ State ââ 2433 let currentUser = null; 2434 let selectedPlan = null; // 'lifetime' | 'yearly' | 'monthly' 2435 let authMode = 'signup'; // 'signup' | 'signin' | 'forgot' 2436 let userSubscription = null; // cached from Firestore via Cloud Function 2437 2438 const CHECKOUT_FUNCTION_URL = 'https://us-central1-dtoymaker.cloudfunctions.net/createStripeCheckoutSession'; 2439 const PORTAL_FUNCTION_URL = 'https://us-central1-dtoymaker.cloudfunctions.net/createStripePortalSession'; 2440 2441 // ââ Auth state listener ââ 2442 auth.onAuthStateChanged(async function (user) { 2443 currentUser = user; 2444 userSubscription = null; 2445 // Update nav 2446 if (user) { 2447 document.getElementById('nav-signed-out').style.display = 'none'; 2448 document.getElementById('nav-signed-in').style.display = 'flex'; 2449 document.getElementById('nav-user-email').textContent = user.email; 2450 await syncUserProfile(user); 2451 await fetchSubscriptionStatus(); 2452 } else { 2453 document.getElementById('nav-signed-out').style.display = 'block'; 2454 document.getElementById('nav-signed-in').style.display = 'none'; 2455 } 2456 if (document.getElementById('sub-modal').classList.contains('active')) { 2457 updateModalForAuthState(); 2458 } 2459 }); 2460 2461 // ââ Check URL params on load ââ 2462 window.addEventListener('DOMContentLoaded', function () { 2463 const params = new URLSearchParams(window.location.search); 2464 2465 if (params.get('success') === '1') { 2466 const plan = params.get('plan') || 'subscription'; 2467 const banner = document.getElementById('success-banner'); 2468 banner.style.display = 'block'; 2469 // Track purchase with value/currency for ROAS optimization 2470 var _purchaseValue = plan === 'lifetime' ? 71.99 : plan === 'monthly' ? 5.59 : 31.99; 2471 var _purchaseName = plan === 'lifetime' ? 'Lifetime License' : plan === 'monthly' ? 'Monthly Subscription' : 'Yearly Subscription'; 2472 if (typeof fbq === 'function') { 2473 fbq('track', 'Purchase', { 2474 value: _purchaseValue, 2475 currency: 'USD', 2476 content_name: _purchaseName, 2477 content_type: 'product' 2478 }); 2479 } 2480 if (typeof gtag === 'function') { 2481 gtag('event', 'purchase', { 2482 currency: 'USD', 2483 value: _purchaseValue, 2484 items: [{ item_name: _purchaseName, price: _purchaseValue, quantity: 1 }] 2485 }); 2486 } 2487 } 2488 2489 if (params.get('cancelled') === '1') { 2490 // Reopen modal if user had a plan selected (stored in sessionStorage) 2491 const savedPlan = sessionStorage.getItem('tcSelectedPlan'); 2492 if (savedPlan) { 2493 sessionStorage.removeItem('tcSelectedPlan'); 2494 openModal(savedPlan); 2495 showError('Checkout was cancelled. You can try again whenever you\'re ready.'); 2496 } 2497 } 2498 2499 // Reveal animation observer 2500 const observer = new IntersectionObserver(function (entries) { 2501 entries.forEach(function (e) { if (e.isIntersecting) e.target.classList.add('visible'); }); 2502 }, { threshold: 0.12 }); 2503 document.querySelectorAll('.reveal').forEach(function (el) { observer.observe(el); }); 2504 }); 2505 2506 // ââ Modal open/close ââ 2507 function openModal(plan, initialMode) { 2508 if (plan) selectedPlan = plan; 2509 if (initialMode) authMode = initialMode; 2510 2511 // Update plan chip 2512 if (selectedPlan) { 2513 const chipText = selectedPlan === 'lifetime' ? 'Lifetime â $71.99' 2514 : selectedPlan === 'monthly' ? 'Monthly â $5.59/mo' 2515 : 'Yearly â $31.99/yr'; 2516 document.getElementById('modal-plan-chip-text').textContent = chipText; 2517 document.getElementById('modal-plan-chip').style.display = 'inline-flex'; 2518 } else { 2519 document.getElementById('modal-plan-chip').style.display = 'none'; 2520 } 2521 2522 clearError(); 2523 document.getElementById('sub-modal').classList.add('active'); 2524 document.body.style.overflow = 'hidden'; 2525 updateModalForAuthState(); 2526 // Track plan view 2527 if (plan && typeof fbq === 'function') { 2528 fbq('track', 'ViewContent', { 2529 content_name: plan === 'lifetime' ? 'Lifetime License' : plan === 'monthly' ? 'Monthly Subscription' : 'Yearly Subscription', 2530 content_category: 'Subscription', 2531 value: plan === 'lifetime' ? 71.99 : plan === 'monthly' ? 5.59 : 31.99, 2532 currency: 'USD' 2533 }); 2534 } 2535 } 2536 2537 function closeModal() { 2538 document.getElementById('sub-modal').classList.remove('active'); 2539 document.body.style.overflow = ''; 2540 } 2541 2542 function handleModalOverlayClick(e) { 2543 if (e.target === document.getElementById('sub-modal')) closeModal(); 2544 } 2545 2546 function updateModalForAuthState() { 2547 if (currentUser) { 2548 const webSub = userSubscription && userSubscription.webSubscription; 2549 const lifetime = userSubscription && userSubscription.lifetime; 2550 const hasActiveSub = (webSub && (webSub.status === 'active' || webSub.status === 'past_due')) 2551 || (lifetime && lifetime.enabled); 2552 2553 if (hasActiveSub) { 2554 showSubscriptionState(); 2555 return; 2556 } 2557 } 2558 2559 if (currentUser && selectedPlan) { 2560 showState('checkout'); 2561 document.getElementById('checkout-user-email').textContent = currentUser.email; 2562 const isLifetime = selectedPlan === 'lifetime'; 2563 const isMonthly = selectedPlan === 'monthly'; 2564 document.getElementById('checkout-plan-name').textContent = isLifetime ? 'Lifetime License' : isMonthly ? 'Monthly Subscription' : 'Yearly Subscription'; 2565 document.getElementById('checkout-plan-price').textContent = isLifetime ? '$71.99' : isMonthly ? '$5.59/mo' : '$31.99'; 2566 document.getElementById('checkout-plan-note').textContent = isLifetime ? 'one-time · use forever' : isMonthly ? 'billed monthly · cancel any time' : 'billed annually · cancel any time'; 2567 } else if (currentUser && !selectedPlan) { 2568 // Signed in, no plan, no active sub â scroll to pricing 2569 closeModal(); 2570 document.querySelector('#pricing').scrollIntoView({ behavior: 'smooth' }); 2571 } else { 2572 showState('auth'); 2573 setAuthMode(authMode, false); 2574 } 2575 } 2576 2577 function showSubscriptionState() { 2578 showState('subscription'); 2579 document.getElementById('sub-state-user-email').textContent = currentUser.email; 2580 2581 const webSub = userSubscription && userSubscription.webSubscription; 2582 const lifetime = userSubscription && userSubscription.lifetime; 2583 const isWebSub = webSub && (webSub.status === 'active' || webSub.status === 'past_due'); 2584 2585 let planName, statusText, statusClass, renewsText = ''; 2586
2587 if (isWebSub) { 2588 planName = webSub.plan === 'lifetime' ? 'Lifetime License' : webSub.plan === 'monthly' ? 'Monthly Subscription' : 'Yearly Subscription'; 2589 if (webSub.status === 'active') { statusText = 'Active'; statusClass = 'active'; } 2590 else { statusText = 'Payment Past Due'; statusClass = 'past_due'; } 2591 2592 if ((webSub.plan === 'yearly' || webSub.plan === 'monthly') && webSub.currentPeriodEnd) { 2593 const d = new Date(webSub.currentPeriodEnd); 2594 renewsText = 'Renews ' + d.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }); 2595 } else if (webSub.plan === 'lifetime') { 2596 renewsText = 'Never expires'; 2597 } 2598 document.getElementById('sub-web-actions').style.display = 'block'; 2599 document.getElementById('sub-inapp-note').style.display = 'none'; 2600 } else { 2601 // In-app subscription 2602 planName = 'Lifetime License'; 2603 statusText = 'Active'; statusClass = 'active'; 2604 renewsText = 'Never expires'; 2605 document.getElementById('sub-web-actions').style.display = 'none'; 2606 document.getElementById('sub-inapp-note').style.display = 'block'; 2607 } 2608 2609 const badge = document.getElementById('sub-status-badge'); 2610 badge.className = 'sub-status-badge ' + statusClass; 2611 document.getElementById('sub-status-text').textContent = statusText; 2612 document.getElementById('sub-plan-name').textContent = planName; 2613 document.getElementById('sub-renews-text').textContent = renewsText; 2614 } 2615 2616 // ââ Modal states ââ 2617 function showState(state) { 2618 document.getElementById('modal-auth-state').style.display = state === 'auth' ? 'block' : 'none'; 2619 document.getElementById('modal-checkout-state').style.display = state === 'checkout' ? 'block' : 'none'; 2620 document.getElementById('modal-loading-state').style.display = state === 'loading' ? 'block' : 'none'; 2621 document.getElementById('modal-success-state').style.display = state === 'success' ? 'block' : 'none'; 2622 document.getElementById('modal-subscription-state').style.display = state === 'subscription' ? 'block' : 'none'; 2623 clearError(); 2624 } 2625 2626 // ââ Auth mode switching ââ 2627 function setAuthMode(mode, clearErr) { 2628 if (clearErr !== false) clearError(); 2629 authMode = mode; 2630 document.getElementById('auth-signup-form').style.display = mode === 'signup' ? 'block' : 'none'; 2631 document.getElementById('auth-signin-form').style.display = mode === 'signin' ? 'block' : 'none'; 2632 document.getElementById('auth-forgot-form').style.display = mode === 'forgot' ? 'block' : 'none'; 2633 document.getElementById('tab-signup').classList.toggle('active', mode === 'signup'); 2634 document.getElementById('tab-signin').classList.toggle('active', mode === 'signin'); 2635 2636 const heading = document.getElementById('auth-state-heading'); 2637 if (mode === 'signup') heading.textContent = 'Create your account'; 2638 else if (mode === 'signin') heading.textContent = 'Sign in to your account'; 2639 else heading.textContent = 'Reset your password'; 2640 } 2641 2642 // ââ Auth handlers ââ 2643 async function handleSignUp() { 2644 const email = document.getElementById('signup-email').value.trim(); 2645 const password = document.getElementById('signup-password').value; 2646 if (!email || !password) { showError('Please enter your email and password.'); return; } 2647 if (password.length < 6) { showError('Password must be at least 6 characters.'); return; } 2648 setSubmitting(true); 2649 try { 2650 await auth.createUserWithEmailAndPassword(email, password); 2651 // Auth state listener calls syncUserProfile + updateModalForAuthState() 2652 if (typeof fbq === 'function') { fbq('track', 'CompleteRegistration'); } 2653 } catch (err) { 2654 if (err.code === 'auth/email-already-in-use') { 2655 // Try signing in with same credentials 2656 try { 2657 await auth.signInWithEmailAndPassword(email, password); 2658 } catch (_) { 2659 showError('An account with this email already exists. Please sign in.'); 2660 setAuthMode('signin'); 2661 } 2662 } else { 2663 showError(getAuthErrorMessage(err)); 2664 } 2665 } finally { 2666 setSubmitting(false); 2667 } 2668 } 2669 2670 async function handleSignIn() { 2671 const email = document.getElementById('signin-email').value.trim(); 2672 const password = document.getElementById('signin-password').value; 2673 if (!email || !password) { showError('Please enter your email and password.'); return; } 2674 setSubmitting(true); 2675 try { 2676 await auth.signInWithEmailAndPassword(email, password); 2677 if (typeof fbq === 'function') { fbq('track', 'Lead'); } 2678 } catch (err) { 2679 showError(getAuthErrorMessage(err)); 2680 } finally { 2681 setSubmitting(false); 2682 } 2683 } 2684 2685 async function handleForgotPassword() { 2686 const email = document.getElementById('forgot-email').value.trim(); 2687 if (!email) { showError('Please enter your email address.'); return; } 2688 setSubmitting(true); 2689 try { 2690 await auth.sendPasswordResetEmail(email); 2691 showError('Password reset email sent! Check your inbox.', 'success'); 2692 } catch (err) { 2693 showError(getAuthErrorMessage(err)); 2694 } finally { 2695 setSubmitting(false); 2696 } 2697 } 2698 2699 function handleSignOut() { 2700 auth.signOut(); 2701 } 2702 2703 // ââ Fetch subscription status directly from Firestore ââ 2704 // ââ Sync user profile to Firestore (matches app's syncUserAccountEntitlements) ââ 2705 // Uses commit API with updateMask so webSubscription/lifetime fields are never overwritten. 2706 async function syncUserProfile(user) { 2707 try { 2708 const token = await user.getIdToken(); 2709 const nowIso = new Date().toISOString(); 2710 const docPath = 'projects/dtoymaker/databases/toycadusersdb/documents/users/' + user.uid; 2711 await fetch('https://firestore.googleapis.com/v1/projects/dtoymaker/databases/toycadusersdb/documents:commit', { 2712 method: 'POST', 2713 headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, 2714 body: JSON.stringify({ 2715 writes: [ 2716 { 2717 update: { 2718 name: docPath, 2719 fields: { 2720 updatedAtIso: { stringValue: nowIso }, 2721 profile: { 2722 mapValue: { 2723 fields: { 2724 uid: { stringValue: user.uid }, 2725 email: { stringValue: user.email || '' }, 2726 lastSeenAtIso: { stringValue: nowIso } 2727 } 2728 } 2729 }, 2730 identity: { 2731 mapValue: { 2732 fields: { 2733 revenueCatLinked: { booleanValue: false }, 2734 lastSyncAtIso: { stringValue: nowIso } 2735 } 2736 } 2737 } 2738 } 2739 }, 2740 updateMask: { 2741 fieldPaths: [ 2742 'updatedAtIso', 2743 'profile.uid', 'profile.email', 'profile.lastSeenAtIso', 2744 'identity.revenueCatLinked', 'identity.lastSyncAtIso' 2745 ] 2746 } 2747 }, 2748 { 2749 transform: { 2750 document: docPath, 2751 fieldTransforms: [ 2752 { fieldPath: 'updatedAt', setToServerValue: 'REQUEST_TIME' }, 2753 { fieldPath: 'profile.lastSeenAt', setToServerValue: 'REQUEST_TIME' }, 2754 { fieldPath: 'identity.lastSyncAt', setToServerValue: 'REQUEST_TIME' } 2755 ] 2756 } 2757 } 2758 ] 2759 }) 2760 }); 2761 } catch (err) { 2762 console.warn('Could not sync user profile:', err); 2763 } 2764 } 2765 2766 // ââ Fetch subscription status via Firestore REST API (named database) ââ
2767 async function fetchSubscriptionStatus() { 2768 if (!currentUser) return; 2769 try { 2770 const token = await currentUser.getIdToken(); 2771 const url = 'https://firestore.googleapis.com/v1/projects/dtoymaker/databases/toycadusersdb/documents/users/' + currentUser.uid; 2772 const response = await fetch(url, { 2773 headers: { 'Authorization': 'Bearer ' + token } 2774 }); 2775 if (response.status === 404) { 2776 // New user â profile will be created by syncUserProfile (called before this) 2777 userSubscription = null; 2778 return; 2779 } 2780 if (!response.ok) throw new Error('Firestore REST error ' + response.status); 2781 const doc = await response.json(); 2782 const fields = doc.fields || {}; 2783 2784 function getField(obj, key) { 2785 return obj && obj[key] ? (obj[key].stringValue || obj[key].booleanValue || null) : null; 2786 } 2787 function getMapField(fields, key) { 2788 return fields[key] && fields[key].mapValue ? fields[key].mapValue.fields || {} : null; 2789 } 2790 2791 const webSubFields = getMapField(fields, 'webSubscription'); 2792 const lifetimeFields = getMapField(fields, 'lifetime'); 2793 2794 userSubscription = { 2795 webSubscription: webSubFields ? { 2796 plan: getField(webSubFields, 'plan'), 2797 status: getField(webSubFields, 'status'), 2798 currentPeriodEnd: getField(webSubFields, 'currentPeriodEnd') 2799 } : null, 2800 lifetime: lifetimeFields ? { 2801 enabled: getField(lifetimeFields, 'enabled') === true || lifetimeFields.enabled && lifetimeFields.enabled.booleanValue === true, 2802 source: getField(lifetimeFields, 'source') 2803 } : null 2804 }; 2805 } catch (err) { 2806 console.warn('Could not fetch subscription status:', err); 2807 userSubscription = null; 2808 } 2809 } 2810 2811 // ââ Open Stripe Customer Portal ââ 2812 async function handleManageSubscription() { 2813 const btn = document.getElementById('manage-sub-btn'); 2814 if (btn) { btn.disabled = true; btn.textContent = 'Openingâ¦'; } 2815 try { 2816 const token = await currentUser.getIdToken(); 2817 const response = await fetch(PORTAL_FUNCTION_URL, { 2818 method: 'POST', 2819 headers: { 2820 'Content-Type': 'application/json', 2821 'Authorization': 'Bearer ' + token 2822 } 2823 }); 2824 if (!response.ok) { 2825 const errData = await response.json().catch(function () { return {}; }); 2826 throw new Error(errData.error || 'Could not open billing portal.'); 2827 } 2828 const data = await response.json(); 2829 if (data.url) { 2830 window.open(data.url, '_blank', 'noopener,noreferrer'); 2831 } 2832 } catch (err) { 2833 showError(err.message || 'Could not open billing portal. Please try again.'); 2834 } finally { 2835 if (btn) { btn.disabled = false; btn.textContent = 'Manage Subscription â'; } 2836 } 2837 } 2838 2839 // ââ Stripe Checkout ââ 2840 async function handleCheckout() { 2841 if (!currentUser || !selectedPlan) return; 2842 2843 showState('loading'); 2844 sessionStorage.setItem('tcSelectedPlan', selectedPlan); 2845 2846 // Track checkout initiation 2847 var _checkoutValue = selectedPlan === 'lifetime' ? 71.99 : selectedPlan === 'monthly' ? 5.59 : 31.99; 2848 var _checkoutName = selectedPlan === 'lifetime' ? 'Lifetime License' : selectedPlan === 'monthly' ? 'Monthly Subscription' : 'Yearly Subscription'; 2849 if (typeof fbq === 'function') { 2850 fbq('track', 'InitiateCheckout', { 2851 value: _checkoutValue, 2852 currency: 'USD', 2853 num_items: 1, 2854 content_name: _checkoutName 2855 }); 2856 } 2857 if (typeof gtag === 'function') { 2858 gtag('event', 'begin_checkout', { 2859 currency: 'USD', 2860 value: _checkoutValue, 2861 items: [{ item_name: _checkoutName, price: _checkoutValue, quantity: 1 }] 2862 }); 2863 } 2864 2865 const priceId = selectedPlan === 'lifetime' 2866 ? 'price_1TVx6uAhAiOWG0pLmEGow064' 2867 : selectedPlan === 'monthly' 2868 ? 'price_1Tcq7uAhAiOWG0pLn6hPHUM3' 2869 : 'price_1TVx8IAhAiOWG0pLCx4lEkAt'; 2870 2871 try { 2872 const token = await currentUser.getIdToken(); 2873 // Include Rewardful referral ID if present (for affiliate tracking) 2874 var _referral = (window.Rewardful && window.Rewardful.referral) || null; 2875 const response = await fetch(CHECKOUT_FUNCTION_URL, { 2876 method: 'POST', 2877 headers: { 2878 'Content-Type': 'application/json', 2879 'Authorization': 'Bearer ' + token 2880 }, 2881 body: JSON.stringify({ 2882 priceId: priceId, 2883 plan: selectedPlan, 2884 uid: currentUser.uid, 2885 email: currentUser.email, 2886 referral: _referral 2887 }) 2888 }); 2889 2890 if (!response.ok) { 2891 const errData = await response.json().catch(function () { return {}; }); 2892 throw new Error(errData.error || 'Server error. Please try again.'); 2893 } 2894 2895 const data = await response.json(); 2896 if (data.url) { 2897 window.location.href = data.url; 2898 } else { 2899 throw new Error('Invalid response from server.'); 2900 } 2901 } catch (err) { 2902 console.error('Checkout error:', err); 2903 showState('checkout'); 2904 showError('Could not start checkout: ' + (err.message || 'Please try again.')); 2905 } 2906 } 2907 2908 // ââ Helpers ââ 2909 function showError(msg, type) { 2910 const el = document.getElementById('modal-error'); 2911 el.textContent = msg; 2912 el.style.display = 'block'; 2913 if (type === 'success') { 2914 el.classList.add('success-msg'); 2915 } else { 2916 el.classList.remove('success-msg'); 2917 } 2918 } 2919 2920 function clearError() { 2921 const el = document.getElementById('modal-error'); 2922 el.style.display = 'none'; 2923 el.textContent = ''; 2924 } 2925 2926 function setSubmitting(loading) { 2927 ['signup-btn', 'signin-btn', 'forgot-btn'].forEach(function (id) { 2928 const btn = document.getElementById(id); 2929 if (btn) btn.disabled = loading; 2930 }); 2931 } 2932 2933 function getAuthErrorMessage(err) { 2934 switch (err.code) { 2935 case 'auth/invalid-email': return 'Invalid email address.'; 2936 case 'auth/user-not-found': return 'No account found with this email. Please sign up.'; 2937 case 'auth/wrong-password': return 'Incorrect password. Try again or reset your password.'; 2938 case 'auth/too-many-requests': return 'Too many attempts. Please wait a few minutes and try again.'; 2939 case 'auth/weak-password': return 'Password must be at least 6 characters.'; 2940 case 'auth/network-request-failed': return 'Network error. Please check your connection.'; 2941 default: return err.message || 'An error occurred. Please try again.'; 2942 } 2943 } 2944 2945 function toggleFaq(btn) { 2946 const item = btn.parentElement; 2947 const isOpen = item.classList.contains('open'); 2948 document.querySelectorAll('.faq-item').forEach(function (i) { i.classList.remove('open'); }); 2949 if (!isOpen) item.classList.add('open'); 2950 } 2951 </script>
2951 2952 2953
2953<script> 2954 (function () { 2955 const slider = document.getElementById('heroSlider'); 2956 if (!slider) return; 2957 const slidesEl = slider.querySelector('.slides'); 2958 const prevBtn = document.getElementById('sliderPrev'); 2959 const nextBtn = document.getElementById('sliderNext'); 2960 const introVideo = document.getElementById('heroIntroVideo'); 2961 const slideCount = slidesEl.children.length; 2962 let index = 0; 2963 let timer = null; 2964 2965 function syncIntroVideo() { 2966 if (!introVideo) return; 2967 if (index === 1) { 2968 introVideo.controls = true; 2969 introVideo.muted = true; 2970 const playPromise = introVideo.play(); 2971 if (playPromise && typeof playPromise.catch === 'function') { 2972 playPromise.catch(function () { }); 2973 } 2974 } else { 2975 introVideo.controls = false; 2976 introVideo.pause(); 2977 } 2978 } 2979 2980 function goTo(i) { 2981 index = (i + slideCount) % slideCount; 2982 slidesEl.style.transform = 'translateX(' + (-index * 100) + '%)'; 2983 syncIntroVideo(); 2984 if (timer) resetTimer(); 2985 } 2986 2987 function next() { goTo(index + 1); } 2988 function prev() { goTo(index - 1); } 2989 2990 prevBtn.addEventListener('click', function () { prev(); resetTimer(); }); 2991 nextBtn.addEventListener('click', function () { next(); resetTimer(); }); 2992 2993 function getCurrentIntervalMs() { return index === 1 ? 15000 : 3000; } 2994 2995 function startTimer() { 2996 if (shouldPauseCarousel()) return; 2997 timer = setInterval(next, getCurrentIntervalMs()); 2998 } 2999 3000 function pauseTimer() { 3001 if (timer) { clearInterval(timer); timer = null; } 3002 } 3003 3004 function resetTimer() { pauseTimer(); startTimer(); } 3005 3006 function isIntroVideoUnmutedOnActiveSlide() { 3007 return !!introVideo && index === 1 && !introVideo.muted; 3008 } 3009 3010 function shouldPauseCarousel() { 3011 return isIntroVideoFullscreen() || isIntroVideoUnmutedOnActiveSlide(); 3012 } 3013 3014 function isIntroVideoFullscreen() { 3015 return document.fullscreenElement === introVideo 3016 || document.webkitFullscreenElement === introVideo 3017 || document.msFullscreenElement === introVideo; 3018 } 3019 3020 function handleFullscreenChange() { 3021 if (!introVideo) return; 3022 if (shouldPauseCarousel()) { pauseTimer(); } 3023 else if (!timer) { startTimer(); } 3024 } 3025 3026 document.addEventListener('fullscreenchange', handleFullscreenChange); 3027 document.addEventListener('webkitfullscreenchange', handleFullscreenChange); 3028 document.addEventListener('MSFullscreenChange', handleFullscreenChange); 3029 3030 if (introVideo) { 3031 introVideo.addEventListener('volumechange', handleFullscreenChange); 3032 } 3033 3034 (function addSwipeHandlers() { 3035 let startX = 0; 3036 let deltaX = 0; 3037 let pointerDown = false; 3038 const threshold = 50; 3039 3040 function handleSwipeEnd() { 3041 if (Math.abs(deltaX) > threshold) { 3042 if (deltaX < 0) next(); else prev(); 3043 resetTimer(); 3044 } 3045 deltaX = 0; startX = 0; pointerDown = false; 3046 } 3047 3048 function onTouchStart(e) { if (!e.touches || !e.touches.length) return; startX = e.touches[0].clientX; deltaX = 0; } 3049 function onTouchMove(e) { if (!e.touches || !e.touches.length) return; deltaX = e.touches[0].clientX - startX; } 3050 function onTouchEnd() { handleSwipeEnd(); } 3051 3052 function onPointerDown(e) { pointerDown = true; startX = e.clientX; deltaX = 0; if (slider.setPointerCapture) try { slider.setPointerCapture(e.pointerId); } catch (_) { } } 3053 function onPointerMove(e) { if (!pointerDown) return; deltaX = e.clientX - startX; } 3054 function onPointerUp(e) { if (pointerDown) handleSwipeEnd(); try { slider.releasePointerCapture && slider.releasePointerCapture(e.pointerId); } catch (_) { } } 3055 3056 slidesEl.addEventListener('touchstart', onTouchStart, { passive: true }); 3057 slidesEl.addEventListener('touchmove', onTouchMove, { passive: true }); 3058 slidesEl.addEventListener('touchend', onTouchEnd); 3059 slidesEl.addEventListener('pointerdown', onPointerDown); 3060 slidesEl.addEventListener('pointermove', onPointerMove); 3061 slidesEl.addEventListener('pointerup', onPointerUp); 3062 slidesEl.addEventListener('pointercancel', onPointerUp); 3063 })(); 3064 3065 goTo(0); 3066 startTimer(); 3067 })(); 3068 </script>
3068 3069</body> 3070 3071</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.