enzostvs HF Staff commited on
Commit
8dcdef9
·
verified ·
1 Parent(s): 9c8505b

Improve the UI of the contact form, input are not designed.

Browse files
Files changed (2) hide show
  1. script.js +852 -1
  2. style.css +36 -0
script.js CHANGED
@@ -130,7 +130,6 @@ function validateForm() {
130
 
131
  return isValid;
132
  }
133
-
134
  function collectFormData() {
135
  const nameInput = document.querySelector('custom-input#name');
136
  const emailInput = document.querySelector('custom-input#email');
@@ -145,6 +144,27 @@ function collectFormData() {
145
  };
146
  }
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  // Setup custom input event listeners when elements are ready
149
  waitForCustomElements().then(() => {
150
  // Clear errors on input
@@ -503,3 +523,834 @@ function initTypingAnimation() {
503
  }
504
  // Initialize typing animation when page loads
505
  document.addEventListener('DOMContentLoaded', initTypingAnimation);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
  return isValid;
132
  }
 
133
  function collectFormData() {
134
  const nameInput = document.querySelector('custom-input#name');
135
  const emailInput = document.querySelector('custom-input#email');
 
144
  };
145
  }
146
 
147
+ // Enhanced form submission with better UX
148
+ async function submitForm(data) {
149
+ // Simulate API call
150
+ try {
151
+ const response = await fetch('/api/contact', {
152
+ method: 'POST',
153
+ headers: {
154
+ 'Content-Type': 'application/json',
155
+ },
156
+ body: JSON.stringify(data)
157
+ });
158
+
159
+ if (!response.ok) {
160
+ throw new Error('Network response was not ok');
161
+ }
162
+
163
+ return { success: true, message: 'Message sent successfully!' };
164
+ } catch (error) {
165
+ throw new Error('Failed to send message. Please try again.');
166
+ }
167
+ }
168
  // Setup custom input event listeners when elements are ready
