mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
* feat(catalog): play the real composition on catalog pages Catalog previews were uploaded MP4s, published by hand to the CDN. Each page now embeds the composition itself, running in <hyperframes-player>, so a preview is the block rather than a recording of it. The composition is delivered as JSON under docs/public/catalog, and the player is mounted inside an iframe. Both are forced: the docs host publishes only JSON and images out of docs/public, and its MDX renderer strips unknown custom elements, so a player written into the page never reaches the DOM and an .html payload 404s in production. Assets are inlined as data URIs, which sidesteps the file-type restriction for fonts, scripts and models alike. 164 of 168 items build a payload; the rest keep their MP4 and say so. * chore: rebuild docs preview * chore: route isolation probe * chore: drop the route isolation probe page * chore: probe which asset types the docs host publishes * refactor(catalog): host preview assets instead of inlining them Fonts were being base64'd into every payload that used them, so a handful of files cost tens of megabytes in the repository to say the same thing over and over. Assets are now written once, content-addressed, under docs/public/catalog/assets and linked. Which types can be hosted was settled by fetching one file of each from a deployed preview: woff2, wav, mp4, svg and the image formats are published, glb is not. Types the host drops still travel inside the payload, because a link that 404s is worse than a larger payload. Also fixes the four type errors the scripts typecheck caught, all of them unchecked index access on a split() result. * refactor(catalog): share preview assets instead of repeating them * style: format the asset module
1 line
28 KiB
JSON
1 line
28 KiB
JSON
{"html":"<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=1920, height=1080\" />\n <script src=\"https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js\"></script>\n <style>\n * {\n box-sizing: border-box;\n }\n\n html,\n body {\n margin: 0;\n width: 1920px;\n height: 1080px;\n overflow: hidden;\n background: #ffffff;\n font-family:\n -apple-system, BlinkMacSystemFont, \"SF Pro Display\", Inter, \"Helvetica Neue\", Arial,\n sans-serif;\n }\n\n #root {\n position: relative;\n width: 1920px;\n height: 1080px;\n overflow: hidden;\n color: #111111;\n background:\n radial-gradient(circle at 20% 16%, rgba(0, 122, 255, 0.13), transparent 24%),\n radial-gradient(circle at 82% 22%, rgba(52, 199, 89, 0.14), transparent 22%),\n linear-gradient(180deg, #ffffff 0%, #f5f5f7 100%);\n }\n\n .stage {\n position: absolute;\n inset: 0;\n display: grid;\n place-items: center;\n padding: 70px 120px;\n }\n\n .headline {\n position: absolute;\n left: 118px;\n top: 104px;\n width: 580px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n z-index: 2;\n pointer-events: none;\n }\n\n .headline span {\n display: block;\n font-size: 92px;\n line-height: 0.92;\n font-weight: 900;\n letter-spacing: 0;\n }\n\n .headline .blue {\n color: #007aff;\n }\n\n .headline .sub {\n margin-top: 20px;\n width: 410px;\n color: #6e6e73;\n font-size: 27px;\n line-height: 1.18;\n font-weight: 700;\n }\n\n .ambient-word {\n position: absolute;\n right: 78px;\n bottom: 64px;\n color: rgba(17, 17, 17, 0.05);\n font-size: 208px;\n line-height: 0.84;\n font-weight: 900;\n letter-spacing: 0;\n pointer-events: none;\n }\n\n .ambient-line {\n position: absolute;\n height: 2px;\n width: 360px;\n border-radius: 999px;\n background: rgba(0, 122, 255, 0.2);\n transform-origin: center;\n pointer-events: none;\n }\n\n .line-a {\n left: 198px;\n bottom: 230px;\n }\n\n .line-b {\n right: 208px;\n top: 196px;\n width: 248px;\n background: rgba(52, 199, 89, 0.2);\n }\n\n .snap-shadow {\n position: absolute;\n left: 50%;\n bottom: 132px;\n width: 560px;\n height: 52px;\n border-radius: 50%;\n transform: translateX(-50%);\n background: rgba(17, 17, 17, 0.14);\n filter: blur(24px);\n opacity: 0;\n }\n\n .phone {\n position: relative;\n width: 462px;\n height: 952px;\n border-radius: 72px;\n padding: 16px;\n background: linear-gradient(145deg, #1f2025 0%, #07070a 100%);\n box-shadow:\n 0 46px 120px rgba(17, 17, 17, 0.22),\n inset 0 0 0 2px rgba(255, 255, 255, 0.12);\n z-index: 4;\n will-change: transform;\n }\n\n .phone::before {\n content: \"\";\n position: absolute;\n inset: 8px;\n border-radius: 64px;\n border: 2px solid rgba(255, 255, 255, 0.07);\n pointer-events: none;\n }\n\n .screen {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n border-radius: 56px;\n background: #f5f5f7;\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.04);\n }\n\n .island {\n position: absolute;\n left: 50%;\n top: 15px;\n width: 138px;\n height: 38px;\n border-radius: 999px;\n transform: translateX(-50%);\n background: #07070a;\n z-index: 30;\n }\n\n .store-view,\n .detail-view,\n .white-wipe {\n position: absolute;\n inset: 0;\n }\n\n .store-view {\n padding: 70px 24px 26px;\n z-index: 2;\n }\n\n .store-top {\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n margin-bottom: 16px;\n }\n\n .store-title {\n font-size: 38px;\n line-height: 1;\n font-weight: 900;\n letter-spacing: 0;\n }\n\n .avatar {\n width: 38px;\n height: 38px;\n border-radius: 50%;\n background:\n radial-gradient(circle at 62% 38%, rgba(255, 255, 255, 0.9) 0 10%, transparent 11%),\n linear-gradient(135deg, #007aff, #34c759);\n box-shadow: 0 8px 18px rgba(0, 122, 255, 0.25);\n }\n\n .search-pill {\n display: flex;\n align-items: center;\n gap: 9px;\n height: 45px;\n padding: 0 16px;\n border-radius: 15px;\n background: #e9e9ed;\n color: #6e6e73;\n font-size: 18px;\n font-weight: 700;\n margin-bottom: 16px;\n }\n\n .search-icon {\n width: 16px;\n height: 16px;\n border: 3px solid #8e8e93;\n border-radius: 50%;\n position: relative;\n }\n\n .search-icon::after {\n content: \"\";\n position: absolute;\n right: -8px;\n bottom: -5px;\n width: 9px;\n height: 3px;\n border-radius: 999px;\n background: #8e8e93;\n transform: rotate(45deg);\n }\n\n .screen-window {\n height: 720px;\n overflow: hidden;\n border-radius: 26px;\n }\n\n .scroll-content {\n display: flex;\n flex-direction: column;\n gap: 12px;\n will-change: transform;\n }\n\n .store-row {\n display: grid;\n grid-template-columns: 72px 1fr 78px;\n align-items: center;\n gap: 13px;\n min-height: 104px;\n padding: 13px 12px;\n border-radius: 23px;\n background: rgba(255, 255, 255, 0.88);\n box-shadow: 0 2px 0 rgba(0, 0, 0, 0.035);\n }\n\n .app-icon {\n width: 72px;\n height: 72px;\n border-radius: 18px;\n position: relative;\n overflow: hidden;\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.38);\n }\n\n .icon-cloud {\n background: linear-gradient(135deg, #70d7ff, #007aff);\n }\n\n .icon-photo {\n background:\n radial-gradient(circle at 32% 35%, #ffffff 0 10%, transparent 11%),\n linear-gradient(135deg, #ff9f0a, #ff375f);\n }\n\n .icon-music {\n background: linear-gradient(135deg, #ff2d55, #5856d6);\n }\n\n .icon-bank {\n background: linear-gradient(135deg, #111111, #8e8e93);\n }\n\n .icon-map {\n background: linear-gradient(135deg, #34c759, #ffd60a);\n }\n\n .icon-vpn {\n background:\n radial-gradient(circle at 72% 16%, rgba(255, 255, 255, 0.62) 0 12%, transparent 13%),\n linear-gradient(145deg, #101828 0%, #007aff 64%, #34c759 100%);\n }\n\n .icon-vpn::before {\n content: \"\";\n position: absolute;\n left: 21px;\n top: 16px;\n width: 30px;\n height: 38px;\n border-radius: 16px 16px 18px 18px;\n background: rgba(255, 255, 255, 0.94);\n clip-path: polygon(50% 0, 88% 13%, 88% 46%, 50% 100%, 12% 46%, 12% 13%);\n }\n\n .icon-vpn::after {\n content: \"\";\n position: absolute;\n left: 31px;\n top: 29px;\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #007aff;\n }\n\n .row-copy {\n min-width: 0;\n }\n\n .row-title {\n color: #111111;\n font-size: 18px;\n line-height: 1.24;\n font-weight: 800;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .row-sub {\n margin-top: 4px;\n color: #6e6e73;\n font-size: 12px;\n line-height: 1.28;\n font-weight: 600;\n }\n\n .stars {\n margin-top: 6px;\n color: #8e8e93;\n font-size: 12px;\n line-height: 1.25;\n font-weight: 800;\n }\n\n .get-button,\n .install-button,\n .open-button {\n display: grid;\n place-items: center;\n height: 34px;\n border-radius: 999px;\n font-size: 14px;\n line-height: 1;\n font-weight: 900;\n letter-spacing: 0;\n }\n\n .get-button,\n .open-button {\n width: 72px;\n color: #007aff;\n background: #ececf1;\n }\n\n #vpn-row {\n position: relative;\n transform-origin: center;\n outline: 0 solid rgba(0, 122, 255, 0);\n }\n\n .tap-dot {\n position: absolute;\n right: 47px;\n top: 52px;\n width: 42px;\n height: 42px;\n border-radius: 50%;\n border: 4px solid rgba(0, 122, 255, 0.8);\n background: rgba(0, 122, 255, 0.18);\n opacity: 0;\n z-index: 9;\n }\n\n .detail-view {\n padding: 78px 24px 26px;\n background: #f5f5f7;\n opacity: 0;\n z-index: 8;\n }\n\n .detail-card {\n height: 100%;\n border-radius: 36px;\n padding: 28px 22px 24px;\n background: #ffffff;\n box-shadow: 0 26px 70px rgba(17, 17, 17, 0.12);\n overflow: hidden;\n }\n\n .detail-hero {\n display: grid;\n grid-template-columns: 108px 1fr;\n gap: 16px;\n align-items: center;\n }\n\n .detail-icon {\n width: 108px;\n height: 108px;\n border-radius: 26px;\n }\n\n .detail-title {\n font-size: 31px;\n line-height: 1;\n font-weight: 900;\n }\n\n .detail-sub {\n margin-top: 7px;\n color: #6e6e73;\n font-size: 14px;\n line-height: 1.2;\n font-weight: 700;\n }\n\n .install-button {\n position: relative;\n width: 94px;\n margin-top: 15px;\n color: #ffffff;\n background: #007aff;\n overflow: hidden;\n }\n\n .install-label,\n .open-label,\n .ring {\n position: absolute;\n }\n\n .install-label,\n .open-label {\n inset: 0;\n display: grid;\n place-items: center;\n }\n\n .open-label,\n .ring {\n opacity: 0;\n }\n\n .ring {\n inset: 4px auto auto 34px;\n width: 26px;\n height: 26px;\n transform: rotate(-90deg);\n }\n\n .ring .track {\n fill: none;\n stroke: rgba(255, 255, 255, 0.36);\n stroke-width: 4;\n }\n\n .ring .progress {\n fill: none;\n stroke: #ffffff;\n stroke-width: 4;\n stroke-linecap: round;\n stroke-dasharray: 69;\n stroke-dashoffset: 69;\n }\n\n .stats {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 8px;\n margin: 26px 0;\n }\n\n .stat {\n min-height: 72px;\n border-radius: 22px;\n padding: 12px 8px;\n background: #f5f5f7;\n text-align: center;\n }\n\n .stat-label {\n color: #8e8e93;\n font-size: 11px;\n font-weight: 800;\n }\n\n .stat-value {\n margin-top: 7px;\n color: #111111;\n font-size: 22px;\n line-height: 1;\n font-weight: 900;\n }\n\n .progress-panel {\n position: relative;\n display: grid;\n place-items: center;\n height: 260px;\n border-radius: 34px;\n background:\n radial-gradient(circle at 50% 40%, rgba(0, 122, 255, 0.12), transparent 48%), #f5f5f7;\n overflow: hidden;\n }\n\n .progress-halo {\n position: absolute;\n width: 196px;\n height: 196px;\n border-radius: 50%;\n border: 24px solid rgba(0, 122, 255, 0.1);\n }\n\n .progress-big {\n width: 190px;\n height: 190px;\n transform: rotate(-90deg);\n }\n\n .big-track,\n .big-progress {\n fill: none;\n stroke-width: 18;\n }\n\n .big-track {\n stroke: rgba(0, 122, 255, 0.12);\n }\n\n .big-progress {\n stroke: #007aff;\n stroke-linecap: round;\n stroke-dasharray: 440;\n stroke-dashoffset: 440;\n }\n\n .percent {\n position: absolute;\n color: #111111;\n font-size: 48px;\n line-height: 1;\n font-weight: 900;\n }\n\n .shield-status {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n margin-top: 18px;\n color: #34c759;\n font-size: 20px;\n font-weight: 900;\n opacity: 0;\n }\n\n .check {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n display: grid;\n place-items: center;\n color: #ffffff;\n background: #34c759;\n }\n\n .speed-lines {\n position: absolute;\n inset: 0;\n opacity: 0;\n z-index: 20;\n pointer-events: none;\n }\n\n .speed-lines i {\n position: absolute;\n left: 50%;\n width: 4px;\n height: 210px;\n border-radius: 999px;\n background: rgba(17, 17, 17, 0.18);\n transform: translateX(-50%);\n }\n\n .speed-lines i:nth-child(1) {\n top: 70px;\n margin-left: -310px;\n }\n\n .speed-lines i:nth-child(2) {\n top: 130px;\n margin-left: 304px;\n height: 240px;\n }\n\n .speed-lines i:nth-child(3) {\n top: 260px;\n margin-left: -232px;\n height: 300px;\n }\n\n .speed-lines i:nth-child(4) {\n top: 220px;\n margin-left: 228px;\n height: 320px;\n }\n\n .white-wipe {\n background: #ffffff;\n opacity: 0;\n z-index: 40;\n pointer-events: none;\n }\n </style>\n </head>\n <body>\n <div\n id=\"root\"\n data-composition-id=\"vpn-youtube-spot\"\n data-start=\"0\"\n data-duration=\"7\"\n data-width=\"1920\"\n data-height=\"1080\"\n >\n <audio\n id=\"sfx\"\n data-start=\"0\"\n data-duration=\"7\"\n data-track-index=\"1\"\n src=\"/public/catalog/assets/f1f4fe4111dbd265.wav\"\n data-volume=\"0.95\"\n ></audio>\n\n <div class=\"stage\">\n <div class=\"headline\" id=\"stageHeadline\">\n <span>PRIVATE</span>\n <span class=\"blue\">SIMPLY</span>\n <div class=\"sub\">A calmer way to browse.</div>\n </div>\n <div class=\"ambient-word\">SAFE</div>\n <div class=\"ambient-line line-a\"></div>\n <div class=\"ambient-line line-b\"></div>\n <div class=\"snap-shadow\"></div>\n\n <div class=\"phone\">\n <div class=\"screen\">\n <div class=\"island\"></div>\n\n <section class=\"store-view\">\n <div class=\"store-top\">\n <div class=\"store-title\">Search</div>\n <div class=\"avatar\"></div>\n </div>\n <div class=\"search-pill\">\n <div class=\"search-icon\"></div>\n <div>privacy apps</div>\n </div>\n <div class=\"screen-window\" data-layout-allow-overflow>\n <div class=\"scroll-content\">\n <div class=\"store-row\">\n <div class=\"app-icon icon-cloud\"></div>\n <div class=\"row-copy\">\n <div class=\"row-title\">CloudPocket</div>\n <div class=\"row-sub\">Files and backups</div>\n <div class=\"stars\">4.6 ★★★★★</div>\n </div>\n <div class=\"get-button\">GET</div>\n </div>\n <div class=\"store-row\">\n <div class=\"app-icon icon-photo\"></div>\n <div class=\"row-copy\">\n <div class=\"row-title\">LensLab</div>\n <div class=\"row-sub\">Photo editor</div>\n <div class=\"stars\">4.8 ★★★★★</div>\n </div>\n <div class=\"get-button\">GET</div>\n </div>\n <div class=\"store-row\">\n <div class=\"app-icon icon-music\"></div>\n <div class=\"row-copy\">\n <div class=\"row-title\">BeatShelf</div>\n <div class=\"row-sub\">Music discovery</div>\n <div class=\"stars\">4.4 ★★★★☆</div>\n </div>\n <div class=\"get-button\">GET</div>\n </div>\n <div class=\"store-row\">\n <div class=\"app-icon icon-bank\"></div>\n <div class=\"row-copy\">\n <div class=\"row-title\">VaultPay</div>\n <div class=\"row-sub\">Budget tracker</div>\n <div class=\"stars\">4.7 ★★★★★</div>\n </div>\n <div class=\"get-button\">GET</div>\n </div>\n <div class=\"store-row\">\n <div class=\"app-icon icon-map\"></div>\n <div class=\"row-copy\">\n <div class=\"row-title\">TrailPing</div>\n <div class=\"row-sub\">Maps for trips</div>\n <div class=\"stars\">4.5 ★★★★☆</div>\n </div>\n <div class=\"get-button\">GET</div>\n </div>\n <div class=\"store-row\" id=\"vpn-row\">\n <div class=\"app-icon icon-vpn\"></div>\n <div class=\"row-copy\">\n <div class=\"row-title\">VPN</div>\n <div class=\"row-sub\">Simple private browsing</div>\n <div class=\"stars\">4.9 ★★★★★</div>\n </div>\n <div class=\"get-button\">GET</div>\n <div class=\"tap-dot\"></div>\n </div>\n <div class=\"store-row\">\n <div class=\"app-icon icon-cloud\"></div>\n <div class=\"row-copy\">\n <div class=\"row-title\">QuickTunnel</div>\n <div class=\"row-sub\">Network tools</div>\n <div class=\"stars\">4.2 ★★★★☆</div>\n </div>\n <div class=\"get-button\">GET</div>\n </div>\n <div class=\"store-row\">\n <div class=\"app-icon icon-photo\"></div>\n <div class=\"row-copy\">\n <div class=\"row-title\">PrivateFrame</div>\n <div class=\"row-sub\">Photo lockers</div>\n <div class=\"stars\">4.3 ★★★★☆</div>\n </div>\n <div class=\"get-button\">GET</div>\n </div>\n </div>\n </div>\n </section>\n\n <section class=\"detail-view\">\n <div class=\"detail-card\">\n <div class=\"detail-hero\">\n <div class=\"app-icon icon-vpn detail-icon\"></div>\n <div>\n <div class=\"detail-title\">VPN</div>\n <div class=\"detail-sub\">Friendly privacy for everyday browsing.</div>\n <div class=\"install-button\">\n <span class=\"install-label\">GET</span>\n <svg class=\"ring\" viewBox=\"0 0 28 28\" aria-hidden=\"true\">\n <circle class=\"track\" cx=\"14\" cy=\"14\" r=\"11\"></circle>\n <circle class=\"progress\" cx=\"14\" cy=\"14\" r=\"11\"></circle>\n </svg>\n <span class=\"open-label\"></span>\n </div>\n </div>\n </div>\n\n <div class=\"stats\">\n <div class=\"stat\">\n <div class=\"stat-label\">RATING</div>\n <div class=\"stat-value\">4.9</div>\n </div>\n <div class=\"stat\">\n <div class=\"stat-label\">SPEED</div>\n <div class=\"stat-value\">Fast</div>\n </div>\n <div class=\"stat\">\n <div class=\"stat-label\">AGE</div>\n <div class=\"stat-value\">4+</div>\n </div>\n </div>\n\n <div class=\"progress-panel\">\n <div class=\"progress-halo\"></div>\n <svg class=\"progress-big\" viewBox=\"0 0 160 160\" aria-hidden=\"true\">\n <circle class=\"big-track\" cx=\"80\" cy=\"80\" r=\"70\"></circle>\n <circle id=\"progressArc\" class=\"big-progress\" cx=\"80\" cy=\"80\" r=\"70\"></circle>\n </svg>\n <div class=\"percent\" id=\"progressText\">0%</div>\n </div>\n <div class=\"shield-status\">\n <div class=\"check\">✓</div>\n <div>Ready to browse</div>\n </div>\n </div>\n </section>\n </div>\n </div>\n </div>\n\n <div class=\"speed-lines\">\n <i></i>\n <i></i>\n <i></i>\n <i></i>\n </div>\n <div class=\"white-wipe\"></div>\n </div>\n\n <script>\n window.__timelines = window.__timelines || {};\n\n gsap.defaults({ force3D: true });\n\n const tl = gsap.timeline({\n paused: true,\n defaults: { duration: 0.45, ease: \"power3.out\" },\n });\n\n const breatheRepeats = Math.ceil(7 / 2.4) - 1;\n\n tl.from(\n \".headline span\",\n { y: 46, opacity: 0, stagger: 0.055, duration: 0.38, ease: \"back.out(1.35)\" },\n 0.05,\n )\n .from(\".headline .sub\", { y: 20, opacity: 0, duration: 0.28 }, 0.2)\n .from(\".ambient-word\", { scale: 0.92, opacity: 0, duration: 0.5 }, 0.08)\n .from(\".ambient-line\", { scaleX: 0, opacity: 0, stagger: 0.08, duration: 0.42 }, 0.16)\n .to(\n \".ambient-line\",\n {\n scaleX: 1.08,\n opacity: 0.55,\n duration: 2.4,\n yoyo: true,\n repeat: breatheRepeats,\n ease: \"sine.inOut\",\n },\n 0.55,\n )\n .from(\n \".phone\",\n {\n y: 760,\n scale: 0.82,\n rotation: -2.5,\n opacity: 0,\n duration: 0.47,\n ease: \"back.out(1.6)\",\n },\n 0,\n )\n .from(\".store-title\", { y: 26, opacity: 0, duration: 0.28 }, 0.28)\n .from(\".avatar\", { scale: 0.4, opacity: 0, duration: 0.24, ease: \"back.out(1.8)\" }, 0.34)\n .from(\".search-pill\", { y: 18, opacity: 0, duration: 0.26 }, 0.4)\n .from(\n \".store-row\",\n { y: 44, opacity: 0, stagger: 0.035, duration: 0.36, ease: \"power3.out\" },\n 0.48,\n );\n\n tl.to(\".scroll-content\", { y: -456, duration: 1.2, ease: \"power4.out\" }, 0.62)\n .to(\n \".screen-window\",\n { scale: 1.012, duration: 0.18, yoyo: true, repeat: 1, ease: \"power2.out\" },\n 0.92,\n )\n .to(\n \".screen-window\",\n { scale: 1.014, duration: 0.14, yoyo: true, repeat: 1, ease: \"power2.out\" },\n 1.25,\n )\n .to(\n \"#vpn-row\",\n {\n scale: 1.035,\n outlineWidth: 3,\n outlineColor: \"rgba(0,122,255,0.34)\",\n duration: 0.2,\n ease: \"back.out(1.7)\",\n },\n 1.72,\n )\n .to(\"#vpn-row\", { scale: 1, duration: 0.22 }, 1.93)\n .fromTo(\n \".tap-dot\",\n { scale: 0.25, opacity: 0.95 },\n { scale: 2.15, opacity: 0, duration: 0.42, ease: \"power2.out\" },\n 2.02,\n )\n .to(\"#vpn-row .get-button\", { scale: 0.82, duration: 0.1, ease: \"power3.in\" }, 2.04)\n .to(\"#vpn-row .get-button\", { scale: 1, duration: 0.14, ease: \"back.out(2)\" }, 2.14);\n\n tl.fromTo(\n \".detail-view\",\n { y: 620, scale: 0.92, opacity: 0 },\n { y: 0, scale: 1, opacity: 1, duration: 0.46, ease: \"power4.out\" },\n 2.32,\n )\n .from(\".detail-card\", { y: 56, scale: 0.96, duration: 0.42, ease: \"back.out(1.2)\" }, 2.34)\n .from(\n \".detail-icon\",\n { scale: 0.55, rotation: -8, opacity: 0, duration: 0.32, ease: \"back.out(1.8)\" },\n 2.47,\n )\n .from(\".detail-title\", { y: 20, opacity: 0, duration: 0.28 }, 2.54)\n .from(\".detail-sub\", { y: 14, opacity: 0, duration: 0.24 }, 2.6)\n .from(\n \".install-button\",\n { scale: 0.72, opacity: 0, duration: 0.24, ease: \"back.out(2)\" },\n 2.66,\n )\n .from(\".stat\", { y: 28, opacity: 0, stagger: 0.05, duration: 0.3 }, 2.78)\n .from(\".progress-panel\", { y: 42, scale: 0.96, opacity: 0, duration: 0.38 }, 2.92);\n\n tl.to(\".install-label\", { y: -18, opacity: 0, duration: 0.16 }, 2.86)\n .set(\".install-label\", { textContent: \"\" }, 3.02)\n .to(\".ring\", { opacity: 1, duration: 0.16 }, 2.96)\n .fromTo(\n \".ring .progress\",\n { strokeDashoffset: 69 },\n { strokeDashoffset: 0, duration: 1.2, ease: \"power2.inOut\" },\n 3.04,\n )\n .fromTo(\n \"#progressArc\",\n { strokeDashoffset: 440 },\n { strokeDashoffset: 0, duration: 1.42, ease: \"power2.inOut\" },\n 3.02,\n )\n .set(\"#progressText\", { textContent: \"18%\" }, 3.18)\n .to(\".progress-panel\", { scale: 1.025, duration: 0.12, yoyo: true, repeat: 1 }, 3.18)\n .set(\"#progressText\", { textContent: \"52%\" }, 3.56)\n .to(\".progress-panel\", { scale: 1.03, duration: 0.12, yoyo: true, repeat: 1 }, 3.56)\n .set(\"#progressText\", { textContent: \"87%\" }, 3.9)\n .to(\".progress-panel\", { scale: 1.035, duration: 0.12, yoyo: true, repeat: 1 }, 3.9)\n .set(\"#progressText\", { textContent: \"100%\" }, 4.28)\n .to(\n \".progress-panel\",\n { scale: 1.04, duration: 0.12, yoyo: true, repeat: 1, ease: \"back.out(1.7)\" },\n 4.28,\n )\n .to(\".ring\", { scale: 0.35, opacity: 0, duration: 0.16 }, 4.44)\n .to(\".install-button\", { backgroundColor: \"#34c759\", width: 94, duration: 0.18 }, 4.5)\n .set(\".open-label\", { textContent: \"OPEN\" }, 4.52)\n .to(\".open-label\", { opacity: 1, duration: 0.16 }, 4.56)\n .fromTo(\n \".shield-status\",\n { y: 22, scale: 0.9, opacity: 0 },\n { y: 0, scale: 1, opacity: 1, duration: 0.34, ease: \"back.out(1.8)\" },\n 4.62,\n )\n .to(\n \".phone\",\n { scale: 1.025, duration: 0.18, yoyo: true, repeat: 1, ease: \"back.out(1.5)\" },\n 4.7,\n );\n\n tl.to(\".headline\", { y: -18, opacity: 0.35, duration: 0.22 }, 5.18)\n .to(\".ambient-word\", { opacity: 0.018, duration: 0.22 }, 5.18)\n .to(\".snap-shadow\", { opacity: 1, scaleX: 0.8, duration: 0.16 }, 5.35)\n .fromTo(\n \".speed-lines\",\n { y: -220, opacity: 0 },\n { y: 420, opacity: 1, duration: 0.26, ease: \"power4.in\" },\n 5.42,\n )\n .to(\n \".phone\",\n { y: 1050, scale: 0.78, rotation: -3.5, duration: 0.48, ease: \"power4.in\" },\n 5.44,\n )\n .to(\".snap-shadow\", { opacity: 0, scaleX: 1.6, duration: 0.28 }, 5.62)\n .to(\".speed-lines\", { opacity: 0, duration: 0.2 }, 5.74)\n .to(\".white-wipe\", { opacity: 1, duration: 0.42, ease: \"power2.out\" }, 5.86);\n\n window.__timelines[\"vpn-youtube-spot\"] = tl;\n </script>\n </body>\n</html>\n"} |