chore: update dependencies and add new mobile-first features page with interactive demos
This commit is contained in:
		
							
								
								
									
										2005
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2005
									
								
								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" |     "preview": "vite preview" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "react": "^19.1.1", |     "@headlessui/react": "^2.1.0", | ||||||
|     "react-dom": "^19.1.1", |     "@tailwindcss/forms": "^0.5.3", | ||||||
|     "react-router-dom": "^7.1.3", |     "@tailwindcss/postcss": "^4.1.7", | ||||||
|     "framer-motion": "^11.18.0", |     "@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", |     "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-countup": "^6.5.3", | ||||||
|  |     "react-dom": "^18.2.0", | ||||||
|     "react-icons": "^5.5.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": { |   "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", |     "@vitejs/plugin-react": "^5.0.4", | ||||||
|     "autoprefixer": "^10.4.20", |     "autoprefixer": "^10.4.20", | ||||||
|     "eslint": "^9.36.0", |     "eslint": "^8.56.0", | ||||||
|     "eslint-plugin-react-hooks": "^5.2.0", |     "prettier": "^3.3.2", | ||||||
|     "eslint-plugin-react-refresh": "^0.4.22", |     "prettier-plugin-tailwindcss": "^0.6.11", | ||||||
|     "globals": "^16.4.0", |     "sharp": "0.33.1", | ||||||
|     "postcss": "^8.5.1", |  | ||||||
|     "tailwindcss": "^4.1.7", |  | ||||||
|     "typescript": "~5.9.3", |  | ||||||
|     "typescript-eslint": "^8.45.0", |  | ||||||
|     "vite": "^7.1.7" |     "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,28 +1,440 @@ | |||||||
| 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() { | export function PrimaryFeatures() { | ||||||
|   return ( |   return ( | ||||||
|     <section |     <section | ||||||
|       id="howitworks" |       id="howitworks" | ||||||
|       aria-label="How Mycelium works" |       aria-label="Features for investing all your money" | ||||||
|       className="bg-gray-900 py-20 sm:py-32" |       className="bg-gray-900 py-20 sm:py-32" | ||||||
|     > |     > | ||||||
|       <Container> |       <Container> | ||||||
|         <div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-3xl"> |         <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> |           <Eyebrow color="accent">How It Works</Eyebrow> | ||||||
|           <p className="text-3xl lg:text-4xl font-medium tracking-tight text-white"> |           <SectionHeader color="white" className="mt-2"> | ||||||
|             How Mycelium Operates |             How Mycelium Operates | ||||||
|           </p> |           </SectionHeader> | ||||||
|           <p className="mt-6 text-lg text-gray-300"> |           <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. |             Mycelium, like its natural namesake, thrives on decentralization, | ||||||
|           </p> |             efficiency, and security, making it a truly powerful force in the world | ||||||
|         </div> |             of decentralized networks. | ||||||
|         <div className="mt-16 text-center"> |           </P> | ||||||
|           <p className="text-lg text-gray-400"> |  | ||||||
|             Interactive features demonstration coming soon... |  | ||||||
|           </p> |  | ||||||
|         </div> |         </div> | ||||||
|       </Container> |       </Container> | ||||||
|  |       <div className="mt-16 md:hidden"> | ||||||
|  |         <FeaturesMobile /> | ||||||
|  |       </div> | ||||||
|  |       <Container className="hidden md:mt-20 md:block"> | ||||||
|  |         <FeaturesDesktop /> | ||||||
|  |       </Container> | ||||||
|     </section> |     </section> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,10 @@ | |||||||
| { | { | ||||||
|   "compilerOptions": { |   "compilerOptions": { | ||||||
|     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", | ||||||
|  |     "baseUrl": ".", | ||||||
|  |     "paths": { | ||||||
|  |       "@/*": ["src/*"] | ||||||
|  |     }, | ||||||
|     "target": "ES2022", |     "target": "ES2022", | ||||||
|     "useDefineForClassFields": true, |     "useDefineForClassFields": true, | ||||||
|     "lib": ["ES2022", "DOM", "DOM.Iterable"], |     "lib": ["ES2022", "DOM", "DOM.Iterable"], | ||||||
|   | |||||||
| @@ -1,7 +1,13 @@ | |||||||
| import { defineConfig } from 'vite' | import { defineConfig } from 'vite' | ||||||
| import react from '@vitejs/plugin-react' | import react from '@vitejs/plugin-react' | ||||||
|  | import path from 'node:path' | ||||||
|  |  | ||||||
| // https://vite.dev/config/ | // https://vite.dev/config/ | ||||||
| export default defineConfig({ | export default defineConfig({ | ||||||
|   plugins: [react()], |   plugins: [react()], | ||||||
|  |   resolve: { | ||||||
|  |     alias: { | ||||||
|  |       '@': path.resolve(__dirname, './src'), | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
| }) | }) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user