chore: update dependencies and add new mobile-first features page with interactive demos
This commit is contained in:
		
							
								
								
									
										2007
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2007
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										41
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								package.json
									
									
									
									
									
								
							| @@ -10,33 +10,34 @@ | ||||
|     "preview": "vite preview" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "react": "^19.1.1", | ||||
|     "react-dom": "^19.1.1", | ||||
|     "react-router-dom": "^7.1.3", | ||||
|     "framer-motion": "^11.18.0", | ||||
|     "@headlessui/react": "^2.1.0", | ||||
|     "@tailwindcss/forms": "^0.5.3", | ||||
|     "@tailwindcss/postcss": "^4.1.7", | ||||
|     "@types/node": "^20.10.8", | ||||
|     "@types/react": "^18.2.47", | ||||
|     "@types/react-dom": "^18.2.18", | ||||
|     "@types/react-router-dom": "^5.3.3", | ||||
|     "clsx": "^2.1.1", | ||||
|     "react-type-animation": "^3.2.0", | ||||
|     "cobe": "^0.6.5", | ||||
|     "framer-motion": "^10.15.0", | ||||
|     "react": "^18.2.0", | ||||
|     "react-countup": "^6.5.3", | ||||
|     "react-dom": "^18.2.0", | ||||
|     "react-icons": "^5.5.0", | ||||
|     "cobe": "^0.6.4" | ||||
|     "react-router-dom": "^7.9.4", | ||||
|     "react-type-animation": "^3.2.0", | ||||
|     "tailwind-merge": "^3.3.1", | ||||
|     "tailwindcss": "^4.1.7", | ||||
|     "typescript": "^5.3.3", | ||||
|     "use-debounce": "^10.0.6" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@eslint/js": "^9.36.0", | ||||
|     "@tailwindcss/forms": "^0.5.9", | ||||
|     "@tailwindcss/postcss": "^4.1.7", | ||||
|     "@types/node": "^24.6.0", | ||||
|     "@types/react": "^19.1.16", | ||||
|     "@types/react-dom": "^19.1.9", | ||||
|     "@vitejs/plugin-react": "^5.0.4", | ||||
|     "autoprefixer": "^10.4.20", | ||||
|     "eslint": "^9.36.0", | ||||
|     "eslint-plugin-react-hooks": "^5.2.0", | ||||
|     "eslint-plugin-react-refresh": "^0.4.22", | ||||
|     "globals": "^16.4.0", | ||||
|     "postcss": "^8.5.1", | ||||
|     "tailwindcss": "^4.1.7", | ||||
|     "typescript": "~5.9.3", | ||||
|     "typescript-eslint": "^8.45.0", | ||||
|     "eslint": "^8.56.0", | ||||
|     "prettier": "^3.3.2", | ||||
|     "prettier-plugin-tailwindcss": "^0.6.11", | ||||
|     "sharp": "0.33.1", | ||||
|     "vite": "^7.1.7" | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										231
									
								
								public/images/phone-frame.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								public/images/phone-frame.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,231 @@ | ||||
