diff --git a/build/404.html b/build/404.html deleted file mode 100644 index 829eda8..0000000 --- a/build/404.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - Page Not Found - - - - -
-

404

-

Page Not Found

-

The specified file was not found on this website. Please check the URL for mistakes and try again.

-

Why am I seeing this?

-

This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.

-
- - diff --git a/build/assets/index-BdWk3V4v.js b/build/assets/index-BdWk3V4v.js new file mode 100644 index 0000000..1cd9f61 --- /dev/null +++ b/build/assets/index-BdWk3V4v.js @@ -0,0 +1,3 @@ +import{r as a,j as l,C as xn,u as yt,F as bn,M as wn,c as _n}from"./three-CMo9PyBJ.js";import{g as En,s as xt,i as bt,a as qe,b as wt,c as Pn,d as Sn,e as Cn,f as jn,r as Rn,h as Ln,j as _t,k as kn,l as Tn,m as In,o as Mn,S as Nn,H as Dn,F as O,n as An,p as Et,q as ye,t as $n,u as Ye,v as A,w as Pt,x as Vn,y as Fn,z as On,A as xe,B as Me,C as Je,D as Wn,E as Bn,G as D,I as Hn,J as Un,K as zn,L as Xe,M as Gn,N as Kn,O as qn,P as Yn,Q as fe,R as Jn,T as Xn,U as St,V as Qn,W as Zn}from"./motion-BIOHP8Ul.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const i of s.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&r(i)}).observe(document,{childList:!0,subtree:!0});function n(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(o){if(o.ep)return;o.ep=!0;const s=n(o);fetch(o.href,s)}})();var Qe="popstate";function er(e={}){function t(r,o){let{pathname:s,search:i,hash:c}=r.location;return Ne("",{pathname:s,search:i,hash:c},o.state&&o.state.usr||null,o.state&&o.state.key||"default")}function n(r,o){return typeof o=="string"?o:re(o)}return nr(t,n,null,e)}function E(e,t){if(e===!1||e===null||typeof e>"u")throw new Error(t)}function N(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function tr(){return Math.random().toString(36).substring(2,10)}function Ze(e,t){return{usr:e.state,key:e.key,idx:t}}function Ne(e,t,n=null,r){return{pathname:typeof e=="string"?e:e.pathname,search:"",hash:"",...typeof t=="string"?Y(t):t,state:n,key:t&&t.key||r||tr()}}function re({pathname:e="/",search:t="",hash:n=""}){return t&&t!=="?"&&(e+=t.charAt(0)==="?"?t:"?"+t),n&&n!=="#"&&(e+=n.charAt(0)==="#"?n:"#"+n),e}function Y(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substring(n),e=e.substring(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substring(r),e=e.substring(0,r)),e&&(t.pathname=e)}return t}function nr(e,t,n,r={}){let{window:o=document.defaultView,v5Compat:s=!1}=r,i=o.history,c="POP",d=null,f=h();f==null&&(f=0,i.replaceState({...i.state,idx:f},""));function h(){return(i.state||{idx:null}).idx}function u(){c="POP";let x=h(),b=x==null?null:x-f;f=x,d&&d({action:c,location:y.location,delta:b})}function m(x,b){c="PUSH";let v=Ne(y.location,x,b);f=h()+1;let w=Ze(v,f),j=y.createHref(v);try{i.pushState(w,"",j)}catch(L){if(L instanceof DOMException&&L.name==="DataCloneError")throw L;o.location.assign(j)}s&&d&&d({action:c,location:y.location,delta:1})}function p(x,b){c="REPLACE";let v=Ne(y.location,x,b);f=h();let w=Ze(v,f),j=y.createHref(v);i.replaceState(w,"",j),s&&d&&d({action:c,location:y.location,delta:0})}function g(x){return rr(x)}let y={get action(){return c},get location(){return e(o,i)},listen(x){if(d)throw new Error("A history only accepts one active listener");return o.addEventListener(Qe,u),d=x,()=>{o.removeEventListener(Qe,u),d=null}},createHref(x){return t(o,x)},createURL:g,encodeLocation(x){let b=g(x);return{pathname:b.pathname,search:b.search,hash:b.hash}},push:m,replace:p,go(x){return i.go(x)}};return y}function rr(e,t=!1){let n="http://localhost";typeof window<"u"&&(n=window.location.origin!=="null"?window.location.origin:window.location.href),E(n,"No window.location.(origin|href) available to create URL");let r=typeof e=="string"?e:re(e);return r=r.replace(/ $/,"%20"),!t&&r.startsWith("//")&&(r=n+r),new URL(r,n)}function Ct(e,t,n="/"){return or(e,t,n,!1)}function or(e,t,n,r){let o=typeof t=="string"?Y(t):t,s=V(o.pathname||"/",n);if(s==null)return null;let i=jt(e);sr(i);let c=null;for(let d=0;c==null&&d{let h={relativePath:f===void 0?i.path||"":f,caseSensitive:i.caseSensitive===!0,childrenIndex:c,route:i};if(h.relativePath.startsWith("/")){if(!h.relativePath.startsWith(r)&&d)return;E(h.relativePath.startsWith(r),`Absolute route path "${h.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),h.relativePath=h.relativePath.slice(r.length)}let u=$([r,h.relativePath]),m=n.concat(h);i.children&&i.children.length>0&&(E(i.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${u}".`),jt(i.children,t,m,u,d)),!(i.path==null&&!i.index)&&t.push({path:u,score:hr(u,i.index),routesMeta:m})};return e.forEach((i,c)=>{if(i.path===""||!i.path?.includes("?"))s(i,c);else for(let d of Rt(i.path))s(i,c,!0,d)}),t}function Rt(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,o=n.endsWith("?"),s=n.replace(/\?$/,"");if(r.length===0)return o?[s,""]:[s];let i=Rt(r.join("/")),c=[];return c.push(...i.map(d=>d===""?s:[s,d].join("/"))),o&&c.push(...i),c.map(d=>e.startsWith("/")&&d===""?"/":d)}function sr(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:fr(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}var ir=/^:[\w-]+$/,ar=3,lr=2,cr=1,ur=10,dr=-2,et=e=>e==="*";function hr(e,t){let n=e.split("/"),r=n.length;return n.some(et)&&(r+=dr),t&&(r+=lr),n.filter(o=>!et(o)).reduce((o,s)=>o+(ir.test(s)?ar:s===""?cr:ur),r)}function fr(e,t){return e.length===t.length&&e.slice(0,-1).every((r,o)=>r===t[o])?e[e.length-1]-t[t.length-1]:0}function mr(e,t,n=!1){let{routesMeta:r}=e,o={},s="/",i=[];for(let c=0;c{if(h==="*"){let g=c[m]||"";i=s.slice(0,s.length-g.length).replace(/(.)\/+$/,"$1")}const p=c[m];return u&&!p?f[h]=void 0:f[h]=(p||"").replace(/%2F/g,"/"),f},{}),pathname:s,pathnameBase:i,pattern:e}}function pr(e,t=!1,n=!0){N(e==="*"||!e.endsWith("*")||e.endsWith("/*"),`Route path "${e}" will be treated as if it were "${e.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${e.replace(/\*$/,"/*")}".`);let r=[],o="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(i,c,d)=>(r.push({paramName:c,isOptional:d!=null}),d?"/?([^\\/]+)?":"/([^\\/]+)")).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return e.endsWith("*")?(r.push({paramName:"*"}),o+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?o+="\\/*$":e!==""&&e!=="/"&&(o+="(?:(?=\\/|$))"),[new RegExp(o,t?void 0:"i"),r]}function gr(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return N(!1,`The URL path "${e}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${t}).`),e}}function V(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith("/")?t.length-1:t.length,r=e.charAt(n);return r&&r!=="/"?null:e.slice(n)||"/"}var Lt=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,vr=e=>Lt.test(e);function yr(e,t="/"){let{pathname:n,search:r="",hash:o=""}=typeof e=="string"?Y(e):e,s;if(n)if(vr(n))s=n;else{if(n.includes("//")){let i=n;n=n.replace(/\/\/+/g,"/"),N(!1,`Pathnames cannot have embedded double slashes - normalizing ${i} -> ${n}`)}n.startsWith("/")?s=tt(n.substring(1),"/"):s=tt(n,t)}else s=t;return{pathname:s,search:wr(r),hash:_r(o)}}function tt(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(o=>{o===".."?n.length>1&&n.pop():o!=="."&&n.push(o)}),n.length>1?n.join("/"):"/"}function Se(e,t,n,r){return`Cannot include a '${e}' character in a manually specified \`to.${t}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${n}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function xr(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function kt(e){let t=xr(e);return t.map((n,r)=>r===t.length-1?n.pathname:n.pathnameBase)}function Tt(e,t,n,r=!1){let o;typeof e=="string"?o=Y(e):(o={...e},E(!o.pathname||!o.pathname.includes("?"),Se("?","pathname","search",o)),E(!o.pathname||!o.pathname.includes("#"),Se("#","pathname","hash",o)),E(!o.search||!o.search.includes("#"),Se("#","search","hash",o)));let s=e===""||o.pathname==="",i=s?"/":o.pathname,c;if(i==null)c=n;else{let u=t.length-1;if(!r&&i.startsWith("..")){let m=i.split("/");for(;m[0]==="..";)m.shift(),u-=1;o.pathname=m.join("/")}c=u>=0?t[u]:"/"}let d=yr(o,c),f=i&&i!=="/"&&i.endsWith("/"),h=(s||i===".")&&n.endsWith("/");return!d.pathname.endsWith("/")&&(f||h)&&(d.pathname+="/"),d}var $=e=>e.join("/").replace(/\/\/+/g,"/"),br=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),wr=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,_r=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e,Er=class{constructor(e,t,n,r=!1){this.status=e,this.statusText=t||"",this.internal=r,n instanceof Error?(this.data=n.toString(),this.error=n):this.data=n}};function Pr(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}function Sr(e){return e.map(t=>t.route.path).filter(Boolean).join("/").replace(/\/\/*/g,"/")||"/"}var It=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Mt(e,t){let n=e;if(typeof n!="string"||!Lt.test(n))return{absoluteURL:void 0,isExternal:!1,to:n};let r=n,o=!1;if(It)try{let s=new URL(window.location.href),i=n.startsWith("//")?new URL(s.protocol+n):new URL(n),c=V(i.pathname,t);i.origin===s.origin&&c!=null?n=c+i.search+i.hash:o=!0}catch{N(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:o,to:n}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var Nt=["POST","PUT","PATCH","DELETE"];new Set(Nt);var Cr=["GET",...Nt];new Set(Cr);var J=a.createContext(null);J.displayName="DataRouter";var _e=a.createContext(null);_e.displayName="DataRouterState";var jr=a.createContext(!1),Dt=a.createContext({isTransitioning:!1});Dt.displayName="ViewTransition";var Rr=a.createContext(new Map);Rr.displayName="Fetchers";var Lr=a.createContext(null);Lr.displayName="Await";var I=a.createContext(null);I.displayName="Navigation";var se=a.createContext(null);se.displayName="Location";var F=a.createContext({outlet:null,matches:[],isDataRoute:!1});F.displayName="Route";var Fe=a.createContext(null);Fe.displayName="RouteError";var At="REACT_ROUTER_ERROR",kr="REDIRECT",Tr="ROUTE_ERROR_RESPONSE";function Ir(e){if(e.startsWith(`${At}:${kr}:{`))try{let t=JSON.parse(e.slice(28));if(typeof t=="object"&&t&&typeof t.status=="number"&&typeof t.statusText=="string"&&typeof t.location=="string"&&typeof t.reloadDocument=="boolean"&&typeof t.replace=="boolean")return t}catch{}}function Mr(e){if(e.startsWith(`${At}:${Tr}:{`))try{let t=JSON.parse(e.slice(40));if(typeof t=="object"&&t&&typeof t.status=="number"&&typeof t.statusText=="string")return new Er(t.status,t.statusText,t.data)}catch{}}function Nr(e,{relative:t}={}){E(ie(),"useHref() may be used only in the context of a component.");let{basename:n,navigator:r}=a.useContext(I),{hash:o,pathname:s,search:i}=ae(e,{relative:t}),c=s;return n!=="/"&&(c=s==="/"?n:$([n,s])),r.createHref({pathname:c,search:i,hash:o})}function ie(){return a.useContext(se)!=null}function W(){return E(ie(),"useLocation() may be used only in the context of a component."),a.useContext(se).location}var $t="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function Vt(e){a.useContext(I).static||a.useLayoutEffect(e)}function Dr(){let{isDataRoute:e}=a.useContext(F);return e?qr():Ar()}function Ar(){E(ie(),"useNavigate() may be used only in the context of a component.");let e=a.useContext(J),{basename:t,navigator:n}=a.useContext(I),{matches:r}=a.useContext(F),{pathname:o}=W(),s=JSON.stringify(kt(r)),i=a.useRef(!1);return Vt(()=>{i.current=!0}),a.useCallback((d,f={})=>{if(N(i.current,$t),!i.current)return;if(typeof d=="number"){n.go(d);return}let h=Tt(d,JSON.parse(s),o,f.relative==="path");e==null&&t!=="/"&&(h.pathname=h.pathname==="/"?t:$([t,h.pathname])),(f.replace?n.replace:n.push)(h,f.state,f)},[t,n,s,o,e])}a.createContext(null);function ae(e,{relative:t}={}){let{matches:n}=a.useContext(F),{pathname:r}=W(),o=JSON.stringify(kt(n));return a.useMemo(()=>Tt(e,JSON.parse(o),r,t==="path"),[e,o,r,t])}function $r(e,t){return Ft(e,t)}function Ft(e,t,n,r,o){E(ie(),"useRoutes() may be used only in the context of a component.");let{navigator:s}=a.useContext(I),{matches:i}=a.useContext(F),c=i[i.length-1],d=c?c.params:{},f=c?c.pathname:"/",h=c?c.pathnameBase:"/",u=c&&c.route;{let v=u&&u.path||"";Wt(f,!u||v.endsWith("*")||v.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${f}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + +Please change the parent to .`)}let m=W(),p;if(t){let v=typeof t=="string"?Y(t):t;E(h==="/"||v.pathname?.startsWith(h),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${h}" but pathname "${v.pathname}" was given in the \`location\` prop.`),p=v}else p=m;let g=p.pathname||"/",y=g;if(h!=="/"){let v=h.replace(/^\//,"").split("/");y="/"+g.replace(/^\//,"").split("/").slice(v.length).join("/")}let x=Ct(e,{pathname:y});N(u||x!=null,`No routes matched location "${p.pathname}${p.search}${p.hash}" `),N(x==null||x[x.length-1].route.element!==void 0||x[x.length-1].route.Component!==void 0||x[x.length-1].route.lazy!==void 0,`Matched leaf route at location "${p.pathname}${p.search}${p.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let b=Br(x&&x.map(v=>Object.assign({},v,{params:Object.assign({},d,v.params),pathname:$([h,s.encodeLocation?s.encodeLocation(v.pathname.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:v.pathname]),pathnameBase:v.pathnameBase==="/"?h:$([h,s.encodeLocation?s.encodeLocation(v.pathnameBase.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:v.pathnameBase])})),i,n,r,o);return t&&b?a.createElement(se.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",...p},navigationType:"POP"}},b):b}function Vr(){let e=Kr(),t=Pr(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,r="rgba(200,200,200, 0.5)",o={padding:"0.5rem",backgroundColor:r},s={padding:"2px 4px",backgroundColor:r},i=null;return console.error("Error handled by React Router default ErrorBoundary:",e),i=a.createElement(a.Fragment,null,a.createElement("p",null,"💿 Hey developer 👋"),a.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",a.createElement("code",{style:s},"ErrorBoundary")," or"," ",a.createElement("code",{style:s},"errorElement")," prop on your route.")),a.createElement(a.Fragment,null,a.createElement("h2",null,"Unexpected Application Error!"),a.createElement("h3",{style:{fontStyle:"italic"}},t),n?a.createElement("pre",{style:o},n):null,i)}var Fr=a.createElement(Vr,null),Ot=class extends a.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!=="idle"&&e.revalidation==="idle"?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error!==void 0?e.error:t.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){this.props.onError?this.props.onError(e,t):console.error("React Router caught the following error during render",e)}render(){let e=this.state.error;if(this.context&&typeof e=="object"&&e&&"digest"in e&&typeof e.digest=="string"){const n=Mr(e.digest);n&&(e=n)}let t=e!==void 0?a.createElement(F.Provider,{value:this.props.routeContext},a.createElement(Fe.Provider,{value:e,children:this.props.component})):this.props.children;return this.context?a.createElement(Or,{error:e},t):t}};Ot.contextType=jr;var Ce=new WeakMap;function Or({children:e,error:t}){let{basename:n}=a.useContext(I);if(typeof t=="object"&&t&&"digest"in t&&typeof t.digest=="string"){let r=Ir(t.digest);if(r){let o=Ce.get(t);if(o)throw o;let s=Mt(r.location,n);if(It&&!Ce.get(t))if(s.isExternal||r.reloadDocument)window.location.href=s.absoluteURL||s.to;else{const i=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(s.to,{replace:r.replace}));throw Ce.set(t,i),i}return a.createElement("meta",{httpEquiv:"refresh",content:`0;url=${s.absoluteURL||s.to}`})}}return e}function Wr({routeContext:e,match:t,children:n}){let r=a.useContext(J);return r&&r.static&&r.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=t.route.id),a.createElement(F.Provider,{value:e},n)}function Br(e,t=[],n=null,r=null,o=null){if(e==null){if(!n)return null;if(n.errors)e=n.matches;else if(t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let s=e,i=n?.errors;if(i!=null){let h=s.findIndex(u=>u.route.id&&i?.[u.route.id]!==void 0);E(h>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(i).join(",")}`),s=s.slice(0,Math.min(s.length,h+1))}let c=!1,d=-1;if(n)for(let h=0;h=0?s=s.slice(0,d+1):s=[s[0]];break}}}let f=n&&r?(h,u)=>{r(h,{location:n.location,params:n.matches?.[0]?.params??{},unstable_pattern:Sr(n.matches),errorInfo:u})}:void 0;return s.reduceRight((h,u,m)=>{let p,g=!1,y=null,x=null;n&&(p=i&&u.route.id?i[u.route.id]:void 0,y=u.route.errorElement||Fr,c&&(d<0&&m===0?(Wt("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),g=!0,x=null):d===m&&(g=!0,x=u.route.hydrateFallbackElement||null)));let b=t.concat(s.slice(0,m+1)),v=()=>{let w;return p?w=y:g?w=x:u.route.Component?w=a.createElement(u.route.Component,null):u.route.element?w=u.route.element:w=h,a.createElement(Wr,{match:u,routeContext:{outlet:h,matches:b,isDataRoute:n!=null},children:w})};return n&&(u.route.ErrorBoundary||u.route.errorElement||m===0)?a.createElement(Ot,{location:n.location,revalidation:n.revalidation,component:y,error:p,children:v(),routeContext:{outlet:null,matches:b,isDataRoute:!0},onError:f}):v()},null)}function Oe(e){return`${e} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function Hr(e){let t=a.useContext(J);return E(t,Oe(e)),t}function Ur(e){let t=a.useContext(_e);return E(t,Oe(e)),t}function zr(e){let t=a.useContext(F);return E(t,Oe(e)),t}function We(e){let t=zr(e),n=t.matches[t.matches.length-1];return E(n.route.id,`${e} can only be used on routes that contain a unique "id"`),n.route.id}function Gr(){return We("useRouteId")}function Kr(){let e=a.useContext(Fe),t=Ur("useRouteError"),n=We("useRouteError");return e!==void 0?e:t.errors?.[n]}function qr(){let{router:e}=Hr("useNavigate"),t=We("useNavigate"),n=a.useRef(!1);return Vt(()=>{n.current=!0}),a.useCallback(async(o,s={})=>{N(n.current,$t),n.current&&(typeof o=="number"?await e.navigate(o):await e.navigate(o,{fromRouteId:t,...s}))},[e,t])}var nt={};function Wt(e,t,n){!t&&!nt[e]&&(nt[e]=!0,N(!1,n))}a.memo(Yr);function Yr({routes:e,future:t,state:n,onError:r}){return Ft(e,void 0,n,r,t)}function me(e){E(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function Jr({basename:e="/",children:t=null,location:n,navigationType:r="POP",navigator:o,static:s=!1,unstable_useTransitions:i}){E(!ie(),"You cannot render a inside another . You should never have more than one in your app.");let c=e.replace(/^\/*/,"/"),d=a.useMemo(()=>({basename:c,navigator:o,static:s,unstable_useTransitions:i,future:{}}),[c,o,s,i]);typeof n=="string"&&(n=Y(n));let{pathname:f="/",search:h="",hash:u="",state:m=null,key:p="default"}=n,g=a.useMemo(()=>{let y=V(f,c);return y==null?null:{location:{pathname:y,search:h,hash:u,state:m,key:p},navigationType:r}},[c,f,h,u,m,p,r]);return N(g!=null,` is not able to match the URL "${f}${h}${u}" because it does not start with the basename, so the won't render anything.`),g==null?null:a.createElement(I.Provider,{value:d},a.createElement(se.Provider,{children:t,value:g}))}function Xr({children:e,location:t}){return $r(De(e),t)}function De(e,t=[]){let n=[];return a.Children.forEach(e,(r,o)=>{if(!a.isValidElement(r))return;let s=[...t,o];if(r.type===a.Fragment){n.push.apply(n,De(r.props.children,s));return}E(r.type===me,`[${typeof r.type=="string"?r.type:r.type.name}] is not a component. All component children of must be a or `),E(!r.props.index||!r.props.children,"An index route cannot have child routes.");let i={id:r.props.id||s.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,middleware:r.props.middleware,loader:r.props.loader,action:r.props.action,hydrateFallbackElement:r.props.hydrateFallbackElement,HydrateFallback:r.props.HydrateFallback,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary:r.props.hasErrorBoundary===!0||r.props.ErrorBoundary!=null||r.props.errorElement!=null,shouldRevalidate:r.props.shouldRevalidate,handle:r.props.handle,lazy:r.props.lazy};r.props.children&&(i.children=De(r.props.children,s)),n.push(i)}),n}var pe="get",ge="application/x-www-form-urlencoded";function Ee(e){return typeof HTMLElement<"u"&&e instanceof HTMLElement}function Qr(e){return Ee(e)&&e.tagName.toLowerCase()==="button"}function Zr(e){return Ee(e)&&e.tagName.toLowerCase()==="form"}function eo(e){return Ee(e)&&e.tagName.toLowerCase()==="input"}function to(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function no(e,t){return e.button===0&&(!t||t==="_self")&&!to(e)}var ue=null;function ro(){if(ue===null)try{new FormData(document.createElement("form"),0),ue=!1}catch{ue=!0}return ue}var oo=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function je(e){return e!=null&&!oo.has(e)?(N(!1,`"${e}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${ge}"`),null):e}function so(e,t){let n,r,o,s,i;if(Zr(e)){let c=e.getAttribute("action");r=c?V(c,t):null,n=e.getAttribute("method")||pe,o=je(e.getAttribute("enctype"))||ge,s=new FormData(e)}else if(Qr(e)||eo(e)&&(e.type==="submit"||e.type==="image")){let c=e.form;if(c==null)throw new Error('Cannot submit a + + + + + + ); +} diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts new file mode 100644 index 0000000..a92d0dd --- /dev/null +++ b/src/components/layout/index.ts @@ -0,0 +1,3 @@ +export { Navbar } from './Navbar'; +export { Footer } from './Footer'; +export { CustomCursor } from './CustomCursor'; diff --git a/src/components/sections/Hero.module.css b/src/components/sections/Hero.module.css new file mode 100644 index 0000000..7822f18 --- /dev/null +++ b/src/components/sections/Hero.module.css @@ -0,0 +1,146 @@ +.hero { + position: relative; + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: var(--space-3xl) 0; + overflow: hidden; +} + +.content { + position: relative; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.text { + max-width: 800px; + text-align: center; +} + +.greeting { + font-size: 1.125rem; + font-weight: 500; + color: var(--md-sys-color-primary); + margin-bottom: var(--space-md); + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.title { + font-size: clamp(2.5rem, 6vw, 5rem); + font-weight: 700; + line-height: 1.1; + margin-bottom: var(--space-lg); + background: linear-gradient( + 135deg, + var(--md-sys-color-on-surface) 0%, + var(--md-sys-color-primary) 50%, + var(--md-sys-color-on-surface) 100% + ); + background-size: 200% auto; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: shimmer 3s ease-in-out infinite; +} + +@keyframes shimmer { + 0%, 100% { + background-position: 0% center; + } + 50% { + background-position: 100% center; + } +} + +.tagline { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 0.5em; + font-size: clamp(1.25rem, 3vw, 2rem); + color: var(--md-sys-color-on-surface); + opacity: 0.9; + margin-bottom: var(--space-2xl); +} + +.typed { + color: var(--md-sys-color-primary); + font-weight: 600; + min-width: 200px; + text-align: left; +} + +.cursor { + display: inline-block; + margin-left: 2px; + animation: blink 1s step-end infinite; +} + +@keyframes blink { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0; + } +} + +.cta { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: var(--space-md); +} + +.scrollIndicator { + position: absolute; + bottom: var(--space-2xl); + left: 50%; + transform: translateX(-50%); + z-index: 1; +} + +.scrollMouse { + width: 26px; + height: 42px; + border: 2px solid var(--md-sys-color-outline); + border-radius: 13px; + display: flex; + justify-content: center; + padding-top: 8px; +} + +.scrollWheel { + width: 4px; + height: 8px; + background-color: var(--md-sys-color-primary); + border-radius: 2px; +} + +@media (max-width: 768px) { + .typed { + min-width: auto; + display: block; + width: 100%; + text-align: center; + } + + .cta { + flex-direction: column; + } + + .cta a { + width: 100%; + } + + .cta button { + width: 100%; + } +} diff --git a/src/components/sections/Hero.tsx b/src/components/sections/Hero.tsx new file mode 100644 index 0000000..0b9a38e --- /dev/null +++ b/src/components/sections/Hero.tsx @@ -0,0 +1,92 @@ +import { Link } from 'react-router-dom'; +import { motion } from 'motion/react'; +import { useTranslation } from '../../i18n'; +import { useTypingEffect } from '../../hooks'; +import { Scene3D } from '../effects'; +import { Button } from '../ui'; +import styles from './Hero.module.css'; + +export function Hero() { + const { t } = useTranslation(); + + const { text } = useTypingEffect({ + words: t.hero.rotatingWords, + typingSpeed: 80, + deletingSpeed: 40, + pauseDuration: 2500, + }); + + return ( +
+ + +
+ + + {t.hero.greeting} + + + + {t.hero.company} + + + + {t.hero.tagline} + + {text} + | + + + + + + + + + + + + +
+ +
+ + + +
+
+ ); +} diff --git a/src/components/sections/Services.module.css b/src/components/sections/Services.module.css new file mode 100644 index 0000000..042c231 --- /dev/null +++ b/src/components/sections/Services.module.css @@ -0,0 +1,36 @@ +.services { + padding: var(--space-3xl) 0; + background-color: var(--md-sys-color-surface-container-lowest); +} + +.header { + text-align: center; + margin-bottom: var(--space-3xl); +} + +.title { + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 700; + margin-bottom: var(--space-md); + color: var(--md-sys-color-on-surface); +} + +.subtitle { + font-size: 1.125rem; + color: var(--md-sys-color-on-surface); + opacity: 0.75; + max-width: 600px; + margin: 0 auto; +} + +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--space-xl); +} + +@media (min-width: 1024px) { + .grid { + grid-template-columns: repeat(4, 1fr); + } +} diff --git a/src/components/sections/Services.tsx b/src/components/sections/Services.tsx new file mode 100644 index 0000000..a0fa1cf --- /dev/null +++ b/src/components/sections/Services.tsx @@ -0,0 +1,95 @@ +import { motion, type Variants } from 'motion/react'; +import { useTranslation } from '../../i18n'; +import { Card } from '../ui'; +import styles from './Services.module.css'; + +const icons = { + code: ( + + + + + ), + support: ( + + + + ), + consulting: ( + + + + + ), + hosting: ( + + + + + + + ), +}; + +const containerVariants: Variants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.15, + }, + }, +}; + +const itemVariants: Variants = { + hidden: { opacity: 0, y: 30 }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.5, + ease: [0.4, 0, 0.2, 1], + }, + }, +}; + +export function Services() { + const { t } = useTranslation(); + + return ( +
+
+ +

{t.services.title}

+

{t.services.subtitle}

+
+ + + {t.services.items.map((service, index) => ( + + + + {icons[service.icon as keyof typeof icons]} + + {service.title} + {service.description} + + + ))} + +
+
+ ); +} diff --git a/src/components/sections/index.ts b/src/components/sections/index.ts new file mode 100644 index 0000000..8c32bd8 --- /dev/null +++ b/src/components/sections/index.ts @@ -0,0 +1,2 @@ +export { Hero } from './Hero'; +export { Services } from './Services'; diff --git a/src/components/ui/Button.module.css b/src/components/ui/Button.module.css new file mode 100644 index 0000000..151f0f9 --- /dev/null +++ b/src/components/ui/Button.module.css @@ -0,0 +1,82 @@ +.button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-sm); + font-family: var(--md-sys-typescale-body-font); + font-weight: 600; + border: none; + border-radius: var(--radius-full); + cursor: pointer; + transition: + background-color var(--transition-fast), + color var(--transition-fast), + box-shadow var(--transition-fast); +} + +.button:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* Sizes */ +.sm { + padding: var(--space-sm) var(--space-md); + font-size: 0.875rem; +} + +.md { + padding: var(--space-md) var(--space-xl); + font-size: 1rem; +} + +.lg { + padding: var(--space-lg) var(--space-2xl); + font-size: 1.125rem; +} + +/* Variants */ +.primary { + background-color: var(--md-sys-color-primary); + color: var(--md-sys-color-on-primary); +} + +.primary:hover:not(:disabled) { + background-color: var(--md-sys-color-on-primary-container); + box-shadow: 0 4px 20px rgba(127, 217, 152, 0.3); +} + +.secondary { + background-color: var(--md-sys-color-surface-container-high); + color: var(--md-sys-color-on-surface); +} + +.secondary:hover:not(:disabled) { + background-color: var(--md-sys-color-surface-container-highest); +} + +.outline { + background-color: transparent; + color: var(--md-sys-color-primary); + border: 2px solid var(--md-sys-color-primary); +} + +.outline:hover:not(:disabled) { + background-color: rgba(127, 217, 152, 0.1); +} + +/* Loader */ +.loader { + width: 20px; + height: 20px; + border: 2px solid transparent; + border-top-color: currentColor; + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx new file mode 100644 index 0000000..09f6d6b --- /dev/null +++ b/src/components/ui/Button.tsx @@ -0,0 +1,42 @@ +import { type ReactNode } from 'react'; +import { motion } from 'motion/react'; +import styles from './Button.module.css'; + +interface ButtonProps { + variant?: 'primary' | 'secondary' | 'outline'; + size?: 'sm' | 'md' | 'lg'; + children: ReactNode; + isLoading?: boolean; + disabled?: boolean; + className?: string; + type?: 'button' | 'submit' | 'reset'; + onClick?: () => void; +} + +export function Button({ + variant = 'primary', + size = 'md', + children, + isLoading, + disabled, + className, + type = 'button', + onClick, +}: ButtonProps) { + return ( + + {isLoading ? ( + + ) : ( + children + )} + + ); +} diff --git a/src/components/ui/Card.module.css b/src/components/ui/Card.module.css new file mode 100644 index 0000000..c335bd9 --- /dev/null +++ b/src/components/ui/Card.module.css @@ -0,0 +1,51 @@ +.card { + position: relative; + padding: var(--space-xl); + background-color: var(--md-sys-color-surface-container); + border: 1px solid var(--md-sys-color-outline-variant); + border-radius: var(--radius-lg); + transition: + border-color var(--transition-fast), + box-shadow var(--transition-fast), + background-color var(--transition-fast); +} + +.card.hoverable:hover { + border-color: var(--md-sys-color-primary); + background-color: var(--md-sys-color-surface-container-high); + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.3), + 0 0 0 1px rgba(127, 217, 152, 0.1), + inset 0 1px 0 rgba(127, 217, 152, 0.1); +} + +.icon { + display: flex; + align-items: center; + justify-content: center; + width: 56px; + height: 56px; + margin-bottom: var(--space-lg); + background-color: var(--md-sys-color-primary-container); + border-radius: var(--radius-md); + color: var(--md-sys-color-on-primary-container); +} + +.icon svg { + width: 28px; + height: 28px; +} + +.title { + margin-bottom: var(--space-sm); + font-size: 1.25rem; + font-weight: 600; + color: var(--md-sys-color-on-surface); +} + +.description { + font-size: 0.95rem; + line-height: 1.6; + color: var(--md-sys-color-on-surface); + opacity: 0.75; +} diff --git a/src/components/ui/Card.tsx b/src/components/ui/Card.tsx new file mode 100644 index 0000000..8f1eb07 --- /dev/null +++ b/src/components/ui/Card.tsx @@ -0,0 +1,49 @@ +import { type ReactNode } from 'react'; +import { motion } from 'motion/react'; +import styles from './Card.module.css'; + +interface CardProps { + children: ReactNode; + className?: string; + hover?: boolean; +} + +export function Card({ children, className, hover = true }: CardProps) { + return ( + + {children} + + ); +} + +interface CardIconProps { + children: ReactNode; +} + +export function CardIcon({ children }: CardIconProps) { + return
{children}
; +} + +interface CardTitleProps { + children: ReactNode; +} + +export function CardTitle({ children }: CardTitleProps) { + return

{children}

; +} + +interface CardDescriptionProps { + children: ReactNode; +} + +export function CardDescription({ children }: CardDescriptionProps) { + return

{children}

; +} + +Card.Icon = CardIcon; +Card.Title = CardTitle; +Card.Description = CardDescription; diff --git a/src/components/ui/Input.module.css b/src/components/ui/Input.module.css new file mode 100644 index 0000000..6cb7aa9 --- /dev/null +++ b/src/components/ui/Input.module.css @@ -0,0 +1,58 @@ +.field { + display: flex; + flex-direction: column; + gap: var(--space-sm); +} + +.label { + font-size: 0.875rem; + font-weight: 500; + color: var(--md-sys-color-on-surface); +} + +.input { + padding: var(--space-md); + font-family: var(--md-sys-typescale-body-font); + font-size: 1rem; + color: var(--md-sys-color-on-surface); + background-color: var(--md-sys-color-surface-container); + border: 1px solid var(--md-sys-color-outline-variant); + border-radius: var(--radius-md); + transition: + border-color var(--transition-fast), + box-shadow var(--transition-fast), + background-color var(--transition-fast); +} + +.input::placeholder { + color: var(--md-sys-color-outline); +} + +.input:hover { + border-color: var(--md-sys-color-outline); + background-color: var(--md-sys-color-surface-container-high); +} + +.input:focus { + outline: none; + border-color: var(--md-sys-color-primary); + box-shadow: 0 0 0 3px rgba(127, 217, 152, 0.15); +} + +.textarea { + min-height: 150px; + resize: vertical; +} + +.hasError .input { + border-color: var(--md-sys-color-error); +} + +.hasError .input:focus { + box-shadow: 0 0 0 3px rgba(255, 180, 171, 0.15); +} + +.error { + font-size: 0.8125rem; + color: var(--md-sys-color-error); +} diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx new file mode 100644 index 0000000..e44f1df --- /dev/null +++ b/src/components/ui/Input.tsx @@ -0,0 +1,58 @@ +import { type InputHTMLAttributes, type TextareaHTMLAttributes, forwardRef } from 'react'; +import styles from './Input.module.css'; + +interface InputProps extends InputHTMLAttributes { + label: string; + error?: string; +} + +export const Input = forwardRef( + ({ label, error, id, className, ...props }, ref) => { + const inputId = id || label.toLowerCase().replace(/\s+/g, '-'); + + return ( +
+ + + {error && {error}} +
+ ); + } +); + +Input.displayName = 'Input'; + +interface TextareaProps extends TextareaHTMLAttributes { + label: string; + error?: string; +} + +export const Textarea = forwardRef( + ({ label, error, id, className, ...props }, ref) => { + const inputId = id || label.toLowerCase().replace(/\s+/g, '-'); + + return ( +
+ +