l(hr,T))P[K]=hr,P[Pt]=T,K=Pt;else break e}}return j}function l(P,j){var T=P.sortIndex-j.sortIndex;return T!==0?T:P.id-j.id}if(typeof performance=="object"&&typeof performance.now=="function"){var o=performance;e.unstable_now=function(){return o.now()}}else{var i=Date,u=i.now();e.unstable_now=function(){return i.now()-u}}var a=[],s=[],h=1,p=null,m=3,S=!1,w=!1,k=!1,_=typeof setTimeout=="function"?setTimeout:null,f=typeof clearTimeout=="function"?clearTimeout:null,c=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function d(P){for(var j=n(s);j!==null;){if(j.callback===null)r(s);else if(j.startTime<=P)r(s),j.sortIndex=j.expirationTime,t(a,j);else break;j=n(s)}}function v(P){if(k=!1,d(P),!w)if(n(a)!==null)w=!0,Dl(E);else{var j=n(s);j!==null&&Ml(v,j.startTime-P)}}function E(P,j){w=!1,k&&(k=!1,f(R),R=-1),S=!0;var T=m;try{for(d(j),p=n(a);p!==null&&(!(p.expirationTime>j)||P&&!ge());){var K=p.callback;if(typeof K=="function"){p.callback=null,m=p.priorityLevel;var q=K(p.expirationTime<=j);j=e.unstable_now(),typeof q=="function"?p.callback=q:p===n(a)&&r(a),d(j)}else r(a);p=n(a)}if(p!==null)var pr=!0;else{var Ct=n(s);Ct!==null&&Ml(v,Ct.startTime-j),pr=!1}return pr}finally{p=null,m=T,S=!1}}var N=!1,L=null,R=-1,$=5,z=-1;function ge(){return!(e.unstable_now()-z<$)}function Sn(){if(L!==null){var P=e.unstable_now();z=P;var j=!0;try{j=L(!0,P)}finally{j?kn():(N=!1,L=null)}}else N=!1}var kn;if(typeof c=="function")kn=function(){c(Sn)};else if(typeof MessageChannel<"u"){var iu=new MessageChannel,ef=iu.port2;iu.port1.onmessage=Sn,kn=function(){ef.postMessage(null)}}else kn=function(){_(Sn,0)};function Dl(P){L=P,N||(N=!0,kn())}function Ml(P,j){R=_(function(){P(e.unstable_now())},j)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(P){P.callback=null},e.unstable_continueExecution=function(){w||S||(w=!0,Dl(E))},e.unstable_forceFrameRate=function(P){0>P||125K?(P.sortIndex=T,t(s,P),n(a)===null&&P===n(s)&&(k?(f(R),R=-1):k=!0,Ml(v,T-K))):(P.sortIndex=q,t(a,P),w||S||(w=!0,Dl(E))),P},e.unstable_shouldYield=ge,e.unstable_wrapCallback=function(P){var j=m;return function(){var T=m;m=j;try{return P.apply(this,arguments)}finally{m=T}}}})(Va);Ba.exports=Va;var Pf=Ba.exports;/**
+ * @license React
+ * react-dom.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */var Nf=g,xe=Pf;function x(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),po=Object.prototype.hasOwnProperty,Lf=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,cu={},fu={};function Rf(e){return po.call(fu,e)?!0:po.call(cu,e)?!1:Lf.test(e)?fu[e]=!0:(cu[e]=!0,!1)}function jf(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function zf(e,t,n,r){if(t===null||typeof t>"u"||jf(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function de(e,t,n,r,l,o,i){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=o,this.removeEmptyString=i}var re={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){re[e]=new de(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];re[t]=new de(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){re[e]=new de(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){re[e]=new de(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){re[e]=new de(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){re[e]=new de(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){re[e]=new de(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){re[e]=new de(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){re[e]=new de(e,5,!1,e.toLowerCase(),null,!1,!1)});var pi=/[\-:]([a-z])/g;function hi(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(pi,hi);re[t]=new de(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(pi,hi);re[t]=new de(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(pi,hi);re[t]=new de(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){re[e]=new de(e,1,!1,e.toLowerCase(),null,!1,!1)});re.xlinkHref=new de("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){re[e]=new de(e,1,!1,e.toLowerCase(),null,!0,!0)});function mi(e,t,n,r){var l=re.hasOwnProperty(t)?re[t]:null;(l!==null?l.type!==0:r||!(2u||l[i]!==o[u]){var a=`
+`+l[i].replace(" at new "," at ");return e.displayName&&a.includes("")&&(a=a.replace("",e.displayName)),a}while(1<=i&&0<=u);break}}}finally{$l=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?jn(e):""}function Tf(e){switch(e.tag){case 5:return jn(e.type);case 16:return jn("Lazy");case 13:return jn("Suspense");case 19:return jn("SuspenseList");case 0:case 2:case 15:return e=Bl(e.type,!1),e;case 11:return e=Bl(e.type.render,!1),e;case 1:return e=Bl(e.type,!0),e;default:return""}}function yo(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Wt:return"Fragment";case Vt:return"Portal";case ho:return"Profiler";case vi:return"StrictMode";case mo:return"Suspense";case vo:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Qa:return(e.displayName||"Context")+".Consumer";case Ha:return(e._context.displayName||"Context")+".Provider";case yi:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case gi:return t=e.displayName||null,t!==null?t:yo(e.type)||"Memo";case rt:t=e._payload,e=e._init;try{return yo(e(t))}catch{}}return null}function Of(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return yo(t);case 8:return t===vi?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function wt(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Ya(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function If(e){var t=Ya(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,o=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(i){r=""+i,o.call(this,i)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(i){r=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function yr(e){e._valueTracker||(e._valueTracker=If(e))}function Ga(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Ya(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Hr(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function go(e,t){var n=t.checked;return H({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function pu(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=wt(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Xa(e,t){t=t.checked,t!=null&&mi(e,"checked",t,!1)}function wo(e,t){Xa(e,t);var n=wt(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?So(e,t.type,n):t.hasOwnProperty("defaultValue")&&So(e,t.type,wt(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function hu(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function So(e,t,n){(t!=="number"||Hr(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var zn=Array.isArray;function en(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=gr.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Hn(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var In={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Df=["Webkit","ms","Moz","O"];Object.keys(In).forEach(function(e){Df.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),In[t]=In[e]})});function ba(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||In.hasOwnProperty(e)&&In[e]?(""+t).trim():t+"px"}function es(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=ba(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var Mf=H({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Eo(e,t){if(t){if(Mf[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(x(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(x(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(x(61))}if(t.style!=null&&typeof t.style!="object")throw Error(x(62))}}function _o(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Co=null;function wi(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Po=null,tn=null,nn=null;function yu(e){if(e=fr(e)){if(typeof Po!="function")throw Error(x(280));var t=e.stateNode;t&&(t=kl(t),Po(e.stateNode,e.type,t))}}function ts(e){tn?nn?nn.push(e):nn=[e]:tn=e}function ns(){if(tn){var e=tn,t=nn;if(nn=tn=null,yu(e),t)for(e=0;e>>=0,e===0?32:31-(Yf(e)/Gf|0)|0}var wr=64,Sr=4194304;function Tn(e){switch(e&-e){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: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&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Gr(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,o=e.pingedLanes,i=n&268435455;if(i!==0){var u=i&~l;u!==0?r=Tn(u):(o&=i,o!==0&&(r=Tn(o)))}else i=n&~l,i!==0?r=Tn(i):o!==0&&(r=Tn(o));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,o=t&-t,l>=o||l===16&&(o&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function sr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Me(t),e[t]=n}function qf(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Mn),Pu=" ",Nu=!1;function xs(e,t){switch(e){case"keyup":return Pd.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Es(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Ht=!1;function Ld(e,t){switch(e){case"compositionend":return Es(t);case"keypress":return t.which!==32?null:(Nu=!0,Pu);case"textInput":return e=t.data,e===Pu&&Nu?null:e;default:return null}}function Rd(e,t){if(Ht)return e==="compositionend"||!Ni&&xs(e,t)?(e=Ss(),Dr=_i=ut=null,Ht=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=zu(n)}}function Ns(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Ns(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ls(){for(var e=window,t=Hr();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Hr(e.document)}return t}function Li(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function Ud(e){var t=Ls(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Ns(n.ownerDocument.documentElement,n)){if(r!==null&&Li(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,o=Math.min(r.start,l);r=r.end===void 0?o:Math.min(r.end,l),!e.extend&&o>r&&(l=r,r=o,o=l),l=Tu(n,o);var i=Tu(n,r);l&&i&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==i.node||e.focusOffset!==i.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),o>r?(e.addRange(t),e.extend(i.node,i.offset)):(t.setEnd(i.node,i.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Qt=null,To=null,Un=null,Oo=!1;function Ou(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Oo||Qt==null||Qt!==Hr(r)||(r=Qt,"selectionStart"in r&&Li(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Un&&Zn(Un,r)||(Un=r,r=Jr(To,"onSelect"),0Gt||(e.current=Ao[Gt],Ao[Gt]=null,Gt--)}function M(e,t){Gt++,Ao[Gt]=e.current,e.current=t}var St={},ae=xt(St),me=xt(!1),It=St;function an(e,t){var n=e.type.contextTypes;if(!n)return St;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},o;for(o in n)l[o]=t[o];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function ve(e){return e=e.childContextTypes,e!=null}function br(){U(me),U(ae)}function $u(e,t,n){if(ae.current!==St)throw Error(x(168));M(ae,t),M(me,n)}function Fs(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(x(108,Of(e)||"Unknown",l));return H({},n,r)}function el(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||St,It=ae.current,M(ae,e),M(me,me.current),!0}function Bu(e,t,n){var r=e.stateNode;if(!r)throw Error(x(169));n?(e=Fs(e,t,It),r.__reactInternalMemoizedMergedChildContext=e,U(me),U(ae),M(ae,e)):U(me),M(me,n)}var Qe=null,xl=!1,to=!1;function Us(e){Qe===null?Qe=[e]:Qe.push(e)}function Zd(e){xl=!0,Us(e)}function Et(){if(!to&&Qe!==null){to=!0;var e=0,t=D;try{var n=Qe;for(D=1;e>=i,l-=i,Ke=1<<32-Me(t)+l|n<R?($=L,L=null):$=L.sibling;var z=m(f,L,d[R],v);if(z===null){L===null&&(L=$);break}e&&L&&z.alternate===null&&t(f,L),c=o(z,c,R),N===null?E=z:N.sibling=z,N=z,L=$}if(R===d.length)return n(f,L),A&&Nt(f,R),E;if(L===null){for(;RR?($=L,L=null):$=L.sibling;var ge=m(f,L,z.value,v);if(ge===null){L===null&&(L=$);break}e&&L&&ge.alternate===null&&t(f,L),c=o(ge,c,R),N===null?E=ge:N.sibling=ge,N=ge,L=$}if(z.done)return n(f,L),A&&Nt(f,R),E;if(L===null){for(;!z.done;R++,z=d.next())z=p(f,z.value,v),z!==null&&(c=o(z,c,R),N===null?E=z:N.sibling=z,N=z);return A&&Nt(f,R),E}for(L=r(f,L);!z.done;R++,z=d.next())z=S(L,f,R,z.value,v),z!==null&&(e&&z.alternate!==null&&L.delete(z.key===null?R:z.key),c=o(z,c,R),N===null?E=z:N.sibling=z,N=z);return e&&L.forEach(function(Sn){return t(f,Sn)}),A&&Nt(f,R),E}function _(f,c,d,v){if(typeof d=="object"&&d!==null&&d.type===Wt&&d.key===null&&(d=d.props.children),typeof d=="object"&&d!==null){switch(d.$$typeof){case vr:e:{for(var E=d.key,N=c;N!==null;){if(N.key===E){if(E=d.type,E===Wt){if(N.tag===7){n(f,N.sibling),c=l(N,d.props.children),c.return=f,f=c;break e}}else if(N.elementType===E||typeof E=="object"&&E!==null&&E.$$typeof===rt&&Hu(E)===N.type){n(f,N.sibling),c=l(N,d.props),c.ref=Nn(f,N,d),c.return=f,f=c;break e}n(f,N);break}else t(f,N);N=N.sibling}d.type===Wt?(c=Ot(d.props.children,f.mode,v,d.key),c.return=f,f=c):(v=Wr(d.type,d.key,d.props,null,f.mode,v),v.ref=Nn(f,c,d),v.return=f,f=v)}return i(f);case Vt:e:{for(N=d.key;c!==null;){if(c.key===N)if(c.tag===4&&c.stateNode.containerInfo===d.containerInfo&&c.stateNode.implementation===d.implementation){n(f,c.sibling),c=l(c,d.children||[]),c.return=f,f=c;break e}else{n(f,c);break}else t(f,c);c=c.sibling}c=so(d,f.mode,v),c.return=f,f=c}return i(f);case rt:return N=d._init,_(f,c,N(d._payload),v)}if(zn(d))return w(f,c,d,v);if(xn(d))return k(f,c,d,v);Nr(f,d)}return typeof d=="string"&&d!==""||typeof d=="number"?(d=""+d,c!==null&&c.tag===6?(n(f,c.sibling),c=l(c,d),c.return=f,f=c):(n(f,c),c=ao(d,f.mode,v),c.return=f,f=c),i(f)):n(f,c)}return _}var cn=Vs(!0),Ws=Vs(!1),rl=xt(null),ll=null,Jt=null,Ti=null;function Oi(){Ti=Jt=ll=null}function Ii(e){var t=rl.current;U(rl),e._currentValue=t}function Vo(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function ln(e,t){ll=e,Ti=Jt=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(he=!0),e.firstContext=null)}function Re(e){var t=e._currentValue;if(Ti!==e)if(e={context:e,memoizedValue:t,next:null},Jt===null){if(ll===null)throw Error(x(308));Jt=e,ll.dependencies={lanes:0,firstContext:e}}else Jt=Jt.next=e;return t}var jt=null;function Di(e){jt===null?jt=[e]:jt.push(e)}function Hs(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Di(t)):(n.next=l.next,l.next=n),t.interleaved=n,Je(e,r)}function Je(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var lt=!1;function Mi(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Qs(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Ge(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function ht(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,I&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,Je(e,n)}return l=r.interleaved,l===null?(t.next=t,Di(r)):(t.next=l.next,l.next=t),r.interleaved=t,Je(e,n)}function Fr(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ki(e,n)}}function Qu(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,o=null;if(n=n.firstBaseUpdate,n!==null){do{var i={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};o===null?l=o=i:o=o.next=i,n=n.next}while(n!==null);o===null?l=o=t:o=o.next=t}else l=o=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:o,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function ol(e,t,n,r){var l=e.updateQueue;lt=!1;var o=l.firstBaseUpdate,i=l.lastBaseUpdate,u=l.shared.pending;if(u!==null){l.shared.pending=null;var a=u,s=a.next;a.next=null,i===null?o=s:i.next=s,i=a;var h=e.alternate;h!==null&&(h=h.updateQueue,u=h.lastBaseUpdate,u!==i&&(u===null?h.firstBaseUpdate=s:u.next=s,h.lastBaseUpdate=a))}if(o!==null){var p=l.baseState;i=0,h=s=a=null,u=o;do{var m=u.lane,S=u.eventTime;if((r&m)===m){h!==null&&(h=h.next={eventTime:S,lane:0,tag:u.tag,payload:u.payload,callback:u.callback,next:null});e:{var w=e,k=u;switch(m=t,S=n,k.tag){case 1:if(w=k.payload,typeof w=="function"){p=w.call(S,p,m);break e}p=w;break e;case 3:w.flags=w.flags&-65537|128;case 0:if(w=k.payload,m=typeof w=="function"?w.call(S,p,m):w,m==null)break e;p=H({},p,m);break e;case 2:lt=!0}}u.callback!==null&&u.lane!==0&&(e.flags|=64,m=l.effects,m===null?l.effects=[u]:m.push(u))}else S={eventTime:S,lane:m,tag:u.tag,payload:u.payload,callback:u.callback,next:null},h===null?(s=h=S,a=p):h=h.next=S,i|=m;if(u=u.next,u===null){if(u=l.shared.pending,u===null)break;m=u,u=m.next,m.next=null,l.lastBaseUpdate=m,l.shared.pending=null}}while(!0);if(h===null&&(a=p),l.baseState=a,l.firstBaseUpdate=s,l.lastBaseUpdate=h,t=l.shared.interleaved,t!==null){l=t;do i|=l.lane,l=l.next;while(l!==t)}else o===null&&(l.shared.lanes=0);Ft|=i,e.lanes=i,e.memoizedState=p}}function Ku(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=ro.transition;ro.transition={};try{e(!1),t()}finally{D=n,ro.transition=r}}function ac(){return je().memoizedState}function ep(e,t,n){var r=vt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},sc(e))cc(t,n);else if(n=Hs(e,t,n,r),n!==null){var l=ce();Fe(n,e,r,l),fc(n,t,r)}}function tp(e,t,n){var r=vt(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(sc(e))cc(t,l);else{var o=e.alternate;if(e.lanes===0&&(o===null||o.lanes===0)&&(o=t.lastRenderedReducer,o!==null))try{var i=t.lastRenderedState,u=o(i,n);if(l.hasEagerState=!0,l.eagerState=u,Ue(u,i)){var a=t.interleaved;a===null?(l.next=l,Di(t)):(l.next=a.next,a.next=l),t.interleaved=l;return}}catch{}finally{}n=Hs(e,t,l,r),n!==null&&(l=ce(),Fe(n,e,r,l),fc(n,t,r))}}function sc(e){var t=e.alternate;return e===V||t!==null&&t===V}function cc(e,t){An=ul=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function fc(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,ki(e,n)}}var al={readContext:Re,useCallback:oe,useContext:oe,useEffect:oe,useImperativeHandle:oe,useInsertionEffect:oe,useLayoutEffect:oe,useMemo:oe,useReducer:oe,useRef:oe,useState:oe,useDebugValue:oe,useDeferredValue:oe,useTransition:oe,useMutableSource:oe,useSyncExternalStore:oe,useId:oe,unstable_isNewReconciler:!1},np={readContext:Re,useCallback:function(e,t){return $e().memoizedState=[e,t===void 0?null:t],e},useContext:Re,useEffect:Gu,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Ar(4194308,4,rc.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Ar(4194308,4,e,t)},useInsertionEffect:function(e,t){return Ar(4,2,e,t)},useMemo:function(e,t){var n=$e();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=$e();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=ep.bind(null,V,e),[r.memoizedState,e]},useRef:function(e){var t=$e();return e={current:e},t.memoizedState=e},useState:Yu,useDebugValue:Hi,useDeferredValue:function(e){return $e().memoizedState=e},useTransition:function(){var e=Yu(!1),t=e[0];return e=bd.bind(null,e[1]),$e().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=V,l=$e();if(A){if(n===void 0)throw Error(x(407));n=n()}else{if(n=t(),ee===null)throw Error(x(349));Mt&30||Xs(r,t,n)}l.memoizedState=n;var o={value:n,getSnapshot:t};return l.queue=o,Gu(Js.bind(null,r,o,e),[e]),r.flags|=2048,lr(9,Zs.bind(null,r,o,n,t),void 0,null),n},useId:function(){var e=$e(),t=ee.identifierPrefix;if(A){var n=Ye,r=Ke;n=(r&~(1<<32-Me(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=nr++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=i.createElement(n,{is:r.is}):(e=i.createElement(n),n==="select"&&(i=e,r.multiple?i.multiple=!0:r.size&&(i.size=r.size))):e=i.createElementNS(e,n),e[Be]=t,e[bn]=r,kc(e,t,!1,!1),t.stateNode=e;e:{switch(i=_o(n,r),n){case"dialog":F("cancel",e),F("close",e),l=r;break;case"iframe":case"object":case"embed":F("load",e),l=r;break;case"video":case"audio":for(l=0;lpn&&(t.flags|=128,r=!0,Ln(o,!1),t.lanes=4194304)}else{if(!r)if(e=il(i),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Ln(o,!0),o.tail===null&&o.tailMode==="hidden"&&!i.alternate&&!A)return ie(t),null}else 2*Y()-o.renderingStartTime>pn&&n!==1073741824&&(t.flags|=128,r=!0,Ln(o,!1),t.lanes=4194304);o.isBackwards?(i.sibling=t.child,t.child=i):(n=o.last,n!==null?n.sibling=i:t.child=i,o.last=i)}return o.tail!==null?(t=o.tail,o.rendering=t,o.tail=t.sibling,o.renderingStartTime=Y(),t.sibling=null,n=B.current,M(B,r?n&1|2:n&1),t):(ie(t),null);case 22:case 23:return Zi(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?we&1073741824&&(ie(t),t.subtreeFlags&6&&(t.flags|=8192)):ie(t),null;case 24:return null;case 25:return null}throw Error(x(156,t.tag))}function cp(e,t){switch(ji(t),t.tag){case 1:return ve(t.type)&&br(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return fn(),U(me),U(ae),Ai(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Ui(t),null;case 13:if(U(B),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(x(340));sn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return U(B),null;case 4:return fn(),null;case 10:return Ii(t.type._context),null;case 22:case 23:return Zi(),null;case 24:return null;default:return null}}var Rr=!1,ue=!1,fp=typeof WeakSet=="function"?WeakSet:Set,C=null;function qt(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Q(e,t,r)}else n.current=null}function Jo(e,t,n){try{n()}catch(r){Q(e,t,r)}}var oa=!1;function dp(e,t){if(Io=Xr,e=Ls(),Li(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break e}var i=0,u=-1,a=-1,s=0,h=0,p=e,m=null;t:for(;;){for(var S;p!==n||l!==0&&p.nodeType!==3||(u=i+l),p!==o||r!==0&&p.nodeType!==3||(a=i+r),p.nodeType===3&&(i+=p.nodeValue.length),(S=p.firstChild)!==null;)m=p,p=S;for(;;){if(p===e)break t;if(m===n&&++s===l&&(u=i),m===o&&++h===r&&(a=i),(S=p.nextSibling)!==null)break;p=m,m=p.parentNode}p=S}n=u===-1||a===-1?null:{start:u,end:a}}else n=null}n=n||{start:0,end:0}}else n=null;for(Do={focusedElem:e,selectionRange:n},Xr=!1,C=t;C!==null;)if(t=C,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,C=e;else for(;C!==null;){t=C;try{var w=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(w!==null){var k=w.memoizedProps,_=w.memoizedState,f=t.stateNode,c=f.getSnapshotBeforeUpdate(t.elementType===t.type?k:Te(t.type,k),_);f.__reactInternalSnapshotBeforeUpdate=c}break;case 3:var d=t.stateNode.containerInfo;d.nodeType===1?d.textContent="":d.nodeType===9&&d.documentElement&&d.removeChild(d.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(x(163))}}catch(v){Q(t,t.return,v)}if(e=t.sibling,e!==null){e.return=t.return,C=e;break}C=t.return}return w=oa,oa=!1,w}function $n(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var o=l.destroy;l.destroy=void 0,o!==void 0&&Jo(t,n,o)}l=l.next}while(l!==r)}}function Cl(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function qo(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function _c(e){var t=e.alternate;t!==null&&(e.alternate=null,_c(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Be],delete t[bn],delete t[Uo],delete t[Gd],delete t[Xd])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Cc(e){return e.tag===5||e.tag===3||e.tag===4}function ia(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Cc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function bo(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=qr));else if(r!==4&&(e=e.child,e!==null))for(bo(e,t,n),e=e.sibling;e!==null;)bo(e,t,n),e=e.sibling}function ei(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ei(e,t,n),e=e.sibling;e!==null;)ei(e,t,n),e=e.sibling}var te=null,Oe=!1;function nt(e,t,n){for(n=n.child;n!==null;)Pc(e,t,n),n=n.sibling}function Pc(e,t,n){if(Ve&&typeof Ve.onCommitFiberUnmount=="function")try{Ve.onCommitFiberUnmount(yl,n)}catch{}switch(n.tag){case 5:ue||qt(n,t);case 6:var r=te,l=Oe;te=null,nt(e,t,n),te=r,Oe=l,te!==null&&(Oe?(e=te,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):te.removeChild(n.stateNode));break;case 18:te!==null&&(Oe?(e=te,n=n.stateNode,e.nodeType===8?eo(e.parentNode,n):e.nodeType===1&&eo(e,n),Gn(e)):eo(te,n.stateNode));break;case 4:r=te,l=Oe,te=n.stateNode.containerInfo,Oe=!0,nt(e,t,n),te=r,Oe=l;break;case 0:case 11:case 14:case 15:if(!ue&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var o=l,i=o.destroy;o=o.tag,i!==void 0&&(o&2||o&4)&&Jo(n,t,i),l=l.next}while(l!==r)}nt(e,t,n);break;case 1:if(!ue&&(qt(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(u){Q(n,t,u)}nt(e,t,n);break;case 21:nt(e,t,n);break;case 22:n.mode&1?(ue=(r=ue)||n.memoizedState!==null,nt(e,t,n),ue=r):nt(e,t,n);break;default:nt(e,t,n)}}function ua(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new fp),t.forEach(function(r){var l=kp.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function ze(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=i),r&=~o}if(r=l,r=Y()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*hp(r/1960))-r,10e?16:e,at===null)var r=!1;else{if(e=at,at=null,fl=0,I&6)throw Error(x(331));var l=I;for(I|=4,C=e.current;C!==null;){var o=C,i=o.child;if(C.flags&16){var u=o.deletions;if(u!==null){for(var a=0;aY()-Gi?Tt(e,0):Yi|=n),ye(e,t)}function Ic(e,t){t===0&&(e.mode&1?(t=Sr,Sr<<=1,!(Sr&130023424)&&(Sr=4194304)):t=1);var n=ce();e=Je(e,t),e!==null&&(sr(e,t,n),ye(e,n))}function Sp(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Ic(e,n)}function kp(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(x(314))}r!==null&&r.delete(t),Ic(e,n)}var Dc;Dc=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||me.current)he=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return he=!1,ap(e,t,n);he=!!(e.flags&131072)}else he=!1,A&&t.flags&1048576&&As(t,nl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;$r(e,t),e=t.pendingProps;var l=an(t,ae.current);ln(t,n),l=Bi(null,t,r,e,l,n);var o=Vi();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,ve(r)?(o=!0,el(t)):o=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Mi(t),l.updater=_l,t.stateNode=l,l._reactInternals=t,Ho(t,r,e,n),t=Yo(null,t,r,!0,o,n)):(t.tag=0,A&&o&&Ri(t),se(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch($r(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=Ep(r),e=Te(r,e),l){case 0:t=Ko(null,t,r,e,n);break e;case 1:t=na(null,t,r,e,n);break e;case 11:t=ea(null,t,r,e,n);break e;case 14:t=ta(null,t,r,Te(r.type,e),n);break e}throw Error(x(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Te(r,l),Ko(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Te(r,l),na(e,t,r,l,n);case 3:e:{if(gc(t),e===null)throw Error(x(387));r=t.pendingProps,o=t.memoizedState,l=o.element,Qs(e,t),ol(t,r,null,n);var i=t.memoizedState;if(r=i.element,o.isDehydrated)if(o={element:r,isDehydrated:!1,cache:i.cache,pendingSuspenseBoundaries:i.pendingSuspenseBoundaries,transitions:i.transitions},t.updateQueue.baseState=o,t.memoizedState=o,t.flags&256){l=dn(Error(x(423)),t),t=ra(e,t,r,n,l);break e}else if(r!==l){l=dn(Error(x(424)),t),t=ra(e,t,r,n,l);break e}else for(Se=pt(t.stateNode.containerInfo.firstChild),ke=t,A=!0,Ie=null,n=Ws(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(sn(),r===l){t=qe(e,t,n);break e}se(e,t,r,n)}t=t.child}return t;case 5:return Ks(t),e===null&&Bo(t),r=t.type,l=t.pendingProps,o=e!==null?e.memoizedProps:null,i=l.children,Mo(r,l)?i=null:o!==null&&Mo(r,o)&&(t.flags|=32),yc(e,t),se(e,t,i,n),t.child;case 6:return e===null&&Bo(t),null;case 13:return wc(e,t,n);case 4:return Fi(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=cn(t,null,r,n):se(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Te(r,l),ea(e,t,r,l,n);case 7:return se(e,t,t.pendingProps,n),t.child;case 8:return se(e,t,t.pendingProps.children,n),t.child;case 12:return se(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,o=t.memoizedProps,i=l.value,M(rl,r._currentValue),r._currentValue=i,o!==null)if(Ue(o.value,i)){if(o.children===l.children&&!me.current){t=qe(e,t,n);break e}}else for(o=t.child,o!==null&&(o.return=t);o!==null;){var u=o.dependencies;if(u!==null){i=o.child;for(var a=u.firstContext;a!==null;){if(a.context===r){if(o.tag===1){a=Ge(-1,n&-n),a.tag=2;var s=o.updateQueue;if(s!==null){s=s.shared;var h=s.pending;h===null?a.next=a:(a.next=h.next,h.next=a),s.pending=a}}o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),Vo(o.return,n,t),u.lanes|=n;break}a=a.next}}else if(o.tag===10)i=o.type===t.type?null:o.child;else if(o.tag===18){if(i=o.return,i===null)throw Error(x(341));i.lanes|=n,u=i.alternate,u!==null&&(u.lanes|=n),Vo(i,n,t),i=o.sibling}else i=o.child;if(i!==null)i.return=o;else for(i=o;i!==null;){if(i===t){i=null;break}if(o=i.sibling,o!==null){o.return=i.return,i=o;break}i=i.return}o=i}se(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,ln(t,n),l=Re(l),r=r(l),t.flags|=1,se(e,t,r,n),t.child;case 14:return r=t.type,l=Te(r,t.pendingProps),l=Te(r.type,l),ta(e,t,r,l,n);case 15:return mc(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Te(r,l),$r(e,t),t.tag=1,ve(r)?(e=!0,el(t)):e=!1,ln(t,n),dc(t,r,l),Ho(t,r,l,n),Yo(null,t,r,!0,e,n);case 19:return Sc(e,t,n);case 22:return vc(e,t,n)}throw Error(x(156,t.tag))};function Mc(e,t){return ss(e,t)}function xp(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Ne(e,t,n,r){return new xp(e,t,n,r)}function qi(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Ep(e){if(typeof e=="function")return qi(e)?1:0;if(e!=null){if(e=e.$$typeof,e===yi)return 11;if(e===gi)return 14}return 2}function yt(e,t){var n=e.alternate;return n===null?(n=Ne(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Wr(e,t,n,r,l,o){var i=2;if(r=e,typeof e=="function")qi(e)&&(i=1);else if(typeof e=="string")i=5;else e:switch(e){case Wt:return Ot(n.children,l,o,t);case vi:i=8,l|=8;break;case ho:return e=Ne(12,n,t,l|2),e.elementType=ho,e.lanes=o,e;case mo:return e=Ne(13,n,t,l),e.elementType=mo,e.lanes=o,e;case vo:return e=Ne(19,n,t,l),e.elementType=vo,e.lanes=o,e;case Ka:return Nl(n,l,o,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case Ha:i=10;break e;case Qa:i=9;break e;case yi:i=11;break e;case gi:i=14;break e;case rt:i=16,r=null;break e}throw Error(x(130,e==null?e:typeof e,""))}return t=Ne(i,n,t,l),t.elementType=e,t.type=r,t.lanes=o,t}function Ot(e,t,n,r){return e=Ne(7,e,r,t),e.lanes=n,e}function Nl(e,t,n,r){return e=Ne(22,e,r,t),e.elementType=Ka,e.lanes=n,e.stateNode={isHidden:!1},e}function ao(e,t,n){return e=Ne(6,e,null,t),e.lanes=n,e}function so(e,t,n){return t=Ne(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function _p(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Wl(0),this.expirationTimes=Wl(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Wl(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function bi(e,t,n,r,l,o,i,u,a){return e=new _p(e,t,n,u,a),t===1?(t=1,o===!0&&(t|=8)):t=0,o=Ne(3,null,null,t),e.current=o,o.stateNode=e,o.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Mi(o),e}function Cp(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE($c)}catch(e){console.error(e)}}$c(),$a.exports=Ee;var jp=$a.exports,ma=jp;fo.createRoot=ma.createRoot,fo.hydrateRoot=ma.hydrateRoot;/**
+ * @remix-run/router v1.23.3
+ *
+ * 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
+ */function ir(){return ir=Object.assign?Object.assign.bind():function(e){for(var t=1;t"u")throw new Error(t)}function ru(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function Tp(){return Math.random().toString(36).substr(2,8)}function ya(e,t){return{usr:e.state,key:e.key,idx:t}}function oi(e,t,n,r){return n===void 0&&(n=null),ir({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?gn(t):t,{state:n,key:t&&t.key||r||Tp()})}function hl(e){let{pathname:t="/",search:n="",hash:r=""}=e;return n&&n!=="?"&&(t+=n.charAt(0)==="?"?n:"?"+n),r&&r!=="#"&&(t+=r.charAt(0)==="#"?r:"#"+r),t}function gn(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substr(n),e=e.substr(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substr(r),e=e.substr(0,r)),e&&(t.pathname=e)}return t}function Op(e,t,n,r){r===void 0&&(r={});let{window:l=document.defaultView,v5Compat:o=!1}=r,i=l.history,u=st.Pop,a=null,s=h();s==null&&(s=0,i.replaceState(ir({},i.state,{idx:s}),""));function h(){return(i.state||{idx:null}).idx}function p(){u=st.Pop;let _=h(),f=_==null?null:_-s;s=_,a&&a({action:u,location:k.location,delta:f})}function m(_,f){u=st.Push;let c=oi(k.location,_,f);s=h()+1;let d=ya(c,s),v=k.createHref(c);try{i.pushState(d,"",v)}catch(E){if(E instanceof DOMException&&E.name==="DataCloneError")throw E;l.location.assign(v)}o&&a&&a({action:u,location:k.location,delta:1})}function S(_,f){u=st.Replace;let c=oi(k.location,_,f);s=h();let d=ya(c,s),v=k.createHref(c);i.replaceState(d,"",v),o&&a&&a({action:u,location:k.location,delta:0})}function w(_){let f=l.location.origin!=="null"?l.location.origin:l.location.href,c=typeof _=="string"?_:hl(_);return c=c.replace(/ $/,"%20"),W(f,"No window.location.(origin|href) available to create URL for href: "+c),new URL(c,f)}let k={get action(){return u},get location(){return e(l,i)},listen(_){if(a)throw new Error("A history only accepts one active listener");return l.addEventListener(va,p),a=_,()=>{l.removeEventListener(va,p),a=null}},createHref(_){return t(l,_)},createURL:w,encodeLocation(_){let f=w(_);return{pathname:f.pathname,search:f.search,hash:f.hash}},push:m,replace:S,go(_){return i.go(_)}};return k}var ga;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(ga||(ga={}));function Ip(e,t,n){return n===void 0&&(n="/"),Dp(e,t,n)}function Dp(e,t,n,r){let l=typeof t=="string"?gn(t):t,o=hn(l.pathname||"/",n);if(o==null)return null;let i=Bc(e);Mp(i);let u=null,a=Yp(o);for(let s=0;u==null&&s{let a={relativePath:u===void 0?o.path||"":u,caseSensitive:o.caseSensitive===!0,childrenIndex:i,route:o};a.relativePath.startsWith("/")&&(W(a.relativePath.startsWith(r),'Absolute route path "'+a.relativePath+'" nested under path '+('"'+r+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),a.relativePath=a.relativePath.slice(r.length));let s=gt([r,a.relativePath]),h=n.concat(a);o.children&&o.children.length>0&&(W(o.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+s+'".')),Bc(o.children,t,h,s)),!(o.path==null&&!o.index)&&t.push({path:s,score:Wp(s,o.index),routesMeta:h})};return e.forEach((o,i)=>{var u;if(o.path===""||!((u=o.path)!=null&&u.includes("?")))l(o,i);else for(let a of Vc(o.path))l(o,i,a)}),t}function Vc(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,l=n.endsWith("?"),o=n.replace(/\?$/,"");if(r.length===0)return l?[o,""]:[o];let i=Vc(r.join("/")),u=[];return u.push(...i.map(a=>a===""?o:[o,a].join("/"))),l&&u.push(...i),u.map(a=>e.startsWith("/")&&a===""?"/":a)}function Mp(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:Hp(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}const Fp=/^:[\w-]+$/,Up=3,Ap=2,$p=1,Bp=10,Vp=-2,wa=e=>e==="*";function Wp(e,t){let n=e.split("/"),r=n.length;return n.some(wa)&&(r+=Vp),t&&(r+=Ap),n.filter(l=>!wa(l)).reduce((l,o)=>l+(Fp.test(o)?Up:o===""?$p:Bp),r)}function Hp(e,t){return e.length===t.length&&e.slice(0,-1).every((r,l)=>r===t[l])?e[e.length-1]-t[t.length-1]:0}function Qp(e,t,n){let{routesMeta:r}=e,l={},o="/",i=[];for(let u=0;u{let{paramName:m,isOptional:S}=h;if(m==="*"){let k=u[p]||"";i=o.slice(0,o.length-k.length).replace(/(.)\/+$/,"$1")}const w=u[p];return S&&!w?s[m]=void 0:s[m]=(w||"").replace(/%2F/g,"/"),s},{}),pathname:o,pathnameBase:i,pattern:e}}function Kp(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!0),ru(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=[],l="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(i,u,a)=>(r.push({paramName:u,isOptional:a!=null}),a?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(r.push({paramName:"*"}),l+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?l+="\\/*$":e!==""&&e!=="/"&&(l+="(?:(?=\\/|$))"),[new RegExp(l,t?void 0:"i"),r]}function Yp(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return ru(!1,'The URL path "'+e+'" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent '+("encoding ("+t+").")),e}}function hn(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)||"/"}const Gp=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Xp=e=>Gp.test(e);function Zp(e,t){t===void 0&&(t="/");let{pathname:n,search:r="",hash:l=""}=typeof e=="string"?gn(e):e,o;if(n)if(Xp(n))o=n;else{if(n.includes("//")){let i=n;n=Wc(n),ru(!1,"Pathnames cannot have embedded double slashes - normalizing "+(i+" -> "+n))}n.startsWith("/")?o=Sa(n.substring(1),"/"):o=Sa(n,t)}else o=t;return{pathname:o,search:bp(r),hash:eh(l)}}function Sa(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(l=>{l===".."?n.length>1&&n.pop():l!=="."&&n.push(l)}),n.length>1?n.join("/"):"/"}function co(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 Jp(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function lu(e,t){let n=Jp(e);return t?n.map((r,l)=>l===n.length-1?r.pathname:r.pathnameBase):n.map(r=>r.pathnameBase)}function ou(e,t,n,r){r===void 0&&(r=!1);let l;typeof e=="string"?l=gn(e):(l=ir({},e),W(!l.pathname||!l.pathname.includes("?"),co("?","pathname","search",l)),W(!l.pathname||!l.pathname.includes("#"),co("#","pathname","hash",l)),W(!l.search||!l.search.includes("#"),co("#","search","hash",l)));let o=e===""||l.pathname==="",i=o?"/":l.pathname,u;if(i==null)u=n;else{let p=t.length-1;if(!r&&i.startsWith("..")){let m=i.split("/");for(;m[0]==="..";)m.shift(),p-=1;l.pathname=m.join("/")}u=p>=0?t[p]:"/"}let a=Zp(l,u),s=i&&i!=="/"&&i.endsWith("/"),h=(o||i===".")&&n.endsWith("/");return!a.pathname.endsWith("/")&&(s||h)&&(a.pathname+="/"),a}const Wc=e=>e.replace(/\/\/+/g,"/"),gt=e=>Wc(e.join("/")),qp=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),bp=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,eh=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;function th(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}const Hc=["post","put","patch","delete"];new Set(Hc);const nh=["get",...Hc];new Set(nh);/**
+ * React Router v6.30.4
+ *
+ * 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
+ */function ur(){return ur=Object.assign?Object.assign.bind():function(e){for(var t=1;t{u.current=!0}),g.useCallback(function(s,h){if(h===void 0&&(h={}),!u.current)return;if(typeof s=="number"){r.go(s);return}let p=ou(s,JSON.parse(i),o,h.relative==="path");e==null&&t!=="/"&&(p.pathname=p.pathname==="/"?t:gt([t,p.pathname])),(h.replace?r.replace:r.push)(p,h.state,h)},[t,r,i,o,e])}const oh=g.createContext(null);function ih(e){let t=g.useContext(tt).outlet;return t&&g.createElement(oh.Provider,{value:e},t)}function Il(e,t){let{relative:n}=t===void 0?{}:t,{future:r}=g.useContext(et),{matches:l}=g.useContext(tt),{pathname:o}=_t(),i=JSON.stringify(lu(l,r.v7_relativeSplatPath));return g.useMemo(()=>ou(e,JSON.parse(i),o,n==="path"),[e,i,o,n])}function uh(e,t){return ah(e,t)}function ah(e,t,n,r){wn()||W(!1);let{navigator:l}=g.useContext(et),{matches:o}=g.useContext(tt),i=o[o.length-1],u=i?i.params:{};i&&i.pathname;let a=i?i.pathnameBase:"/";i&&i.route;let s=_t(),h;if(t){var p;let _=typeof t=="string"?gn(t):t;a==="/"||(p=_.pathname)!=null&&p.startsWith(a)||W(!1),h=_}else h=s;let m=h.pathname||"/",S=m;if(a!=="/"){let _=a.replace(/^\//,"").split("/");S="/"+m.replace(/^\//,"").split("/").slice(_.length).join("/")}let w=Ip(e,{pathname:S}),k=ph(w&&w.map(_=>Object.assign({},_,{params:Object.assign({},u,_.params),pathname:gt([a,l.encodeLocation?l.encodeLocation(_.pathname).pathname:_.pathname]),pathnameBase:_.pathnameBase==="/"?a:gt([a,l.encodeLocation?l.encodeLocation(_.pathnameBase).pathname:_.pathnameBase])})),o,n,r);return t&&k?g.createElement(Ol.Provider,{value:{location:ur({pathname:"/",search:"",hash:"",state:null,key:"default"},h),navigationType:st.Pop}},k):k}function sh(){let e=yh(),t=th(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,l={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"};return g.createElement(g.Fragment,null,g.createElement("h2",null,"Unexpected Application Error!"),g.createElement("h3",{style:{fontStyle:"italic"}},t),n?g.createElement("pre",{style:l},n):null,null)}const ch=g.createElement(sh,null);class fh extends g.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,n){return n.location!==t.location||n.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:n.error,location:n.location,revalidation:t.revalidation||n.revalidation}}componentDidCatch(t,n){console.error("React Router caught the following error during render",t,n)}render(){return this.state.error!==void 0?g.createElement(tt.Provider,{value:this.props.routeContext},g.createElement(Kc.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function dh(e){let{routeContext:t,match:n,children:r}=e,l=g.useContext(Tl);return l&&l.static&&l.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(l.staticContext._deepestRenderedBoundaryId=n.route.id),g.createElement(tt.Provider,{value:t},r)}function ph(e,t,n,r){var l;if(t===void 0&&(t=[]),n===void 0&&(n=null),r===void 0&&(r=null),e==null){var o;if(!n)return null;if(n.errors)e=n.matches;else if((o=r)!=null&&o.v7_partialHydration&&t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let i=e,u=(l=n)==null?void 0:l.errors;if(u!=null){let h=i.findIndex(p=>p.route.id&&(u==null?void 0:u[p.route.id])!==void 0);h>=0||W(!1),i=i.slice(0,Math.min(i.length,h+1))}let a=!1,s=-1;if(n&&r&&r.v7_partialHydration)for(let h=0;h=0?i=i.slice(0,s+1):i=[i[0]];break}}}return i.reduceRight((h,p,m)=>{let S,w=!1,k=null,_=null;n&&(S=u&&p.route.id?u[p.route.id]:void 0,k=p.route.errorElement||ch,a&&(s<0&&m===0?(wh("route-fallback"),w=!0,_=null):s===m&&(w=!0,_=p.route.hydrateFallbackElement||null)));let f=t.concat(i.slice(0,m+1)),c=()=>{let d;return S?d=k:w?d=_:p.route.Component?d=g.createElement(p.route.Component,null):p.route.element?d=p.route.element:d=h,g.createElement(dh,{match:p,routeContext:{outlet:h,matches:f,isDataRoute:n!=null},children:d})};return n&&(p.route.ErrorBoundary||p.route.errorElement||m===0)?g.createElement(fh,{location:n.location,revalidation:n.revalidation,component:k,error:S,children:c(),routeContext:{outlet:null,matches:f,isDataRoute:!0}}):c()},null)}var Xc=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e}(Xc||{}),Zc=function(e){return e.UseBlocker="useBlocker",e.UseLoaderData="useLoaderData",e.UseActionData="useActionData",e.UseRouteError="useRouteError",e.UseNavigation="useNavigation",e.UseRouteLoaderData="useRouteLoaderData",e.UseMatches="useMatches",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e.UseRouteId="useRouteId",e}(Zc||{});function hh(e){let t=g.useContext(Tl);return t||W(!1),t}function mh(e){let t=g.useContext(Qc);return t||W(!1),t}function vh(e){let t=g.useContext(tt);return t||W(!1),t}function Jc(e){let t=vh(),n=t.matches[t.matches.length-1];return n.route.id||W(!1),n.route.id}function yh(){var e;let t=g.useContext(Kc),n=mh(),r=Jc();return t!==void 0?t:(e=n.errors)==null?void 0:e[r]}function gh(){let{router:e}=hh(Xc.UseNavigateStable),t=Jc(Zc.UseNavigateStable),n=g.useRef(!1);return Yc(()=>{n.current=!0}),g.useCallback(function(l,o){o===void 0&&(o={}),n.current&&(typeof l=="number"?e.navigate(l):e.navigate(l,ur({fromRouteId:t},o)))},[e,t])}const ka={};function wh(e,t,n){ka[e]||(ka[e]=!0)}function Sh(e,t){e==null||e.v7_startTransition,e==null||e.v7_relativeSplatPath}function xa(e){let{to:t,replace:n,state:r,relative:l}=e;wn()||W(!1);let{future:o,static:i}=g.useContext(et),{matches:u}=g.useContext(tt),{pathname:a}=_t(),s=Gc(),h=ou(t,lu(u,o.v7_relativeSplatPath),a,l==="path"),p=JSON.stringify(h);return g.useEffect(()=>s(JSON.parse(p),{replace:n,state:r,relative:l}),[s,p,l,n,r]),null}function om(e){return ih(e.context)}function G(e){W(!1)}function kh(e){let{basename:t="/",children:n=null,location:r,navigationType:l=st.Pop,navigator:o,static:i=!1,future:u}=e;wn()&&W(!1);let a=t.replace(/^\/*/,"/"),s=g.useMemo(()=>({basename:a,navigator:o,static:i,future:ur({v7_relativeSplatPath:!1},u)}),[a,u,o,i]);typeof r=="string"&&(r=gn(r));let{pathname:h="/",search:p="",hash:m="",state:S=null,key:w="default"}=r,k=g.useMemo(()=>{let _=hn(h,a);return _==null?null:{location:{pathname:_,search:p,hash:m,state:S,key:w},navigationType:l}},[a,h,p,m,S,w,l]);return k==null?null:g.createElement(et.Provider,{value:s},g.createElement(Ol.Provider,{children:n,value:k}))}function Ea(e){let{children:t,location:n}=e;return uh(ui(t),n)}new Promise(()=>{});function ui(e,t){t===void 0&&(t=[]);let n=[];return g.Children.forEach(e,(r,l)=>{if(!g.isValidElement(r))return;let o=[...t,l];if(r.type===g.Fragment){n.push.apply(n,ui(r.props.children,o));return}r.type!==G&&W(!1),!r.props.index||!r.props.children||W(!1);let i={id:r.props.id||o.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,loader:r.props.loader,action:r.props.action,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary: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=ui(r.props.children,o)),n.push(i)}),n}/**
+ * React Router DOM v6.30.4
+ *
+ * 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
+ */function ml(){return ml=Object.assign?Object.assign.bind():function(e){for(var t=1;t{s&&_a?_a(()=>a(p)):a(p)},[a,s]);return g.useLayoutEffect(()=>i.listen(h),[i,h]),g.useEffect(()=>Sh(r),[r]),g.createElement(kh,{basename:t,children:n,location:u.location,navigationType:u.action,navigator:i,future:r})}const jh=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",zh=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,De=g.forwardRef(function(t,n){let{onClick:r,relative:l,reloadDocument:o,replace:i,state:u,target:a,to:s,preventScrollReset:h,viewTransition:p}=t,m=qc(t,_h),{basename:S}=g.useContext(et),w,k=!1;if(typeof s=="string"&&zh.test(s)&&(w=s,jh))try{let d=new URL(window.location.href),v=s.startsWith("//")?new URL(d.protocol+s):new URL(s),E=hn(v.pathname,S);v.origin===d.origin&&E!=null?s=E+v.search+v.hash:k=!0}catch{}let _=rh(s,{relative:l}),f=Oh(s,{replace:i,state:u,target:a,preventScrollReset:h,relative:l,viewTransition:p});function c(d){r&&r(d),d.defaultPrevented||f(d)}return g.createElement("a",ml({},m,{href:w||_,onClick:k||o?r:c,ref:n,target:a}))}),im=g.forwardRef(function(t,n){let{"aria-current":r="page",caseSensitive:l=!1,className:o="",end:i=!1,style:u,to:a,viewTransition:s,children:h}=t,p=qc(t,Ch),m=Il(a,{relative:p.relative}),S=_t(),w=g.useContext(Qc),{navigator:k,basename:_}=g.useContext(et),f=w!=null&&Ih(m)&&s===!0,c=k.encodeLocation?k.encodeLocation(m).pathname:m.pathname,d=S.pathname,v=w&&w.navigation&&w.navigation.location?w.navigation.location.pathname:null;l||(d=d.toLowerCase(),v=v?v.toLowerCase():null,c=c.toLowerCase()),v&&_&&(v=hn(v,_)||v);const E=c!=="/"&&c.endsWith("/")?c.length-1:c.length;let N=d===c||!i&&d.startsWith(c)&&d.charAt(E)==="/",L=v!=null&&(v===c||!i&&v.startsWith(c)&&v.charAt(c.length)==="/"),R={isActive:N,isPending:L,isTransitioning:f},$=N?r:void 0,z;typeof o=="function"?z=o(R):z=[o,N?"active":null,L?"pending":null,f?"transitioning":null].filter(Boolean).join(" ");let ge=typeof u=="function"?u(R):u;return g.createElement(De,ml({},p,{"aria-current":$,className:z,ref:n,style:ge,to:a,viewTransition:s}),typeof h=="function"?h(R):h)});var ai;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(ai||(ai={}));var Ca;(function(e){e.UseFetcher="useFetcher",e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(Ca||(Ca={}));function Th(e){let t=g.useContext(Tl);return t||W(!1),t}function Oh(e,t){let{target:n,replace:r,state:l,preventScrollReset:o,relative:i,viewTransition:u}=t===void 0?{}:t,a=Gc(),s=_t(),h=Il(e,{relative:i});return g.useCallback(p=>{if(Eh(p,n)){p.preventDefault();let m=r!==void 0?r:hl(s)===hl(h);a(e,{replace:m,state:l,preventScrollReset:o,relative:i,viewTransition:u})}},[s,a,h,r,l,n,e,o,i,u])}function Ih(e,t){t===void 0&&(t={});let n=g.useContext(Nh);n==null&&W(!1);let{basename:r}=Th(ai.useViewTransitionState),l=Il(e,{relative:t.relative});if(!n.isTransitioning)return!1;let o=hn(n.currentLocation.pathname,r)||n.currentLocation.pathname,i=hn(n.nextLocation.pathname,r)||n.nextLocation.pathname;return ii(l.pathname,i)!=null||ii(l.pathname,o)!=null}const Dh="modulepreload",Mh=function(e){return"/"+e},Pa={},le=function(t,n,r){let l=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const i=document.querySelector("meta[property=csp-nonce]"),u=(i==null?void 0:i.nonce)||(i==null?void 0:i.getAttribute("nonce"));l=Promise.allSettled(n.map(a=>{if(a=Mh(a),a in Pa)return;Pa[a]=!0;const s=a.endsWith(".css"),h=s?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${a}"]${h}`))return;const p=document.createElement("link");if(p.rel=s?"stylesheet":Dh,s||(p.as="script"),p.crossOrigin="",p.href=a,u&&p.setAttribute("nonce",u),document.head.appendChild(p),s)return new Promise((m,S)=>{p.addEventListener("load",m),p.addEventListener("error",()=>S(new Error(`Unable to preload CSS for ${a}`)))})}))}function o(i){const u=new Event("vite:preloadError",{cancelable:!0});if(u.payload=i,window.dispatchEvent(u),!u.defaultPrevented)throw i}return l.then(i=>{for(const u of i||[])u.status==="rejected"&&o(u.reason);return t().catch(o)})},Na=[{id:"company",label:"회사소개",children:[{label:"CEO 인사말",path:"/company/greeting"},{label:"연혁",path:"/company/history"},{label:"조직도",path:"/company/organization"},{label:"CI 소개",path:"/company/ci"},{label:"오시는 길",path:"/company/location"}]},{id:"solution",label:"솔루션",children:[{label:"GUARDiA ITSM",path:"/solution/guardia",badge:"NEW"},{label:"ERP",path:"/solution/erp"},{label:"CRM",path:"/solution/crm"},{label:"BI",path:"/solution/bi"}]},{id:"business",label:"사업실적",children:[{label:"구축 레퍼런스",path:"/business/reference"},{label:"파트너",path:"/business/partner"}]},{id:"support",label:"고객지원",children:[{label:"공지사항",path:"/support/notice"},{label:"FAQ",path:"/support/faq"},{label:"카탈로그",path:"/support/catalog"},{label:"문의하기",path:"/support/contact"}]},{id:"recruit",label:"채용",children:[{label:"채용공고",path:"/recruit/jobs"},{label:"복리후생",path:"/recruit/welfare"},{label:"지원하기",path:"/recruit/apply"}]},{id:"news",label:"뉴스",children:[{label:"뉴스룸",path:"/news/newsroom"},{label:"기술 블로그",path:"/news/blog"}]}];function Fh(){const[e,t]=g.useState(!1),[n,r]=g.useState(null),[l,o]=g.useState(!1),i=_t();g.useEffect(()=>{const a=()=>t(window.scrollY>60);return window.addEventListener("scroll",a,{passive:!0}),()=>window.removeEventListener("scroll",a)},[]),g.useEffect(()=>{o(!1),r(null)},[i]);const u=a=>{var s;return(s=a.children)==null?void 0:s.some(h=>i.pathname.startsWith(h.path))};return y.jsxs(y.Fragment,{children:[y.jsx("a",{href:"#main-content",className:"skip-link",children:"본문 바로가기"}),y.jsxs("header",{className:`header ${e?"scrolled":""} ${l?"mobile-open":""}`,role:"banner",children:[y.jsxs("div",{className:"header-inner container",children:[y.jsxs(De,{to:"/",className:"logo","aria-label":"(주)지오정보기술 홈으로",children:[y.jsx("img",{src:"/logo.png",alt:"(주)지오정보기술 로고",height:"40",onError:a=>{a.target.style.display="none",a.target.nextSibling.style.display="flex"}}),y.jsxs("span",{className:"logo-text",style:{display:"none"},children:[y.jsx("strong",{children:"Zio"}),"Info"]})]}),y.jsx("nav",{className:"nav-desktop",role:"navigation","aria-label":"주요 메뉴",children:Na.map(a=>y.jsxs("div",{className:`nav-item ${u(a)?"active":""}`,onMouseEnter:()=>r(a.id),onMouseLeave:()=>r(null),children:[y.jsx("button",{className:"nav-trigger","aria-haspopup":"true","aria-expanded":n===a.id,children:a.label}),n===a.id&&y.jsx("div",{className:"dropdown",role:"menu",children:a.children.map(s=>y.jsxs(De,{to:s.path,className:`dropdown-item ${i.pathname===s.path?"current":""}`,role:"menuitem",children:[s.label,s.badge&&y.jsx("span",{className:"badge badge-new",children:s.badge})]},s.path))})]},a.id))}),y.jsx(De,{to:"/support/contact",className:"btn btn-primary btn-sm header-cta",children:"문의하기"}),y.jsxs("button",{className:"hamburger","aria-label":"모바일 메뉴","aria-expanded":l,onClick:()=>o(a=>!a),children:[y.jsx("span",{}),y.jsx("span",{}),y.jsx("span",{})]})]}),l&&y.jsxs("nav",{className:"nav-mobile",role:"navigation","aria-label":"모바일 메뉴",children:[Na.map(a=>y.jsxs("details",{className:"mobile-group",children:[y.jsx("summary",{className:"mobile-group-header",children:a.label}),y.jsx("div",{className:"mobile-children",children:a.children.map(s=>y.jsxs(De,{to:s.path,className:"mobile-child",children:[s.label,s.badge&&y.jsx("span",{className:"badge badge-new",children:s.badge})]},s.path))})]},a.id)),y.jsx(De,{to:"/support/contact",className:"btn btn-primary",style:{margin:"16px"},children:"문의하기"})]})]})]})}const Uh=[{title:"회사소개",links:[{label:"CEO 인사말",path:"/company/greeting"},{label:"연혁",path:"/company/history"},{label:"조직도",path:"/company/organization"},{label:"오시는 길",path:"/company/location"}]},{title:"솔루션",links:[{label:"GUARDiA ITSM",path:"/solution/guardia"},{label:"ERP",path:"/solution/erp"},{label:"CRM",path:"/solution/crm"},{label:"BI",path:"/solution/bi"}]},{title:"고객지원",links:[{label:"공지사항",path:"/support/notice"},{label:"FAQ",path:"/support/faq"},{label:"카탈로그",path:"/support/catalog"},{label:"문의하기",path:"/support/contact"}]},{title:"채용",links:[{label:"채용공고",path:"/recruit/jobs"},{label:"복리후생",path:"/recruit/welfare"},{label:"지원하기",path:"/recruit/apply"}]}];function Ah(){return y.jsxs("footer",{className:"footer",role:"contentinfo",children:[y.jsx("div",{className:"footer-top",children:y.jsxs("div",{className:"container footer-top-inner",children:[y.jsxs("div",{className:"footer-brand",children:[y.jsxs(De,{to:"/",className:"footer-logo",children:[y.jsx("img",{src:"/logo-white.png",alt:"(주)지오정보기술 로고",height:"36",onError:e=>{e.target.style.display="none",e.target.nextSibling.style.display="block"}}),y.jsxs("span",{className:"footer-logo-text",style:{display:"none"},children:[y.jsx("strong",{children:"Zio"}),"Info"]})]}),y.jsxs("p",{className:"footer-tagline",children:["AI 기반 레거시 인프라 자율 운영 플랫폼",y.jsx("br",{}),"GUARDiA ITSM으로 공공기관 IT를 혁신합니다."]}),y.jsxs("div",{className:"footer-contact-list",children:[y.jsxs("div",{className:"footer-contact-item",children:[y.jsx("span",{className:"contact-label",children:"대표전화"}),y.jsx("span",{children:"02-000-0000"})]}),y.jsxs("div",{className:"footer-contact-item",children:[y.jsx("span",{className:"contact-label",children:"이메일"}),y.jsx("a",{href:"mailto:info@zioinfo.co.kr",children:"info@zioinfo.co.kr"})]}),y.jsxs("div",{className:"footer-contact-item",children:[y.jsx("span",{className:"contact-label",children:"주소"}),y.jsx("span",{children:"서울특별시"})]})]})]}),Uh.map((e,t)=>y.jsxs("div",{className:"footer-menu-group",children:[y.jsx("h3",{className:"footer-menu-title",children:e.title}),y.jsx("ul",{className:"footer-menu-list",children:e.links.map((n,r)=>y.jsx("li",{children:y.jsx(De,{to:n.path,children:n.label})},r))})]},t))]})}),y.jsx("div",{className:"footer-bottom",children:y.jsxs("div",{className:"container footer-bottom-inner",children:[y.jsxs("div",{className:"footer-legal",children:[y.jsx(De,{to:"/privacy",children:"개인정보처리방침"}),y.jsx(De,{to:"/terms",children:"이용약관"}),y.jsx(De,{to:"/sitemap",children:"사이트맵"})]}),y.jsx("p",{className:"footer-copyright",children:"Copyright © 2026 (주)지오정보기술 All Rights Reserved."}),y.jsxs("div",{className:"footer-powered",children:["Powered by ",y.jsx("strong",{children:"GUARDiA ITSM"})]})]})})]})}const $h=g.lazy(()=>le(()=>import("./Home-BC38QtTl.js"),__vite__mapDeps([0,1,2]))),Bh=g.lazy(()=>le(()=>import("./GuardiaDetail-5Pm8bk4O.js"),__vite__mapDeps([3,4]))),Vh=g.lazy(()=>le(()=>import("./SolutionPage-Da0Vpoc-.js"),__vite__mapDeps([5,6,7]))),Wh=g.lazy(()=>le(()=>import("./Company-BOdWAIQ4.js"),__vite__mapDeps([8,9,7]))),Hh=g.lazy(()=>le(()=>import("./Business-EGnXphuY.js"),__vite__mapDeps([10,11,7]))),Qh=g.lazy(()=>le(()=>import("./Contact-C6p_tBWi.js"),__vite__mapDeps([12,1,13]))),Kh=g.lazy(()=>le(()=>import("./Support-C5QVP1gW.js"),__vite__mapDeps([14,15,7]))),Yh=g.lazy(()=>le(()=>import("./NewsPage-mgytOZhS.js"),__vite__mapDeps([16,17,7]))),Gh=g.lazy(()=>le(()=>import("./Recruit-DlKGM6KQ.js"),__vite__mapDeps([18,19,7]))),Xh=g.lazy(()=>le(()=>import("./NotFound-KZZDVQMb.js"),[])),Zh=g.lazy(()=>le(()=>import("./AdminLogin-DcRT5LbX.js"),__vite__mapDeps([20,21]))),Jh=g.lazy(()=>le(()=>import("./AdminLayout-uWX9KsdF.js"),__vite__mapDeps([22,21]))),qh=g.lazy(()=>le(()=>import("./AdminDashboard-B5ryl_KI.js"),[])),bh=g.lazy(()=>le(()=>import("./AdminNews-CDSgPR9E.js"),[])),em=g.lazy(()=>le(()=>import("./AdminInquiry-BBFBdE8S.js"),[])),tm=g.lazy(()=>le(()=>import("./AdminRecruit-CfX4mhQb.js"),[])),nm=g.lazy(()=>le(()=>import("./AdminSettings-DaHEGHsg.js"),[]));function bc(){return y.jsx("div",{style:{display:"flex",alignItems:"center",justifyContent:"center",height:"60vh",color:"var(--gray-400)",fontSize:"14px"},children:"로딩 중..."})}function rm({children:e}){return y.jsxs(y.Fragment,{children:[y.jsx(Fh,{}),y.jsx(g.Suspense,{fallback:y.jsx(bc,{}),children:e}),y.jsx(Ah,{})]})}function lm(){return _t().pathname.startsWith("/admin")?y.jsx(g.Suspense,{fallback:y.jsx(bc,{}),children:y.jsxs(Ea,{children:[y.jsx(G,{path:"/admin/login",element:y.jsx(Zh,{})}),y.jsxs(G,{path:"/admin",element:y.jsx(Jh,{}),children:[y.jsx(G,{index:!0,element:y.jsx(xa,{to:"/admin/dashboard",replace:!0})}),y.jsx(G,{path:"dashboard",element:y.jsx(qh,{})}),y.jsx(G,{path:"news",element:y.jsx(bh,{})}),y.jsx(G,{path:"inquiries",element:y.jsx(em,{})}),y.jsx(G,{path:"recruit",element:y.jsx(tm,{})}),y.jsx(G,{path:"settings",element:y.jsx(nm,{})})]}),y.jsx(G,{path:"*",element:y.jsx(xa,{to:"/admin/login",replace:!0})})]})}):y.jsx(rm,{children:y.jsxs(Ea,{children:[y.jsx(G,{path:"/",element:y.jsx($h,{})}),y.jsx(G,{path:"/solution/guardia",element:y.jsx(Bh,{})}),y.jsx(G,{path:"/solution/*",element:y.jsx(Vh,{})}),y.jsx(G,{path:"/company/*",element:y.jsx(Wh,{})}),y.jsx(G,{path:"/business/*",element:y.jsx(Hh,{})}),y.jsx(G,{path:"/support/contact",element:y.jsx(Qh,{})}),y.jsx(G,{path:"/support/*",element:y.jsx(Kh,{})}),y.jsx(G,{path:"/recruit/*",element:y.jsx(Gh,{})}),y.jsx(G,{path:"/news/*",element:y.jsx(Yh,{})}),y.jsx(G,{path:"*",element:y.jsx(Xh,{})})]})})}fo.createRoot(document.getElementById("root")).render(y.jsx(Ua.StrictMode,{children:y.jsx(Rh,{children:y.jsx(lm,{})})}));export{De as L,im as N,om as O,Ua as R,G as a,Ea as b,Gc as c,y as j,g as r,_t as u};
diff --git a/backend/src/main/resources/static/assets/index-DcNlVx-A.js b/backend/src/main/resources/static/assets/index-DcNlVx-A.js
new file mode 100644
index 00000000..9cf4beb5
--- /dev/null
+++ b/backend/src/main/resources/static/assets/index-DcNlVx-A.js
@@ -0,0 +1,9 @@
+function tt(e,t){return function(){return e.apply(t,arguments)}}const{toString:_t}=Object.prototype,{getPrototypeOf:de}=Object,{iterator:pe,toStringTag:nt}=Symbol,he=(e=>t=>{const n=_t.call(t);return e[n]||(e[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),L=e=>(e=e.toLowerCase(),t=>he(t)===e),me=e=>t=>typeof t===e,{isArray:v}=Array,K=me("undefined");function Z(e){return e!==null&&!K(e)&&e.constructor!==null&&!K(e.constructor)&&C(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}const rt=L("ArrayBuffer");function Tt(e){let t;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?t=ArrayBuffer.isView(e):t=e&&e.buffer&&rt(e.buffer),t}const xt=me("string"),C=me("function"),st=me("number"),ee=e=>e!==null&&typeof e=="object",Ct=e=>e===!0||e===!1,ce=e=>{if(he(e)!=="object")return!1;const t=de(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(nt in e)&&!(pe in e)},Nt=e=>{if(!ee(e)||Z(e))return!1;try{return Object.keys(e).length===0&&Object.getPrototypeOf(e)===Object.prototype}catch{return!1}},Pt=L("Date"),Dt=L("File"),Lt=e=>!!(e&&typeof e.uri<"u"),Ft=e=>e&&typeof e.getParts<"u",Ut=L("Blob"),Bt=L("FileList"),kt=e=>ee(e)&&C(e.pipe);function jt(){return typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{}}const Me=jt(),ze=typeof Me.FormData<"u"?Me.FormData:void 0,qt=e=>{if(!e)return!1;if(ze&&e instanceof ze)return!0;const t=de(e);if(!t||t===Object.prototype||!C(e.append))return!1;const n=he(e);return n==="formdata"||n==="object"&&C(e.toString)&&e.toString()==="[object FormData]"},It=L("URLSearchParams"),[Ht,Mt,zt,$t]=["ReadableStream","Request","Response","Headers"].map(L),Vt=e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function te(e,t,{allOwnKeys:n=!1}={}){if(e===null||typeof e>"u")return;let r,s;if(typeof e!="object"&&(e=[e]),v(e))for(r=0,s=e.length;r0;)if(s=n[r],t===s.toLowerCase())return s;return null}const z=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,it=e=>!K(e)&&e!==z;function _e(...e){const{caseless:t,skipUndefined:n}=it(this)&&this||{},r={},s=(o,i)=>{if(i==="__proto__"||i==="constructor"||i==="prototype")return;const c=t&&ot(r,i)||i,l=Te(r,c)?r[c]:void 0;ce(l)&&ce(o)?r[c]=_e(l,o):ce(o)?r[c]=_e({},o):v(o)?r[c]=o.slice():(!n||!K(o))&&(r[c]=o)};for(let o=0,i=e.length;o(te(t,(s,o)=>{n&&C(s)?Object.defineProperty(e,o,{__proto__:null,value:tt(s,n),writable:!0,enumerable:!0,configurable:!0}):Object.defineProperty(e,o,{__proto__:null,value:s,writable:!0,enumerable:!0,configurable:!0})},{allOwnKeys:r}),e),Wt=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),Kt=(e,t,n,r)=>{e.prototype=Object.create(t.prototype,r),Object.defineProperty(e.prototype,"constructor",{__proto__:null,value:e,writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(e,"super",{__proto__:null,value:t.prototype}),n&&Object.assign(e.prototype,n)},vt=(e,t,n,r)=>{let s,o,i;const c={};if(t=t||{},e==null)return t;do{for(s=Object.getOwnPropertyNames(e),o=s.length;o-- >0;)i=s[o],(!r||r(i,e,t))&&!c[i]&&(t[i]=e[i],c[i]=!0);e=n!==!1&&de(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},Xt=(e,t,n)=>{e=String(e),(n===void 0||n>e.length)&&(n=e.length),n-=t.length;const r=e.indexOf(t,n);return r!==-1&&r===n},Gt=e=>{if(!e)return null;if(v(e))return e;let t=e.length;if(!st(t))return null;const n=new Array(t);for(;t-- >0;)n[t]=e[t];return n},Qt=(e=>t=>e&&t instanceof e)(typeof Uint8Array<"u"&&de(Uint8Array)),Yt=(e,t)=>{const r=(e&&e[pe]).call(e);let s;for(;(s=r.next())&&!s.done;){const o=s.value;t.call(e,o[0],o[1])}},Zt=(e,t)=>{let n;const r=[];for(;(n=e.exec(t))!==null;)r.push(n);return r},en=L("HTMLFormElement"),tn=e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(n,r,s){return r.toUpperCase()+s}),Te=(({hasOwnProperty:e})=>(t,n)=>e.call(t,n))(Object.prototype),nn=L("RegExp"),at=(e,t)=>{const n=Object.getOwnPropertyDescriptors(e),r={};te(n,(s,o)=>{let i;(i=t(s,o,e))!==!1&&(r[o]=i||s)}),Object.defineProperties(e,r)},rn=e=>{at(e,(t,n)=>{if(C(e)&&["arguments","caller","callee"].includes(n))return!1;const r=e[n];if(C(r)){if(t.enumerable=!1,"writable"in t){t.writable=!1;return}t.set||(t.set=()=>{throw Error("Can not rewrite read-only method '"+n+"'")})}})},sn=(e,t)=>{const n={},r=s=>{s.forEach(o=>{n[o]=!0})};return v(e)?r(e):r(String(e).split(t)),n},on=()=>{},an=(e,t)=>e!=null&&Number.isFinite(e=+e)?e:t;function cn(e){return!!(e&&C(e.append)&&e[nt]==="FormData"&&e[pe])}const ln=e=>{const t=new WeakSet,n=r=>{if(ee(r)){if(t.has(r))return;if(Z(r))return r;if(!("toJSON"in r)){t.add(r);const s=v(r)?[]:{};return te(r,(o,i)=>{const c=n(o);!K(c)&&(s[i]=c)}),t.delete(r),s}}return r};return n(e)},un=L("AsyncFunction"),fn=e=>e&&(ee(e)||C(e))&&C(e.then)&&C(e.catch),ct=((e,t)=>e?setImmediate:t?((n,r)=>(z.addEventListener("message",({source:s,data:o})=>{s===z&&o===n&&r.length&&r.shift()()},!1),s=>{r.push(s),z.postMessage(n,"*")}))(`axios@${Math.random()}`,[]):n=>setTimeout(n))(typeof setImmediate=="function",C(z.postMessage)),dn=typeof queueMicrotask<"u"?queueMicrotask.bind(z):typeof process<"u"&&process.nextTick||ct,pn=e=>e!=null&&C(e[pe]),a={isArray:v,isArrayBuffer:rt,isBuffer:Z,isFormData:qt,isArrayBufferView:Tt,isString:xt,isNumber:st,isBoolean:Ct,isObject:ee,isPlainObject:ce,isEmptyObject:Nt,isReadableStream:Ht,isRequest:Mt,isResponse:zt,isHeaders:$t,isUndefined:K,isDate:Pt,isFile:Dt,isReactNativeBlob:Lt,isReactNative:Ft,isBlob:Ut,isRegExp:nn,isFunction:C,isStream:kt,isURLSearchParams:It,isTypedArray:Qt,isFileList:Bt,forEach:te,merge:_e,extend:Jt,trim:Vt,stripBOM:Wt,inherits:Kt,toFlatObject:vt,kindOf:he,kindOfTest:L,endsWith:Xt,toArray:Gt,forEachEntry:Yt,matchAll:Zt,isHTMLForm:en,hasOwnProperty:Te,hasOwnProp:Te,reduceDescriptors:at,freezeMethods:rn,toObjectSet:sn,toCamelCase:tn,noop:on,toFiniteNumber:an,findKey:ot,global:z,isContextDefined:it,isSpecCompliantForm:cn,toJSONObject:ln,isAsyncFn:un,isThenable:fn,setImmediate:ct,asap:dn,isIterable:pn},hn=a.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),mn=e=>{const t={};let n,r,s;return e&&e.split(`
+`).forEach(function(i){s=i.indexOf(":"),n=i.substring(0,s).trim().toLowerCase(),r=i.substring(s+1).trim(),!(!n||t[n]&&hn[n])&&(n==="set-cookie"?t[n]?t[n].push(r):t[n]=[r]:t[n]=t[n]?t[n]+", "+r:r)}),t};function yn(e){let t=0,n=e.length;for(;tt;){const r=e.charCodeAt(n-1);if(r!==9&&r!==32)break;n-=1}return t===0&&n===e.length?e:e.slice(t,n)}const bn=new RegExp("[\\u0000-\\u0008\\u000a-\\u001f\\u007f]+","g"),wn=new RegExp("[^\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+","g");function Pe(e,t){return a.isArray(e)?e.map(n=>Pe(n,t)):yn(String(e).replace(t,""))}const En=e=>Pe(e,bn),Rn=e=>Pe(e,wn);function lt(e){const t=Object.create(null);return a.forEach(e.toJSON(),(n,r)=>{t[r]=Rn(n)}),t}const $e=Symbol("internals");function Y(e){return e&&String(e).trim().toLowerCase()}function le(e){return e===!1||e==null?e:a.isArray(e)?e.map(le):En(String(e))}function gn(e){const t=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let r;for(;r=n.exec(e);)t[r[1]]=r[2];return t}const On=e=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim());function ge(e,t,n,r,s){if(a.isFunction(r))return r.call(this,t,n);if(s&&(t=n),!!a.isString(t)){if(a.isString(r))return t.indexOf(r)!==-1;if(a.isRegExp(r))return r.test(t)}}function Sn(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(t,n,r)=>n.toUpperCase()+r)}function An(e,t){const n=a.toCamelCase(" "+t);["get","set","has"].forEach(r=>{Object.defineProperty(e,r+n,{__proto__:null,value:function(s,o,i){return this[r].call(this,t,s,o,i)},configurable:!0})})}let x=class{constructor(t){t&&this.set(t)}set(t,n,r){const s=this;function o(c,l,f){const u=Y(l);if(!u)throw new Error("header name must be a non-empty string");const y=a.findKey(s,u);(!y||s[y]===void 0||f===!0||f===void 0&&s[y]!==!1)&&(s[y||l]=le(c))}const i=(c,l)=>a.forEach(c,(f,u)=>o(f,u,l));if(a.isPlainObject(t)||t instanceof this.constructor)i(t,n);else if(a.isString(t)&&(t=t.trim())&&!On(t))i(mn(t),n);else if(a.isObject(t)&&a.isIterable(t)){let c={},l,f;for(const u of t){if(!a.isArray(u))throw TypeError("Object iterator must return a key-value pair");c[f=u[0]]=(l=c[f])?a.isArray(l)?[...l,u[1]]:[l,u[1]]:u[1]}i(c,n)}else t!=null&&o(n,t,r);return this}get(t,n){if(t=Y(t),t){const r=a.findKey(this,t);if(r){const s=this[r];if(!n)return s;if(n===!0)return gn(s);if(a.isFunction(n))return n.call(this,s,r);if(a.isRegExp(n))return n.exec(s);throw new TypeError("parser must be boolean|regexp|function")}}}has(t,n){if(t=Y(t),t){const r=a.findKey(this,t);return!!(r&&this[r]!==void 0&&(!n||ge(this,this[r],r,n)))}return!1}delete(t,n){const r=this;let s=!1;function o(i){if(i=Y(i),i){const c=a.findKey(r,i);c&&(!n||ge(r,r[c],c,n))&&(delete r[c],s=!0)}}return a.isArray(t)?t.forEach(o):o(t),s}clear(t){const n=Object.keys(this);let r=n.length,s=!1;for(;r--;){const o=n[r];(!t||ge(this,this[o],o,t,!0))&&(delete this[o],s=!0)}return s}normalize(t){const n=this,r={};return a.forEach(this,(s,o)=>{const i=a.findKey(r,o);if(i){n[i]=le(s),delete n[o];return}const c=t?Sn(o):String(o).trim();c!==o&&delete n[o],n[c]=le(s),r[c]=!0}),this}concat(...t){return this.constructor.concat(this,...t)}toJSON(t){const n=Object.create(null);return a.forEach(this,(r,s)=>{r!=null&&r!==!1&&(n[s]=t&&a.isArray(r)?r.join(", "):r)}),n}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([t,n])=>t+": "+n).join(`
+`)}getSetCookie(){return this.get("set-cookie")||[]}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(t){return t instanceof this?t:new this(t)}static concat(t,...n){const r=new this(t);return n.forEach(s=>r.set(s)),r}static accessor(t){const r=(this[$e]=this[$e]={accessors:{}}).accessors,s=this.prototype;function o(i){const c=Y(i);r[c]||(An(s,i),r[c]=!0)}return a.isArray(t)?t.forEach(o):o(t),this}};x.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);a.reduceDescriptors(x.prototype,({value:e},t)=>{let n=t[0].toUpperCase()+t.slice(1);return{get:()=>e,set(r){this[n]=r}}});a.freezeMethods(x);const _n="[REDACTED ****]";function Tn(e){if(a.hasOwnProp(e,"toJSON"))return!0;let t=Object.getPrototypeOf(e);for(;t&&t!==Object.prototype;){if(a.hasOwnProp(t,"toJSON"))return!0;t=Object.getPrototypeOf(t)}return!1}function xn(e,t){const n=new Set(t.map(o=>String(o).toLowerCase())),r=[],s=o=>{if(o===null||typeof o!="object"||a.isBuffer(o))return o;if(r.indexOf(o)!==-1)return;o instanceof x&&(o=o.toJSON()),r.push(o);let i;if(a.isArray(o))i=[],o.forEach((c,l)=>{const f=s(c);a.isUndefined(f)||(i[l]=f)});else{if(!a.isPlainObject(o)&&Tn(o))return r.pop(),o;i=Object.create(null);for(const[c,l]of Object.entries(o)){const f=n.has(c.toLowerCase())?_n:s(l);a.isUndefined(f)||(i[c]=f)}}return r.pop(),i};return s(e)}let p=class ut extends Error{static from(t,n,r,s,o,i){const c=new ut(t.message,n||t.code,r,s,o);return c.cause=t,c.name=t.name,t.status!=null&&c.status==null&&(c.status=t.status),i&&Object.assign(c,i),c}constructor(t,n,r,s,o){super(t),Object.defineProperty(this,"message",{__proto__:null,value:t,enumerable:!0,writable:!0,configurable:!0}),this.name="AxiosError",this.isAxiosError=!0,n&&(this.code=n),r&&(this.config=r),s&&(this.request=s),o&&(this.response=o,this.status=o.status)}toJSON(){const t=this.config,n=t&&a.hasOwnProp(t,"redact")?t.redact:void 0,r=a.isArray(n)&&n.length>0?xn(t,n):a.toJSONObject(t);return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:r,code:this.code,status:this.status}}};p.ERR_BAD_OPTION_VALUE="ERR_BAD_OPTION_VALUE";p.ERR_BAD_OPTION="ERR_BAD_OPTION";p.ECONNABORTED="ECONNABORTED";p.ETIMEDOUT="ETIMEDOUT";p.ECONNREFUSED="ECONNREFUSED";p.ERR_NETWORK="ERR_NETWORK";p.ERR_FR_TOO_MANY_REDIRECTS="ERR_FR_TOO_MANY_REDIRECTS";p.ERR_DEPRECATED="ERR_DEPRECATED";p.ERR_BAD_RESPONSE="ERR_BAD_RESPONSE";p.ERR_BAD_REQUEST="ERR_BAD_REQUEST";p.ERR_CANCELED="ERR_CANCELED";p.ERR_NOT_SUPPORT="ERR_NOT_SUPPORT";p.ERR_INVALID_URL="ERR_INVALID_URL";p.ERR_FORM_DATA_DEPTH_EXCEEDED="ERR_FORM_DATA_DEPTH_EXCEEDED";const Cn=null;function xe(e){return a.isPlainObject(e)||a.isArray(e)}function ft(e){return a.endsWith(e,"[]")?e.slice(0,-2):e}function Oe(e,t,n){return e?e.concat(t).map(function(s,o){return s=ft(s),!n&&o?"["+s+"]":s}).join(n?".":""):t}function Nn(e){return a.isArray(e)&&!e.some(xe)}const Pn=a.toFlatObject(a,{},null,function(t){return/^is[A-Z]/.test(t)});function ye(e,t,n){if(!a.isObject(e))throw new TypeError("target must be an object");t=t||new FormData,n=a.toFlatObject(n,{metaTokens:!0,dots:!1,indexes:!1},!1,function(d,m){return!a.isUndefined(m[d])});const r=n.metaTokens,s=n.visitor||y,o=n.dots,i=n.indexes,c=n.Blob||typeof Blob<"u"&&Blob,l=n.maxDepth===void 0?100:n.maxDepth,f=c&&a.isSpecCompliantForm(t);if(!a.isFunction(s))throw new TypeError("visitor must be a function");function u(h){if(h===null)return"";if(a.isDate(h))return h.toISOString();if(a.isBoolean(h))return h.toString();if(!f&&a.isBlob(h))throw new p("Blob is not supported. Use a Buffer instead.");return a.isArrayBuffer(h)||a.isTypedArray(h)?f&&typeof Blob=="function"?new Blob([h]):Buffer.from(h):h}function y(h,d,m){let O=h;if(a.isReactNative(t)&&a.isReactNativeBlob(h))return t.append(Oe(m,d,o),u(h)),!1;if(h&&!m&&typeof h=="object"){if(a.endsWith(d,"{}"))d=r?d:d.slice(0,-2),h=JSON.stringify(h);else if(a.isArray(h)&&Nn(h)||(a.isFileList(h)||a.endsWith(d,"[]"))&&(O=a.toArray(h)))return d=ft(d),O.forEach(function(R,N){!(a.isUndefined(R)||R===null)&&t.append(i===!0?Oe([d],N,o):i===null?d:d+"[]",u(R))}),!1}return xe(h)?!0:(t.append(Oe(m,d,o),u(h)),!1)}const E=[],b=Object.assign(Pn,{defaultVisitor:y,convertValue:u,isVisitable:xe});function w(h,d,m=0){if(!a.isUndefined(h)){if(m>l)throw new p("Object is too deeply nested ("+m+" levels). Max depth: "+l,p.ERR_FORM_DATA_DEPTH_EXCEEDED);if(E.indexOf(h)!==-1)throw Error("Circular reference detected in "+d.join("."));E.push(h),a.forEach(h,function(g,R){(!(a.isUndefined(g)||g===null)&&s.call(t,g,a.isString(R)?R.trim():R,d,b))===!0&&w(g,d?d.concat(R):[R],m+1)}),E.pop()}}if(!a.isObject(e))throw new TypeError("data must be an object");return w(e),t}function Ve(e){const t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"};return encodeURIComponent(e).replace(/[!'()~]|%20/g,function(r){return t[r]})}function De(e,t){this._pairs=[],e&&ye(e,this,t)}const dt=De.prototype;dt.append=function(t,n){this._pairs.push([t,n])};dt.toString=function(t){const n=t?function(r){return t.call(this,r,Ve)}:Ve;return this._pairs.map(function(s){return n(s[0])+"="+n(s[1])},"").join("&")};function Dn(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+")}function pt(e,t,n){if(!t)return e;const r=n&&n.encode||Dn,s=a.isFunction(n)?{serialize:n}:n,o=s&&s.serialize;let i;if(o?i=o(t,s):i=a.isURLSearchParams(t)?t.toString():new De(t,s).toString(r),i){const c=e.indexOf("#");c!==-1&&(e=e.slice(0,c)),e+=(e.indexOf("?")===-1?"?":"&")+i}return e}class Je{constructor(){this.handlers=[]}use(t,n,r){return this.handlers.push({fulfilled:t,rejected:n,synchronous:r?r.synchronous:!1,runWhen:r?r.runWhen:null}),this.handlers.length-1}eject(t){this.handlers[t]&&(this.handlers[t]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(t){a.forEach(this.handlers,function(r){r!==null&&t(r)})}}const Le={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1,legacyInterceptorReqResOrdering:!0},Ln=typeof URLSearchParams<"u"?URLSearchParams:De,Fn=typeof FormData<"u"?FormData:null,Un=typeof Blob<"u"?Blob:null,Bn={isBrowser:!0,classes:{URLSearchParams:Ln,FormData:Fn,Blob:Un},protocols:["http","https","file","blob","url","data"]},Fe=typeof window<"u"&&typeof document<"u",Ce=typeof navigator=="object"&&navigator||void 0,kn=Fe&&(!Ce||["ReactNative","NativeScript","NS"].indexOf(Ce.product)<0),jn=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",qn=Fe&&window.location.href||"http://localhost",In=Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:Fe,hasStandardBrowserEnv:kn,hasStandardBrowserWebWorkerEnv:jn,navigator:Ce,origin:qn},Symbol.toStringTag,{value:"Module"})),T={...In,...Bn};function Hn(e,t){return ye(e,new T.classes.URLSearchParams,{visitor:function(n,r,s,o){return T.isNode&&a.isBuffer(n)?(this.append(r,n.toString("base64")),!1):o.defaultVisitor.apply(this,arguments)},...t})}function Mn(e){return a.matchAll(/\w+|\[(\w*)]/g,e).map(t=>t[0]==="[]"?"":t[1]||t[0])}function zn(e){const t={},n=Object.keys(e);let r;const s=n.length;let o;for(r=0;r=n.length;return i=!i&&a.isArray(s)?s.length:i,l?(a.hasOwnProp(s,i)?s[i]=a.isArray(s[i])?s[i].concat(r):[s[i],r]:s[i]=r,!c):((!a.hasOwnProp(s,i)||!a.isObject(s[i]))&&(s[i]=[]),t(n,r,s[i],o)&&a.isArray(s[i])&&(s[i]=zn(s[i])),!c)}if(a.isFormData(e)&&a.isFunction(e.entries)){const n={};return a.forEachEntry(e,(r,s)=>{t(Mn(r),s,n,0)}),n}return null}const W=(e,t)=>e!=null&&a.hasOwnProp(e,t)?e[t]:void 0;function $n(e,t,n){if(a.isString(e))try{return(t||JSON.parse)(e),a.trim(e)}catch(r){if(r.name!=="SyntaxError")throw r}return(n||JSON.stringify)(e)}const ne={transitional:Le,adapter:["xhr","http","fetch"],transformRequest:[function(t,n){const r=n.getContentType()||"",s=r.indexOf("application/json")>-1,o=a.isObject(t);if(o&&a.isHTMLForm(t)&&(t=new FormData(t)),a.isFormData(t))return s?JSON.stringify(ht(t)):t;if(a.isArrayBuffer(t)||a.isBuffer(t)||a.isStream(t)||a.isFile(t)||a.isBlob(t)||a.isReadableStream(t))return t;if(a.isArrayBufferView(t))return t.buffer;if(a.isURLSearchParams(t))return n.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),t.toString();let c;if(o){const l=W(this,"formSerializer");if(r.indexOf("application/x-www-form-urlencoded")>-1)return Hn(t,l).toString();if((c=a.isFileList(t))||r.indexOf("multipart/form-data")>-1){const f=W(this,"env"),u=f&&f.FormData;return ye(c?{"files[]":t}:t,u&&new u,l)}}return o||s?(n.setContentType("application/json",!1),$n(t)):t}],transformResponse:[function(t){const n=W(this,"transitional")||ne.transitional,r=n&&n.forcedJSONParsing,s=W(this,"responseType"),o=s==="json";if(a.isResponse(t)||a.isReadableStream(t))return t;if(t&&a.isString(t)&&(r&&!s||o)){const c=!(n&&n.silentJSONParsing)&&o;try{return JSON.parse(t,W(this,"parseReviver"))}catch(l){if(c)throw l.name==="SyntaxError"?p.from(l,p.ERR_BAD_RESPONSE,this,null,W(this,"response")):l}}return t}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:T.classes.FormData,Blob:T.classes.Blob},validateStatus:function(t){return t>=200&&t<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};a.forEach(["delete","get","head","post","put","patch","query"],e=>{ne.headers[e]={}});function Se(e,t){const n=this||ne,r=t||n,s=x.from(r.headers);let o=r.data;return a.forEach(e,function(c){o=c.call(n,o,s.normalize(),t?t.status:void 0)}),s.normalize(),o}function mt(e){return!!(e&&e.__CANCEL__)}let re=class extends p{constructor(t,n,r){super(t??"canceled",p.ERR_CANCELED,n,r),this.name="CanceledError",this.__CANCEL__=!0}};function yt(e,t,n){const r=n.config.validateStatus;!n.status||!r||r(n.status)?e(n):t(new p("Request failed with status code "+n.status,n.status>=400&&n.status<500?p.ERR_BAD_REQUEST:p.ERR_BAD_RESPONSE,n.config,n.request,n))}function Vn(e){const t=/^([-+\w]{1,25}):(?:\/\/)?/.exec(e);return t&&t[1]||""}function Jn(e,t){e=e||10;const n=new Array(e),r=new Array(e);let s=0,o=0,i;return t=t!==void 0?t:1e3,function(l){const f=Date.now(),u=r[o];i||(i=f),n[s]=l,r[s]=f;let y=o,E=0;for(;y!==s;)E+=n[y++],y=y%e;if(s=(s+1)%e,s===o&&(o=(o+1)%e),f-i{n=u,s=null,o&&(clearTimeout(o),o=null),e(...f)};return[(...f)=>{const u=Date.now(),y=u-n;y>=r?i(f,u):(s=f,o||(o=setTimeout(()=>{o=null,i(s)},r-y)))},()=>s&&i(s)]}const fe=(e,t,n=3)=>{let r=0;const s=Jn(50,250);return Wn(o=>{if(!o||typeof o.loaded!="number")return;const i=o.loaded,c=o.lengthComputable?o.total:void 0,l=c!=null?Math.min(i,c):i,f=Math.max(0,l-r),u=s(f);r=Math.max(r,l);const y={loaded:l,total:c,progress:c?l/c:void 0,bytes:f,rate:u||void 0,estimated:u&&c?(c-l)/u:void 0,event:o,lengthComputable:c!=null,[t?"download":"upload"]:!0};e(y)},n)},We=(e,t)=>{const n=e!=null;return[r=>t[0]({lengthComputable:n,total:e,loaded:r}),t[1]]},Ke=e=>(...t)=>a.asap(()=>e(...t)),Kn=T.hasStandardBrowserEnv?((e,t)=>n=>(n=new URL(n,T.origin),e.protocol===n.protocol&&e.host===n.host&&(t||e.port===n.port)))(new URL(T.origin),T.navigator&&/(msie|trident)/i.test(T.navigator.userAgent)):()=>!0,vn=T.hasStandardBrowserEnv?{write(e,t,n,r,s,o,i){if(typeof document>"u")return;const c=[`${e}=${encodeURIComponent(t)}`];a.isNumber(n)&&c.push(`expires=${new Date(n).toUTCString()}`),a.isString(r)&&c.push(`path=${r}`),a.isString(s)&&c.push(`domain=${s}`),o===!0&&c.push("secure"),a.isString(i)&&c.push(`SameSite=${i}`),document.cookie=c.join("; ")},read(e){if(typeof document>"u")return null;const t=document.cookie.split(";");for(let n=0;ne instanceof x?{...e}:e;function V(e,t){t=t||{};const n=Object.create(null);Object.defineProperty(n,"hasOwnProperty",{__proto__:null,value:Object.prototype.hasOwnProperty,enumerable:!1,writable:!0,configurable:!0});function r(f,u,y,E){return a.isPlainObject(f)&&a.isPlainObject(u)?a.merge.call({caseless:E},f,u):a.isPlainObject(u)?a.merge({},u):a.isArray(u)?u.slice():u}function s(f,u,y,E){if(a.isUndefined(u)){if(!a.isUndefined(f))return r(void 0,f,y,E)}else return r(f,u,y,E)}function o(f,u){if(!a.isUndefined(u))return r(void 0,u)}function i(f,u){if(a.isUndefined(u)){if(!a.isUndefined(f))return r(void 0,f)}else return r(void 0,u)}function c(f,u,y){if(a.hasOwnProp(t,y))return r(f,u);if(a.hasOwnProp(e,y))return r(void 0,f)}const l={url:o,method:o,data:o,baseURL:i,transformRequest:i,transformResponse:i,paramsSerializer:i,timeout:i,timeoutMessage:i,withCredentials:i,withXSRFToken:i,adapter:i,responseType:i,xsrfCookieName:i,xsrfHeaderName:i,onUploadProgress:i,onDownloadProgress:i,decompress:i,maxContentLength:i,maxBodyLength:i,beforeRedirect:i,transport:i,httpAgent:i,httpsAgent:i,cancelToken:i,socketPath:i,allowedSocketPaths:i,responseEncoding:i,validateStatus:c,headers:(f,u,y)=>s(ve(f),ve(u),y,!0)};return a.forEach(Object.keys({...e,...t}),function(u){if(u==="__proto__"||u==="constructor"||u==="prototype")return;const y=a.hasOwnProp(l,u)?l[u]:s,E=a.hasOwnProp(e,u)?e[u]:void 0,b=a.hasOwnProp(t,u)?t[u]:void 0,w=y(E,b,u);a.isUndefined(w)&&y!==c||(n[u]=w)}),n}const Qn=["content-type","content-length"];function Yn(e,t,n){if(n!=="content-only"){e.set(t);return}Object.entries(t).forEach(([r,s])=>{Qn.includes(r.toLowerCase())&&e.set(r,s)})}const Zn=e=>encodeURIComponent(e).replace(/%([0-9A-F]{2})/gi,(t,n)=>String.fromCharCode(parseInt(n,16))),wt=e=>{const t=V({},e),n=E=>a.hasOwnProp(t,E)?t[E]:void 0,r=n("data");let s=n("withXSRFToken");const o=n("xsrfHeaderName"),i=n("xsrfCookieName");let c=n("headers");const l=n("auth"),f=n("baseURL"),u=n("allowAbsoluteUrls"),y=n("url");if(t.headers=c=x.from(c),t.url=pt(bt(f,y,u),e.params,e.paramsSerializer),l&&c.set("Authorization","Basic "+btoa((l.username||"")+":"+(l.password?Zn(l.password):""))),a.isFormData(r)&&(T.hasStandardBrowserEnv||T.hasStandardBrowserWebWorkerEnv?c.setContentType(void 0):a.isFunction(r.getHeaders)&&Yn(c,r.getHeaders(),n("formDataHeaderPolicy"))),T.hasStandardBrowserEnv&&(a.isFunction(s)&&(s=s(t)),s===!0||s==null&&Kn(t.url))){const b=o&&i&&vn.read(i);b&&c.set(o,b)}return t},er=typeof XMLHttpRequest<"u",tr=er&&function(e){return new Promise(function(n,r){const s=wt(e);let o=s.data;const i=x.from(s.headers).normalize();let{responseType:c,onUploadProgress:l,onDownloadProgress:f}=s,u,y,E,b,w;function h(){b&&b(),w&&w(),s.cancelToken&&s.cancelToken.unsubscribe(u),s.signal&&s.signal.removeEventListener("abort",u)}let d=new XMLHttpRequest;d.open(s.method.toUpperCase(),s.url,!0),d.timeout=s.timeout;function m(){if(!d)return;const g=x.from("getAllResponseHeaders"in d&&d.getAllResponseHeaders()),N={data:!c||c==="text"||c==="json"?d.responseText:d.response,status:d.status,statusText:d.statusText,headers:g,config:e,request:d};yt(function(X){n(X),h()},function(X){r(X),h()},N),d=null}"onloadend"in d?d.onloadend=m:d.onreadystatechange=function(){!d||d.readyState!==4||d.status===0&&!(d.responseURL&&d.responseURL.startsWith("file:"))||setTimeout(m)},d.onabort=function(){d&&(r(new p("Request aborted",p.ECONNABORTED,e,d)),h(),d=null)},d.onerror=function(R){const N=R&&R.message?R.message:"Network Error",I=new p(N,p.ERR_NETWORK,e,d);I.event=R||null,r(I),h(),d=null},d.ontimeout=function(){let R=s.timeout?"timeout of "+s.timeout+"ms exceeded":"timeout exceeded";const N=s.transitional||Le;s.timeoutErrorMessage&&(R=s.timeoutErrorMessage),r(new p(R,N.clarifyTimeoutError?p.ETIMEDOUT:p.ECONNABORTED,e,d)),h(),d=null},o===void 0&&i.setContentType(null),"setRequestHeader"in d&&a.forEach(lt(i),function(R,N){d.setRequestHeader(N,R)}),a.isUndefined(s.withCredentials)||(d.withCredentials=!!s.withCredentials),c&&c!=="json"&&(d.responseType=s.responseType),f&&([E,w]=fe(f,!0),d.addEventListener("progress",E)),l&&d.upload&&([y,b]=fe(l),d.upload.addEventListener("progress",y),d.upload.addEventListener("loadend",b)),(s.cancelToken||s.signal)&&(u=g=>{d&&(r(!g||g.type?new re(null,e,d):g),d.abort(),h(),d=null)},s.cancelToken&&s.cancelToken.subscribe(u),s.signal&&(s.signal.aborted?u():s.signal.addEventListener("abort",u)));const O=Vn(s.url);if(O&&!T.protocols.includes(O)){r(new p("Unsupported protocol "+O+":",p.ERR_BAD_REQUEST,e));return}d.send(o||null)})},nr=(e,t)=>{if(e=e?e.filter(Boolean):[],!t&&!e.length)return;const n=new AbortController;let r=!1;const s=function(l){if(!r){r=!0,i();const f=l instanceof Error?l:this.reason;n.abort(f instanceof p?f:new re(f instanceof Error?f.message:f))}};let o=t&&setTimeout(()=>{o=null,s(new p(`timeout of ${t}ms exceeded`,p.ETIMEDOUT))},t);const i=()=>{e&&(o&&clearTimeout(o),o=null,e.forEach(l=>{l.unsubscribe?l.unsubscribe(s):l.removeEventListener("abort",s)}),e=null)};e.forEach(l=>l.addEventListener("abort",s));const{signal:c}=n;return c.unsubscribe=()=>a.asap(i),c},rr=function*(e,t){let n=e.byteLength;if(n{const s=sr(e,t);let o=0,i,c=l=>{i||(i=!0,r&&r(l))};return new ReadableStream({async pull(l){try{const{done:f,value:u}=await s.next();if(f){c(),l.close();return}let y=u.byteLength;if(n){let E=o+=y;n(E)}l.enqueue(new Uint8Array(u))}catch(f){throw c(f),f}},cancel(l){return c(l),s.return()}},{highWaterMark:2})};function ir(e){if(!e||typeof e!="string"||!e.startsWith("data:"))return 0;const t=e.indexOf(",");if(t<0)return 0;const n=e.slice(5,t),r=e.slice(t+1);if(/;base64/i.test(n)){let i=r.length;const c=r.length;for(let b=0;b=48&&w<=57||w>=65&&w<=70||w>=97&&w<=102)&&(h>=48&&h<=57||h>=65&&h<=70||h>=97&&h<=102)&&(i-=2,b+=2)}let l=0,f=c-1;const u=b=>b>=2&&r.charCodeAt(b-2)===37&&r.charCodeAt(b-1)===51&&(r.charCodeAt(b)===68||r.charCodeAt(b)===100);f>=0&&(r.charCodeAt(f)===61?(l++,f--):u(f)&&(l++,f-=3)),l===1&&f>=0&&(r.charCodeAt(f)===61||u(f))&&l++;const E=Math.floor(i/4)*3-(l||0);return E>0?E:0}if(typeof Buffer<"u"&&typeof Buffer.byteLength=="function")return Buffer.byteLength(r,"utf8");let o=0;for(let i=0,c=r.length;i=55296&&l<=56319&&i+1=56320&&f<=57343?(o+=4,i++):o+=3}else o+=3}return o}const Ue="1.16.1",Ge=64*1024,{isFunction:ae}=a,Qe=(e,...t)=>{try{return!!e(...t)}catch{return!1}},ar=e=>{const t=a.global!==void 0&&a.global!==null?a.global:globalThis,{ReadableStream:n,TextEncoder:r}=t;e=a.merge.call({skipUndefined:!0},{Request:t.Request,Response:t.Response},e);const{fetch:s,Request:o,Response:i}=e,c=s?ae(s):typeof fetch=="function",l=ae(o),f=ae(i);if(!c)return!1;const u=c&&ae(n),y=c&&(typeof r=="function"?(m=>O=>m.encode(O))(new r):async m=>new Uint8Array(await new o(m).arrayBuffer())),E=l&&u&&Qe(()=>{let m=!1;const O=new o(T.origin,{body:new n,method:"POST",get duplex(){return m=!0,"half"}}),g=O.headers.has("Content-Type");return O.body!=null&&O.body.cancel(),m&&!g}),b=f&&u&&Qe(()=>a.isReadableStream(new i("").body)),w={stream:b&&(m=>m.body)};c&&["text","arrayBuffer","blob","formData","stream"].forEach(m=>{!w[m]&&(w[m]=(O,g)=>{let R=O&&O[m];if(R)return R.call(O);throw new p(`Response type '${m}' is not supported`,p.ERR_NOT_SUPPORT,g)})});const h=async m=>{if(m==null)return 0;if(a.isBlob(m))return m.size;if(a.isSpecCompliantForm(m))return(await new o(T.origin,{method:"POST",body:m}).arrayBuffer()).byteLength;if(a.isArrayBufferView(m)||a.isArrayBuffer(m))return m.byteLength;if(a.isURLSearchParams(m)&&(m=m+""),a.isString(m))return(await y(m)).byteLength},d=async(m,O)=>{const g=a.toFiniteNumber(m.getContentLength());return g??h(O)};return async m=>{let{url:O,method:g,data:R,signal:N,cancelToken:I,timeout:X,onDownloadProgress:we,onUploadProgress:ke,responseType:k,headers:H,withCredentials:se="same-origin",fetchOptions:je,maxContentLength:F,maxBodyLength:Ee}=wt(m);const G=a.isNumber(F)&&F>-1,St=a.isNumber(Ee)&&Ee>-1;let qe=s||fetch;k=k?(k+"").toLowerCase():"text";let j=nr([N,I&&I.toAbortSignal()],X),P=null;const M=j&&j.unsubscribe&&(()=>{j.unsubscribe()});let Ie;try{if(G&&typeof O=="string"&&O.startsWith("data:")&&ir(O)>F)throw new p("maxContentLength size of "+F+" exceeded",p.ERR_BAD_RESPONSE,m,P);if(St&&g!=="get"&&g!=="head"){const S=await d(H,R);if(typeof S=="number"&&isFinite(S)&&S>Ee)throw new p("Request body larger than maxBodyLength limit",p.ERR_BAD_REQUEST,m,P)}if(ke&&E&&g!=="get"&&g!=="head"&&(Ie=await d(H,R))!==0){let S=new o(O,{method:"POST",body:R,duplex:"half"}),J;if(a.isFormData(R)&&(J=S.headers.get("content-type"))&&H.setContentType(J),S.body){const[oe,ie]=We(Ie,fe(Ke(ke)));R=Xe(S.body,Ge,oe,ie)}}a.isString(se)||(se=se?"include":"omit");const _=l&&"credentials"in o.prototype;if(a.isFormData(R)){const S=H.getContentType();S&&/^multipart\/form-data/i.test(S)&&!/boundary=/i.test(S)&&H.delete("content-type")}H.set("User-Agent","axios/"+Ue,!1);const q={...je,signal:j,method:g.toUpperCase(),headers:lt(H.normalize()),body:R,duplex:"half",credentials:_?se:void 0};P=l&&new o(O,q);let U=await(l?qe(P,je):qe(O,q));if(G){const S=a.toFiniteNumber(U.headers.get("content-length"));if(S!=null&&S>F)throw new p("maxContentLength size of "+F+" exceeded",p.ERR_BAD_RESPONSE,m,P)}const Re=b&&(k==="stream"||k==="response");if(b&&U.body&&(we||G||Re&&M)){const S={};["status","statusText","headers"].forEach(Q=>{S[Q]=U[Q]});const J=a.toFiniteNumber(U.headers.get("content-length")),[oe,ie]=we&&We(J,fe(Ke(we),!0))||[];let He=0;const At=Q=>{if(G&&(He=Q,He>F))throw new p("maxContentLength size of "+F+" exceeded",p.ERR_BAD_RESPONSE,m,P);oe&&oe(Q)};U=new i(Xe(U.body,Ge,At,()=>{ie&&ie(),M&&M()}),S)}k=k||"text";let B=await w[a.findKey(w,k)||"text"](U,m);if(G&&!b&&!Re){let S;if(B!=null&&(typeof B.byteLength=="number"?S=B.byteLength:typeof B.size=="number"?S=B.size:typeof B=="string"&&(S=typeof r=="function"?new r().encode(B).byteLength:B.length)),typeof S=="number"&&S>F)throw new p("maxContentLength size of "+F+" exceeded",p.ERR_BAD_RESPONSE,m,P)}return!Re&&M&&M(),await new Promise((S,J)=>{yt(S,J,{data:B,headers:x.from(U.headers),status:U.status,statusText:U.statusText,config:m,request:P})})}catch(_){if(M&&M(),j&&j.aborted&&j.reason instanceof p){const q=j.reason;throw q.config=m,P&&(q.request=P),_!==q&&(q.cause=_),q}throw _&&_.name==="TypeError"&&/Load failed|fetch/i.test(_.message)?Object.assign(new p("Network Error",p.ERR_NETWORK,m,P,_&&_.response),{cause:_.cause||_}):p.from(_,_&&_.code,m,P,_&&_.response)}}},cr=new Map,Et=e=>{let t=e&&e.env||{};const{fetch:n,Request:r,Response:s}=t,o=[r,s,n];let i=o.length,c=i,l,f,u=cr;for(;c--;)l=o[c],f=u.get(l),f===void 0&&u.set(l,f=c?new Map:ar(t)),u=f;return f};Et();const Be={http:Cn,xhr:tr,fetch:{get:Et}};a.forEach(Be,(e,t)=>{if(e){try{Object.defineProperty(e,"name",{__proto__:null,value:t})}catch{}Object.defineProperty(e,"adapterName",{__proto__:null,value:t})}});const Ye=e=>`- ${e}`,lr=e=>a.isFunction(e)||e===null||e===!1;function ur(e,t){e=a.isArray(e)?e:[e];const{length:n}=e;let r,s;const o={};for(let i=0;i`adapter ${l} `+(f===!1?"is not supported by the environment":"is not available in the build"));let c=n?i.length>1?`since :
+`+i.map(Ye).join(`
+`):" "+Ye(i[0]):"as no adapter specified";throw new p("There is no suitable adapter to dispatch the request "+c,"ERR_NOT_SUPPORT")}return s}const Rt={getAdapter:ur,adapters:Be};function Ae(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new re(null,e)}function Ze(e){return Ae(e),e.headers=x.from(e.headers),e.data=Se.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),Rt.getAdapter(e.adapter||ne.adapter,e)(e).then(function(r){Ae(e),e.response=r;try{r.data=Se.call(e,e.transformResponse,r)}finally{delete e.response}return r.headers=x.from(r.headers),r},function(r){if(!mt(r)&&(Ae(e),r&&r.response)){e.response=r.response;try{r.response.data=Se.call(e,e.transformResponse,r.response)}finally{delete e.response}r.response.headers=x.from(r.response.headers)}return Promise.reject(r)})}const be={};["object","boolean","number","function","string","symbol"].forEach((e,t)=>{be[e]=function(r){return typeof r===e||"a"+(t<1?"n ":" ")+e}});const et={};be.transitional=function(t,n,r){function s(o,i){return"[Axios v"+Ue+"] Transitional option '"+o+"'"+i+(r?". "+r:"")}return(o,i,c)=>{if(t===!1)throw new p(s(i," has been removed"+(n?" in "+n:"")),p.ERR_DEPRECATED);return n&&!et[i]&&(et[i]=!0,console.warn(s(i," has been deprecated since v"+n+" and will be removed in the near future"))),t?t(o,i,c):!0}};be.spelling=function(t){return(n,r)=>(console.warn(`${r} is likely a misspelling of ${t}`),!0)};function fr(e,t,n){if(typeof e!="object")throw new p("options must be an object",p.ERR_BAD_OPTION_VALUE);const r=Object.keys(e);let s=r.length;for(;s-- >0;){const o=r[s],i=Object.prototype.hasOwnProperty.call(t,o)?t[o]:void 0;if(i){const c=e[o],l=c===void 0||i(c,o,e);if(l!==!0)throw new p("option "+o+" must be "+l,p.ERR_BAD_OPTION_VALUE);continue}if(n!==!0)throw new p("Unknown option "+o,p.ERR_BAD_OPTION)}}const ue={assertOptions:fr,validators:be},D=ue.validators;let $=class{constructor(t){this.defaults=t||{},this.interceptors={request:new Je,response:new Je}}async request(t,n){try{return await this._request(t,n)}catch(r){if(r instanceof Error){let s={};Error.captureStackTrace?Error.captureStackTrace(s):s=new Error;const o=(()=>{if(!s.stack)return"";const i=s.stack.indexOf(`
+`);return i===-1?"":s.stack.slice(i+1)})();try{if(!r.stack)r.stack=o;else if(o){const i=o.indexOf(`
+`),c=i===-1?-1:o.indexOf(`
+`,i+1),l=c===-1?"":o.slice(c+1);String(r.stack).endsWith(l)||(r.stack+=`
+`+o)}}catch{}}throw r}}_request(t,n){typeof t=="string"?(n=n||{},n.url=t):n=t||{},n=V(this.defaults,n);const{transitional:r,paramsSerializer:s,headers:o}=n;r!==void 0&&ue.assertOptions(r,{silentJSONParsing:D.transitional(D.boolean),forcedJSONParsing:D.transitional(D.boolean),clarifyTimeoutError:D.transitional(D.boolean),legacyInterceptorReqResOrdering:D.transitional(D.boolean)},!1),s!=null&&(a.isFunction(s)?n.paramsSerializer={serialize:s}:ue.assertOptions(s,{encode:D.function,serialize:D.function},!0)),n.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?n.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:n.allowAbsoluteUrls=!0),ue.assertOptions(n,{baseUrl:D.spelling("baseURL"),withXsrfToken:D.spelling("withXSRFToken")},!0),n.method=(n.method||this.defaults.method||"get").toLowerCase();let i=o&&a.merge(o.common,o[n.method]);o&&a.forEach(["delete","get","head","post","put","patch","query","common"],w=>{delete o[w]}),n.headers=x.concat(i,o);const c=[];let l=!0;this.interceptors.request.forEach(function(h){if(typeof h.runWhen=="function"&&h.runWhen(n)===!1)return;l=l&&h.synchronous;const d=n.transitional||Le;d&&d.legacyInterceptorReqResOrdering?c.unshift(h.fulfilled,h.rejected):c.push(h.fulfilled,h.rejected)});const f=[];this.interceptors.response.forEach(function(h){f.push(h.fulfilled,h.rejected)});let u,y=0,E;if(!l){const w=[Ze.bind(this),void 0];for(w.unshift(...c),w.push(...f),E=w.length,u=Promise.resolve(n);y{if(!r._listeners)return;let o=r._listeners.length;for(;o-- >0;)r._listeners[o](s);r._listeners=null}),this.promise.then=s=>{let o;const i=new Promise(c=>{r.subscribe(c),o=c}).then(s);return i.cancel=function(){r.unsubscribe(o)},i},t(function(o,i,c){r.reason||(r.reason=new re(o,i,c),n(r.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}unsubscribe(t){if(!this._listeners)return;const n=this._listeners.indexOf(t);n!==-1&&this._listeners.splice(n,1)}toAbortSignal(){const t=new AbortController,n=r=>{t.abort(r)};return this.subscribe(n),t.signal.unsubscribe=()=>this.unsubscribe(n),t.signal}static source(){let t;return{token:new gt(function(s){t=s}),cancel:t}}};function pr(e){return function(n){return e.apply(null,n)}}function hr(e){return a.isObject(e)&&e.isAxiosError===!0}const Ne={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(Ne).forEach(([e,t])=>{Ne[t]=e});function Ot(e){const t=new $(e),n=tt($.prototype.request,t);return a.extend(n,$.prototype,t,{allOwnKeys:!0}),a.extend(n,t,null,{allOwnKeys:!0}),n.create=function(s){return Ot(V(e,s))},n}const A=Ot(ne);A.Axios=$;A.CanceledError=re;A.CancelToken=dr;A.isCancel=mt;A.VERSION=Ue;A.toFormData=ye;A.AxiosError=p;A.Cancel=A.CanceledError;A.all=function(t){return Promise.all(t)};A.spread=pr;A.isAxiosError=hr;A.mergeConfig=V;A.AxiosHeaders=x;A.formToJSON=e=>ht(a.isHTMLForm(e)?new FormData(e):e);A.getAdapter=Rt.getAdapter;A.HttpStatusCode=Ne;A.default=A;const{Axios:wr,AxiosError:Er,CanceledError:Rr,isCancel:gr,CancelToken:Or,VERSION:Sr,all:Ar,Cancel:_r,isAxiosError:Tr,spread:xr,toFormData:Cr,AxiosHeaders:Nr,HttpStatusCode:Pr,formToJSON:Dr,getAdapter:Lr,mergeConfig:Fr,create:Ur}=A;export{A as a};
diff --git a/backend/src/main/resources/static/assets/index-Dk81znn6.css b/backend/src/main/resources/static/assets/index-Dk81znn6.css
new file mode 100644
index 00000000..698c3d54
--- /dev/null
+++ b/backend/src/main/resources/static/assets/index-Dk81znn6.css
@@ -0,0 +1 @@
+.skip-link{position:absolute;top:-60px;left:0;z-index:9999;background:var(--primary);color:#fff;padding:10px 20px;border-radius:0 0 8px;transition:top .2s}.skip-link:focus{top:0}.header{position:fixed;top:0;left:0;right:0;z-index:1000;height:var(--header-h);background:#1a1a2ef5;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border-bottom:1px solid rgba(255,255,255,.08);transition:all var(--mid) var(--ease)}.header.scrolled{background:#1a1a2efc;box-shadow:0 4px 24px #0000004d}.header-inner{display:flex;align-items:center;gap:32px;height:100%}.logo{display:flex;align-items:center;gap:10px;flex-shrink:0}.logo img{height:40px;width:auto;filter:brightness(0) invert(1)}.logo-text{color:#fff;font-size:20px;font-weight:700}.logo-text strong{color:var(--accent)}.nav-desktop{display:flex;align-items:center;gap:4px;margin-left:24px;flex:1}.nav-item{position:relative}.nav-trigger{height:var(--header-h);padding:0 16px;color:#ffffffd9;font-size:15px;font-weight:500;transition:color var(--fast);display:flex;align-items:center}.nav-trigger:hover,.nav-item.active .nav-trigger{color:#fff}.nav-item.active .nav-trigger{border-bottom:2px solid var(--accent)}.dropdown{position:absolute;top:calc(var(--header-h) - 2px);left:0;min-width:180px;background:#fff;border-radius:0 0 var(--radius) var(--radius);box-shadow:var(--shadow-lg);border-top:3px solid var(--primary);padding:8px 0;animation:fadeDown .18s ease}@keyframes fadeDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}.dropdown-item{display:flex;align-items:center;gap:8px;padding:10px 20px;font-size:14px;color:var(--gray-700);transition:all var(--fast)}.dropdown-item:hover,.dropdown-item.current{background:var(--primary-light);color:var(--primary)}.header-cta{margin-left:auto;flex-shrink:0}.hamburger{display:none;flex-direction:column;gap:5px;padding:8px;margin-left:auto}.hamburger span{display:block;width:24px;height:2px;background:#fff;border-radius:2px;transition:all var(--mid)}.nav-mobile{display:none;flex-direction:column;background:var(--secondary);border-top:1px solid rgba(255,255,255,.1);max-height:calc(100vh - var(--header-h));overflow-y:auto}.mobile-group{border-bottom:1px solid rgba(255,255,255,.08)}.mobile-group-header{display:flex;align-items:center;padding:14px 24px;color:#ffffffd9;font-size:15px;font-weight:500;cursor:pointer}.mobile-children{background:#0003}.mobile-child{display:flex;align-items:center;gap:8px;padding:10px 36px;font-size:14px;color:#ffffffb3}.mobile-child:hover{color:#fff}@media (max-width: 1024px){.nav-desktop,.header-cta{display:none}.hamburger,.header.mobile-open .nav-mobile{display:flex}.header.mobile-open{height:auto}}.footer{background:var(--secondary);color:#fffc}.footer-top{padding:60px 0}.footer-top-inner{display:grid;grid-template-columns:280px repeat(4,1fr);gap:40px}.footer-logo{display:flex;align-items:center;gap:10px;margin-bottom:16px}.footer-logo img{height:36px}.footer-logo-text{font-size:20px;font-weight:700;color:#fff}.footer-logo-text strong{color:var(--accent)}.footer-tagline{font-size:13px;line-height:1.8;color:#fff9;margin-bottom:20px}.footer-contact-list{display:flex;flex-direction:column;gap:8px}.footer-contact-item{display:flex;gap:10px;font-size:13px}.contact-label{color:#fff6;min-width:60px}.footer-contact-item a{color:var(--accent)}.footer-contact-item a:hover{text-decoration:underline}.footer-menu-title{font-size:13px;font-weight:700;color:#fff;letter-spacing:.5px;text-transform:uppercase;margin-bottom:16px;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,.1)}.footer-menu-list{display:flex;flex-direction:column;gap:10px}.footer-menu-list a{font-size:13px;color:#fff9;transition:color var(--fast)}.footer-menu-list a:hover{color:var(--accent)}.footer-bottom{border-top:1px solid rgba(255,255,255,.08);padding:18px 0}.footer-bottom-inner{display:flex;align-items:center;gap:24px;font-size:12px;color:#fff6}.footer-legal{display:flex;gap:16px}.footer-legal a{color:#fff6}.footer-legal a:hover{color:#fffc}.footer-copyright{flex:1;text-align:center}.footer-powered{color:#ffffff4d}.footer-powered strong{color:var(--accent)}@media (max-width: 1024px){.footer-top-inner{grid-template-columns:1fr 1fr}.footer-brand{grid-column:1 / -1}}@media (max-width: 768px){.footer-top-inner{grid-template-columns:1fr 1fr}.footer-bottom-inner{flex-direction:column;text-align:center;gap:12px}.footer-copyright{order:-1}}:root{--primary: #0051A2;--primary-dark: #003A7A;--primary-light: #E8F0FA;--accent: #00A3E0;--accent-dark: #0080B0;--secondary: #1A1A2E;--gray-900: #111827;--gray-800: #1F2937;--gray-700: #374151;--gray-600: #4B5563;--gray-400: #9CA3AF;--gray-200: #E5E7EB;--gray-100: #F3F4F6;--gray-50: #F9FAFB;--white: #FFFFFF;--success: #10B981;--warning: #F59E0B;--danger: #EF4444;--font-sans: "Noto Sans KR", "Inter", -apple-system, sans-serif;--font-en: "Inter", sans-serif;--container: 1280px;--header-h: 72px;--radius-sm: 6px;--radius: 12px;--radius-lg: 20px;--ease: cubic-bezier(.4,0,.2,1);--fast: .15s;--mid: .3s;--slow: .5s;--shadow-sm: 0 1px 3px rgba(0,0,0,.1);--shadow: 0 4px 16px rgba(0,0,0,.12);--shadow-lg: 0 12px 40px rgba(0,0,0,.16)}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}html{scroll-behavior:smooth;font-size:16px}body{font-family:var(--font-sans);color:var(--gray-800);background:var(--white);line-height:1.6;-webkit-font-smoothing:antialiased}img{max-width:100%;height:auto;display:block}a{color:inherit;text-decoration:none}ul,ol{list-style:none}button{cursor:pointer;border:none;background:none;font-family:inherit}.container{max-width:var(--container);margin:0 auto;padding:0 24px}.section{padding:80px 0}.section-sm{padding:48px 0}.section-lg{padding:120px 0}.section-header{text-align:center;margin-bottom:56px}.section-label{display:inline-block;font-size:13px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(--accent);margin-bottom:12px}.section-title{font-size:clamp(28px,4vw,44px);font-weight:900;color:var(--gray-900);line-height:1.2}.section-title em{color:var(--primary);font-style:normal}.section-desc{margin-top:16px;font-size:17px;color:var(--gray-600);max-width:600px;margin-left:auto;margin-right:auto}.btn{display:inline-flex;align-items:center;gap:8px;padding:12px 28px;border-radius:var(--radius);font-size:15px;font-weight:600;transition:all var(--mid) var(--ease);line-height:1}.btn-primary{background:var(--primary);color:var(--white)}.btn-primary:hover{background:var(--primary-dark);transform:translateY(-2px);box-shadow:0 8px 24px #0051a24d}.btn-outline{border:2px solid var(--primary);color:var(--primary)}.btn-outline:hover{background:var(--primary);color:var(--white)}.btn-white{background:var(--white);color:var(--primary);font-weight:700}.btn-white:hover{background:var(--gray-100);transform:translateY(-2px)}.btn-lg{padding:16px 36px;font-size:16px}.btn-sm{padding:8px 20px;font-size:13px}.card{background:var(--white);border-radius:var(--radius);box-shadow:var(--shadow-sm);border:1px solid var(--gray-200);transition:all var(--mid) var(--ease);overflow:hidden}.card:hover{box-shadow:var(--shadow-lg);transform:translateY(-4px);border-color:var(--primary-light)}.grid-2{display:grid;grid-template-columns:repeat(2,1fr);gap:24px}.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:24px}.grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:24px}.badge{display:inline-block;padding:3px 10px;border-radius:20px;font-size:12px;font-weight:600}.badge-primary{background:var(--primary-light);color:var(--primary)}.badge-accent{background:#00a3e01f;color:var(--accent-dark)}.badge-new{background:var(--danger);color:var(--white)}.divider{width:48px;height:4px;background:var(--accent);border-radius:2px;margin:16px auto 0}.divider-left{margin-left:0}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:var(--gray-100)}::-webkit-scrollbar-thumb{background:var(--gray-400);border-radius:3px}@keyframes fadeUp{0%{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}.fade-up{animation:fadeUp var(--slow) var(--ease) both}@media (max-width: 1024px){.grid-4{grid-template-columns:repeat(2,1fr)}}@media (max-width: 768px){.section{padding:60px 0}.grid-2,.grid-3,.grid-4{grid-template-columns:1fr}.container{padding:0 16px}}
diff --git a/backend/src/main/resources/static/favicon.ico b/backend/src/main/resources/static/favicon.ico
new file mode 100644
index 00000000..3252b9f8
Binary files /dev/null and b/backend/src/main/resources/static/favicon.ico differ
diff --git a/backend/src/main/resources/static/index.html b/backend/src/main/resources/static/index.html
new file mode 100644
index 00000000..74161120
--- /dev/null
+++ b/backend/src/main/resources/static/index.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+ (주)지오정보기술
+
+
+
+
+
+
+
+
+
+
+
diff --git a/backend/src/main/resources/static/logo-white.png b/backend/src/main/resources/static/logo-white.png
new file mode 100644
index 00000000..0ae65e05
Binary files /dev/null and b/backend/src/main/resources/static/logo-white.png differ
diff --git a/backend/src/main/resources/static/logo.png b/backend/src/main/resources/static/logo.png
new file mode 100644
index 00000000..4f88262a
Binary files /dev/null and b/backend/src/main/resources/static/logo.png differ
diff --git a/backend/src/main/resources/static/screenshots/01_dashboard.png b/backend/src/main/resources/static/screenshots/01_dashboard.png
new file mode 100644
index 00000000..6728dfd8
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/01_dashboard.png differ
diff --git a/backend/src/main/resources/static/screenshots/01_home.png b/backend/src/main/resources/static/screenshots/01_home.png
new file mode 100644
index 00000000..9dd704fe
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/01_home.png differ
diff --git a/backend/src/main/resources/static/screenshots/01_home_viewport.png b/backend/src/main/resources/static/screenshots/01_home_viewport.png
new file mode 100644
index 00000000..64e7d888
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/01_home_viewport.png differ
diff --git a/backend/src/main/resources/static/screenshots/02_guardia.png b/backend/src/main/resources/static/screenshots/02_guardia.png
new file mode 100644
index 00000000..df6d4c74
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/02_guardia.png differ
diff --git a/backend/src/main/resources/static/screenshots/02_guardia_viewport.png b/backend/src/main/resources/static/screenshots/02_guardia_viewport.png
new file mode 100644
index 00000000..19b5addd
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/02_guardia_viewport.png differ
diff --git a/backend/src/main/resources/static/screenshots/02_sr_list.png b/backend/src/main/resources/static/screenshots/02_sr_list.png
new file mode 100644
index 00000000..6728dfd8
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/02_sr_list.png differ
diff --git a/backend/src/main/resources/static/screenshots/03_company.png b/backend/src/main/resources/static/screenshots/03_company.png
new file mode 100644
index 00000000..88e925d3
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/03_company.png differ
diff --git a/backend/src/main/resources/static/screenshots/03_company_viewport.png b/backend/src/main/resources/static/screenshots/03_company_viewport.png
new file mode 100644
index 00000000..e64b8177
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/03_company_viewport.png differ
diff --git a/backend/src/main/resources/static/screenshots/03_si_project.png b/backend/src/main/resources/static/screenshots/03_si_project.png
new file mode 100644
index 00000000..6481c7dc
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/03_si_project.png differ
diff --git a/backend/src/main/resources/static/screenshots/04_contact.png b/backend/src/main/resources/static/screenshots/04_contact.png
new file mode 100644
index 00000000..6bfc0ebf
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/04_contact.png differ
diff --git a/backend/src/main/resources/static/screenshots/04_contact_viewport.png b/backend/src/main/resources/static/screenshots/04_contact_viewport.png
new file mode 100644
index 00000000..f456b4e8
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/04_contact_viewport.png differ
diff --git a/backend/src/main/resources/static/screenshots/04_incidents.png b/backend/src/main/resources/static/screenshots/04_incidents.png
new file mode 100644
index 00000000..a50691cb
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/04_incidents.png differ
diff --git a/backend/src/main/resources/static/screenshots/05_agents.png b/backend/src/main/resources/static/screenshots/05_agents.png
new file mode 100644
index 00000000..847ccb8f
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/05_agents.png differ
diff --git a/backend/src/main/resources/static/screenshots/05_news.png b/backend/src/main/resources/static/screenshots/05_news.png
new file mode 100644
index 00000000..48dc2141
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/05_news.png differ
diff --git a/backend/src/main/resources/static/screenshots/05_news_viewport.png b/backend/src/main/resources/static/screenshots/05_news_viewport.png
new file mode 100644
index 00000000..f212eb23
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/05_news_viewport.png differ
diff --git a/backend/src/main/resources/static/screenshots/06_license.png b/backend/src/main/resources/static/screenshots/06_license.png
new file mode 100644
index 00000000..52f91192
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/06_license.png differ
diff --git a/backend/src/main/resources/static/screenshots/06_mobile_home.png b/backend/src/main/resources/static/screenshots/06_mobile_home.png
new file mode 100644
index 00000000..c3f3355d
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/06_mobile_home.png differ
diff --git a/backend/src/main/resources/static/screenshots/all-pages-result.json b/backend/src/main/resources/static/screenshots/all-pages-result.json
new file mode 100644
index 00000000..49a94b33
--- /dev/null
+++ b/backend/src/main/resources/static/screenshots/all-pages-result.json
@@ -0,0 +1,212 @@
+[
+ {
+ "page": "홈",
+ "url": "/",
+ "http": 200,
+ "loadMs": 5849,
+ "title": "(주)지오정보기술",
+ "h1": "AI 기반 인프라자율 운영 플랫폼",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "GUARDiA ITSM",
+ "url": "/solution/guardia",
+ "http": 200,
+ "loadMs": 1292,
+ "title": "(주)지오정보기술",
+ "h1": "GUARDiA ITSM",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "솔루션-ERP",
+ "url": "/solution/erp",
+ "http": 200,
+ "loadMs": 1767,
+ "title": "(주)지오정보기술",
+ "h1": "ERP 솔루션",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "솔루션-CRM",
+ "url": "/solution/crm",
+ "http": 200,
+ "loadMs": 1139,
+ "title": "(주)지오정보기술",
+ "h1": "CRM 솔루션",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "솔루션-BI",
+ "url": "/solution/bi",
+ "http": 200,
+ "loadMs": 966,
+ "title": "(주)지오정보기술",
+ "h1": "BI 솔루션",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "회사-CEO인사말",
+ "url": "/company/greeting",
+ "http": 200,
+ "loadMs": 1098,
+ "title": "(주)지오정보기술",
+ "h1": "CEO 인사말",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "회사-연혁",
+ "url": "/company/history",
+ "http": 200,
+ "loadMs": 1548,
+ "title": "(주)지오정보기술",
+ "h1": "연혁",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "회사-조직도",
+ "url": "/company/organization",
+ "http": 200,
+ "loadMs": 892,
+ "title": "(주)지오정보기술",
+ "h1": "조직도",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "회사-CI소개",
+ "url": "/company/ci",
+ "http": 200,
+ "loadMs": 1007,
+ "title": "(주)지오정보기술",
+ "h1": "CI 소개",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "회사-오시는길",
+ "url": "/company/location",
+ "http": 200,
+ "loadMs": 1070,
+ "title": "(주)지오정보기술",
+ "h1": "오시는 길",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "사업-레퍼런스",
+ "url": "/business/reference",
+ "http": 200,
+ "loadMs": 1111,
+ "title": "(주)지오정보기술",
+ "h1": "구축 레퍼런스",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "사업-파트너",
+ "url": "/business/partner",
+ "http": 200,
+ "loadMs": 1090,
+ "title": "(주)지오정보기술",
+ "h1": "파트너",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "지원-공지사항",
+ "url": "/support/notice",
+ "http": 200,
+ "loadMs": 949,
+ "title": "(주)지오정보기술",
+ "h1": "공지사항",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "지원-FAQ",
+ "url": "/support/faq",
+ "http": 200,
+ "loadMs": 931,
+ "title": "(주)지오정보기술",
+ "h1": "자주 묻는 질문",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "지원-카탈로그",
+ "url": "/support/catalog",
+ "http": 200,
+ "loadMs": 963,
+ "title": "(주)지오정보기술",
+ "h1": "카탈로그",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "지원-문의하기",
+ "url": "/support/contact",
+ "http": 200,
+ "loadMs": 1007,
+ "title": "(주)지오정보기술",
+ "h1": "문의하기",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "채용-공고",
+ "url": "/recruit/jobs",
+ "http": 200,
+ "loadMs": 984,
+ "title": "(주)지오정보기술",
+ "h1": "채용공고",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "채용-복리후생",
+ "url": "/recruit/welfare",
+ "http": 200,
+ "loadMs": 1275,
+ "title": "(주)지오정보기술",
+ "h1": "복리후생",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "채용-지원하기",
+ "url": "/recruit/apply",
+ "http": 200,
+ "loadMs": 880,
+ "title": "(주)지오정보기술",
+ "h1": "지원하기",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "뉴스-뉴스룸",
+ "url": "/news/newsroom",
+ "http": 200,
+ "loadMs": 1144,
+ "title": "(주)지오정보기술",
+ "h1": "뉴스룸",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "뉴스-블로그",
+ "url": "/news/blog",
+ "http": 200,
+ "loadMs": 989,
+ "title": "(주)지오정보기술",
+ "h1": "기술 블로그",
+ "errors": 0,
+ "ok": true
+ }
+]
\ No newline at end of file
diff --git a/backend/src/main/resources/static/screenshots/business_partner.png b/backend/src/main/resources/static/screenshots/business_partner.png
new file mode 100644
index 00000000..3dce8a21
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/business_partner.png differ
diff --git a/backend/src/main/resources/static/screenshots/business_ref.png b/backend/src/main/resources/static/screenshots/business_ref.png
new file mode 100644
index 00000000..8b93a0f9
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/business_ref.png differ
diff --git a/backend/src/main/resources/static/screenshots/company_ci.png b/backend/src/main/resources/static/screenshots/company_ci.png
new file mode 100644
index 00000000..2eb6f3ca
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/company_ci.png differ
diff --git a/backend/src/main/resources/static/screenshots/company_greeting.png b/backend/src/main/resources/static/screenshots/company_greeting.png
new file mode 100644
index 00000000..9f932701
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/company_greeting.png differ
diff --git a/backend/src/main/resources/static/screenshots/company_history.png b/backend/src/main/resources/static/screenshots/company_history.png
new file mode 100644
index 00000000..bc970cb1
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/company_history.png differ
diff --git a/backend/src/main/resources/static/screenshots/company_location.png b/backend/src/main/resources/static/screenshots/company_location.png
new file mode 100644
index 00000000..5132a049
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/company_location.png differ
diff --git a/backend/src/main/resources/static/screenshots/company_org.png b/backend/src/main/resources/static/screenshots/company_org.png
new file mode 100644
index 00000000..c38e7b0e
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/company_org.png differ
diff --git a/backend/src/main/resources/static/screenshots/guardia.png b/backend/src/main/resources/static/screenshots/guardia.png
new file mode 100644
index 00000000..19b5addd
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/guardia.png differ
diff --git a/backend/src/main/resources/static/screenshots/home.png b/backend/src/main/resources/static/screenshots/home.png
new file mode 100644
index 00000000..5b4df9de
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/home.png differ
diff --git a/backend/src/main/resources/static/screenshots/news_blog.png b/backend/src/main/resources/static/screenshots/news_blog.png
new file mode 100644
index 00000000..94697ec8
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/news_blog.png differ
diff --git a/backend/src/main/resources/static/screenshots/news_newsroom.png b/backend/src/main/resources/static/screenshots/news_newsroom.png
new file mode 100644
index 00000000..d799d773
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/news_newsroom.png differ
diff --git a/backend/src/main/resources/static/screenshots/recruit_apply.png b/backend/src/main/resources/static/screenshots/recruit_apply.png
new file mode 100644
index 00000000..8e79f187
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/recruit_apply.png differ
diff --git a/backend/src/main/resources/static/screenshots/recruit_jobs.png b/backend/src/main/resources/static/screenshots/recruit_jobs.png
new file mode 100644
index 00000000..951e94f3
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/recruit_jobs.png differ
diff --git a/backend/src/main/resources/static/screenshots/recruit_welfare.png b/backend/src/main/resources/static/screenshots/recruit_welfare.png
new file mode 100644
index 00000000..53752fb2
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/recruit_welfare.png differ
diff --git a/backend/src/main/resources/static/screenshots/solution_bi.png b/backend/src/main/resources/static/screenshots/solution_bi.png
new file mode 100644
index 00000000..6999f9f1
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/solution_bi.png differ
diff --git a/backend/src/main/resources/static/screenshots/solution_crm.png b/backend/src/main/resources/static/screenshots/solution_crm.png
new file mode 100644
index 00000000..a45d34d9
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/solution_crm.png differ
diff --git a/backend/src/main/resources/static/screenshots/solution_erp.png b/backend/src/main/resources/static/screenshots/solution_erp.png
new file mode 100644
index 00000000..c6471957
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/solution_erp.png differ
diff --git a/backend/src/main/resources/static/screenshots/support_catalog.png b/backend/src/main/resources/static/screenshots/support_catalog.png
new file mode 100644
index 00000000..e9ee2998
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/support_catalog.png differ
diff --git a/backend/src/main/resources/static/screenshots/support_contact.png b/backend/src/main/resources/static/screenshots/support_contact.png
new file mode 100644
index 00000000..f456b4e8
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/support_contact.png differ
diff --git a/backend/src/main/resources/static/screenshots/support_faq.png b/backend/src/main/resources/static/screenshots/support_faq.png
new file mode 100644
index 00000000..62006a57
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/support_faq.png differ
diff --git a/backend/src/main/resources/static/screenshots/support_notice.png b/backend/src/main/resources/static/screenshots/support_notice.png
new file mode 100644
index 00000000..eba6bd03
Binary files /dev/null and b/backend/src/main/resources/static/screenshots/support_notice.png differ
diff --git a/backend/src/main/resources/static/screenshots/test-result.json b/backend/src/main/resources/static/screenshots/test-result.json
new file mode 100644
index 00000000..04877fa2
--- /dev/null
+++ b/backend/src/main/resources/static/screenshots/test-result.json
@@ -0,0 +1,67 @@
+[
+ {
+ "page": "홈",
+ "url": "/",
+ "status": 200,
+ "loadMs": 9745,
+ "title": "(주)지오정보기술",
+ "links": 37,
+ "images": 2,
+ "h1": 1,
+ "errors": 0,
+ "errorMsgs": [],
+ "screenshot": "C:\\GUARDiA\\workspace\\zioinfo-web\\frontend\\public\\screenshots\\01_home.png"
+ },
+ {
+ "page": "GUARDiA 소개",
+ "url": "/solution/guardia",
+ "status": 200,
+ "loadMs": 1130,
+ "title": "(주)지오정보기술",
+ "links": 27,
+ "images": 8,
+ "h1": 1,
+ "errors": 0,
+ "errorMsgs": [],
+ "screenshot": "C:\\GUARDiA\\workspace\\zioinfo-web\\frontend\\public\\screenshots\\02_guardia.png"
+ },
+ {
+ "page": "회사소개",
+ "url": "/company/greeting",
+ "status": 200,
+ "loadMs": 971,
+ "title": "(주)지오정보기술",
+ "links": 23,
+ "images": 2,
+ "h1": 1,
+ "errors": 0,
+ "errorMsgs": [],
+ "screenshot": "C:\\GUARDiA\\workspace\\zioinfo-web\\frontend\\public\\screenshots\\03_company.png"
+ },
+ {
+ "page": "문의하기",
+ "url": "/support/contact",
+ "status": 200,
+ "loadMs": 890,
+ "title": "(주)지오정보기술",
+ "links": 24,
+ "images": 2,
+ "h1": 1,
+ "errors": 0,
+ "errorMsgs": [],
+ "screenshot": "C:\\GUARDiA\\workspace\\zioinfo-web\\frontend\\public\\screenshots\\04_contact.png"
+ },
+ {
+ "page": "뉴스",
+ "url": "/news/press",
+ "status": 200,
+ "loadMs": 1007,
+ "title": "(주)지오정보기술",
+ "links": 23,
+ "images": 2,
+ "h1": 1,
+ "errors": 0,
+ "errorMsgs": [],
+ "screenshot": "C:\\GUARDiA\\workspace\\zioinfo-web\\frontend\\public\\screenshots\\05_news.png"
+ }
+]
\ No newline at end of file
diff --git a/deploy/01_oracle_cloud_guide.md b/deploy/01_oracle_cloud_guide.md
new file mode 100644
index 00000000..825a4a84
--- /dev/null
+++ b/deploy/01_oracle_cloud_guide.md
@@ -0,0 +1,69 @@
+# Oracle Cloud Always Free — zio-server 구축 가이드
+
+## 1단계: Oracle Cloud 계정 생성
+
+1. https://www.oracle.com/cloud/free/ 접속
+2. "Start for free" 클릭
+3. 정보 입력:
+ - Country: South Korea
+ - 이름, 이메일, 비밀번호
+4. **신용카드 등록 필수** (과금 없음 — 인증용)
+5. 가입 완료 후 홈 리전 선택: **South Korea Central (Seoul)**
+
+> ⚠️ 홈 리전은 변경 불가 — 반드시 Seoul 선택
+
+---
+
+## 2단계: VM 인스턴스 생성 (zio-server)
+
+### 콘솔 접속
+Oracle Cloud Console → Compute → Instances → Create Instance
+
+### 설정값
+
+| 항목 | 값 |
+|------|----|
+| **Name** | `zio-server` |
+| **Image** | Ubuntu 22.04 (Canonical) |
+| **Shape** | VM.Standard.A1.Flex (Ampere) |
+| **OCPU** | 4 |
+| **Memory** | 24 GB |
+| **Boot Volume** | 100 GB |
+| **Network** | Default VCN, Public Subnet |
+| **공인 IP** | Assign public IP: Yes |
+
+### SSH 키 생성
+```
+로컬에서:
+ssh-keygen -t rsa -b 4096 -f C:\Users\{username}\.ssh\zio-server
+```
+- 생성된 `zio-server.pub` 내용을 콘솔에 붙여넣기
+
+### 생성 완료
+- 약 2~3분 후 Running 상태 확인
+- 공인 IP 메모 (예: 140.238.xxx.xxx)
+
+---
+
+## 3단계: 방화벽 오픈 (Security List)
+
+Networking → Virtual Cloud Networks → Default VCN
+→ Security Lists → Default Security List
+→ Add Ingress Rules:
+
+| 포트 | 프로토콜 | 용도 |
+|------|---------|------|
+| 22 | TCP | SSH |
+| 80 | TCP | HTTP |
+| 443 | TCP | HTTPS |
+| 8080 | TCP | Spring Boot (개발용) |
+
+---
+
+## 4단계: SSH 접속
+
+```powershell
+ssh -i C:\Users\{username}\.ssh\zio-server ubuntu@{공인IP}
+```
+
+접속 성공 후 → 5단계 서버 설정 스크립트 실행
diff --git a/deploy/02_server_setup.sh b/deploy/02_server_setup.sh
new file mode 100644
index 00000000..876b62ad
--- /dev/null
+++ b/deploy/02_server_setup.sh
@@ -0,0 +1,110 @@
+#!/bin/bash
+# ============================================================
+# zio-server 초기 환경 구성 스크립트
+# Oracle Cloud Ubuntu 22.04 ARM (Ampere A1)
+# 실행: bash 02_server_setup.sh
+# ============================================================
+
+set -e
+GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
+info() { echo -e "${GREEN}[OK]${NC} $1"; }
+section() { echo -e "\n${CYAN}=== $1 ===${NC}"; }
+
+section "1. 시스템 업데이트"
+sudo apt-get update -y && sudo apt-get upgrade -y
+sudo apt-get install -y curl wget git unzip net-tools ufw htop
+info "시스템 업데이트 완료"
+
+section "2. Java 21 설치 (Spring Boot용)"
+sudo apt-get install -y openjdk-21-jdk
+java -version
+info "Java 21 설치 완료"
+
+section "3. Node.js 20 LTS 설치 (React 빌드용)"
+curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
+sudo apt-get install -y nodejs
+node -v && npm -v
+info "Node.js $(node -v) 설치 완료"
+
+section "4. Nginx 설치"
+sudo apt-get install -y nginx
+sudo systemctl enable nginx
+sudo systemctl start nginx
+info "Nginx 설치 완료"
+
+section "5. UFW 방화벽 설정"
+sudo ufw allow ssh
+sudo ufw allow 80/tcp
+sudo ufw allow 443/tcp
+sudo ufw allow 8080/tcp
+sudo ufw --force enable
+sudo ufw status
+info "방화벽 설정 완료"
+
+# Oracle Cloud 내부 iptables도 열기 (필수!)
+section "6. Oracle Cloud iptables 규칙 추가"
+sudo iptables -I INPUT 6 -m state --state NEW -p tcp --dport 80 -j ACCEPT
+sudo iptables -I INPUT 6 -m state --state NEW -p tcp --dport 443 -j ACCEPT
+sudo iptables -I INPUT 6 -m state --state NEW -p tcp --dport 8080 -j ACCEPT
+sudo netfilter-persistent save 2>/dev/null || {
+ sudo apt-get install -y iptables-persistent
+ sudo netfilter-persistent save
+}
+info "iptables 규칙 저장 완료"
+
+section "7. 앱 디렉터리 생성"
+sudo mkdir -p /var/www/zioinfo
+sudo mkdir -p /opt/zioinfo/app
+sudo chown -R ubuntu:ubuntu /var/www/zioinfo /opt/zioinfo
+info "디렉터리 생성 완료"
+
+section "8. Nginx 설정"
+sudo tee /etc/nginx/sites-available/zioinfo > /dev/null <<'NGINX'
+server {
+ listen 80;
+ server_name _;
+
+ root /var/www/zioinfo;
+ index index.html;
+
+ # React SPA — 모든 경로를 index.html로
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+
+ # Spring Boot API 프록시
+ location /api/ {
+ proxy_pass http://localhost:8080;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_read_timeout 60s;
+ }
+
+ # 정적 파일 캐시
+ location ~* \.(js|css|png|jpg|gif|ico|svg|woff2)$ {
+ expires 30d;
+ add_header Cache-Control "public, immutable";
+ }
+
+ # 보안 헤더
+ add_header X-Frame-Options "SAMEORIGIN";
+ add_header X-Content-Type-Options "nosniff";
+ add_header X-XSS-Protection "1; mode=block";
+
+ # Gzip 압축
+ gzip on;
+ gzip_types text/plain text/css application/javascript application/json image/svg+xml;
+ gzip_min_length 1024;
+}
+NGINX
+
+sudo ln -sf /etc/nginx/sites-available/zioinfo /etc/nginx/sites-enabled/
+sudo rm -f /etc/nginx/sites-enabled/default
+sudo nginx -t && sudo systemctl reload nginx
+info "Nginx 설정 완료"
+
+section "✅ 서버 초기 구성 완료!"
+echo ""
+echo -e "${YELLOW}다음 단계: 로컬에서 03_deploy.sh 실행${NC}"
+echo -e "서버 IP: $(curl -s ifconfig.me)"
diff --git a/deploy/03_deploy.ps1 b/deploy/03_deploy.ps1
new file mode 100644
index 00000000..a5f63745
--- /dev/null
+++ b/deploy/03_deploy.ps1
@@ -0,0 +1,110 @@
+# ============================================================
+# zio-server 홈페이지 배포 스크립트 (Windows PowerShell)
+# 실행: .\deploy\03_deploy.ps1 -ServerIP "140.238.xxx.xxx"
+# ============================================================
+
+param(
+ [Parameter(Mandatory=$true)]
+ [string]$ServerIP,
+ [string]$KeyPath = "$env:USERPROFILE\.ssh\zio-server",
+ [string]$User = "ubuntu"
+)
+
+$GREEN = "`e[32m"
+$YELLOW = "`e[33m"
+$CYAN = "`e[36m"
+$NC = "`e[0m"
+
+function Log-Info { param($msg) Write-Host "${GREEN}[OK]${NC} $msg" }
+function Log-Section { param($msg) Write-Host "`n${CYAN}=== $msg ===${NC}" }
+function Log-Warn { param($msg) Write-Host "${YELLOW}[!]${NC} $msg" }
+
+$SSH = "ssh -i `"$KeyPath`" -o StrictHostKeyChecking=no ${User}@${ServerIP}"
+$SCP = "scp -i `"$KeyPath`" -o StrictHostKeyChecking=no"
+$ROOT = "C:\GUARDiA\workspace\zioinfo-web"
+
+Log-Section "1. React 프론트엔드 빌드"
+Set-Location "$ROOT\frontend"
+
+# vite.config.js 빌드 경로를 임시 dist로 변경
+$viteCfg = Get-Content "vite.config.js" -Raw
+$buildCfg = $viteCfg -replace "outDir: '.*?'", "outDir: 'dist'"
+$buildCfg | Set-Content "vite.config.js" -Encoding utf8
+
+npm run build
+if ($LASTEXITCODE -ne 0) { Write-Error "빌드 실패"; exit 1 }
+Log-Info "React 빌드 완료 → frontend/dist/"
+
+Log-Section "2. 빌드 파일 서버 업로드"
+Invoke-Expression "$SSH 'rm -rf /var/www/zioinfo/* && mkdir -p /var/www/zioinfo'"
+Invoke-Expression "$SCP -r `"$ROOT\frontend\dist\*`" ${User}@${ServerIP}:/var/www/zioinfo/"
+Log-Info "정적 파일 업로드 완료"
+
+Log-Section "3. Spring Boot JAR 빌드"
+Set-Location "$ROOT"
+if (Test-Path "pom.xml") {
+ # Maven 빌드 (Spring Boot 백엔드)
+ $mvnw = if (Test-Path "mvnw.cmd") { ".\mvnw.cmd" } else { "mvn" }
+ & $mvnw clean package -DskipTests -q
+ $jar = Get-ChildItem "target\*.jar" -Exclude "*sources*" | Select-Object -First 1
+ if ($jar) {
+ Log-Info "JAR 빌드 완료: $($jar.Name)"
+ Invoke-Expression "$SCP `"$($jar.FullName)`" ${User}@${ServerIP}:/opt/zioinfo/app/zioinfo.jar"
+ Log-Info "JAR 업로드 완료"
+ }
+} else {
+ Log-Warn "pom.xml 없음 — Spring Boot 배포 스킵 (정적 파일만 배포)"
+}
+
+Log-Section "4. systemd 서비스 등록 (Spring Boot)"
+$serviceScript = @'
+# Spring Boot 서비스 설정
+sudo tee /etc/systemd/system/zioinfo.service > /dev/null </dev/null || echo "서비스 시작 대기 중..."
+'@
+
+if (Test-Path "$ROOT\target\*.jar") {
+ Invoke-Expression "$SSH '$serviceScript'"
+ Log-Info "Spring Boot 서비스 등록 완료"
+}
+
+Log-Section "5. Nginx 재시작 및 최종 확인"
+$checkScript = @"
+sudo systemctl reload nginx
+echo '--- Nginx 상태 ---'
+sudo systemctl is-active nginx
+echo '--- 포트 확인 ---'
+ss -tlnp | grep -E ':80|:443|:8080'
+echo '--- 디스크 사용량 ---'
+df -h /
+echo '--- 메모리 ---'
+free -h
+"@
+Invoke-Expression "$SSH '$checkScript'"
+
+Log-Section "✅ 배포 완료!"
+Write-Host ""
+Write-Host "${GREEN}홈페이지 주소:${NC} http://$ServerIP"
+Write-Host "${GREEN}SSH 접속:${NC} ssh -i `"$KeyPath`" ubuntu@$ServerIP"
+Write-Host ""
+Write-Host "${YELLOW}브라우저에서 확인:${NC}"
+Start-Process "http://$ServerIP"
diff --git a/deploy/04_ssl_setup.sh b/deploy/04_ssl_setup.sh
new file mode 100644
index 00000000..ffd3cea6
--- /dev/null
+++ b/deploy/04_ssl_setup.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# ============================================================
+# SSL 인증서 설정 (Let's Encrypt / Certbot)
+# 도메인이 있을 경우 실행
+# 실행: bash 04_ssl_setup.sh yourdomain.com
+# ============================================================
+
+DOMAIN=${1:-"zioinfo.co.kr"}
+
+echo "[1] Certbot 설치"
+sudo apt-get install -y certbot python3-certbot-nginx
+
+echo "[2] SSL 인증서 발급 — $DOMAIN"
+sudo certbot --nginx -d $DOMAIN -d www.$DOMAIN \
+ --non-interactive --agree-tos --email admin@$DOMAIN \
+ --redirect
+
+echo "[3] 자동 갱신 확인"
+sudo certbot renew --dry-run
+
+echo "[4] Nginx 재시작"
+sudo systemctl reload nginx
+
+echo "✅ SSL 설정 완료!"
+echo " https://$DOMAIN 으로 접속하세요"
diff --git a/deploy/05_update_deploy.sh b/deploy/05_update_deploy.sh
new file mode 100644
index 00000000..8a0138f7
--- /dev/null
+++ b/deploy/05_update_deploy.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+# ============================================================
+# 빠른 업데이트 배포 스크립트 (서버에서 실행)
+# 로컬에서 SCP로 파일 올린 후 서버에서 실행
+# 실행: bash 05_update_deploy.sh
+# ============================================================
+
+echo "[1] 새 파일 적용"
+sudo cp -r /tmp/dist/* /var/www/zioinfo/
+sudo chown -R www-data:www-data /var/www/zioinfo/
+
+echo "[2] Spring Boot 재시작 (있을 경우)"
+if systemctl is-active --quiet zioinfo; then
+ sudo systemctl restart zioinfo
+ echo " Spring Boot 재시작됨"
+fi
+
+echo "[3] Nginx 재로드"
+sudo nginx -t && sudo systemctl reload nginx
+
+echo "✅ 업데이트 완료! $(date)"
diff --git a/deploy/06_guardia_server_setup.sh b/deploy/06_guardia_server_setup.sh
new file mode 100644
index 00000000..d7f920df
--- /dev/null
+++ b/deploy/06_guardia_server_setup.sh
@@ -0,0 +1,135 @@
+#!/bin/bash
+# ============================================================
+# GUARDiA ITSM 서버 환경 구성
+# Oracle Cloud Ubuntu 22.04 ARM (Ampere A1)
+# 실행: bash 06_guardia_server_setup.sh
+# ============================================================
+
+set -e
+GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
+info() { echo -e "${GREEN}[OK]${NC} $1"; }
+warn() { echo -e "${YELLOW}[!]${NC} $1"; }
+section() { echo -e "\n${CYAN}════════════════════════════════${NC}"; echo -e "${CYAN} $1${NC}"; echo -e "${CYAN}════════════════════════════════${NC}"; }
+
+# ── 1. Python 3.11 ──────────────────────────────────────────
+section "1. Python 3.11 설치"
+sudo apt-get install -y python3.11 python3.11-venv python3.11-dev python3-pip
+sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1
+python3 --version
+info "Python 3.11 설치 완료"
+
+# ── 2. PostgreSQL 15 ─────────────────────────────────────────
+section "2. PostgreSQL 15 설치"
+sudo apt-get install -y postgresql postgresql-contrib
+sudo systemctl enable postgresql
+sudo systemctl start postgresql
+
+# DB / 사용자 생성
+sudo -u postgres psql < /opt/guardia/app/.env < /dev/null </dev/null || true
+fi
+
+# DB 마이그레이션
+python3 db_init.py --force 2>/dev/null || python3 -c "
+from database import engine, Base
+from models import *
+import asyncio
+async def init():
+ async with engine.begin() as conn:
+ await conn.run_sync(Base.metadata.create_all)
+asyncio.run(init())
+print('DB 초기화 완료')
+" 2>/dev/null || echo "DB 초기화 스킵 (이미 존재)"
+
+echo "[4] 서비스 재시작"
+sudo systemctl restart guardia
+sleep 3
+sudo systemctl is-active guardia && echo "GUARDiA 서비스 실행 중" || echo "서비스 시작 실패 — 로그 확인 필요"
+
+echo "[5] 정리"
+rm -rf /tmp/guardia_src /tmp/guardia_deploy.zip
+echo "배포 완료!"
+'@
+
+Invoke-Expression "$SSH 'bash -s'" | bash -s <<< "$deployScript"
+
+Log-Section "4. 접속 확인"
+$checkScript = @"
+echo '--- GUARDiA 서비스 상태 ---'
+sudo systemctl status guardia --no-pager -l | head -20
+echo ''
+echo '--- 포트 확인 ---'
+ss -tlnp | grep -E ':8001|:8080|:80'
+echo ''
+echo '--- 메모리 사용량 ---'
+free -h
+echo ''
+echo '--- GUARDiA API 헬스체크 ---'
+sleep 2
+curl -s http://localhost:8001/api/admin/health | python3 -m json.tool 2>/dev/null || echo 'API 응답 대기 중...'
+"@
+Invoke-Expression "$SSH '$checkScript'"
+
+Write-Host ""
+Write-Host "`e[32m✅ GUARDiA ITSM 배포 완료!`e[0m"
+Write-Host ""
+Write-Host " 홈페이지: http://$ServerIP"
+Write-Host " GUARDiA ITSM: http://$ServerIP`:8001"
+Write-Host " 관리자 로그인: admin / admin (최초 접속 후 변경 필수)"
+Write-Host ""
+Write-Host "`e[33m보안 권고:`e[0m"
+Write-Host " 1. 최초 로그인 후 즉시 비밀번호 변경"
+Write-Host " 2. MFA 활성화: POST /api/auth/mfa/setup"
+Write-Host " 3. 포트 8001 → Nginx 뒤로 숨기기 (07_nginx_all.sh 실행)"
diff --git a/deploy/09_full_deploy_guide.md b/deploy/09_full_deploy_guide.md
new file mode 100644
index 00000000..3cf53dc1
--- /dev/null
+++ b/deploy/09_full_deploy_guide.md
@@ -0,0 +1,44 @@
+# zio-server 전체 배포 가이드
+
+## 서버 구성 완료 후 실행 순서
+
+```bash
+# 1. 기본 환경 (Java, Node, Nginx)
+bash 02_server_setup.sh
+
+# 2. GUARDiA 환경 (Python, PostgreSQL, Ollama)
+bash 06_guardia_server_setup.sh
+
+# 3. Nginx 통합 설정
+bash 07_nginx_all.sh zioinfo.co.kr itsm.zioinfo.co.kr
+
+# 4. 홈페이지 배포 (Windows에서)
+.\deploy\03_deploy.ps1 -ServerIP "서버IP"
+
+# 5. GUARDiA 배포 (Windows에서)
+.\deploy\08_deploy_guardia.ps1 -ServerIP "서버IP"
+
+# 6. SSL 인증서 (도메인 있을 때)
+bash 04_ssl_setup.sh zioinfo.co.kr
+```
+
+## 최종 서비스 목록
+
+| 서비스 | 주소 | 포트 |
+|--------|------|------|
+| 지오정보기술 홈페이지 | http://zioinfo.co.kr | 80/443 |
+| GUARDiA ITSM | http://itsm.zioinfo.co.kr | 80/443 |
+| PostgreSQL | 내부 전용 | 5432 |
+| Ollama LLM | 내부 전용 | 11434 |
+
+## 리소스 사용 예상
+
+| 구성 요소 | CPU | RAM |
+|-----------|-----|-----|
+| Nginx | 0.1 OCPU | 50 MB |
+| Spring Boot | 0.5 OCPU | 512 MB |
+| FastAPI (GUARDiA) | 0.5 OCPU | 300 MB |
+| PostgreSQL | 0.3 OCPU | 256 MB |
+| Ollama + LLaMA-3 8B | 1.0 OCPU | 6 GB |
+| **합계** | **2.4 OCPU** | **~7.1 GB** |
+| **여유** | **1.6 OCPU** | **~16.9 GB** ✅ |
diff --git a/deploy/10_gitea_smtp_setup.sh b/deploy/10_gitea_smtp_setup.sh
new file mode 100644
index 00000000..ef2d41f0
--- /dev/null
+++ b/deploy/10_gitea_smtp_setup.sh
@@ -0,0 +1,258 @@
+#!/bin/bash
+# ============================================================
+# Gitea (Git 서버) + SMTP (Postfix) 설치 스크립트
+# Oracle Cloud Ubuntu 22.04 ARM (Ampere A1)
+# 실행: bash 10_gitea_smtp_setup.sh [도메인]
+# ============================================================
+
+set -e
+DOMAIN=${1:-"$(curl -s ifconfig.me)"}
+GITEA_DOMAIN="git.${DOMAIN}"
+GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[1;33m'; NC='\033[0m'
+info() { echo -e "${GREEN}[OK]${NC} $1"; }
+section() { echo -e "\n${CYAN}════════════════════════════════${NC}"; echo -e "${CYAN} $1${NC}"; echo -e "${CYAN}════════════════════════════════${NC}"; }
+
+# ────────────────────────────────────────────────
+# PART 1: Gitea 설치
+# ────────────────────────────────────────────────
+
+section "1. Gitea 사용자 생성"
+sudo adduser --system --shell /bin/bash --gecos 'Git Version Control' \
+ --group --disabled-password --home /home/git git 2>/dev/null || true
+info "git 사용자 준비 완료"
+
+section "2. Gitea 바이너리 다운로드 (ARM64)"
+GITEA_VER="1.22.3"
+sudo mkdir -p /opt/gitea/bin
+sudo wget -q "https://dl.gitea.com/gitea/${GITEA_VER}/gitea-${GITEA_VER}-linux-arm64" \
+ -O /opt/gitea/bin/gitea
+sudo chmod +x /opt/gitea/bin/gitea
+sudo ln -sf /opt/gitea/bin/gitea /usr/local/bin/gitea
+gitea --version
+info "Gitea ${GITEA_VER} 다운로드 완료"
+
+section "3. Gitea 디렉터리 구조"
+sudo mkdir -p /var/lib/gitea/{custom,data,log}
+sudo mkdir -p /etc/gitea
+sudo chown -R git:git /var/lib/gitea /etc/gitea
+sudo chmod -R 750 /var/lib/gitea /etc/gitea
+info "Gitea 디렉터리 생성 완료"
+
+section "4. Gitea PostgreSQL DB 생성"
+sudo -u postgres psql </dev/null || true
+CREATE USER gitea WITH PASSWORD 'G1tea_2026!';
+CREATE DATABASE gitea_db OWNER gitea;
+GRANT ALL PRIVILEGES ON DATABASE gitea_db TO gitea;
+PSQL
+info "Gitea DB 생성 완료"
+
+section "5. Gitea 설정 파일 생성"
+sudo tee /etc/gitea/app.ini > /dev/null < /dev/null < /dev/null < /dev/null < /dev/null <
+
+
+
+
+
+
+
+
+
+ (주)지오정보기술
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 00000000..6641d059
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,2174 @@
+{
+ "name": "zioinfo-web-frontend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "zioinfo-web-frontend",
+ "version": "1.0.0",
+ "dependencies": {
+ "axios": "^1.7.2",
+ "react": "^18.3.1",
+ "react-countup": "^6.5.3",
+ "react-dom": "^18.3.1",
+ "react-intersection-observer": "^9.10.3",
+ "react-router-dom": "^6.23.1",
+ "react-scroll": "^1.9.0",
+ "swiper": "^11.1.4"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.0",
+ "vite": "^5.2.11"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
+ "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
+ "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.23.3",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz",
+ "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz",
+ "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz",
+ "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz",
+ "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz",
+ "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz",
+ "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz",
+ "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz",
+ "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz",
+ "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz",
+ "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz",
+ "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz",
+ "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz",
+ "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz",
+ "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz",
+ "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz",
+ "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz",
+ "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz",
+ "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz",
+ "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz",
+ "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz",
+ "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz",
+ "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz",
+ "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz",
+ "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz",
+ "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz",
+ "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.29",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz",
+ "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz",
+ "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.16.0",
+ "form-data": "^4.0.5",
+ "https-proxy-agent": "^5.0.1",
+ "proxy-from-env": "^2.1.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.32",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz",
+ "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001793",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
+ "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/countup.js": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.10.0.tgz",
+ "integrity": "sha512-QQpZx7oYxsR+OeITlZe46fY/OQjV11oBqjY8wgIXzLU2jIz8GzOrbMhqKLysGY8bWI3T1ZNrYkwGzKb4JNgyzg==",
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.364",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz",
+ "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lodash.throttle": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+ "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.46",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz",
+ "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-countup": {
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.5.3.tgz",
+ "integrity": "sha512-udnqVQitxC7QWADSPDOxVWULkLvKUWrDapn5i53HE4DPRVgs+Y5rr4bo25qEl8jSh+0l2cToJgGMx+clxPM3+w==",
+ "license": "MIT",
+ "dependencies": {
+ "countup.js": "^2.8.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.3.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-intersection-observer": {
+ "version": "9.16.0",
+ "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz",
+ "integrity": "sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "6.30.4",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz",
+ "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.3"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.30.4",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz",
+ "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.3",
+ "react-router": "6.30.4"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/react-scroll": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/react-scroll/-/react-scroll-1.9.3.tgz",
+ "integrity": "sha512-xv7FXqF3k63aSLNu4/NjFvRNI0ge7DmmmsbeGarP7LZVAlJMSjUuW3dTtLxp1Afijyv0lS2qwC0GiFHvx1KBHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash.throttle": "^4.1.1",
+ "prop-types": "^15.7.2"
+ },
+ "peerDependencies": {
+ "react": "^15.5.4 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^15.5.4 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz",
+ "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.60.4",
+ "@rollup/rollup-android-arm64": "4.60.4",
+ "@rollup/rollup-darwin-arm64": "4.60.4",
+ "@rollup/rollup-darwin-x64": "4.60.4",
+ "@rollup/rollup-freebsd-arm64": "4.60.4",
+ "@rollup/rollup-freebsd-x64": "4.60.4",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.4",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.4",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.4",
+ "@rollup/rollup-linux-arm64-musl": "4.60.4",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.4",
+ "@rollup/rollup-linux-loong64-musl": "4.60.4",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.4",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.4",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.4",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.4",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.4",
+ "@rollup/rollup-linux-x64-gnu": "4.60.4",
+ "@rollup/rollup-linux-x64-musl": "4.60.4",
+ "@rollup/rollup-openbsd-x64": "4.60.4",
+ "@rollup/rollup-openharmony-arm64": "4.60.4",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.4",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.4",
+ "@rollup/rollup-win32-x64-gnu": "4.60.4",
+ "@rollup/rollup-win32-x64-msvc": "4.60.4",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/swiper": {
+ "version": "11.2.10",
+ "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.10.tgz",
+ "integrity": "sha512-RMeVUUjTQH+6N3ckimK93oxz6Sn5la4aDlgPzB+rBrG/smPdCTicXyhxa+woIpopz+jewEloiEE3lKo1h9w2YQ==",
+ "funding": [
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/swiperjs"
+ },
+ {
+ "type": "open_collective",
+ "url": "http://opencollective.com/swiper"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.7.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 00000000..07a25ea7
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "zioinfo-web-frontend",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-router-dom": "^6.23.1",
+ "axios": "^1.7.2",
+ "swiper": "^11.1.4",
+ "react-intersection-observer": "^9.10.3",
+ "react-countup": "^6.5.3",
+ "react-scroll": "^1.9.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.0",
+ "vite": "^5.2.11"
+ }
+}
diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico
new file mode 100644
index 00000000..3252b9f8
Binary files /dev/null and b/frontend/public/favicon.ico differ
diff --git a/frontend/public/index.html b/frontend/public/index.html
new file mode 100644
index 00000000..fe630a0e
--- /dev/null
+++ b/frontend/public/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+ (주)지오정보기술
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/public/logo-white.png b/frontend/public/logo-white.png
new file mode 100644
index 00000000..0ae65e05
Binary files /dev/null and b/frontend/public/logo-white.png differ
diff --git a/frontend/public/logo.png b/frontend/public/logo.png
new file mode 100644
index 00000000..4f88262a
Binary files /dev/null and b/frontend/public/logo.png differ
diff --git a/frontend/public/screenshots/01_dashboard.png b/frontend/public/screenshots/01_dashboard.png
new file mode 100644
index 00000000..6728dfd8
Binary files /dev/null and b/frontend/public/screenshots/01_dashboard.png differ
diff --git a/frontend/public/screenshots/01_home.png b/frontend/public/screenshots/01_home.png
new file mode 100644
index 00000000..9dd704fe
Binary files /dev/null and b/frontend/public/screenshots/01_home.png differ
diff --git a/frontend/public/screenshots/01_home_viewport.png b/frontend/public/screenshots/01_home_viewport.png
new file mode 100644
index 00000000..64e7d888
Binary files /dev/null and b/frontend/public/screenshots/01_home_viewport.png differ
diff --git a/frontend/public/screenshots/02_guardia.png b/frontend/public/screenshots/02_guardia.png
new file mode 100644
index 00000000..df6d4c74
Binary files /dev/null and b/frontend/public/screenshots/02_guardia.png differ
diff --git a/frontend/public/screenshots/02_guardia_viewport.png b/frontend/public/screenshots/02_guardia_viewport.png
new file mode 100644
index 00000000..19b5addd
Binary files /dev/null and b/frontend/public/screenshots/02_guardia_viewport.png differ
diff --git a/frontend/public/screenshots/02_sr_list.png b/frontend/public/screenshots/02_sr_list.png
new file mode 100644
index 00000000..6728dfd8
Binary files /dev/null and b/frontend/public/screenshots/02_sr_list.png differ
diff --git a/frontend/public/screenshots/03_company.png b/frontend/public/screenshots/03_company.png
new file mode 100644
index 00000000..88e925d3
Binary files /dev/null and b/frontend/public/screenshots/03_company.png differ
diff --git a/frontend/public/screenshots/03_company_viewport.png b/frontend/public/screenshots/03_company_viewport.png
new file mode 100644
index 00000000..e64b8177
Binary files /dev/null and b/frontend/public/screenshots/03_company_viewport.png differ
diff --git a/frontend/public/screenshots/03_si_project.png b/frontend/public/screenshots/03_si_project.png
new file mode 100644
index 00000000..6481c7dc
Binary files /dev/null and b/frontend/public/screenshots/03_si_project.png differ
diff --git a/frontend/public/screenshots/04_contact.png b/frontend/public/screenshots/04_contact.png
new file mode 100644
index 00000000..6bfc0ebf
Binary files /dev/null and b/frontend/public/screenshots/04_contact.png differ
diff --git a/frontend/public/screenshots/04_contact_viewport.png b/frontend/public/screenshots/04_contact_viewport.png
new file mode 100644
index 00000000..f456b4e8
Binary files /dev/null and b/frontend/public/screenshots/04_contact_viewport.png differ
diff --git a/frontend/public/screenshots/04_incidents.png b/frontend/public/screenshots/04_incidents.png
new file mode 100644
index 00000000..a50691cb
Binary files /dev/null and b/frontend/public/screenshots/04_incidents.png differ
diff --git a/frontend/public/screenshots/05_agents.png b/frontend/public/screenshots/05_agents.png
new file mode 100644
index 00000000..847ccb8f
Binary files /dev/null and b/frontend/public/screenshots/05_agents.png differ
diff --git a/frontend/public/screenshots/05_news.png b/frontend/public/screenshots/05_news.png
new file mode 100644
index 00000000..48dc2141
Binary files /dev/null and b/frontend/public/screenshots/05_news.png differ
diff --git a/frontend/public/screenshots/05_news_viewport.png b/frontend/public/screenshots/05_news_viewport.png
new file mode 100644
index 00000000..f212eb23
Binary files /dev/null and b/frontend/public/screenshots/05_news_viewport.png differ
diff --git a/frontend/public/screenshots/06_license.png b/frontend/public/screenshots/06_license.png
new file mode 100644
index 00000000..52f91192
Binary files /dev/null and b/frontend/public/screenshots/06_license.png differ
diff --git a/frontend/public/screenshots/06_mobile_home.png b/frontend/public/screenshots/06_mobile_home.png
new file mode 100644
index 00000000..c3f3355d
Binary files /dev/null and b/frontend/public/screenshots/06_mobile_home.png differ
diff --git a/frontend/public/screenshots/all-pages-result.json b/frontend/public/screenshots/all-pages-result.json
new file mode 100644
index 00000000..49a94b33
--- /dev/null
+++ b/frontend/public/screenshots/all-pages-result.json
@@ -0,0 +1,212 @@
+[
+ {
+ "page": "홈",
+ "url": "/",
+ "http": 200,
+ "loadMs": 5849,
+ "title": "(주)지오정보기술",
+ "h1": "AI 기반 인프라자율 운영 플랫폼",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "GUARDiA ITSM",
+ "url": "/solution/guardia",
+ "http": 200,
+ "loadMs": 1292,
+ "title": "(주)지오정보기술",
+ "h1": "GUARDiA ITSM",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "솔루션-ERP",
+ "url": "/solution/erp",
+ "http": 200,
+ "loadMs": 1767,
+ "title": "(주)지오정보기술",
+ "h1": "ERP 솔루션",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "솔루션-CRM",
+ "url": "/solution/crm",
+ "http": 200,
+ "loadMs": 1139,
+ "title": "(주)지오정보기술",
+ "h1": "CRM 솔루션",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "솔루션-BI",
+ "url": "/solution/bi",
+ "http": 200,
+ "loadMs": 966,
+ "title": "(주)지오정보기술",
+ "h1": "BI 솔루션",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "회사-CEO인사말",
+ "url": "/company/greeting",
+ "http": 200,
+ "loadMs": 1098,
+ "title": "(주)지오정보기술",
+ "h1": "CEO 인사말",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "회사-연혁",
+ "url": "/company/history",
+ "http": 200,
+ "loadMs": 1548,
+ "title": "(주)지오정보기술",
+ "h1": "연혁",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "회사-조직도",
+ "url": "/company/organization",
+ "http": 200,
+ "loadMs": 892,
+ "title": "(주)지오정보기술",
+ "h1": "조직도",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "회사-CI소개",
+ "url": "/company/ci",
+ "http": 200,
+ "loadMs": 1007,
+ "title": "(주)지오정보기술",
+ "h1": "CI 소개",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "회사-오시는길",
+ "url": "/company/location",
+ "http": 200,
+ "loadMs": 1070,
+ "title": "(주)지오정보기술",
+ "h1": "오시는 길",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "사업-레퍼런스",
+ "url": "/business/reference",
+ "http": 200,
+ "loadMs": 1111,
+ "title": "(주)지오정보기술",
+ "h1": "구축 레퍼런스",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "사업-파트너",
+ "url": "/business/partner",
+ "http": 200,
+ "loadMs": 1090,
+ "title": "(주)지오정보기술",
+ "h1": "파트너",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "지원-공지사항",
+ "url": "/support/notice",
+ "http": 200,
+ "loadMs": 949,
+ "title": "(주)지오정보기술",
+ "h1": "공지사항",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "지원-FAQ",
+ "url": "/support/faq",
+ "http": 200,
+ "loadMs": 931,
+ "title": "(주)지오정보기술",
+ "h1": "자주 묻는 질문",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "지원-카탈로그",
+ "url": "/support/catalog",
+ "http": 200,
+ "loadMs": 963,
+ "title": "(주)지오정보기술",
+ "h1": "카탈로그",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "지원-문의하기",
+ "url": "/support/contact",
+ "http": 200,
+ "loadMs": 1007,
+ "title": "(주)지오정보기술",
+ "h1": "문의하기",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "채용-공고",
+ "url": "/recruit/jobs",
+ "http": 200,
+ "loadMs": 984,
+ "title": "(주)지오정보기술",
+ "h1": "채용공고",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "채용-복리후생",
+ "url": "/recruit/welfare",
+ "http": 200,
+ "loadMs": 1275,
+ "title": "(주)지오정보기술",
+ "h1": "복리후생",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "채용-지원하기",
+ "url": "/recruit/apply",
+ "http": 200,
+ "loadMs": 880,
+ "title": "(주)지오정보기술",
+ "h1": "지원하기",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "뉴스-뉴스룸",
+ "url": "/news/newsroom",
+ "http": 200,
+ "loadMs": 1144,
+ "title": "(주)지오정보기술",
+ "h1": "뉴스룸",
+ "errors": 0,
+ "ok": true
+ },
+ {
+ "page": "뉴스-블로그",
+ "url": "/news/blog",
+ "http": 200,
+ "loadMs": 989,
+ "title": "(주)지오정보기술",
+ "h1": "기술 블로그",
+ "errors": 0,
+ "ok": true
+ }
+]
\ No newline at end of file
diff --git a/frontend/public/screenshots/business_partner.png b/frontend/public/screenshots/business_partner.png
new file mode 100644
index 00000000..3dce8a21
Binary files /dev/null and b/frontend/public/screenshots/business_partner.png differ
diff --git a/frontend/public/screenshots/business_ref.png b/frontend/public/screenshots/business_ref.png
new file mode 100644
index 00000000..8b93a0f9
Binary files /dev/null and b/frontend/public/screenshots/business_ref.png differ
diff --git a/frontend/public/screenshots/company_ci.png b/frontend/public/screenshots/company_ci.png
new file mode 100644
index 00000000..2eb6f3ca
Binary files /dev/null and b/frontend/public/screenshots/company_ci.png differ
diff --git a/frontend/public/screenshots/company_greeting.png b/frontend/public/screenshots/company_greeting.png
new file mode 100644
index 00000000..9f932701
Binary files /dev/null and b/frontend/public/screenshots/company_greeting.png differ
diff --git a/frontend/public/screenshots/company_history.png b/frontend/public/screenshots/company_history.png
new file mode 100644
index 00000000..bc970cb1
Binary files /dev/null and b/frontend/public/screenshots/company_history.png differ
diff --git a/frontend/public/screenshots/company_location.png b/frontend/public/screenshots/company_location.png
new file mode 100644
index 00000000..5132a049
Binary files /dev/null and b/frontend/public/screenshots/company_location.png differ
diff --git a/frontend/public/screenshots/company_org.png b/frontend/public/screenshots/company_org.png
new file mode 100644
index 00000000..c38e7b0e
Binary files /dev/null and b/frontend/public/screenshots/company_org.png differ
diff --git a/frontend/public/screenshots/guardia.png b/frontend/public/screenshots/guardia.png
new file mode 100644
index 00000000..19b5addd
Binary files /dev/null and b/frontend/public/screenshots/guardia.png differ
diff --git a/frontend/public/screenshots/home.png b/frontend/public/screenshots/home.png
new file mode 100644
index 00000000..5b4df9de
Binary files /dev/null and b/frontend/public/screenshots/home.png differ
diff --git a/frontend/public/screenshots/news_blog.png b/frontend/public/screenshots/news_blog.png
new file mode 100644
index 00000000..94697ec8
Binary files /dev/null and b/frontend/public/screenshots/news_blog.png differ
diff --git a/frontend/public/screenshots/news_newsroom.png b/frontend/public/screenshots/news_newsroom.png
new file mode 100644
index 00000000..d799d773
Binary files /dev/null and b/frontend/public/screenshots/news_newsroom.png differ
diff --git a/frontend/public/screenshots/recruit_apply.png b/frontend/public/screenshots/recruit_apply.png
new file mode 100644
index 00000000..8e79f187
Binary files /dev/null and b/frontend/public/screenshots/recruit_apply.png differ
diff --git a/frontend/public/screenshots/recruit_jobs.png b/frontend/public/screenshots/recruit_jobs.png
new file mode 100644
index 00000000..951e94f3
Binary files /dev/null and b/frontend/public/screenshots/recruit_jobs.png differ
diff --git a/frontend/public/screenshots/recruit_welfare.png b/frontend/public/screenshots/recruit_welfare.png
new file mode 100644
index 00000000..53752fb2
Binary files /dev/null and b/frontend/public/screenshots/recruit_welfare.png differ
diff --git a/frontend/public/screenshots/solution_bi.png b/frontend/public/screenshots/solution_bi.png
new file mode 100644
index 00000000..6999f9f1
Binary files /dev/null and b/frontend/public/screenshots/solution_bi.png differ
diff --git a/frontend/public/screenshots/solution_crm.png b/frontend/public/screenshots/solution_crm.png
new file mode 100644
index 00000000..a45d34d9
Binary files /dev/null and b/frontend/public/screenshots/solution_crm.png differ
diff --git a/frontend/public/screenshots/solution_erp.png b/frontend/public/screenshots/solution_erp.png
new file mode 100644
index 00000000..c6471957
Binary files /dev/null and b/frontend/public/screenshots/solution_erp.png differ
diff --git a/frontend/public/screenshots/support_catalog.png b/frontend/public/screenshots/support_catalog.png
new file mode 100644
index 00000000..e9ee2998
Binary files /dev/null and b/frontend/public/screenshots/support_catalog.png differ
diff --git a/frontend/public/screenshots/support_contact.png b/frontend/public/screenshots/support_contact.png
new file mode 100644
index 00000000..f456b4e8
Binary files /dev/null and b/frontend/public/screenshots/support_contact.png differ
diff --git a/frontend/public/screenshots/support_faq.png b/frontend/public/screenshots/support_faq.png
new file mode 100644
index 00000000..62006a57
Binary files /dev/null and b/frontend/public/screenshots/support_faq.png differ
diff --git a/frontend/public/screenshots/support_notice.png b/frontend/public/screenshots/support_notice.png
new file mode 100644
index 00000000..eba6bd03
Binary files /dev/null and b/frontend/public/screenshots/support_notice.png differ
diff --git a/frontend/public/screenshots/test-result.json b/frontend/public/screenshots/test-result.json
new file mode 100644
index 00000000..04877fa2
--- /dev/null
+++ b/frontend/public/screenshots/test-result.json
@@ -0,0 +1,67 @@
+[
+ {
+ "page": "홈",
+ "url": "/",
+ "status": 200,
+ "loadMs": 9745,
+ "title": "(주)지오정보기술",
+ "links": 37,
+ "images": 2,
+ "h1": 1,
+ "errors": 0,
+ "errorMsgs": [],
+ "screenshot": "C:\\GUARDiA\\workspace\\zioinfo-web\\frontend\\public\\screenshots\\01_home.png"
+ },
+ {
+ "page": "GUARDiA 소개",
+ "url": "/solution/guardia",
+ "status": 200,
+ "loadMs": 1130,
+ "title": "(주)지오정보기술",
+ "links": 27,
+ "images": 8,
+ "h1": 1,
+ "errors": 0,
+ "errorMsgs": [],
+ "screenshot": "C:\\GUARDiA\\workspace\\zioinfo-web\\frontend\\public\\screenshots\\02_guardia.png"
+ },
+ {
+ "page": "회사소개",
+ "url": "/company/greeting",
+ "status": 200,
+ "loadMs": 971,
+ "title": "(주)지오정보기술",
+ "links": 23,
+ "images": 2,
+ "h1": 1,
+ "errors": 0,
+ "errorMsgs": [],
+ "screenshot": "C:\\GUARDiA\\workspace\\zioinfo-web\\frontend\\public\\screenshots\\03_company.png"
+ },
+ {
+ "page": "문의하기",
+ "url": "/support/contact",
+ "status": 200,
+ "loadMs": 890,
+ "title": "(주)지오정보기술",
+ "links": 24,
+ "images": 2,
+ "h1": 1,
+ "errors": 0,
+ "errorMsgs": [],
+ "screenshot": "C:\\GUARDiA\\workspace\\zioinfo-web\\frontend\\public\\screenshots\\04_contact.png"
+ },
+ {
+ "page": "뉴스",
+ "url": "/news/press",
+ "status": 200,
+ "loadMs": 1007,
+ "title": "(주)지오정보기술",
+ "links": 23,
+ "images": 2,
+ "h1": 1,
+ "errors": 0,
+ "errorMsgs": [],
+ "screenshot": "C:\\GUARDiA\\workspace\\zioinfo-web\\frontend\\public\\screenshots\\05_news.png"
+ }
+]
\ No newline at end of file
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
new file mode 100644
index 00000000..04abb3de
--- /dev/null
+++ b/frontend/src/App.jsx
@@ -0,0 +1,84 @@
+import React, { Suspense, lazy } from 'react';
+import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
+import Header from './components/layout/Header';
+import Footer from './components/layout/Footer';
+
+const Home = lazy(() => import('./pages/Home'));
+const GuardiaDetail = lazy(() => import('./pages/GuardiaDetail'));
+const SolutionPage = lazy(() => import('./pages/SolutionPage'));
+const Company = lazy(() => import('./pages/Company'));
+const Business = lazy(() => import('./pages/Business'));
+const Contact = lazy(() => import('./pages/Contact'));
+const Support = lazy(() => import('./pages/Support'));
+const NewsPage = lazy(() => import('./pages/NewsPage'));
+const Recruit = lazy(() => import('./pages/Recruit'));
+const NotFound = lazy(() => import('./pages/NotFound'));
+
+// Admin
+const AdminLogin = lazy(() => import('./pages/admin/AdminLogin'));
+const AdminLayout = lazy(() => import('./pages/admin/AdminLayout'));
+const AdminDashboard = lazy(() => import('./pages/admin/AdminDashboard'));
+const AdminNews = lazy(() => import('./pages/admin/AdminNews'));
+const AdminInquiry = lazy(() => import('./pages/admin/AdminInquiry'));
+const AdminRecruit = lazy(() => import('./pages/admin/AdminRecruit'));
+const AdminSettings = lazy(() => import('./pages/admin/AdminSettings'));
+
+function Loading() {
+ return (
+
+ 로딩 중...
+
+ );
+}
+
+function PublicLayout({ children }) {
+ return (
+ <>
+
+ }>{children}
+
+ >
+ );
+}
+
+export default function App() {
+ const location = useLocation();
+ const isAdmin = location.pathname.startsWith('/admin');
+
+ if (isAdmin) {
+ return (
+ }>
+
+ } />
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
+
+
+ );
+ }
+
+ return (
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+ );
+}
diff --git a/frontend/src/components/layout/Footer.css b/frontend/src/components/layout/Footer.css
new file mode 100644
index 00000000..71e7867e
--- /dev/null
+++ b/frontend/src/components/layout/Footer.css
@@ -0,0 +1,69 @@
+/* ─── Footer ──────────────────────────────────────────────── */
+.footer { background: var(--secondary); color: rgba(255,255,255,.8); }
+
+.footer-top { padding: 60px 0; }
+.footer-top-inner {
+ display: grid;
+ grid-template-columns: 280px repeat(4, 1fr);
+ gap: 40px;
+}
+
+.footer-brand { }
+.footer-logo { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
+.footer-logo img { height: 36px; }
+.footer-logo-text { font-size: 20px; font-weight: 700; color: #fff; }
+.footer-logo-text strong { color: var(--accent); }
+.footer-tagline {
+ font-size: 13px; line-height: 1.8;
+ color: rgba(255,255,255,.6);
+ margin-bottom: 20px;
+}
+.footer-contact-list { display: flex; flex-direction: column; gap: 8px; }
+.footer-contact-item { display: flex; gap: 10px; font-size: 13px; }
+.contact-label { color: rgba(255,255,255,.4); min-width: 60px; }
+.footer-contact-item a { color: var(--accent); }
+.footer-contact-item a:hover { text-decoration: underline; }
+
+.footer-menu-group { }
+.footer-menu-title {
+ font-size: 13px; font-weight: 700;
+ color: #fff; letter-spacing: .5px;
+ text-transform: uppercase;
+ margin-bottom: 16px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid rgba(255,255,255,.1);
+}
+.footer-menu-list { display: flex; flex-direction: column; gap: 10px; }
+.footer-menu-list a {
+ font-size: 13px; color: rgba(255,255,255,.6);
+ transition: color var(--fast);
+}
+.footer-menu-list a:hover { color: var(--accent); }
+
+/* 하단 바 */
+.footer-bottom {
+ border-top: 1px solid rgba(255,255,255,.08);
+ padding: 18px 0;
+}
+.footer-bottom-inner {
+ display: flex; align-items: center; gap: 24px;
+ font-size: 12px; color: rgba(255,255,255,.4);
+}
+.footer-legal { display: flex; gap: 16px; }
+.footer-legal a { color: rgba(255,255,255,.4); }
+.footer-legal a:hover { color: rgba(255,255,255,.8); }
+.footer-copyright { flex: 1; text-align: center; }
+.footer-powered { color: rgba(255,255,255,.3); }
+.footer-powered strong { color: var(--accent); }
+
+@media (max-width: 1024px) {
+ .footer-top-inner {
+ grid-template-columns: 1fr 1fr;
+ }
+ .footer-brand { grid-column: 1 / -1; }
+}
+@media (max-width: 768px) {
+ .footer-top-inner { grid-template-columns: 1fr 1fr; }
+ .footer-bottom-inner { flex-direction: column; text-align: center; gap: 12px; }
+ .footer-copyright { order: -1; }
+}
diff --git a/frontend/src/components/layout/Footer.jsx b/frontend/src/components/layout/Footer.jsx
new file mode 100644
index 00000000..799614c5
--- /dev/null
+++ b/frontend/src/components/layout/Footer.jsx
@@ -0,0 +1,111 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import './Footer.css';
+
+const FOOTER_MENUS = [
+ {
+ title: '회사소개',
+ links: [
+ { label: 'CEO 인사말', path: '/company/greeting' },
+ { label: '연혁', path: '/company/history' },
+ { label: '조직도', path: '/company/organization' },
+ { label: '오시는 길', path: '/company/location' },
+ ]
+ },
+ {
+ title: '솔루션',
+ links: [
+ { label: 'GUARDiA ITSM', path: '/solution/guardia' },
+ { label: 'ERP', path: '/solution/erp' },
+ { label: 'CRM', path: '/solution/crm' },
+ { label: 'BI', path: '/solution/bi' },
+ ]
+ },
+ {
+ title: '고객지원',
+ links: [
+ { label: '공지사항', path: '/support/notice' },
+ { label: 'FAQ', path: '/support/faq' },
+ { label: '카탈로그', path: '/support/catalog' },
+ { label: '문의하기', path: '/support/contact' },
+ ]
+ },
+ {
+ title: '채용',
+ links: [
+ { label: '채용공고', path: '/recruit/jobs' },
+ { label: '복리후생', path: '/recruit/welfare' },
+ { label: '지원하기', path: '/recruit/apply' },
+ ]
+ },
+];
+
+export default function Footer() {
+ return (
+
+ );
+}
diff --git a/frontend/src/components/layout/Header.css b/frontend/src/components/layout/Header.css
new file mode 100644
index 00000000..3cc3e002
--- /dev/null
+++ b/frontend/src/components/layout/Header.css
@@ -0,0 +1,126 @@
+/* ─── Header ──────────────────────────────────────────────── */
+.skip-link {
+ position: absolute; top: -60px; left: 0; z-index: 9999;
+ background: var(--primary); color: #fff;
+ padding: 10px 20px; border-radius: 0 0 8px 0;
+ transition: top .2s;
+}
+.skip-link:focus { top: 0; }
+
+.header {
+ position: fixed; top: 0; left: 0; right: 0;
+ z-index: 1000; height: var(--header-h);
+ background: rgba(26, 26, 46, 0.96);
+ backdrop-filter: blur(12px);
+ border-bottom: 1px solid rgba(255,255,255,.08);
+ transition: all var(--mid) var(--ease);
+}
+.header.scrolled {
+ background: rgba(26, 26, 46, 0.99);
+ box-shadow: 0 4px 24px rgba(0,0,0,.3);
+}
+.header-inner {
+ display: flex; align-items: center; gap: 32px;
+ height: 100%;
+}
+
+/* 로고 */
+.logo { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
+.logo img { height: 40px; width: auto; filter: brightness(0) invert(1); }
+.logo-text { color: #fff; font-size: 20px; font-weight: 700; }
+.logo-text strong { color: var(--accent); }
+
+/* 데스크톱 메뉴 */
+.nav-desktop {
+ display: flex; align-items: center; gap: 4px;
+ margin-left: 24px; flex: 1;
+}
+.nav-item { position: relative; }
+.nav-trigger {
+ height: var(--header-h);
+ padding: 0 16px;
+ color: rgba(255,255,255,.85);
+ font-size: 15px; font-weight: 500;
+ transition: color var(--fast);
+ display: flex; align-items: center;
+}
+.nav-trigger:hover,
+.nav-item.active .nav-trigger { color: #fff; }
+.nav-item.active .nav-trigger { border-bottom: 2px solid var(--accent); }
+
+/* 드롭다운 */
+.dropdown {
+ position: absolute; top: calc(var(--header-h) - 2px); left: 0;
+ min-width: 180px;
+ background: #fff;
+ border-radius: 0 0 var(--radius) var(--radius);
+ box-shadow: var(--shadow-lg);
+ border-top: 3px solid var(--primary);
+ padding: 8px 0;
+ animation: fadeDown .18s ease;
+}
+@keyframes fadeDown {
+ from { opacity:0; transform:translateY(-8px); }
+ to { opacity:1; transform:translateY(0); }
+}
+.dropdown-item {
+ display: flex; align-items: center; gap: 8px;
+ padding: 10px 20px;
+ font-size: 14px; color: var(--gray-700);
+ transition: all var(--fast);
+}
+.dropdown-item:hover, .dropdown-item.current {
+ background: var(--primary-light);
+ color: var(--primary);
+}
+
+/* CTA 버튼 */
+.header-cta { margin-left: auto; flex-shrink: 0; }
+
+/* 햄버거 */
+.hamburger {
+ display: none;
+ flex-direction: column;
+ gap: 5px;
+ padding: 8px;
+ margin-left: auto;
+}
+.hamburger span {
+ display: block; width: 24px; height: 2px;
+ background: #fff;
+ border-radius: 2px;
+ transition: all var(--mid);
+}
+
+/* 모바일 메뉴 */
+.nav-mobile {
+ display: none;
+ flex-direction: column;
+ background: var(--secondary);
+ border-top: 1px solid rgba(255,255,255,.1);
+ max-height: calc(100vh - var(--header-h));
+ overflow-y: auto;
+}
+.mobile-group { border-bottom: 1px solid rgba(255,255,255,.08); }
+.mobile-group-header {
+ display: flex; align-items: center;
+ padding: 14px 24px;
+ color: rgba(255,255,255,.85);
+ font-size: 15px; font-weight: 500;
+ cursor: pointer;
+}
+.mobile-children { background: rgba(0,0,0,.2); }
+.mobile-child {
+ display: flex; align-items: center; gap: 8px;
+ padding: 10px 36px;
+ font-size: 14px; color: rgba(255,255,255,.7);
+}
+.mobile-child:hover { color: #fff; }
+
+/* 반응형 */
+@media (max-width: 1024px) {
+ .nav-desktop, .header-cta { display: none; }
+ .hamburger { display: flex; }
+ .header.mobile-open .nav-mobile { display: flex; }
+ .header.mobile-open { height: auto; }
+}
diff --git a/frontend/src/components/layout/Header.jsx b/frontend/src/components/layout/Header.jsx
new file mode 100644
index 00000000..4a415f68
--- /dev/null
+++ b/frontend/src/components/layout/Header.jsx
@@ -0,0 +1,159 @@
+import React, { useState, useEffect, useCallback } from 'react';
+import { Link, useLocation } from 'react-router-dom';
+import './Header.css';
+
+const MENU = [
+ {
+ id: 'company', label: '회사소개',
+ children: [
+ { label: 'CEO 인사말', path: '/company/greeting' },
+ { label: '연혁', path: '/company/history' },
+ { label: '조직도', path: '/company/organization' },
+ { label: 'CI 소개', path: '/company/ci' },
+ { label: '오시는 길', path: '/company/location' },
+ ]
+ },
+ {
+ id: 'solution', label: '솔루션',
+ children: [
+ { label: 'GUARDiA ITSM', path: '/solution/guardia', badge: 'NEW' },
+ { label: 'ERP', path: '/solution/erp' },
+ { label: 'CRM', path: '/solution/crm' },
+ { label: 'BI', path: '/solution/bi' },
+ ]
+ },
+ {
+ id: 'business', label: '사업실적',
+ children: [
+ { label: '구축 레퍼런스', path: '/business/reference' },
+ { label: '파트너', path: '/business/partner' },
+ ]
+ },
+ {
+ id: 'support', label: '고객지원',
+ children: [
+ { label: '공지사항', path: '/support/notice' },
+ { label: 'FAQ', path: '/support/faq' },
+ { label: '카탈로그', path: '/support/catalog' },
+ { label: '문의하기', path: '/support/contact' },
+ ]
+ },
+ {
+ id: 'recruit', label: '채용',
+ children: [
+ { label: '채용공고', path: '/recruit/jobs' },
+ { label: '복리후생', path: '/recruit/welfare' },
+ { label: '지원하기', path: '/recruit/apply' },
+ ]
+ },
+ {
+ id: 'news', label: '뉴스',
+ children: [
+ { label: '뉴스룸', path: '/news/newsroom' },
+ { label: '기술 블로그', path: '/news/blog' },
+ ]
+ },
+];
+
+export default function Header() {
+ const [scrolled, setScrolled] = useState(false);
+ const [activeMenu, setActiveMenu] = useState(null);
+ const [mobileOpen, setMobileOpen] = useState(false);
+ const location = useLocation();
+
+ useEffect(() => {
+ const onScroll = () => setScrolled(window.scrollY > 60);
+ window.addEventListener('scroll', onScroll, { passive: true });
+ return () => window.removeEventListener('scroll', onScroll);
+ }, []);
+
+ useEffect(() => {
+ setMobileOpen(false);
+ setActiveMenu(null);
+ }, [location]);
+
+ const isActive = (menu) =>
+ menu.children?.some(c => location.pathname.startsWith(c.path));
+
+ return (
+ <>
+ {/* 접근성 스킵 링크 */}
+ 본문 바로가기
+
+
+ >
+ );
+}
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
new file mode 100644
index 00000000..1f820099
--- /dev/null
+++ b/frontend/src/main.jsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { BrowserRouter } from 'react-router-dom';
+import App from './App';
+import './styles/global.css';
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+
+
+
+);
diff --git a/frontend/src/pages/Business.css b/frontend/src/pages/Business.css
new file mode 100644
index 00000000..c3272f37
--- /dev/null
+++ b/frontend/src/pages/Business.css
@@ -0,0 +1,44 @@
+/* 레퍼런스 필터 */
+.ref-filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 24px; }
+.ref-filter-btn {
+ padding: 7px 18px; border-radius: 20px; border: 1px solid var(--gray-200);
+ font-size: 13px; font-weight: 500; color: var(--gray-600); cursor: pointer;
+ transition: all var(--fast) var(--ease); background: var(--white);
+}
+.ref-filter-btn:hover { border-color: var(--primary); color: var(--primary); }
+.ref-filter-btn.active { background: var(--primary); border-color: var(--primary); color: #fff; }
+
+/* 레퍼런스 테이블 */
+.ref-table-wrap { overflow-x: auto; border-radius: 12px; border: 1px solid var(--gray-200); }
+.ref-table { width: 100%; border-collapse: collapse; min-width: 800px; }
+.ref-table th {
+ background: var(--secondary); color: rgba(255,255,255,.8);
+ padding: 14px 16px; text-align: left; font-size: 12px;
+ font-weight: 600; letter-spacing: .5px;
+}
+.ref-table td { padding: 13px 16px; font-size: 13px; border-bottom: 1px solid var(--gray-100); vertical-align: middle; }
+.ref-table tr:last-child td { border-bottom: none; }
+.ref-table tr:hover td { background: var(--gray-50); }
+.ref-period { color: var(--gray-500); font-size: 12px; white-space: nowrap; }
+.ref-client { font-weight: 700; color: var(--gray-800); white-space: nowrap; }
+.ref-project { color: var(--gray-700); }
+.ref-role {
+ padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 700;
+ background: var(--primary-light); color: var(--primary); white-space: nowrap;
+}
+.ref-tech { font-size: 12px; color: var(--gray-500); }
+.ref-cat-badge { padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; white-space: nowrap; }
+
+/* 파트너 카드 */
+.partner-card { padding: 32px 24px; text-align: center; }
+.partner-logo { font-size: 48px; margin-bottom: 12px; }
+.partner-tier { display: inline-block; padding: 3px 12px; border-radius: 12px; font-size: 11px; font-weight: 700; margin-bottom: 12px; }
+.partner-name { font-size: 16px; font-weight: 700; color: var(--gray-900); margin-bottom: 10px; }
+.partner-desc { font-size: 13px; color: var(--gray-600); line-height: 1.6; }
+.partner-cta {
+ margin-top: 64px; text-align: center; padding: 56px;
+ background: linear-gradient(135deg, var(--primary-light), rgba(0,163,224,.08));
+ border-radius: 16px; border: 1px solid var(--gray-200);
+}
+.partner-cta h3 { font-size: 24px; font-weight: 800; margin-bottom: 12px; }
+.partner-cta p { color: var(--gray-600); margin-bottom: 24px; font-size: 15px; }
diff --git a/frontend/src/pages/Business.jsx b/frontend/src/pages/Business.jsx
new file mode 100644
index 00000000..9170ffbb
--- /dev/null
+++ b/frontend/src/pages/Business.jsx
@@ -0,0 +1,211 @@
+import React, { useState } from 'react';
+import { Routes, Route, NavLink } from 'react-router-dom';
+import './Common.css';
+import './Business.css';
+
+const SUB_NAV = [
+ { path: '/business/reference', label: '구축 레퍼런스' },
+ { path: '/business/partner', label: '파트너' },
+];
+
+function SubNav({ title }) {
+ return (
+ <>
+
+
+ Business
+
{title}
+
+
+
+ >
+ );
+}
+
+/* ── 레퍼런스 데이터 ── */
+const REFS = [
+ { period:'24.12~25.02', client:'엠로', project:'DELL 차세대 CRM 구축', role:'DBA', tech:'Oracle 19C, SQL/PLSQL, Java', category:'금융·제조' },
+ { period:'24.09~25.10', client:'삼성전자', project:'삼성전자 차세대 CRM 구축', role:'DB튜너',tech:'JBOSS, EDB, SQL/PLSQL, Java', category:'대기업' },
+ { period:'24.03~24.06', client:'서울신용보증재단', project:'소상공인 컨설팅시스템 구축', role:'PM', tech:'JSP/Java, Websquare, Spring, Oracle', category:'공공기관' },
+ { period:'23.11~24.02', client:'국민연금관리공단', project:'국민연금 차세대 시스템 구축', role:'AA', tech:'JSP/Java, Nexacro, Spring, CI/CD', category:'공공기관' },
+ { period:'23.08~23.10', client:'헌법재판소', project:'헌법재판소 포털시스템 구축', role:'PM', tech:'Java, Egov, Spring, JEUS', category:'공공기관' },
+ { period:'22.08~23.07', client:'서울신용보증재단', project:'재단 모바일앱 구축', role:'PM', tech:'Java, Nexacro, Spring, EDB', category:'공공기관' },
+ { period:'22.01~22.07', client:'에이텍에이피', project:'통합유지보수관리시스템 개발', role:'PM', tech:'Java, Nexacro, Spring, Tomcat', category:'IT서비스' },
+ { period:'21.10~21.12', client:'헌법재판소', project:'통합보안관제시스템 구축 / DB이관', role:'PM', tech:'Java, Spring, JEUS, Oracle 12c', category:'공공기관' },
+ { period:'20.05~21.09', client:'현대백화점', project:'현대백화점 HKOS 시스템 개발', role:'PM', tech:'Java, Nexacro, Spring, Pro*C', category:'유통·물류' },
+ { period:'20.12~21.04', client:'서울시립대', project:'대학행정정보시스템 성능개선', role:'PL', tech:'Java, Spring, JMeter, JEUS, OZ', category:'교육기관' },
+ { period:'20.07~20.11', client:'에이텍에이피', project:'WMS 공통 프레임워크 구축', role:'PM', tech:'Java, Spring, Nexacro, Oracle', category:'IT서비스' },
+ { period:'20.01~20.06', client:'농협하나로마트', project:'농협 하나로마트 ESL시스템', role:'PM', tech:'C#, Java Spring Batch, REST API', category:'유통·물류' },
+ { period:'19.07~19.12', client:'장보고식자재마트', project:'정산시스템 구축', role:'PM', tech:'Java, Spring, Xplatform, Oracle', category:'유통·물류' },
+ { period:'19.01~19.06', client:'한화갤러리아', project:'갤러리아백화점 PDA 정산시스템', role:'PM', tech:'Java, Spring, Xplatform, Oracle', category:'유통·물류' },
+ { period:'18.07~18.12', client:'이마트', project:'이마트 정산시스템 프로젝트', role:'DA', tech:'Java, Spring, Xplatform, Oracle', category:'유통·물류' },
+ { period:'18.11~18.06', client:'우정사업정보센터', project:'우체국금융 스마트ATM 도입', role:'PMO', tech:'Visual C/C++, TCP/IP 소켓통신', category:'공공기관' },
+ { period:'18.02~18.10', client:'현대백화점', project:'무인POS시스템 구축', role:'PM', tech:'Java, Spring, Xplatform, Oracle', category:'유통·물류' },
+ { period:'17.11~18.01', client:'KOCES', project:'KocesICPos 자동업데이트 런처', role:'개발', tech:'C# .NET, Java/JSP, C/PRO*C', category:'금융·제조' },
+ { period:'15.12~17.10', client:'LG U+', project:'LG U+ VAN 고도화', role:'AA', tech:'Anylink/Tmax, WebLogic, C/PRO*C', category:'통신·금융' },
+ { period:'15.07~15.11', client:'한화S&C', project:'한화그룹 4사 통합 HR시스템', role:'PL', tech:'Java/JSP, Web Service (SOAP), IBSheet',category:'대기업' },
+ { period:'14.11~15.03', client:'참좋은여행', project:'콜센터 어플리케이션 구축', role:'PL', tech:'ASP/.NET, Visual Studio 2012', category:'서비스' },
+ { period:'14.07~14.10', client:'현대캐피탈', project:'현대캐피탈 차세대시스템', role:'PL', tech:'Java/JSP, Web Service, XPlatform', category:'금융·제조' },
+ { period:'14.02~14.06', client:'중소기업청', project:'중소기업 1357 통합콜센터', role:'PL', tech:'Java, Spring, XPlatform, 전자정부FW', category:'공공기관' },
+ { period:'13.08~13.12', client:'삼성전자', project:'삼성전자 품질관리시스템(QWINGS)', role:'PM', tech:'Java, Weblogic, Web Service, MiPlatform',category:'대기업' },
+ { period:'13.03~13.07', client:'대우증권', project:'대우증권 통합인프라시스템', role:'DBA', tech:'Java, Spring, XPlatform, Oracle', category:'통신·금융' },
+ { period:'12.04~13.02', client:'삼성전자서비스', project:'eZone Renewal 프로젝트', role:'PL', tech:'Java, Weblogic, PRO*C, Android', category:'대기업' },
+ { period:'12.01~12.04', client:'농수산식품유통공사',project:'무역통계시스템 구축', role:'DBA', tech:'Java, Spring, Hibernate, 전자정부FW', category:'공공기관' },
+ { period:'11.02~11.12', client:'현대모비스', project:'원가관리시스템 구축', role:'DBA', tech:'Java, Spring, Hibernate, MiPlatform', category:'대기업' },
+ { period:'10.07~11.01', client:'한국전기안전공사', project:'전기안전포털시스템 구축', role:'DBA', tech:'Java, Spring, Hibernate, XPlatform', category:'공공기관' },
+ { period:'09.09~10.04', client:'엽연초생산협동조합',project:'엽연초경작통합시스템 구축', role:'PM', tech:'Java, Struts, i-Batis, Spring', category:'공공기관' },
+ { period:'09.02~09.08', client:'한국전기안전공사', project:'안전점검 고도화', role:'DBA', tech:'Java, Weblogic, 전자정부FW', category:'공공기관' },
+ { period:'08.09~08.12', client:'국민은행', project:'국민은행 차세대 포탈', role:'PL', tech:'Java, AquaLogic, Struts/i-Batis', category:'통신·금융' },
+ { period:'08.06~08.08', client:'한국원자력연료', project:'인사정보(HMS)시스템', role:'DBA', tech:'Java/JSP/JSF, 자체 프레임워크', category:'공공기관' },
+];
+
+const CATEGORIES = ['전체', '공공기관', '대기업', '유통·물류', '통신·금융', '금융·제조', '교육기관', 'IT서비스', '서비스'];
+
+const CAT_COLOR = {
+ '공공기관': '#0051A2', '대기업': '#7c3aed', '유통·물류': '#059669',
+ '통신·금융': '#d97706', '금융·제조': '#dc2626', '교육기관': '#0891b2',
+ 'IT서비스': '#6366f1', '서비스': '#db2777',
+};
+
+function Reference() {
+ const [cat, setCat] = useState('전체');
+ const filtered = cat === '전체' ? REFS : REFS.filter(r => r.category === cat);
+
+ return (
+
+
+
+
+
+
Reference
+
구축 실적
+
2008년부터 현재까지 국내 주요 기관·기업 {REFS.length}개 프로젝트 성공 수행
+
+
+ {/* 통계 */}
+
+ {[
+ { val: `${REFS.length}+`, label: '총 프로젝트', color: 'var(--primary)' },
+ { val: '20+', label: '년 경력', color: 'var(--accent)' },
+ { val: '15+', label: '공공기관', color: '#7c3aed' },
+ { val: '10+', label: '대기업·금융', color: '#059669' },
+ ].map((s, i) => (
+
+ ))}
+
+
+ {/* 카테고리 필터 */}
+
+ {CATEGORIES.map(c => (
+
+ ))}
+
+
+ {/* 테이블 */}
+
+
+
+
+ | 기간 | 고객사 | 프로젝트명 |
+ 역할 | 주요기술 | 분야 |
+
+
+
+ {filtered.map((r, i) => (
+
+ | {r.period} |
+ {r.client} |
+ {r.project} |
+ {r.role} |
+ {r.tech} |
+
+
+ {r.category}
+
+ |
+
+ ))}
+
+
+
+
+
+
+ );
+}
+
+/* ── 파트너 ── */
+const PARTNERS = [
+ { name: 'Oracle', logo: '🔴', desc: 'Oracle DB 공식 파트너 — Oracle 19c 전문 DBA 인증', tier: 'Gold' },
+ { name: 'Red Hat', logo: '🎩', desc: 'RHEL·OpenShift 파트너 — 리눅스 인프라 구축', tier: 'Silver' },
+ { name: 'JEUS (TmaxSoft)', logo: '⚙️', desc: '국산 WAS JEUS/Tmax 공식 파트너', tier: 'Gold' },
+ { name: 'Tibero', logo: '🗄️', desc: 'Tibero DBMS 공식 파트너 — 공공기관 DB 전환', tier: 'Gold' },
+ { name: 'Samsung SDS', logo: '💼', desc: '삼성SDS 협력사 — 삼성전자 CRM/품질 시스템 공동 수행', tier: 'Partner' },
+ { name: 'Nexacro', logo: '🖥️', desc: '투비소프트 Nexacro 공식 파트너 — UI 개발 전문', tier: 'Silver' },
+ { name: 'OZ Report', logo: '📊', desc: 'OZ e-Form 공식 파트너 — 공공 전자문서 솔루션', tier: 'Silver' },
+ { name: 'Ollama', logo: '🤖', desc: '온프레미스 LLM 파트너 — GUARDiA AI 엔진 공급사', tier: 'Tech' },
+];
+
+const TIER_COLOR = { Gold:'#d97706', Silver:'#6b7280', Partner:'#0051A2', Tech:'#7c3aed' };
+
+function Partner() {
+ return (
+
+
+
+
+
+
Partners
+
기술 파트너
+
최고의 기술 파트너와 함께 최선의 솔루션을 제공합니다
+
+
+ {PARTNERS.map((p, i) => (
+
+
{p.logo}
+
+ {p.tier} Partner
+
+
{p.name}
+
{p.desc}
+
+ ))}
+
+
+ {/* 협력 안내 */}
+
+
파트너십 문의
+
지오정보기술과 기술 파트너십을 맺고 싶으신 기업은 아래로 연락 주십시오.
+
+ 파트너십 제안하기
+
+
+
+
+
+ );
+}
+
+export default function Business() {
+ return (
+
+ } />
+ } />
+ } />
+
+ );
+}
diff --git a/frontend/src/pages/Common.css b/frontend/src/pages/Common.css
new file mode 100644
index 00000000..f6e49bd6
--- /dev/null
+++ b/frontend/src/pages/Common.css
@@ -0,0 +1,7 @@
+.inner-page { padding-top: var(--header-h); }
+.page-hero {
+ background: linear-gradient(135deg, var(--secondary), var(--primary-dark));
+ padding: 60px 0; color: #fff;
+}
+.page-hero-title { font-size: 40px; font-weight: 900; margin: 8px 0 12px; }
+.page-hero p { color: rgba(255,255,255,.75); font-size: 16px; }
diff --git a/frontend/src/pages/Company.css b/frontend/src/pages/Company.css
new file mode 100644
index 00000000..b94e0f54
--- /dev/null
+++ b/frontend/src/pages/Company.css
@@ -0,0 +1,133 @@
+/* ── 서브 네비 ── */
+.sub-nav { background: var(--white); border-bottom: 1px solid var(--gray-200); }
+.sub-nav .container { display: flex; gap: 0; overflow-x: auto; }
+.sub-nav-item {
+ padding: 14px 22px; font-size: 14px; font-weight: 500; color: var(--gray-600);
+ white-space: nowrap; border-bottom: 2px solid transparent;
+ transition: all var(--fast) var(--ease);
+}
+.sub-nav-item:hover { color: var(--primary); }
+.sub-nav-item.active { color: var(--primary); border-bottom-color: var(--primary); font-weight: 700; }
+
+/* ── CEO ── */
+.ceo-wrap { display: grid; grid-template-columns: 220px 1fr; gap: 60px; align-items: start; }
+.ceo-photo { text-align: center; }
+.ceo-avatar {
+ width: 160px; height: 160px; border-radius: 50%;
+ background: linear-gradient(135deg, var(--primary), var(--accent));
+ display: flex; align-items: center; justify-content: center;
+ font-size: 28px; font-weight: 900; color: #fff; margin: 0 auto 16px;
+}
+.ceo-name { font-size: 18px; font-weight: 700; color: var(--gray-800); }
+.ceo-sign { font-size: 13px; color: var(--gray-500); margin-top: 4px; }
+.ceo-text h2 { font-size: 26px; font-weight: 900; color: var(--gray-900); line-height: 1.4; }
+.ceo-para { font-size: 15px; line-height: 1.9; color: var(--gray-700); margin-bottom: 18px; }
+
+/* ── 연혁 ── */
+.timeline { position: relative; padding-left: 0; }
+.timeline-row {
+ display: grid; grid-template-columns: 120px 24px 1fr;
+ gap: 0 24px; margin-bottom: 40px; align-items: start;
+}
+.timeline-year {
+ font-size: 22px; font-weight: 900; color: var(--primary);
+ text-align: right; padding-top: 2px; line-height: 1.2;
+}
+.timeline-dot {
+ width: 16px; height: 16px; border-radius: 50%;
+ background: var(--primary); border: 3px solid var(--primary-light);
+ margin-top: 4px; position: relative; flex-shrink: 0;
+}
+.timeline-dot::after {
+ content: ''; position: absolute; top: 100%; left: 50%;
+ transform: translateX(-50%); width: 2px;
+ height: calc(100% + 40px); background: var(--gray-200);
+}
+.timeline-row:last-child .timeline-dot::after { display: none; }
+.timeline-content { padding-bottom: 8px; }
+.timeline-item {
+ display: flex; gap: 10px; font-size: 15px; color: var(--gray-700);
+ margin-bottom: 8px; line-height: 1.6; align-items: flex-start;
+}
+.timeline-bullet {
+ width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
+ flex-shrink: 0; margin-top: 7px;
+}
+
+/* ── 조직도 ── */
+.org-chart { text-align: center; }
+.org-top { display: flex; justify-content: center; margin-bottom: 0; }
+.org-box {
+ padding: 14px 28px; border-radius: 10px; font-weight: 700; font-size: 15px;
+ display: inline-flex; align-items: center; justify-content: center;
+}
+.org-box.ceo { background: var(--secondary); color: #fff; min-width: 160px; font-size: 17px; }
+.org-box.dept {
+ background: var(--white); border: 2px solid; min-width: 140px;
+ font-size: 14px;
+}
+.org-box.team {
+ background: var(--gray-50); border: 1px solid var(--gray-200);
+ color: var(--gray-700); font-size: 13px; padding: 10px 16px;
+ margin-bottom: 8px; min-width: 120px; font-weight: 500;
+}
+.org-line-v { width: 2px; height: 40px; background: var(--gray-300); margin: 0 auto; }
+.org-line-v-short { width: 2px; height: 24px; background: var(--gray-300); margin: 0 auto; }
+.org-depts { display: flex; justify-content: center; gap: 32px; position: relative; }
+.org-dept-col { display: flex; flex-direction: column; align-items: center; }
+.org-teams { display: flex; flex-direction: column; align-items: center; margin-top: 12px; }
+.org-line-h { width: 100%; height: 2px; background: var(--gray-300); }
+
+/* ── CI ── */
+.ci-section { margin-bottom: 56px; }
+.ci-title { font-size: 20px; font-weight: 800; color: var(--gray-900); margin-bottom: 24px; padding-bottom: 12px; border-bottom: 2px solid var(--gray-200); }
+.ci-logo-wrap { display: flex; gap: 24px; flex-wrap: wrap; }
+.ci-logo-box {
+ flex: 1; min-width: 200px; padding: 48px 32px; border-radius: 12px;
+ display: flex; flex-direction: column; align-items: center; gap: 16px;
+}
+.ci-logo-box.dark { background: var(--secondary); }
+.ci-logo-box.light { background: var(--gray-50); border: 1px solid var(--gray-200); }
+.ci-logo-text-wrap { display: flex; flex-direction: column; align-items: center; gap: 4px; }
+.ci-logo-mark { font-size: 32px; font-weight: 900; color: var(--accent); letter-spacing: 4px; }
+.ci-logo-sub { font-size: 14px; font-weight: 600; color: rgba(255,255,255,.7); letter-spacing: 2px; }
+.ci-logo-label { font-size: 12px; color: rgba(255,255,255,.5); }
+.ci-colors { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
+.ci-color-card { border: 1px solid var(--gray-200); border-radius: 10px; overflow: hidden; }
+.ci-color-swatch { height: 100px; }
+.ci-color-info { padding: 16px; display: flex; flex-direction: column; gap: 4px; }
+.ci-color-info strong { font-size: 14px; font-weight: 700; }
+.ci-hex { font-size: 12px; font-family: monospace; color: var(--primary); }
+.ci-cmyk { font-size: 11px; color: var(--gray-500); }
+.ci-usage { font-size: 12px; color: var(--gray-600); }
+.ci-slogan-wrap { background: linear-gradient(135deg, var(--secondary), var(--primary-dark)); border-radius: 16px; padding: 56px; text-align: center; }
+.ci-slogan-main { font-size: 28px; font-weight: 900; color: #fff; margin-bottom: 12px; }
+.ci-slogan-sub { font-size: 16px; color: rgba(255,255,255,.7); }
+
+/* ── 오시는 길 ── */
+.map-wrap { margin-bottom: 32px; }
+.map-placeholder {
+ height: 320px; background: var(--gray-100); border-radius: 16px;
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
+ border: 1px solid var(--gray-200); color: var(--gray-600); font-size: 16px; font-weight: 600;
+}
+.map-pin { font-size: 48px; margin-bottom: 12px; }
+.location-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
+.loc-office-title { font-size: 18px; font-weight: 700; margin-bottom: 20px; color: var(--gray-900); }
+.loc-table { width: 100%; border-collapse: collapse; }
+.loc-table th { width: 90px; text-align: left; font-size: 13px; color: var(--gray-500); font-weight: 600; padding: 10px 0; vertical-align: top; }
+.loc-table td { font-size: 14px; color: var(--gray-700); padding: 10px 0; line-height: 1.6; border-bottom: 1px solid var(--gray-100); }
+.transport-list { display: flex; flex-direction: column; gap: 20px; }
+.transport-header { display: flex; gap: 8px; align-items: center; margin-bottom: 6px; }
+.transport-header strong { font-size: 14px; font-weight: 700; color: var(--primary); }
+.transport-item p { font-size: 13px; color: var(--gray-600); padding-left: 24px; line-height: 1.7; }
+
+/* ── 반응형 ── */
+@media (max-width: 768px) {
+ .ceo-wrap { grid-template-columns: 1fr; }
+ .timeline-row { grid-template-columns: 80px 20px 1fr; gap: 0 16px; }
+ .timeline-year { font-size: 16px; }
+ .org-depts { flex-direction: column; align-items: center; gap: 24px; }
+ .ci-colors { grid-template-columns: repeat(2, 1fr); }
+ .location-grid { grid-template-columns: 1fr; }
+}
diff --git a/frontend/src/pages/Company.jsx b/frontend/src/pages/Company.jsx
new file mode 100644
index 00000000..5e30cd5e
--- /dev/null
+++ b/frontend/src/pages/Company.jsx
@@ -0,0 +1,436 @@
+import React from 'react';
+import { Routes, Route, NavLink, useNavigate } from 'react-router-dom';
+import './Common.css';
+import './Company.css';
+
+/* ── 서브 네비 ── */
+const SUB_NAV = [
+ { path: '/company/greeting', label: 'CEO 인사말' },
+ { path: '/company/history', label: '연혁' },
+ { path: '/company/organization', label: '조직도' },
+ { path: '/company/ci', label: 'CI 소개' },
+ { path: '/company/location', label: '오시는 길' },
+];
+
+function SubNav({ title }) {
+ return (
+ <>
+
+
+ Company
+
{title}
+
+
+
+ >
+ );
+}
+
+/* ── CEO 인사말 ── */
+function Greeting() {
+ return (
+
+
+
+
+
+
+
+ CEO
+
+
대표이사
+
(주)지오정보기술
+
+
+
안녕하십니까,
(주)지오정보기술 대표이사입니다.
+
+ {[
+ '저희 (주)지오정보기술은 2000년 창립 이래 20년 이상 공공기관 및 대기업 IT 전문 서비스 기업으로 성장해 왔습니다. 삼성전자, 현대백화점, 국민연금, 헌법재판소 등 국내 주요 기관·기업의 핵심 시스템을 성공적으로 구축·운영한 풍부한 경험을 보유하고 있습니다.',
+ '최근에는 GUARDiA ITSM 플랫폼을 통해 "AI 기반 인프라 자율 운영"이라는 새로운 패러다임을 제시하고 있습니다. 메신저 한 줄 명령으로 1,000개 이상의 관공서 레거시 인프라를 자동화하는 혁신적인 솔루션으로, 대상 서버에 별도 소프트웨어 설치 없이 표준 SSH/SFTP 프로토콜만으로 운영 자동화를 실현합니다.',
+ '앞으로도 고객의 성공이 곧 저희의 성공이라는 신념 아래, 최고의 기술력과 서비스로 보답하겠습니다. 언제나 여러분 곁에서 디지털 혁신의 파트너가 되겠습니다.',
+ '감사합니다.',
+ ].map((p, i) => (
+
{p}
+ ))}
+
+
+
+ {/* 핵심 가치 */}
+
+
+ Core Values
+
핵심 가치
+
+
+ {[
+ { icon: '🎯', title: '고객 중심', desc: '고객의 성공을 최우선으로 생각하며 최적의 솔루션을 제공합니다' },
+ { icon: '🚀', title: '기술 혁신', desc: 'AI·클라우드 최신 기술로 고객의 디지털 전환을 선도합니다' },
+ { icon: '🤝', title: '신뢰와 책임', desc: '20년 이상 축적된 신뢰로 책임감 있는 서비스를 제공합니다' },
+ { icon: '🌱', title: '지속 성장', desc: '구성원과 고객이 함께 성장하는 지속 가능한 파트너십을 추구합니다' },
+ ].map((v, i) => (
+
+
{v.icon}
+
{v.title}
+
{v.desc}
+
+ ))}
+
+
+
+
+
+ );
+}
+
+/* ── 연혁 ── */
+const HISTORY = [
+ {
+ year: '2026', items: [
+ 'GUARDiA ITSM v2.0 출시 — AI ChatOps 오케스트레이션 플랫폼',
+ 'GS인증 1등급 신청 준비 완료 (TTA 심사 예정)',
+ '공공기관 1,000개 이상 멀티테넌트 지원 목표 달성',
+ ]
+ },
+ {
+ year: '2025', items: [
+ '삼성전자 차세대 CRM 구축 (DB Migration / DA / 튜닝)',
+ 'GUARDiA ITSM v1.0 베타 서비스 개시',
+ 'AI 기반 인프라 자동화 특허 출원',
+ ]
+ },
+ {
+ year: '2024', items: [
+ 'DELL 차세대 CRM 구축 — DBA 역할 수행 (엠로)',
+ '소상공인컨설팅시스템 구축 (서울신용보증재단, PM)',
+ '국민연금 차세대 시스템 구축 (AA)',
+ ]
+ },
+ {
+ year: '2023', items: [
+ '헌법재판소 포털시스템 구축 (PM)',
+ '서울신용보증재단 모바일앱 구축 완료 (PM)',
+ ]
+ },
+ {
+ year: '2022', items: [
+ '에이텍에이피 통합유지보수관리시스템 개발 (PM)',
+ '헌법재판소 통합보안관제시스템 구축 (PM)',
+ ]
+ },
+ {
+ year: '2020–2021', items: [
+ '현대백화점 HKOS 시스템 개발/구축 (PM)',
+ '서울시립대 대학행정정보시스템 성능 개선 (PL)',
+ '농협 하나로마트 ESL 시스템 구축 (PM)',
+ ]
+ },
+ {
+ year: '2018–2019', items: [
+ '이마트 정산시스템 프로젝트 (DA)',
+ '우체국금융 스마트ATM 도입 (PMO)',
+ '현대백화점 무인POS시스템 구축 (PM)',
+ '갤러리아백화점 PDA 정산시스템 (PM)',
+ ]
+ },
+ {
+ year: '2015–2017', items: [
+ 'LG U+ VAN 고도화 — 승인시스템 개발 FEP/AP/BEP (AA)',
+ '한화그룹 4사 통합 HR시스템 구축 (PL)',
+ '참좋은여행 콜센터 어플리케이션 구축 (PL)',
+ ]
+ },
+ {
+ year: '2013–2014', items: [
+ '삼성전자 품질관리시스템(QWINGS) 구축 (PM)',
+ '대우증권 통합인프라시스템 (DBA)',
+ '현대캐피탈 차세대시스템 (PL)',
+ '중소기업 1357 통합콜센터 구축 (PL)',
+ ]
+ },
+ {
+ year: '2010–2012', items: [
+ '삼성전자서비스 eZone 갱신 (PL)',
+ '현대모비스 원가관리시스템 (DBA)',
+ '한국전기안전공사 전기안전포털시스템 (DBA)',
+ ]
+ },
+ {
+ year: '2008–2009', items: [
+ '국민은행 차세대 포탈 구축 (PL)',
+ '한국원자력연료 인사정보(HMS)시스템 (DBA)',
+ '한국전기안전공사 안전점검 고도화 (DBA)',
+ ]
+ },
+ {
+ year: '2000', items: [
+ '(주)지오정보기술 창립',
+ '공공기관 IT 인프라 서비스 개시',
+ ]
+ },
+];
+
+function History() {
+ return (
+
+
+
+
+
+
History
+
20년+ 성장의 역사
+
2000년 창립 이래 국내 주요 기관·기업과 함께 성장해 왔습니다
+
+
+ {HISTORY.map((h, i) => (
+
+
{h.year}
+
+
+ {h.items.map((item, j) => (
+
+
+ {item}
+
+ ))}
+
+
+ ))}
+
+
+
+
+ );
+}
+
+/* ── 조직도 ── */
+function Organization() {
+ return (
+
+
+
+
+
+ Organization
+
조직 구성
+
+
+ {/* 대표이사 */}
+
+
+ {/* 본부 */}
+
+ {[
+ {
+ name: '개발본부', color: 'var(--primary)',
+ teams: ['AI개발팀', '플랫폼개발팀', '프론트엔드팀']
+ },
+ {
+ name: '기술본부', color: 'var(--accent)',
+ teams: ['인프라팀', 'DBA팀', '보안팀']
+ },
+ {
+ name: 'PM본부', color: '#7c3aed',
+ teams: ['SI사업팀', 'SM운영팀', 'PMO팀']
+ },
+ {
+ name: '경영지원본부', color: '#059669',
+ teams: ['경영기획팀', '영업팀', '인사·총무팀']
+ },
+ ].map((dept, i) => (
+
+
+
+
+ {dept.name}
+
+
+ {dept.teams.map((t, j) => (
+
{t}
+ ))}
+
+
+ ))}
+
+
+
+ {/* 인원 현황 */}
+
+ {[
+ { label: '전체 임직원', value: '50+', unit: '명' },
+ { label: '개발 인력', value: '70', unit: '%' },
+ { label: '평균 경력', value: '8', unit: '년+' },
+ { label: '국가 공인 자격', value: '30+', unit: '개' },
+ ].map((s, i) => (
+
+
+ {s.value}{s.unit}
+
+
{s.label}
+
+ ))}
+
+
+
+
+ );
+}
+
+/* ── CI 소개 ── */
+function CI() {
+ return (
+
+
+
+
+
+
Corporate Identity
+
브랜드 아이덴티티
+
지오정보기술의 브랜드는 신뢰·혁신·전문성을 상징합니다
+
+
+ {/* 로고 */}
+
+
로고 시스템
+
+
+
+ GEO
+ 정보기술
+
+
다크 배경용
+
+
+
+ GEO
+ 정보기술
+
+
라이트 배경용
+
+
+
+
+ {/* 브랜드 컬러 */}
+
+
브랜드 컬러
+
+ {[
+ { name: 'Primary Blue', hex: '#0051A2', cmyk: 'C100 M50 Y0 K37', usage: '주요 UI·버튼·강조' },
+ { name: 'Accent Blue', hex: '#00A3E0', cmyk: 'C100 M28 Y0 K12', usage: '포인트·링크·아이콘' },
+ { name: 'Dark Navy', hex: '#1A1A2E', cmyk: 'C40 M40 Y0 K82', usage: '헤더·배경·텍스트' },
+ { name: 'Pure White', hex: '#FFFFFF', cmyk: 'C0 M0 Y0 K0', usage: '배경·반전 텍스트' },
+ ].map((c, i) => (
+
+
+
+ {c.name}
+ {c.hex}
+ {c.cmyk}
+ {c.usage}
+
+
+ ))}
+
+
+
+ {/* 슬로건 */}
+
+
브랜드 슬로건
+
+
"공공기관 IT, 지오정보기술과 함께"
+
20년 신뢰의 기술력으로 AI 인프라 혁신을 이끕니다
+
+
+
+
+
+ );
+}
+
+/* ── 오시는 길 ── */
+function Location() {
+ return (
+
+
+
+
+
+ Location
+
오시는 길
+
+
+ {/* 지도 영역 */}
+
+
+
📍
+
(주)지오정보기술
+
+ 서울특별시 강서구 양천로 570
+
+
+
+
+
+ {/* 본사 */}
+
+
📍 본사
+
+
+ | 주소 | 서울특별시 강서구 양천로 570 NH서울타워 5층 |
+ | 대표전화 | 02-784-9271 |
+ | 팩스 | 02-784-9272 |
+ | 이메일 | info@zioinfo.co.kr |
+ | 운영시간 | 평일 09:00 ~ 18:00 (점심 12:00~13:00) |
+
+
+
+
+ {/* 교통 안내 */}
+
+
🚇 교통 안내
+
+ {[
+ { type: '지하철', icon: '🚇', lines: ['5호선 발산역 1번 출구 도보 5분', '9호선 마곡나루역 3번 출구 도보 10분'] },
+ { type: '버스', icon: '🚌', lines: ['강서05, 강서06 — 강서구청 정류장 하차', '60, 62, 604 — 발산역 하차'] },
+ { type: '자가용', icon: '🚗', lines: ['올림픽대로 → 강서IC → 양천로 방면', '주차 가능 (방문 전 사전 연락 권장)'] },
+ ].map((t, i) => (
+
+
+ {t.icon}
+ {t.type}
+
+ {t.lines.map((l, j) =>
{l}
)}
+
+ ))}
+
+
+
+
+
+
+ );
+}
+
+/* ── 라우터 ── */
+export default function Company() {
+ return (
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ );
+}
diff --git a/frontend/src/pages/Contact.css b/frontend/src/pages/Contact.css
new file mode 100644
index 00000000..224903d1
--- /dev/null
+++ b/frontend/src/pages/Contact.css
@@ -0,0 +1,42 @@
+.contact-page { padding-top: var(--header-h); }
+.page-hero {
+ background: linear-gradient(135deg, var(--secondary), var(--primary-dark));
+ padding: 60px 0;
+ color: #fff;
+}
+.page-hero-title { font-size: 40px; font-weight: 900; margin: 8px 0 12px; }
+.page-hero p { color: rgba(255,255,255,.75); font-size: 16px; }
+
+.contact-grid { display: grid; grid-template-columns: 340px 1fr; gap: 48px; align-items: start; }
+.contact-info h2 { font-size: 22px; font-weight: 700; margin-bottom: 28px; }
+.info-item { display: flex; gap: 16px; margin-bottom: 24px; align-items: flex-start; }
+.info-icon { font-size: 24px; }
+.info-item strong { display: block; font-size: 13px; font-weight: 700; color: var(--gray-500); margin-bottom: 2px; }
+.info-item p { font-size: 15px; color: var(--gray-800); }
+
+.contact-form { padding: 36px; }
+.contact-form h2 { font-size: 22px; font-weight: 700; margin-bottom: 24px; }
+.form-alert { padding: 12px 16px; border-radius: var(--radius-sm); font-size: 14px; margin-bottom: 16px; }
+.form-alert.success { background: #d1fae5; color: #065f46; }
+.form-alert.error { background: #fee2e2; color: #991b1b; }
+.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
+.form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
+.form-group label { font-size: 13px; font-weight: 600; color: var(--gray-700); }
+.required { color: var(--danger); }
+.form-group input, .form-group select, .form-group textarea {
+ padding: 10px 14px;
+ border: 1px solid var(--gray-200);
+ border-radius: var(--radius-sm);
+ font-size: 14px; font-family: inherit;
+ transition: border-color var(--fast);
+ outline: none;
+}
+.form-group input:focus, .form-group select:focus, .form-group textarea:focus {
+ border-color: var(--primary);
+ box-shadow: 0 0 0 3px rgba(0,81,162,.1);
+}
+.privacy-agree { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--gray-600); margin-bottom: 20px; cursor: pointer; }
+.privacy-agree a { color: var(--primary); }
+
+@media (max-width: 1024px) { .contact-grid { grid-template-columns: 1fr; } }
+@media (max-width: 768px) { .form-row { grid-template-columns: 1fr; } }
diff --git a/frontend/src/pages/Contact.jsx b/frontend/src/pages/Contact.jsx
new file mode 100644
index 00000000..4096328c
--- /dev/null
+++ b/frontend/src/pages/Contact.jsx
@@ -0,0 +1,126 @@
+import React, { useState } from 'react';
+import axios from 'axios';
+import './Contact.css';
+
+export default function Contact() {
+ const [form, setForm] = useState({
+ name:'', email:'', phone:'', category:'제품문의', subject:'', content:'', agreePrivacy: false
+ });
+ const [status, setStatus] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ const handleChange = e => {
+ const { name, value, type, checked } = e.target;
+ setForm(f => ({ ...f, [name]: type === 'checkbox' ? checked : value }));
+ };
+
+ const handleSubmit = async e => {
+ e.preventDefault();
+ if (!form.agreePrivacy) { setStatus({ type: 'error', msg: '개인정보 수집·이용에 동의해주세요.' }); return; }
+ setLoading(true);
+ try {
+ await axios.post('/api/inquiry', form);
+ setStatus({ type: 'success', msg: '문의가 접수되었습니다. 빠른 시일 내에 연락드리겠습니다.' });
+ setForm({ name:'', email:'', phone:'', category:'제품문의', subject:'', content:'', agreePrivacy: false });
+ } catch {
+ setStatus({ type: 'error', msg: '문의 접수 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.' });
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
Contact Us
+
문의하기
+
GUARDiA ITSM 도입 문의 및 제품 상담을 받아드립니다.
+
+
+
+
+
+
+ {/* 연락처 정보 */}
+
+
연락처 정보
+ {[
+ { icon: '📞', label: '대표전화', value: '02-000-0000' },
+ { icon: '✉️', label: '이메일', value: 'info@zioinfo.co.kr' },
+ { icon: '🕐', label: '운영시간', value: '평일 09:00 ~ 18:00' },
+ { icon: '📍', label: '주소', value: '서울특별시' },
+ ].map((c,i) => (
+
+
{c.icon}
+
+
{c.label}
+
{c.value}
+
+
+ ))}
+
+
+ {/* 문의 폼 */}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/GuardiaDetail.css b/frontend/src/pages/GuardiaDetail.css
new file mode 100644
index 00000000..02a79751
--- /dev/null
+++ b/frontend/src/pages/GuardiaDetail.css
@@ -0,0 +1,281 @@
+/* ─── GUARDiA 상세 페이지 ────────────────────────────────── */
+
+.guardia-page { padding-top: var(--header-h); }
+
+/* ── 히어로 ── */
+.gd-hero {
+ position: relative;
+ background: linear-gradient(135deg, #0a0f24 0%, #001f5c 50%, #0051A2 100%);
+ padding: 80px 0 60px;
+ overflow: hidden;
+}
+.gd-hero::before {
+ content: '';
+ position: absolute; inset: 0;
+ background: radial-gradient(ellipse at 70% 50%, rgba(0,163,224,.15) 0%, transparent 70%);
+}
+.gd-hero-overlay { position: absolute; inset: 0; background: rgba(0,0,0,.2); }
+.gd-hero-inner {
+ position: relative; z-index: 2;
+ display: grid; grid-template-columns: 1fr auto;
+ gap: 60px; align-items: center;
+}
+.gd-hero-title {
+ font-size: clamp(40px, 5vw, 64px);
+ font-weight: 900; color: #fff;
+ margin: 12px 0 16px;
+}
+.gd-hero-title span { color: var(--accent); }
+.gd-hero-sub {
+ font-size: 18px; color: rgba(255,255,255,.8);
+ line-height: 1.7; margin-bottom: 32px;
+}
+.gd-hero-sub strong { color: #fff; }
+.gd-hero-actions { display: flex; gap: 16px; flex-wrap: wrap; }
+.gd-hero-stats {
+ display: grid; grid-template-columns: repeat(2,1fr);
+ gap: 16px; flex-shrink: 0;
+}
+.gd-stat {
+ background: rgba(255,255,255,.08);
+ border: 1px solid rgba(255,255,255,.15);
+ border-radius: var(--radius);
+ padding: 20px 24px; text-align: center;
+}
+.gd-stat-val { font-size: 28px; font-weight: 900; color: var(--accent); }
+.gd-stat-lab { font-size: 12px; color: rgba(255,255,255,.7); margin-top: 4px; }
+
+/* ── 탭 바 ── */
+.gd-tabs-bar {
+ background: var(--white);
+ border-bottom: 2px solid var(--gray-200);
+ position: sticky; top: var(--header-h); z-index: 100;
+}
+.gd-tabs { display: flex; gap: 0; overflow-x: auto; }
+.gd-tab {
+ padding: 16px 24px;
+ font-size: 14px; font-weight: 600;
+ color: var(--gray-600);
+ border-bottom: 3px solid transparent;
+ margin-bottom: -2px;
+ white-space: nowrap;
+ transition: all var(--fast);
+}
+.gd-tab:hover { color: var(--primary); }
+.gd-tab.active { color: var(--primary); border-bottom-color: var(--primary); }
+
+/* ── 핵심 기능 ── */
+.gd-features-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 20px;
+}
+.gd-feature-card {
+ padding: 28px;
+ display: flex; flex-direction: column; gap: 12px;
+}
+.gd-feature-icon { font-size: 36px; }
+.gd-feature-card h3 { font-size: 16px; font-weight: 700; color: var(--gray-900); }
+.gd-feature-card p { font-size: 14px; color: var(--gray-600); line-height: 1.7; }
+
+/* ── 스크린샷 갤러리 ── */
+.gd-screenshots {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 20px;
+ margin-top: 40px;
+}
+.screenshot-card {
+ border-radius: var(--radius);
+ overflow: hidden;
+ box-shadow: var(--shadow);
+ border: 1px solid var(--gray-200);
+ transition: all var(--mid) var(--ease);
+}
+.screenshot-card:hover {
+ transform: translateY(-4px);
+ box-shadow: var(--shadow-lg);
+}
+.screenshot-img {
+ width: 100%; aspect-ratio: 16/9;
+ object-fit: cover;
+ display: block;
+}
+.screenshot-placeholder {
+ width: 100%; aspect-ratio: 16/9;
+ background: linear-gradient(135deg, #1e2333, #2d3748);
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
+ gap: 8px; color: var(--gray-400); font-size: 13px;
+}
+.screenshot-placeholder .icon { font-size: 32px; }
+.screenshot-caption {
+ padding: 12px 16px;
+ font-size: 13px; font-weight: 600; color: var(--gray-700);
+ background: var(--white);
+}
+
+/* ── 메신저 ── */
+.messenger-platforms {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 16px;
+ margin-bottom: 48px;
+}
+.messenger-platform {
+ border-radius: var(--radius);
+ padding: 20px;
+ display: flex; align-items: center; gap: 14px;
+}
+.platform-icon { font-size: 28px; flex-shrink: 0; }
+.messenger-platform strong { display: block; font-size: 15px; font-weight: 700; }
+.messenger-platform p { font-size: 12px; margin-top: 2px; }
+
+.cmd-catalog {
+ background: var(--gray-900);
+ border-radius: var(--radius-lg);
+ padding: 32px;
+ margin-bottom: 48px;
+}
+.cmd-catalog-title {
+ color: #fff; font-size: 18px; font-weight: 700;
+ margin-bottom: 24px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid rgba(255,255,255,.1);
+}
+.cmd-group { margin-bottom: 20px; }
+.cmd-group-title {
+ font-size: 11px; font-weight: 700;
+ letter-spacing: 1.5px; text-transform: uppercase;
+ color: var(--accent); margin-bottom: 10px;
+}
+.cmd-list { display: flex; flex-direction: column; gap: 6px; }
+.cmd-item { display: flex; align-items: baseline; gap: 16px; }
+.cmd-code {
+ font-family: 'Courier New', monospace;
+ font-size: 13px; color: #a5f3fc;
+ background: rgba(165,243,252,.08);
+ padding: 3px 8px; border-radius: 4px;
+ white-space: nowrap; flex-shrink: 0;
+ min-width: 220px;
+}
+.cmd-desc { font-size: 13px; color: rgba(255,255,255,.65); }
+
+/* 메신저 데모 */
+.messenger-demo { }
+.demo-title { font-size: 22px; font-weight: 700; color: var(--gray-900); margin-bottom: 24px; }
+.demo-scenario { display: flex; flex-direction: column; gap: 32px; }
+.demo-step { display: flex; gap: 20px; }
+.step-num {
+ width: 40px; height: 40px; flex-shrink: 0;
+ background: var(--primary); color: #fff;
+ border-radius: 50%;
+ display: flex; align-items: center; justify-content: center;
+ font-weight: 700;
+}
+.step-content { flex: 1; }
+.step-content strong { font-size: 16px; color: var(--gray-900); display: block; margin-bottom: 6px; }
+.step-content > p { font-size: 14px; color: var(--gray-600); margin-bottom: 10px; }
+.chat-bubble {
+ padding: 12px 16px;
+ border-radius: 12px;
+ font-size: 13px; line-height: 1.6;
+ margin-top: 6px;
+ max-width: 520px;
+}
+.chat-bubble.bot { background: #1e2333; color: rgba(255,255,255,.9); }
+.chat-bubble.user { background: var(--primary-light); color: var(--primary); font-weight: 600; }
+
+/* ── 에디션 ── */
+.gd-editions-grid {
+ display: grid; grid-template-columns: repeat(3,1fr);
+ gap: 24px;
+}
+.gd-edition-card {
+ border: 2px solid var(--gray-200);
+ border-radius: var(--radius-lg);
+ padding: 36px 28px;
+ position: relative;
+ display: flex; flex-direction: column; gap: 16px;
+ transition: all var(--mid) var(--ease);
+}
+.gd-edition-card:hover { box-shadow: var(--shadow-lg); }
+.gd-edition-card.highlight {
+ border-color: var(--primary);
+ box-shadow: 0 0 0 4px rgba(0,81,162,.1);
+}
+.edition-recommend {
+ position: absolute; top: -12px; left: 50%;
+ transform: translateX(-50%);
+ background: var(--primary); color: #fff;
+ font-size: 12px; font-weight: 700;
+ padding: 3px 14px; border-radius: 20px;
+}
+.edition-header { display: flex; align-items: center; justify-content: space-between; }
+.edition-header h3 { font-size: 22px; font-weight: 900; color: var(--ed-color, var(--gray-900)); }
+.edition-badge {
+ background: color-mix(in srgb, var(--ed-color, var(--primary)) 15%, transparent);
+ color: var(--ed-color, var(--primary));
+ padding: 3px 10px; border-radius: 12px;
+ font-size: 12px; font-weight: 700;
+}
+.edition-target { font-size: 13px; color: var(--gray-500); }
+.edition-features { display: flex; flex-direction: column; gap: 10px; flex: 1; }
+.edition-features li { display: flex; align-items: flex-start; gap: 10px; font-size: 14px; color: var(--gray-700); }
+.check { color: var(--ed-color, var(--primary)); font-weight: 700; flex-shrink: 0; }
+.edition-cta {
+ background: var(--ed-color, var(--primary));
+ color: #fff; text-align: center;
+ padding: 12px; border-radius: var(--radius);
+ font-weight: 600; width: 100%;
+ justify-content: center;
+}
+
+/* ── 기술 ── */
+.gd-tech-grid {
+ display: grid; grid-template-columns: repeat(3,1fr); gap: 20px;
+}
+.gd-tech-card { padding: 28px; }
+.tech-category { font-size: 16px; font-weight: 700; color: var(--primary); margin-bottom: 16px; }
+.tech-items { display: flex; flex-direction: column; gap: 10px; }
+.tech-items li {
+ font-size: 14px; color: var(--gray-700);
+ padding: 8px 12px;
+ background: var(--gray-50); border-radius: var(--radius-sm);
+ border-left: 3px solid var(--accent);
+}
+
+/* ── 도입 사례 ── */
+.gd-usecases { display: grid; grid-template-columns: repeat(3,1fr); gap: 20px; }
+.usecase-card { padding: 28px; display: flex; flex-direction: column; gap: 12px; }
+.usecase-card h3 { font-size: 17px; font-weight: 700; color: var(--gray-900); }
+.usecase-card p { font-size: 14px; color: var(--gray-600); line-height: 1.7; }
+
+/* ── CTA ── */
+.gd-cta {
+ background: linear-gradient(135deg, var(--primary-dark), #0a0f24);
+ padding: 80px 0; text-align: center;
+}
+.gd-cta h2 { font-size: 32px; font-weight: 800; color: #fff; margin-bottom: 12px; }
+.gd-cta p { font-size: 17px; color: rgba(255,255,255,.75); margin-bottom: 32px; }
+.gd-cta-actions { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; }
+
+/* ── 반응형 ── */
+@media (max-width: 1024px) {
+ .gd-hero-inner { grid-template-columns: 1fr; }
+ .gd-hero-stats { grid-template-columns: repeat(4,1fr); }
+ .gd-features-grid { grid-template-columns: repeat(2,1fr); }
+ .gd-screenshots { grid-template-columns: repeat(2,1fr); }
+ .messenger-platforms { grid-template-columns: repeat(2,1fr); }
+ .gd-editions-grid { grid-template-columns: 1fr; }
+ .gd-tech-grid { grid-template-columns: repeat(2,1fr); }
+ .gd-usecases { grid-template-columns: 1fr; }
+}
+@media (max-width: 768px) {
+ .gd-features-grid { grid-template-columns: 1fr; }
+ .gd-screenshots { grid-template-columns: 1fr; }
+ .messenger-platforms { grid-template-columns: 1fr; }
+ .gd-hero-stats { grid-template-columns: repeat(2,1fr); }
+ .gd-tech-grid { grid-template-columns: 1fr; }
+ .cmd-item { flex-direction: column; gap: 4px; }
+ .cmd-code { min-width: unset; }
+}
diff --git a/frontend/src/pages/GuardiaDetail.jsx b/frontend/src/pages/GuardiaDetail.jsx
new file mode 100644
index 00000000..7eecf6a8
--- /dev/null
+++ b/frontend/src/pages/GuardiaDetail.jsx
@@ -0,0 +1,405 @@
+import React, { useState } from 'react';
+import { Link } from 'react-router-dom';
+import './GuardiaDetail.css';
+
+const FEATURES = [
+ { icon:'🤖', title:'AI 에이전트 자동화',
+ desc:'Ollama 온프레미스 sLLM 기반. 메신저 한 줄 명령 → 자연어 파싱 → 자동 배포·운영. 외부 API 완전 차단으로 폐쇄망 환경 최적화.' },
+ { icon:'🔧', title:'에이전트리스 아키텍처',
+ desc:'대상 서버에 어떤 소프트웨어도 설치하지 않습니다. 표준 SSH/SFTP 프로토콜만으로 레거시 WAS(Tomcat/JBoss/WebLogic)를 원격 관리.' },
+ { icon:'💬', title:'ChatOps 메신저 통합',
+ desc:'카카오워크, 네이버웍스, 슬랙 등 익숙한 메신저에서 /deploy, /status, /incident 명령으로 인프라를 즉시 제어.' },
+ { icon:'📊', title:'통합 ITSM 대시보드',
+ desc:'SR·인시던트·변경관리·SLA·CMDB·예측 유지보수를 단일 플랫폼에서 관리. 7일 추이 차트와 AI 인사이트 실시간 제공.' },
+ { icon:'🔒', title:'엔터프라이즈 보안',
+ desc:'AES-256-GCM 암호화, MFA/OTP, PAM 특권접근관리, SHA-256 해시체인 불변 감사로그, Zero Trust 지속 인증.' },
+ { icon:'🏗️', title:'PMS 프로젝트 관리',
+ desc:'WBS·산출물·일간/주간/월간 자동 보고서(Excel·PDF·PPT). 이슈·위험 관리, Gitea 연동, Jenkins CI/CD 파이프라인.' },
+ { icon:'🌐', title:'공공기관 필수 준수',
+ desc:'행안부 SW 보안약점 자동 점검, KWCAG 2.1 웹접근성, 개인정보보호법 준수 스캔. 19개 공공기관 체크리스트 내장.' },
+ { icon:'📡', title:'Scouter APM 모니터링',
+ desc:'Java WAS(Tomcat/JBoss) 전문 APM. CPU·Heap·TPS·응답시간 실시간 수집, 이상 탐지 시 자동 인시던트 생성.' },
+];
+
+const EDITIONS = [
+ {
+ name: 'COMMUNITY', badge: '무료',
+ color: '#10B981',
+ target: '소규모 기관·검토용',
+ features: ['기본 SR 관리 (무제한)', 'CMDB 서버 20대', '사용자 10명', '대시보드', '봇 기본 명령어'],
+ cta: '무료 시작',
+ href: '/support/contact?type=community',
+ },
+ {
+ name: 'STANDARD', badge: '권장',
+ color: 'var(--primary)',
+ target: '중형 기관',
+ features: ['전체 ITSM 기능', 'AI 에이전트 자동화', 'LDAP/AD 연동', 'MFA 보안', 'SLA 관리', 'PMS 기본'],
+ cta: '도입 문의',
+ href: '/support/contact?type=standard',
+ highlight: true,
+ },
+ {
+ name: 'ENTERPRISE', badge: '맞춤',
+ color: '#6366F1',
+ target: '대형 관공서·광역기관',
+ features: ['무제한 서버·기관', '취약점 자동 스캔', 'Scouter APM', 'FinOps 비용 분석', 'SIEM 연동', '전담 기술 지원'],
+ cta: '전문가 상담',
+ href: '/support/contact?type=enterprise',
+ },
+];
+
+/* ── 메신저 봇 명령어 목록 ──────────────────────────────── */
+const BOT_COMMANDS = [
+ { cmd: '/sr <제목>', desc: 'SR(서비스요청) 즉시 접수', cat: 'SR 관리' },
+ { cmd: '/status', desc: '시스템 전체 현황 요약', cat: 'SR 관리' },
+ { cmd: '/assign <담당자>', desc: 'SR 담당자 즉시 배정', cat: 'SR 관리' },
+ { cmd: '/approve ', desc: 'SR 즉시 승인', cat: 'SR 관리' },
+ { cmd: '/sla', desc: 'SLA 위반 현황 목록', cat: 'SR 관리' },
+ { cmd: '/incident <제목> [P1~P4]', desc: '인시던트 빠른 등록', cat: '인시던트' },
+ { cmd: '/oncall', desc: '현재 당직자 즉시 조회', cat: '인시던트' },
+ { cmd: '/rca ', desc: 'AI 자동 RCA 근본원인 분석', cat: '인시던트' },
+ { cmd: '/escalate ', desc: '당직자에게 에스컬레이션', cat: '인시던트' },
+ { cmd: '!deploy <세션ID>', desc: 'WAS 배포 실행 (SSH)', cat: '배포 제어' },
+ { cmd: '/rollback <세션ID>', desc: '긴급 롤백', cat: '배포 제어' },
+ { cmd: '!health <서버명>', desc: '서버 헬스체크', cat: '배포 제어' },
+ { cmd: '/pms <프로젝트코드>', desc: '프로젝트 진척 현황', cat: 'PMS' },
+ { cmd: '/report <코드> weekly', desc: '주간 보고서 메신저 발송', cat: 'PMS' },
+ { cmd: '/wbs <코드>', desc: 'WBS 지연 현황', cat: 'PMS' },
+ { cmd: '/scouter <서버명>', desc: 'Scouter APM 실시간 메트릭', cat: '모니터링' },
+ { cmd: '/scan', desc: '시큐어코딩·보안 자동 점검', cat: '보안' },
+ { cmd: '/vuln <서버|IP>', desc: '취약점 스캔', cat: '보안' },
+ { cmd: '/notify <메시지>', desc: '운영팀 전체 공지 발송', cat: '공지' },
+];
+
+const MESSENGER_PLATFORMS = [
+ { name: '카카오워크', icon: '💬', color: '#FAE100', textColor: '#3C1E1E', desc: '결재 버튼 + 봇 명령 완벽 지원' },
+ { name: '네이버웍스', icon: '🟢', color: '#03C75A', textColor: '#fff', desc: 'Flex 메시지 + Rich 결과 표시' },
+ { name: '슬랙', icon: '💜', color: '#611F69', textColor: '#fff', desc: '슬래시 명령 + 블록킷 UI' },
+ { name: '자체 메신저', icon: '🔵', color: '#0051A2', textColor: '#fff', desc: 'GUARDiA 내장 Slack형 메신저' },
+];
+
+const TECH_STACK = [
+ { category: 'Backend', items: ['Python 3.11 / FastAPI', 'SQLAlchemy Async', 'PostgreSQL / SQLite'] },
+ { category: 'AI·LLM', items: ['Ollama (온프레미스)', 'llama3.1:8b / codellama', '외부 API 완전 차단'] },
+ { category: 'Infra', items: ['paramiko SSH/SFTP', '에이전트리스', 'AES-256-GCM 암호화'] },
+ { category: 'Frontend', items: ['React.js / PWA', 'Chart.js 대시보드', 'D3.js 토폴로지'] },
+ { category: 'DevOps', items: ['Jenkins CI/CD', 'Gitea 형상관리', 'Docker / K8s'] },
+ { category: '모니터링', items: ['Scouter APM', 'Prometheus/Grafana', 'ELK/Splunk SIEM'] },
+];
+
+export default function GuardiaDetail() {
+ const [activeTab, setActiveTab] = useState('features');
+
+ return (
+
+
+ {/* ── 히어로 ────────────────────────────────────────── */}
+
+
+
+
+
NEW v2.0
+
+ GUARDiA ITSM
+
+
+ AI 기반 레거시 인프라 자율 운영 플랫폼
+ 메신저 한 줄 명령으로 1,000개 관공서 인프라를 자동화
+
+
+
+
+ {[
+ {val:'1,000+', lab:'관리 기관'},
+ {val:'99.9%', lab:'가용성'},
+ {val:'70%', lab:'운영 비용 절감'},
+ {val:'0개', lab:'대상 서버 추가 설치'},
+ ].map((s,i) => (
+
+ ))}
+
+
+
+
+ {/* ── 탭 메뉴 ──────────────────────────────────────── */}
+
+
+
+ {[
+ {id:'features', label:'핵심 기능'},
+ {id:'messenger', label:'Messenger Bot'},
+ {id:'editions', label:'에디션 비교'},
+ {id:'tech', label:'기술 스택'},
+ {id:'usecase', label:'도입 사례'},
+ ].map(t => (
+
+ ))}
+
+
+
+
+ {/* ── 핵심 기능 ─────────────────────────────────────── */}
+ {activeTab === 'features' && (
+
+
+
+
Core Features
+
GUARDiA가 제공하는
8가지 핵심 기능
+
+
+ {/* 실행 화면 스크린샷 */}
+
+ {[
+ {file:'01_dashboard', caption:'통합 대시보드 — SR·SLA·AI 인사이트'},
+ {file:'02_sr_list', caption:'SR 서비스 요청 — 칸반/목록 뷰'},
+ {file:'03_si_project', caption:'PMS 프로젝트 — WBS·산출물·보고서'},
+ {file:'04_incidents', caption:'인시던트 관리 — AI 자동 RCA'},
+ {file:'05_agents', caption:'AI 에이전트 — Ollama 온프레미스'},
+ {file:'06_license', caption:'라이선스 관리 — 에디션·체험판'},
+ ].map((s,i) => (
+
+

{e.target.style.display='none';e.target.nextSibling.style.display='flex';}}/>
+
+ 🖥️준비 중
+
+
{s.caption}
+
+ ))}
+
+
+
+
핵심 기능 상세
+
+
+ {FEATURES.map((f,i) => (
+
+
{f.icon}
+
{f.title}
+
{f.desc}
+
+ ))}
+
+
+
+ )}
+
+ {/* ── 메신저 봇 상세 소개 ──────────────────────────── */}
+ {activeTab === 'messenger' && (
+
+
+
+
ChatOps Messenger
+
메신저 하나로
모든 인프라를 제어
+
+
+ 익숙한 메신저에서 명령어 하나로 서버 배포·장애 대응·보고서 발송까지.
+ GUARDiA Bot은 25개 명령어로 IT 운영의 모든 순간을 지원합니다.
+
+
+
+ {/* 지원 플랫폼 */}
+
+ {MESSENGER_PLATFORMS.map((p,i) => (
+
+ ))}
+
+
+ {/* 명령어 카탈로그 */}
+
+
25개 봇 명령어 전체 목록
+ {['SR 관리','인시던트','배포 제어','PMS','모니터링','보안','공지'].map(cat => {
+ const cmds = BOT_COMMANDS.filter(c => c.cat === cat);
+ return (
+
+
{cat}
+
+ {cmds.map((c,i) => (
+
+ {c.cmd}
+ {c.desc}
+
+ ))}
+
+
+ );
+ })}
+
+
+ {/* 데모 시나리오 */}
+
+
실제 운영 시나리오
+
+
+
1
+
+
장애 탐지
+
Scouter APM이 서버 CPU 90% 감지 → 자동으로 GUARDiA 운영 채널에 경보 발송
+
🚨 web-01 CPU 90.3% — P2 인시던트 자동 등록: INC-20260530-A1B2C3
+
+
+
+
2
+
+
담당자 즉시 대응
+
메신저에서 RCA 분석 요청
+
/rca INC-20260530-A1B2C3
+
+ 🤖 AI RCA 분석 완료
+ 근본원인: 메모리 누수 (Heap 98%)
+ 재발방지: WAS 재기동 + 힙덤프 분석
+ 신뢰도: 87%
+
+
+
+
+
3
+
+
원격 조치 실행
+
SSH 재기동 명령 실행
+
!sm web-01 tomcat_restart
+
+ ✅ web-01 Tomcat 재기동 완료
+ 소요: 38초 | CPU: 12% | 정상화
+
+
+
+
+
4
+
+
자동 보고
+
인시던트 처리 결과 자동 보고서 발송
+
/notify 22:15 web-01 서버 장애 복구 완료. 원인: 메모리 누수 재발 방지 조치 완료.
+
✅ 운영팀 전체 공지 발송 완료 (ops 채널)
+
+
+
+
+
+
+
+ )}
+
+ {/* ── 에디션 비교 ──────────────────────────────────── */}
+ {activeTab === 'editions' && (
+
+
+
+
Editions
+
기관 규모에 맞는
에디션 선택
+
+
+
+ {EDITIONS.map((e,i) => (
+
+ {e.highlight &&
추천
}
+
+
{e.name}
+ {e.badge}
+
+
{e.target}
+
+ {e.features.map((f,j) => (
+ - ✓{f}
+ ))}
+
+
+ {e.cta}
+
+
+ ))}
+
+
+
+ )}
+
+ {/* ── 기술 스택 ─────────────────────────────────────── */}
+ {activeTab === 'tech' && (
+
+
+
+
Technology
+
검증된
기술 스택
+
+
온프레미스 전용 설계 — 외부 클라우드 의존 없는 완전 폐쇄망 동작
+
+
+ {TECH_STACK.map((t,i) => (
+
+
{t.category}
+
+ {t.items.map((item,j) => (
+ - {item}
+ ))}
+
+
+ ))}
+
+
+
+ )}
+
+ {/* ── 도입 사례 ─────────────────────────────────────── */}
+ {activeTab === 'usecase' && (
+
+
+
+
Use Cases
+
실제 도입 사례
+
+
+
+ {[
+ {org:'광역 지방자치단체', result:'레거시 서버 200대 SSH 자동화, 운영 인력 3명→1명', badge:'중앙부처'},
+ {org:'공공기관 IT센터', result:'월간 SR 500건 처리, AI 자동분류로 80% 응답시간 단축', badge:'공공기관'},
+ {org:'지방 교육청', result:'Tomcat 100대 무중단 배포 자동화, 장애 대응 시간 70% 단축', badge:'교육'},
+ ].map((u,i) => (
+
+
{u.badge}
+
{u.org}
+
{u.result}
+
+ ))}
+
+
+
+ )}
+
+ {/* ── CTA ─────────────────────────────────────────────── */}
+
+
+
지금 바로 무료 데모를 경험해 보세요
+
전문 컨설턴트가 귀 기관 환경에 맞는 최적의 구성을 제안해 드립니다.
+
+ 무료 데모 신청
+ 제품 소개서
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/Home.css b/frontend/src/pages/Home.css
new file mode 100644
index 00000000..f55a713d
--- /dev/null
+++ b/frontend/src/pages/Home.css
@@ -0,0 +1,303 @@
+/* ─── 히어로 ──────────────────────────────────────────────── */
+.hero {
+ position: relative;
+ height: 100vh; min-height: 600px;
+ display: flex; align-items: center;
+ overflow: hidden;
+ background: linear-gradient(135deg, #0a0f24 0%, #0d2463 50%, #0a1a4a 100%);
+ transition: background var(--slow);
+}
+.hero-0 { background: linear-gradient(135deg, #0a0f24 0%, #0d2463 60%, #0051A2 100%); }
+.hero-1 { background: linear-gradient(135deg, #1a0f3a 0%, #2d1b69 60%, #0051A2 100%); }
+.hero-2 { background: linear-gradient(135deg, #001a3a 0%, #003070 60%, #00A3E0 100%); }
+
+/* 애니메이션 배경 패턴 */
+.hero::before {
+ content: '';
+ position: absolute; inset: 0;
+ background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
+}
+
+.hero-overlay {
+ position: absolute; inset: 0;
+ background: linear-gradient(to right, rgba(0,0,0,.5) 0%, rgba(0,0,0,.1) 100%);
+}
+.hero-content {
+ position: relative; z-index: 2;
+ max-width: 700px;
+}
+.hero-badge {
+ display: inline-block;
+ background: var(--accent);
+ color: #fff;
+ font-size: 12px; font-weight: 700;
+ letter-spacing: 2px;
+ padding: 4px 12px; border-radius: 20px;
+ margin-bottom: 20px;
+ animation: fadeUp .5s ease;
+}
+.hero-title {
+ font-size: clamp(36px, 5.5vw, 64px);
+ font-weight: 900;
+ color: #fff;
+ line-height: 1.15;
+ margin-bottom: 20px;
+ animation: fadeUp .5s .1s ease both;
+}
+.hero-sub {
+ font-size: clamp(16px, 2vw, 20px);
+ color: rgba(255,255,255,.8);
+ margin-bottom: 40px;
+ line-height: 1.6;
+ animation: fadeUp .5s .2s ease both;
+}
+.hero-actions {
+ display: flex; gap: 16px; flex-wrap: wrap;
+ animation: fadeUp .5s .3s ease both;
+}
+.hero-btn-contact {
+ border-color: rgba(255,255,255,.5);
+ color: #fff !important;
+}
+.hero-btn-contact:hover {
+ background: rgba(255,255,255,.15) !important;
+ color: #fff !important;
+}
+
+/* 컨트롤 */
+.hero-controls {
+ position: absolute; bottom: 40px; left: 50%;
+ transform: translateX(-50%);
+ display: flex; align-items: center; gap: 12px;
+ z-index: 3;
+}
+.hero-arrow {
+ width: 36px; height: 36px;
+ border-radius: 50%;
+ background: rgba(255,255,255,.15);
+ color: #fff;
+ font-size: 20px;
+ display: flex; align-items: center; justify-content: center;
+ transition: background var(--fast);
+}
+.hero-arrow:hover { background: rgba(255,255,255,.3); }
+.hero-dots { display: flex; gap: 8px; }
+.hero-dot {
+ width: 8px; height: 8px;
+ border-radius: 50%;
+ background: rgba(255,255,255,.4);
+ transition: all var(--mid);
+}
+.hero-dot.active {
+ background: #fff; width: 24px; border-radius: 4px;
+}
+.hero-pause {
+ width: 32px; height: 32px;
+ border-radius: 50%;
+ background: rgba(255,255,255,.1);
+ color: rgba(255,255,255,.7);
+ font-size: 12px;
+ display: flex; align-items: center; justify-content: center;
+}
+
+/* 스크롤 힌트 */
+.hero-scroll-hint {
+ position: absolute; right: 40px; bottom: 40px;
+ display: flex; flex-direction: column; align-items: center; gap: 8px;
+ color: rgba(255,255,255,.5); font-size: 10px; letter-spacing: 2px;
+}
+.scroll-line {
+ width: 1px; height: 60px;
+ background: linear-gradient(to bottom, rgba(255,255,255,.5), transparent);
+ animation: scrollPulse 1.5s ease infinite;
+}
+@keyframes scrollPulse {
+ 0%,100% { opacity:.5; transform:scaleY(1); }
+ 50% { opacity:1; transform:scaleY(1.1); }
+}
+
+/* ─── 핵심 사업 ──────────────────────────────────────────── */
+.business-grid {
+ display: grid;
+ grid-template-columns: repeat(3,1fr);
+ gap: 28px;
+}
+.business-card {
+ background: var(--white);
+ border-radius: var(--radius-lg);
+ padding: 40px 32px;
+ border: 1px solid var(--gray-200);
+ transition: all var(--mid) var(--ease);
+ display: flex; flex-direction: column; gap: 12px;
+}
+.business-card:hover {
+ box-shadow: var(--shadow-lg);
+ transform: translateY(-6px);
+ border-color: transparent;
+}
+.business-icon {
+ width: 64px; height: 64px;
+ border-radius: var(--radius);
+ font-size: 28px;
+ display: flex; align-items: center; justify-content: center;
+}
+.business-title { font-size: 22px; font-weight: 700; color: var(--gray-900); }
+.business-desc { font-size: 15px; color: var(--gray-600); line-height: 1.7; flex: 1; }
+.business-more { font-size: 14px; font-weight: 600; }
+
+/* ─── GUARDiA 섹션 ───────────────────────────────────────── */
+.section-guardia { background: var(--gray-50); }
+.guardia-inner {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 80px;
+ align-items: center;
+}
+.guardia-desc {
+ font-size: 16px; color: var(--gray-600);
+ line-height: 1.8; margin: 20px 0 28px;
+}
+.guardia-features {
+ display: flex; flex-direction: column; gap: 16px;
+ margin-bottom: 32px;
+}
+.guardia-feature {
+ display: flex; align-items: flex-start; gap: 16px;
+ padding: 14px 18px;
+ background: var(--white);
+ border-radius: var(--radius);
+ border: 1px solid var(--gray-200);
+}
+.feature-icon { font-size: 24px; flex-shrink: 0; }
+.guardia-feature strong { display: block; font-size: 15px; color: var(--gray-900); }
+.guardia-feature p { font-size: 13px; color: var(--gray-600); margin-top: 2px; }
+.guardia-actions { display: flex; gap: 12px; flex-wrap: wrap; }
+
+/* 모의 채팅 UI */
+.guardia-mockup {
+ background: #1e1e2e;
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+ box-shadow: var(--shadow-lg);
+ border: 1px solid rgba(255,255,255,.1);
+}
+.mockup-bar {
+ background: #2d2d3f;
+ padding: 10px 16px;
+ display: flex; gap: 6px;
+}
+.mockup-bar span {
+ width: 12px; height: 12px;
+ border-radius: 50%;
+ background: #ff5f56;
+}
+.mockup-bar span:nth-child(2) { background: #ffbd2e; }
+.mockup-bar span:nth-child(3) { background: #27c93f; }
+.mockup-content { padding: 20px; }
+.mockup-chat { display: flex; flex-direction: column; gap: 12px; }
+.chat-msg { max-width: 80%; }
+.chat-msg.bot { align-self: flex-start; }
+.chat-msg.user { align-self: flex-end; }
+.chat-name { font-size: 11px; color: var(--accent); font-weight: 700; display: block; margin-bottom: 4px; }
+.chat-msg p {
+ padding: 10px 14px;
+ border-radius: 12px;
+ font-size: 13px; line-height: 1.5;
+}
+.chat-msg.bot p { background: #2d2d3f; color: rgba(255,255,255,.9); }
+.chat-msg.user p { background: var(--primary); color: #fff; }
+
+/* ─── KPI ────────────────────────────────────────────────── */
+.section-kpi {
+ background: var(--primary);
+ padding: 60px 0;
+}
+.kpi-grid {
+ display: grid;
+ grid-template-columns: repeat(4,1fr);
+ gap: 0;
+}
+.kpi-item {
+ text-align: center;
+ padding: 24px;
+ border-right: 1px solid rgba(255,255,255,.2);
+}
+.kpi-item:last-child { border-right: none; }
+.kpi-value {
+ font-size: clamp(32px, 4vw, 48px);
+ font-weight: 900;
+ color: #fff;
+}
+.kpi-label {
+ font-size: 14px; color: rgba(255,255,255,.75);
+ margin-top: 6px;
+}
+
+/* ─── 소식 ───────────────────────────────────────────────── */
+.news-grid {
+ display: grid;
+ grid-template-columns: repeat(4,1fr);
+ gap: 20px;
+}
+.news-card { display: flex; flex-direction: column; }
+.news-card-body { padding: 24px; flex: 1; display: flex; flex-direction: column; gap: 10px; }
+.news-title {
+ font-size: 16px; font-weight: 700; color: var(--gray-900);
+ line-height: 1.4;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+.news-summary {
+ font-size: 13px; color: var(--gray-600);
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ flex: 1;
+}
+.news-card-footer {
+ display: flex; justify-content: space-between; align-items: center;
+ padding: 12px 24px;
+ border-top: 1px solid var(--gray-100);
+ font-size: 12px;
+}
+.news-date { color: var(--gray-400); }
+.news-more { color: var(--primary); font-weight: 600; }
+
+/* 스켈레톤 */
+.skeleton { animation: pulse 1.5s ease infinite; }
+@keyframes pulse { 0%,100%{opacity:.6} 50%{opacity:1} }
+.skel-line { background: var(--gray-200); border-radius: 4px; }
+
+/* ─── CTA ────────────────────────────────────────────────── */
+.section-cta {
+ background: linear-gradient(135deg, var(--secondary), var(--primary-dark));
+ padding: 80px 0;
+}
+.cta-inner {
+ display: flex; align-items: center; justify-content: space-between;
+ gap: 40px; flex-wrap: wrap;
+}
+.cta-text h2 { font-size: 28px; font-weight: 800; color: #fff; }
+.cta-text p { color: rgba(255,255,255,.75); margin-top: 8px; font-size: 16px; }
+.cta-actions { display: flex; gap: 16px; flex-wrap: wrap; flex-shrink: 0; }
+
+/* ─── 반응형 ──────────────────────────────────────────────── */
+@media (max-width: 1024px) {
+ .business-grid { grid-template-columns: repeat(2,1fr); }
+ .guardia-inner { grid-template-columns: 1fr; gap: 40px; }
+ .kpi-grid { grid-template-columns: repeat(2,1fr); }
+ .news-grid { grid-template-columns: repeat(2,1fr); }
+}
+@media (max-width: 768px) {
+ .hero-title { font-size: 32px; }
+ .hero-sub { font-size: 15px; }
+ .hero-scroll-hint { display: none; }
+ .business-grid { grid-template-columns: 1fr; }
+ .kpi-grid { grid-template-columns: repeat(2,1fr); }
+ .news-grid { grid-template-columns: 1fr; }
+ .cta-inner { flex-direction: column; text-align: center; }
+ .cta-actions { justify-content: center; }
+}
diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx
new file mode 100644
index 00000000..2b4eb47e
--- /dev/null
+++ b/frontend/src/pages/Home.jsx
@@ -0,0 +1,315 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { Link } from 'react-router-dom';
+import axios from 'axios';
+import './Home.css';
+
+/* ── 히어로 슬라이드 데이터 ─────────────────────────────── */
+const SLIDES = [
+ {
+ title: 'AI 기반 인프라\n자율 운영 플랫폼',
+ sub: 'GUARDiA ITSM — 메신저 한 줄로 1,000개 관공서 인프라를 자동화',
+ cta: { label: 'GUARDiA 알아보기', path: '/solution/guardia' },
+ badge: 'NEW',
+ bg: 'slide-1',
+ },
+ {
+ title: '공공기관 전문\nIT 솔루션 기업',
+ sub: '20년 경험의 지오정보기술이 최첨단 AI 기술로 여러분과 함께합니다',
+ cta: { label: '회사소개 보기', path: '/company/greeting' },
+ badge: '',
+ bg: 'slide-2',
+ },
+ {
+ title: '에이전트리스\n자동화 혁신',
+ sub: '대상 서버에 소프트웨어 설치 없이 SSH만으로 레거시 인프라를 관리',
+ cta: { label: '도입 문의', path: '/support/contact' },
+ badge: '',
+ bg: 'slide-3',
+ },
+];
+
+/* ── 핵심 사업 영역 ──────────────────────────────────────── */
+const BUSINESS = [
+ {
+ icon: '🤖',
+ title: 'AI 자동화',
+ desc: 'GUARDiA ITSM 플랫폼으로 레거시 인프라 운영을 완전 자동화',
+ path: '/solution/guardia',
+ color: 'var(--primary)',
+ },
+ {
+ icon: '🏗️',
+ title: 'SI 구축',
+ desc: '공공기관 정보화사업 시스템 통합 및 맞춤형 개발',
+ path: '/business/reference',
+ color: 'var(--accent)',
+ },
+ {
+ icon: '💼',
+ title: 'ERP·CRM·BI',
+ desc: '기업 경영 효율화를 위한 통합 솔루션 패키지',
+ path: '/solution/erp',
+ color: '#10B981',
+ },
+];
+
+/* ── GUARDiA 핵심 기능 ───────────────────────────────────── */
+const GUARDIA_FEATURES = [
+ { icon: '💬', label: 'ChatOps', desc: '메신저 명령으로 인프라 제어' },
+ { icon: '🔧', label: '에이전트리스', desc: 'SSH만으로 에이전트 설치 없음' },
+ { icon: '📊', label: '통합 ITSM', desc: 'SR·인시던트·변경·SLA 통합' },
+ { icon: '🔒', label: '엔터프라이즈 보안', desc: 'MFA·PAM·Zero Trust' },
+];
+
+/* ── KPI 수치 ────────────────────────────────────────────── */
+const KPIS = [
+ { value: '1,000+', label: '관리 가능 기관 수' },
+ { value: '99.9%', label: '시스템 가용성' },
+ { value: '70%', label: 'SR 처리 시간 단축' },
+ { value: '20년+', label: 'IT 사업 경험' },
+];
+
+export default function Home() {
+ const [slide, setSlide] = useState(0);
+ const [paused, setPaused] = useState(false);
+ const [news, setNews] = useState([]);
+ const timerRef = useRef(null);
+
+ /* 슬라이드 자동 전환 */
+ useEffect(() => {
+ if (paused) return;
+ timerRef.current = setInterval(() => setSlide(s => (s + 1) % SLIDES.length), 5000);
+ return () => clearInterval(timerRef.current);
+ }, [paused]);
+
+ /* 소식 로드 */
+ useEffect(() => {
+ axios.get('/api/news?size=4').then(r => setNews(r.data.content || [])).catch(() => {});
+ }, []);
+
+ const prevSlide = () => setSlide(s => (s - 1 + SLIDES.length) % SLIDES.length);
+ const nextSlide = () => setSlide(s => (s + 1) % SLIDES.length);
+
+ const current = SLIDES[slide];
+
+ return (
+
+
+ {/* ── 히어로 슬라이더 ──────────────────────────────── */}
+ setPaused(true)}
+ onMouseLeave={() => setPaused(false)}
+ aria-label="메인 슬라이더">
+
+
+ {current.badge && (
+
{current.badge}
+ )}
+
+ {current.title.split('\n').map((line, i) => (
+ {line}{i < current.title.split('\n').length - 1 &&
}
+ ))}
+
+
{current.sub}
+
+
+ {current.cta.label} →
+
+
+ 무료 상담
+
+
+
+
+ {/* 슬라이드 컨트롤 */}
+
+
+
+ {SLIDES.map((_, i) => (
+
+
+
+
+
+ {/* 스크롤 유도 */}
+
+
+
+ {/* ── 핵심 사업 영역 ───────────────────────────────── */}
+
+
+
+
Our Business
+
기업과 기관을 위한
맞춤형 IT 솔루션
+
+
+
+ {BUSINESS.map((b, i) => (
+
+
+ {b.icon}
+
+
{b.title}
+
{b.desc}
+
자세히 보기 →
+
+ ))}
+
+
+
+
+ {/* ── GUARDiA 솔루션 하이라이트 ──────────────────────── */}
+
+
+
+
+
대표 솔루션
+
+ GUARDiA ITSM
+ AI 기반 인프라 자율 운영
+
+
+
+ 1,000개 이상의 관공서 레거시 인프라를 메신저 한 줄 명령으로 제어하는
+ 온프레미스 AI ChatOps 플랫폼. 에이전트 설치 없이 SSH/SFTP만으로
+ 배포·운영을 완전 자동화합니다.
+
+
+ {GUARDIA_FEATURES.map((f, i) => (
+
+ ))}
+
+
+
+ GUARDiA 상세보기
+
+
+ 도입 문의
+
+
+
+
+
+
+
+
+
+
+
+
GUARDiA Bot
+
안녕하세요! 무엇을 도와드릴까요?
+
+
+
/deploy web-server-01
+
+
+
✅ web-server-01 배포 완료
헬스체크: 정상 | 소요: 42초
+
+
+
+
📊 시스템 현황
SR 처리중: 3건 | SLA 준수율: 98.2%
서버 이상: 0건 ✅
+
+
+
+
+
+
+
+
+
+ {/* ── KPI 수치 ────────────────────────────────────────── */}
+
+
+
+ {KPIS.map((k, i) => (
+
+
{k.value}
+
{k.label}
+
+ ))}
+
+
+
+
+ {/* ── 최신 소식 ───────────────────────────────────────── */}
+
+
+
+
Latest News
+
지오정보기술 소식
+
+
+
+ {news.length > 0 ? news.map(n => (
+
+
+
{n.category}
+
{n.title}
+
{n.summary}
+
+
+
+ {n.createdAt ? new Date(n.createdAt).toLocaleDateString('ko-KR') : ''}
+
+ 더보기 →
+
+
+ )) : (
+ /* 스켈레톤 */
+ Array.from({length: 4}).map((_, i) => (
+
+ ))
+ )}
+
+
+
+ 모든 소식 보기
+
+
+
+
+
+ {/* ── CTA 배너 ────────────────────────────────────────── */}
+
+
+
+
+
GUARDiA ITSM 도입을 검토하고 계신가요?
+
전문 컨설턴트가 귀 기관 환경에 맞는 최적의 방안을 제안해 드립니다.
+
+
+
+ 무료 상담 신청
+
+
+ 제품 소개서 다운로드
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/NewsPage.css b/frontend/src/pages/NewsPage.css
new file mode 100644
index 00000000..a30e39e7
--- /dev/null
+++ b/frontend/src/pages/NewsPage.css
@@ -0,0 +1,19 @@
+.notice-back { font-size:14px; color:var(--primary); margin-bottom:24px; display:inline-flex; align-items:center; gap:4px; cursor:pointer; background:none; border:none; }
+.news-cat-badge { display:inline-block; padding:4px 12px; border-radius:12px; font-size:12px; font-weight:700; margin-bottom:12px; }
+.news-cat-badge.hot { background:rgba(239,68,68,.12); color:var(--danger); }
+.news-main { padding:40px; cursor:pointer; background:linear-gradient(135deg, var(--secondary), var(--primary-dark)); border:none; margin-bottom:0; }
+.news-main:hover { transform:none; box-shadow:var(--shadow-lg); }
+.news-main-title { font-size:26px; font-weight:900; color:#fff; margin-bottom:16px; line-height:1.35; }
+.news-main-summary { font-size:15px; color:rgba(255,255,255,.7); line-height:1.8; margin-bottom:16px; max-width:640px; }
+.news-date { font-size:12px; color:rgba(255,255,255,.5); }
+.news-card { padding:28px; cursor:pointer; display:flex; flex-direction:column; }
+.news-card-title { font-size:15px; font-weight:700; color:var(--gray-900); margin-bottom:10px; line-height:1.5; flex:1; }
+.news-card-summary { font-size:13px; color:var(--gray-600); line-height:1.7; margin-bottom:12px; flex:1; }
+.news-date { font-size:12px; color:var(--gray-400); }
+.blog-card { padding:28px; display:flex; flex-direction:column; }
+.blog-tag { display:inline-block; padding:4px 12px; border-radius:12px; font-size:12px; font-weight:700; margin-bottom:14px; align-self:flex-start; }
+.blog-title { font-size:16px; font-weight:700; color:var(--gray-900); line-height:1.5; margin-bottom:12px; }
+.blog-summary { font-size:13px; color:var(--gray-600); line-height:1.7; flex:1; margin-bottom:16px; }
+.blog-meta { display:flex; gap:16px; font-size:12px; color:var(--gray-400); margin-bottom:16px; }
+.blog-read-btn { padding:10px 20px; background:var(--primary-light); color:var(--primary); border-radius:8px; font-size:14px; font-weight:700; border:none; cursor:pointer; transition:all var(--fast); text-align:center; }
+.blog-read-btn:hover { background:var(--primary); color:#fff; }
diff --git a/frontend/src/pages/NewsPage.jsx b/frontend/src/pages/NewsPage.jsx
new file mode 100644
index 00000000..a9df16f1
--- /dev/null
+++ b/frontend/src/pages/NewsPage.jsx
@@ -0,0 +1,219 @@
+import React, { useState } from 'react';
+import { Routes, Route, NavLink, Link } from 'react-router-dom';
+import './Common.css';
+import './NewsPage.css';
+
+const SUB_NAV = [
+ { path: '/news/newsroom', label: '뉴스룸' },
+ { path: '/news/blog', label: '기술 블로그' },
+];
+
+function SubNav({ title }) {
+ return (
+ <>
+
+
+ >
+ );
+}
+
+/* ── 뉴스룸 ── */
+const NEWS = [
+ {
+ id:1, cat:'제품 출시', date:'2026.05.15',
+ title:'GUARDiA ITSM v2.0 정식 출시 — AI ChatOps 오케스트레이션 플랫폼',
+ summary:'메신저 한 줄 명령으로 1,000개+ 공공기관 레거시 인프라를 자동 운영하는 GUARDiA ITSM v2.0이 정식 출시되었습니다. 신규 기능으로 AI 자연어 명령, 에이전트리스 배포 엔진, 멀티테넌트 지원이 추가됐습니다.',
+ content: `GUARDiA ITSM v2.0은 공공기관의 레거시 IT 인프라 운영 자동화를 위한 AI 기반 플랫폼입니다.\n\n주요 신기능:\n- AI ChatOps: 메신저 자연어 명령 → Ollama LLM 파싱 → 자동 실행\n- 에이전트리스 배포: SSH/SFTP만으로 WAS 배포·롤백 자동화\n- 멀티테넌트: 1,000개+ 기관 동시 관리\n- GS인증 1등급 신청 완료\n\n자세한 사항은 GUARDiA 소개 페이지를 참조해 주십시오.`,
+ hot: true,
+ },
+ {
+ id:2, cat:'수주 소식', date:'2026.04.20',
+ title:'삼성전자 차세대 CRM 시스템 DB 마이그레이션 프로젝트 수주',
+ summary:'(주)지오정보기술이 삼성전자 차세대 CRM 구축 프로젝트의 DB Migration/DA/튜닝을 담당합니다. EDB PostgreSQL 환경으로의 전환을 포함한 대규모 DB 현대화 작업을 수행합니다.',
+ content: '삼성전자와의 두 번째 협력 프로젝트로, DB 마이그레이션 및 성능 튜닝을 담당합니다.',
+ hot: false,
+ },
+ {
+ id:3, cat:'기술 인증', date:'2026.03.10',
+ title:'GUARDiA ITSM GS인증 1등급 신청 완료 — TTA 심사 예정',
+ summary:'GUARDiA ITSM이 한국정보통신기술협회(TTA)에 GS인증 1등급을 신청하였습니다. 기능적합성, 신뢰성, 사용성, 보안성 등 ISO/IEC 25010 기준 8대 품질 특성 심사를 앞두고 있습니다.',
+ content: 'GS인증 심사는 2026년 9월 예정이며, 1등급 취득 시 조달청 나라장터 우선 등재가 가능합니다.',
+ hot: false,
+ },
+ {
+ id:4, cat:'수주 소식', date:'2026.02.15',
+ title:'국민연금공단 차세대 시스템 구축 — AA 역할 수행',
+ summary:'국민연금공단 차세대 시스템 구축 프로젝트에 Application Architect(AA)로 참여합니다. JSP/Java, Nexacro, Spring 기반의 대규모 공공기관 시스템 구축을 담당합니다.',
+ content: '국민연금관리공단의 차세대 시스템은 수천만 가입자의 연금 관리 시스템으로, CI/CD 파이프라인 기반의 현대적인 개발 환경을 구축합니다.',
+ hot: false,
+ },
+ {
+ id:5, cat:'기업 소식', date:'2025.12.01',
+ title:'2025년 사업실적 — 연간 프로젝트 10건 성공 수행',
+ summary:'2025년 한 해 동안 삼성전자, 서울신용보증재단, 헌법재판소 등 10개 주요 프로젝트를 성공적으로 완료했습니다. 매출은 전년 대비 25% 성장하였습니다.',
+ content: '창립 이래 최대 성과를 기록한 2025년 사업실적을 공유드립니다.',
+ hot: false,
+ },
+ {
+ id:6, cat:'파트너십', date:'2025.09.10',
+ title:'Tibero 공식 파트너사 등록 — 공공기관 DB 전환 솔루션 강화',
+ summary:'국산 DBMS Tibero의 공식 파트너사로 등록되었습니다. Oracle에서 Tibero로의 마이그레이션 및 공공기관 DB 현대화 사업을 공동으로 추진합니다.',
+ content: '공공기관의 Oracle 라이선스 절감을 위한 Tibero 전환 프로젝트를 전문적으로 지원합니다.',
+ hot: false,
+ },
+];
+
+function Newsroom() {
+ const [selected, setSelected] = useState(null);
+ const item = NEWS.find(n => n.id === selected);
+ return (
+
+
+
+
+ {item ? (
+
+
+
+
{item.cat}
+
{item.title}
+
{item.date}
+
+ {item.content.split('\n').map((p, i) => (
+ p.trim() ?
{p}
: null
+ ))}
+
+
+ ) : (
+ <>
+ {/* 메인 뉴스 */}
+
setSelected(NEWS[0].id)}>
+
+
🔥 {NEWS[0].cat}
+
{NEWS[0].title}
+
{NEWS[0].summary}
+
{NEWS[0].date}
+
+
+
+ {NEWS.slice(1).map(n => (
+
setSelected(n.id)}>
+
{n.cat}
+
{n.title}
+
{n.summary}
+
{n.date}
+
+ ))}
+
+ >
+ )}
+
+
+
+ );
+}
+
+/* ── 기술 블로그 ── */
+const BLOGS = [
+ {
+ id:1, tag:'AI·LLM', date:'2026.05.20',
+ title:'온프레미스 Ollama로 폐쇄망 ChatOps 구현하기',
+ summary:'인터넷 없이 내부망에서 LLM을 운영하는 방법. Llama-3-8B 모델을 Ollama로 구동하고 FastAPI와 연동하는 전체 과정을 설명합니다.',
+ readMin: 12,
+ },
+ {
+ id:2, tag:'DevOps', date:'2026.05.10',
+ title:'에이전트리스 WAS 배포 자동화 — paramiko SSH로 레거시 서버 관리',
+ summary:'JEUS·Tomcat 등 레거시 WAS에 SSH/SFTP만으로 배포하는 방법. 백업→배포→헬스체크→롤백 파이프라인 구현 예제.',
+ readMin: 15,
+ },
+ {
+ id:3, tag:'보안', date:'2026.04.28',
+ title:'AES-256-GCM으로 서버 자격증명을 안전하게 저장하는 법',
+ summary:'공공기관 서버 SSH 비밀번호를 DB에 안전하게 암호화 저장하는 방법. IV·암호문·GCM Tag 구조 설계와 Python 구현.',
+ readMin: 8,
+ },
+ {
+ id:4, tag:'데이터베이스', date:'2026.04.15',
+ title:'Oracle 19c → EDB PostgreSQL 마이그레이션 실전 가이드',
+ summary:'삼성전자 CRM 프로젝트에서 실제 수행한 Oracle→EDB 마이그레이션 경험 공유. Smeta, ExemOne 활용 SQL 변환 전략.',
+ readMin: 20,
+ },
+ {
+ id:5, tag:'성능', date:'2026.03.25',
+ title:'공공기관 행정정보시스템 SQL 튜닝 — 서울시립대 사례',
+ summary:'대학행정정보시스템 성능 개선 프로젝트 실전 사례. JMeter 부하테스트와 Oracle 실행계획 분석으로 응답시간 60% 단축.',
+ readMin: 18,
+ },
+ {
+ id:6, tag:'아키텍처', date:'2026.03.10',
+ title:'FastAPI 비동기 WebSocket으로 실시간 대시보드 구축하기',
+ summary:'GUARDiA ITSM 실시간 모니터링 대시보드 구현 방법. FastAPI SSE + WebSocket + React를 조합한 풀스택 아키텍처.',
+ readMin: 14,
+ },
+];
+
+const TAG_COLORS = {
+ 'AI·LLM': '#7c3aed', 'DevOps': '#0051A2', '보안': '#dc2626',
+ '데이터베이스': '#d97706', '성능': '#059669', '아키텍처': '#0891b2'
+};
+
+function Blog() {
+ return (
+
+
+
+
+
+
Tech Blog
+
기술 인사이트 공유
+
20년 이상의 프로젝트 경험에서 얻은 기술 노하우를 공유합니다
+
+
+ {BLOGS.map(b => (
+
+
+ {b.tag}
+
+
{b.title}
+
{b.summary}
+
+ 📅 {b.date}
+ ⏱ {b.readMin}분 읽기
+
+
+
+ ))}
+
+
+
+
+ );
+}
+
+export default function NewsPage() {
+ return (
+
+ } />
+ } />
+ } />
+ } />
+
+ );
+}
diff --git a/frontend/src/pages/NotFound.jsx b/frontend/src/pages/NotFound.jsx
new file mode 100644
index 00000000..ae08dad6
--- /dev/null
+++ b/frontend/src/pages/NotFound.jsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { Link } from "react-router-dom";
+
+export default function NotFound() {
+ return (
+
+ 404
+ 페이지를 찾을 수 없습니다
+ 요청하신 페이지가 존재하지 않거나 이동되었습니다.
+ 홈으로 돌아가기
+
+ );
+}
diff --git a/frontend/src/pages/Recruit.css b/frontend/src/pages/Recruit.css
new file mode 100644
index 00000000..375f9de0
--- /dev/null
+++ b/frontend/src/pages/Recruit.css
@@ -0,0 +1,34 @@
+.notice-back { font-size:14px; color:var(--primary); margin-bottom:24px; display:inline-flex; align-items:center; gap:4px; cursor:pointer; background:none; border:none; }
+.job-card { padding:28px; display:flex; gap:24px; align-items:flex-start; cursor:pointer; }
+.job-card:hover { border-color:var(--primary); }
+.job-info { flex:1; }
+.job-title { font-size:18px; font-weight:700; color:var(--gray-900); margin-bottom:8px; }
+.job-desc { font-size:13px; color:var(--gray-600); line-height:1.6; margin-bottom:12px; }
+.job-stack { display:flex; gap:6px; flex-wrap:wrap; }
+.job-tech { padding:4px 10px; background:var(--secondary); color:var(--accent); border-radius:4px; font-size:11px; font-weight:600; }
+.job-meta { display:flex; flex-direction:column; gap:8px; align-items:flex-end; min-width:100px; }
+.job-meta > div { display:flex; flex-direction:column; align-items:flex-end; font-size:13px; color:var(--gray-700); }
+.job-meta-label { font-size:11px; color:var(--gray-400); margin-bottom:2px; }
+.welfare-cat { font-size:18px; font-weight:800; color:var(--gray-900); margin-bottom:20px; }
+.welfare-card { padding:28px; text-align:center; }
+.welfare-icon { font-size:36px; margin-bottom:12px; }
+.welfare-name { font-size:15px; font-weight:700; margin-bottom:8px; color:var(--gray-900); }
+.welfare-desc { font-size:13px; color:var(--gray-600); line-height:1.6; }
+.talent-wrap { background:var(--gray-50); border-radius:16px; padding:56px; margin-top:32px; }
+.apply-form .form-group { margin-bottom:20px; }
+.apply-form label { display:block; font-size:13px; font-weight:600; color:var(--gray-700); margin-bottom:6px; }
+.apply-form input, .apply-form select, .apply-form textarea {
+ width:100%; padding:12px 14px; border:1px solid var(--gray-200); border-radius:8px;
+ font-size:14px; font-family:inherit; transition:border-color var(--fast);
+}
+.apply-form input:focus, .apply-form select:focus, .apply-form textarea:focus { outline:none; border-color:var(--primary); }
+.apply-form .form-row { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
+.required { color:var(--danger); }
+.apply-success { text-align:center; padding:80px 40px; background:var(--gray-50); border-radius:16px; }
+.apply-success h3 { font-size:24px; font-weight:800; margin-bottom:16px; }
+.apply-success p { font-size:15px; color:var(--gray-600); line-height:1.8; }
+@media (max-width:768px) {
+ .job-card { flex-direction:column; }
+ .job-meta { align-items:flex-start; flex-direction:row; flex-wrap:wrap; }
+ .apply-form .form-row { grid-template-columns:1fr; }
+}
diff --git a/frontend/src/pages/Recruit.jsx b/frontend/src/pages/Recruit.jsx
new file mode 100644
index 00000000..7a5bd48c
--- /dev/null
+++ b/frontend/src/pages/Recruit.jsx
@@ -0,0 +1,317 @@
+import React, { useState } from 'react';
+import { Routes, Route, NavLink } from 'react-router-dom';
+import './Common.css';
+import './Recruit.css';
+
+const SUB_NAV = [
+ { path: '/recruit/jobs', label: '채용공고' },
+ { path: '/recruit/welfare', label: '복리후생' },
+ { path: '/recruit/apply', label: '지원하기' },
+];
+
+function SubNav({ title }) {
+ return (
+ <>
+
+
+
Recruit
+
{title}
+
지오정보기술과 함께 AI 인프라 혁신을 이끌어 갈 인재를 모십니다.
+
+
+
+ >
+ );
+}
+
+/* ── 채용공고 ── */
+const JOBS = [
+ {
+ id: 1, title: 'AI/LLM 엔지니어', dept: 'AI팀', type: '정규직', exp: '경력 3년 이상',
+ stack: ['Python', 'Ollama', 'LangChain', 'FastAPI'],
+ desc: 'GUARDiA ITSM의 온프레미스 AI 엔진 개발. 자연어→명령 파싱, LLM 파인튜닝, RAG 파이프라인 구축.',
+ deadline: '2026.06.30', hot: true,
+ },
+ {
+ id: 2, title: 'Java 백엔드 개발자 (Spring Boot)', dept: '개발팀', type: '정규직', exp: '경력 3년 이상',
+ stack: ['Java', 'Spring Boot', 'Oracle', 'MyBatis'],
+ desc: '공공기관 SI/SM 프로젝트 백엔드 개발. ERP·CRM·행정정보시스템 구축 및 유지보수.',
+ deadline: '2026.06.30', hot: true,
+ },
+ {
+ id: 3, title: 'React 프론트엔드 개발자', dept: '개발팀', type: '정규직', exp: '경력 2년 이상',
+ stack: ['React', 'TypeScript', 'Vite', 'Chart.js'],
+ desc: 'GUARDiA ITSM 및 고객사 포털 프론트엔드 개발. 공공기관 웹접근성(KWCAG 2.1) 준수 필수.',
+ deadline: '2026.06.30', hot: false,
+ },
+ {
+ id: 4, title: '인프라 운영 엔지니어 (DBA)', dept: '운영팀', type: '정규직', exp: '경력 3년 이상',
+ stack: ['Oracle', 'Tibero', 'Linux', 'Shell'],
+ desc: 'Oracle/Tibero DB 설계·튜닝·이관. 삼성전자·국민연금급 대형 DB 운영 경험 우대.',
+ deadline: '2026.06.15', hot: false,
+ },
+ {
+ id: 5, title: 'PM / PL (공공 SI)', dept: 'PM본부', type: '정규직', exp: '경력 5년 이상',
+ stack: ['PMP', 'PMBOK', 'MS Project', 'Jira'],
+ desc: '공공기관 정보화사업 PM/PL. 헌법재판소·국민연금·시립대 수준 프로젝트 관리 경험 보유자.',
+ deadline: '2026.06.15', hot: false,
+ },
+ {
+ id: 6, title: 'DevOps / CI·CD 엔지니어', dept: '개발팀', type: '정규직', exp: '경력 2년 이상',
+ stack: ['Docker', 'Kubernetes', 'Jenkins', 'GitHub Actions'],
+ desc: 'GUARDiA Vibe CD 파이프라인 구축 및 운영. 폐쇄망 환경 GitOps 경험 우대.',
+ deadline: '상시', hot: false,
+ },
+];
+
+function Jobs() {
+ const [selected, setSelected] = useState(null);
+ if (selected) {
+ const j = JOBS.find(j => j.id === selected);
+ return (
+
+
+
+
+
+
+
+ {j.hot &&
HOT}
+
{j.title}
+
+ {j.dept}
+ {j.type}
+ 경력: {j.exp}
+ 마감: {j.deadline}
+
+
+
+
업무 내용
+
{j.desc}
+
기술 스택
+
+ {j.stack.map((s, i) => (
+ {s}
+ ))}
+
+
지원 방법
+
이력서 및 포트폴리오를 recruit@zioinfo.co.kr 로 제출하시거나, 아래 지원하기 버튼을 이용해 주십시오.
+
지원하기 →
+
+
+
+
+ );
+ }
+ return (
+
+
+
+
+
+ Open Positions
+
현재 채용 중인 포지션
+
+
+ {JOBS.map(j => (
+
setSelected(j.id)}>
+
+
+ {j.hot && HOT}
+ {j.dept}
+ {j.type}
+
+
{j.title}
+
{j.desc}
+
+ {j.stack.map((s, i) => {s})}
+
+
+
+
경력{j.exp}
+
마감{j.deadline}
+
+
+
+ ))}
+
+
+
+
+ );
+}
+
+/* ── 복리후생 ── */
+const WELFARE = [
+ {
+ cat: '💼 근무환경', items: [
+ { icon: '🕘', name: '유연근무제', desc: '코어타임(10시~16시) 외 자유로운 출퇴근 시간 선택' },
+ { icon: '🏠', name: '재택근무', desc: '직무에 따라 주 1~2회 재택근무 지원' },
+ { icon: '💻', name: '장비 지원', desc: '맥북 또는 고성능 윈도우 노트북 선택 지급' },
+ { icon: '🎯', name: '목표 관리(OKR)', desc: '분기별 OKR로 명확한 목표·성과 관리' },
+ ]
+ },
+ {
+ cat: '📚 성장 지원', items: [
+ { icon: '📖', name: '교육비 지원', desc: '연 200만원 교육비 지원 (도서, 강의, 세미나)' },
+ { icon: '🏆', name: '자격증 지원', desc: '정보처리기사, PMP, AWS, Oracle 자격증 취득 지원' },
+ { icon: '🎓', name: '사내 강의', desc: 'AI·클라우드·보안 월 1회 사내 기술 세미나' },
+ { icon: '✈️', name: '컨퍼런스', desc: 'AWS re:Invent, Google I/O 등 국내외 컨퍼런스 참가 지원' },
+ ]
+ },
+ {
+ cat: '🎁 복지 혜택', items: [
+ { icon: '🏥', name: '건강검진', desc: '연 1회 종합건강검진 (배우자 포함)' },
+ { icon: '🎂', name: '경조사 지원', desc: '경조금·경조휴가 제공 (결혼, 출산, 상조)' },
+ { icon: '🍽️', name: '식대 지원', desc: '점심 식대 월 15만원 지원 (식권 또는 카드)' },
+ { icon: '🎉', name: '명절 선물', desc: '설·추석 명절 선물 및 상여금 지급' },
+ ]
+ },
+];
+
+function Welfare() {
+ return (
+
+
+
+
+
+
Welfare
+
함께 성장하는 환경을 만듭니다
+
구성원이 최고의 역량을 발휘할 수 있도록 다양한 지원을 제공합니다
+
+ {WELFARE.map((w, wi) => (
+
+
{w.cat}
+
+ {w.items.map((item, i) => (
+
+
{item.icon}
+
{item.name}
+
{item.desc}
+
+ ))}
+
+
+ ))}
+
+ {/* 인재상 */}
+
+
+ Talent
+
우리가 찾는 인재
+
+
+ {[
+ { icon: '🔥', title: '도전하는 인재', desc: '새로운 기술과 문제에 두려움 없이 도전하는 분' },
+ { icon: '🤝', title: '협력하는 인재', desc: '팀과 함께 성장하며 지식을 나누는 분' },
+ { icon: '🎯', title: '책임지는 인재', desc: '맡은 업무에 오너십을 갖고 끝까지 완수하는 분' },
+ ].map((t, i) => (
+
+
{t.icon}
+
{t.title}
+
{t.desc}
+
+ ))}
+
+
+
+
+
+ );
+}
+
+/* ── 지원하기 ── */
+function Apply() {
+ const [form, setForm] = useState({ name:'', email:'', phone:'', position:'', exp:'', portfolio:'', message:'' });
+ const [status, setStatus] = useState(null);
+ const handleChange = e => setForm(f => ({ ...f, [e.target.name]: e.target.value }));
+ const handleSubmit = e => {
+ e.preventDefault();
+ setStatus('success');
+ };
+ return (
+
+
+
+
+
+
Apply
+
입사 지원서
+
아래 양식을 작성하시거나 recruit@zioinfo.co.kr로 이력서를 보내주세요
+
+ {status === 'success' ? (
+
+
✅
+
지원이 완료되었습니다!
+
검토 후 영업일 기준 3~5일 내에 연락 드리겠습니다.
recruit@zioinfo.co.kr 로도 이력서를 추가 제출하시면 더욱 빠르게 처리됩니다.
+
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
+
+export default function Recruit() {
+ return (
+
+ } />
+ } />
+ } />
+ } />
+
+ );
+}
diff --git a/frontend/src/pages/SolutionPage.css b/frontend/src/pages/SolutionPage.css
new file mode 100644
index 00000000..32abd84f
--- /dev/null
+++ b/frontend/src/pages/SolutionPage.css
@@ -0,0 +1,64 @@
+/* ── 솔루션 히어로 ── */
+.sol-hero-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 64px; align-items: center; }
+.sol-title { font-size: clamp(26px,3.5vw,40px); font-weight: 900; color: var(--gray-900); line-height: 1.25; margin: 12px 0 20px; }
+.sol-title em { color: var(--primary); font-style: normal; }
+.sol-desc { font-size: 15px; color: var(--gray-600); line-height: 1.85; margin-bottom: 24px; }
+.sol-features { display: flex; flex-direction: column; gap: 10px; }
+.sol-feature-item { display: flex; gap: 10px; font-size: 14px; color: var(--gray-700); }
+.sol-check { color: var(--accent); font-weight: 700; flex-shrink: 0; }
+
+/* ── 모듈 카드 ── */
+.sol-module-card { padding: 32px 24px; }
+.sol-module-icon { font-size: 36px; margin-bottom: 16px; }
+.sol-module-card h3 { font-size: 16px; font-weight: 700; margin-bottom: 10px; color: var(--gray-900); }
+.sol-module-card p { font-size: 13px; color: var(--gray-600); line-height: 1.7; }
+
+/* ── 솔루션 비주얼 ── */
+.sol-visual { display: flex; justify-content: center; }
+.sol-screen {
+ background: var(--secondary); border-radius: 16px; padding: 24px;
+ width: 100%; max-width: 360px; box-shadow: var(--shadow-lg);
+}
+.sol-screen-header {
+ display: flex; align-items: center; gap: 8px; margin-bottom: 20px;
+ font-size: 12px; color: rgba(255,255,255,.6); font-weight: 600;
+}
+.sol-screen-header span {
+ width: 10px; height: 10px; border-radius: 50%; background: var(--accent); flex-shrink: 0;
+}
+
+/* ERP */
+.sol-chart-bar-wrap { display: flex; gap: 8px; height: 120px; align-items: flex-end; margin-bottom: 20px; }
+.sol-chart-bar { flex: 1; background: linear-gradient(to top, var(--primary), var(--accent)); border-radius: 4px 4px 0 0; }
+.sol-stat-row { display: flex; gap: 12px; }
+.sol-stat { flex: 1; background: rgba(255,255,255,.06); border-radius: 8px; padding: 12px; text-align: center; }
+.sol-stat strong { display: block; font-size: 14px; color: #fff; font-weight: 700; }
+.sol-stat span { font-size: 10px; color: rgba(255,255,255,.5); margin-top: 4px; display: block; }
+
+/* CRM */
+.crm-items { display: flex; flex-direction: column; gap: 12px; }
+.crm-item { display: flex; align-items: center; gap: 12px; background: rgba(255,255,255,.05); border-radius: 8px; padding: 10px 12px; }
+.crm-avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--primary); display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 700; color: #fff; flex-shrink: 0; }
+.crm-info { flex: 1; }
+.crm-info strong { display: block; font-size: 13px; color: #fff; font-weight: 600; }
+.crm-info span { font-size: 11px; color: rgba(255,255,255,.5); }
+.crm-status { font-size: 11px; font-weight: 700; }
+
+/* BI */
+.bi-kpis { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 20px; }
+.bi-kpi { background: rgba(255,255,255,.06); border-radius: 8px; padding: 12px; }
+.bi-kpi-label { display: block; font-size: 10px; color: rgba(255,255,255,.5); margin-bottom: 4px; }
+.bi-kpi-val { display: block; font-size: 15px; color: #fff; font-weight: 700; }
+.bi-kpi-delta { font-size: 11px; font-weight: 600; }
+.bi-bar-chart { display: flex; gap: 12px; height: 80px; align-items: flex-end; }
+.bi-bar-group { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; height: 100%; }
+.bi-bar-pair { display: flex; gap: 4px; width: 100%; height: 100%; align-items: flex-end; }
+.bi-bar { flex: 1; border-radius: 3px 3px 0 0; }
+.bi-bar.revenue { background: var(--accent); }
+.bi-bar.cost { background: rgba(239,68,68,.6); }
+.bi-bar-group span { font-size: 10px; color: rgba(255,255,255,.5); }
+
+@media (max-width: 768px) {
+ .sol-hero-grid { grid-template-columns: 1fr; }
+ .sol-visual { order: -1; }
+}
diff --git a/frontend/src/pages/SolutionPage.jsx b/frontend/src/pages/SolutionPage.jsx
new file mode 100644
index 00000000..03874f71
--- /dev/null
+++ b/frontend/src/pages/SolutionPage.jsx
@@ -0,0 +1,292 @@
+import React from 'react';
+import { Routes, Route, NavLink } from 'react-router-dom';
+import { Link } from 'react-router-dom';
+import './Common.css';
+import './SolutionPage.css';
+
+const SUB_NAV = [
+ { path: '/solution/guardia', label: 'GUARDiA ITSM', badge: 'NEW' },
+ { path: '/solution/erp', label: 'ERP' },
+ { path: '/solution/crm', label: 'CRM' },
+ { path: '/solution/bi', label: 'BI' },
+];
+
+function SubNav({ title }) {
+ return (
+ <>
+
+
+ Solution
+
{title}
+
+
+
+ >
+ );
+}
+
+/* ── ERP ── */
+function ERP() {
+ const modules = [
+ { icon: '💰', name: '재무·회계', desc: '전표처리, 결산, 세무신고, 원가계산 자동화' },
+ { icon: '🏭', name: '생산관리', desc: 'BOM 관리, 생산계획, 공정관리, 품질관리' },
+ { icon: '📦', name: '구매·재고', desc: '발주, 입출고, 재고 현황, 협력사 포털' },
+ { icon: '👥', name: '인사·급여', desc: '근태관리, 급여계산, 조직도, 인사평가' },
+ { icon: '🛒', name: '영업·물류', desc: '수주관리, 배송, 매출 분석, 고객 관리' },
+ { icon: '📊', name: '경영 분석', desc: 'KPI 대시보드, 예산 vs 실적, 경영 보고서' },
+ ];
+ return (
+
+
+
+
+
+
+
Enterprise Resource Planning
+
공공·중견기업 맞춤형
통합 ERP 솔루션
+
+ 20년 이상 현대모비스, 한화그룹, 이마트 등 국내 주요 기업의 핵심 업무 시스템을 구축한 경험을 바탕으로,
+ 고객사의 업무 프로세스에 최적화된 맞춤형 ERP를 제공합니다.
+
+
+ {['공공기관 표준 회계 기준 적용', 'Oracle / Tibero DB 지원', '모바일 결재·보고 지원', '기존 레거시 시스템 연계'].map((f, i) => (
+
+ ✓ {f}
+
+ ))}
+
+
+ 무료 데모 신청
+ 카탈로그 다운로드
+
+
+
+
+
재무 대시보드
+
+ {[80, 65, 90, 72, 88, 55, 95].map((h, i) => (
+
+ ))}
+
+
+
₩12.4억이번달 매출
+
98.2%예산 집행률
+
+18%전월 대비
+
+
+
+
+
+ {/* 모듈 */}
+
+
+ Modules
+
6대 핵심 모듈
+
+
+ {modules.map((m, i) => (
+
+
{m.icon}
+
{m.name}
+
{m.desc}
+
+ ))}
+
+
+
+
+
+ );
+}
+
+/* ── CRM ── */
+function CRM() {
+ const features = [
+ { icon: '📇', name: '고객 360˚', desc: '고객 정보, 구매이력, 상담이력, 선호도를 단일 뷰로 통합' },
+ { icon: '📞', name: '멀티채널 상담', desc: '전화·이메일·채팅·SNS 통합 인입, 상담 이력 자동 기록' },
+ { icon: '🎯', name: '영업 파이프라인', desc: '리드 발굴부터 계약까지 전 단계 시각화 관리' },
+ { icon: '📨', name: '마케팅 자동화', desc: '고객 세그먼트별 자동 캠페인, 이메일·SMS 발송' },
+ { icon: '🤖', name: 'AI 상담 추천', desc: 'Ollama LLM 기반 최적 답변 자동 추천 및 요약' },
+ { icon: '📈', name: '성과 분석', desc: '상담사별·채널별 KPI, 고객 만족도, 전환율 리포트' },
+ ];
+ return (
+
+
+
+
+
+
+
Customer Relationship Management
+
AI 기반
고객 관계 관리 플랫폼
+
+ 삼성전자 차세대 CRM, LG U+ VAN 고도화, 현대캐피탈 차세대 시스템 등
+ 국내 최대 규모 CRM 프로젝트를 성공적으로 수행한 전문 역량으로 구축합니다.
+ 온프레미스 AI(Ollama) 연동으로 데이터 외부 유출 없이 지능형 상담을 실현합니다.
+
+
+ {['삼성전자·LG·현대 구축 레퍼런스', '온프레미스 AI 상담 추천', 'CTI 연동 (콜센터 솔루션)', '공공기관 개인정보보호법 준수'].map((f, i) => (
+
+ ✓ {f}
+
+ ))}
+
+
+ 데모 신청
+ 카탈로그
+
+
+
+
+
고객 상담 현황
+
+ {[
+ { name: '김민준', type: '제품문의', status: '처리중', color: '#f59e0b' },
+ { name: '이서연', type: '기술지원', status: '완료', color: '#10b981' },
+ { name: '박지후', type: '불만접수', status: '대기', color: '#ef4444' },
+ { name: '최수아', type: '데모신청', status: '완료', color: '#10b981' },
+ ].map((c, i) => (
+
+
{c.name[0]}
+
+ {c.name}
+ {c.type}
+
+
{c.status}
+
+ ))}
+
+
+
+
+
+
+
+ Features
+
주요 기능
+
+
+ {features.map((f, i) => (
+
+
{f.icon}
+
{f.name}
+
{f.desc}
+
+ ))}
+
+
+
+
+
+ );
+}
+
+/* ── BI ── */
+function BI() {
+ const charts = [
+ { icon: '📊', name: '경영 대시보드', desc: '실시간 KPI 모니터링, 부서별 성과 지표, 경영진 요약 보고' },
+ { icon: '📉', name: '매출·비용 분석', desc: '기간별·제품별·채널별 매출 트렌드, 비용 구조 분석' },
+ { icon: '🗺️', name: '지역별 분석', desc: '지도 기반 시각화, 공공기관 지역별 서비스 현황' },
+ { icon: '🔮', name: 'AI 예측 분석', desc: '머신러닝 기반 수요 예측, 이상 패턴 자동 탐지' },
+ { icon: '📋', name: '자동 보고서', desc: '일·주·월 보고서 자동 생성, 이메일·메신저 배포' },
+ { icon: '🔗', name: 'ETL 파이프라인', desc: 'Oracle, SAP, 공공DB 등 다양한 소스 데이터 연계' },
+ ];
+ return (
+
+
+
+
+
+
+
Business Intelligence
+
데이터 기반
의사결정 플랫폼
+
+ OZ Report, MiPlatform, JasperReports 등 다양한 보고 도구와의 연동 경험을 바탕으로,
+ 공공기관·중견기업 맞춤형 BI 플랫폼을 구축합니다.
+ 기존 레거시 DB에서 실시간 데이터를 수집해 경영 인사이트를 제공합니다.
+
+
+ {['OZ·MiPlatform·JasperReport 연동', '실시간 대시보드 (WebSocket)', 'Oracle·Tibero·PostgreSQL 지원', '공공기관 표준 보고서 양식'].map((f, i) => (
+
+ ✓ {f}
+
+ ))}
+
+
+ 데모 신청
+ 카탈로그
+
+
+
+
+
경영 대시보드
+
+ {[
+ { label:'매출', val:'₩48.2억', up:true, delta:'+12%' },
+ { label:'비용', val:'₩31.7억', up:false, delta:'-3%' },
+ { label:'이익', val:'₩16.5억', up:true, delta:'+28%' },
+ { label:'고객', val:'1,240명', up:true, delta:'+8%' },
+ ].map((k, i) => (
+
+ {k.label}
+ {k.val}
+
+ {k.delta}
+
+
+ ))}
+
+
+ {['1Q','2Q','3Q','4Q'].map((q, i) => (
+
+ ))}
+
+
+
+
+
+
+
+ Features
+
주요 기능
+
+
+ {charts.map((c, i) => (
+
+
{c.icon}
+
{c.name}
+
{c.desc}
+
+ ))}
+
+
+
+
+
+ );
+}
+
+export default function SolutionPage() {
+ return (
+
+ } />
+ } />
+ } />
+
+ );
+}
diff --git a/frontend/src/pages/Support.css b/frontend/src/pages/Support.css
new file mode 100644
index 00000000..0d0ed755
--- /dev/null
+++ b/frontend/src/pages/Support.css
@@ -0,0 +1,40 @@
+/* 공지사항 */
+.notice-header-row { display:grid; grid-template-columns:80px 1fr 100px; gap:16px; padding:12px 16px; background:var(--gray-50); border-radius:8px 8px 0 0; font-size:12px; font-weight:700; color:var(--gray-500); border:1px solid var(--gray-200); border-bottom:none; }
+.notice-row { display:grid; grid-template-columns:80px 1fr 100px; gap:16px; padding:14px 16px; border:1px solid var(--gray-200); border-top:none; cursor:pointer; align-items:center; transition:background var(--fast); }
+.notice-row:last-child { border-radius:0 0 8px 8px; }
+.notice-row:hover { background:var(--gray-50); }
+.notice-cat { display:inline-block; padding:3px 10px; border-radius:12px; font-size:11px; font-weight:700; text-align:center; }
+.notice-title-text { font-size:14px; color:var(--gray-800); display:flex; align-items:center; gap:8px; }
+.notice-hot { background:var(--danger); color:#fff; font-size:10px; padding:2px 6px; border-radius:4px; font-weight:700; flex-shrink:0; }
+.notice-date { font-size:12px; color:var(--gray-500); }
+.notice-detail { max-width:760px; }
+.notice-back { font-size:14px; color:var(--primary); margin-bottom:24px; display:inline-flex; align-items:center; gap:4px; cursor:pointer; background:none; border:none; }
+.notice-detail-header { border-bottom:2px solid var(--gray-200); padding-bottom:20px; margin-bottom:32px; }
+.notice-detail-header h2 { font-size:22px; font-weight:800; margin:12px 0 8px; }
+.notice-body { display:flex; flex-direction:column; gap:16px; font-size:15px; line-height:1.85; color:var(--gray-700); }
+
+/* FAQ */
+.faq-cat-wrap { margin-bottom:40px; }
+.faq-cat-title { font-size:16px; font-weight:800; color:var(--primary); margin-bottom:12px; padding-bottom:8px; border-bottom:2px solid var(--primary-light); }
+.faq-item { border:1px solid var(--gray-200); border-radius:8px; margin-bottom:8px; overflow:hidden; transition:box-shadow var(--fast); }
+.faq-item.open { box-shadow:var(--shadow); border-color:var(--primary-light); }
+.faq-q { width:100%; display:flex; align-items:center; gap:14px; padding:16px 20px; font-size:15px; font-weight:600; color:var(--gray-800); text-align:left; background:none; border:none; cursor:pointer; transition:background var(--fast); }
+.faq-q:hover { background:var(--gray-50); }
+.faq-icon { width:24px; height:24px; border-radius:50%; background:var(--primary); color:#fff; display:flex; align-items:center; justify-content:center; font-size:16px; flex-shrink:0; }
+.faq-a { padding:0 20px 20px 58px; font-size:14px; color:var(--gray-600); line-height:1.8; }
+.faq-more { text-align:center; padding:48px; background:var(--gray-50); border-radius:12px; margin-top:32px; }
+.faq-more p { color:var(--gray-600); margin-bottom:20px; font-size:16px; }
+
+/* 카탈로그 */
+.catalog-card { padding:0; display:flex; flex-direction:column; }
+.catalog-icon-wrap { padding:32px; display:flex; align-items:center; justify-content:center; }
+.catalog-icon { font-size:48px; }
+.catalog-info { padding:0 24px 16px; flex:1; }
+.catalog-title { font-size:15px; font-weight:700; margin-bottom:8px; color:var(--gray-900); }
+.catalog-desc { font-size:13px; color:var(--gray-600); margin-bottom:12px; line-height:1.6; }
+.catalog-meta { display:flex; gap:12px; font-size:12px; color:var(--gray-400); }
+.catalog-btn { margin:0 16px 20px; padding:12px; border-radius:8px; background:var(--gray-50); border:1px solid var(--gray-200); font-size:14px; font-weight:600; color:var(--primary); cursor:pointer; transition:all var(--fast); text-align:center; }
+.catalog-btn:hover { background:var(--primary); color:#fff; border-color:var(--primary); }
+.catalog-request { text-align:center; padding:56px; background:var(--gray-50); border-radius:16px; margin-top:48px; }
+.catalog-request h3 { font-size:22px; font-weight:800; margin-bottom:12px; }
+.catalog-request p { color:var(--gray-600); margin-bottom:24px; }
diff --git a/frontend/src/pages/Support.jsx b/frontend/src/pages/Support.jsx
new file mode 100644
index 00000000..49c0878a
--- /dev/null
+++ b/frontend/src/pages/Support.jsx
@@ -0,0 +1,226 @@
+import React, { useState } from 'react';
+import { Routes, Route, NavLink } from 'react-router-dom';
+import './Common.css';
+import './Support.css';
+
+const SUB_NAV = [
+ { path: '/support/notice', label: '공지사항' },
+ { path: '/support/faq', label: 'FAQ' },
+ { path: '/support/catalog', label: '카탈로그' },
+ { path: '/support/contact', label: '문의하기' },
+];
+
+function SubNav({ title }) {
+ return (
+ <>
+
+
+ Support
+
{title}
+
+
+
+ >
+ );
+}
+
+/* ── 공지사항 ── */
+const NOTICES = [
+ { id:1, cat:'공지', title:'GUARDiA ITSM v2.0 정식 출시 안내', date:'2026.05.15', hot:true },
+ { id:2, cat:'공지', title:'2026년 상반기 유지보수 점검 일정 안내 (6월 1일~2일)', date:'2026.05.10', hot:false },
+ { id:3, cat:'보안', title:'Apache Log4j 취약점 긴급 패치 안내', date:'2026.04.28', hot:false },
+ { id:4, cat:'공지', title:'개인정보처리방침 개정 안내 (2026년 4월)', date:'2026.04.01', hot:false },
+ { id:5, cat:'이벤트', title:'2026 공공기관 디지털전환 세미나 참가 안내 (5월 20일)', date:'2026.03.25', hot:false },
+ { id:6, cat:'공지', title:'GUARDiA ITSM GS인증 1등급 신청 완료 안내', date:'2026.03.10', hot:false },
+ { id:7, cat:'공지', title:'신규 파트너사 협약 체결 — Tibero 공식 파트너 등록', date:'2026.02.20', hot:false },
+ { id:8, cat:'보안', title:'2026년 정보보안 교육 실시 안내 (임직원 필독)', date:'2026.01.15', hot:false },
+ { id:9, cat:'공지', title:'2025년 사업성과 및 2026년 사업계획 발표', date:'2026.01.02', hot:false },
+ { id:10,'cat':'공지', title:'연말연시 고객지원팀 운영시간 안내 (12/24~1/3)', date:'2025.12.20', hot:false },
+];
+const CAT_COLORS = { '공지':'var(--primary)', '보안':'var(--danger)', '이벤트':'var(--accent)' };
+
+function Notice() {
+ const [selected, setSelected] = useState(null);
+ return (
+
+
+
+
+ {selected ? (
+
+
+
+
{selected.cat}
+
{selected.title}
+
{selected.date}
+
+
+
안녕하세요, (주)지오정보기술입니다.
+
본 공지는 {selected.title}에 관한 안내입니다.
+
자세한 사항은 고객지원팀(02-784-9271)으로 문의해 주시기 바랍니다.
+
감사합니다.
+
+
+ ) : (
+
+
+ 구분제목등록일
+
+ {NOTICES.map(n => (
+
setSelected(n)}>
+ {n.cat}
+
+ {n.hot && HOT}
+ {n.title}
+
+ {n.date}
+
+ ))}
+
+ )}
+
+
+
+ );
+}
+
+/* ── FAQ ── */
+const FAQS = [
+ {
+ cat: 'GUARDiA ITSM',
+ items: [
+ { q: 'GUARDiA ITSM은 어떤 제품인가요?', a: 'GUARDiA ITSM은 메신저 한 줄 명령으로 1,000개 이상 공공기관의 레거시 IT 인프라를 자동 운영하는 AI 기반 ChatOps 플랫폼입니다. 대상 서버에 별도 소프트웨어 설치 없이 표준 SSH/SFTP 프로토콜만으로 배포·운영·모니터링을 자동화합니다.' },
+ { q: '서버에 에이전트를 설치해야 하나요?', a: '아니요. GUARDiA ITSM은 에이전트리스(Agentless) 방식으로 동작합니다. 대상 서버에 어떠한 소프트웨어도 설치할 필요가 없으며, 표준 SSH(22번 포트)만 열려 있으면 즉시 연동 가능합니다.' },
+ { q: '클라우드 없이 사용할 수 있나요?', a: '예. GUARDiA ITSM은 완전한 온프레미스(On-premise) 솔루션으로, 외부 클라우드나 인터넷 연결 없이 폐쇄망 환경에서도 100% 동작합니다. AI 엔진(Ollama)도 내부 서버에서 구동됩니다.' },
+ { q: '지원되는 운영체제는 무엇인가요?', a: 'GUARDiA ITSM 서버: Ubuntu 20.04+, CentOS 7+, RHEL 8+, Windows Server 2019+를 지원합니다. 관리 대상 서버: SSH가 지원되는 모든 Linux/Unix/Windows Server 환경에서 사용 가능합니다.' },
+ ]
+ },
+ {
+ cat: '도입·계약',
+ items: [
+ { q: '도입 비용은 어떻게 되나요?', a: '기관 규모와 관리 서버 수에 따라 맞춤 견적을 제공합니다. 7일 무료 체험판을 먼저 신청하신 후 문의 주시면 상세한 견적을 안내해 드립니다.' },
+ { q: '체험판을 사용할 수 있나요?', a: '예. 7일 무료 체험판을 제공합니다. 문의하기 또는 GUARDiA 페이지의 "무료 데모 신청" 버튼을 통해 신청하시면 영업일 기준 1일 이내에 안내 드립니다.' },
+ { q: '공공기관 나라장터 조달 구매가 가능한가요?', a: '예. GUARDiA ITSM은 조달청 나라장터 등록을 준비 중이며, 공공기관 입찰을 통한 구매를 지원합니다. 자세한 사항은 영업팀(02-784-9271)에 문의해 주십시오.' },
+ ]
+ },
+ {
+ cat: '기술 지원',
+ items: [
+ { q: '기술 지원은 어떻게 받을 수 있나요?', a: '이메일(support@zioinfo.co.kr), 전화(02-784-9271), GUARDiA ITSM 내 챗봇을 통해 기술 지원을 제공합니다. 운영 중 긴급 장애는 24시간 온콜 지원이 가능합니다.' },
+ { q: '업그레이드는 어떻게 진행되나요?', a: '정기 업데이트는 연 2~4회 제공되며, 보안 패치는 즉시 제공됩니다. 업그레이드는 GUARDiA 내 자동 배포 기능을 통해 다운타임 없이 진행할 수 있습니다.' },
+ ]
+ },
+];
+
+function FAQ() {
+ const [openIdx, setOpenIdx] = useState({});
+ const toggle = (ci, qi) => setOpenIdx(p => ({ ...p, [`${ci}-${qi}`]: !p[`${ci}-${qi}`] }));
+ return (
+
+
+
+
+
+ FAQ
+
자주 묻는 질문
+
+ {FAQS.map((cat, ci) => (
+
+
{cat.cat}
+ {cat.items.map((item, qi) => {
+ const key = `${ci}-${qi}`;
+ const open = openIdx[key];
+ return (
+
+
+ {open &&
{item.a}
}
+
+ );
+ })}
+
+ ))}
+
+
+
+
+ );
+}
+
+/* ── 카탈로그 ── */
+const CATALOGS = [
+ { title:'GUARDiA ITSM v2.0 제품 카탈로그', desc:'GUARDiA ITSM 전체 기능 및 도입 가이드', pages:'24p', size:'4.2MB', date:'2026.05', icon:'⚡', color:'var(--primary)' },
+ { title:'GUARDiA ITSM 기술 명세서', desc:'API 명세, 아키텍처, 보안 설계 문서', pages:'48p', size:'8.1MB', date:'2026.05', icon:'📐', color:'#7c3aed' },
+ { title:'ERP 솔루션 카탈로그', desc:'공공·중견기업 맞춤형 ERP 모듈 소개', pages:'16p', size:'3.5MB', date:'2026.03', icon:'💰', color:'#059669' },
+ { title:'CRM 솔루션 카탈로그', desc:'AI 기반 고객관리 플랫폼 소개', pages:'12p', size:'2.8MB', date:'2026.03', icon:'📞', color:'#d97706' },
+ { title:'BI 솔루션 카탈로그', desc:'경영 분석 및 대시보드 플랫폼 소개', pages:'12p', size:'2.4MB', date:'2026.02', icon:'📊', color:'#0891b2' },
+ { title:'지오정보기술 회사 소개서', desc:'회사 연혁, 사업 영역, 주요 레퍼런스', pages:'20p', size:'5.6MB', date:'2026.01', icon:'🏢', color:'var(--secondary)' },
+];
+
+function Catalog() {
+ return (
+
+
+
+
+
+
Catalog
+
자료실
+
제품 카탈로그 및 기술 문서를 다운로드하세요
+
+
+ {CATALOGS.map((c, i) => (
+
+
+ {c.icon}
+
+
+
{c.title}
+
{c.desc}
+
+ {c.pages}
+ {c.size}
+ {c.date}
+
+
+
+
+ ))}
+
+
+
다른 자료가 필요하신가요?
+
NDA 자료, 기술 제안서, 가격표 등 별도 문서는 영업팀에 요청해 주십시오.
+
자료 요청하기
+
+
+
+
+ );
+}
+
+export default function Support() {
+ return (
+
+ } />
+ } />
+ } />
+ } />
+
+ );
+}
diff --git a/frontend/src/pages/admin/AdminDashboard.jsx b/frontend/src/pages/admin/AdminDashboard.jsx
new file mode 100644
index 00000000..cd8affdf
--- /dev/null
+++ b/frontend/src/pages/admin/AdminDashboard.jsx
@@ -0,0 +1,93 @@
+import { useEffect, useState } from 'react';
+import { Link } from 'react-router-dom';
+
+const API = (path) => fetch(path, {
+ headers: { Authorization: `Bearer ${localStorage.getItem('admin_token')}` },
+}).then(r => r.json());
+
+export default function AdminDashboard() {
+ const [stats, setStats] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ API('/api/admin/dashboard')
+ .then(setStats)
+ .finally(() => setLoading(false));
+ }, []);
+
+ if (loading) return 로딩 중...
;
+ if (!stats) return null;
+
+ const STAT_CARDS = [
+ { icon: '📰', label: '전체 뉴스', value: stats.totalNews, sub: `공개 ${stats.visibleNews}건`, color: 'blue' },
+ { icon: '📩', label: '전체 문의', value: stats.totalInquiries, sub: `미답변 ${stats.pendingInquiries}건`, color: stats.pendingInquiries > 0 ? 'red' : 'green' },
+ { icon: '👥', label: '채용공고', value: stats.totalRecruits, sub: `진행중 ${stats.activeRecruits}건`, color: 'green' },
+ ];
+
+ return (
+ <>
+ {/* Stats */}
+
+ {STAT_CARDS.map(s => (
+
+
{s.icon}
+
+
{s.value}
+
{s.label}
{s.sub}
+
+
+ ))}
+ {stats.pendingInquiries > 0 && (
+
+
🔔
+
+
{stats.pendingInquiries}
+
미답변 문의
바로가기 →
+
+
+ )}
+
+
+ {/* Recent panels */}
+
+
+
+
📰 최근 뉴스
+ 전체보기
+
+
+ {(stats.recentNews || []).map(n => (
+ -
+
+ {n.title}
+ {n.category}
+
+ ))}
+ {!stats.recentNews?.length && (
+ - 등록된 뉴스가 없습니다.
+ )}
+
+
+
+
+
+
📩 최근 문의
+ 전체보기
+
+
+ {(stats.recentInquiries || []).map(q => (
+ -
+
+ {q.subject}
+ {q.name}
+
+ ))}
+ {!stats.recentInquiries?.length && (
+ - 접수된 문의가 없습니다.
+ )}
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/admin/AdminInquiry.jsx b/frontend/src/pages/admin/AdminInquiry.jsx
new file mode 100644
index 00000000..42a73023
--- /dev/null
+++ b/frontend/src/pages/admin/AdminInquiry.jsx
@@ -0,0 +1,152 @@
+import { useEffect, useState, useCallback } from 'react';
+
+const token = () => localStorage.getItem('admin_token');
+const authFetch = (url, opts = {}) =>
+ fetch(url, { ...opts, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token()}`, ...opts.headers } });
+
+const STATUS_LABEL = { PENDING: '미답변', ANSWERED: '답변완료', CLOSED: '종결' };
+const STATUS_BADGE = { PENDING: 'badge-red', ANSWERED: 'badge-green', CLOSED: 'badge-gray' };
+
+export default function AdminInquiry() {
+ const [page, setPage] = useState(0);
+ const [filter, setFilter] = useState('');
+ const [data, setData] = useState({ content: [], totalPages: 0, totalElements: 0 });
+ const [selected, setSelected] = useState(null);
+ const [toast, setToast] = useState(null);
+
+ const showToast = (msg, type = 'success') => { setToast({ msg, type }); setTimeout(() => setToast(null), 2500); };
+
+ const load = useCallback(() => {
+ const q = filter ? `&status=${filter}` : '';
+ authFetch(`/api/admin/inquiries?page=${page}&size=10${q}`)
+ .then(r => r.json()).then(setData);
+ }, [page, filter]);
+
+ useEffect(() => { load(); }, [load]);
+
+ const handleStatus = async (id, status) => {
+ const res = await authFetch(`/api/admin/inquiries/${id}/status`, {
+ method: 'PATCH', body: JSON.stringify({ status }),
+ });
+ if (res.ok) {
+ load();
+ if (selected?.id === id) setSelected(p => ({ ...p, status }));
+ showToast('상태가 변경되었습니다.');
+ }
+ };
+
+ const openDetail = async (id) => {
+ const res = await authFetch(`/api/admin/inquiries/${id}`);
+ if (res.ok) setSelected(await res.json());
+ };
+
+ return (
+ <>
+ {toast && }
+
+
+
+ 전체 {data.totalElements}건
+
+
+
+
+
+
+ | No | 이름 | 제목 | 카테고리 | 상태 | 접수일 | 관리 |
+
+
+ {data.content.map((q, i) => (
+
+ | {data.totalElements - page * 10 - i} |
+ {q.name} |
+ openDetail(q.id)}>
+ {q.subject}
+ |
+ {q.category || '기타'} |
+ {STATUS_LABEL[q.status] || q.status} |
+ {q.createdAt?.slice(0, 10)} |
+
+
+ {q.status === 'PENDING' && (
+
+ )}
+ {q.status !== 'CLOSED' && (
+
+ )}
+
+ |
+
+ ))}
+ {!data.content.length && (
+ |
+ )}
+
+
+
+
+ {data.totalPages > 1 && (
+
+
페이지 {page + 1} / {data.totalPages}
+
+
+ {Array.from({ length: Math.min(data.totalPages, 7) }, (_, i) => (
+
+ ))}
+
+
+
+ )}
+
+
+ {selected && (
+ e.target === e.currentTarget && setSelected(null)}>
+
+
+
문의 상세
+
+
+
+
+ {[['이름', selected.name], ['이메일', selected.email], ['연락처', selected.phone || '-'], ['유형', selected.category || '기타']].map(([l, v]) => (
+
+ ))}
+
+
+
제목
+
{selected.subject}
+
+
+
내용
+
+ {selected.content}
+
+
+
+ 접수일: {selected.createdAt?.slice(0, 16)}
+ {STATUS_LABEL[selected.status]}
+
+
+
+ {selected.status === 'PENDING' && (
+
+ )}
+ {selected.status !== 'CLOSED' && (
+
+ )}
+
+
+
+
+ )}
+ >
+ );
+}
diff --git a/frontend/src/pages/admin/AdminLayout.jsx b/frontend/src/pages/admin/AdminLayout.jsx
new file mode 100644
index 00000000..a355799a
--- /dev/null
+++ b/frontend/src/pages/admin/AdminLayout.jsx
@@ -0,0 +1,108 @@
+import { useEffect, useState } from 'react';
+import { NavLink, Outlet, useNavigate, useLocation } from 'react-router-dom';
+import './admin.css';
+
+const NAV = [
+ { section: '메인' },
+ { path: '/admin/dashboard', icon: '📊', label: '대시보드' },
+ { section: '콘텐츠 관리' },
+ { path: '/admin/news', icon: '📰', label: '뉴스/공지사항' },
+ { path: '/admin/recruit', icon: '👥', label: '채용공고' },
+ { section: '고객 관리' },
+ { path: '/admin/inquiries', icon: '📩', label: '문의 관리', badgeKey: 'pendingInquiries' },
+ { section: '시스템' },
+ { path: '/admin/settings', icon: '⚙️', label: '설정' },
+];
+
+export default function AdminLayout() {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const [user, setUser] = useState(null);
+ const [pageTitle, setPageTitle] = useState('대시보드');
+ const [badges, setBadges] = useState({});
+
+ useEffect(() => {
+ const token = localStorage.getItem('admin_token');
+ if (!token) { navigate('/admin/login'); return; }
+ const userData = JSON.parse(localStorage.getItem('admin_user') || '{}');
+ setUser(userData);
+ fetchBadges(token);
+ }, [navigate]);
+
+ useEffect(() => {
+ const map = {
+ '/admin/dashboard': '대시보드',
+ '/admin/news': '뉴스/공지사항 관리',
+ '/admin/inquiries': '문의 관리',
+ '/admin/recruit': '채용공고 관리',
+ '/admin/settings': '설정',
+ };
+ setPageTitle(map[location.pathname] || '관리자');
+ }, [location.pathname]);
+
+ const fetchBadges = async (token) => {
+ try {
+ const res = await fetch('/api/admin/dashboard', {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+ if (res.ok) {
+ const d = await res.json();
+ setBadges({ pendingInquiries: d.pendingInquiries || 0 });
+ }
+ } catch {}
+ };
+
+ const logout = () => {
+ localStorage.removeItem('admin_token');
+ localStorage.removeItem('admin_user');
+ navigate('/admin/login');
+ };
+
+ if (!user) return null;
+
+ return (
+
+ );
+}
diff --git a/frontend/src/pages/admin/AdminLogin.jsx b/frontend/src/pages/admin/AdminLogin.jsx
new file mode 100644
index 00000000..aeb99d09
--- /dev/null
+++ b/frontend/src/pages/admin/AdminLogin.jsx
@@ -0,0 +1,66 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import './admin.css';
+
+export default function AdminLogin() {
+ const [form, setForm] = useState({ username: '', password: '' });
+ const [error, setError] = useState('');
+ const [loading, setLoading] = useState(false);
+ const navigate = useNavigate();
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setError(''); setLoading(true);
+ try {
+ const res = await fetch('/api/admin/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(form),
+ });
+ const data = await res.json();
+ if (!res.ok) { setError(data.message || '로그인 실패'); return; }
+ localStorage.setItem('admin_token', data.token);
+ localStorage.setItem('admin_user', JSON.stringify({ username: data.username, displayName: data.displayName }));
+ navigate('/admin/dashboard');
+ } catch {
+ setError('서버 연결 오류가 발생했습니다.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
ADMIN
+
(주)지오정보기술
+
홈페이지 관리자 시스템
+
+ {error &&
⚠ {error}
}
+
+
+ 홈페이지로 돌아가기: 메인 페이지
+
+
+
+ );
+}
diff --git a/frontend/src/pages/admin/AdminNews.jsx b/frontend/src/pages/admin/AdminNews.jsx
new file mode 100644
index 00000000..c924a766
--- /dev/null
+++ b/frontend/src/pages/admin/AdminNews.jsx
@@ -0,0 +1,175 @@
+import { useEffect, useState, useCallback } from 'react';
+
+const token = () => localStorage.getItem('admin_token');
+const authFetch = (url, opts = {}) =>
+ fetch(url, { ...opts, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token()}`, ...opts.headers } });
+
+const EMPTY = { title: '', category: '공지사항', summary: '', content: '', thumbnailUrl: '', visible: true };
+const CATS = ['공지사항', '보도자료', '이벤트'];
+
+export default function AdminNews() {
+ const [page, setPage] = useState(0);
+ const [data, setData] = useState({ content: [], totalPages: 0, totalElements: 0 });
+ const [modal, setModal] = useState(null); // null | 'create' | 'edit'
+ const [form, setForm] = useState(EMPTY);
+ const [editId, setEditId] = useState(null);
+ const [saving, setSaving] = useState(false);
+ const [toast, setToast] = useState(null);
+
+ const showToast = (msg, type = 'success') => {
+ setToast({ msg, type });
+ setTimeout(() => setToast(null), 2500);
+ };
+
+ const load = useCallback(() => {
+ authFetch(`/api/admin/news?page=${page}&size=10`)
+ .then(r => r.json()).then(setData);
+ }, [page]);
+
+ useEffect(() => { load(); }, [load]);
+
+ const openCreate = () => { setForm(EMPTY); setEditId(null); setModal('form'); };
+ const openEdit = (n) => { setForm({ ...n }); setEditId(n.id); setModal('form'); };
+
+ const handleSave = async () => {
+ setSaving(true);
+ const url = editId ? `/api/admin/news/${editId}` : '/api/admin/news';
+ const method = editId ? 'PUT' : 'POST';
+ const res = await authFetch(url, { method, body: JSON.stringify(form) });
+ setSaving(false);
+ if (res.ok) { setModal(null); load(); showToast(editId ? '수정되었습니다.' : '등록되었습니다.'); }
+ else showToast('저장 실패', 'error');
+ };
+
+ const handleDelete = async (id) => {
+ if (!confirm('삭제하시겠습니까?')) return;
+ const res = await authFetch(`/api/admin/news/${id}`, { method: 'DELETE' });
+ if (res.ok) { load(); showToast('삭제되었습니다.'); }
+ };
+
+ const toggleVisible = async (id) => {
+ await authFetch(`/api/admin/news/${id}/visibility`, { method: 'PATCH' });
+ load();
+ };
+
+ const set = (k, v) => setForm(p => ({ ...p, [k]: v }));
+
+ return (
+ <>
+ {toast && (
+
+ )}
+
+
+
+
전체 {data.totalElements}건
+
+
+
+
+
+
+
+
+
+ | No | 제목 | 카테고리 | 공개 | 조회수 | 등록일 | 관리 |
+
+
+
+ {data.content.map((n, i) => (
+
+ | {data.totalElements - page * 10 - i} |
+ {n.title} |
+ {n.category} |
+
+
+ |
+ {n.viewCount} |
+ {n.createdAt?.slice(0, 10)} |
+
+
+
+
+
+ |
+
+ ))}
+ {!data.content.length && (
+ |
+ )}
+
+
+
+
+ {data.totalPages > 1 && (
+
+
페이지 {page + 1} / {data.totalPages}
+
+
+ {Array.from({ length: data.totalPages }, (_, i) => (
+
+ ))}
+
+
+
+ )}
+
+
+ {modal === 'form' && (
+ e.target === e.currentTarget && setModal(null)}>
+
+
+
{editId ? '뉴스 수정' : '뉴스 등록'}
+
+
+
+
+
+ set('title', e.target.value)} placeholder="뉴스 제목을 입력하세요" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ set('summary', e.target.value)} placeholder="목록에 표시될 요약 문구" />
+
+
+
+ set('thumbnailUrl', e.target.value)} placeholder="https://..." />
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+}
diff --git a/frontend/src/pages/admin/AdminRecruit.jsx b/frontend/src/pages/admin/AdminRecruit.jsx
new file mode 100644
index 00000000..0fe5e0a2
--- /dev/null
+++ b/frontend/src/pages/admin/AdminRecruit.jsx
@@ -0,0 +1,169 @@
+import { useEffect, useState, useCallback } from 'react';
+
+const token = () => localStorage.getItem('admin_token');
+const authFetch = (url, opts = {}) =>
+ fetch(url, { ...opts, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token()}`, ...opts.headers } });
+
+const EMPTY = { title: '', department: '', jobType: '정규직', description: '', requirements: '', preferred: '', deadline: '', headcount: 1, active: true };
+const JOB_TYPES = ['정규직', '계약직', '인턴', '프리랜서'];
+
+export default function AdminRecruit() {
+ const [page, setPage] = useState(0);
+ const [data, setData] = useState({ content: [], totalPages: 0, totalElements: 0 });
+ const [modal, setModal] = useState(false);
+ const [form, setForm] = useState(EMPTY);
+ const [editId, setEditId] = useState(null);
+ const [saving, setSaving] = useState(false);
+ const [toast, setToast] = useState(null);
+
+ const showToast = (msg, type = 'success') => { setToast({ msg, type }); setTimeout(() => setToast(null), 2500); };
+
+ const load = useCallback(() => {
+ authFetch(`/api/admin/recruits?page=${page}&size=10`)
+ .then(r => r.json()).then(setData);
+ }, [page]);
+
+ useEffect(() => { load(); }, [load]);
+
+ const openCreate = () => { setForm(EMPTY); setEditId(null); setModal(true); };
+ const openEdit = (r) => { setForm({ ...r, deadline: r.deadline || '' }); setEditId(r.id); setModal(true); };
+
+ const handleSave = async () => {
+ setSaving(true);
+ const url = editId ? `/api/admin/recruits/${editId}` : '/api/admin/recruits';
+ const res = await authFetch(url, { method: editId ? 'PUT' : 'POST', body: JSON.stringify(form) });
+ setSaving(false);
+ if (res.ok) { setModal(false); load(); showToast(editId ? '수정되었습니다.' : '등록되었습니다.'); }
+ else showToast('저장 실패', 'error');
+ };
+
+ const handleDelete = async (id) => {
+ if (!confirm('삭제하시겠습니까?')) return;
+ const res = await authFetch(`/api/admin/recruits/${id}`, { method: 'DELETE' });
+ if (res.ok) { load(); showToast('삭제되었습니다.'); }
+ };
+
+ const set = (k, v) => setForm(p => ({ ...p, [k]: v }));
+
+ return (
+ <>
+ {toast && }
+
+
+
+
전체 {data.totalElements}건
+
+
+
+
+
+
+
+
+ | No | 공고명 | 부서 | 유형 | 모집인원 | 마감일 | 상태 | 관리 |
+
+
+ {data.content.map((r, i) => (
+
+ | {data.totalElements - page * 10 - i} |
+ {r.title} |
+ {r.department || '-'} |
+ {r.jobType} |
+ {r.headcount}명 |
+ {r.deadline || '상시'} |
+ {r.active ? '진행중' : '마감'} |
+
+
+
+
+
+ |
+
+ ))}
+ {!data.content.length && (
+ |
+ )}
+
+
+
+
+ {data.totalPages > 1 && (
+
+
페이지 {page + 1} / {data.totalPages}
+
+
+ {Array.from({ length: data.totalPages }, (_, i) => (
+
+ ))}
+
+
+
+ )}
+
+
+ {modal && (
+ e.target === e.currentTarget && setModal(false)}>
+
+
+
{editId ? '채용공고 수정' : '채용공고 등록'}
+
+
+
+
+
+ set('title', e.target.value)} placeholder="예: 백엔드 개발자 (Java/Spring)" />
+
+
+
+
+ set('department', e.target.value)} placeholder="개발팀, 영업팀 등" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+}
diff --git a/frontend/src/pages/admin/AdminSettings.jsx b/frontend/src/pages/admin/AdminSettings.jsx
new file mode 100644
index 00000000..9cd26843
--- /dev/null
+++ b/frontend/src/pages/admin/AdminSettings.jsx
@@ -0,0 +1,91 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+const token = () => localStorage.getItem('admin_token');
+
+export default function AdminSettings() {
+ const navigate = useNavigate();
+ const user = JSON.parse(localStorage.getItem('admin_user') || '{}');
+ const [form, setForm] = useState({ currentPassword: '', newPassword: '', confirmPassword: '' });
+ const [msg, setMsg] = useState(null);
+ const [saving, setSaving] = useState(false);
+
+ const handleChange = async () => {
+ if (form.newPassword !== form.confirmPassword) {
+ setMsg({ text: '새 비밀번호가 일치하지 않습니다.', type: 'error' }); return;
+ }
+ if (form.newPassword.length < 8) {
+ setMsg({ text: '비밀번호는 8자 이상이어야 합니다.', type: 'error' }); return;
+ }
+ setSaving(true);
+ const res = await fetch('/api/admin/password', {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token()}` },
+ body: JSON.stringify({ currentPassword: form.currentPassword, newPassword: form.newPassword }),
+ });
+ const data = await res.json();
+ setSaving(false);
+ if (res.ok) {
+ setMsg({ text: '비밀번호가 변경되었습니다. 다시 로그인해주세요.', type: 'success' });
+ setForm({ currentPassword: '', newPassword: '', confirmPassword: '' });
+ setTimeout(() => {
+ localStorage.removeItem('admin_token');
+ navigate('/admin/login');
+ }, 2000);
+ } else {
+ setMsg({ text: data.message || '변경 실패', type: 'error' });
+ }
+ };
+
+ return (
+
+ {/* 계정 정보 */}
+
+
👤 계정 정보
+
+ {[['아이디', user.username], ['표시 이름', user.displayName || '-']].map(([l, v]) => (
+
+ {l}
+ {v}
+
+ ))}
+
+
+
+ {/* 비밀번호 변경 */}
+
+
🔒 비밀번호 변경
+ {msg && (
+
+ {msg.text}
+
+ )}
+
+
+ setForm(p => ({ ...p, currentPassword: e.target.value }))} />
+
+
+
+ setForm(p => ({ ...p, newPassword: e.target.value }))} />
+
+
+
+ setForm(p => ({ ...p, confirmPassword: e.target.value }))} />
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/admin/admin.css b/frontend/src/pages/admin/admin.css
new file mode 100644
index 00000000..f73fda5e
--- /dev/null
+++ b/frontend/src/pages/admin/admin.css
@@ -0,0 +1,188 @@
+/* ===== Admin System Styles ===== */
+:root {
+ --admin-sidebar-w: 220px;
+ --admin-bg: #f0f2f5;
+ --admin-sidebar-bg: #1a1d2e;
+ --admin-sidebar-hover: #2a2d3e;
+ --admin-accent: #4f6ef7;
+ --admin-accent-hover: #3a5be0;
+ --admin-text: #1e293b;
+ --admin-muted: #64748b;
+ --admin-border: #e2e8f0;
+ --admin-card: #ffffff;
+ --admin-danger: #ef4444;
+ --admin-success: #22c55e;
+ --admin-warning: #f59e0b;
+}
+
+/* Layout */
+.admin-wrap { display: flex; min-height: 100vh; background: var(--admin-bg); font-family: 'Pretendard', -apple-system, sans-serif; }
+
+/* Sidebar */
+.admin-sidebar {
+ width: var(--admin-sidebar-w);
+ background: var(--admin-sidebar-bg);
+ display: flex; flex-direction: column;
+ position: fixed; top: 0; left: 0; height: 100vh;
+ z-index: 100; transition: transform .25s;
+}
+.admin-sidebar-logo {
+ padding: 20px 20px 16px;
+ border-bottom: 1px solid rgba(255,255,255,.08);
+}
+.admin-sidebar-logo h2 { color: #fff; font-size: 15px; font-weight: 700; margin: 0; }
+.admin-sidebar-logo span { color: #7c85a8; font-size: 11px; }
+
+.admin-nav { flex: 1; overflow-y: auto; padding: 12px 0; }
+.admin-nav-section { padding: 12px 16px 4px; color: #7c85a8; font-size: 10px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; }
+.admin-nav a {
+ display: flex; align-items: center; gap: 10px;
+ padding: 9px 20px; color: #b0b7cc; text-decoration: none;
+ font-size: 13.5px; border-radius: 0; transition: all .15s;
+}
+.admin-nav a:hover { background: var(--admin-sidebar-hover); color: #fff; }
+.admin-nav a.active { background: var(--admin-accent); color: #fff; }
+.admin-nav a .nav-icon { width: 16px; text-align: center; flex-shrink: 0; }
+.admin-nav-badge { background: var(--admin-danger); color: #fff; font-size: 10px; padding: 1px 6px; border-radius: 10px; margin-left: auto; }
+
+.admin-sidebar-footer { padding: 16px 20px; border-top: 1px solid rgba(255,255,255,.08); }
+.admin-sidebar-footer button { width: 100%; background: transparent; border: 1px solid rgba(255,255,255,.15); color: #b0b7cc; padding: 8px 12px; border-radius: 6px; font-size: 13px; cursor: pointer; transition: all .15s; }
+.admin-sidebar-footer button:hover { background: rgba(255,255,255,.08); color: #fff; }
+
+/* Main */
+.admin-main { margin-left: var(--admin-sidebar-w); flex: 1; display: flex; flex-direction: column; min-height: 100vh; }
+.admin-topbar { background: var(--admin-card); border-bottom: 1px solid var(--admin-border); padding: 0 28px; height: 56px; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 50; }
+.admin-topbar h1 { font-size: 16px; font-weight: 600; color: var(--admin-text); margin: 0; }
+.admin-topbar-right { display: flex; align-items: center; gap: 12px; }
+.admin-user-badge { background: #e8ecff; color: var(--admin-accent); padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 600; }
+
+.admin-content { padding: 28px; flex: 1; }
+
+/* Cards */
+.admin-card { background: var(--admin-card); border-radius: 10px; border: 1px solid var(--admin-border); padding: 20px; }
+.admin-card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
+.admin-card-header h3 { font-size: 14px; font-weight: 600; color: var(--admin-text); margin: 0; }
+
+/* Stat Cards */
+.admin-stats { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
+.stat-card { background: var(--admin-card); border-radius: 10px; padding: 20px; border: 1px solid var(--admin-border); display: flex; align-items: center; gap: 16px; }
+.stat-icon { width: 44px; height: 44px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
+.stat-icon.blue { background: #eff2ff; }
+.stat-icon.green { background: #f0fdf4; }
+.stat-icon.orange { background: #fff7ed; }
+.stat-icon.red { background: #fff1f2; }
+.stat-info h4 { font-size: 22px; font-weight: 700; color: var(--admin-text); margin: 0 0 2px; }
+.stat-info p { font-size: 12px; color: var(--admin-muted); margin: 0; }
+
+/* Table */
+.admin-table-wrap { overflow-x: auto; }
+.admin-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
+.admin-table th { background: #f8fafc; color: var(--admin-muted); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; padding: 10px 14px; border-bottom: 1px solid var(--admin-border); text-align: left; white-space: nowrap; }
+.admin-table td { padding: 12px 14px; border-bottom: 1px solid #f1f5f9; color: var(--admin-text); vertical-align: middle; }
+.admin-table tr:last-child td { border-bottom: none; }
+.admin-table tr:hover td { background: #f8fafc; }
+.admin-table .truncate { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+
+/* Badges */
+.badge { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 20px; font-size: 11px; font-weight: 600; }
+.badge-green { background: #dcfce7; color: #16a34a; }
+.badge-red { background: #fee2e2; color: #dc2626; }
+.badge-blue { background: #dbeafe; color: #2563eb; }
+.badge-orange { background: #ffedd5; color: #ea580c; }
+.badge-gray { background: #f1f5f9; color: #64748b; }
+
+/* Buttons */
+.btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 7px; font-size: 13px; font-weight: 500; cursor: pointer; border: none; transition: all .15s; text-decoration: none; }
+.btn-primary { background: var(--admin-accent); color: #fff; }
+.btn-primary:hover { background: var(--admin-accent-hover); }
+.btn-outline { background: transparent; color: var(--admin-text); border: 1px solid var(--admin-border); }
+.btn-outline:hover { background: #f8fafc; }
+.btn-danger { background: transparent; color: var(--admin-danger); border: 1px solid #fecaca; }
+.btn-danger:hover { background: #fff1f2; }
+.btn-sm { padding: 5px 10px; font-size: 12px; }
+.btn-icon { padding: 6px; border-radius: 6px; }
+
+/* Action buttons row */
+.action-btns { display: flex; gap: 6px; align-items: center; }
+
+/* Toolbar */
+.admin-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }
+.admin-toolbar-right { margin-left: auto; display: flex; gap: 8px; }
+.admin-search { position: relative; }
+.admin-search input { padding: 8px 12px 8px 34px; border: 1px solid var(--admin-border); border-radius: 7px; font-size: 13px; outline: none; width: 200px; background: #fff; color: var(--admin-text); }
+.admin-search input:focus { border-color: var(--admin-accent); }
+.admin-search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--admin-muted); font-size: 14px; }
+.admin-select { padding: 8px 12px; border: 1px solid var(--admin-border); border-radius: 7px; font-size: 13px; outline: none; background: #fff; color: var(--admin-text); cursor: pointer; }
+.admin-select:focus { border-color: var(--admin-accent); }
+
+/* Modal */
+.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 1000; display: flex; align-items: center; justify-content: center; padding: 16px; }
+.modal { background: var(--admin-card); border-radius: 12px; width: 100%; max-width: 640px; max-height: 90vh; overflow-y: auto; box-shadow: 0 20px 60px rgba(0,0,0,.2); }
+.modal-header { padding: 20px 24px 16px; border-bottom: 1px solid var(--admin-border); display: flex; align-items: center; justify-content: space-between; }
+.modal-header h3 { font-size: 15px; font-weight: 600; margin: 0; }
+.modal-header button { background: none; border: none; cursor: pointer; color: var(--admin-muted); font-size: 18px; line-height: 1; padding: 4px; }
+.modal-body { padding: 24px; }
+.modal-footer { padding: 16px 24px; border-top: 1px solid var(--admin-border); display: flex; justify-content: flex-end; gap: 10px; }
+
+/* Form */
+.form-group { margin-bottom: 16px; }
+.form-group label { display: block; font-size: 12px; font-weight: 600; color: var(--admin-muted); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 6px; }
+.form-control { width: 100%; padding: 9px 12px; border: 1px solid var(--admin-border); border-radius: 7px; font-size: 13.5px; color: var(--admin-text); outline: none; box-sizing: border-box; background: #fff; font-family: inherit; }
+.form-control:focus { border-color: var(--admin-accent); box-shadow: 0 0 0 3px rgba(79,110,247,.12); }
+textarea.form-control { resize: vertical; min-height: 100px; }
+.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
+.form-check { display: flex; align-items: center; gap: 8px; font-size: 13.5px; cursor: pointer; }
+.form-check input { width: 16px; height: 16px; cursor: pointer; accent-color: var(--admin-accent); }
+
+/* Pagination */
+.admin-pagination { display: flex; align-items: center; justify-content: space-between; margin-top: 16px; }
+.admin-pagination-info { font-size: 12px; color: var(--admin-muted); }
+.pagination-btns { display: flex; gap: 4px; }
+.pagination-btns button { padding: 5px 10px; border: 1px solid var(--admin-border); background: #fff; color: var(--admin-text); border-radius: 5px; font-size: 12px; cursor: pointer; transition: all .15s; }
+.pagination-btns button:hover:not(:disabled) { background: var(--admin-accent); color: #fff; border-color: var(--admin-accent); }
+.pagination-btns button.active { background: var(--admin-accent); color: #fff; border-color: var(--admin-accent); }
+.pagination-btns button:disabled { opacity: .4; cursor: not-allowed; }
+
+/* Login */
+.admin-login-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #1a1d2e 0%, #2a2d4e 100%); padding: 16px; }
+.admin-login-box { background: #fff; border-radius: 14px; padding: 40px 36px; width: 100%; max-width: 380px; box-shadow: 0 20px 60px rgba(0,0,0,.3); }
+.admin-login-box .login-logo { text-align: center; margin-bottom: 28px; }
+.admin-login-box .login-logo h1 { font-size: 22px; font-weight: 800; color: var(--admin-text); margin: 8px 0 4px; }
+.admin-login-box .login-logo p { font-size: 12px; color: var(--admin-muted); margin: 0; }
+.admin-login-box .login-badge { display: inline-block; background: #eff2ff; color: var(--admin-accent); padding: 2px 10px; border-radius: 20px; font-size: 11px; font-weight: 700; margin-bottom: 6px; }
+.login-input-group { margin-bottom: 14px; }
+.login-input-group label { display: block; font-size: 12px; font-weight: 600; color: var(--admin-muted); margin-bottom: 6px; }
+.login-input-group input { width: 100%; padding: 11px 14px; border: 1.5px solid var(--admin-border); border-radius: 8px; font-size: 14px; outline: none; box-sizing: border-box; transition: border-color .15s; }
+.login-input-group input:focus { border-color: var(--admin-accent); }
+.login-btn { width: 100%; padding: 12px; background: var(--admin-accent); color: #fff; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; margin-top: 6px; transition: background .15s; }
+.login-btn:hover { background: var(--admin-accent-hover); }
+.login-error { background: #fff1f2; color: var(--admin-danger); font-size: 12.5px; padding: 9px 12px; border-radius: 7px; margin-bottom: 14px; border: 1px solid #fecaca; }
+
+/* Dashboard recent list */
+.recent-list { list-style: none; padding: 0; margin: 0; }
+.recent-list li { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid #f1f5f9; font-size: 13px; }
+.recent-list li:last-child { border-bottom: none; }
+.recent-list .rl-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--admin-accent); flex-shrink: 0; }
+.recent-list .rl-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--admin-text); }
+.recent-list .rl-meta { color: var(--admin-muted); font-size: 11px; white-space: nowrap; }
+
+/* Empty state */
+.empty-state { text-align: center; padding: 48px 0; color: var(--admin-muted); }
+.empty-state .empty-icon { font-size: 40px; margin-bottom: 12px; }
+.empty-state p { font-size: 13.5px; margin: 0; }
+
+/* Toast */
+.admin-toast { position: fixed; bottom: 24px; right: 24px; z-index: 9999; display: flex; flex-direction: column; gap: 8px; }
+.toast-item { background: #1e293b; color: #fff; padding: 12px 20px; border-radius: 8px; font-size: 13px; animation: slideUp .2s ease; box-shadow: 0 4px 16px rgba(0,0,0,.2); }
+.toast-item.success { border-left: 3px solid var(--admin-success); }
+.toast-item.error { border-left: 3px solid var(--admin-danger); }
+@keyframes slideUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
+
+/* Responsive */
+@media (max-width: 768px) {
+ .admin-sidebar { transform: translateX(-100%); }
+ .admin-sidebar.open { transform: translateX(0); }
+ .admin-main { margin-left: 0; }
+ .form-row { grid-template-columns: 1fr; }
+ .admin-stats { grid-template-columns: 1fr 1fr; }
+}
diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css
new file mode 100644
index 00000000..60c2e0b9
--- /dev/null
+++ b/frontend/src/styles/global.css
@@ -0,0 +1,215 @@
+/* ============================================================
+ (주)지오정보기술 글로벌 스타일 — URP 스타일 기반
+ ============================================================ */
+
+:root {
+ /* ── 브랜드 컬러 ── */
+ --primary: #0051A2; /* 딥블루 — 메인 브랜드 */
+ --primary-dark: #003A7A;
+ --primary-light: #E8F0FA;
+ --accent: #00A3E0; /* 밝은 파랑 — 포인트 */
+ --accent-dark: #0080B0;
+ --secondary: #1A1A2E; /* 다크 네이비 — 헤더 배경 */
+
+ /* ── 그레이 스케일 ── */
+ --gray-900: #111827;
+ --gray-800: #1F2937;
+ --gray-700: #374151;
+ --gray-600: #4B5563;
+ --gray-400: #9CA3AF;
+ --gray-200: #E5E7EB;
+ --gray-100: #F3F4F6;
+ --gray-50: #F9FAFB;
+ --white: #FFFFFF;
+
+ /* ── 시맨틱 ── */
+ --success: #10B981;
+ --warning: #F59E0B;
+ --danger: #EF4444;
+
+ /* ── 타이포그래피 ── */
+ --font-sans: 'Noto Sans KR', 'Inter', -apple-system, sans-serif;
+ --font-en: 'Inter', sans-serif;
+
+ /* ── 레이아웃 ── */
+ --container: 1280px;
+ --header-h: 72px;
+ --radius-sm: 6px;
+ --radius: 12px;
+ --radius-lg: 20px;
+
+ /* ── 트랜지션 ── */
+ --ease: cubic-bezier(.4,0,.2,1);
+ --fast: 0.15s;
+ --mid: 0.3s;
+ --slow: 0.5s;
+
+ /* ── 그림자 ── */
+ --shadow-sm: 0 1px 3px rgba(0,0,0,.1);
+ --shadow: 0 4px 16px rgba(0,0,0,.12);
+ --shadow-lg: 0 12px 40px rgba(0,0,0,.16);
+}
+
+/* ── 리셋 ─────────────────────────────────────────── */
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+html { scroll-behavior: smooth; font-size: 16px; }
+body {
+ font-family: var(--font-sans);
+ color: var(--gray-800);
+ background: var(--white);
+ line-height: 1.6;
+ -webkit-font-smoothing: antialiased;
+}
+img { max-width: 100%; height: auto; display: block; }
+a { color: inherit; text-decoration: none; }
+ul, ol { list-style: none; }
+button { cursor: pointer; border: none; background: none; font-family: inherit; }
+
+/* ── 공통 레이아웃 ─────────────────────────────────── */
+.container {
+ max-width: var(--container);
+ margin: 0 auto;
+ padding: 0 24px;
+}
+
+.section {
+ padding: 80px 0;
+}
+.section-sm { padding: 48px 0; }
+.section-lg { padding: 120px 0; }
+
+.section-header {
+ text-align: center;
+ margin-bottom: 56px;
+}
+.section-label {
+ display: inline-block;
+ font-size: 13px;
+ font-weight: 700;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ color: var(--accent);
+ margin-bottom: 12px;
+}
+.section-title {
+ font-size: clamp(28px, 4vw, 44px);
+ font-weight: 900;
+ color: var(--gray-900);
+ line-height: 1.2;
+}
+.section-title em {
+ color: var(--primary);
+ font-style: normal;
+}
+.section-desc {
+ margin-top: 16px;
+ font-size: 17px;
+ color: var(--gray-600);
+ max-width: 600px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+/* ── 버튼 ──────────────────────────────────────────── */
+.btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px 28px;
+ border-radius: var(--radius);
+ font-size: 15px;
+ font-weight: 600;
+ transition: all var(--mid) var(--ease);
+ line-height: 1;
+}
+.btn-primary {
+ background: var(--primary);
+ color: var(--white);
+}
+.btn-primary:hover {
+ background: var(--primary-dark);
+ transform: translateY(-2px);
+ box-shadow: 0 8px 24px rgba(0,81,162,.3);
+}
+.btn-outline {
+ border: 2px solid var(--primary);
+ color: var(--primary);
+}
+.btn-outline:hover {
+ background: var(--primary);
+ color: var(--white);
+}
+.btn-white {
+ background: var(--white);
+ color: var(--primary);
+ font-weight: 700;
+}
+.btn-white:hover {
+ background: var(--gray-100);
+ transform: translateY(-2px);
+}
+.btn-lg { padding: 16px 36px; font-size: 16px; }
+.btn-sm { padding: 8px 20px; font-size: 13px; }
+
+/* ── 카드 ──────────────────────────────────────────── */
+.card {
+ background: var(--white);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-sm);
+ border: 1px solid var(--gray-200);
+ transition: all var(--mid) var(--ease);
+ overflow: hidden;
+}
+.card:hover {
+ box-shadow: var(--shadow-lg);
+ transform: translateY(-4px);
+ border-color: var(--primary-light);
+}
+
+/* ── 그리드 ────────────────────────────────────────── */
+.grid-2 { display: grid; grid-template-columns: repeat(2,1fr); gap: 24px; }
+.grid-3 { display: grid; grid-template-columns: repeat(3,1fr); gap: 24px; }
+.grid-4 { display: grid; grid-template-columns: repeat(4,1fr); gap: 24px; }
+
+/* ── 배지 ──────────────────────────────────────────── */
+.badge {
+ display: inline-block;
+ padding: 3px 10px;
+ border-radius: 20px;
+ font-size: 12px;
+ font-weight: 600;
+}
+.badge-primary { background: var(--primary-light); color: var(--primary); }
+.badge-accent { background: rgba(0,163,224,.12); color: var(--accent-dark); }
+.badge-new { background: var(--danger); color: var(--white); }
+
+/* ── 구분선 ────────────────────────────────────────── */
+.divider {
+ width: 48px; height: 4px;
+ background: var(--accent);
+ border-radius: 2px;
+ margin: 16px auto 0;
+}
+.divider-left { margin-left: 0; }
+
+/* ── 스크롤바 ──────────────────────────────────────── */
+::-webkit-scrollbar { width: 6px; }
+::-webkit-scrollbar-track { background: var(--gray-100); }
+::-webkit-scrollbar-thumb { background: var(--gray-400); border-radius: 3px; }
+
+/* ── 페이드인 애니메이션 ────────────────────────────── */
+@keyframes fadeUp {
+ from { opacity:0; transform:translateY(30px); }
+ to { opacity:1; transform:translateY(0); }
+}
+.fade-up { animation: fadeUp var(--slow) var(--ease) both; }
+
+/* ── 반응형 ────────────────────────────────────────── */
+@media (max-width: 1024px) {
+ .grid-4 { grid-template-columns: repeat(2,1fr); }
+}
+@media (max-width: 768px) {
+ .section { padding: 60px 0; }
+ .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
+ .container { padding: 0 16px; }
+}
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
new file mode 100644
index 00000000..ecfed851
--- /dev/null
+++ b/frontend/vite.config.js
@@ -0,0 +1,19 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 3000,
+ proxy: {
+ '/api': {
+ target: 'http://localhost:8080',
+ changeOrigin: true,
+ }
+ }
+ },
+ build: {
+ outDir: '../backend/src/main/resources/static',
+ emptyOutDir: true,
+ }
+});
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..c1b43c7c
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,59 @@
+{
+ "name": "zioinfo-web",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "devDependencies": {
+ "playwright": "^1.60.0"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.60.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
+ "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.60.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.60.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
+ "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..8b4ada07
--- /dev/null
+++ b/package.json
@@ -0,0 +1,5 @@
+{
+ "devDependencies": {
+ "playwright": "^1.60.0"
+ }
+}
diff --git a/start.ps1 b/start.ps1
new file mode 100644
index 00000000..e05e09b0
--- /dev/null
+++ b/start.ps1
@@ -0,0 +1,177 @@
+# =============================================================
+# (주)지오정보기술 홈페이지 스타트업 스크립트 — Windows
+# =============================================================
+# 사용법: .\start.ps1 [dev|prod|build]
+# =============================================================
+
+param([string]$Mode = "dev")
+
+$ErrorActionPreference = "Stop"
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$BackendDir = "$ScriptDir\backend"
+$FrontendDir = "$ScriptDir\frontend"
+
+function Write-OK { param($msg) Write-Host "[OK] $msg" -ForegroundColor Green }
+function Write-Warn { param($msg) Write-Host "[WARN] $msg" -ForegroundColor Yellow }
+function Write-Fail { param($msg) Write-Host "[FAIL] $msg" -ForegroundColor Red; exit 1 }
+function Write-Info { param($msg) Write-Host " $msg" }
+
+Write-Host "=================================================="
+Write-Host " (주)지오정보기술 홈페이지 시작 — Windows"
+Write-Host " 모드: $Mode"
+Write-Host "=================================================="
+
+# ── 1. Java 확인 ──────────────────────────────────────────────
+if (-not (Get-Command java -ErrorAction SilentlyContinue)) {
+ Write-Fail "Java가 설치되지 않았습니다. setup_windows.ps1을 먼저 실행하세요."
+}
+$javaVer = & java -version 2>&1 | Select-Object -First 1
+Write-OK "Java 감지: $javaVer"
+
+# ── 2. Maven 감지 및 설치 ────────────────────────────────────
+function Get-MavenCommand {
+ # mvnw.cmd (Wrapper) 우선 확인
+ $mvnw = "$BackendDir\mvnw.cmd"
+ if (Test-Path $mvnw) {
+ Write-OK "Maven Wrapper(mvnw.cmd) 사용 — 별도 Maven 설치 불필요"
+ return $mvnw
+ }
+
+ # 시스템 Maven 확인
+ $mvnCmd = Get-Command mvn -ErrorAction SilentlyContinue
+ if ($mvnCmd) {
+ $ver = & mvn --version 2>&1 | Select-Object -First 1
+ Write-OK "시스템 Maven 감지: $ver"
+ return "mvn"
+ }
+
+ # Maven 없음 → 자동 설치
+ Write-Warn "Maven이 설치되지 않았습니다. 자동 설치를 진행합니다..."
+ Install-Maven
+ return "mvn"
+}
+
+function Install-Maven {
+ # Chocolatey로 설치 시도
+ if (Get-Command choco -ErrorAction SilentlyContinue) {
+ Write-Info "Chocolatey로 Maven 설치 중..."
+ choco install maven -y --no-progress 2>&1 | Out-Null
+ $mp = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
+ $up = [System.Environment]::GetEnvironmentVariable("Path", "User")
+ $env:Path = "$mp;$up"
+ if (Get-Command mvn -ErrorAction SilentlyContinue) {
+ Write-OK "Maven 설치 완료 (Chocolatey)"
+ return
+ }
+ }
+
+ # 수동 설치
+ $MavenVer = if ($env:MAVEN_VER) { $env:MAVEN_VER } else { "3.9.6" }
+ $MavenUrl = "https://archive.apache.org/dist/maven/maven-3/$MavenVer/binaries/apache-maven-$MavenVer-bin.zip"
+ $MavenHome = "C:\tools\maven"
+
+ Write-Info "Maven $MavenVer 수동 설치 중..."
+ try {
+ Invoke-WebRequest $MavenUrl -OutFile "$env:TEMP\maven.zip" -UseBasicParsing -TimeoutSec 120
+ Expand-Archive "$env:TEMP\maven.zip" -DestinationPath "C:\tools" -Force
+ if (Test-Path "C:\tools\apache-maven-$MavenVer") {
+ if (Test-Path $MavenHome) { Remove-Item $MavenHome -Recurse -Force }
+ Rename-Item "C:\tools\apache-maven-$MavenVer" $MavenHome
+ }
+ [System.Environment]::SetEnvironmentVariable("MAVEN_HOME", $MavenHome, "Machine")
+ $currentPath = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
+ [System.Environment]::SetEnvironmentVariable("Path", "$currentPath;$MavenHome\bin", "Machine")
+ $env:MAVEN_HOME = $MavenHome
+ $env:Path = "$env:Path;$MavenHome\bin"
+ Write-OK "Maven $MavenVer 수동 설치 완료: $MavenHome"
+ } catch {
+ Write-Fail "Maven 설치 실패: $_ — 수동으로 https://maven.apache.org/download.cgi 에서 설치하세요."
+ }
+}
+
+$MvnCmd = Get-MavenCommand
+
+# ── 3. Node.js/npm 확인 ──────────────────────────────────────
+$HasNpm = $false
+if (Get-Command npm -ErrorAction SilentlyContinue) {
+ $npmVer = npm --version
+ Write-OK "npm $npmVer 감지"
+ $HasNpm = $true
+} else {
+ Write-Warn "npm 없음 — 프론트엔드는 빌드 없이 실행됩니다."
+}
+
+# ── 4. 실행 ───────────────────────────────────────────────────
+switch ($Mode) {
+
+ "dev" {
+ Write-Host ""
+ Write-Info "=== 개발 모드 시작 ==="
+ Write-Info "백엔드: http://localhost:8080"
+ if ($HasNpm) { Write-Info "프론트: http://localhost:3000" }
+
+ # 프론트엔드 의존성 설치
+ if ($HasNpm -and -not (Test-Path "$FrontendDir\node_modules")) {
+ Push-Location $FrontendDir
+ npm install
+ Pop-Location
+ }
+
+ # 백엔드 시작
+ New-Item -ItemType Directory -Force "$BackendDir\data" | Out-Null
+ Push-Location $BackendDir
+ $springJob = Start-Job -ScriptBlock { param($dir,$mvn) cd $dir; & $mvn spring-boot:run } -ArgumentList $BackendDir, $MvnCmd
+ Pop-Location
+
+ # 프론트엔드 시작 (npm 있을 때)
+ if ($HasNpm) {
+ Push-Location $FrontendDir
+ Start-Process npm -ArgumentList "run dev" -NoNewWindow
+ Pop-Location
+ }
+
+ Write-OK "서비스 시작 완료"
+ Write-Host "종료: Ctrl+C"
+ Wait-Job $springJob
+ }
+
+ "prod" {
+ Write-Host ""
+ Write-Info "=== 운영 모드 ==="
+ if ($HasNpm) {
+ Push-Location $FrontendDir
+ if (-not (Test-Path "node_modules")) { npm install }
+ npm run build
+ Write-OK "React 빌드 완료"
+ Pop-Location
+ }
+ New-Item -ItemType Directory -Force "$BackendDir\data" | Out-Null
+ Push-Location $BackendDir
+ & $MvnCmd clean package "-DskipTests" -q
+ $jar = Get-ChildItem target -Filter "*.jar" | Where-Object { $_.Name -notmatch "sources" } | Select-Object -First 1
+ Write-OK "패키징 완료: $($jar.Name)"
+ java -jar $jar.FullName
+ Pop-Location
+ }
+
+ "build" {
+ if ($HasNpm) {
+ Push-Location $FrontendDir
+ if (-not (Test-Path "node_modules")) { npm install }
+ npm run build
+ Write-OK "React 빌드 완료"
+ Pop-Location
+ }
+ Push-Location $BackendDir
+ & $MvnCmd clean package "-DskipTests" -q
+ $jar = Get-ChildItem target -Filter "*.jar" | Where-Object { $_.Name -notmatch "sources" } | Select-Object -First 1
+ Write-OK "빌드 완료: $BackendDir\$($jar.Name)"
+ Write-Info "실행: java -jar $BackendDir\target\$($jar.Name)"
+ Pop-Location
+ }
+
+ default {
+ Write-Host "사용법: .\start.ps1 [dev|prod|build]"
+ exit 1
+ }
+}
diff --git a/start.sh b/start.sh
new file mode 100644
index 00000000..e13215d6
--- /dev/null
+++ b/start.sh
@@ -0,0 +1,194 @@
+#!/bin/bash
+# =============================================================
+# (주)지오정보기술 홈페이지 스타트업 스크립트
+# =============================================================
+# 사용법: bash start.sh [dev|prod|build]
+# dev : 개발 모드 (React 핫리로드 + Spring Boot)
+# prod : 운영 모드 (빌드 후 단일 JAR 실행)
+# build : 빌드만 (서버 시작 안 함)
+# =============================================================
+
+set -euo pipefail
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+BACKEND_DIR="$SCRIPT_DIR/backend"
+FRONTEND_DIR="$SCRIPT_DIR/frontend"
+MODE="${1:-dev}"
+
+GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
+ok() { echo -e "${GREEN}[OK]${NC} $*"; }
+warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
+fail() { echo -e "${RED}[FAIL]${NC} $*"; exit 1; }
+info() { echo -e " $*"; }
+
+echo "=================================================="
+echo " (주)지오정보기술 홈페이지 시작"
+echo " 모드: $MODE"
+echo "=================================================="
+
+# ── 1. Java 확인 ──────────────────────────────────────────────
+if ! command -v java &>/dev/null; then
+ fail "Java가 설치되지 않았습니다. setup_ubuntu.sh 또는 setup_windows.ps1을 먼저 실행하세요."
+fi
+JAVA_VER=$(java -version 2>&1 | head -1 | awk -F'"' '{print $2}' | cut -d'.' -f1)
+ok "Java $JAVA_VER 감지"
+
+# ── 2. Maven 감지 및 설치 ────────────────────────────────────
+ensure_maven() {
+ # mvnw (Wrapper) 우선 확인
+ if [[ -f "$BACKEND_DIR/mvnw" ]]; then
+ chmod +x "$BACKEND_DIR/mvnw"
+ ok "Maven Wrapper(mvnw) 사용 — 별도 Maven 설치 불필요"
+ MVN="$BACKEND_DIR/mvnw"
+ return
+ fi
+
+ # 시스템 Maven 확인
+ if command -v mvn &>/dev/null; then
+ MVN_VER=$(mvn --version 2>/dev/null | head -1 | awk '{print $3}')
+ ok "시스템 Maven $MVN_VER 감지"
+ MVN="mvn"
+ return
+ fi
+
+ # Maven 없음 → 자동 설치
+ warn "Maven이 설치되지 않았습니다. 자동 설치를 진행합니다..."
+ install_maven
+ MVN="mvn"
+}
+
+install_maven() {
+ MAVEN_VER="${MAVEN_VER:-3.9.6}"
+ MAVEN_URL="https://archive.apache.org/dist/maven/maven-3/${MAVEN_VER}/binaries/apache-maven-${MAVEN_VER}-bin.tar.gz"
+ MAVEN_HOME="/opt/maven"
+
+ # OS 감지하여 패키지 관리자로 우선 시도
+ if command -v apt-get &>/dev/null; then
+ info "apt로 Maven 설치 시도..."
+ apt-get install -y -qq maven 2>/dev/null && {
+ ok "Maven 설치 완료 (apt)"
+ return
+ }
+ elif command -v dnf &>/dev/null; then
+ dnf install -y maven 2>/dev/null && { ok "Maven 설치 완료 (dnf)"; return; }
+ elif command -v yum &>/dev/null; then
+ yum install -y maven 2>/dev/null && { ok "Maven 설치 완료 (yum)"; return; }
+ fi
+
+ # 패키지 관리자 실패 → 수동 설치
+ info "Maven ${MAVEN_VER} 수동 설치 중..."
+ MIRROR="${MAVEN_MIRROR:-$MAVEN_URL}"
+ wget -q "$MIRROR" -O /tmp/maven.tar.gz \
+ || fail "Maven 다운로드 실패. MAVEN_MIRROR 환경변수를 내부 미러로 설정하세요."
+
+ mkdir -p "$MAVEN_HOME"
+ tar -xzf /tmp/maven.tar.gz -C "$MAVEN_HOME" --strip-components=1
+ rm -f /tmp/maven.tar.gz
+
+ # PATH 등록
+ cat > /etc/profile.d/maven.sh << MVNEOF
+export MAVEN_HOME=$MAVEN_HOME
+export PATH=\$MAVEN_HOME/bin:\$PATH
+MVNEOF
+ export MAVEN_HOME="$MAVEN_HOME"
+ export PATH="$MAVEN_HOME/bin:$PATH"
+ ok "Maven ${MAVEN_VER} 수동 설치 완료: $MAVEN_HOME"
+}
+
+ensure_maven
+
+# ── 3. Node.js / npm 확인 (dev 모드) ─────────────────────────
+if [[ "$MODE" == "dev" ]]; then
+ if ! command -v npm &>/dev/null; then
+ warn "npm이 없습니다. 프론트엔드는 빌드 없이 실행됩니다."
+ info "npm 설치: https://nodejs.org 또는 nvm 사용"
+ MODE="spring-only"
+ else
+ NPM_VER=$(npm --version)
+ ok "npm $NPM_VER 감지"
+ fi
+fi
+
+# ── 4. 실행 ───────────────────────────────────────────────────
+case "$MODE" in
+
+ dev)
+ echo ""
+ info "=== 개발 모드 시작 ==="
+ info "백엔드: http://localhost:8080"
+ info "프론트: http://localhost:3000"
+ echo ""
+
+ # 프론트엔드 의존성 설치
+ if [[ ! -d "$FRONTEND_DIR/node_modules" ]]; then
+ info "npm install 실행 중..."
+ cd "$FRONTEND_DIR" && npm install
+ fi
+
+ # 백엔드 백그라운드 실행
+ cd "$BACKEND_DIR"
+ mkdir -p data
+ info "Spring Boot 시작 중..."
+ $MVN spring-boot:run &
+ SPRING_PID=$!
+
+ # 프론트엔드 포그라운드 실행
+ cd "$FRONTEND_DIR"
+ npm run dev &
+ VITE_PID=$!
+
+ # 종료 핸들러
+ trap "kill $SPRING_PID $VITE_PID 2>/dev/null; exit" INT TERM
+ wait $SPRING_PID $VITE_PID
+ ;;
+
+ spring-only)
+ echo ""
+ info "=== Spring Boot만 실행 (npm 없음) ==="
+ cd "$BACKEND_DIR"
+ mkdir -p data
+ $MVN spring-boot:run
+ ;;
+
+ prod)
+ echo ""
+ info "=== 운영 모드 빌드 후 실행 ==="
+ # React 빌드
+ if command -v npm &>/dev/null; then
+ cd "$FRONTEND_DIR"
+ [[ ! -d node_modules ]] && npm install
+ npm run build
+ ok "React 빌드 완료 → backend/src/main/resources/static/"
+ else
+ warn "npm 없음 — 기존 빌드 결과물 사용"
+ fi
+ # Spring Boot 패키징
+ cd "$BACKEND_DIR"
+ mkdir -p data
+ $MVN clean package -DskipTests -q
+ ok "Spring Boot 패키징 완료"
+ JAR=$(find target -name "*.jar" ! -name "*sources*" | head -1)
+ info "실행: java -jar $JAR"
+ java -jar "$JAR"
+ ;;
+
+ build)
+ echo ""
+ info "=== 빌드만 실행 ==="
+ if command -v npm &>/dev/null; then
+ cd "$FRONTEND_DIR"
+ [[ ! -d node_modules ]] && npm install
+ npm run build
+ ok "React 빌드 완료"
+ fi
+ cd "$BACKEND_DIR"
+ $MVN clean package -DskipTests -q
+ JAR=$(find target -name "*.jar" ! -name "*sources*" | head -1)
+ ok "빌드 완료: $JAR"
+ info "실행 명령: java -jar $BACKEND_DIR/$JAR"
+ ;;
+
+ *)
+ echo "사용법: bash start.sh [dev|prod|build|spring-only]"
+ exit 1
+ ;;
+esac
diff --git a/test-all-pages.js b/test-all-pages.js
new file mode 100644
index 00000000..ea04b112
--- /dev/null
+++ b/test-all-pages.js
@@ -0,0 +1,68 @@
+const { chromium } = require('playwright');
+const path = require('path');
+const fs = require('fs');
+
+const BASE = 'http://localhost:3000';
+const OUT = path.join(__dirname, 'frontend/public/screenshots');
+if (!fs.existsSync(OUT)) fs.mkdirSync(OUT, { recursive: true });
+
+const PAGES = [
+ { name:'home', url:'/', label:'홈' },
+ { name:'guardia', url:'/solution/guardia', label:'GUARDiA ITSM' },
+ { name:'solution_erp', url:'/solution/erp', label:'솔루션-ERP' },
+ { name:'solution_crm', url:'/solution/crm', label:'솔루션-CRM' },
+ { name:'solution_bi', url:'/solution/bi', label:'솔루션-BI' },
+ { name:'company_greeting', url:'/company/greeting', label:'회사-CEO인사말' },
+ { name:'company_history', url:'/company/history', label:'회사-연혁' },
+ { name:'company_org', url:'/company/organization', label:'회사-조직도' },
+ { name:'company_ci', url:'/company/ci', label:'회사-CI소개' },
+ { name:'company_location', url:'/company/location', label:'회사-오시는길' },
+ { name:'business_ref', url:'/business/reference', label:'사업-레퍼런스' },
+ { name:'business_partner', url:'/business/partner', label:'사업-파트너' },
+ { name:'support_notice', url:'/support/notice', label:'지원-공지사항' },
+ { name:'support_faq', url:'/support/faq', label:'지원-FAQ' },
+ { name:'support_catalog', url:'/support/catalog', label:'지원-카탈로그' },
+ { name:'support_contact', url:'/support/contact', label:'지원-문의하기' },
+ { name:'recruit_jobs', url:'/recruit/jobs', label:'채용-공고' },
+ { name:'recruit_welfare', url:'/recruit/welfare', label:'채용-복리후생' },
+ { name:'recruit_apply', url:'/recruit/apply', label:'채용-지원하기' },
+ { name:'news_newsroom', url:'/news/newsroom', label:'뉴스-뉴스룸' },
+ { name:'news_blog', url:'/news/blog', label:'뉴스-블로그' },
+];
+
+(async () => {
+ const browser = await chromium.launch({ headless: true });
+ const context = await browser.newContext({ viewport:{ width:1440, height:900 }, locale:'ko-KR' });
+ const results = [];
+
+ for (const pg of PAGES) {
+ const page = await context.newPage();
+ const errors = [];
+ page.on('pageerror', e => errors.push(e.message));
+
+ const t0 = Date.now();
+ try {
+ const resp = await page.goto(BASE + pg.url, { waitUntil:'networkidle', timeout:15000 });
+ await page.waitForSelector('#root > *', { timeout:5000 }).catch(() => {});
+ const loadMs = Date.now() - t0;
+ await page.screenshot({ path: path.join(OUT, pg.name + '.png'), fullPage:false });
+ const title = await page.title();
+ const h1 = await page.$eval('h1', el => el.textContent).catch(() => '');
+ results.push({ page:pg.label, url:pg.url, http:resp?.status(), loadMs, title, h1:h1.trim(), errors:errors.length, ok:true });
+ const icon = errors.length ? '⚠️' : '✅';
+ console.log(`${icon} ${pg.label.padEnd(20)} HTTP${resp?.status()} ${loadMs}ms H1:"${h1.trim().slice(0,30)}"`);
+ } catch(e) {
+ results.push({ page:pg.label, url:pg.url, ok:false, error:e.message.slice(0,80) });
+ console.log(`❌ ${pg.label.padEnd(20)} FAIL: ${e.message.slice(0,60)}`);
+ }
+ await page.close();
+ }
+
+ await browser.close();
+
+ const pass = results.filter(r => r.ok).length;
+ const fail = results.filter(r => !r.ok).length;
+ console.log(`\n${'='.repeat(50)}`);
+ console.log(`결과: ${pass}/${results.length} 통과 ${fail > 0 ? `(실패 ${fail}건)` : '— 전체 통과 🎉'}`);
+ fs.writeFileSync(path.join(OUT, 'all-pages-result.json'), JSON.stringify(results, null, 2));
+})();
diff --git a/test-homepage.js b/test-homepage.js
new file mode 100644
index 00000000..0765aa40
--- /dev/null
+++ b/test-homepage.js
@@ -0,0 +1,106 @@
+const { chromium } = require('playwright');
+const path = require('path');
+const fs = require('fs');
+
+const BASE = 'http://localhost:3000';
+const OUT = path.join(__dirname, 'frontend/public/screenshots');
+if (!fs.existsSync(OUT)) fs.mkdirSync(OUT, { recursive: true });
+
+const PAGES = [
+ { name: '01_home', url: '/', label: '홈' },
+ { name: '02_guardia', url: '/solution/guardia', label: 'GUARDiA 소개' },
+ { name: '03_company', url: '/company/greeting', label: '회사소개' },
+ { name: '04_contact', url: '/support/contact', label: '문의하기' },
+ { name: '05_news', url: '/news/press', label: '뉴스' },
+];
+
+const RESULTS = [];
+
+(async () => {
+ const browser = await chromium.launch({ headless: true });
+ const context = await browser.newContext({
+ viewport: { width: 1440, height: 900 },
+ locale: 'ko-KR',
+ });
+
+ for (const pg of PAGES) {
+ const page = await context.newPage();
+ const errors = [];
+ page.on('console', msg => { if (msg.type() === 'error') errors.push(msg.text()); });
+ page.on('pageerror', err => errors.push(err.message));
+
+ const t0 = Date.now();
+ let status = 'OK';
+
+ try {
+ const resp = await page.goto(BASE + pg.url, { waitUntil: 'networkidle', timeout: 15000 });
+ const loadTime = Date.now() - t0;
+
+ // 메인 컨텐츠 대기
+ await page.waitForSelector('#root > *', { timeout: 5000 }).catch(() => {});
+
+ // 풀페이지 스크린샷
+ const ssPath = path.join(OUT, pg.name + '.png');
+ await page.screenshot({ path: ssPath, fullPage: true });
+
+ // 뷰포트 스크린샷 (Above-the-fold)
+ const vpPath = path.join(OUT, pg.name + '_viewport.png');
+ await page.screenshot({ path: vpPath, fullPage: false });
+
+ // 타이틀, 링크 수 확인
+ const title = await page.title();
+ const links = await page.$$eval('a[href]', els => els.length);
+ const images = await page.$$eval('img', els => els.length);
+ const h1Count = await page.$$eval('h1', els => els.length);
+
+ RESULTS.push({
+ page: pg.label,
+ url: pg.url,
+ status: resp ? resp.status() : 'err',
+ loadMs: loadTime,
+ title,
+ links,
+ images,
+ h1: h1Count,
+ errors: errors.length,
+ errorMsgs: errors.slice(0, 3),
+ screenshot: ssPath,
+ });
+ console.log(`[OK] ${pg.label} (${loadTime}ms) — title: "${title}" links:${links} imgs:${images}`);
+ } catch (e) {
+ status = 'FAIL';
+ RESULTS.push({ page: pg.label, url: pg.url, status: 'FAIL', error: e.message });
+ console.error(`[FAIL] ${pg.label}: ${e.message}`);
+ }
+
+ await page.close();
+ }
+
+ // 모바일 홈 스크린샷
+ const mobilePage = await context.newPage();
+ await mobilePage.setViewportSize({ width: 390, height: 844 });
+ await mobilePage.goto(BASE + '/', { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
+ await mobilePage.screenshot({ path: path.join(OUT, '06_mobile_home.png'), fullPage: false });
+ await mobilePage.close();
+ console.log('[OK] 모바일 홈 스크린샷 완료');
+
+ await browser.close();
+
+ // 결과 출력
+ console.log('\n=== 테스트 결과 요약 ===');
+ const passed = RESULTS.filter(r => r.status !== 'FAIL').length;
+ console.log(`통과: ${passed}/${RESULTS.length}`);
+ RESULTS.forEach(r => {
+ if (r.status === 'FAIL') {
+ console.log(` FAIL ${r.page}: ${r.error}`);
+ } else {
+ const warn = r.errors > 0 ? ` [콘솔오류 ${r.errors}개]` : '';
+ console.log(` PASS ${r.page} HTTP${r.status} ${r.loadMs}ms${warn}`);
+ }
+ });
+
+ // JSON 저장
+ fs.writeFileSync(path.join(OUT, 'test-result.json'), JSON.stringify(RESULTS, null, 2));
+ console.log(`\n스크린샷 저장 위치: ${OUT}`);
+ process.exit(passed === RESULTS.length ? 0 : 1);
+})();