| <svg width="366" height="729" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
|   <g mask="url(#mask)"> | ||||
|     <g filter="url(#a)"> | ||||
|       <path | ||||
|         d="M363.315 64.213C363.315 22.99 341.312 1 300.092 1H66.751C25.53 1 3.528 22.99 3.528 64.213v44.68l-.857.143A2 2 0 0 0 1 111.009v24.611a2 2 0 0 0 1.671 1.973l.95.158a2.26 2.26 0 0 1-.093.236v26.173c.212.1.398.296.541.643l-1.398.233A2 2 0 0 0 1 167.009v47.611a2 2 0 0 0 1.671 1.973l1.368.228c-.139.319-.314.533-.511.653v16.637c.221.104.414.313.56.689l-1.417.236A2 2 0 0 0 1 237.009v47.611a2 2 0 0 0 1.671 1.973l1.347.225c-.135.294-.302.493-.49.607v377.681c0 41.213 22 63.208 63.223 63.208h95.074c.947-.504 2.717-.843 4.745-.843l.141.001h.194l.086-.001 33.704.005c1.849.043 3.442.37 4.323.838h95.074c41.222 0 63.223-21.999 63.223-63.212v-394.63c-.259-.275-.48-.796-.63-1.47l-.011-.133 1.655-.276A2 2 0 0 0 366 266.62v-77.611a2 2 0 0 0-1.671-1.973l-1.712-.285c.148-.839.396-1.491.698-1.811V64.213Z" | ||||
|         fill="url(#b)" /> | ||||
|       <path | ||||
|         d="M363.315 64.213C363.315 22.99 341.312 1 300.092 1H66.751C25.53 1 3.528 22.99 3.528 64.213v44.68l-.857.143A2 2 0 0 0 1 111.009v24.611a2 2 0 0 0 1.671 1.973l.95.158a2.26 2.26 0 0 1-.093.236v26.173c.212.1.398.296.541.643l-1.398.233A2 2 0 0 0 1 167.009v47.611a2 2 0 0 0 1.671 1.973l1.368.228c-.139.319-.314.533-.511.653v16.637c.221.104.414.313.56.689l-1.417.236A2 2 0 0 0 1 237.009v47.611a2 2 0 0 0 1.671 1.973l1.347.225c-.135.294-.302.493-.49.607v377.681c0 41.213 22 63.208 63.223 63.208h95.074c.947-.504 2.717-.843 4.745-.843l.141.001h.194l.086-.001 33.704.005c1.849.043 3.442.37 4.323.838h95.074c41.222 0 63.223-21.999 63.223-63.212v-394.63c-.259-.275-.48-.796-.63-1.47l-.011-.133 1.655-.276A2 2 0 0 0 366 266.62v-77.611a2 2 0 0 0-1.671-1.973l-1.712-.285c.148-.839.396-1.491.698-1.811V64.213Z" | ||||
|         fill="url(#c)" /> | ||||
|     </g> | ||||
|     <g filter="url(#d)"> | ||||
|       <path | ||||
|         d="M5 133.772v-21.15c0-1.359-.54-2.661-1.5-3.622-.844-.073-2.496.257-2.496 2.157v24.562c.406 2.023 2.605 2.023 2.605 2.023A6.363 6.363 0 0 0 5 133.772Z" | ||||
|         fill="url(#e)" /> | ||||
|       <path | ||||
|         d="M5 133.772v-21.15c0-1.359-.54-2.661-1.5-3.622-.844-.073-2.496.257-2.496 2.157v24.562c.406 2.023 2.605 2.023 2.605 2.023A6.363 6.363 0 0 0 5 133.772Z" | ||||
|         fill="url(#f)" fill-opacity=".1" /> | ||||
|     </g> | ||||
|     <g filter="url(#g)"> | ||||
|       <path | ||||
|         d="M5 213.772v-46.15c0-1.359-.54-2.661-1.5-3.622-.844-.073-2.496.257-2.496 2.157v49.562c.406 2.023 2.605 2.023 2.605 2.023A6.363 6.363 0 0 0 5 213.772Z" | ||||
|         fill="url(#h)" /> | ||||
|       <path | ||||
|         d="M5 213.772v-46.15c0-1.359-.54-2.661-1.5-3.622-.844-.073-2.496.257-2.496 2.157v49.562c.406 2.023 2.605 2.023 2.605 2.023A6.363 6.363 0 0 0 5 213.772Z" | ||||
|         fill="url(#i)" fill-opacity=".1" /> | ||||
|     </g> | ||||
|     <g filter="url(#j)"> | ||||
|       <path | ||||
|         d="M5 283.772v-46.15c0-1.359-.54-2.661-1.5-3.622-.844-.073-2.496.257-2.496 2.157v49.562c.406 2.023 2.605 2.023 2.605 2.023A6.363 6.363 0 0 0 5 283.772Z" | ||||
|         fill="url(#k)" /> | ||||
|       <path | ||||
|         d="M5 283.772v-46.15c0-1.359-.54-2.661-1.5-3.622-.844-.073-2.496.257-2.496 2.157v49.562c.406 2.023 2.605 2.023 2.605 2.023A6.363 6.363 0 0 0 5 283.772Z" | ||||
|         fill="url(#l)" fill-opacity=".1" /> | ||||
|     </g> | ||||
|     <g filter="url(#m)"> | ||||
|       <path | ||||
|         d="M362.004 266.772v-78.15a5.12 5.12 0 0 1 1.5-3.622c.844-.073 2.496.257 2.496 2.157v81.562c-.406 2.023-2.605 2.023-2.605 2.023a6.359 6.359 0 0 1-1.391-3.97Z" | ||||
|         fill="url(#n)" /> | ||||
|       <path | ||||
|         d="M362.004 266.772v-78.15a5.12 5.12 0 0 1 1.5-3.622c.844-.073 2.496.257 2.496 2.157v81.562c-.406 2.023-2.605 2.023-2.605 2.023a6.359 6.359 0 0 1-1.391-3.97Z" | ||||
|         fill="url(#o)" fill-opacity=".1" /> | ||||
|     </g> | ||||
|     <path | ||||
|       d="M305 14.5H59c-24.577 0-44.5 19.923-44.5 44.5v615c0 23.472 19.028 42.5 42.5 42.5h250c23.472 0 42.5-19.028 42.5-42.5V59c0-24.577-19.923-44.5-44.5-44.5Z" | ||||
|       stroke="url(#p)" stroke-opacity=".5" /> | ||||
|     <g filter="url(#q)" shape-rendering="crispEdges"> | ||||
|       <path | ||||
|         d="M16 59c0-23.748 19.252-43 43-43h246c23.748 0 43 19.252 43 43v615c0 23.196-18.804 42-42 42H58c-23.196 0-42-18.804-42-42V59Z" | ||||
|         fill="url(#r)" fill-opacity=".3" /> | ||||
|       <path | ||||
|         d="M305 15.5H59c-24.024 0-43.5 19.476-43.5 43.5v615c0 23.472 19.028 42.5 42.5 42.5h248c23.472 0 42.5-19.028 42.5-42.5V59c0-24.024-19.476-43.5-43.5-43.5Z" | ||||
|         stroke="#000" stroke-opacity=".07" /> | ||||
|     </g> | ||||
|     <g filter="url(#s)"> | ||||
|       <rect x="154" y="29" width="56" height="5" rx="2.5" fill="#D4D4D4" /> | ||||
|     </g> | ||||
|   </g> | ||||
|   <defs> | ||||
|     <mask id="mask"> | ||||
|       <rect width="366" height="729" fill="#fff" /> | ||||
|       <path fill-rule="evenodd" clip-rule="evenodd" | ||||
|         d="M89.728 24a4.213 4.213 0 0 1 4.213 4.212v2.527c0 10.235 8.3 18.532 18.539 18.532h139.04c10.239 0 18.539-8.297 18.539-18.532v-2.527A4.212 4.212 0 0 1 274.272 24h32.864C325.286 24 340 38.71 340 56.853v618.295c0 18.144-14.714 32.853-32.864 32.853H56.864c-18.15 0-32.864-14.709-32.864-32.853V56.853C24 38.709 38.714 24 56.864 24h32.864Z" | ||||
|         fill="#000" /> | ||||
|     </mask> | ||||
|     <linearGradient id="e" x1="1.004" y1="123.367" x2="5" y2="123.367" gradientUnits="userSpaceOnUse"> | ||||
|       <stop stop-color="#D4D4D4" /> | ||||
|       <stop offset="1" stop-color="#E6E6E6" /> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="f" x1="3.002" y1="108.991" x2="3.002" y2="116.75" gradientUnits="userSpaceOnUse"> | ||||
|       <stop stop-color="#171717" /> | ||||
|       <stop offset=".783" stop-color="#171717" stop-opacity="0" /> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="h" x1="1.004" y1="190.867" x2="5" y2="190.867" gradientUnits="userSpaceOnUse"> | ||||
|       <stop stop-color="#D4D4D4" /> | ||||
|       <stop offset="1" stop-color="#E6E6E6" /> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="i" x1="3.002" y1="163.991" x2="3.002" y2="178.497" gradientUnits="userSpaceOnUse"> | ||||
|       <stop stop-color="#171717" /> | ||||
|       <stop offset=".783" stop-color="#171717" stop-opacity="0" /> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="k" x1="1.004" y1="260.867" x2="5" y2="260.867" gradientUnits="userSpaceOnUse"> | ||||
|       <stop stop-color="#D4D4D4" /> | ||||
|       <stop offset="1" stop-color="#E6E6E6" /> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="l" x1="3.002" y1="233.991" x2="3.002" y2="248.497" gradientUnits="userSpaceOnUse"> | ||||
|       <stop stop-color="#171717" /> | ||||
|       <stop offset=".783" stop-color="#171717" stop-opacity="0" /> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="n" x1="362.004" y1="226.25" x2="366" y2="226.25" gradientUnits="userSpaceOnUse"> | ||||
|       <stop offset=".124" stop-color="#E6E6E6" /> | ||||
|       <stop offset="1" stop-color="#D4D4D4" /> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="o" x1="364.002" y1="184.991" x2="364.002" y2="208.134" gradientUnits="userSpaceOnUse"> | ||||
|       <stop stop-color="#171717" /> | ||||
|       <stop offset=".783" stop-color="#171717" stop-opacity="0" /> | ||||
|     </linearGradient> | ||||
|     <linearGradient id="p" x1="182" y1="15" x2="182" y2="716" gradientUnits="userSpaceOnUse"> | ||||
|       <stop stop-color="#fff" /> | ||||
|       <stop offset=".381" stop-color="#fff" stop-opacity="0" /> | ||||
|     </linearGradient> | ||||
|     <filter id="a" x="-1" y="-1" width="367" height="730.314" filterUnits="userSpaceOnUse" | ||||
|       color-interpolation-filters="sRGB"> | ||||
|       <feFlood flood-opacity="0" result="BackgroundImageFix" /> | ||||
|       <feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dy="-2" /> | ||||
|       <feGaussianBlur stdDeviation="1.5" /> | ||||
|       <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /> | ||||
|       <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" /> | ||||
|       <feBlend in2="shape" result="effect1_innerShadow_104_2007" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dx="-2" /> | ||||
|       <feGaussianBlur stdDeviation="2" /> | ||||
|       <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /> | ||||
|       <feColorMatrix values="0 0 0 0 0.0901961 0 0 0 0 0.0901961 0 0 0 0 0.0901961 0 0 0 0.17 0" /> | ||||
|       <feBlend in2="effect1_innerShadow_104_2007" result="effect2_innerShadow_104_2007" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dy="2" /> | ||||
|       <feGaussianBlur stdDeviation=".5" /> | ||||
|       <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /> | ||||
|       <feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.6 0" /> | ||||
|       <feBlend in2="effect2_innerShadow_104_2007" result="effect3_innerShadow_104_2007" /> | ||||
|     </filter> | ||||
|     <filter id="d" x="1.004" y="108.991" width="4.996" height="28.751" filterUnits="userSpaceOnUse" | ||||
|       color-interpolation-filters="sRGB"> | ||||
|       <feFlood flood-opacity="0" result="BackgroundImageFix" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dx="1" /> | ||||
|       <feComposite in2="hardAlpha" operator="out" /> | ||||
|       <feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0" /> | ||||
|       <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_104_2007" /> | ||||
|       <feBlend in="SourceGraphic" in2="effect1_dropShadow_104_2007" result="shape" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dx="-1" /> | ||||
|       <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /> | ||||
|       <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0" /> | ||||
|       <feBlend in2="shape" result="effect2_innerShadow_104_2007" /> | ||||
|     </filter> | ||||
|     <filter id="g" x="1.004" y="163.991" width="4.996" height="53.751" filterUnits="userSpaceOnUse" | ||||
|       color-interpolation-filters="sRGB"> | ||||
|       <feFlood flood-opacity="0" result="BackgroundImageFix" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dx="1" /> | ||||
|       <feComposite in2="hardAlpha" operator="out" /> | ||||
|       <feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0" /> | ||||
|       <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_104_2007" /> | ||||
|       <feBlend in="SourceGraphic" in2="effect1_dropShadow_104_2007" result="shape" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dx="-1" /> | ||||
|       <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /> | ||||
|       <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0" /> | ||||
|       <feBlend in2="shape" result="effect2_innerShadow_104_2007" /> | ||||
|     </filter> | ||||
|     <filter id="j" x="1.004" y="233.991" width="4.996" height="53.751" filterUnits="userSpaceOnUse" | ||||
|       color-interpolation-filters="sRGB"> | ||||
|       <feFlood flood-opacity="0" result="BackgroundImageFix" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dx="1" /> | ||||
|       <feComposite in2="hardAlpha" operator="out" /> | ||||
|       <feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0" /> | ||||
|       <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_104_2007" /> | ||||
|       <feBlend in="SourceGraphic" in2="effect1_dropShadow_104_2007" result="shape" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dx="-1" /> | ||||
|       <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /> | ||||
|       <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0" /> | ||||
|       <feBlend in2="shape" result="effect2_innerShadow_104_2007" /> | ||||
|     </filter> | ||||
|     <filter id="m" x="361.004" y="184.991" width="4.996" height="85.751" filterUnits="userSpaceOnUse" | ||||
|       color-interpolation-filters="sRGB"> | ||||
|       <feFlood flood-opacity="0" result="BackgroundImageFix" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dx="-1" /> | ||||
|       <feComposite in2="hardAlpha" operator="out" /> | ||||
|       <feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0" /> | ||||
|       <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_104_2007" /> | ||||
|       <feBlend in="SourceGraphic" in2="effect1_dropShadow_104_2007" result="shape" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dx="1" /> | ||||
|       <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /> | ||||
|       <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0" /> | ||||
|       <feBlend in2="shape" result="effect2_innerShadow_104_2007" /> | ||||
|     </filter> | ||||
|     <filter id="q" x="15" y="15" width="334" height="703" filterUnits="userSpaceOnUse" | ||||
|       color-interpolation-filters="sRGB"> | ||||
|       <feFlood flood-opacity="0" result="BackgroundImageFix" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dy="1" /> | ||||
|       <feComposite in2="hardAlpha" operator="out" /> | ||||
|       <feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0" /> | ||||
|       <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_104_2007" /> | ||||
|       <feBlend in="SourceGraphic" in2="effect1_dropShadow_104_2007" result="shape" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dy="1" /> | ||||
|       <feGaussianBlur stdDeviation="2.5" /> | ||||
|       <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /> | ||||
|       <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.03 0" /> | ||||
|       <feBlend in2="shape" result="effect2_innerShadow_104_2007" /> | ||||
|     </filter> | ||||
|     <filter id="s" x="154" y="29" width="56" height="6" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> | ||||
|       <feFlood flood-opacity="0" result="BackgroundImageFix" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dy="1" /> | ||||
|       <feComposite in2="hardAlpha" operator="out" /> | ||||
|       <feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.3 0" /> | ||||
|       <feBlend in2="BackgroundImageFix" result="effect1_dropShadow_104_2007" /> | ||||
|       <feBlend in="SourceGraphic" in2="effect1_dropShadow_104_2007" result="shape" /> | ||||
|       <feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" /> | ||||
|       <feOffset dy="1" /> | ||||
|       <feGaussianBlur stdDeviation=".5" /> | ||||
|       <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" /> | ||||
|       <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0" /> | ||||
|       <feBlend in2="shape" result="effect2_innerShadow_104_2007" /> | ||||
|     </filter> | ||||
|     <radialGradient id="b" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" | ||||
|       gradientTransform="matrix(0 727 -642 0 184 1)"> | ||||
|       <stop stop-color="#FAFAFA" /> | ||||
|       <stop offset="1" stop-color="#E6E6E6" /> | ||||
|     </radialGradient> | ||||
|     <radialGradient id="c" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" | ||||
|       gradientTransform="matrix(0 319 -295.5 0 183.5 1)"> | ||||
|       <stop stop-color="#fff" /> | ||||
|       <stop offset=".533" stop-color="#fff" stop-opacity="0" /> | ||||
|     </radialGradient> | ||||
|     <radialGradient id="r" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" | ||||
|       gradientTransform="matrix(0 689 -326.783 0 182 27)"> | ||||
|       <stop offset=".319" stop-color="#D4D4D4" /> | ||||
|       <stop offset="1" stop-color="#E6E6E6" /> | ||||
|     </radialGradient> | ||||
|   </defs> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/images/phoneframe.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/images/phoneframe.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 537 KiB | 
							
								
								
									
										22
									
								
								src/components/PhoneFrame.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/components/PhoneFrame.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| import clsx from 'clsx' | ||||