169
  waitForCustomElements().then(() => {
170
  // Clear errors on input
 
523
  }
524
  // Initialize typing animation when page loads
525
  document.addEventListener('DOMContentLoaded', initTypingAnimation);
526
+
527
+ // Custom Input Component
528
+ class CustomInput extends HTMLElement {
529
+ constructor() {
530
+ super();
531
+ this.attachShadow({ mode: 'open' });
532
+ this.value = '';
533
+ }
534
+
535
+ static get observedAttributes() {
536
+ return ['placeholder', 'type', 'name', 'required', 'value'];
537
+ }
538
+
539
+ connectedCallback() {
540
+ this.render();
541
+ }
542
+
543
+ attributeChangedCallback() {
544
+ this.render();
545
+ }
546
+
547
+ render() {
548
+ const placeholder = this.getAttribute('placeholder') || '';
549
+ const type = this.getAttribute('type') || 'text';
550
+ const name = this.getAttribute('name') || '';
551
+ const required = this.hasAttribute('required');
552
+
553
+ this.shadowRoot.innerHTML = `
554
+ <style>
555
+ :host {
556
+ display: block;
557
+ width: 100%;
558
+ }
559
+
560
+ .input-wrapper {
561
+ position: relative;
562
+ width: 100%;
563
+ }
564
+
565
+ .input-field {
566
+ width: 100%;
567
+ padding: 14px 16px 14px 44px;
568
+ background: rgba(255, 255, 255, 0.05);
569
+ border: 1px solid rgba(255, 255, 255, 0.1);
570
+ border-radius: 12px;
571
+ color: rgb(var(--fg));
572
+ font-size: 14px;
573
+ outline: none;
574
+ transition: all 0.2s ease;
575
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
576
+ }
577
+
578
+ .input-field:focus {
579
+ border-color: rgba(var(--color-primary-500), 0.6);
580
+ box-shadow: 0 0 0 3px rgba(var(--color-primary-500), 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
581
+ background: rgba(255, 255, 255, 0.08);
582
+ }
583
+
584
+ .input-field::placeholder {
585
+ color: rgba(161, 161, 170, 0.6);
586
+ transition: color 0.2s ease;
587
+ }
588
+
589
+ .input-field:focus::placeholder {
590
+ color: rgba(161, 161, 170, 0.4);
591
+ }
592
+
593
+ .input-field.error {
594
+ border-color: rgb(239, 68, 68);
595
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
596
+ }
597
+
598
+ .input-icon {
599
+ position: absolute;
600
+ left: 14px;
601
+ top: 50%;
602
+ transform: translateY(-50%);
603
+ width: 16px;
604
+ height: 16px;
605
+ color: rgba(161, 161, 170, 0.8);
606
+ transition: all 0.2s ease;
607
+ pointer-events: none;
608
+ }
609
+
610
+ .input-field:focus + .input-icon {
611
+ color: rgba(var(--color-primary-500), 0.8);
612
+ }
613
+
614
+ .error-text {
615
+ margin-top: 6px;
616
+ font-size: 12px;
617
+ color: rgb(239, 68, 68);
618
+ opacity: 0;
619
+ transform: translateY(-4px);
620
+ transition: all 0.2s ease;
621
+ }
622
+
623
+ .error-text.show {
624
+ opacity: 1;
625
+ transform: translateY(0);
626
+ }
627
+
628
+ /* Light theme styles */
629
+ :host(.light) .input-field {
630
+ background: rgba(255, 255, 255, 0.8);
631
+ border-color: rgba(15, 23, 42, 0.15);
632
+ color: rgb(15, 23, 42);
633
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
634
+ }
635
+
636
+ :host(.light) .input-field:focus {
637
+ background: rgba(255, 255, 255, 0.95);
638
+ border-color: rgba(var(--color-primary-500), 0.6);
639
+ box-shadow: 0 0 0 3px rgba(var(--color-primary-500), 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.5);
640
+ }
641
+
642
+ :host(.light) .input-field::placeholder {
643
+ color: rgba(100, 116, 139, 0.8);
644
+ }
645
+
646
+ :host(.light) .input-icon {
647
+ color: rgba(100, 116, 139, 0.8);
648
+ }
649
+ </style>
650
+
651
+ <div class="input-wrapper">
652
+ <input
653
+ type="${type}"
654
+ name="${name}"
655
+ placeholder="${placeholder}"
656
+ ${required ? 'required' : ''}
657
+ class="input-field"
658
+ />
659
+ <div class="input-icon">
660
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
661
+ <circle cx="12" cy="12" r="10"></circle>
662
+ <path d="m9 12 2 2 4-4"></path>
663
+ </svg>
664
+ </div>
665
+ <div class="error-text"></div>
666
+ </div>
667
+ `;
668
+
669
+ this.value = this.getAttribute('value') || '';
670
+ this.setupEventListeners();
671
+ this.updateTheme();
672
+ }
673
+
674
+ setupEventListeners() {
675
+ const input = this.shadowRoot.querySelector('.input-field');
676
+ const errorText = this.shadowRoot.querySelector('.error-text');
677
+
678
+ if (input) {
679
+ input.addEventListener('input', (e) => {
680
+ this.value = e.target.value;
681
+ this.dispatchEvent(new CustomEvent('input', { detail: { value: this.value } }));
682
+ this.clearError();
683
+ });
684
+
685
+ input.addEventListener('blur', () => {
686
+ this.dispatchEvent(new CustomEvent('blur', { detail: { value: this.value } }));
687
+ });
688
+
689
+ input.addEventListener('focus', () => {
690
+ this.dispatchEvent(new CustomEvent('focus', { detail: { value: this.value } }));
691
+ });
692
+ }
693
+ }
694
+
695
+ updateTheme() {
696
+ const html = document.documentElement;
697
+ if (html.classList.contains('light')) {
698
+ this.shadowRoot.host.classList.add('light');
699
+ } else {
700
+ this.shadowRoot.host.classList.remove('light');
701
+ }
702
+ }
703
+
704
+ setError(message) {
705
+ const input = this.shadowRoot.querySelector('.input-field');
706
+ const errorText = this.shadowRoot.querySelector('.error-text');
707
+
708
+ if (input) input.classList.add('error');
709
+ if (errorText) {
710
+ errorText.textContent = message;
711
+ errorText.classList.add('show');
712
+ }
713
+ }
714
+
715
+ clearError() {
716
+ const input = this.shadowRoot.querySelector('.input-field');
717
+ const errorText = this.shadowRoot.querySelector('.error-text');
718
+
719
+ if (input) input.classList.remove('error');
720
+ if (errorText) {
721
+ errorText.classList.remove('show');
722
+ }
723
+ }
724
+
725
+ get value() {
726
+ return this._value || '';
727
+ }
728
+
729
+ set value(val) {
730
+ this._value = val;
731
+ const input = this.shadowRoot?.querySelector('.input-field');
732
+ if (input) {
733
+ input.value = val;
734
+ }
735
+ }
736
+ }
737
+
738
+ // Custom Textarea Component
739
+ class CustomTextarea extends HTMLElement {
740
+ constructor() {
741
+ super();
742
+ this.attachShadow({ mode: 'open' });
743
+ this.value = '';
744
+ }
745
+
746
+ static get observedAttributes() {
747
+ return ['placeholder', 'name', 'required', 'rows', 'value'];
748
+ }
749
+
750
+ connectedCallback() {
751
+ this.render();
752
+ }
753
+
754
+ attributeChangedCallback() {
755
+ this.render();
756
+ }
757
+
758
+ render() {
759
+ const placeholder = this.getAttribute('placeholder') || '';
760
+ const name = this.getAttribute('name') || '';
761
+ const required = this.hasAttribute('required');
762
+ const rows = this.getAttribute('rows') || '4';
763
+
764
+ this.shadowRoot.innerHTML = `
765
+ <style>
766
+ :host {
767
+ display: block;
768
+ width: 100%;
769
+ }
770
+
771
+ .textarea-wrapper {
772
+ position: relative;
773
+ width: 100%;
774
+ }
775
+
776
+ .textarea-field {
777
+ width: 100%;
778
+ min-height: 120px;
779
+ padding: 14px 16px 14px 16px;
780
+ background: rgba(255, 255, 255, 0.05);
781
+ border: 1px solid rgba(255, 255, 255, 0.1);
782
+ border-radius: 12px;
783
+ color: rgb(var(--fg));
784
+ font-size: 14px;
785
+ outline: none;
786
+ transition: all 0.2s ease;
787
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
788
+ resize: vertical;
789
+ font-family: inherit;
790
+ line-height: 1.5;
791
+ }
792
+
793
+ .textarea-field:focus {
794
+ border-color: rgba(var(--color-primary-500), 0.6);
795
+ box-shadow: 0 0 0 3px rgba(var(--color-primary-500), 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
796
+ background: rgba(255, 255, 255, 0.08);
797
+ }
798
+
799
+ .textarea-field::placeholder {
800
+ color: rgba(161, 161, 170, 0.6);
801
+ transition: color 0.2s ease;
802
+ }
803
+
804
+ .textarea-field:focus::placeholder {
805
+ color: rgba(161, 161, 170, 0.4);
806
+ }
807
+
808
+ .textarea-field.error {
809
+ border-color: rgb(239, 68, 68);
810
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
811
+ }
812
+
813
+ .error-text {
814
+ margin-top: 6px;
815
+ font-size: 12px;
816
+ color: rgb(239, 68, 68);
817
+ opacity: 0;
818
+ transform: translateY(-4px);
819
+ transition: all 0.2s ease;
820
+ }
821
+
822
+ .error-text.show {
823
+ opacity: 1;
824
+ transform: translateY(0);
825
+ }
826
+
827
+ /* Light theme styles */
828
+ :host(.light) .textarea-field {
829
+ background: rgba(255, 255, 255, 0.8);
830
+ border-color: rgba(15, 23, 42, 0.15);
831
+ color: rgb(15, 23, 42);
832
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
833
+ }
834
+
835
+ :host(.light) .textarea-field:focus {
836
+ background: rgba(255, 255, 255, 0.95);
837
+ border-color: rgba(var(--color-primary-500), 0.6);
838
+ box-shadow: 0 0 0 3px rgba(var(--color-primary-500), 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.5);
839
+ }
840
+
841
+ :host(.light) .textarea-field::placeholder {
842
+ color: rgba(100, 116, 139, 0.8);
843
+ }
844
+ </style>
845
+
846
+ <div class="textarea-wrapper">
847
+ <textarea
848
+ name="${name}"
849
+ placeholder="${placeholder}"
850
+ ${required ? 'required' : ''}
851
+ rows="${rows}"
852
+ class="textarea-field"
853
+ ></textarea>
854
+ <div class="error-text"></div>
855
+ </div>
856
+ `;
857
+
858
+ this.value = this.getAttribute('value') || '';
859
+ this.setupEventListeners();
860
+ this.updateTheme();
861
+ }
862
+
863
+ setupEventListeners() {
864
+ const textarea = this.shadowRoot.querySelector('.textarea-field');
865
+ const errorText = this.shadowRoot.querySelector('.error-text');
866
+
867
+ if (textarea) {
868
+ textarea.addEventListener('input', (e) => {
869
+ this.value = e.target.value;
870
+ this.dispatchEvent(new CustomEvent('input', { detail: { value: this.value } }));
871
+ this.clearError();
872
+ });
873
+
874
+ textarea.addEventListener('blur', () => {
875
+ this.dispatchEvent(new CustomEvent('blur', { detail: { value: this.value } }));
876
+ });
877
+
878
+ textarea.addEventListener('focus', () => {
879
+ this.dispatchEvent(new CustomEvent('focus', { detail: { value: this.value } }));
880
+ });
881
+ }
882
+ }
883
+
884
+ updateTheme() {
885
+ const html = document.documentElement;
886
+ if (html.classList.contains('light')) {
887
+ this.shadowRoot.host.classList.add('light');
888
+ } else {
889
+ this.shadowRoot.host.classList.remove('light');
890
+ }
891
+ }
892
+
893
+ setError(message) {
894
+ const textarea = this.shadowRoot.querySelector('.textarea-field');
895
+ const errorText = this.shadowRoot.querySelector('.error-text');
896
+
897
+ if (textarea) textarea.classList.add('error');
898
+ if (errorText) {
899
+ errorText.textContent = message;
900
+ errorText.classList.add('show');
901
+ }
902
+ }
903
+
904
+ clearError() {
905
+ const textarea = this.shadowRoot.querySelector('.textarea-field');
906
+ const errorText = this.shadowRoot.querySelector('.error-text');
907
+
908
+ if (textarea) textarea.classList.remove('error');
909
+ if (errorText) {
910
+ errorText.classList.remove('show');
911
+ }
912
+ }
913
+
914
+ get value() {
915
+ return this._value || '';
916
+ }
917
+
918
+ set value(val) {
919
+ this._value = val;
920
+ const textarea = this.shadowRoot?.querySelector('.textarea-field');
921
+ if (textarea) {
922
+ textarea.value = val;
923
+ }
924
+ }
925
+ }
926
+
927
+ // Register the custom elements
928
+ customElements.define('custom-input', CustomInput);
929
+ customElements.define('custom-textarea', CustomTextarea);
930
+
931
+ // Theme change observer
932
+ const themeObserver = new MutationObserver(() => {
933
+ document.querySelectorAll('custom-input, custom-textarea').forEach(el => {
934
+ if (el.updateTheme) el.updateTheme();
935
+ });
936
+ });
937
+
938
+ themeObserver.observe(document.documentElement, {
939
+ attributes: true,
940
+ attributeFilter: ['class']
941
+ });
942
+ // Custom Input Component
943
+ class CustomInput extends HTMLElement {
944
+ constructor() {
945
+ super();
946
+ this.attachShadow({ mode: 'open' });
947
+ this.value = '';
948
+ }
949
+
950
+ static get observedAttributes() {
951
+ return ['placeholder', 'type', 'name', 'required', 'value'];
952
+ }
953
+
954
+ connectedCallback() {
955
+ this.render();
956
+ }
957
+
958
+ attributeChangedCallback() {
959
+ this.render();
960
+ }
961
+
962
+ render() {
963
+ const placeholder = this.getAttribute('placeholder') || '';
964
+ const type = this.getAttribute('type') || 'text';
965
+ const name = this.getAttribute('name') || '';
966
+ const required = this.hasAttribute('required');
967
+
968
+ this.shadowRoot.innerHTML = `
969
+ <style>
970
+ :host {
971
+ display: block;
972
+ width: 100%;
973
+ }
974
+
975
+ .input-wrapper {
976
+ position: relative;
977
+ width: 100%;
978
+ }
979
+
980
+ .input-field {
981
+ width: 100%;
982
+ padding: 14px 16px 14px 44px;
983
+ background: rgba(255, 255, 255, 0.05);
984
+ border: 1px solid rgba(255, 255, 255, 0.1);
985
+ border-radius: 12px;
986
+ color: rgb(var(--fg));
987
+ font-size: 14px;
988
+ outline: none;
989
+ transition: all 0.2s ease;
990
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
991
+ }
992
+
993
+ .input-field:focus {
994
+ border-color: rgba(var(--color-primary-500), 0.6);
995
+ box-shadow: 0 0 0 3px rgba(var(--color-primary-500), 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
996
+ background: rgba(255, 255, 255, 0.08);
997
+ }
998
+
999
+ .input-field::placeholder {
1000
+ color: rgba(161, 161, 170, 0.6);
1001
+ transition: color 0.2s ease;
1002
+ }
1003
+
1004
+ .input-field:focus::placeholder {
1005
+ color: rgba(161, 161, 170, 0.4);
1006
+ }
1007
+
1008
+ .input-field.error {
1009
+ border-color: rgb(239, 68, 68);
1010
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
1011
+ }
1012
+
1013
+ .input-icon {
1014
+ position: absolute;
1015
+ left: 14px;
1016
+ top: 50%;
1017
+ transform: translateY(-50%);
1018
+ width: 16px;
1019
+ height: 16px;
1020
+ color: rgba(161, 161, 170, 0.8);
1021
+ transition: all 0.2s ease;
1022
+ pointer-events: none;
1023
+ }
1024
+
1025
+ .input-field:focus + .input-icon {
1026
+ color: rgba(var(--color-primary-500), 0.8);
1027
+ }
1028
+
1029
+ .error-text {
1030
+ margin-top: 6px;
1031
+ font-size: 12px;
1032
+ color: rgb(239, 68, 68);
1033
+ opacity: 0;
1034
+ transform: translateY(-4px);
1035
+ transition: all 0.2s ease;
1036
+ }
1037
+
1038
+ .error-text.show {
1039
+ opacity: 1;
1040
+ transform: translateY(0);
1041
+ }
1042
+
1043
+ /* Light theme styles */
1044
+ :host(.light) .input-field {
1045
+ background: rgba(255, 255, 255, 0.8);
1046
+ border-color: rgba(15, 23, 42, 0.15);
1047
+ color: rgb(15, 23, 42);
1048
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
1049
+ }
1050
+
1051
+ :host(.light) .input-field:focus {
1052
+ background: rgba(255, 255, 255, 0.95);
1053
+ border-color: rgba(var(--color-primary-500), 0.6);
1054
+ box-shadow: 0 0 0 3px rgba(var(--color-primary-500), 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.5);
1055
+ }
1056
+
1057
+ :host(.light) .input-field::placeholder {
1058
+ color: rgba(100, 116, 139, 0.8);
1059
+ }
1060
+
1061
+ :host(.light) .input-icon {
1062
+ color: rgba(100, 116, 139, 0.8);
1063
+ }
1064
+ </style>
1065
+
1066
+ <div class="input-wrapper">
1067
+ <input
1068
+ type="${type}"
1069
+ name="${name}"
1070
+ placeholder="${placeholder}"
1071
+ ${required ? 'required' : ''}
1072
+ class="input-field"
1073
+ />
1074
+ <div class="input-icon">
1075
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1076
+ <circle cx="12" cy="12" r="10"></circle>
1077
+ <path d="m9 12 2 2 4-4"></path>
1078
+ </svg>
1079
+ </div>
1080
+ <div class="error-text"></div>
1081
+ </div>
1082
+ `;
1083
+
1084
+ this.value = this.getAttribute('value') || '';
1085
+ this.setupEventListeners();
1086
+ this.updateTheme();
1087
+ }
1088
+
1089
+ setupEventListeners() {
1090
+ const input = this.shadowRoot.querySelector('.input-field');
1091
+ const errorText = this.shadowRoot.querySelector('.error-text');
1092
+
1093
+ if (input) {
1094
+ input.addEventListener('input', (e) => {
1095
+ this.value = e.target.value;
1096
+ this.dispatchEvent(new CustomEvent('input', { detail: { value: this.value } }));
1097
+ this.clearError();
1098
+ });
1099
+
1100
+ input.addEventListener('blur', () => {
1101
+ this.dispatchEvent(new CustomEvent('blur', { detail: { value: this.value } }));
1102
+ });
1103
+
1104
+ input.addEventListener('focus', () => {
1105
+ this.dispatchEvent(new CustomEvent('focus', { detail: { value: this.value } }));
1106
+ });
1107
+ }
1108
+ }
1109
+
1110
+ updateTheme() {
1111
+ const html = document.documentElement;
1112
+ if (html.classList.contains('light')) {
1113
+ this.shadowRoot.host.classList.add('light');
1114
+ } else {
1115
+ this.shadowRoot.host.classList.remove('light');
1116
+ }
1117
+ }
1118
+
1119
+ setError(message) {
1120
+ const input = this.shadowRoot.querySelector('.input-field');
1121
+ const errorText = this.shadowRoot.querySelector('.error-text');
1122
+
1123
+ if (input) input.classList.add('error');
1124
+ if (errorText) {
1125
+ errorText.textContent = message;
1126
+ errorText.classList.add('show');
1127
+ }
1128
+ }
1129
+
1130
+ clearError() {
1131
+ const input = this.shadowRoot.querySelector('.input-field');
1132
+ const errorText = this.shadowRoot.querySelector('.error-text');
1133
+
1134
+ if (input) input.classList.remove('error');
1135
+ if (errorText) {
1136
+ errorText.classList.remove('show');
1137
+ }
1138
+ }
1139
+
1140
+ get value() {
1141
+ return this._value || '';
1142
+ }
1143
+
1144
+ set value(val) {
1145
+ this._value = val;
1146
+ const input = this.shadowRoot?.querySelector('.input-field');
1147
+ if (input) {
1148
+ input.value = val;
1149
+ }
1150
+ }
1151
+ }
1152
+
1153
+ // Custom Textarea Component
1154
+ class CustomTextarea extends HTMLElement {
1155
+ constructor() {
1156
+ super();
1157
+ this.attachShadow({ mode: 'open' });
1158
+ this.value = '';
1159
+ }
1160
+
1161
+ static get observedAttributes() {
1162
+ return ['placeholder', 'name', 'required', 'rows', 'value'];
1163
+ }
1164
+
1165
+ connectedCallback() {
1166
+ this.render();
1167
+ }
1168
+
1169
+ attributeChangedCallback() {
1170
+ this.render();
1171
+ }
1172
+
1173
+ render() {
1174
+ const placeholder = this.getAttribute('placeholder') || '';
1175
+ const name = this.getAttribute('name') || '';
1176
+ const required = this.hasAttribute('required');
1177
+ const rows = this.getAttribute('rows') || '4';
1178
+
1179
+ this.shadowRoot.innerHTML = `
1180
+ <style>
1181
+ :host {
1182
+ display: block;
1183
+ width: 100%;
1184
+ }
1185
+
1186
+ .textarea-wrapper {
1187
+ position: relative;
1188
+ width: 100%;
1189
+ }
1190
+
1191
+ .textarea-field {
1192
+ width: 100%;
1193
+ min-height: 120px;
1194
+ padding: 14px 16px 14px 16px;
1195
+ background: rgba(255, 255, 255, 0.05);
1196
+ border: 1px solid rgba(255, 255, 255, 0.1);
1197
+ border-radius: 12px;
1198
+ color: rgb(var(--fg));
1199
+ font-size: 14px;
1200
+ outline: none;
1201
+ transition: all 0.2s ease;
1202
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
1203
+ resize: vertical;
1204
+ font-family: inherit;
1205
+ line-height: 1.5;
1206
+ }
1207
+
1208
+ .textarea-field:focus {
1209
+ border-color: rgba(var(--color-primary-500), 0.6);
1210
+ box-shadow: 0 0 0 3px rgba(var(--color-primary-500), 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
1211
+ background: rgba(255, 255, 255, 0.08);
1212
+ }
1213
+
1214
+ .textarea-field::placeholder {
1215
+ color: rgba(161, 161, 170, 0.6);
1216
+ transition: color 0.2s ease;
1217
+ }
1218
+
1219
+ .textarea-field:focus::placeholder {
1220
+ color: rgba(161, 161, 170, 0.4);
1221
+ }
1222
+
1223
+ .textarea-field.error {
1224
+ border-color: rgb(239, 68, 68);
1225
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
1226
+ }
1227
+
1228
+ .error-text {
1229
+ margin-top: 6px;
1230
+ font-size: 12px;
1231
+ color: rgb(239, 68, 68);
1232
+ opacity: 0;
1233
+ transform: translateY(-4px);
1234
+ transition: all 0.2s ease;
1235
+ }
1236
+
1237
+ .error-text.show {
1238
+ opacity: 1;
1239
+ transform: translateY(0);
1240
+ }
1241
+
1242
+ /* Light theme styles */
1243
+ :host(.light) .textarea-field {
1244
+ background: rgba(255, 255, 255, 0.8);
1245
+ border-color: rgba(15, 23, 42, 0.15);
1246
+ color: rgb(15, 23, 42);
1247
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
1248
+ }
1249
+
1250
+ :host(.light) .textarea-field:focus {
1251
+ background: rgba(255, 255, 255, 0.95);
1252
+ border-color: rgba(var(--color-primary-500), 0.6);
1253
+ box-shadow: 0 0 0 3px rgba(var(--color-primary-500), 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.5);
1254
+ }
1255
+
1256
+ :host(.light) .textarea-field::placeholder {
1257
+ color: rgba(100, 116, 139, 0.8);
1258
+ }
1259
+ </style>
1260
+
1261
+ <div class="textarea-wrapper">
1262
+ <textarea
1263
+ name="${name}"
1264
+ placeholder="${placeholder}"
1265
+ ${required ? 'required' : ''}
1266
+ rows="${rows}"
1267
+ class="textarea-field"
1268
+ ></textarea>
1269
+ <div class="error-text"></div>
1270
+ </div>
1271
+ `;
1272
+
1273
+ this.value = this.getAttribute('value') || '';
1274
+ this.setupEventListeners();
1275
+ this.updateTheme();
1276
+ }
1277
+
1278
+ setupEventListeners() {
1279
+ const textarea = this.shadowRoot.querySelector('.textarea-field');
1280
+ const errorText = this.shadowRoot.querySelector('.error-text');
1281
+
1282
+ if (textarea) {
1283
+ textarea.addEventListener('input', (e) => {
1284
+ this.value = e.target.value;
1285
+ this.dispatchEvent(new CustomEvent('input', { detail: { value: this.value } }));
1286
+ this.clearError();
1287
+ });
1288
+
1289
+ textarea.addEventListener('blur', () => {
1290
+ this.dispatchEvent(new CustomEvent('blur', { detail: { value: this.value } }));
1291
+ });
1292
+
1293
+ textarea.addEventListener('focus', () => {
1294
+ this.dispatchEvent(new CustomEvent('focus', { detail: { value: this.value } }));
1295
+ });
1296
+ }
1297
+ }
1298
+
1299
+ updateTheme() {
1300
+ const html = document.documentElement;
1301
+ if (html.classList.contains('light')) {
1302
+ this.shadowRoot.host.classList.add('light');
1303
+ } else {
1304
+ this.shadowRoot.host.classList.remove('light');
1305
+ }
1306
+ }
1307
+
1308
+ setError(message) {
1309
+ const textarea = this.shadowRoot.querySelector('.textarea-field');
1310
+ const errorText = this.shadowRoot.querySelector('.error-text');
1311
+
1312
+ if (textarea) textarea.classList.add('error');
1313
+ if (errorText) {
1314
+ errorText.textContent = message;
1315
+ errorText.classList.add('show');
1316
+ }
1317
+ }
1318
+
1319
+ clearError() {
1320
+ const textarea = this.shadowRoot.querySelector('.textarea-field');
1321
+ const errorText = this.shadowRoot.querySelector('.error-text');
1322
+
1323
+ if (textarea) textarea.classList.remove('error');
1324
+ if (errorText) {
1325
+ errorText.classList.remove('show');
1326
+ }
1327
+ }
1328
+
1329
+ get value() {
1330
+ return this._value || '';
1331
+ }
1332
+
1333
+ set value(val) {
1334
+ this._value = val;
1335
+ const textarea = this.shadowRoot?.querySelector('.textarea-field');
1336
+ if (textarea) {
1337
+ textarea.value = val;
1338
+ }
1339
+ }
1340
+ }
1341
+
1342
+ // Register the custom elements
1343
+ customElements.define('custom-input', CustomInput);
1344
+ customElements.define('custom-textarea', CustomTextarea);
1345
+
1346
+ // Theme change observer
1347
+ const themeObserver = new MutationObserver(() => {
1348
+ document.querySelectorAll('custom-input, custom-textarea').forEach(el => {
1349
+ if (el.updateTheme) el.updateTheme();
1350
+ });
1351
+ });
1352
+
1353
+ themeObserver.observe(document.documentElement, {
1354
+ attributes: true,
1355
+ attributeFilter: ['class']
1356
+ });
style.css CHANGED
@@ -86,6 +86,42 @@ html.light .chip {
86
  }
87
  /* Enhanced Form Styling for custom components */
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  /* Ensure web components inherit dark mode */
90
  html.light custom-input,
91
  html.light custom-textarea {
 
86
  }
87
  /* Enhanced Form Styling for custom components */
88
 
89
+ /* Form labels positioning */
90
+ .label {
91
+ @apply block text-sm font-medium text-zinc-300 mb-3 mt-1;
92
+ }
93
+
94
+ /* Custom component containers */
95
+ custom-input, custom-textarea {
96
+ margin-bottom: 6px;
97
+ }
98
+
99
+ /* Enhanced input focus states for consistency */
100
+ :host custom-input:focus-within .input-field,
101
+ :host custom-textarea:focus-within .textarea-field {
102
+ transform: translateY(-1px);
103
+ }
104
+
105
+ /* Smooth transitions for form interactions */
106
+ custom-input, custom-textarea {
107
+ transition: all 0.2s ease;
108
+ }
109
+
110
+ /* Error state animations */
111
+ custom-input[error], custom-textarea[error] {
112
+ animation: shake 0.3s ease-in-out;
113
+ }
114
+
115
+ @keyframes shake {
116
+ 0%, 100% { transform: translateX(0); }
117
+ 25% { transform: translateX(-4px); }
118
+ 75% { transform: translateX(4px); }
119
+ }
120
+
121
+ /* Light theme label colors */
122
+ html.light .label {
123
+ color: rgb(71, 85, 105);
124
+ }
125
  /* Ensure web components inherit dark mode */
126
  html.light custom-input,
127
  html.light custom-textarea {