comparison src/index.html.luan @ 0:8f4df159f06b

start public repo
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 11 Jul 2025 20:57:49 -0600
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:8f4df159f06b
1 local Luan = require "luan:Luan.luan"
2 local error = Luan.error
3 local pairs = Luan.pairs or error()
4 local Table = require "luan:Table.luan"
5 local is_empty = Table.is_empty or error()
6 local concat = Table.concat or error()
7 local Html = require "luan:Html.luan"
8 local url_encode = Html.url_encode or error()
9 local Io = require "luan:Io.luan"
10 local Http = require "luan:http/Http.luan"
11 local Shared = require "site:/lib/Shared.luan"
12 local head = Shared.head or error()
13 local body_header = Shared.body_header or error()
14 local footer = Shared.footer or error()
15 local User = require "site:/lib/User.luan"
16 local current_user = User.current or error()
17
18
19 return function()
20 local user = current_user()
21 Io.stdout = Http.response.text_writer()
22 %>
23 <!doctype html>
24 <html lang="en">
25 <head>
26 <% head() %>
27 <title>Link My Style</title>
28 <style>
29 div[block] > div {
30 max-width: 1000px;
31 margin-left: auto;
32 margin-right: auto;
33 }
34 div[block] p {
35 font-size: 18px;
36 }
37 div[block] h2 {
38 font-size: 28px;
39 color: rgb(76, 61, 174);
40 }
41 div[block] h3 {
42 font-size: 24px;
43 color: rgb(76, 61, 174);
44 margin: 0;
45 }
46
47 div[block="top"] {
48 background-color: rgb(138, 127, 210);
49 text-align: center;
50 }
51 div[block="top"] > div {
52 display: flex;
53 flex-direction: column;
54 align-items: center;
55 }
56 div[block="top"] h1 {
57 color: rgb(255, 235, 15);
58 font-size: 36px;
59 max-width: 12em;
60 }
61 div[block="top"] p {
62 color: rgb(236, 236, 236);
63 max-width: 45em;
64 }
65 div[animation] {
66 width: 98%;
67 aspect-ratio: 950 / 535;
68 position: relative;
69 }
70 div[animation] img {
71 position: absolute;
72 top: 50%;
73 left: 50%;
74 transform: translate(-50%,-50%);
75 animation-duration: 7s;
76 animation-iteration-count: infinite;
77 box-shadow: 5px 5px 20px 1px rgba(0,0,0,0.5);
78 visibility: hidden;
79 animation-play-state: paused;
80 }
81 div[animation] img[circle] {
82 border-radius: 50%;
83 }
84 div[animation] img[bio] {
85 animation-name: bio;
86 width: 12%;
87 }
88 @keyframes bio {
89 0% {
90 opacity: 0;
91 transform: translate(-50%,-50%) scale(1);
92 }
93 15% {
94 opacity: 1;
95 transform: translate(-50%,-50%) scale(1.4375);
96 }
97 20%, 50% {
98 opacity: 1;
99 transform: translate(-50%,-50%) scale(1);
100 }
101 60% {
102 opacity: 1;
103 transform: translate(-50%,-50%) scale(1.1875);
104 }
105 65% {
106 opacity: 1;
107 transform: translate(-50%,-50%) scale(0.875);
108 }
109 70% {
110 opacity: 1;
111 transform: translate(-50%,-50%) scale(1.1875);
112 }
113 75% {
114 opacity: 1;
115 transform: translate(-50%,-50%) scale(0.875);
116 }
117 80%, 100% {
118 opacity: 1;
119 transform: translate(-50%,-50%) scale(1);
120 }
121 }
122 div[animation] img[i1] {
123 animation-name: i1;
124 width: 14%;
125 }
126 @keyframes i1 {
127 0%, 28% {
128 opacity: 0;
129 top: 50%;
130 left: 50%;
131 }
132 38% {
133 opacity: 0.1;
134 }
135 48%, 78% {
136 opacity: 1;
137 top: 61%;
138 left: 89%;
139 }
140 85% {
141 opacity: 0.1;
142 }
143 90%, 100% {
144 opacity: 0;
145 top: 50%;
146 left: 50%;
147 }
148 }
149 div[animation] img[i2] {
150 animation-name: i2;
151 width: 17%;
152 }
153 @keyframes i2 {
154 0%, 30% {
155 opacity: 0;
156 top: 50%;
157 left: 50%;
158 }
159 40% {
160 opacity: 0.1;
161 }
162 50%, 80% {
163 opacity: 1;
164 top: 74%;
165 left: 69%;
166 }
167 85% {
168 opacity: 0.1;
169 }
170 90%, 100% {
171 opacity: 0;
172 top: 50%;
173 left: 50%;
174 }
175 }
176 div[animation] img[i3] {
177 animation-name: i3;
178 width: 15%;
179 }
180 @keyframes i3 {
181 0%, 26% {
182 opacity: 0;
183 top: 50%;
184 left: 50%;
185 }
186 36% {
187 opacity: 0.1;
188 }
189 46%, 76% {
190 opacity: 1;
191 top: 25%;
192 left: 75%;
193 }
194 85% {
195 opacity: 0.1;
196 }
197 90%, 100% {
198 opacity: 0;
199 top: 50%;
200 left: 50%;
201 }
202 }
203 div[animation] img[i4] {
204 animation-name: i4;
205 width: 13%;
206 }
207 @keyframes i4 {
208 0%, 20% {
209 opacity: 0;
210 top: 50%;
211 left: 50%;
212 }
213 30% {
214 opacity: 0.1;
215 }
216 40%, 70% {
217 opacity: 1;
218 top: 76%;
219 left: 32%;
220 }
221 85% {
222 opacity: 0.1;
223 }
224 90%, 100% {
225 opacity: 0;
226 top: 50%;
227 left: 50%;
228 }
229 }
230 div[animation] img[i5] {
231 animation-name: i5;
232 width: 16%;
233 }
234 @keyframes i5 {
235 0%, 22% {
236 opacity: 0;
237 top: 50%;
238 left: 50%;
239 }
240 32% {
241 opacity: 0.1;
242 }
243 42%, 72% {
244 opacity: 1;
245 top: 56%;
246 left: 12%;
247 }
248 85% {
249 opacity: 0.1;
250 }
251 90%, 100% {
252 opacity: 0;
253 top: 50%;
254 left: 50%;
255 }
256 }
257 div[animation] img[i6] {
258 animation-name: i6;
259 width: 14%;
260 }
261 @keyframes i6 {
262 0%, 24% {
263 opacity: 0;
264 top: 50%;
265 left: 50%;
266 }
267 24% {
268 opacity: 0.1;
269 }
270 44%, 74% {
271 opacity: 1;
272 top: 20%;
273 left: 26%;
274 }
275 85% {
276 opacity: 0.1;
277 }
278 90%, 100% {
279 opacity: 0;
280 top: 50%;
281 left: 50%;
282 }
283 }
284 div[animation] img[ins] {
285 animation-name: ins;
286 width: 7%;
287 }
288 @keyframes ins {
289 0%, 30% {
290 opacity: 0;
291 top: 50%;
292 left: 50%;
293 }
294 40% {
295 opacity: 0.1;
296 }
297 50%, 80% {
298 opacity: 1;
299 top: 25%;
300 left: 58%;
301 }
302 85% {
303 opacity: 0.1;
304 }
305 90%, 100% {
306 opacity: 0;
307 top: 50%;
308 left: 50%;
309 }
310 }
311 div[animation] img[tk] {
312 animation-name: tk;
313 width: 7%;
314 }
315 @keyframes tk {
316 0%, 32% {
317 opacity: 0;
318 top: 50%;
319 left: 50%;
320 }
321 42% {
322 opacity: 0.1;
323 }
324 52%, 82% {
325 opacity: 1;
326 top: 45%;
327 left: 35%;
328 }
329 85% {
330 opacity: 0.1;
331 }
332 90%, 100% {
333 opacity: 0;
334 top: 50%;
335 left: 50%;
336 }
337 }
338 div[animation] img[yt] {
339 animation-name: yt;
340 width: 7%;
341 }
342 @keyframes yt {
343 0%, 34% {
344 opacity: 0;
345 top: 50%;
346 left: 50%;
347 }
348 44% {
349 opacity: 0.1;
350 }
351 54%, 84% {
352 opacity: 1;
353 top: 23%;
354 left: 43%;
355 }
356 85% {
357 opacity: 0.1;
358 }
359 90%, 100% {
360 opacity: 0;
361 top: 50%;
362 left: 50%;
363 }
364 }
365
366 svg[round] {
367 display: block;
368 margin-top: -1px;
369 }
370
371 div[block="video"] {
372 margin-top: 100px;
373 margin-bottom: 100px;
374 }
375 div[block="video"] > div {
376 display: flex;
377 }
378
379 div[block="midsize"] {
380 background-color: rgb(232, 234, 236);
381 text-align: center;
382 padding-bottom: 60px;
383 }
384 div[block="midsize"] h2 {
385 padding-top: 2em;
386 }
387 div[block="midsize"] p[see] {
388 font-weight: bold;
389 }
390 div[block="midsize"] > div > div {
391 text-align: left;
392 margin-top: 20px;
393 }
394 div[block="midsize"] a {
395 display: block;
396 }
397 div[block="midsize"] img {
398 width: 100%;
399 }
400 div[block="midsize"] > div > div p {
401 font-size: 17px;
402 margin-top: 10px;
403 }
404
405 div[block="users"] {
406 text-align: center;
407 }
408 div[block="users"] h1 {
409 margin-bottom: 0;
410 }
411 div[block="users"] p {
412 margin-bottom: 26px;
413 }
414 div[block="users"] a[more] {
415 text-decoration: none;
416 display: inline-block;
417 background-color: #FFEC0F;
418 padding: 16px 28px;
419 font-size: 20px;
420 border-radius: 4px;
421 }
422 div[block="users"] div[img] {
423 display: flex;
424 margin-top: 20px;
425 }
426 div[block="users"] div[img] span {
427 border: 6px solid white;
428 border-radius: 50%;
429 box-shadow: 5px 5px 20px 1px grey;
430 overflow: hidden;
431 }
432 div[block="users"] div[img] a {
433 display: block;
434 width: 100%;
435 height: 0;
436 padding-top: 100%;
437 position: relative;
438 }
439 div[block="users"] div[img] img {
440 display: block;
441 position: absolute;
442 top: 0;
443 bottom: 0;
444 left: 0;
445 right: 0;
446 width: 100%;
447 }
448
449 div[block="free"] {
450 text-align: center;
451 font-size: 20px;
452 }
453 div[block="free"] > div {
454 display: flex;
455 justify-content: center;
456 align-items: center;
457 gap: 20px;
458 }
459 div[block="free"] div[rect] {
460 background-color: #9181EE;
461 width: 350px;
462 height: 200px;
463 border-radius: 20px;
464 box-shadow: 5px 5px 20px 1px grey;
465 }
466 div[block="free"] div[rect] div[top] {
467 color: #FFEC0F;
468 margin-top: 40px;
469 margin-bottom: 10px;
470 }
471 div[block="free"] div[rect] div[bottom] {
472 display: flex;
473 justify-content: center;
474 align-items: center;
475 gap: 20px;
476 }
477 div[block="free"] div[rect] img {
478 width: 70px;
479 height: 70px;
480 }
481 div[block="free"] div[rect] span {
482 color: white;
483 }
484 div[block="free"] div[plus] {
485 background-color: #FFEC0F;
486 border-radius: 50%;
487 font-size: 25px;
488 width: 40px;
489 height: 40px;
490 display: flex;
491 justify-content: center;
492 align-items: center;
493 }
494
495 div[block="register"] {
496 text-align: center;
497 display: flex;
498 flex-direction: column;
499 align-items: center;
500 }
501 div[block="register"] a[register] {
502 padding: 16px 28px;
503 color: white;
504 background-color: rgb(137, 126, 210);
505 font-size: 20px;
506 border-radius: 4px;
507 }
508 div[block="register"] a[register]:hover {
509 background-color: rgb(164, 151, 252);
510 }
511
512 div[block="feedback"] > div {
513 display: flex;
514 justify-content: center;
515 align-items: center;
516 gap: 20px;
517 }
518 div[block="feedback"] > div > div {
519 max-width: 400px;
520 }
521 div[block="feedback"] img {
522 height: 150px;
523 width: 150px;
524 }
525 div[block="feedback"] h2 {
526 font-size: 28px;
527 }
528
529 @media (min-width: 886px) {
530 div[block] > div {
531 padding-left: 1%;
532 padding-right: 1%;
533 }
534 div[block="video"] > div {
535 justify-content: space-evenly;
536 }
537 div[block="video"] span[text] {
538 max-width: 30%;
539 }
540 div[block="midsize"] > div > div {
541 display: inline-block;
542 width: 45%;
543 margin-left: 2%;
544 margin-right: 2%;
545 }
546 div[block="users"] {
547 margin-top: 60px;
548 }
549 div[block="feedback"],
550 div[block="free"] {
551 margin-top: 80px;
552 }
553 div[block="register"] {
554 margin-top: 40px;
555 }
556 div[block="feedback"],
557 div[block="register"] {
558 padding-bottom: 60px;
559 }
560 div[img] {
561 justify-content: center;
562 gap: 40px;
563 }
564 div[img] span {
565 width: 200px;
566 }
567 }
568
569 @media (max-width: 885px) {
570 div[block] > div {
571 padding-left: 4%;
572 padding-right: 4%;
573 }
574 div[block="video"] > div {
575 flex-direction: column;
576 align-items: center;
577 }
578 div[block="video"] span[text] {
579 max-width: 80%;
580 margin-bottom: 50px;
581 }
582 div[block="free"] {
583 margin-top: 100px;
584 width: 100%;
585 }
586 div[block="free"] > div {
587 flex-direction: column;
588 }
589 div[block="users"] {
590 margin-top: 100px;
591 }
592 div[img] {
593 justify-content: space-evenly;
594 }
595 div[img] span {
596 width: 30%;
597 }
598 div[block="feedback"] {
599 text-align: center;
600 }
601 div[block="feedback"],
602 div[block="register"] {
603 margin-top: 100px;
604 padding-bottom: 100px;
605 }
606 div[block="feedback"] > div {
607 flex-direction: column;
608 }
609 }
610
611 span[video] {
612 cursor: pointer;
613 }
614 div[relative] {
615 position: relative;
616 }
617 span[video] video {
618 display: block;
619 width: 250px;
620 }
621 div[progress] {
622 background-color: #E0E0E0;
623 }
624 div[progress] div {
625 height: 6px;
626 background-color: #52A08E;
627 width: 0;
628 }
629 @media (hover: hover) {
630 div[progress]:hover {
631 background-color: #CECECE;
632 }
633 div[progress]:hover div {
634 background-color: #31665A;
635 }
636 }
637 div[play] {
638 position: absolute;
639 top: 0;
640 left: 0;
641 bottom: 0;
642 right: 0;
643 display: none;
644 justify-content: center;
645 align-items: center;
646 }
647 div[play] img {
648 width: 60px;
649 filter: invert(100%);
650 opacity: 0.7;
651 user-select: none;
652 }
653 </style>
654 <script>
655 function mouseAction(event,progress) {
656 //console.log(event);
657 if( event.buttons === 0 )
658 return;
659 let video = progress.parentNode.querySelector('video');
660 video.currentTime = video.duration * event.offsetX / progress.clientWidth;
661 }
662
663 function touchMove(event,progress) {
664 //console.log(event);
665 let touches = event.touches;
666 if( touches.length !== 1 )
667 return;
668 let touch = touches[0];
669 let touchX = touch.clientX;
670 let rect = event.target.getBoundingClientRect();
671 let targetX = rect.x;
672 if( touchX < targetX || touchX > rect.right )
673 return;
674 event.offsetX = touchX - targetX;
675 //console.log(event.offsetX);
676 mouseAction(event,progress);
677 }
678
679 function stop(video) {
680 video.pause();
681 let play = video.nextElementSibling;
682 play.style.display = 'flex';
683 }
684
685 function play(video) {
686 video.play();
687 let play = video.nextElementSibling;
688 play.style.display = 'none';
689 }
690
691 function updateProgress(video) {
692 //console.log('init');
693 let progress = video.parentNode.parentNode.querySelector('div[progress] div');
694 let pct = 100*video.currentTime/video.duration;
695 //console.log(pct);
696 progress.style.width = pct+'%';
697 }
698
699 function resized() {
700 let imgs = document.querySelectorAll('div[animation] img[rect]');
701 for( let img of imgs ) {
702 img.style['border-radius'] = 0.3 * img.width + 'px';
703 }
704 }
705 function loaded() {
706 resized();
707 let imgs = document.querySelectorAll('div[animation] img');
708 for( let img of imgs ) {
709 img.style.visibility = 'visible';
710 img.style['animation-play-state'] = 'running';
711 }
712 }
713 </script>
714 </head>
715 <body onload="loaded()" onresize="resized()">
716 <div full>
717 <% body_header() %>
718 <div body>
719 <div block=top>
720 <div>
721 <h1>The link in bio that increases affiliate sales.</h1>
722 <div animation>
723 <img bio rect src="/images/home/bio.png">
724 <img i1 rect src="/images/home/i1.png">
725 <img i2 rect src="/images/home/i2.png">
726 <img i3 rect src="/images/home/i3.png">
727 <img i4 rect src="/images/home/i4.png">
728 <img i5 rect src="/images/home/i5.png">
729 <img i6 rect src="/images/home/i6.png">
730 <img ins circle src="/images/home/ins.png">
731 <img tk circle src="/images/home/tk.png">
732 <img yt circle src="/images/home/yt.png">
733 </div>
734 <p>One link to all of your social media posts with affiliated products.</p>
735 <p>People that switch to LinkMyStyle report that their sales increased by over 100%. LinkMyStyle is completely free to use, so there is no risk for you to try it out and see how well it works for you.</p>
736 </div>
737 </div>
738 <svg round viewBox="0 0 100 7" xmlns="http://www.w3.org/2000/svg">
739 <circle cx="50" cy="-193" r="200" fill="rgb(138, 127, 210)" />
740 </svg>
741 <div block=video>
742 <div>
743 <span text>
744 <h2>A fast lane to showcase products</h2>
745 <p>Your followers can now easily find the products shown in your videos.</p>
746 </span>
747 <span video>
748 <div relative>
749 <video src="https://ucarecdn.com/9be9b1b8-98a9-4f12-8cd4-992de8d3dc70/" muted autoplay loop playsinline onclick="stop(this)" ontimeupdate="updateProgress(this)">
750 </video>
751 <div play onclick="play(previousElementSibling)"><img src="/images/play.svg"></div>
752 </div>
753 <div progress onmousedown="mouseAction(event,this)" onmousemove="mouseAction(event,this)" ontouchstart="touchMove(event,this)" ontouchmove="touchMove(event,this)">
754 <div></div>
755 </div>
756 </span>
757 </div>
758 </div>
759 <div block=midsize>
760 <div>
761 <h2>User Example</h2>
762 <p see>See how @JessicaGrace increased her affiliated sales by over 100%.</p>
763 <div>
764 <a href="https://www.tiktok.com/@jessica_gracexx"><img src="/images/home/01.png"></a>
765 <h3>1. Add a link to bio.</h3>
766 <p>She puts her LinkMyStyle link in both her TikTok and Instagram bios so users can easily find the products shown in her videos.</p>
767 </div>
768 <div>
769 <a href="https://www.tiktok.com/@jessica_gracexx/video/7321390538190540064"><img src="/images/home/02.png"></a>
770 <h3>2. Post a photo with links.</h3>
771 <p>She posts videos where she shows her outfits to her followers, and then posts a screenshot to LinkMyStyle with the product links.</p>
772 </div>
773 <div>
774 <a href="https://linkmy.style/JessicaGrace#pc9209"><img src="/images/home/03.png"></a>
775 <h3>3. Followers find the product.</h3>
776 <p>Users then go to her LinkMyStyle page and look for the thumbnail to the video they just saw.</p>
777 </div>
778 <div>
779 <a href="https://linkmy.style/pic.html?pic=9209"><img src="/images/home/04.png"></a>
780 <h3>4. Followers click and go.</h3>
781 <p>After clicking on the thumbnail, they can easily find the products shown in her video.</p>
782 </div>
783 </div>
784 </div>
785 <div block=users>
786 <div>
787 <h2>User Highlights</h2>
788 <p><a more href="https://linkmy.style/linkmystyle">View More</a></p>
789 <div img>
790 <span><a href="https://linkmy.style/hyn_x"><img src="/images/home/hyn_x.jpeg"></a></span>
791 <span><a href="https://linkmy.style/JessicaGrace"><img src="/images/home/midsize.jpeg"></a></span>
792 <span><a href="https://linkmy.style/kenziekayfashion"><img src="/images/home/kenzie.jpeg"></a></span>
793 </div>
794 </div>
795 </div>
796 <div block=free>
797 <div>
798 <div rect>
799 <div top>Free Custom Backgrounds</div>
800 <div bottom>
801 <img src="/images/home/background.svg">
802 <span>Make your page<br>truly yours</span>
803 </div>
804 </div>
805 <div plus><span>+</span></div>
806 <div rect>
807 <div top>Free Analytics</div>
808 <div bottom>
809 <img src="/images/home/analytics.svg">
810 <span>See what your<br>audience is doing</span>
811 </div>
812 </div>
813 </div>
814 </div>
815 <% if user ~= nil then %>
816 <div block=feedback>
817 <div>
818 <div>
819 <h2>We would love to get your feedback about our website</h2>
820 <p black>Email us your thoughts, suggestions, or support questions.<br>support@linkmy.style</p>
821 </div>
822 <img src="/images/home/mail.svg">
823 </div>
824 </div>
825 <% else %>
826 <div block=register>
827 <div>
828 <h2>Completely Free to Use</h2>
829 <div>
830 <a button register href="/register.html">Register Now</a>
831 </div>
832 </div>
833 </div>
834 <% end %>
835 </div>
836 <% footer() %>
837 </div>
838 </body>
839 </html>
840 <%
841 end