|  | ||||
| import phoneFrame from '@/images/phone-frame.svg' | ||||
|  | ||||
| export function PhoneFrame({ | ||||
|   className, | ||||
|   children, | ||||
|   ...props | ||||
| }: React.ComponentPropsWithoutRef<'div'>) { | ||||
|   return ( | ||||
|     <div className={clsx('relative aspect-[366/729]', className)} {...props}> | ||||
|       <img | ||||
|         src={phoneFrame} | ||||
|         alt="" | ||||
|         className="pointer-events-none absolute inset-0 w-full h-full" | ||||
|       /> | ||||
|       <div className="absolute inset-x-[6.3%] top-[3.15%] bottom-[2.75%] rounded-3xl overflow-y-auto bg-gray-900"> | ||||
|         {children} | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
							
								
								
									
										147
									
								
								src/components/Texts.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								src/components/Texts.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | ||||
| 'use client' | ||||
|  | ||||
| import React from 'react' | ||||
| import { cn } from '@/lib/utils' | ||||
|  | ||||
| const colorVariants = { | ||||
|   primary: 'text-gray-900', | ||||
|   secondary: 'text-gray-600', | ||||
|   light: 'text-gray-50', | ||||
|   accent: 'text-cyan-500', | ||||
|   white: 'text-white', | ||||
|   dark: 'text-gray-950', | ||||
|   tertiary: 'text-gray-700', | ||||
|   lightSecondary: 'text-gray-300', | ||||
| } as const | ||||
|  | ||||
| type TextOwnProps = { | ||||
|   color?: keyof typeof colorVariants | ||||
|   className?: string | ||||
| } | ||||
|  | ||||
| // Polymorphic helpers | ||||
| type PolymorphicProps<E extends React.ElementType, P> = P & { | ||||
|   as?: E | ||||
| } & Omit<React.ComponentPropsWithoutRef<E>, keyof P | 'as'> | ||||
|  | ||||
| const createTextComponent = <DefaultElement extends React.ElementType>( | ||||
|   defaultElement: DefaultElement, | ||||
|   defaultClassName: string | ||||
| ) => { | ||||
|   type Props<E extends React.ElementType = DefaultElement> = PolymorphicProps< | ||||
|     E, | ||||
|     TextOwnProps | ||||
|   > | ||||
|  | ||||
|   function Text<E extends React.ElementType = DefaultElement>({ | ||||
|     as, | ||||
|     color = 'primary', | ||||
|     className, | ||||
|     children, | ||||
|     ...props | ||||
|   }: Props<E>) { | ||||
|     const Tag = (as || defaultElement) as React.ElementType | ||||
|     return ( | ||||
|       <Tag | ||||
|         className={cn(defaultClassName, colorVariants[color], className)} | ||||
|         {...props} | ||||
|       > | ||||
|         {children} | ||||
|       </Tag> | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   ;(Text as any).displayName = `Text(${typeof defaultElement === 'string' ? defaultElement : 'Component' | ||||
|   })` | ||||
|   return Text | ||||
| } | ||||
|  | ||||
| // Exports based on your tailwind.css and the example | ||||
| export const H1 = createTextComponent( | ||||
|   'h1', | ||||
|   'text-6xl lg:text-7xl font-medium leading-tight tracking-tight' | ||||
| ) | ||||
| export const H2 = createTextComponent( | ||||
|   'h2', | ||||
|   'text-4xl lg:text-6xl font-medium leading-tight tracking-tight' | ||||
| ) | ||||
| export const H3 = createTextComponent( | ||||
|   'h3', | ||||
|   'text-3xl lg:text-5xl font-medium leading-tight tracking-tight' | ||||
| ) | ||||
| export const H4 = createTextComponent( | ||||
|   'h4', | ||||
|   'text-2xl lg:text-4xl font-medium leading-snug tracking-tight' | ||||
| ) | ||||
| export const P = createTextComponent( | ||||
|   'p', | ||||
|   'text-base lg:text-lg leading-relaxed' | ||||
| ) | ||||
| export const Small = createTextComponent( | ||||
|   'small', | ||||
|   'text-sm font-medium leading-normal tracking-normal' | ||||
| ) | ||||
| export const Subtle = createTextComponent( | ||||
|   'p', | ||||
|   'text-sm leading-normal tracking-normal text-gray-500' | ||||
| ) | ||||
| export const H5 = createTextComponent( | ||||
|   'h5', | ||||
|   'text-xl lg:text-2xl font-semibold leading-snug tracking-tight' | ||||
| ) | ||||
| export const Eyebrow = createTextComponent( | ||||
|   'h2', | ||||
|   'text-base/7 font-semibold tracking-wide' | ||||
| ) | ||||
| export const SectionHeader = createTextComponent( | ||||
|   'p', | ||||
|   'text-3xl lg:text-4xl font-medium leading-tight tracking-tight' | ||||
| ) | ||||
| export const CardEyebrow = createTextComponent( | ||||
|   'h3', | ||||
|   'text-sm/4 font-semibold tracking-wide' | ||||
| ) | ||||
| export const CardTitle = createTextComponent( | ||||
|   'p', | ||||
|   'text-lg font-medium leading-snug tracking-tight' | ||||
| ) | ||||
| export const CardDescription = createTextComponent( | ||||
|   'p', | ||||
|   'text-sm/6 leading-normal tracking-normal' | ||||
| ) | ||||
| export const FeatureTitle = createTextComponent( | ||||
|   'h3', | ||||
|   'text-lg font-semibold leading-snug tracking-tight' | ||||
| ) | ||||
| export const FeatureDescription = createTextComponent( | ||||
|   'p', | ||||
|   'text-sm leading-normal tracking-normal' | ||||
| ) | ||||
| export const MobileFeatureTitle = createTextComponent( | ||||
|   'h3', | ||||
|   'text-sm font-semibold sm:text-lg leading-snug tracking-tight' | ||||
| ) | ||||
| export const SecondaryFeatureTitle = createTextComponent( | ||||
|   'h3', | ||||
|   'text-base font-semibold leading-snug tracking-tight' | ||||
| ) | ||||
| export const Question = createTextComponent( | ||||
|   'h3', | ||||
|   'text-lg/6 font-semibold tracking-tight' | ||||
| ) | ||||
| export const Answer = createTextComponent( | ||||
|   'p', | ||||
|   'mt-4 text-sm leading-normal tracking-normal' | ||||
| ) | ||||
| export const PageHeader = createTextComponent( | ||||
|   'h2', | ||||
|   'text-5xl lg:text-6xl font-medium leading-tight tracking-tight' | ||||
| ) | ||||
| export const DownloadCardTitle = createTextComponent( | ||||
|   'dt', | ||||
|   'text-base/7 font-semibold tracking-wide' | ||||
| ) | ||||
| export const DownloadCardDescription = createTextComponent( | ||||
|   'dd', | ||||
|   'text-base/7 leading-normal tracking-normal' | ||||
| ) | ||||
							
								
								
									
										
											BIN
										
									
								
								src/images/linux copy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/images/linux copy.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 43 KiB | 
							
								
								
									
										6
									
								
								src/lib/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/lib/utils.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| import { type ClassValue, clsx } from 'clsx' | ||||
| import { twMerge } from 'tailwind-merge' | ||||
|  | ||||
| export function cn(...inputs: ClassValue[]) { | ||||
|   return twMerge(clsx(inputs)) | ||||
| } | ||||
							
								
								
									
										109
									
								
								src/pages/network/AppScreen.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/pages/network/AppScreen.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| import { forwardRef } from 'react' | ||||
| import clsx from 'clsx' | ||||
|  | ||||
| function Logo(props: React.ComponentPropsWithoutRef<'svg'>) { | ||||
|   return ( | ||||
|     <svg viewBox="0 0 79 24" fill="none" aria-hidden="true" {...props}> | ||||
|       <path | ||||
|         d="M12 24C5.373 24 0 18.627 0 12S5.373 0 12 0s12 5.373 12 12-5.373 12-12 12ZM2.4 12a9.004 9.004 0 0 0 6.055 8.507c1.565.542 2.945-.85 2.945-2.507V6c0-1.657-1.38-3.049-2.945-2.507A9.004 9.004 0 0 0 2.4 12Z" | ||||
|         fill="#06B6D4" | ||||
|       /> | ||||
|       <path | ||||
|         d="M33.004 17V6.818h3.818c.783 0 1.439.146 1.97.438.533.291.935.692 1.207 1.203.275.507.413 1.084.413 1.73 0 .653-.138 1.233-.413 1.74a2.948 2.948 0 0 1-1.218 1.198c-.537.288-1.198.433-1.983.433h-2.531v-1.517h2.282c.457 0 .832-.08 1.124-.238.291-.16.507-.378.646-.657.142-.278.214-.598.214-.96 0-.36-.072-.679-.214-.954a1.452 1.452 0 0 0-.651-.641c-.292-.156-.668-.234-1.129-.234h-1.69V17h-1.845Zm12.152.15c-.746 0-1.392-.165-1.939-.493a3.343 3.343 0 0 1-1.273-1.377c-.298-.59-.447-1.28-.447-2.068 0-.79.15-1.48.447-2.073a3.335 3.335 0 0 1 1.273-1.383c.547-.328 1.193-.492 1.94-.492.745 0 1.391.164 1.938.492.547.329.97.79 1.268 1.383.301.593.452 1.284.452 2.073 0 .789-.15 1.478-.452 2.068a3.309 3.309 0 0 1-1.268 1.377c-.547.328-1.193.492-1.939.492Zm.01-1.443c.404 0 .742-.11 1.014-.333.272-.225.474-.527.607-.905.136-.377.204-.798.204-1.262 0-.468-.068-.89-.204-1.268a2.007 2.007 0 0 0-.607-.91c-.272-.225-.61-.338-1.014-.338-.414 0-.759.113-1.034.338a2.041 2.041 0 0 0-.612.91 3.81 3.81 0 0 0-.198 1.268c0 .464.066.885.198 1.262.136.378.34.68.612.905.275.222.62.333 1.034.333Zm8.508 1.442c-.763 0-1.417-.167-1.964-.502a3.352 3.352 0 0 1-1.258-1.387c-.292-.593-.437-1.276-.437-2.048 0-.776.149-1.46.447-2.054a3.34 3.34 0 0 1 1.263-1.392c.547-.334 1.193-.502 1.939-.502.62 0 1.168.115 1.645.343.48.226.864.546 1.149.96.285.41.447.891.487 1.441h-1.72a1.644 1.644 0 0 0-.497-.92c-.259-.248-.605-.372-1.04-.372-.367 0-.69.1-.969.298-.278.196-.495.478-.651.845-.153.368-.229.81-.229 1.323 0 .52.076.968.229 1.342.152.371.366.658.641.86.279.2.605.298.98.298.265 0 .502-.05.71-.149.213-.102.39-.25.532-.442.143-.192.24-.426.294-.701h1.72a2.999 2.999 0 0 1-.477 1.437c-.275.414-.65.739-1.124.974-.474.232-1.03.348-1.67.348Zm6.39-2.545-.006-2.173h.289l2.744-3.067h2.103l-3.376 3.758h-.372l-1.383 1.482ZM58.422 17V6.818h1.8V17h-1.8Zm4.792 0-2.485-3.475 1.213-1.268L65.368 17h-2.153Zm6.245.15c-.766 0-1.427-.16-1.984-.478a3.233 3.233 0 0 1-1.278-1.362c-.298-.59-.447-1.285-.447-2.083 0-.786.149-1.475.447-2.069a3.384 3.384 0 0 1 1.263-1.392c.54-.334 1.175-.502 1.904-.502.47 0 .915.076 1.333.229.42.149.792.381 1.113.696.325.315.58.716.766 1.203.186.484.278 1.06.278 1.73v.552h-6.259v-1.213h4.534a1.935 1.935 0 0 0-.224-.92 1.625 1.625 0 0 0-.611-.641 1.719 1.719 0 0 0-.905-.234c-.368 0-.691.09-.97.269a1.848 1.848 0 0 0-.65.696c-.153.285-.231.598-.234.94v1.058c0 .444.08.825.243 1.144.163.315.39.556.681.726.292.165.634.248 1.025.248.261 0 .498-.036.71-.11.213-.075.397-.187.552-.332.156-.146.274-.327.353-.542l1.68.189a2.62 2.62 0 0 1-.606 1.163 2.958 2.958 0 0 1-1.133.766c-.461.179-.988.268-1.581.268Zm8.731-7.786v1.392h-4.39V9.364h4.39Zm-3.306-1.83h1.8v7.17c0 .241.036.427.109.556a.59.59 0 0 0 .298.258c.123.047.259.07.408.07.113 0 .215-.008.308-.025.096-.016.17-.031.219-.045l.303 1.407c-.096.034-.233.07-.412.11-.176.04-.392.063-.647.07a2.934 2.934 0 0 1-1.218-.204 1.895 1.895 0 0 1-.86-.706c-.209-.319-.311-.716-.308-1.194V7.534Z" | ||||
|         fill="#fff" | ||||
|       /> | ||||
|     </svg> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function MenuIcon(props: React.ComponentPropsWithoutRef<'svg'>) { | ||||
|   return ( | ||||
|     <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" {...props}> | ||||
|       <path | ||||
|         d="M5 6h14M5 18h14M5 12h14" | ||||
|         stroke="#fff" | ||||
|         strokeWidth="2" | ||||
|         strokeLinecap="round" | ||||
|         strokeLinejoin="round" | ||||
|       /> | ||||
|     </svg> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function UserIcon(props: React.ComponentPropsWithoutRef<'svg'>) { | ||||
|   return ( | ||||
|     <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" {...props}> | ||||
|       <path | ||||
|         d="M15 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM6.696 19h10.608c1.175 0 2.08-.935 1.532-1.897C18.028 15.69 16.187 14 12 14s-6.028 1.689-6.836 3.103C4.616 18.065 5.521 19 6.696 19Z" | ||||
|         stroke="#fff" | ||||
|         strokeWidth="2" | ||||
|         strokeLinecap="round" | ||||
|         strokeLinejoin="round" | ||||
|       /> | ||||
|     </svg> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export function AppScreen({ | ||||
|   children, | ||||
|   className, | ||||
|   ...props | ||||
| }: React.ComponentPropsWithoutRef<'div'>) { | ||||
|   return ( | ||||
|     <div className={clsx('flex flex-col', className)} {...props}> | ||||
|       <div className="flex justify-between px-4 pt-0"> | ||||
|         <MenuIcon className="h-6 w-6 flex-none" /> | ||||
|         <Logo className="h-6 flex-none" /> | ||||
|         <UserIcon className="h-6 w-6 flex-none" /> | ||||
|       </div> | ||||
|       {children} | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| AppScreen.Header = forwardRef< | ||||
|   React.ElementRef<'div'>, | ||||
|   { children: React.ReactNode } | ||||
| >(function AppScreenHeader({ children }, ref) { | ||||
|   return ( | ||||
|     <div ref={ref} className="mt-6 px-4 text-white"> | ||||
|       {children} | ||||
|     </div> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| AppScreen.Title = forwardRef< | ||||
|   React.ElementRef<'div'>, | ||||
|   { children: React.ReactNode } | ||||
| >(function AppScreenTitle({ children }, ref) { | ||||
|   return ( | ||||
|     <div ref={ref} className="text-2xl text-white"> | ||||
|       {children} | ||||
|     </div> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| AppScreen.Subtitle = forwardRef< | ||||
|   React.ElementRef<'div'>, | ||||
|   { children: React.ReactNode } | ||||
| >(function AppScreenSubtitle({ children }, ref) { | ||||
|   return ( | ||||
|     <div ref={ref} className="text-sm text-gray-500"> | ||||
|       {children} | ||||
|     </div> | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| AppScreen.Body = forwardRef< | ||||
|   React.ElementRef<'div'>, | ||||
|   { className?: string; children: React.ReactNode } | ||||
| >(function AppScreenBody({ children, className }, ref) { | ||||
|   return ( | ||||
|     <div | ||||
|       ref={ref} | ||||
|       className={clsx('mt-6 flex-auto rounded-t-2xl bg-white', className)} | ||||
|     > | ||||
|       {children} | ||||
|     </div> | ||||
|   ) | ||||
| }) | ||||
| @@ -1,27 +1,439 @@ | ||||
| import { Container } from '../../components/Container' | ||||
| 'use client' | ||||
|  | ||||
| import { Fragment, useEffect, useId, useRef, useState } from 'react' | ||||
| import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react' | ||||
| import clsx from 'clsx' | ||||
| import { | ||||
|   type MotionProps, | ||||
|   type Variant, | ||||
|   type Variants, | ||||
|   AnimatePresence, | ||||
|   motion, | ||||
| } from 'framer-motion' | ||||
| import { useDebouncedCallback } from 'use-debounce' | ||||
|  | ||||
| import { AppScreen } from './AppScreen' | ||||
| import { | ||||
|   Eyebrow, | ||||
|   FeatureDescription, | ||||
|   FeatureTitle, | ||||
|   MobileFeatureTitle, | ||||
|   P, | ||||
|   SectionHeader, | ||||
| } from '@/components/Texts' | ||||
| import { CircleBackground } from '@/components/CircleBackground' | ||||
| import { Container } from '@/components/Container' | ||||
|  | ||||
| import connectorImg from '@/images/connector.png' | ||||
| import peersImg from '@/images/peers.png' | ||||
| import settingImg from '@/images/setting.png' | ||||
| import { PhoneFrame } from '@/components/PhoneFrame' | ||||
|  | ||||
|  | ||||
| interface CustomAnimationProps { | ||||
|   isForwards: boolean | ||||
|   changeCount: number | ||||
| } | ||||
|  | ||||
| const features = [ | ||||
|   { | ||||
|     name: 'Mycelium Connector', | ||||
|     description: | ||||
|       "Start (and stop) your Mycelium connector to gain access to sites, apps, and workloads available exclusively on the Mycelium Network. View statistics around peers and traffic.", | ||||
|     icon: DeviceUserIcon, | ||||
|     screen: InviteScreen, | ||||
|   }, | ||||
|   { | ||||
|     name: 'Mycelium Peers', | ||||
|     description: | ||||
|       'Search and discover active peers on the Mycelium Network, or add your own.', | ||||
|     icon: DeviceNotificationIcon, | ||||
|     screen: StocksScreen, | ||||
|   }, | ||||
|   { | ||||
|     name: 'Network Setting', | ||||
|     description: | ||||
|       'Find version and network information and trigger light or dark mode.', | ||||
|     icon: DeviceTouchIcon, | ||||
|     screen: InvestScreen, | ||||
|   }, | ||||
| ] | ||||
|  | ||||
| function DeviceUserIcon(props: React.ComponentPropsWithoutRef<'svg'>) { | ||||
|   return ( | ||||
|     <svg viewBox="0 0 32 32" aria-hidden="true" {...props}> | ||||
|       <circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} /> | ||||
|       <path | ||||
|         fillRule="evenodd" | ||||
|         clipRule="evenodd" | ||||
|         d="M16 23a3 3 0 100-6 3 3 0 000 6zm-1 2a4 4 0 00-4 4v1a2 2 0 002 2h6a2 2 0 002-2v-1a4 4 0 00-4-4h-2z" | ||||
|         fill="#737373" | ||||
|       /> | ||||
|       <path | ||||
|         fillRule="evenodd" | ||||
|         clipRule="evenodd" | ||||
|         d="M5 4a4 4 0 014-4h14a4 4 0 014 4v24a4.002 4.002 0 01-3.01 3.877c-.535.136-.99-.325-.99-.877s.474-.98.959-1.244A2 2 0 0025 28V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9a2 2 0 00-2 2v24a2 2 0 001.041 1.756C8.525 30.02 9 30.448 9 31s-.455 1.013-.99.877A4.002 4.002 0 015 28V4z" | ||||
|         fill="#A3A3A3" | ||||
|       /> | ||||
|     </svg> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function DeviceNotificationIcon(props: React.ComponentPropsWithoutRef<'svg'>) { | ||||
|   return ( | ||||
|     <svg viewBox="0 0 32 32" aria-hidden="true" {...props}> | ||||
|       <circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} /> | ||||
|       <path | ||||
|         fillRule="evenodd" | ||||
|         clipRule="evenodd" | ||||
|         d="M9 0a4 4 0 00-4 4v24a4 4 0 004 4h14a4 4 0 004-4V4a4 4 0 00-4-4H9zm0 2a2 2 0 00-2 2v24a2 2 0 002 2h14a2 2 0 002-2V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9z" | ||||
|         fill="#A3A3A3" | ||||
|       /> | ||||
|       <path | ||||
|         d="M9 8a2 2 0 012-2h10a2 2 0 012 2v2a2 2 0 01-2 2H11a2 2 0 01-2-2V8z" | ||||
|         fill="#737373" | ||||
|       /> | ||||
|     </svg> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function DeviceTouchIcon(props: React.ComponentPropsWithoutRef<'svg'>) { | ||||
|   let id = useId() | ||||
|  | ||||
|   return ( | ||||
|     <svg viewBox="0 0 32 32" fill="none" aria-hidden="true" {...props}> | ||||
|       <defs> | ||||
|         <linearGradient | ||||
|           id={`${id}-gradient`} | ||||
|           x1={14} | ||||
|           y1={14.5} | ||||
|           x2={7} | ||||
|           y2={17} | ||||
|           gradientUnits="userSpaceOnUse" | ||||
|         > | ||||
|           <stop stopColor="#737373" /> | ||||
|           <stop offset={1} stopColor="#D4D4D4" stopOpacity={0} /> | ||||
|         </linearGradient> | ||||
|       </defs> | ||||
|       <circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} /> | ||||
|       <path | ||||
|         fillRule="evenodd" | ||||
|         clipRule="evenodd" | ||||
|         d="M5 4a4 4 0 014-4h14a4 4 0 014 4v13h-2V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9a2 2 0 00-2 2v24a2 2 0 002 2h4v2H9a4 4 0 01-4-4V4z" | ||||
|         fill="#A3A3A3" | ||||
|       /> | ||||
|       <path | ||||
|         d="M7 22c0-4.694 3.5-8 8-8" | ||||
|         stroke={`url(#${id}-gradient)`} | ||||
|         strokeWidth={2} | ||||
|         strokeLinecap="round" | ||||
|         strokeLinejoin="round" | ||||
|       /> | ||||
|       <path | ||||
|         d="M21 20l.217-5.513a1.431 1.431 0 00-2.85-.226L17.5 21.5l-1.51-1.51a2.107 2.107 0 00-2.98 0 .024.024 0 00-.005.024l3.083 9.25A4 4 0 0019.883 32H25a4 4 0 004-4v-5a3 3 0 00-3-3h-5z" | ||||
|         fill="#A3A3A3" | ||||
|       /> | ||||
|     </svg> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const headerAnimation: Variants = { | ||||
|   initial: { opacity: 0, transition: { duration: 0.3 } }, | ||||
|   animate: { opacity: 1, transition: { duration: 0.3, delay: 0.3 } }, | ||||
|   exit: { opacity: 0, transition: { duration: 0.3 } }, | ||||
| } | ||||
|  | ||||
| const maxZIndex = 2147483647 | ||||
|  | ||||
| const bodyVariantBackwards: Variant = { | ||||
|   opacity: 0.4, | ||||
|   scale: 0.8, | ||||
|   zIndex: 0, | ||||
|   filter: 'blur(4px)', | ||||
|   transition: { duration: 0.4 }, | ||||
| } | ||||
|  | ||||
|  | ||||
| const bodyAnimation: MotionProps = { | ||||
|   initial: 'initial', | ||||
|   animate: 'animate', | ||||
|   exit: 'exit', | ||||
|   variants: { | ||||
|     initial: (custom: CustomAnimationProps) => ( | ||||
|       custom.isForwards | ||||
|         ? { | ||||
|             y: '100%', | ||||
|             zIndex: maxZIndex - custom.changeCount, | ||||
|             transition: { duration: 0.4 }, | ||||
|           } | ||||
|         : bodyVariantBackwards | ||||
|     ), | ||||
|     animate: (custom: CustomAnimationProps) => ({ | ||||
|       y: '0%', | ||||
|       opacity: 1, | ||||
|       scale: 1, | ||||
|       zIndex: maxZIndex / 2 - custom.changeCount, | ||||
|       filter: 'blur(0px)', | ||||
|       transition: { duration: 0.4 }, | ||||
|     }), | ||||
|     exit: (custom: CustomAnimationProps) => ( | ||||
|       custom.isForwards | ||||
|         ? bodyVariantBackwards | ||||
|         : { | ||||
|             y: '100%', | ||||
|             zIndex: maxZIndex - custom.changeCount, | ||||
|             transition: { duration: 0.4 }, | ||||
|           } | ||||
|     ), | ||||
|   }, | ||||
| } | ||||
|  | ||||
|  | ||||
| function InviteScreen() { | ||||
|   return ( | ||||
|     <AppScreen className="w-full"> | ||||
|       <img src={connectorImg} alt="Mycelium Connector" width="366" height="732" className="mt-[-2rem]" /> | ||||
|     </AppScreen> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function StocksScreen() { | ||||
|   return ( | ||||
|     <AppScreen className="w-full"> | ||||
|       <img src={peersImg} alt="Mycelium Peers" width="366" height="732" className="mt-[-2rem]" /> | ||||
|     </AppScreen> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function InvestScreen() { | ||||
|   return ( | ||||
|     <AppScreen className="w-full"> | ||||
|       <img src={settingImg} alt="Mycelium Settings" width="366" height="732" className="mt-[-2rem]" /> | ||||
|     </AppScreen> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function usePrevious<T>(value: T) { | ||||
|   const ref = useRef<T>() | ||||
|  | ||||
|   useEffect(() => { | ||||
|     ref.current = value | ||||
|   }, [value]) | ||||
|  | ||||
|   return ref.current | ||||
| } | ||||
|  | ||||
| function FeaturesDesktop() { | ||||
|   let [changeCount, setChangeCount] = useState(0) | ||||
|   let [selectedIndex, setSelectedIndex] = useState(0) | ||||
|   let prevIndex = usePrevious(selectedIndex) | ||||
|   let isForwards = prevIndex === undefined ? true : selectedIndex > prevIndex | ||||
|  | ||||
|   let onChange = useDebouncedCallback( | ||||
|     (selectedIndex: number) => { | ||||
|       setSelectedIndex(selectedIndex) | ||||
|       setChangeCount((changeCount) => changeCount + 1) | ||||
|     }, | ||||
|     100, | ||||
|     { leading: true }, | ||||
|   ) | ||||
|  | ||||
|   return ( | ||||
|     <TabGroup | ||||
|       className="grid grid-cols-12 items-center gap-8 lg:gap-16" | ||||
|       selectedIndex={selectedIndex} | ||||
|       onChange={onChange} | ||||
|       vertical | ||||
|     > | ||||
|       <TabList className="z-10 order-last col-span-6 space-y-6"> | ||||
|         {features.map((feature, featureIndex) => ( | ||||
|           <div | ||||
|             key={feature.name} | ||||
|             className={clsx( | ||||
|               'relative rounded-2xl outline-2 transition-all duration-300 ease-in-out hover:scale-105 hover:bg-gray-800/30', | ||||
|               selectedIndex === featureIndex | ||||
|                 ? 'outline-cyan-500' | ||||
|                 : 'outline-transparent hover:outline-cyan-500', | ||||
|             )} | ||||
|           > | ||||
|             {featureIndex === selectedIndex && ( | ||||
|               <motion.div | ||||
|                 layoutId="activeBackground" | ||||
|                 className="absolute inset-0 bg-gray-800" | ||||
|                 initial={{ borderRadius: 16 }} | ||||
|               /> | ||||
|             )} | ||||
|             <div className="relative z-10 p-8"> | ||||
|               <feature.icon className="h-8 w-8" /> | ||||
|               <FeatureTitle as="h3" color="white" className="mt-6"> | ||||
|                 <Tab className="text-left data-selected:not-data-focus:outline-hidden"> | ||||
|                   <span className="absolute inset-0 rounded-2xl" /> | ||||
|                   {feature.name} | ||||
|                 </Tab> | ||||
|               </FeatureTitle> | ||||
|               <FeatureDescription color="secondary" className="mt-2"> | ||||
|                 {feature.description} | ||||
|               </FeatureDescription> | ||||
|             </div> | ||||
|           </div> | ||||
|         ))} | ||||
|       </TabList> | ||||
|       <div className="relative col-span-6"> | ||||
|         <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"> | ||||
|           <CircleBackground id="primaryfeatures_desktop_circle" color="#13B5C8" className="animate-spin-slower" /> | ||||
|         </div> | ||||
|         <PhoneFrame className="z-10 mx-auto w-full max-w-[366px]"> | ||||
|           <TabPanels as={Fragment}> | ||||
|             <AnimatePresence | ||||
|               initial={false} | ||||
|               custom={{ isForwards, changeCount }} | ||||
|             > | ||||
|               {features.map((feature, featureIndex) => | ||||
|                 selectedIndex === featureIndex ? ( | ||||
|                   <TabPanel | ||||
|                     static | ||||
|                     key={feature.name + changeCount} | ||||
|                     className="col-start-1 row-start-1 flex focus:outline-offset-32 data-selected:not-data-focus:outline-hidden" | ||||
|                   > | ||||
|                     <motion.div {...bodyAnimation} custom={{ isForwards, changeCount }}> | ||||
|                       <feature.screen /> | ||||
|                     </motion.div> | ||||
|                   </TabPanel> | ||||
|                 ) : null, | ||||
|               )} | ||||
|             </AnimatePresence> | ||||
|           </TabPanels> | ||||
|         </PhoneFrame> | ||||
|       </div> | ||||
|     </TabGroup> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function FeaturesMobile() { | ||||
|   let [activeIndex, setActiveIndex] = useState(0) | ||||
|   let slideContainerRef = useRef<React.ElementRef<'div'>>(null) | ||||
|   let slideRefs = useRef<Array<React.ElementRef<'div'>>>([]) | ||||
|  | ||||
|   useEffect(() => { | ||||
|     let observer = new window.IntersectionObserver( | ||||
|       (entries) => { | ||||
|         for (let entry of entries) { | ||||
|           if (entry.isIntersecting && entry.target instanceof HTMLDivElement) { | ||||
|             setActiveIndex(slideRefs.current.indexOf(entry.target)) | ||||
|             break | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       { | ||||
|         root: slideContainerRef.current, | ||||
|         threshold: 0.6, | ||||
|       }, | ||||
|     ) | ||||
|  | ||||
|     for (let slide of slideRefs.current) { | ||||
|       if (slide) { | ||||
|         observer.observe(slide) | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return () => { | ||||
|       observer.disconnect() | ||||
|     } | ||||
|   }, [slideContainerRef, slideRefs]) | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <div | ||||
|         ref={slideContainerRef} | ||||
|         className="-mb-4 flex snap-x snap-mandatory -space-x-4 overflow-x-auto overscroll-x-contain scroll-smooth pb-4 [scrollbar-width:none] sm:-space-x-6 [&::-webkit-scrollbar]:hidden" | ||||
|       > | ||||
|         {features.map((feature, featureIndex) => ( | ||||
|           <div | ||||
|             key={featureIndex} | ||||
|             ref={(ref) => ref && (slideRefs.current[featureIndex] = ref)} | ||||
|             className="w-full flex-none snap-center px-4 sm:px-6 transition-all duration-300 ease-in-out hover:scale-105" | ||||
|           > | ||||
|                         <div | ||||
|               className={clsx( | ||||
|                 'relative transform overflow-hidden rounded-2xl bg-gray-800 px-5 py-6 outline-2 transition-colors', | ||||
|                 activeIndex === featureIndex | ||||
|                   ? 'outline-transparent' // Remove outline for active mobile slide | ||||
|                   : 'outline-transparent hover:outline-cyan-500', | ||||
|               )} | ||||
|             > | ||||
|               <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"> | ||||
|                                 <CircleBackground | ||||
|                   id={`primaryfeatures_mobile_circle_${featureIndex}`} | ||||
|                   color="#13B5C8" | ||||
|                   className={featureIndex % 2 === 1 ? 'rotate-180' : undefined} | ||||
|                 /> | ||||
|               </div> | ||||
|               <PhoneFrame className="relative mx-auto w-full max-w-[366px]"> | ||||
|                 <feature.screen /> | ||||
|               </PhoneFrame> | ||||
|               <div className="absolute inset-x-0 bottom-0 bg-gray-800/95 p-6 backdrop-blur-sm sm:p-10"> | ||||
|                 <feature.icon className="h-8 w-8" /> | ||||
|                 <MobileFeatureTitle color="white" className="mt-6"> | ||||
|                   {feature.name} | ||||
|                 </MobileFeatureTitle> | ||||
|                 <FeatureDescription color="secondary" className="mt-2"> | ||||
|                   {feature.description} | ||||
|                 </FeatureDescription> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         ))} | ||||
|       </div> | ||||
|       <div className="mt-6 flex justify-center gap-3"> | ||||
|         {features.map((_, featureIndex) => ( | ||||
|           <button | ||||
|             type="button" | ||||
|             key={featureIndex} | ||||
|             className={clsx( | ||||
|               'relative h-0.5 w-4 rounded-full', | ||||
|               featureIndex === activeIndex ? 'bg-gray-300' : 'bg-gray-500', | ||||
|             )} | ||||
|             aria-label={`Go to slide ${featureIndex + 1}`} | ||||
|             onClick={() => { | ||||
|               slideRefs.current[featureIndex].scrollIntoView({ | ||||
|                 block: 'nearest', | ||||
|                 inline: 'nearest', | ||||
|               }) | ||||
|             }} | ||||
|           > | ||||
|             <span className="absolute -inset-x-1.5 -inset-y-3" /> | ||||
|           </button> | ||||
|         ))} | ||||
|       </div> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export function PrimaryFeatures() { | ||||
|   return ( | ||||
|     <section | ||||
|       id="howitworks" | ||||
|       aria-label="How Mycelium works" | ||||
|       aria-label="Features for investing all your money" | ||||
|       className="bg-gray-900 py-20 sm:py-32" | ||||
|     > | ||||
|       <Container> | ||||
|         <div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-3xl"> | ||||
|           <h2 className="text-base/7 font-semibold text-cyan-500">How It Works</h2> | ||||
|           <p className="text-3xl lg:text-4xl font-medium tracking-tight text-white"> | ||||
|           <Eyebrow color="accent">How It Works</Eyebrow> | ||||
|           <SectionHeader color="white" className="mt-2"> | ||||
|             How Mycelium Operates | ||||
|           </p> | ||||
|           <p className="mt-6 text-lg text-gray-300"> | ||||
|             Mycelium, like its natural namesake, thrives on decentralization, efficiency, and security, making it a truly powerful force in the world of decentralized networks. | ||||
|           </p> | ||||
|           </SectionHeader> | ||||
|           <P color="light" className="mt-6"> | ||||
|             Mycelium, like its natural namesake, thrives on decentralization, | ||||
|             efficiency, and security, making it a truly powerful force in the world | ||||
|             of decentralized networks. | ||||
|           </P> | ||||
|         </div> | ||||
|         <div className="mt-16 text-center"> | ||||
|           <p className="text-lg text-gray-400"> | ||||
|             Interactive features demonstration coming soon... | ||||
|           </p> | ||||
|       </Container> | ||||
|       <div className="mt-16 md:hidden"> | ||||
|         <FeaturesMobile /> | ||||
|       </div> | ||||
|       <Container className="hidden md:mt-20 md:block"> | ||||
|         <FeaturesDesktop /> | ||||
|       </Container> | ||||
|     </section> | ||||
|   ) | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", | ||||
|     "baseUrl": ".", | ||||
|     "paths": { | ||||
|       "@/*": ["src/*"] | ||||
|     }, | ||||
|     "target": "ES2022", | ||||
|     "useDefineForClassFields": true, | ||||
|     "lib": ["ES2022", "DOM", "DOM.Iterable"], | ||||
|   | ||||
| @@ -1,7 +1,13 @@ | ||||
| import { defineConfig } from 'vite' | ||||
| import react from '@vitejs/plugin-react' | ||||
| import path from 'node:path' | ||||
|  | ||||
| // https://vite.dev/config/ | ||||
| export default defineConfig({ | ||||
|   plugins: [react()], | ||||
|   resolve: { | ||||
|     alias: { | ||||
|       '@': path.resolve(__dirname, './src'), | ||||
|     }, | ||||
|   }, | ||||
| }) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user