Удалена папка node_modules из отслеживания

This commit is contained in:
nafer committed 2026-05-20 12:03:52 +03:00
commit 304a780bab
30 files changed
+3623

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
node_modules
dist
.env
.env.local
*.log
.DS_Store
.vscode
.idea
+5
View File
@@ -0,0 +1,5 @@
# Порядок запуска
PORT=3000
# API (если будет бэкенд в будущем)
VITE_API_URL=http://localhost:3001
+1
View File
@@ -0,0 +1 @@
node_modules/
+20
View File
@@ -0,0 +1,20 @@
# syntax=docker/dockerfile:1
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+1
View File
@@ -0,0 +1 @@
/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-leading:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-duration:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.top-1\/2{top:50%}.z-50{z-index:50}.mx-auto{margin-inline:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.table-row{display:table-row}.aspect-\[16\/10\]{aspect-ratio:16/10}.h-auto{height:auto}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-1\/2{width:50%}.w-full{width:100%}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.object-cover{object-fit:cover}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.text-\[10px\]{font-size:10px}.leading-none{--tw-leading:1;line-height:1}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,ease);transition-duration:var(--tw-duration,0s)}.duration-500{--tw-duration:.5s;transition-duration:.5s}@media(hover:hover){.group-hover\:scale-105:is(:where(.group):hover *){--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}:root{--primary:#0ea5e9}.dark{color-scheme:dark}.card{transition:transform .2s cubic-bezier(.23,1,.32,1),box-shadow .2s cubic-bezier(.23,1,.32,1)}.card:hover{transform:translateY(-2px);box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.table-row{transition:background-color .1s}.table-row:hover{background-color:#f3f4f6}.dark .table-row:hover{background-color:#1f2937}.filter-chip{transition:all .1s}.filter-chip.active{color:#fff;background-color:#0ea5e9;border-color:#0ea5e9}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-dLine truncated
+125
View File
@@ -0,0 +1,125 @@
(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const d of document.querySelectorAll('link[rel="modulepreload"]'))f(d);new MutationObserver(d=>{for(const m of d)if(m.type==="childList")for(const b of m.addedNodes)b.tagName==="LINK"&&b.rel==="modulepreload"&&f(b)}).observe(document,{childList:!0,subtree:!0});function o(d){const m={};return d.integrity&&(m.integrity=d.integrity),d.referrerPolicy&&(m.referrerPolicy=d.referrerPolicy),d.crossOrigin==="use-credentials"?m.credentials="include":d.crossOrigin==="anonymous"?m.credentials="omit":m.credentials="same-origin",m}function f(d){if(d.ep)return;d.ep=!0;const m=o(d);fetch(d.href,m)}})();var Of={exports:{}},Ou={};/**
* @license React
* react-jsx-runtime.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Pd;function oy(){if(Pd)return Ou;Pd=1;var i=Symbol.for("react.transitional.element"),r=Symbol.for("react.fragment");function o(f,d,m){var b=null;if(m!==void 0&&(b=""+m),d.key!==void 0&&(b=""+d.key),"key"in d){m={};for(var A in d)A!=="key"&&(m[A]=d[A])}else m=d;return d=m.ref,{$$typeof:i,type:f,key:b,ref:d!==void 0?d:null,props:m}}return Ou.Fragment=r,Ou.jsx=o,Ou.jsxs=o,Ou}var tm;function dy(){return tm||(tm=1,Of.exports=oy()),Of.exports}var p=dy(),Cf={exports:{}},Cu={},Df={exports:{}},jf={};/**
* @license React
* scheduler.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var em;function my(){return em||(em=1,(function(i){function r(C,B){var $=C.length;C.push(B);t:for(;0<$;){var vt=$-1>>>1,bt=C[vt];if(0<d(bt,B))C[vt]=B,C[$]=bt,$=vt;else break t}}function o(C){return C.length===0?null:C[0]}function f(C){if(C.length===0)return null;var B=C[0],$=C.pop();if($!==B){C[0]=$;t:for(var vt=0,bt=C.length,g=bt>>>1;vt<g;){var U=2*(vt+1)-1,L=C[U],Q=U+1,P=C[Q];if(0>d(L,$))Q<bt&&0>d(P,L)?(C[vt]=P,C[Q]=$,vt=Q):(C[vt]=L,C[U]=$,vt=U);else if(Q<bt&&0>d(P,$))C[vt]=P,C[Q]=$,vt=Q;else break t}}return B}function d(C,B){var $=C.sortIndex-B.sortIndex;return $!==0?$:C.id-B.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var m=performance;i.unstable_now=function(){return m.now()}}else{var b=Date,A=b.now();i.unstable_now=function(){return b.now()-A}}var x=[],v=[],O=1,R=null,H=3,Z=!1,K=!1,G=!1,X=!1,q=typeof setTimeout=="function"?setTimeout:null,I=typeof clearTimeout=="function"?clearTimeout:null,k=typeof setImmediate<"u"?setImmediate:null;function yt(C){for(var B=o(v);B!==null;){if(B.callback===null)f(v);else if(B.startTime<=C)f(v),B.sortIndex=B.expirationTime,r(x,B);else break;B=o(v)}}function dt(C){if(G=!1,yt(C),!K)if(o(x)!==null)K=!0,Et||(Et=!0,Jt());else{var B=o(v);B!==null&&jt(dt,B.startTime-C)}}var Et=!1,W=-1,Mt=5,Kt=-1;function Ue(){return X?!0:!(i.unstable_now()-Kt<Mt)}function he(){if(X=!1,Et){var C=i.unstable_now();Kt=C;var B=!0;try{t:{K=!1,G&&(G=!1,I(W),W=-1),Z=!0;var $=H;try{e:{for(yt(C),R=o(x);R!==null&&!(R.expirationTime>C&&Ue());){var vt=R.callback;if(typeof vt=="function"){R.callback=null,H=R.priorityLevel;var bt=vt(R.expirationTime<=C);if(C=i.unstable_now(),typeof bt=="function"){R.callback=bt,yt(C),B=!0;break e}R===o(x)&&f(x),yt(C)}else f(x);R=o(x)}if(R!==null)B=!0;else{var g=o(v);g!==null&&jt(dt,g.startTime-C),B=!1}}break t}finally{R=null,H=$,Z=!1}B=void 0}}finally{B?Jt():Et=!1}}}var Jt;if(typeof k=="function")Jt=function(){k(he)};else if(typeof MessageChannel<"u"){var He=new MessageChannel,ye=He.port2;He.port1.onmessage=he,Jt=function(){ye.postMessage(null)}}else Jt=function(){q(he,0)};function jt(C,B){W=q(function(){C(i.unstable_now())},B)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(C){C.callback=null},i.unstable_forceFrameRate=function(C){0>C||125<C?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):Mt=0<C?Math.floor(1e3/C):5},i.unstable_getCurrentPriorityLevel=function(){return H},i.unstable_next=function(C){switch(H){case 1:case 2:case 3:var B=3;break;default:B=H}var $=H;H=B;try{return C()}finally{H=$}},i.unstable_requestPaint=function(){X=!0},i.unstable_runWithPriority=function(C,B){switch(C){case 1:case 2:case 3:case 4:case 5:break;default:C=3}var $=H;H=C;try{return B()}finally{H=$}},i.unstable_scheduleCallback=function(C,B,$){var vt=i.unstable_now();switch(typeof $=="object"&&$!==null?($=$.delay,$=typeof $=="number"&&0<$?vt+$:vt):$=vt,C){case 1:var bt=-1;break;case 2:bt=250;break;case 5:bt=1073741823;break;case 4:bt=1e4;break;default:bt=5e3}return bt=$+bt,C={id:O++,callback:B,priorityLevel:C,startTime:$,expirationTime:bt,sortIndex:-1},$>vt?(C.sortIndex=$,r(v,C),o(x)===null&&C===o(v)&&(G?(I(W),W=-1):G=!0,jt(dt,$-vt))):(C.sortIndex=bt,r(x,C),K||Z||(K=!0,Et||(Et=!0,Jt()))),C},i.unstable_shouldYield=Ue,i.unstable_wrapCallback=function(C){var B=H;return function(){var $=H;H=B;try{return C.apply(this,arguments)}finally{H=$}}}})(jf)),jf}var lm;function hy(){return lm||(lm=1,Df.exports=my()),Df.exports}var Uf={exports:{}},F={};/**
* @license React
* react.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var am;function yy(){if(am)return F;am=1;var i=Symbol.for("react.transitional.element"),r=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),f=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),m=Symbol.for("react.consumer"),b=Symbol.for("react.context"),A=Symbol.for("react.forward_ref"),x=Symbol.for("react.suspense"),v=Symbol.for("react.memo"),O=Symbol.for("react.lazy"),R=Symbol.for("react.activity"),H=Symbol.iterator;function Z(g){return g===null||typeof g!="object"?null:(g=H&&g[H]||g["@@iterator"],typeof g=="function"?g:null)}var K={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},G=Object.assign,X={};function q(g,U,L){this.props=g,this.context=U,this.refs=X,this.updater=L||K}q.prototype.isReactComponent={},q.prototype.setState=function(g,U){if(typeof g!="object"&&typeof g!="function"&&g!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,g,U,"setState")},q.prototype.forceUpdate=function(g){this.updater.enqueueForceUpdate(this,g,"forceUpdate")};function I(){}I.prototype=q.prototype;function k(g,U,L){this.props=g,this.context=U,this.refs=X,this.updater=L||K}var yt=k.prototype=new I;yt.constructor=k,G(yt,q.prototype),yt.isPureReactComponent=!0;var dt=Array.isArray;function Et(){}var W={H:null,A:null,T:null,S:null},Mt=Object.prototype.hasOwnProperty;function Kt(g,U,L){var Q=L.ref;return{$$typeof:i,type:g,key:U,ref:Q!==void 0?Q:null,props:L}}function Ue(g,U){return Kt(g.type,U,g.props)}function he(g){return typeof g=="object"&&g!==null&&g.$$typeof===i}function Jt(g){var U={"=":"=0",":":"=2"};return"$"+g.replace(/[=:]/g,function(L){return U[L]})}var He=/\/+/g;function ye(g,U){return typeof g=="object"&&g!==null&&g.key!=null?Jt(""+g.key):U.toString(36)}function jt(g){switch(g.status){case"fulfilled":return g.value;case"rejected":throw g.reason;default:switch(typeof g.status=="string"?g.then(Et,Et):(g.status="pending",g.then(function(U){g.status==="pending"&&(g.status="fulfilled",g.value=U)},function(U){g.status==="pending"&&(g.status="rejected",g.reason=U)})),g.status){case"fulfilled":return g.value;case"rejected":throw g.reason}}throw g}function C(g,U,L,Q,P){var lt=typeof g;(lt==="undefined"||lt==="boolean")&&(g=null);var ot=!1;if(g===null)ot=!0;else switch(lt){case"bigint":case"string":case"number":ot=!0;break;case"object":switch(g.$$typeof){case i:case r:ot=!0;break;case O:return ot=g._init,C(ot(g._payload),U,L,Q,P)}}if(ot)return P=P(g),ot=Q===""?"."+ye(g,0):Q,dt(P)?(L="",ot!=null&&(L=ot.replace(He,"$&/")+"/"),C(P,U,L,"",function(qa){return qa})):P!=null&&(he(P)&&(P=Ue(P,L+(P.key==null||g&&g.key===P.key?"":(""+P.key).replace(He,"$&/")+"/")+ot)),U.push(P)),1;ot=0;var $t=Q===""?".":Q+":";if(dt(g))for(var Ot=0;Ot<g.length;Ot++)Q=g[Ot],lt=$t+ye(Q,Ot),ot+=C(Q,U,L,lt,P);else if(Ot=Z(g),typeof Ot=="function")for(g=Ot.call(g),Ot=0;!(Q=g.next()).done;)Q=Q.value,lt=$t+ye(Q,Ot++),ot+=C(Q,U,L,lt,P);else if(lt==="object"){if(typeof g.then=="function")return C(jt(g),U,L,Q,P);throw U=String(g),Error("Objects are not valid as a React child (found: "+(U==="[object Object]"?"object with keys {"+Object.keys(g).join(", ")+"}":U)+"). If you meant to render a collection of children, use an array instead.")}return ot}function B(g,U,L){if(g==null)return g;var Q=[],P=0;return C(g,Q,"","",function(lt){return U.call(L,lt,P++)}),Q}function $(g){if(g._status===-1){var U=g._result;U=U(),U.then(function(L){(g._status===0||g._status===-1)&&(g._status=1,g._result=L)},function(L){(g._status===0||g._status===-1)&&(g._status=2,g._result=L)}),g._status===-1&&(g._status=0,g._result=U)}if(g._status===1)return g._result.default;throw g._result}var vt=typeof reportError=="function"?reportError:function(g){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var U=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof g=="object"&&g!==null&&typeof g.message=="string"?String(g.message):String(g),error:g});if(!window.dispatchEvent(U))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",g);return}console.error(g)},bt={map:B,forEach:function(g,U,L){B(g,function(){U.apply(this,arguments)},L)},count:function(g){var U=0;return B(g,function(){U++}),U},toArray:function(g){return B(g,function(U){return U})||[]},only:function(g){if(!he(g))throw Error("React.Children.only expected to receive a single React element child.");return g}};return F.Activity=R,F.Children=bt,F.Component=q,F.Fragment=o,F.Profiler=d,F.PureComponent=k,F.StrictMode=f,F.Suspense=x,F.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=W,F.__COMPILER_RUNTIME={__proto__:null,c:function(g){return W.H.useMemoCache(g)}},F.cache=function(g){return function(){return g.apply(null,arguments)}},F.cacheSignal=function(){return null},F.cloneElement=function(g,U,L){if(g==null)throw Error("The argument must bLine truncated
* @license React
* react-dom.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var nm;function vy(){if(nm)return kt;nm=1;var i=Qf();function r(x){var v="https://react.dev/errors/"+x;if(1<arguments.length){v+="?args[]="+encodeURIComponent(arguments[1]);for(var O=2;O<arguments.length;O++)v+="&args[]="+encodeURIComponent(arguments[O])}return"Minified React error #"+x+"; visit "+v+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function o(){}var f={d:{f:o,r:function(){throw Error(r(522))},D:o,C:o,L:o,m:o,X:o,S:o,M:o},p:0,findDOMNode:null},d=Symbol.for("react.portal");function m(x,v,O){var R=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:d,key:R==null?null:""+R,children:x,containerInfo:v,implementation:O}}var b=i.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function A(x,v){if(x==="font")return"";if(typeof v=="string")return v==="use-credentials"?v:""}return kt.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=f,kt.createPortal=function(x,v){var O=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!v||v.nodeType!==1&&v.nodeType!==9&&v.nodeType!==11)throw Error(r(299));return m(x,v,null,O)},kt.flushSync=function(x){var v=b.T,O=f.p;try{if(b.T=null,f.p=2,x)return x()}finally{b.T=v,f.p=O,f.d.f()}},kt.preconnect=function(x,v){typeof x=="string"&&(v?(v=v.crossOrigin,v=typeof v=="string"?v==="use-credentials"?v:"":void 0):v=null,f.d.C(x,v))},kt.prefetchDNS=function(x){typeof x=="string"&&f.d.D(x)},kt.preinit=function(x,v){if(typeof x=="string"&&v&&typeof v.as=="string"){var O=v.as,R=A(O,v.crossOrigin),H=typeof v.integrity=="string"?v.integrity:void 0,Z=typeof v.fetchPriority=="string"?v.fetchPriority:void 0;O==="style"?f.d.S(x,typeof v.precedence=="string"?v.precedence:void 0,{crossOrigin:R,integrity:H,fetchPriority:Z}):O==="script"&&f.d.X(x,{crossOrigin:R,integrity:H,fetchPriority:Z,nonce:typeof v.nonce=="string"?v.nonce:void 0})}},kt.preinitModule=function(x,v){if(typeof x=="string")if(typeof v=="object"&&v!==null){if(v.as==null||v.as==="script"){var O=A(v.as,v.crossOrigin);f.d.M(x,{crossOrigin:O,integrity:typeof v.integrity=="string"?v.integrity:void 0,nonce:typeof v.nonce=="string"?v.nonce:void 0})}}else v==null&&f.d.M(x)},kt.preload=function(x,v){if(typeof x=="string"&&typeof v=="object"&&v!==null&&typeof v.as=="string"){var O=v.as,R=A(O,v.crossOrigin);f.d.L(x,O,{crossOrigin:R,integrity:typeof v.integrity=="string"?v.integrity:void 0,nonce:typeof v.nonce=="string"?v.nonce:void 0,type:typeof v.type=="string"?v.type:void 0,fetchPriority:typeof v.fetchPriority=="string"?v.fetchPriority:void 0,referrerPolicy:typeof v.referrerPolicy=="string"?v.referrerPolicy:void 0,imageSrcSet:typeof v.imageSrcSet=="string"?v.imageSrcSet:void 0,imageSizes:typeof v.imageSizes=="string"?v.imageSizes:void 0,media:typeof v.media=="string"?v.media:void 0})}},kt.preloadModule=function(x,v){if(typeof x=="string")if(v){var O=A(v.as,v.crossOrigin);f.d.m(x,{as:typeof v.as=="string"&&v.as!=="script"?v.as:void 0,crossOrigin:O,integrity:typeof v.integrity=="string"?v.integrity:void 0})}else f.d.m(x)},kt.requestFormReset=function(x){f.d.r(x)},kt.unstable_batchedUpdates=function(x,v){return x(v)},kt.useFormState=function(x,v,O){return b.H.useFormState(x,v,O)},kt.useFormStatus=function(){return b.H.useHostTransitionStatus()},kt.version="19.2.6",kt}var im;function gy(){if(im)return Hf.exports;im=1;function i(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(r){console.error(r)}}return i(),Hf.exports=vy(),Hf.exports}/**
* @license React
* react-dom-client.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var cm;function py(){if(cm)return Cu;cm=1;var i=hy(),r=Qf(),o=gy();function f(t){var e="https://react.dev/errors/"+t;if(1<arguments.length){e+="?args[]="+encodeURIComponent(arguments[1]);for(var l=2;l<arguments.length;l++)e+="&args[]="+encodeURIComponent(arguments[l])}return"Minified React error #"+t+"; visit "+e+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function d(t){return!(!t||t.nodeType!==1&&t.nodeType!==9&&t.nodeType!==11)}function m(t){var e=t,l=t;if(t.alternate)for(;e.return;)e=e.return;else{t=e;do e=t,(e.flags&4098)!==0&&(l=e.return),t=e.return;while(t)}return e.tag===3?l:null}function b(t){if(t.tag===13){var e=t.memoizedState;if(e===null&&(t=t.alternate,t!==null&&(e=t.memoizedState)),e!==null)return e.dehydrated}return null}function A(t){if(t.tag===31){var e=t.memoizedState;if(e===null&&(t=t.alternate,t!==null&&(e=t.memoizedState)),e!==null)return e.dehydrated}return null}function x(t){if(m(t)!==t)throw Error(f(188))}function v(t){var e=t.alternate;if(!e){if(e=m(t),e===null)throw Error(f(188));return e!==t?null:t}for(var l=t,a=e;;){var u=l.return;if(u===null)break;var n=u.alternate;if(n===null){if(a=u.return,a!==null){l=a;continue}break}if(u.child===n.child){for(n=u.child;n;){if(n===l)return x(u),t;if(n===a)return x(u),e;n=n.sibling}throw Error(f(188))}if(l.return!==a.return)l=u,a=n;else{for(var c=!1,s=u.child;s;){if(s===l){c=!0,l=u,a=n;break}if(s===a){c=!0,a=u,l=n;break}s=s.sibling}if(!c){for(s=n.child;s;){if(s===l){c=!0,l=n,a=u;break}if(s===a){c=!0,a=n,l=u;break}s=s.sibling}if(!c)throw Error(f(189))}}if(l.alternate!==a)throw Error(f(190))}if(l.tag!==3)throw Error(f(188));return l.stateNode.current===l?t:e}function O(t){var e=t.tag;if(e===5||e===26||e===27||e===6)return t;for(t=t.child;t!==null;){if(e=O(t),e!==null)return e;t=t.sibling}return null}var R=Object.assign,H=Symbol.for("react.element"),Z=Symbol.for("react.transitional.element"),K=Symbol.for("react.portal"),G=Symbol.for("react.fragment"),X=Symbol.for("react.strict_mode"),q=Symbol.for("react.profiler"),I=Symbol.for("react.consumer"),k=Symbol.for("react.context"),yt=Symbol.for("react.forward_ref"),dt=Symbol.for("react.suspense"),Et=Symbol.for("react.suspense_list"),W=Symbol.for("react.memo"),Mt=Symbol.for("react.lazy"),Kt=Symbol.for("react.activity"),Ue=Symbol.for("react.memo_cache_sentinel"),he=Symbol.iterator;function Jt(t){return t===null||typeof t!="object"?null:(t=he&&t[he]||t["@@iterator"],typeof t=="function"?t:null)}var He=Symbol.for("react.client.reference");function ye(t){if(t==null)return null;if(typeof t=="function")return t.$$typeof===He?null:t.displayName||t.name||null;if(typeof t=="string")return t;switch(t){case G:return"Fragment";case q:return"Profiler";case X:return"StrictMode";case dt:return"Suspense";case Et:return"SuspenseList";case Kt:return"Activity"}if(typeof t=="object")switch(t.$$typeof){case K:return"Portal";case k:return t.displayName||"Context";case I:return(t._context.displayName||"Context")+".Consumer";case yt:var e=t.render;return t=t.displayName,t||(t=e.displayName||e.name||"",t=t!==""?"ForwardRef("+t+")":"ForwardRef"),t;case W:return e=t.displayName||null,e!==null?e:ye(t.type)||"Memo";case Mt:e=t._payload,t=t._init;try{return ye(t(e))}catch{}}return null}var jt=Array.isArray,C=r.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,B=o.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,$={pending:!1,data:null,method:null,action:null},vt=[],bt=-1;function g(t){return{current:t}}function U(t){0>bt||(t.current=vt[bt],vt[bt]=null,bt--)}function L(t,e){bt++,vt[bt]=t.current,t.current=e}var Q=g(null),P=g(null),lt=g(null),ot=g(null);function $t(t,e){switch(L(lt,e),L(P,t),L(Q,null),e.nodeType){case 9:case 11:t=(t=e.documentElement)&&(t=t.namespaceURI)?Ed(t):0;break;default:if(t=e.tagName,e=e.namespaceURI)e=Ed(e),t=Td(e,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}U(Q),L(Q,t)}function Ot(){U(Q),U(P),U(lt)}function qa(t){t.memoizedState!==null&&L(ot,t);var e=Q.current,l=Td(e,t.type);e!==l&&(L(P,t),L(Q,l))}function Lu(t){P.current===t&&(U(Q),U(P)),ot.current===t&&(U(ot),Ru._currentValue=$)}var oi,Ff;function Dl(t){if(oi===void 0)try{throw Error()}catch(l){var e=l.stack.trim().match(/\n( *(at )?)/);oi=e&&e[1]||"",Ff=-1<l.stack.indexOf(`
at`)?" (<anonymous>)":-1<l.stack.indexOf("@")?"@unknown:0:0":""}return`
`+oi+t+Ff}var di=!1;function mi(t,e){if(!t||di)return"";di=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var a={DetermineComponentFrameRoot:function(){try{if(e){var j=function(){throw Error()};if(Object.defineProperty(j.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(j,[])}catch(N){var z=N}Reflect.construct(t,[],j)}else{try{j.call()}catch(N){z=N}t.call(j.prototype)}}else{try{throw Error()}catch(N){z=N}(j=t())&&typeof j.catch=="function"&&j.catch(function(){})}}catch(N){if(N&&z&&typeof N.stack=="string")return[N.stack,z.stack]}return[null,null]}};a.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var u=Object.getOwnPropertyDescriptor(a.DetermineComponentFrameRoot,"name");u&&u.configurable&&Object.defineProperty(a.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var n=a.DetermineComponentFrameRoot(),c=n[0],s=n[1];if(c&&s){var h=c.split(`
`),T=s.split(`
`);for(u=a=0;a<h.length&&!h[a].includes("DetermineComponentFrameRoot");)a++;for(;u<T.length&&!T[u].includes("DetermineComponentFrameRoot");)u++;if(a===h.length||u===T.length)for(a=h.length-1,u=T.length-1;1<=a&&0<=u&&h[a]!==T[u];)u--;for(;1<=a&&0<=u;a--,u--)if(h[a]!==T[u]){if(a!==1||u!==1)do if(a--,u--,0>u||h[a]!==T[u]){var M=`
`+h[a].replace(" at new "," at ");return t.displayName&&M.includes("<anonymous>")&&(M=M.replace("<anonymous>",t.displayName)),M}while(1<=a&&0<=u);break}}}finally{di=!1,Error.prepareStackTrace=l}return(l=t?t.displayName||t.name:"")?Dl(l):""}function Qm(t,e){switch(t.tag){case 26:case 27:case 5:return Dl(t.type);case 16:return Dl("Lazy");case 13:return t.child!==e&&e!==null?Dl("Suspense Fallback"):Dl("Suspense");case 19:return Dl("SuspenseList");case 0:case 15:return mi(t.type,!1);case 11:return mi(t.type.render,!1);case 1:return mi(t.type,!0);case 31:return Dl("Activity");default:return""}}function If(t){try{var e="",l=null;do e+=Qm(t,l),l=t,t=t.return;while(t);return e}catch(a){return`
Error generating stack: `+a.message+`
`+a.stack}}var hi=Object.prototype.hasOwnProperty,yi=i.unstable_scheduleCallback,vi=i.unstable_cancelCallback,wm=i.unstable_shouldYield,Zm=i.unstable_requestPaint,ue=i.unstable_now,Vm=i.unstable_getCurrentPriorityLevel,Pf=i.unstable_ImmediatePriority,tr=i.unstable_UserBlockingPriority,Yu=i.unstable_NormalPriority,Km=i.unstable_LowPriority,er=i.unstable_IdlePriority,Jm=i.log,km=i.unstable_setDisableYieldValue,La=null,ne=null;function cl(t){if(typeof Jm=="function"&&km(t),ne&&typeof ne.setStrictMode=="function")try{ne.setStrictMode(La,t)}catch{}}var ie=Math.clz32?Math.clz32:Fm,$m=Math.log,Wm=Math.LN2;function Fm(t){return t>>>=0,t===0?32:31-($m(t)/Wm|0)|0}var Gu=256,Xu=262144,Qu=4194304;function jl(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function wu(t,e,l){var a=t.pendingLanes;if(a===0)return 0;var u=0,n=t.suspendedLanes,c=t.pingedLanes;t=t.warmLanes;var s=a&134217727;return s!==0?(a=s&~n,a!==0?u=jl(a):(c&=s,c!==0?u=jl(c):l||(l=s&~t,l!==0&&(u=jl(l))))):(s=a&~n,s!==0?u=jl(s):c!==0?u=jl(c):l||(l=a&~t,l!==0&&(u=jl(l)))),u===0?0:e!==0&&e!==u&&(e&n)===0&&(n=u&-u,l=e&-e,n>=l||n===32&&(l&4194048)!==0)?e:u}function Ya(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function Im(t,e){switch(t){case 1:case 2:case 4:case 8:case 64:return e+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function lr(){var t=Qu;return Qu<<=1,(Qu&62914560)===0&&(Qu=4194304),t}function gi(t){for(var e=[],l=0;31>l;l++)e.push(t);return e}function Ga(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function Pm(t,e,l,a,u,n){var c=t.pendingLanes;t.pendingLanes=l,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=l,t.entangledLanes&=l,t.errorRecoveryDisabledLanes&=l,t.shellSuspendCounter=0;var s=t.entanglements,h=t.expirationTimes,T=t.hiddenUpdates;for(l=c&~l;0<l;){var M=31-ie(l),j=1<<M;s[M]=0,h[M]=-1;var z=T[M];if(z!==null)for(T[M]=null,M=0;M<z.length;M++){var N=z[M];N!==null&&(N.lane&=-536870913)}l&=~j}a!==0&&ar(t,a,0),n!==0&&u===0&&t.tag!==0&&(t.suspendedLanes|=n&~(c&~e))}function ar(t,e,l){t.pendingLanes|=e,t.suspendedLanes&=~e;var a=31-ie(e);t.entangledLanes|=e,t.entanglements[a]=t.entanglements[a]|1073741824|l&261930}function ur(t,e){var l=t.entangledLanes|=e;for(t=t.entanglements;l;){var a=31-ie(l),u=1<<a;u&e|t[a]&e&&(t[a]|=e),l&=~u}}function nr(t,e){var l=e&-e;return l=(l&42)!==0?1:pi(l),(l&(t.suspendedLanes|e))!==0?0:l}function pi(t){switch(t){case 2:t=1;break;case 8:t=4;break;case 32:t=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:t=128;break;case 268435456:t=134217728;break;default:t=0}return t}function Si(t){return t&=-t,2<t?8<t?(t&134217727)!==0?32:268435456:8:2}function ir(){var t=B.p;return t!==0?t:(t=window.event,t===void 0?32:Kd(t.type))}function cr(t,e){var l=B.p;try{return B.p=t,e()}finally{B.p=l}}var fl=Math.random().toString(36).slice(2),Xt="__reactFiber$"+fl,Ft="__reactProps$"+fl,Fl="__reactContainer$"+fl,bi="__reactEvents$"+fl,th="__reactListeners$"+fl,eh="__reactHandles$"+fl,fr="__reactResources$"+fl,Xa="__reactMarker$"+fl;function xi(t){delete t[Xt],delete t[Ft],delete t[bi],delete t[th],delete t[eh]}function Il(t){var e=t[Xt];if(e)return e;for(var l=t.parentNode;l;){if(e=l[Fl]||l[Xt]){if(l=e.alternate,e.child!==null||l!==null&&l.child!==null)for(t=Od(t);t!==null;){if(l=t[Xt])return l;t=Od(t)}return e}t=l,l=t.parentNode}return null}function Pl(t){if(t=t[Xt]||t[Fl]){var e=t.tag;if(e===5||e===6||e===13||e===31||e===26||e===27||e===3)return t}return null}function Qa(t){var e=t.tag;if(e===5||e===26||e===27||e===6)return t.stateNode;throw Error(f(33))}function ta(t){var e=t[fr];return e||(e=t[fr]={hoistableStyles:new Map,hoistableScripts:new Map}),e}function Yt(t){t[Xa]=!0}var rr=new Set,sr={};function Ul(t,e){ea(t,e),ea(t+"Capture",e)}function ea(t,e){for(sr[t]=e,t=0;t<e.length;t++)rr.add(e[t])}var lh=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u21 Line truncated
`).replace(j0,"")}function bd(t,e){return e=Sd(e),Sd(t)===e}function pt(t,e,l,a,u,n){switch(l){case"children":typeof a=="string"?e==="body"||e==="textarea"&&a===""||aa(t,a):(typeof a=="number"||typeof a=="bigint")&&e!=="body"&&aa(t,""+a);break;case"className":Vu(t,"class",a);break;case"tabIndex":Vu(t,"tabindex",a);break;case"dir":case"role":case"viewBox":case"width":case"height":Vu(t,l,a);break;case"style":Sr(t,a,n);break;case"data":if(e!=="object"){Vu(t,"data",a);break}case"src":case"href":if(a===""&&(e!=="a"||l!=="href")){t.removeAttribute(l);break}if(a==null||typeof a=="function"||typeof a=="symbol"||typeof a=="boolean"){t.removeAttribute(l);break}a=Ju(""+a),t.setAttribute(l,a);break;case"action":case"formAction":if(typeof a=="function"){t.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof n=="function"&&(l==="formAction"?(e!=="input"&&pt(t,e,"name",u.name,u,null),pt(t,e,"formEncType",u.formEncType,u,null),pt(t,e,"formMethod",u.formMethod,u,null),pt(t,e,"formTarget",u.formTarget,u,null)):(pt(t,e,"encType",u.encType,u,null),pt(t,e,"method",u.method,u,null),pt(t,e,"target",u.target,u,null)));if(a==null||typeof a=="symbol"||typeof a=="boolean"){t.removeAttribute(l);break}a=Ju(""+a),t.setAttribute(l,a);break;case"onClick":a!=null&&(t.onclick=Qe);break;case"onScroll":a!=null&&ut("scroll",t);break;case"onScrollEnd":a!=null&&ut("scrollend",t);break;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(f(61));if(l=a.__html,l!=null){if(u.children!=null)throw Error(f(60));t.innerHTML=l}}break;case"multiple":t.multiple=a&&typeof a!="function"&&typeof a!="symbol";break;case"muted":t.muted=a&&typeof a!="function"&&typeof a!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(a==null||typeof a=="function"||typeof a=="boolean"||typeof a=="symbol"){t.removeAttribute("xlink:href");break}l=Ju(""+a),t.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":a!=null&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(l,""+a):t.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":a&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(l,""):t.removeAttribute(l);break;case"capture":case"download":a===!0?t.setAttribute(l,""):a!==!1&&a!=null&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(l,a):t.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":a!=null&&typeof a!="function"&&typeof a!="symbol"&&!isNaN(a)&&1<=a?t.setAttribute(l,a):t.removeAttribute(l);break;case"rowSpan":case"start":a==null||typeof a=="function"||typeof a=="symbol"||isNaN(a)?t.removeAttribute(l):t.setAttribute(l,a);break;case"popover":ut("beforetoggle",t),ut("toggle",t),Zu(t,"popover",a);break;case"xlinkActuate":Xe(t,"http://www.w3.org/1999/xlink","xlink:actuate",a);break;case"xlinkArcrole":Xe(t,"http://www.w3.org/1999/xlink","xlink:arcrole",a);break;case"xlinkRole":Xe(t,"http://www.w3.org/1999/xlink","xlink:role",a);break;case"xlinkShow":Xe(t,"http://www.w3.org/1999/xlink","xlink:show",a);break;case"xlinkTitle":Xe(t,"http://www.w3.org/1999/xlink","xlink:title",a);break;case"xlinkType":Xe(t,"http://www.w3.org/1999/xlink","xlink:type",a);break;case"xmlBase":Xe(t,"http://www.w3.org/XML/1998/namespace","xml:base",a);break;case"xmlLang":Xe(t,"http://www.w3.org/XML/1998/namespace","xml:lang",a);break;case"xmlSpace":Xe(t,"http://www.w3.org/XML/1998/namespace","xml:space",a);break;case"is":Zu(t,"is",a);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=ch.get(l)||l,Zu(t,l,a))}}function df(t,e,l,a,u,n){switch(l){case"style":Sr(t,a,n);break;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(f(61));if(l=a.__html,l!=null){if(u.children!=null)throw Error(f(60));t.innerHTML=l}}break;case"children":typeof a=="string"?aa(t,a):(typeof a=="number"||typeof a=="bigint")&&aa(t,""+a);break;case"onScroll":a!=null&&ut("scroll",t);break;case"onScrollEnd":a!=null&&ut("scrollend",t);break;case"onClick":a!=null&&(t.onclick=Qe);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne Line truncated
* react-router v7.15.1
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/var rm="popstate";function sm(i){return typeof i=="object"&&i!=null&&"pathname"in i&&"search"in i&&"hash"in i&&"state"in i&&"key"in i}function xy(i={}){function r(f,d){var v;let m=(v=d.state)==null?void 0:v.masked,{pathname:b,search:A,hash:x}=m||f.location;return Yf("",{pathname:b,search:A,hash:x},d.state&&d.state.usr||null,d.state&&d.state.key||"default",m?{pathname:f.location.pathname,search:f.location.search,hash:f.location.hash}:void 0)}function o(f,d){return typeof d=="string"?d:Uu(d)}return Ty(r,o,null,i)}function Rt(i,r){if(i===!1||i===null||typeof i>"u")throw new Error(r)}function Ce(i,r){if(!i){typeof console<"u"&&console.warn(r);try{throw new Error(r)}catch{}}}function Ey(){return Math.random().toString(36).substring(2,10)}function om(i,r){return{usr:i.state,key:i.key,idx:r,masked:i.mask?{pathname:i.pathname,search:i.search,hash:i.hash}:void 0}}function Yf(i,r,o=null,f,d){return{pathname:typeof i=="string"?i:i.pathname,search:"",hash:"",...typeof r=="string"?Ha(r):r,state:o,key:r&&r.key||f||Ey(),mask:d}}function Uu({pathname:i="/",search:r="",hash:o=""}){return r&&r!=="?"&&(i+=r.charAt(0)==="?"?r:"?"+r),o&&o!=="#"&&(i+=o.charAt(0)==="#"?o:"#"+o),i}function Ha(i){let r={};if(i){let o=i.indexOf("#");o>=0&&(r.hash=i.substring(o),i=i.substring(0,o));let f=i.indexOf("?");f>=0&&(r.search=i.substring(f),i=i.substring(0,f)),i&&(r.pathname=i)}return r}function Ty(i,r,o,f={}){let{window:d=document.defaultView,v5Compat:m=!1}=f,b=d.history,A="POP",x=null,v=O();v==null&&(v=0,b.replaceState({...b.state,idx:v},""));function O(){return(b.state||{idx:null}).idx}function R(){A="POP";let X=O(),q=X==null?null:X-v;v=X,x&&x({action:A,location:G.location,delta:q})}function H(X,q){A="PUSH";let I=sm(X)?X:Yf(G.location,X,q);v=O()+1;let k=om(I,v),yt=G.createHref(I.mask||I);try{b.pushState(k,"",yt)}catch(dt){if(dt instanceof DOMException&&dt.name==="DataCloneError")throw dt;d.location.assign(yt)}m&&x&&x({action:A,location:G.location,delta:1})}function Z(X,q){A="REPLACE";let I=sm(X)?X:Yf(G.location,X,q);v=O();let k=om(I,v),yt=G.createHref(I.mask||I);b.replaceState(k,"",yt),m&&x&&x({action:A,location:G.location,delta:0})}function K(X){return zy(X)}let G={get action(){return A},get location(){return i(d,b)},listen(X){if(x)throw new Error("A history only accepts one active listener");return d.addEventListener(rm,R),x=X,()=>{d.removeEventListener(rm,R),x=null}},createHref(X){return r(d,X)},createURL:K,encodeLocation(X){let q=K(X);return{pathname:q.pathname,search:q.search,hash:q.hash}},push:H,replace:Z,go(X){return b.go(X)}};return G}function zy(i,r=!1){let o="http://localhost";typeof window<"u"&&(o=window.location.origin!=="null"?window.location.origin:window.location.href),Rt(o,"No window.location.(origin|href) available to create URL");let f=typeof i=="string"?i:Uu(i);return f=f.replace(/ $/,"%20"),!r&&f.startsWith("//")&&(f=o+f),new URL(f,o)}function gm(i,r,o="/"){return Ay(i,r,o,!1)}function Ay(i,r,o,f,d){let m=typeof r=="string"?Ha(r):r,b=nl(m.pathname||"/",o);if(b==null)return null;let A=Ry(i),x=null,v=Ly(b);for(let O=0;x==null&&O<A.length;++O)x=By(A[O],v,f);return x}function Ry(i){let r=pm(i);return Ny(r),r}function pm(i,r=[],o=[],f="",d=!1){let m=(b,A,x=d,v)=>{let O={relativePath:v===void 0?b.path||"":v,caseSensitive:b.caseSensitive===!0,childrenIndex:A,route:b};if(O.relativePath.startsWith("/")){if(!O.relativePath.startsWith(f)&&x)return;Rt(O.relativePath.startsWith(f),`Absolute route path "${O.relativePath}" nested under path "${f}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),O.relativePath=O.relativePath.slice(f.length)}let R=Oe([f,O.relativePath]),H=o.concat(O);b.children&&b.children.length>0&&(Rt(b.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${R}".`),pm(b.children,r,H,R,x)),!(b.path==null&&!b.index)&&r.push({path:R,score:Uy(R,b.index),routesMeta:H})};return i.forEach((b,A)=>{var x;if(b.path===""||!((x=b.path)!=null&&x.includes("?")))m(b,A);else for(let v of Sm(b.path))m(b,A,!0,v)}),r}function Sm(i){let r=i.split("/");if(r.length===0)return[];let[o,...f]=r,d=o.endsWith("?"),m=o.replace(/\?$/,"");if(f.length===0)return d?[m,""]:[m];let b=Sm(f.join("/")),A=[];return A.push(...b.map(x=>x===""?m:[m,x].join("/"))),d&&A.push(...b),A.map(x=>i.startsWith("/")&&x===""?"/":x)}function Ny(i){i.sort((r,o)=>r.score!==o.score?o.score-r.score:Hy(r.routesMeta.map(f=>f.childrenIndex),o.routesMeta.map(f=>f.childrenIndex)))}var _y=/^:[\w-]+$/,My=3,Oy=2,Cy=1,Dy=10,jy=-2,dm=i=>i==="*";function Uy(i,r){let o=i.split("/"),f=o.length;return o.some(dm)&&(f+=jy),r&&(f+=Oy),o.filter(d=>!dm(d)).reduce((d,m)=>d+(_y.test(m)?My:m===""?Cy:Dy),f)}function Hy(i,r){return i.length===r.length&&i.slice(0,-1).every((f,d)=>f===r[d])?i[i.length-1]-r[r.length-1]:0}function By(i,r,o=!1){let{routesMeta:f}=i,d={},m="/",b=[];for(let A=0;A<f.length;++A){let x=f[A],v=A===f.length-1,O=m==="/"?r:r.slice(m.length)||"/",R=ci({path:x.relativePath,c Line truncated
Please change the parent <Route path="${q}"> to <Route path="${q==="/"?"*":`${q}/*`}">.`)}let O=je(),R;if(r){let q=typeof r=="string"?Ha(r):r;Rt(x==="/"||((X=q.pathname)==null?void 0:X.startsWith(x)),`When overriding the location using \`<Routes location>\` 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 "${x}" but pathname "${q.pathname}" was given in the \`location\` prop.`),R=q}else R=O;let H=R.pathname||"/",Z=H;if(x!=="/"){let q=x.replace(/^\//,"").split("/");Z="/"+H.replace(/^\//,"").split("/").slice(q.length).join("/")}let K=o&&o.state.matches.length?o.state.matches.map(q=>Object.assign(q,{route:o.manifest[q.route.id]||q.route})):gm(i,{pathname:Z});Ce(v||K!=null,`No routes matched location "${R.pathname}${R.search}${R.hash}" `),Ce(K==null||K[K.length-1].route.element!==void 0||K[K.length-1].route.Component!==void 0||K[K.length-1].route.lazy!==void 0,`Matched leaf route at location "${R.pathname}${R.search}${R.hash}" does not have an element or Component. This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.`);let G=dv(K&&K.map(q=>Object.assign({},q,{params:Object.assign({},b,q.params),pathname:Oe([x,f.encodeLocation?f.encodeLocation(q.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:q.pathname]),pathnameBase:q.pathnameBase==="/"?x:Oe([x,f.encodeLocation?f.encodeLocation(q.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:q.pathnameBase])})),d,o);return r&&G?_.createElement(Hu.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",mask:void 0,...R},navigationType:"POP"}},G):G}function fv(){let i=gv(),r=Ky(i)?`${i.status} ${i.statusText}`:i instanceof Error?i.message:JSON.stringify(i),o=i instanceof Error?i.stack:null,f="rgba(200,200,200, 0.5)",d={padding:"0.5rem",backgroundColor:f},m={padding:"2px 4px",backgroundColor:f},b=null;return console.error("Error handled by React Router default ErrorBoundary:",i),b=_.createElement(_.Fragment,null,_.createElement("p",null,"💿 Hey developer 👋"),_.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",_.createElement("code",{style:m},"ErrorBoundary")," or"," ",_.createElement("code",{style:m},"errorElement")," prop on your route.")),_.createElement(_.Fragment,null,_.createElement("h2",null,"Unexpected Application Error!"),_.createElement("h3",{style:{fontStyle:"italic"}},r),o?_.createElement("pre",{style:d},o):null,b)}var rv=_.createElement(fv,null),Dm=class extends _.Component{constructor(i){super(i),this.state={location:i.location,revalidation:i.revalidation,error:i.error}}static getDerivedStateFromError(i){return{error:i}}static getDerivedStateFromProps(i,r){return r.location!==i.location||r.revalidation!=="idle"&&i.revalidation==="idle"?{error:i.error,location:i.location,revalidation:i.revalidation}:{error:i.error!==void 0?i.error:r.error,location:r.location,revalidation:i.revalidation||r.revalidation}}componentDidCatch(i,r){this.props.onError?this.props.onError(i,r):console.error("React Router caught the following error during render",i)}render(){let i=this.state.error;if(this.context&&typeof i=="object"&&i&&"digest"in i&&typeof i.digest=="string"){const o=ev(i.digest);o&&(i=o)}let r=i!==void 0?_.createElement(De.Provider,{value:this.props.routeContext},_.createElement(Zf.Provider,{value:i,children:this.props.component})):this.props.children;return this.context?_.createElement(sv,{error:i},r):r}};Dm.contextType=Am;var qf=new WeakMap;function sv({children:i,error:r}){let{basename:o}=_.useContext(Re);if(typeof r=="object"&&r&&"digest"in r&&typeof r.digest=="string"){let f=tv(r.digest);if(f){let d=qf.get(r);if(d)throw d;let m=Tm(f.location,o);if(Em&&!qf.get(r))if(m.isExternal||f.reloadDocument)window.location.href=m.absoluteURL||m.to;else{const b=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(m.to,{replace:f.replace}));throw qf.set(r,b),b}return _.createElement("meta",{httpEquiv:"refresh",content:`0;url=${m.absoluteURL||m.to}`})}}return i}function ov({routeContext:i,match:r,children:o}){let f=_.useContext(Ba);return f&&f.static&&f.staticContext&&(r.route.errorElement||r.route.ErrorBoundary)&&(f.staticContext._deepestRenderedBoundaryId=r.route.id),_.createElement(De.Provider,{value:i},o)}function dv(i,r=[],o){let f=o==null?void 0:o.state;if(i==null){if(!f)return null;if(f.errors)i=f.matches;else if(r.length===0&&!f.initialized&&f.matches.length>0)i=f.matches;else return null}let d=i,m=f==null?void 0:f.errors;if(m!=null){let O=d.findIndex(R=>R.route.id&&(m==null?void 0:m[R.route.id])!==void 0);Rt(O>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(m).join(",")}`),d=d.slice(0,Math.min(d.length,O+1))}let b=!1,A=-1;if(o&&f){b=f.renderFallback;for(let O=0;O<d.length;O++){let R=d[O];if((R.route.HyLine truncated
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const ug=i=>i.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),ng=i=>i.replace(/^([A-Z])|[\s-_]+(\w)/g,(r,o,f)=>f?f.toUpperCase():o.toLowerCase()),vm=i=>{const r=ng(i);return r.charAt(0).toUpperCase()+r.slice(1)},qm=(...i)=>i.filter((r,o,f)=>!!r&&r.trim()!==""&&f.indexOf(r)===o).join(" ").trim();/**
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/var ig={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/**
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const cg=_.forwardRef(({color:i="currentColor",size:r=24,strokeWidth:o=2,absoluteStrokeWidth:f,className:d="",children:m,iconNode:b,...A},x)=>_.createElement("svg",{ref:x,...ig,width:r,height:r,stroke:i,strokeWidth:f?Number(o)*24/Number(r):o,className:qm("lucide",d),...A},[...b.map(([v,O])=>_.createElement(v,O)),...Array.isArray(m)?m:[m]]));/**
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const il=(i,r)=>{const o=_.forwardRef(({className:f,...d},m)=>_.createElement(cg,{ref:m,iconNode:r,className:qm(`lucide-${ug(vm(i))}`,`lucide-${i}`,f),...d}));return o.displayName=vm(i),o};/**
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const fg=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],rg=il("arrow-left",fg);/**
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const sg=[["rect",{width:"16",height:"10",x:"2",y:"7",rx:"2",ry:"2",key:"1w10f2"}],["line",{x1:"22",x2:"22",y1:"11",y2:"13",key:"4dh1rd"}]],Lm=il("battery",sg);/**
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const og=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",key:"afitv7"}],["path",{d:"M3 9h18",key:"1pudct"}],["path",{d:"M3 15h18",key:"5xshup"}],["path",{d:"M9 3v18",key:"fh3hqa"}],["path",{d:"M15 3v18",key:"14nvp0"}]],dg=il("grid-3x3",og);/**
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const mg=[["path",{d:"M3 12h.01",key:"nlz23k"}],["path",{d:"M3 18h.01",key:"1tta3j"}],["path",{d:"M3 6h.01",key:"1rqtza"}],["path",{d:"M8 12h13",key:"1za7za"}],["path",{d:"M8 18h13",key:"1lx6n3"}],["path",{d:"M8 6h13",key:"ik3vkj"}]],hg=il("list",mg);/**
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const yg=[["path",{d:"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z",key:"a7tn18"}]],vg=il("moon",yg);/**
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const gg=[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["path",{d:"m21 21-4.3-4.3",key:"1qie3q"}]],pg=il("search",gg);/**
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const Sg=[["circle",{cx:"12",cy:"12",r:"4",key:"4exip2"}],["path",{d:"M12 2v2",key:"tus03m"}],["path",{d:"M12 20v2",key:"1lh1kg"}],["path",{d:"m4.93 4.93 1.41 1.41",key:"149t6j"}],["path",{d:"m17.66 17.66 1.41 1.41",key:"ptbguv"}],["path",{d:"M2 12h2",key:"1t8f8n"}],["path",{d:"M20 12h2",key:"1q8mjw"}],["path",{d:"m6.34 17.66-1.41 1.41",key:"1m8zz5"}],["path",{d:"m19.07 4.93-1.41 1.41",key:"1shlcs"}]],Ym=il("sun",Sg);/**
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const bg=[["path",{d:"M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2",key:"wrbu53"}],["path",{d:"M15 18H9",key:"1lyqi6"}],["path",{d:"M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14",key:"lysw3i"}],["circle",{cx:"17",cy:"18",r:"2",key:"332jqn"}],["circle",{cx:"7",cy:"18",r:"2",key:"19iecd"}]],xg=il("truck",bg);/**
* @license lucide-react v0.487.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const Eg=[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]],Tg=il("zap",Eg);function zg(){const i=je(),[r,o]=_.useState(!0);_.useEffect(()=>{const d=localStorage.getItem("theme")==="dark"||!localStorage.getItem("theme")&&window.matchMedia("(prefers-color-scheme: dark)").matches;o(d),document.documentElement.classList.toggle("dark",d)},[]);const f=()=>{const d=!r;o(d),localStorage.setItem("theme",d?"dark":"light"),document.documentElement.classList.toggle("dark",d)};return p.jsx("header",{className:"sticky top-0 z-50 bg-white/95 dark:bg-gray-950/95 backdrop-blur border-b border-gray-200 dark:border-gray-800",children:p.jsxs("div",{className:"max-w-7xl mx-auto px-6 h-16 flex items-center justify-between",children:[p.jsxs(Ge,{to:"/",className:"flex items-center gap-3",children:[p.jsx("div",{className:"w-9 h-9 bg-sky-500 rounded-xl flex items-center justify-center",children:p.jsx(Lm,{className:"w-5 h-5 text-white"})}),p.jsxs("div",{children:[p.jsx("div",{className:"font-semibold tracking-tight text-xl",children:"PowerGrid"}),p.jsx("div",{className:"text-[10px] text-gray-500 -mt-1",children:"INDUSTRIAL BATTERIES"})]})]}),p.jsxs("nav",{className:"flex items-center gap-8 text-sm font-medium",children:[p.jsx(Ge,{to:"/catalog",className:`transition-colors hover:text-sky-500 ${i.pathname==="/catalog"?"text-sky-500":"text-gray-600 dark:text-gray-400"}`,children:"Каталог"}),p.jsx("a",{href:"#about",className:"text-gray-600 dark:text-gray-400 hover:text-sky-500 transition-colors",children:"О проекте"})]}),p.jsxs("div",{className:"flex items-center gap-3",children:[p.jsx("button",{onClick:f,className:"p-2 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors","aria-label":"Toggle theme",children:r?p.jsx(Ym,{className:"w-4 h-4"}):p.jsx(vg,{className:"w-4 h-4"})}),p.jsx(Ge,{to:"/catalog",className:"px-5 py-2 bg-gray-900 dark:bg-white text-white dark:text-gray-900 rounded-2xl text-sm font-medium hover:bg-black dark:hover:bg-gray-200 transition-colors",children:"Перейти в каталог"})]})]})})}function Ag(){return p.jsx("footer",{className:"border-t border-gray-200 dark:border-gray-800 py-8 text-sm text-gray-500",children:p.jsxs("div",{className:"max-w-7xl mx-auto px-6 flex flex-col md:flex-row justify-between gap-4",children:[p.jsxs("div",{children:["© ",new Date().getFullYear()," PowerGrid. Промышленные аккумуляторы."]}),p.jsxs("div",{className:"flex gap-6",children:[p.jsx("a",{href:"#",className:"hover:text-gray-700 dark:hover:text-gray-300",children:"Политика конфиденциальности"}),p.jsx("a",{href:"#",className:"hover:text-gray-700 dark:hover:text-gray-300",children:"Контакты"})]})]})})}function Rg(){return p.jsxs("div",{className:"min-h-screen flex flex-col",children:[p.jsx(zg,{}),p.jsx("main",{className:"flex-1",children:p.jsx(bv,{})}),p.jsx(Ag,{})]})}function Gm({value:i,onChange:r}){return p.jsxs("div",{className:"relative w-full max-w-md",children:[p.jsx(pg,{className:"absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400"}),p.jsx("input",{type:"text",placeholder:"Поиск по модели, бренду, применению...",value:i,onChange:o=>r(o.target.value),className:"w-full pl-11 pr-4 py-3 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-2xl focus:outline-none focus:ring-2 focus:ring-sky-500 text-sm"})]})}function ui({title:i,count:r,icon:o,to:f}){return p.jsx(Ge,{to:f,className:"group block p-6 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-3xl hover:border-sky-500/50 transition-all",children:p.jsxs("div",{className:"flex items-center gap-4",children:[p.jsx("div",{className:"text-sky-500",children:o}),p.jsxs("div",{children:[p.jsx("div",{className:"font-semibold text-lg tracking-tight",children:i}),p.jsxs("div",{className:"text-sm text-gray-500",children:[r," моделей"]})]})]})})}function Ng(){const[i,r]=_.useState("");return p.jsxs("div",{children:[p.jsxs("section",{className:"max-w-5xl mx-auto px-6 pt-20 pb-16 text-center",children:[p.jsx("div",{className:"inline-block px-4 py-1 rounded-full bg-sky-100 dark:bg-sky-950 text-sky-600 dark:text-sky-400 text-sm font-medium mb-4",children:"2026 • Industrial Tech"}),p.jsxs("h1",{className:"text-6xl md:text-7xl font-semibold tracking-tighter leading-none mb-6",children:["Промышленные",p.jsx("br",{}),"аккумуляторы",p.jsx("br",{}),"в одном месте"]}),p.jsx("p",{className:"text-xl text-gray-600 dark:text-gray-400 max-w-md mx-auto mb-10",children:"Сравнивайте, выбирайте и заказывайте LiFePO4, AGM, GEL и свинцово-кислотные батареи для любой отрасли."}),p.jsx("div",{className:"max-w-md mx-auto",children:p.jsx(Gm,{value:i,onChange:r})}),p.jsx("div",{Line truncated
+15
View File
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="ru" class="dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/battery.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Industrial Battery Aggregator — поиск, сравнение и подбор промышленных аккумуляторов LiFePO4, свинцово-кислотных, тяговых, гелевых и АКБ для ИБП и солнечных станций." />
<title>PowerGrid — Агрегатор промышленных аккумуляторов</title>
<script type="module" crossorigin src="/assets/index-sqybokJ8.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BMnNlCpS.css">
</head>
<body class="bg-white dark:bg-gray-950 text-gray-900 dark:text-gray-100 antialiased">
<div id="root"></div>
</body>
</html>
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="ru" class="dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/battery.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Industrial Battery Aggregator — поиск, сравнение и подбор промышленных аккумуляторов LiFePO4, свинцово-кислотных, тяговых, гелевых и АКБ для ИБП и солнечных станций." />
<title>PowerGrid — Агрегатор промышленных аккумуляторов</title>
</head>
<body class="bg-white dark:bg-gray-950 text-gray-900 dark:text-gray-100 antialiased">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+15
View File
@@ -0,0 +1,15 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
+2558
View File
File diff suppressed because it is too large. Load diff
+26
View File
@@ -0,0 +1,26 @@
{
"name": "industrial-battery-aggregator",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"lucide-react": "^0.487.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.5.0"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.4",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"@vitejs/plugin-react": "^4.4.1",
"tailwindcss": "^4.1.4",
"typescript": "~5.8.3",
"vite": "^6.3.2"
}
}
+17
View File
@@ -0,0 +1,17 @@
import { Routes, Route } from 'react-router-dom'
import Layout from './components/Layout'
import Home from './pages/Home'
import Catalog from './pages/Catalog'
import Product from './pages/Product'
export default function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/catalog" element={<Catalog />} />
<Route path="/product/:id" element={<Product />} />
</Route>
</Routes>
)
}
+52
View File
@@ -0,0 +1,52 @@
import { Battery } from '../types/battery'
import { Link } from 'react-router-dom'
interface BatteryCardProps {
battery: Battery
}
export default function BatteryCard({ battery }: BatteryCardProps) {
return (
<Link to={`/product/${battery.id}`} className="group block">
<div className="card bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-3xl overflow-hidden">
<div className="aspect-[16/10] bg-gray-100 dark:bg-gray-800 relative">
<img
src={battery.image}
alt={battery.name}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
<div className="absolute top-4 right-4 px-3 py-1 bg-white/90 dark:bg-gray-950/90 backdrop-blur rounded-full text-xs font-medium">
{battery.chemistry}
</div>
</div>
<div className="p-5 space-y-3">
<div>
<div className="font-semibold tracking-tight text-lg">{battery.name}</div>
<div className="text-sm text-gray-500">{battery.brand}</div>
</div>
<div className="flex items-baseline gap-1 text-xl font-semibold tabular-nums">
{battery.price.toLocaleString('ru-RU')}
<span className="text-sm font-normal text-gray-500">₽</span>
</div>
<div className="grid grid-cols-3 gap-3 text-xs pt-2 border-t border-gray-100 dark:border-gray-800">
<div>
<div className="text-gray-500">Напряжение</div>
<div className="font-medium">{battery.voltage} В</div>
</div>
<div>
<div className="text-gray-500">Ёмкость</div>
<div className="font-medium">{battery.capacity} А·ч</div>
</div>
<div>
<div className="text-gray-500">Циклы</div>
<div className="font-medium">{battery.cycles}</div>
</div>
</div>
</div>
</div>
</Link>
)
}
+36
View File
@@ -0,0 +1,36 @@
import { Battery } from '../types/battery'
interface BatteryTableProps {
batteries: Battery[]
}
export default function BatteryTable({ batteries }: BatteryTableProps) {
return (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-gray-200 dark:border-gray-700 text-left text-gray-500">
<th className="py-3 font-medium">Модель</th>
<th className="py-3 font-medium">Напряжение</th>
<th className="py-3 font-medium">Ёмкость</th>
<th className="py-3 font-medium">Технология</th>
<th className="py-3 font-medium">Циклы</th>
<th className="py-3 font-medium text-right">Цена</th>
</tr>
</thead>
<tbody>
{batteries.map(b => (
<tr key={b.id} className="table-row border-b border-gray-100 dark:border-gray-800">
<td className="py-4 font-medium">{b.name}</td>
<td className="py-4 tabular-nums">{b.voltage} В</td>
<td className="py-4 tabular-nums">{b.capacity} А·ч</td>
<td className="py-4">{b.chemistry}</td>
<td className="py-4 tabular-nums">{b.cycles}</td>
<td className="py-4 text-right font-semibold tabular-nums">{b.price.toLocaleString('ru-RU')} ₽</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
+22
View File
@@ -0,0 +1,22 @@
import { Link } from 'react-router-dom'
interface CategoryCardProps {
title: string
count: number
icon: React.ReactNode
to: string
}
export default function CategoryCard({ title, count, icon, to }: CategoryCardProps) {
return (
<Link to={to} className="group block p-6 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-3xl hover:border-sky-500/50 transition-all">
<div className="flex items-center gap-4">
<div className="text-sky-500">{icon}</div>
<div>
<div className="font-semibold text-lg tracking-tight">{title}</div>
<div className="text-sm text-gray-500">{count} моделей</div>
</div>
</div>
</Link>
)
}
+132
View File
@@ -0,0 +1,132 @@
import { FilterState } from '../types/battery'
interface FilterPanelProps {
filters: FilterState
onChange: (filters: FilterState) => void
}
const voltages = [2, 6, 12, 24, 36, 48, 72, 80]
const chemistries = ['LiFePO4', 'AGM', 'GEL', 'Lead-Acid', 'Ni-Cd']
const applications = ['Solar', 'UPS', 'Marine', 'Forklift', 'Telecom', 'Industrial']
export default function FilterPanel({ filters, onChange }: FilterPanelProps) {
const toggleVoltage = (v: number) => {
const newVoltages = filters.voltage.includes(v)
? filters.voltage.filter(x => x !== v)
: [...filters.voltage, v]
onChange({ ...filters, voltage: newVoltages })
}
const toggleChemistry = (c: string) => {
const newChem = filters.chemistry.includes(c)
? filters.chemistry.filter(x => x !== c)
: [...filters.chemistry, c]
onChange({ ...filters, chemistry: newChem })
}
const toggleApp = (a: string) => {
const newApps = filters.application.includes(a)
? filters.application.filter(x => x !== a)
: [...filters.application, a]
onChange({ ...filters, application: newApps })
}
return (
<div className="space-y-8">
{/* Voltage */}
<div>
<div className="text-sm font-medium mb-3 text-gray-500 dark:text-gray-400">НАПРЯЖЕНИЕ (В)</div>
<div className="flex flex-wrap gap-2">
{voltages.map(v => (
<button
key={v}
onClick={() => toggleVoltage(v)}
className={`filter-chip px-4 py-1.5 text-sm rounded-xl border transition-all ${
filters.voltage.includes(v)
? 'active border-sky-500'
: 'border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800'
}`}
>
{v}В
</button>
))}
</div>
</div>
{/* Capacity Range */}
<div>
<div className="text-sm font-medium mb-3 text-gray-500 dark:text-gray-400">ЁМКОСТЬ (А·Ч)</div>
<div className="flex gap-3">
<input
type="number"
placeholder="От"
value={filters.capacityMin || ''}
onChange={(e) => onChange({ ...filters, capacityMin: parseInt(e.target.value) || 0 })}
className="w-24 px-3 py-2 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl text-sm"
/>
<input
type="number"
placeholder="До"
value={filters.capacityMax || ''}
onChange={(e) => onChange({ ...filters, capacityMax: parseInt(e.target.value) || 9999 })}
className="w-24 px-3 py-2 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl text-sm"
/>
</div>
</div>
{/* Chemistry */}
<div>
<div className="text-sm font-medium mb-3 text-gray-500 dark:text-gray-400">ТЕХНОЛОГИЯ</div>
<div className="flex flex-wrap gap-2">
{chemistries.map(c => (
<button
key={c}
onClick={() => toggleChemistry(c)}
className={`filter-chip px-4 py-1.5 text-sm rounded-xl border transition-all ${
filters.chemistry.includes(c)
? 'active border-sky-500'
: 'border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800'
}`}
>
{c}
</button>
))}
</div>
</div>
{/* Application */}
<div>
<div className="text-sm font-medium mb-3 text-gray-500 dark:text-gray-400">ПРИМЕНЕНИЕ</div>
<div className="flex flex-wrap gap-2">
{applications.map(a => (
<button
key={a}
onClick={() => toggleApp(a)}
className={`filter-chip px-4 py-1.5 text-sm rounded-xl border transition-all ${
filters.application.includes(a)
? 'active border-sky-500'
: 'border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800'
}`}
>
{a}
</button>
))}
</div>
</div>
<button
onClick={() => onChange({
search: '',
voltage: [],
capacityMin: 0,
capacityMax: 9999,
chemistry: [],
application: []
})}
className="text-sm text-sky-500 hover:underline"
>
Сбросить все фильтры
</button>
</div>
)
}
+13
View File
@@ -0,0 +1,13 @@
export default function Footer() {
return (
<footer className="border-t border-gray-200 dark:border-gray-800 py-8 text-sm text-gray-500">
<div className="max-w-7xl mx-auto px-6 flex flex-col md:flex-row justify-between gap-4">
<div>© {new Date().getFullYear()} PowerGrid. Промышленные аккумуляторы.</div>
<div className="flex gap-6">
<a href="#" className="hover:text-gray-700 dark:hover:text-gray-300">Политика конфиденциальности</a>
<a href="#" className="hover:text-gray-700 dark:hover:text-gray-300">Контакты</a>
</div>
</div>
</footer>
)
}
+64
View File
@@ -0,0 +1,64 @@
import { Link, useLocation } from 'react-router-dom'
import { Battery, Sun, Moon } from 'lucide-react'
import { useState, useEffect } from 'react'
export default function Header() {
const location = useLocation()
const [dark, setDark] = useState(true)
useEffect(() => {
const isDark = localStorage.getItem('theme') === 'dark' ||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)
setDark(isDark)
document.documentElement.classList.toggle('dark', isDark)
}, [])
const toggleTheme = () => {
const newDark = !dark
setDark(newDark)
localStorage.setItem('theme', newDark ? 'dark' : 'light')
document.documentElement.classList.toggle('dark', newDark)
}
return (
<header className="sticky top-0 z-50 bg-white/95 dark:bg-gray-950/95 backdrop-blur border-b border-gray-200 dark:border-gray-800">
<div className="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
<Link to="/" className="flex items-center gap-3">
<div className="w-9 h-9 bg-sky-500 rounded-xl flex items-center justify-center">
<Battery className="w-5 h-5 text-white" />
</div>
<div>
<div className="font-semibold tracking-tight text-xl">PowerGrid</div>
<div className="text-[10px] text-gray-500 -mt-1">INDUSTRIAL BATTERIES</div>
</div>
</Link>
<nav className="flex items-center gap-8 text-sm font-medium">
<Link
to="/catalog"
className={`transition-colors hover:text-sky-500 ${location.pathname === '/catalog' ? 'text-sky-500' : 'text-gray-600 dark:text-gray-400'}`}
>
Каталог
</Link>
<a href="#about" className="text-gray-600 dark:text-gray-400 hover:text-sky-500 transition-colors">О проекте</a>
</nav>
<div className="flex items-center gap-3">
<button
onClick={toggleTheme}
className="p-2 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
aria-label="Toggle theme"
>
{dark ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
</button>
<Link
to="/catalog"
className="px-5 py-2 bg-gray-900 dark:bg-white text-white dark:text-gray-900 rounded-2xl text-sm font-medium hover:bg-black dark:hover:bg-gray-200 transition-colors"
>
Перейти в каталог
</Link>
</div>
</div>
</header>
)
}
+15
View File
@@ -0,0 +1,15 @@
import { Outlet } from 'react-router-dom'
import Header from './Header'
import Footer from './Footer'
export default function Layout() {
return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-1">
<Outlet />
</main>
<Footer />
</div>
)
}
+21
View File
@@ -0,0 +1,21 @@
import { Search } from 'lucide-react'
interface SearchBarProps {
value: string
onChange: (value: string) => void
}
export default function SearchBar({ value, onChange }: SearchBarProps) {
return (
<div className="relative w-full max-w-md">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
type="text"
placeholder="Поиск по модели, бренду, применению..."
value={value}
onChange={(e) => onChange(e.target.value)}
className="w-full pl-11 pr-4 py-3 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-2xl focus:outline-none focus:ring-2 focus:ring-sky-500 text-sm"
/>
</div>
)
}
+79
View File
@@ -0,0 +1,79 @@
import { Battery } from '../types/battery'
export const batteries: Battery[] = [
{
id: 1,
name: "LiFePO4 12V 100Ah",
brand: "PowerCell",
voltage: 12,
capacity: 100,
chemistry: "LiFePO4",
application: ["Solar", "UPS", "Marine"],
weight: 10.5,
dimensions: "330×173×220",
cycles: 6000,
price: 24500,
image: "https://picsum.photos/id/1015/400/300",
description: "Высококачественный литий-железо-фосфатный аккумулятор для солнечных станций и ИБП."
},
{
id: 2,
name: "AGM 12V 200Ah",
brand: "Delta",
voltage: 12,
capacity: 200,
chemistry: "AGM",
application: ["UPS", "Telecom"],
weight: 58,
dimensions: "522×240×218",
cycles: 1200,
price: 18900,
image: "https://picsum.photos/id/160/400/300",
description: "Герметичный AGM аккумулятор для систем бесперебойного питания и телекоммуникаций."
},
{
id: 3,
name: "GEL 12V 150Ah",
brand: "Outdo",
voltage: 12,
capacity: 150,
chemistry: "GEL",
application: ["Solar", "UPS"],
weight: 43,
dimensions: "483×170×240",
cycles: 900,
price: 17200,
image: "https://picsum.photos/id/201/400/300",
description: "Гелевый аккумулятор с увеличенным ресурсом для солнечных и стационарных систем."
},
{
id: 4,
name: "LiFePO4 48V 200Ah",
brand: "RoyPow",
voltage: 48,
capacity: 200,
chemistry: "LiFePO4",
application: ["Forklift", "Solar"],
weight: 92,
dimensions: "600×300×280",
cycles: 5000,
price: 128000,
image: "https://picsum.photos/id/251/400/300",
description: "Тяговый литиевый аккумулятор для вилочных погрузчиков и накопителей энергии."
},
{
id: 5,
name: "Lead-Acid 2V 1000Ah",
brand: "Fiamm",
voltage: 2,
capacity: 1000,
chemistry: "Lead-Acid",
application: ["UPS", "Industrial"],
weight: 65,
dimensions: "210×172×330",
cycles: 800,
price: 24500,
image: "https://picsum.photos/id/180/400/300",
description: "Стационарный свинцово-кислотный элемент для крупных ИБП и энергосистем."
}
]
+41
View File
@@ -0,0 +1,41 @@
@tailwind utilities;
:root {
--primary: #0ea5e9;
}
.dark {
color-scheme: dark;
}
.card {
transition: transform 0.2s cubic-bezier(0.23, 1, 0.32, 1),
box-shadow 0.2s cubic-bezier(0.23, 1, 0.32, 1);
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}
.table-row {
transition: background-color 0.1s ease;
}
.table-row:hover {
background-color: rgb(243 244 246);
}
.dark .table-row:hover {
background-color: rgb(31 41 55);
}
.filter-chip {
transition: all 0.1s ease;
}
.filter-chip.active {
background-color: #0ea5e9;
color: white;
border-color: #0ea5e9;
}
+10
View File
@@ -0,0 +1,10 @@
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App.tsx'
import './index.css'
createRoot(document.getElementById('root')!).render(
<BrowserRouter>
<App />
</BrowserRouter>
)
+102
View File
@@ -0,0 +1,102 @@
import { useState, useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'
import { batteries } from '../data/batteries'
import { FilterState } from '../types/battery'
import FilterPanel from '../components/FilterPanel'
import BatteryCard from '../components/BatteryCard'
import BatteryTable from '../components/BatteryTable'
import SearchBar from '../components/SearchBar'
import { Grid, List } from 'lucide-react'
export default function Catalog() {
const [searchParams] = useSearchParams()
const initialApp = searchParams.get('app') || ''
const [filters, setFilters] = useState<FilterState>({
search: '',
voltage: [],
capacityMin: 0,
capacityMax: 9999,
chemistry: [],
application: initialApp ? [initialApp] : []
})
const [view, setView] = useState<'grid' | 'table'>('grid')
const filtered = useMemo(() => {
return batteries.filter(b => {
const matchSearch =
b.name.toLowerCase().includes(filters.search.toLowerCase()) ||
b.brand.toLowerCase().includes(filters.search.toLowerCase()) ||
b.description.toLowerCase().includes(filters.search.toLowerCase())
const matchVoltage = filters.voltage.length === 0 || filters.voltage.includes(b.voltage)
const matchCapacity = b.capacity >= (filters.capacityMin || 0) && b.capacity <= (filters.capacityMax || 9999)
const matchChemistry = filters.chemistry.length === 0 || filters.chemistry.includes(b.chemistry)
const matchApp = filters.application.length === 0 ||
b.application.some(app => filters.application.includes(app))
return matchSearch && matchVoltage && matchCapacity && matchChemistry && matchApp
})
}, [filters])
return (
<div className="max-w-7xl mx-auto px-6 py-10">
<div className="flex flex-col lg:flex-row gap-10">
{/* Sidebar Filters */}
<div className="lg:w-72 shrink-0">
<div className="sticky top-20">
<div className="font-semibold text-lg mb-6 tracking-tight">Фильтры</div>
<FilterPanel filters={filters} onChange={setFilters} />
</div>
</div>
{/* Main Content */}
<div className="flex-1">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8">
<div>
<div className="font-semibold text-3xl tracking-tighter">Каталог аккумуляторов</div>
<div className="text-gray-500 mt-1">{filtered.length} результатов</div>
</div>
<div className="flex items-center gap-3">
<SearchBar
value={filters.search}
onChange={(v) => setFilters({ ...filters, search: v })}
/>
<div className="flex border border-gray-200 dark:border-gray-700 rounded-2xl overflow-hidden">
<button
onClick={() => setView('grid')}
className={`p-3 ${view === 'grid' ? 'bg-gray-100 dark:bg-gray-800' : ''}`}
>
<Grid className="w-4 h-4" />
</button>
<button
onClick={() => setView('table')}
className={`p-3 ${view === 'table' ? 'bg-gray-100 dark:bg-gray-800' : ''}`}
>
<List className="w-4 h-4" />
</button>
</div>
</div>
</div>
{view === 'grid' ? (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
{filtered.map(battery => (
<BatteryCard key={battery.id} battery={battery} />
))}
</div>
) : (
<BatteryTable batteries={filtered} />
)}
{filtered.length === 0 && (
<div className="text-center py-20 text-gray-500">
Ничего не найдено. Попробуйте изменить фильтры.
</div>
)}
</div>
</div>
</div>
)
}
+85
View File
@@ -0,0 +1,85 @@
import { Link } from 'react-router-dom'
import { Battery, Zap, Truck, Sun } from 'lucide-react'
import SearchBar from '../components/SearchBar'
import CategoryCard from '../components/CategoryCard'
import { useState } from 'react'
export default function Home() {
const [search, setSearch] = useState('')
return (
<div>
{/* Hero */}
<section className="max-w-5xl mx-auto px-6 pt-20 pb-16 text-center">
<div className="inline-block px-4 py-1 rounded-full bg-sky-100 dark:bg-sky-950 text-sky-600 dark:text-sky-400 text-sm font-medium mb-4">
2026 • Industrial Tech
</div>
<h1 className="text-6xl md:text-7xl font-semibold tracking-tighter leading-none mb-6">
Промышленные<br />аккумуляторы<br />в одном месте
</h1>
<p className="text-xl text-gray-600 dark:text-gray-400 max-w-md mx-auto mb-10">
Сравнивайте, выбирайте и заказывайте LiFePO4, AGM, GEL и свинцово-кислотные батареи для любой отрасли.
</p>
<div className="max-w-md mx-auto">
<SearchBar value={search} onChange={setSearch} />
</div>
<div className="mt-4">
<Link to="/catalog" className="text-sm text-sky-500 hover:underline">Или перейдите сразу в каталог →</Link>
</div>
</section>
{/* Categories */}
<section className="max-w-6xl mx-auto px-6 pb-20">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<CategoryCard
title="Солнечные станции"
count={124}
icon={<Sun className="w-6 h-6" />}
to="/catalog?app=Solar"
/>
<CategoryCard
title="ИБП и резерв"
count={89}
icon={<Zap className="w-6 h-6" />}
to="/catalog?app=UPS"
/>
<CategoryCard
title="Тяговые (погрузчики)"
count={67}
icon={<Truck className="w-6 h-6" />}
to="/catalog?app=Forklift"
/>
<CategoryCard
title="Морские и RV"
count={41}
icon={<Battery className="w-6 h-6" />}
to="/catalog?app=Marine"
/>
</div>
</section>
{/* Stats */}
<section className="border-t border-gray-200 dark:border-gray-800 py-12">
<div className="max-w-6xl mx-auto px-6 grid grid-cols-2 md:grid-cols-4 gap-y-8 text-center">
<div>
<div className="text-4xl font-semibold tabular-nums">480</div>
<div className="text-sm text-gray-500 mt-1">моделей в каталоге</div>
</div>
<div>
<div className="text-4xl font-semibold tabular-nums">38</div>
<div className="text-sm text-gray-500 mt-1">поставщиков</div>
</div>
<div>
<div className="text-4xl font-semibold tabular-nums">12 400</div>
<div className="text-sm text-gray-500 mt-1">заказов в 2025</div>
</div>
<div>
<div className="text-4xl font-semibold tabular-nums">4.8</div>
<div className="text-sm text-gray-500 mt-1">средняя оценка</div>
</div>
</div>
</section>
</div>
)
}
+79
View File
@@ -0,0 +1,79 @@
import { useParams, Link } from 'react-router-dom'
import { batteries } from '../data/batteries'
import { ArrowLeft } from 'lucide-react'
export default function Product() {
const { id } = useParams()
const battery = batteries.find(b => b.id === Number(id))
if (!battery) {
return (
<div className="max-w-4xl mx-auto px-6 py-20 text-center">
<div className="text-6xl mb-4">🔋</div>
<div className="text-2xl font-semibold mb-2">Аккумулятор не найден</div>
<Link to="/catalog" className="text-sky-500 hover:underline">Вернуться в каталог</Link>
</div>
)
}
return (
<div className="max-w-5xl mx-auto px-6 py-12">
<Link to="/catalog" className="inline-flex items-center gap-2 text-sm text-sky-500 hover:underline mb-8">
<ArrowLeft className="w-4 h-4" /> Назад в каталог
</Link>
<div className="grid md:grid-cols-2 gap-12">
{/* Image */}
<div className="rounded-3xl overflow-hidden border border-gray-200 dark:border-gray-800">
<img src={battery.image} alt={battery.name} className="w-full h-auto" />
</div>
{/* Info */}
<div>
<div className="text-sm text-sky-500 font-medium tracking-wider">{battery.brand}</div>
<h1 className="text-5xl font-semibold tracking-tighter mt-1 mb-6">{battery.name}</h1>
<div className="text-4xl font-semibold tabular-nums mb-8">
{battery.price.toLocaleString('ru-RU')} ₽
</div>
<div className="prose prose-sm dark:prose-invert mb-10">
{battery.description}
</div>
{/* Technical Specs Table */}
<div>
<div className="font-medium mb-4 text-sm tracking-widest text-gray-500">ТЕХНИЧЕСКИЕ ХАРАКТЕРИСТИКИ</div>
<table className="w-full text-sm">
<tbody>
{[
['Напряжение', `${battery.voltage} В`],
['Ёмкость', `${battery.capacity} А·ч`],
['Технология', battery.chemistry],
['Циклов', battery.cycles],
['Вес', `${battery.weight} кг`],
['Габариты (Д×Ш×В)', battery.dimensions + ' мм'],
['Применение', battery.application.join(', ')],
].map(([label, value]) => (
<tr key={label} className="border-b border-gray-100 dark:border-gray-800">
<td className="py-3 pr-6 text-gray-500 w-1/2">{label}</td>
<td className="py-3 font-medium">{value}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="mt-10 flex gap-4">
<button className="flex-1 py-4 bg-gray-900 dark:bg-white text-white dark:text-gray-900 rounded-2xl font-medium hover:bg-black dark:hover:bg-gray-200 transition-colors">
Запросить КП
</button>
<button className="flex-1 py-4 border border-gray-300 dark:border-gray-700 rounded-2xl font-medium hover:bg-gray-50 dark:hover:bg-gray-900 transition-colors">
Добавить в сравнение
</button>
</div>
</div>
</div>
</div>
)
}
+24
View File
@@ -0,0 +1,24 @@
export interface Battery {
id: number
name: string
brand: string
voltage: number
capacity: number
chemistry: 'LiFePO4' | 'AGM' | 'GEL' | 'Lead-Acid' | 'Ni-Cd'
application: string[]
weight: number
dimensions: string
cycles: number
price: number
image: string
description: string
}
export interface FilterState {
search: string
voltage: number[]
capacityMin: number
capacityMax: number
chemistry: string[]
application: string[]
}
+6
View File
@@ -0,0 +1,6 @@
/// <reference types="vite/client" />
declare module '*.css' {
const content: string
export default content
}
+22
View File
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
server: {
port: parseInt(process.env.PORT || '3000'),
host: true,
},
preview: {
port: parseInt(process.env.PORT || '3000'),
host: true,
},
})