diff --git a/assets/app/js/monacoLoader.js b/assets/app/js/monacoLoader.js deleted file mode 100644 index 0ba3cc3..0000000 --- a/assets/app/js/monacoLoader.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict";/*!----------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1) - * Released under the MIT license - * https://github.com/microsoft/vscode/blob/main/LICENSE.txt - *-----------------------------------------------------------*/const _amdLoaderGlobal=this,_commonjsGlobal=typeof global=="object"?global:{};var AMDLoader;(function(u){u.global=_amdLoaderGlobal;class y{get isWindows(){return this._detect(),this._isWindows}get isNode(){return this._detect(),this._isNode}get isElectronRenderer(){return this._detect(),this._isElectronRenderer}get isWebWorker(){return this._detect(),this._isWebWorker}get isElectronNodeIntegrationWebWorker(){return this._detect(),this._isElectronNodeIntegrationWebWorker}constructor(){this._detected=!1,this._isWindows=!1,this._isNode=!1,this._isElectronRenderer=!1,this._isWebWorker=!1,this._isElectronNodeIntegrationWebWorker=!1}_detect(){this._detected||(this._detected=!0,this._isWindows=y._isWindows(),this._isNode=typeof module<"u"&&!!module.exports,this._isElectronRenderer=typeof process<"u"&&typeof process.versions<"u"&&typeof process.versions.electron<"u"&&process.type==="renderer",this._isWebWorker=typeof u.global.importScripts=="function",this._isElectronNodeIntegrationWebWorker=this._isWebWorker&&typeof process<"u"&&typeof process.versions<"u"&&typeof process.versions.electron<"u"&&process.type==="worker")}static _isWindows(){return typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.indexOf("Windows")>=0?!0:typeof process<"u"?process.platform==="win32":!1}}u.Environment=y})(AMDLoader||(AMDLoader={}));var AMDLoader;(function(u){class y{constructor(r,c,a){this.type=r,this.detail=c,this.timestamp=a}}u.LoaderEvent=y;class m{constructor(r){this._events=[new y(1,"",r)]}record(r,c){this._events.push(new y(r,c,u.Utilities.getHighPerformanceTimestamp()))}getEvents(){return this._events}}u.LoaderEventRecorder=m;class p{record(r,c){}getEvents(){return[]}}p.INSTANCE=new p,u.NullLoaderEventRecorder=p})(AMDLoader||(AMDLoader={}));var AMDLoader;(function(u){class y{static fileUriToFilePath(p,h){if(h=decodeURI(h).replace(/%23/g,"#"),p){if(/^file:\/\/\//.test(h))return h.substr(8);if(/^file:\/\//.test(h))return h.substr(5)}else if(/^file:\/\//.test(h))return h.substr(7);return h}static startsWith(p,h){return p.length>=h.length&&p.substr(0,h.length)===h}static endsWith(p,h){return p.length>=h.length&&p.substr(p.length-h.length)===h}static containsQueryString(p){return/^[^\#]*\?/gi.test(p)}static isAbsolutePath(p){return/^((http:\/\/)|(https:\/\/)|(file:\/\/)|(\/))/.test(p)}static forEachProperty(p,h){if(p){let r;for(r in p)p.hasOwnProperty(r)&&h(r,p[r])}}static isEmpty(p){let h=!0;return y.forEachProperty(p,()=>{h=!1}),h}static recursiveClone(p){if(!p||typeof p!="object"||p instanceof RegExp||!Array.isArray(p)&&Object.getPrototypeOf(p)!==Object.prototype)return p;let h=Array.isArray(p)?[]:{};return y.forEachProperty(p,(r,c)=>{c&&typeof c=="object"?h[r]=y.recursiveClone(c):h[r]=c}),h}static generateAnonymousModule(){return"===anonymous"+y.NEXT_ANONYMOUS_ID+++"==="}static isAnonymousModule(p){return y.startsWith(p,"===anonymous")}static getHighPerformanceTimestamp(){return this.PERFORMANCE_NOW_PROBED||(this.PERFORMANCE_NOW_PROBED=!0,this.HAS_PERFORMANCE_NOW=u.global.performance&&typeof u.global.performance.now=="function"),this.HAS_PERFORMANCE_NOW?u.global.performance.now():Date.now()}}y.NEXT_ANONYMOUS_ID=1,y.PERFORMANCE_NOW_PROBED=!1,y.HAS_PERFORMANCE_NOW=!1,u.Utilities=y})(AMDLoader||(AMDLoader={}));var AMDLoader;(function(u){function y(h){if(h instanceof Error)return h;const r=new Error(h.message||String(h)||"Unknown Error");return h.stack&&(r.stack=h.stack),r}u.ensureError=y;class m{static validateConfigurationOptions(r){function c(a){if(a.phase==="loading"){console.error('Loading "'+a.moduleId+'" failed'),console.error(a),console.error("Here are the modules that depend on it:"),console.error(a.neededBy);return}if(a.phase==="factory"){console.error('The factory function of "'+a.moduleId+'" has thrown an exception'),console.error(a),console.error("Here are the modules that depend on it:"),console.error(a.neededBy);return}}if(r=r||{},typeof r.baseUrl!="string"&&(r.baseUrl=""),typeof r.isBuild!="boolean"&&(r.isBuild=!1),typeof r.paths!="object"&&(r.paths={}),typeof r.config!="object"&&(r.config={}),typeof r.catchError>"u"&&(r.catchError=!1),typeof r.recordStats>"u"&&(r.recordStats=!1),typeof r.urlArgs!="string"&&(r.urlArgs=""),typeof r.onError!="function"&&(r.onError=c),Array.isArray(r.ignoreDuplicateModules)||(r.ignoreDuplicateModules=[]),r.baseUrl.length>0&&(u.Utilities.endsWith(r.baseUrl,"/")||(r.baseUrl+="/")),typeof r.cspNonce!="string"&&(r.cspNonce=""),typeof r.preferScriptTags>"u"&&(r.preferScriptTags=!1),r.nodeCachedData&&typeof r.nodeCachedData=="object"&&(typeof r.nodeCachedData.seed!="string"&&(r.nodeCachedData.seed="seed"),(typeof r.nodeCachedData.writeDelay!="number"||r.nodeCachedData.writeDelay<0)&&(r.nodeCachedData.writeDelay=1e3*7),!r.nodeCachedData.path||typeof r.nodeCachedData.path!="string")){const a=y(new Error("INVALID cached data configuration, 'path' MUST be set"));a.phase="configuration",r.onError(a),r.nodeCachedData=void 0}return r}static mergeConfigurationOptions(r=null,c=null){let a=u.Utilities.recursiveClone(c||{});return u.Utilities.forEachProperty(r,(t,e)=>{t==="ignoreDuplicateModules"&&typeof a.ignoreDuplicateModules<"u"?a.ignoreDuplicateModules=a.ignoreDuplicateModules.concat(e):t==="paths"&&typeof a.paths<"u"?u.Utilities.forEachProperty(e,(i,s)=>a.paths[i]=s):t==="config"&&typeof a.config<"u"?u.Utilities.forEachProperty(e,(i,s)=>a.config[i]=s):a[t]=u.Utilities.recursiveClone(e)}),m.validateConfigurationOptions(a)}}u.ConfigurationOptionsUtil=m;class p{constructor(r,c){if(this._env=r,this.options=m.mergeConfigurationOptions(c),this._createIgnoreDuplicateModulesMap(),this._createSortedPathsRules(),this.options.baseUrl===""&&this.options.nodeRequire&&this.options.nodeRequire.main&&this.options.nodeRequire.main.filename&&this._env.isNode){let a=this.options.nodeRequire.main.filename,t=Math.max(a.lastIndexOf("/"),a.lastIndexOf("\\"));this.options.baseUrl=a.substring(0,t+1)}}_createIgnoreDuplicateModulesMap(){this.ignoreDuplicateModulesMap={};for(let r=0;r{Array.isArray(c)?this.sortedPathsRules.push({from:r,to:c}):this.sortedPathsRules.push({from:r,to:[c]})}),this.sortedPathsRules.sort((r,c)=>c.from.length-r.from.length)}cloneAndMerge(r){return new p(this._env,m.mergeConfigurationOptions(r,this.options))}getOptionsLiteral(){return this.options}_applyPaths(r){let c;for(let a=0,t=this.sortedPathsRules.length;athis.triggerCallback(i),d=>this.triggerErrorback(i,d))}triggerCallback(e){let i=this._callbackMap[e];delete this._callbackMap[e];for(let s=0;s{e.removeEventListener("load",l),e.removeEventListener("error",d)},l=o=>{n(),i()},d=o=>{n(),s(o)};e.addEventListener("load",l),e.addEventListener("error",d)}load(e,i,s,n){if(/^node\|/.test(i)){let l=e.getConfig().getOptionsLiteral(),d=c(e.getRecorder(),l.nodeRequire||u.global.nodeRequire),o=i.split("|"),_=null;try{_=d(o[1])}catch(f){n(f);return}e.enqueueDefineAnonymousModule([],()=>_),s()}else{let l=document.createElement("script");l.setAttribute("async","async"),l.setAttribute("type","text/javascript"),this.attachListeners(l,s,n);const{trustedTypesPolicy:d}=e.getConfig().getOptionsLiteral();d&&(i=d.createScriptURL(i)),l.setAttribute("src",i);const{cspNonce:o}=e.getConfig().getOptionsLiteral();o&&l.setAttribute("nonce",o),document.getElementsByTagName("head")[0].appendChild(l)}}}function p(t){const{trustedTypesPolicy:e}=t.getConfig().getOptionsLiteral();try{return(e?self.eval(e.createScript("","true")):new Function("true")).call(self),!0}catch{return!1}}class h{constructor(){this._cachedCanUseEval=null}_canUseEval(e){return this._cachedCanUseEval===null&&(this._cachedCanUseEval=p(e)),this._cachedCanUseEval}load(e,i,s,n){if(/^node\|/.test(i)){const l=e.getConfig().getOptionsLiteral(),d=c(e.getRecorder(),l.nodeRequire||u.global.nodeRequire),o=i.split("|");let _=null;try{_=d(o[1])}catch(f){n(f);return}e.enqueueDefineAnonymousModule([],function(){return _}),s()}else{const{trustedTypesPolicy:l}=e.getConfig().getOptionsLiteral();if(!(/^((http:)|(https:)|(file:))/.test(i)&&i.substring(0,self.origin.length)!==self.origin)&&this._canUseEval(e)){fetch(i).then(o=>{if(o.status!==200)throw new Error(o.statusText);return o.text()}).then(o=>{o=`${o} -//# sourceURL=${i}`,(l?self.eval(l.createScript("",o)):new Function(o)).call(self),s()}).then(void 0,n);return}try{l&&(i=l.createScriptURL(i)),importScripts(i),s()}catch(o){n(o)}}}}class r{constructor(e){this._env=e,this._didInitialize=!1,this._didPatchNodeRequire=!1}_init(e){this._didInitialize||(this._didInitialize=!0,this._fs=e("fs"),this._vm=e("vm"),this._path=e("path"),this._crypto=e("crypto"))}_initNodeRequire(e,i){const{nodeCachedData:s}=i.getConfig().getOptionsLiteral();if(!s||this._didPatchNodeRequire)return;this._didPatchNodeRequire=!0;const n=this,l=e("module");function d(o){const _=o.constructor;let f=function(v){try{return o.require(v)}finally{}};return f.resolve=function(v,E){return _._resolveFilename(v,o,!1,E)},f.resolve.paths=function(v){return _._resolveLookupPaths(v,o)},f.main=process.mainModule,f.extensions=_._extensions,f.cache=_._cache,f}l.prototype._compile=function(o,_){const f=l.wrap(o.replace(/^#!.*/,"")),g=i.getRecorder(),v=n._getCachedDataPath(s,_),E={filename:_};let I;try{const D=n._fs.readFileSync(v);I=D.slice(0,16),E.cachedData=D.slice(16),g.record(60,v)}catch{g.record(61,v)}const C=new n._vm.Script(f,E),P=C.runInThisContext(E),w=n._path.dirname(_),R=d(this),U=[this.exports,R,this,_,w,process,_commonjsGlobal,Buffer],b=P.apply(this.exports,U);return n._handleCachedData(C,f,v,!E.cachedData,i),n._verifyCachedData(C,f,v,I,i),b}}load(e,i,s,n){const l=e.getConfig().getOptionsLiteral(),d=c(e.getRecorder(),l.nodeRequire||u.global.nodeRequire),o=l.nodeInstrumenter||function(f){return f};this._init(d),this._initNodeRequire(d,e);let _=e.getRecorder();if(/^node\|/.test(i)){let f=i.split("|"),g=null;try{g=d(f[1])}catch(v){n(v);return}e.enqueueDefineAnonymousModule([],()=>g),s()}else{i=u.Utilities.fileUriToFilePath(this._env.isWindows,i);const f=this._path.normalize(i),g=this._getElectronRendererScriptPathOrUri(f),v=!!l.nodeCachedData,E=v?this._getCachedDataPath(l.nodeCachedData,i):void 0;this._readSourceAndCachedData(f,E,_,(I,C,P,w)=>{if(I){n(I);return}let R;C.charCodeAt(0)===r._BOM?R=r._PREFIX+C.substring(1)+r._SUFFIX:R=r._PREFIX+C+r._SUFFIX,R=o(R,f);const U={filename:g,cachedData:P},b=this._createAndEvalScript(e,R,U,s,n);this._handleCachedData(b,R,E,v&&!P,e),this._verifyCachedData(b,R,E,w,e)})}}_createAndEvalScript(e,i,s,n,l){const d=e.getRecorder();d.record(31,s.filename);const o=new this._vm.Script(i,s),_=o.runInThisContext(s),f=e.getGlobalAMDDefineFunc();let g=!1;const v=function(){return g=!0,f.apply(null,arguments)};return v.amd=f.amd,_.call(u.global,e.getGlobalAMDRequireFunc(),v,s.filename,this._path.dirname(s.filename)),d.record(32,s.filename),g?n():l(new Error(`Didn't receive define call in ${s.filename}!`)),o}_getElectronRendererScriptPathOrUri(e){if(!this._env.isElectronRenderer)return e;let i=e.match(/^([a-z])\:(.*)/i);return i?`file:///${(i[1].toUpperCase()+":"+i[2]).replace(/\\/g,"/")}`:`file://${e}`}_getCachedDataPath(e,i){const s=this._crypto.createHash("md5").update(i,"utf8").update(e.seed,"utf8").update(process.arch,"").digest("hex"),n=this._path.basename(i).replace(/\.js$/,"");return this._path.join(e.path,`${n}-${s}.code`)}_handleCachedData(e,i,s,n,l){e.cachedDataRejected?this._fs.unlink(s,d=>{l.getRecorder().record(62,s),this._createAndWriteCachedData(e,i,s,l),d&&l.getConfig().onError(d)}):n&&this._createAndWriteCachedData(e,i,s,l)}_createAndWriteCachedData(e,i,s,n){let l=Math.ceil(n.getConfig().getOptionsLiteral().nodeCachedData.writeDelay*(1+Math.random())),d=-1,o=0,_;const f=()=>{setTimeout(()=>{_||(_=this._crypto.createHash("md5").update(i,"utf8").digest());const g=e.createCachedData();if(!(g.length===0||g.length===d||o>=5)){if(g.length{v&&n.getConfig().onError(v),n.getRecorder().record(63,s),f()})}},l*Math.pow(4,o++))};f()}_readSourceAndCachedData(e,i,s,n){if(!i)this._fs.readFile(e,{encoding:"utf8"},n);else{let l,d,o,_=2;const f=g=>{g?n(g):--_===0&&n(void 0,l,d,o)};this._fs.readFile(e,{encoding:"utf8"},(g,v)=>{l=v,f(g)}),this._fs.readFile(i,(g,v)=>{!g&&v&&v.length>0?(o=v.slice(0,16),d=v.slice(16),s.record(60,i)):s.record(61,i),f()})}}_verifyCachedData(e,i,s,n,l){n&&(e.cachedDataRejected||setTimeout(()=>{const d=this._crypto.createHash("md5").update(i,"utf8").digest();n.equals(d)||(l.getConfig().onError(new Error(`FAILED TO VERIFY CACHED DATA, deleting stale '${s}' now, but a RESTART IS REQUIRED`)),this._fs.unlink(s,o=>{o&&l.getConfig().onError(o)}))},Math.ceil(5e3*(1+Math.random()))))}}r._BOM=65279,r._PREFIX="(function (require, define, __filename, __dirname) { ",r._SUFFIX=` -});`;function c(t,e){if(e.__$__isRecorded)return e;const i=function(n){t.record(33,n);try{return e(n)}finally{t.record(34,n)}};return i.__$__isRecorded=!0,i}u.ensureRecordedNodeRequire=c;function a(t){return new y(t)}u.createScriptLoader=a})(AMDLoader||(AMDLoader={}));var AMDLoader;(function(u){class y{constructor(t){let e=t.lastIndexOf("/");e!==-1?this.fromModulePath=t.substr(0,e+1):this.fromModulePath=""}static _normalizeModuleId(t){let e=t,i;for(i=/\/\.\//;i.test(e);)e=e.replace(i,"/");for(e=e.replace(/^\.\//g,""),i=/\/(([^\/])|([^\/][^\/\.])|([^\/\.][^\/])|([^\/][^\/][^\/]+))\/\.\.\//;i.test(e);)e=e.replace(i,"/");return e=e.replace(/^(([^\/])|([^\/][^\/\.])|([^\/\.][^\/])|([^\/][^\/][^\/]+))\/\.\.\//,""),e}resolveModule(t){let e=t;return u.Utilities.isAbsolutePath(e)||(u.Utilities.startsWith(e,"./")||u.Utilities.startsWith(e,"../"))&&(e=y._normalizeModuleId(this.fromModulePath+e)),e}}y.ROOT=new y(""),u.ModuleIdResolver=y;class m{constructor(t,e,i,s,n,l){this.id=t,this.strId=e,this.dependencies=i,this._callback=s,this._errorback=n,this.moduleIdResolver=l,this.exports={},this.error=null,this.exportsPassedIn=!1,this.unresolvedDependenciesCount=this.dependencies.length,this._isComplete=!1}static _safeInvokeFunction(t,e){try{return{returnedValue:t.apply(u.global,e),producedError:null}}catch(i){return{returnedValue:null,producedError:i}}}static _invokeFactory(t,e,i,s){return t.shouldInvokeFactory(e)?t.shouldCatchError()?this._safeInvokeFunction(i,s):{returnedValue:i.apply(u.global,s),producedError:null}:{returnedValue:null,producedError:null}}complete(t,e,i,s){this._isComplete=!0;let n=null;if(this._callback)if(typeof this._callback=="function"){t.record(21,this.strId);let l=m._invokeFactory(e,this.strId,this._callback,i);n=l.producedError,t.record(22,this.strId),!n&&typeof l.returnedValue<"u"&&(!this.exportsPassedIn||u.Utilities.isEmpty(this.exports))&&(this.exports=l.returnedValue)}else this.exports=this._callback;if(n){let l=u.ensureError(n);l.phase="factory",l.moduleId=this.strId,l.neededBy=s(this.id),this.error=l,e.onError(l)}this.dependencies=null,this._callback=null,this._errorback=null,this.moduleIdResolver=null}onDependencyError(t){return this._isComplete=!0,this.error=t,this._errorback?(this._errorback(t),!0):!1}isComplete(){return this._isComplete}}u.Module=m;class p{constructor(){this._nextId=0,this._strModuleIdToIntModuleId=new Map,this._intModuleIdToStrModuleId=[],this.getModuleId("exports"),this.getModuleId("module"),this.getModuleId("require")}getMaxModuleId(){return this._nextId}getModuleId(t){let e=this._strModuleIdToIntModuleId.get(t);return typeof e>"u"&&(e=this._nextId++,this._strModuleIdToIntModuleId.set(t,e),this._intModuleIdToStrModuleId[e]=t),e}getStrModuleId(t){return this._intModuleIdToStrModuleId[t]}}class h{constructor(t){this.id=t}}h.EXPORTS=new h(0),h.MODULE=new h(1),h.REQUIRE=new h(2),u.RegularDependency=h;class r{constructor(t,e,i){this.id=t,this.pluginId=e,this.pluginParam=i}}u.PluginDependency=r;class c{constructor(t,e,i,s,n=0){this._env=t,this._scriptLoader=e,this._loaderAvailableTimestamp=n,this._defineFunc=i,this._requireFunc=s,this._moduleIdProvider=new p,this._config=new u.Configuration(this._env),this._hasDependencyCycle=!1,this._modules2=[],this._knownModules2=[],this._inverseDependencies2=[],this._inversePluginDependencies2=new Map,this._currentAnonymousDefineCall=null,this._recorder=null,this._buildInfoPath=[],this._buildInfoDefineStack=[],this._buildInfoDependencies=[],this._requireFunc.moduleManager=this}reset(){return new c(this._env,this._scriptLoader,this._defineFunc,this._requireFunc,this._loaderAvailableTimestamp)}getGlobalAMDDefineFunc(){return this._defineFunc}getGlobalAMDRequireFunc(){return this._requireFunc}static _findRelevantLocationInStack(t,e){let i=l=>l.replace(/\\/g,"/"),s=i(t),n=e.split(/\n/);for(let l=0;lthis._moduleIdProvider.getStrModuleId(_.id))),this._resolve(o)}_normalizeDependency(t,e){if(t==="exports")return h.EXPORTS;if(t==="module")return h.MODULE;if(t==="require")return h.REQUIRE;let i=t.indexOf("!");if(i>=0){let s=e.resolveModule(t.substr(0,i)),n=e.resolveModule(t.substr(i+1)),l=this._moduleIdProvider.getModuleId(s+"!"+n),d=this._moduleIdProvider.getModuleId(s);return new r(l,d,n)}return new h(this._moduleIdProvider.getModuleId(e.resolveModule(t)))}_normalizeDependencies(t,e){let i=[],s=0;for(let n=0,l=t.length;nthis._moduleIdProvider.getStrModuleId(l));const n=u.ensureError(e);return n.phase="loading",n.moduleId=i,n.neededBy=s,n}_onLoadError(t,e){const i=this._createLoadError(t,e);this._modules2[t]||(this._modules2[t]=new m(t,this._moduleIdProvider.getStrModuleId(t),[],()=>{},null,null));let s=[];for(let d=0,o=this._moduleIdProvider.getMaxModuleId();d0;){let d=l.shift(),o=this._modules2[d];o&&(n=o.onDependencyError(i)||n);let _=this._inverseDependencies2[d];if(_)for(let f=0,g=_.length;f0;){let d=n.shift().dependencies;if(d)for(let o=0,_=d.length;o<_;o++){let f=d[o];if(f.id===e)return!0;let g=this._modules2[f.id];g&&!s[f.id]&&(s[f.id]=!0,n.push(g))}}return!1}_findCyclePath(t,e,i){if(t===e||i===50)return[t];let s=this._modules2[t];if(!s)return null;let n=s.dependencies;if(n)for(let l=0,d=n.length;lthis._relativeRequire(t,i,s,n);return e.toUrl=i=>this._config.requireToUrl(t.resolveModule(i)),e.getStats=()=>this.getLoaderEvents(),e.hasDependencyCycle=()=>this._hasDependencyCycle,e.config=(i,s=!1)=>{this.configure(i,s)},e.__$__nodeRequire=u.global.nodeRequire,e}_loadModule(t){if(this._modules2[t]||this._knownModules2[t])return;this._knownModules2[t]=!0;let e=this._moduleIdProvider.getStrModuleId(t),i=this._config.moduleIdToPaths(e),s=/^@[^\/]+\/[^\/]+$/;this._env.isNode&&(e.indexOf("/")===-1||s.test(e))&&i.push("node|"+e);let n=-1,l=d=>{if(n++,n>=i.length)this._onLoadError(t,d);else{let o=i[n],_=this.getRecorder();if(this._config.isBuild()&&o==="empty:"){this._buildInfoPath[t]=o,this.defineModule(this._moduleIdProvider.getStrModuleId(t),[],null,null,null),this._onLoad(t);return}_.record(10,o),this._scriptLoader.load(this,o,()=>{this._config.isBuild()&&(this._buildInfoPath[t]=o),_.record(11,o),this._onLoad(t)},f=>{_.record(12,o),l(f)})}};l(null)}_loadPluginDependency(t,e){if(this._modules2[e.id]||this._knownModules2[e.id])return;this._knownModules2[e.id]=!0;let i=s=>{this.defineModule(this._moduleIdProvider.getStrModuleId(e.id),[],s,null,null)};i.error=s=>{this._config.onError(this._createLoadError(e.id,s))},t.load(e.pluginParam,this._createRequire(y.ROOT),i,this._config.getOptionsLiteral())}_resolve(t){let e=t.dependencies;if(e)for(let i=0,s=e.length;ithis._moduleIdProvider.getStrModuleId(o)).join(` => -`)),t.unresolvedDependenciesCount--;continue}if(this._inverseDependencies2[n.id]=this._inverseDependencies2[n.id]||[],this._inverseDependencies2[n.id].push(t.id),n instanceof r){let d=this._modules2[n.pluginId];if(d&&d.isComplete()){this._loadPluginDependency(d.exports,n);continue}let o=this._inversePluginDependencies2.get(n.pluginId);o||(o=[],this._inversePluginDependencies2.set(n.pluginId,o)),o.push(n),this._loadModule(n.pluginId);continue}this._loadModule(n.id)}t.unresolvedDependenciesCount===0&&this._onModuleComplete(t)}_onModuleComplete(t){let e=this.getRecorder();if(t.isComplete())return;let i=t.dependencies,s=[];if(i)for(let o=0,_=i.length;o<_;o++){let f=i[o];if(f===h.EXPORTS){s[o]=t.exports;continue}if(f===h.MODULE){s[o]={id:t.strId,config:()=>this._config.getConfigForModule(t.strId)};continue}if(f===h.REQUIRE){s[o]=this._createRequire(t.moduleIdResolver);continue}let g=this._modules2[f.id];if(g){s[o]=g.exports;continue}s[o]=null}const n=o=>(this._inverseDependencies2[o]||[]).map(_=>this._moduleIdProvider.getStrModuleId(_));t.complete(e,this._config,s,n);let l=this._inverseDependencies2[t.id];if(this._inverseDependencies2[t.id]=null,l)for(let o=0,_=l.length;o<_;o++){let f=l[o],g=this._modules2[f];g.unresolvedDependenciesCount--,g.unresolvedDependenciesCount===0&&this._onModuleComplete(g)}let d=this._inversePluginDependencies2.get(t.id);if(d){this._inversePluginDependencies2.delete(t.id);for(let o=0,_=d.length;o<_;o++)this._loadPluginDependency(t.exports,d[o])}}}u.ModuleManager=c})(AMDLoader||(AMDLoader={}));var define,AMDLoader;(function(u){const y=new u.Environment;let m=null;const p=function(a,t,e){typeof a!="string"&&(e=t,t=a,a=null),(typeof t!="object"||!Array.isArray(t))&&(e=t,t=null),t||(t=["require","exports","module"]),a?m.defineModule(a,t,e,null,null):m.enqueueDefineAnonymousModule(t,e)};p.amd={jQuery:!0};const h=function(a,t=!1){m.configure(a,t)},r=function(){if(arguments.length===1){if(arguments[0]instanceof Object&&!Array.isArray(arguments[0])){h(arguments[0]);return}if(typeof arguments[0]=="string")return m.synchronousRequire(arguments[0])}if((arguments.length===2||arguments.length===3)&&Array.isArray(arguments[0])){m.defineModule(u.Utilities.generateAnonymousModule(),arguments[0],arguments[1],arguments[2],null);return}throw new Error("Unrecognized require call")};r.config=h,r.getConfig=function(){return m.getConfig().getOptionsLiteral()},r.reset=function(){m=m.reset()},r.getBuildInfo=function(){return m.getBuildInfo()},r.getStats=function(){return m.getLoaderEvents()},r.define=p;function c(){if(typeof u.global.require<"u"||typeof require<"u"){const a=u.global.require||require;if(typeof a=="function"&&typeof a.resolve=="function"){const t=u.ensureRecordedNodeRequire(m.getRecorder(),a);u.global.nodeRequire=t,r.nodeRequire=t,r.__$__nodeRequire=t}}y.isNode&&!y.isElectronRenderer&&!y.isElectronNodeIntegrationWebWorker?module.exports=r:(y.isElectronRenderer||(u.global.define=p),u.global.require=r)}u.init=c,(typeof u.global.define!="function"||!u.global.define.amd)&&(m=new u.ModuleManager(y,u.createScriptLoader(y),p,r,u.Utilities.getHighPerformanceTimestamp()),typeof u.global.require<"u"&&typeof u.global.require!="function"&&r.config(u.global.require),define=function(){return p.apply(null,arguments)},define.amd=p.amd,typeof doNotInitLoader>"u"&&c())})(AMDLoader||(AMDLoader={})); - -//# sourceMappingURL=../../min-maps/vs/loader.js.map \ No newline at end of file diff --git a/package.json b/package.json index 24dce19..b723725 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "electron": "43.2.0", "electron-builder": "26.8.1", "lucide-solid": "^1.16.0", + "monaco-editor": "0.52.2", "rolldown": "1.0.3", "rollup-plugin-copy": "^3.5.0", "solid-js": "^1.9.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d379739..87b4c22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,6 +84,9 @@ importers: lucide-solid: specifier: ^1.16.0 version: 1.16.0(solid-js@1.9.14) + monaco-editor: + specifier: 0.52.2 + version: 0.52.2 rolldown: specifier: 1.0.3 version: 1.0.3 @@ -2104,6 +2107,9 @@ packages: mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + monaco-editor@0.52.2: + resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -4845,6 +4851,8 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.4 + monaco-editor@0.52.2: {} + ms@2.1.3: {} neo-async@2.6.2: {} diff --git a/rolldown.config.ts b/rolldown.config.ts index c63609e..72f49b2 100644 --- a/rolldown.config.ts +++ b/rolldown.config.ts @@ -34,6 +34,8 @@ export default defineConfig([ { src: "src/**/**/*.js", dest: "ts-out/js/" }, { src: "package.json", dest: "ts-out/" }, { src: "assets/**/**", dest: "ts-out/assets/" }, + // Monaco AMD tree for the offline Quick CSS editor (next to editor.html) + { src: "node_modules/monaco-editor/min/vs/**/*", dest: "ts-out/html/monaco/vs" }, ], }), ], diff --git a/src/common/sanitization.ts b/src/common/sanitization.ts new file mode 100644 index 0000000..a5c69a1 --- /dev/null +++ b/src/common/sanitization.ts @@ -0,0 +1,88 @@ +/** True when hostname is exactly `domain` or a subdomain of it (case-insensitive). */ +export function hostnameMatches(hostname: string, domain: string): boolean { + const host = hostname.toLowerCase(); + const target = domain.toLowerCase(); + return host === target || host.endsWith(`.${target}`); +} + +export function tryParseUrl(urlString: string): URL | null { + try { + return new URL(urlString); + } catch { + return null; + } +} + +/** True when the frame is a YouTube embed (or Discord Activities YouTube proxy). */ +export function isYouTubeEmbedOrProxyFrame(frameUrl: string): boolean { + const url = tryParseUrl(frameUrl); + if (!url) return false; + + if (hostnameMatches(url.hostname, "youtube.com") && url.pathname.includes("/embed/")) { + return true; + } + + // Discord Activities may proxy YouTube through *.discordsays.com (youtube.com appears in the path/query) + if ( + hostnameMatches(url.hostname, "discordsays.com") && + (url.pathname.includes("youtube.com") || url.search.includes("youtube.com")) + ) { + return true; + } + + return false; +} + +/** Telemetry / noise hosts and Discord science endpoints we cancel in webRequest. */ +export function isTelemetryBlockedUrl(requestUrl: string): boolean { + const url = tryParseUrl(requestUrl); + if (url?.protocol !== "https:") return false; + + if (/^\/api\/v\d+\/science(?:\/|$)/.test(url.pathname)) return true; + if (hostnameMatches(url.hostname, "sentry.io")) return true; + if (hostnameMatches(url.hostname, "nel.cloudflare.com")) return true; + + return false; +} + +/** + * Blob downloads for Discord calendar (.ics) use `blob:https://discord.com/`. + * Parse the blob payload URL so `discord.com.evil` / userinfo tricks cannot match. + */ +export function isDiscordIcsBlobUrl(urlString: string): boolean { + const blobUrl = tryParseUrl(urlString); + if (blobUrl?.protocol !== "blob:") return false; + + const inner = tryParseUrl(blobUrl.pathname); + if (!inner || (inner.protocol !== "https:" && inner.protocol !== "http:")) return false; + + return hostnameMatches(inner.hostname, "discord.com"); +} + +/** Discord stream popout windows (stable / canary / PTB). */ +export function isDiscordPopoutUrl(urlString: string): boolean { + const url = tryParseUrl(urlString); + if (url?.protocol !== "https:" || url.pathname !== "/popout") return false; + + const host = url.hostname.toLowerCase(); + return host === "discord.com" || host === "canary.discord.com" || host === "ptb.discord.com"; +} + +const DEFAULT_ALLOWED_LOCALHOST_WS_PORTS = new Set([1211, 1112, 6888]); + +/** + * Block stray localhost WebSocket probes, except known Legcord/local RPC ports. + * Uses URL parsing instead of substring checks on the raw request URL. + */ +export function isBlockedLocalhostWebSocket( + requestUrl: string, + allowedPorts: ReadonlySet = DEFAULT_ALLOWED_LOCALHOST_WS_PORTS, +): boolean { + const url = tryParseUrl(requestUrl); + if (!url || (url.protocol !== "ws:" && url.protocol !== "wss:")) return false; + if (url.hostname !== "127.0.0.1" && url.hostname !== "localhost") return false; + + const port = url.port ? Number(url.port) : url.protocol === "wss:" ? 443 : 80; + if (!Number.isFinite(port)) return true; + return !allowedPorts.has(port); +} diff --git a/src/cssEditor/editor.html b/src/cssEditor/editor.html index da5d146..a02d850 100644 --- a/src/cssEditor/editor.html +++ b/src/cssEditor/editor.html @@ -6,20 +6,23 @@ - +
- \ No newline at end of file + diff --git a/src/cssEditor/main.ts b/src/cssEditor/main.ts index 9543d97..9f20e16 100644 --- a/src/cssEditor/main.ts +++ b/src/cssEditor/main.ts @@ -14,7 +14,7 @@ export function openCssEditor(file: string) { preload: path.join(import.meta.dirname, "cssEditor", "preload.mjs"), }, }); - cssWindow.loadURL("legcord://html/editor.html"); + void cssWindow.loadFile(path.join(import.meta.dirname, "html", "editor.html")); ipcMain.on("editor-setCSS", (_event, css: string) => { fs.writeFileSync(file, css); diff --git a/src/discord/window.ts b/src/discord/window.ts index 5057644..7cb529d 100644 --- a/src/discord/window.ts +++ b/src/discord/window.ts @@ -19,6 +19,13 @@ import { navigateTo } from "../common/dom.js"; import { forceQuit, setForceQuit } from "../common/forceQuit.js"; import { handleCommands, passedValidArgument } from "../common/handleCommands.js"; import { getLang } from "../common/lang.js"; +import { + isBlockedLocalhostWebSocket, + isDiscordIcsBlobUrl, + isDiscordPopoutUrl, + isTelemetryBlockedUrl, + isYouTubeEmbedOrProxyFrame, +} from "../common/sanitization.js"; import { injectThemesMain } from "../common/themes.js"; import { DEFAULT_WINDOW_HEIGHT, @@ -199,10 +206,7 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { return; } frame.once("dom-ready", async () => { - if ( - frame.url.includes("youtube.com/embed/") || - (frame.url.includes("discordsays") && frame.url.includes("youtube.com")) - ) { + if (isYouTubeEmbedOrProxyFrame(frame.url)) { await frame.executeJavaScript(readFileSync(path.join(__dirname, "assets/js/adguard.js"), "utf-8")); } }); @@ -211,18 +215,14 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { // Allow about:blank (used by Vencord & Equicord QuickCss popup) if (url === "about:blank") return { action: "allow" }; // Saving ics files on future events - if (url.startsWith("blob:https://discord.com/")) { + if (isDiscordIcsBlobUrl(url)) { return { action: "allow", overrideBrowserWindowOptions: { show: false }, }; } // Allow Discord stream popout - if ( - url === "https://discord.com/popout" || - url === "https://canary.discord.com/popout" || - url === "https://ptb.discord.com/popout" - ) + if (isDiscordPopoutUrl(url)) return { action: "allow", overrideBrowserWindowOptions: { @@ -267,21 +267,8 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { registerCustomHandler(); - const blockedPatterns = [ - /https:\/\/.*\/api\/v\d+\/science/, - /https:\/\/sentry\.io\/.*/, - /https:\/\/.*\.nel\.cloudflare\.com\/.*/, - ]; passedWindow.webContents.session.webRequest.onBeforeRequest((details, callback) => { - if (blockedPatterns.some((pattern) => pattern.test(details.url))) { - return callback({ cancel: true }); - } - if ( - details.url.includes("ws://127.0.0.1:") && - !details.url.includes("127.0.0.1:1211") && - !details.url.includes("127.0.0.1:1112") && - !details.url.includes("127.0.0.1:6888") - ) { + if (isTelemetryBlockedUrl(details.url) || isBlockedLocalhostWebSocket(details.url)) { return callback({ cancel: true }); } return callback({}); diff --git a/src/shelter/settings/components/AboutPopup.tsx b/src/shelter/settings/components/AboutPopup.tsx index 7edef0f..d27f3c8 100644 --- a/src/shelter/settings/components/AboutPopup.tsx +++ b/src/shelter/settings/components/AboutPopup.tsx @@ -41,17 +41,60 @@ export const AboutPopup = (props: { close: () => void }) => {
-

Legcord

+
+ +
+ + {window.legcord?.version === "0.0.0" + ? "Dev Build" + : `v${window.legcord?.version ?? "1.3.0"}`} +

- Legcord is a free and open-source Discord client that offers a personalized experience with - advanced features, better performance, and a modern design. Developed by the community, for - the community. + A free and open-source Discord client crafted by the community, for the community — + delivering advanced features, better performance, and a modern design.

- {/* Quick Actions */}
-

Quick Actions

+

Quick Actions

- {/* Contributors Section */}
-

Contributors

+

Contributors

+ {!loading() && !error() && ( + {contributors().length} contributors + )} {loading() &&
Loading contributors...
} {error() &&
Failed to load contributors: {error()}
} {!loading() && !error() && ( @@ -147,6 +249,22 @@ export const AboutPopup = (props: { close: () => void }) => {
)}
+ +
+

Technologies

+
+ Electron + SolidJS + TypeScript + Rolldown + shelter + Biome +
+
+ +
+ Copyright © Legcord 2024-2026 · OSL-3.0 +
diff --git a/src/shelter/settings/components/SupportBanner.module.css b/src/shelter/settings/components/SupportBanner.module.css index e62fcfa..31be299 100644 --- a/src/shelter/settings/components/SupportBanner.module.css +++ b/src/shelter/settings/components/SupportBanner.module.css @@ -93,75 +93,75 @@ min-height: auto; } -.aboutHeader { - text-align: center; - margin-bottom: 32px; - padding: 24px; - background: var(--background-secondary); - border: 1px solid var(--background-modifier-accent); - border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +.aboutLogo { + display: flex; + justify-content: center; + margin-bottom: 20px; } -.aboutTitle { - font-size: 32px; - font-weight: 700; - margin: 0 0 20px 0; - color: var(--header-primary); - letter-spacing: -0.5px; +.aboutLogo svg { + opacity: 0.95; +} + +.aboutHeader { + text-align: center; + margin-bottom: 24px; + padding: 32px 24px 24px; + background: linear-gradient(180deg, var(--background-secondary-alt) 0%, var(--background-secondary) 100%); + border: 1px solid var(--background-modifier-accent); + border-radius: 12px; +} + +.aboutVersion { + font-size: 13px; + color: var(--text-muted); + margin: 0 0 16px 0; + padding: 6px 14px; + background: var(--background-tertiary); + border-radius: 20px; + display: inline-block; + font-family: var(--font-mono); + border: 1px solid var(--background-modifier-accent); + font-weight: 500; + letter-spacing: 0.3px; } .aboutDescription { - font-size: 16px; - line-height: 1.6; + font-size: 14px; + line-height: 1.5; color: var(--text-muted); - margin: 0 0 24px 0; - max-width: 600px; + margin: 0; + padding: 0 8px; + max-width: 520px; margin-left: auto; margin-right: auto; font-weight: 400; } -.aboutVersion { - font-size: 14px; - color: var(--text-muted); - margin: 0; - padding: 10px 18px; - background: var(--background-tertiary); - border-radius: 8px; - display: inline-block; - font-family: var(--font-mono); - border: 1px solid var(--background-modifier-accent); - font-weight: 500; +.sectionTitle { + font-size: 16px; + font-weight: 600; + margin: 0 0 16px 0; + color: var(--header-primary); + letter-spacing: -0.2px; + text-transform: uppercase; + opacity: 0.7; } .quickActions { - background: var(--background-secondary); - border: 1px solid var(--background-modifier-accent); - border-radius: 12px; - padding: 20px; - margin: 0 0 32px 0; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); -} - -.quickActionsTitle { - font-size: 20px; - font-weight: 600; - margin: 0 0 20px 0; - color: var(--header-primary); - letter-spacing: -0.3px; + margin-bottom: 24px; } .quickActionsList { display: flex; flex-direction: column; - gap: 12px; + gap: 8px; } .quickActionButton { - background: var(--background-secondary-alt); + background: var(--background-secondary); border: 1px solid var(--background-modifier-accent); - border-radius: 8px; + border-radius: 10px; padding: 12px 16px; font-size: 14px; font-weight: 500; @@ -171,73 +171,110 @@ align-items: center; text-align: left; width: 100%; - transition: all 0.15s ease; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + transition: all 0.2s ease; position: relative; overflow: hidden; } +.quickActionButton::before { + content: ""; + position: absolute; + inset: 0; + background: var(--brand-experiment); + opacity: 0; + transition: opacity 0.2s ease; +} + .quickActionButton:hover { - background: var(--background-tertiary); - border-color: var(--background-modifier-hover); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - transform: translateY(-1px); + border-color: var(--brand-experiment); + transform: translateX(4px); +} + +.quickActionButton:hover::before { + opacity: 0.04; } .quickActionButton:active { - transform: translateY(0); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + transform: translateX(2px); } .quickActionButtonIcon { margin-right: 12px; - font-size: 18px; - color: var(--interactive-normal); - transition: color 0.15s ease; display: flex; align-items: center; justify-content: center; - width: 20px; - height: 20px; + width: 36px; + height: 36px; + border-radius: 8px; + background: var(--background-tertiary); + color: var(--interactive-normal); + flex-shrink: 0; + transition: all 0.2s ease; } .quickActionButton:hover .quickActionButtonIcon { - color: var(--interactive-hover); + color: var(--brand-experiment); + background: var(--background-secondary-alt); +} + +.quickActionButtonLabel { + display: flex; + flex-direction: column; + gap: 2px; + flex: 1; + min-width: 0; +} + +.quickActionButtonTitle { + font-size: 14px; + font-weight: 600; + color: var(--header-primary); +} + +.quickActionButtonDesc { + font-size: 12px; + color: var(--text-muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.quickActionButtonArrow { + margin-left: 8px; + color: var(--text-muted); + flex-shrink: 0; + opacity: 0; + transition: all 0.2s ease; +} + +.quickActionButton:hover .quickActionButtonArrow { + opacity: 1; + transform: translateX(2px); + color: var(--brand-experiment); } .contributorsSection { padding: 20px; background: var(--background-secondary); - border: 1px solid var(--background-accent); + border: 1px solid var(--background-modifier-accent); border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); -} - -.contributorsTitle { - font-size: 20px; - font-weight: 600; - margin: 0 0 24px 0; - color: var(--header-primary); - text-align: center; - letter-spacing: -0.3px; } .contributorsGrid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 16px; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 10px; padding: 0; max-width: 100%; width: 100%; - justify-items: center; - max-height: 40vh; + max-height: 280px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--background-modifier-accent) transparent; } .contributorsGrid::-webkit-scrollbar { - width: 6px; + width: 4px; } .contributorsGrid::-webkit-scrollbar-track { @@ -246,7 +283,7 @@ .contributorsGrid::-webkit-scrollbar-thumb { background: var(--background-modifier-accent); - border-radius: 3px; + border-radius: 2px; } .contributorsGrid::-webkit-scrollbar-thumb:hover { @@ -256,27 +293,23 @@ .contributorCard { background: var(--background-primary); border: 1px solid var(--background-modifier-accent); - border-radius: 12px; - padding: 20px; + border-radius: 10px; + padding: 14px 12px; text-align: center; - transition: all 0.15s ease; - min-width: 180px; - max-width: 100%; + transition: all 0.2s ease; box-sizing: border-box; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); - position: relative; } .contributorCard:hover { - background: var(--background-secondary); - border-color: var(--background-modifier-hover); - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); + background: var(--background-secondary-alt); + border-color: var(--brand-experiment); transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } .contributorLink { @@ -290,46 +323,98 @@ } .contributorAvatar { - width: 64px; - height: 64px; + width: 48px; + height: 48px; border-radius: 50%; - margin-bottom: 16px; + margin-bottom: 10px; max-width: 100%; - border: 3px solid var(--background-modifier-accent); + border: 2px solid var(--background-modifier-accent); object-fit: cover; - transition: all 0.15s ease; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: all 0.2s ease; } .contributorCard:hover .contributorAvatar { border-color: var(--brand-experiment); - transform: scale(1.05); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + transform: scale(1.1); } .contributorName { font-weight: 600; - font-size: 16px; - margin-bottom: 8px; + font-size: 13px; + margin-bottom: 4px; color: var(--header-primary); word-break: break-word; overflow-wrap: break-word; text-align: center; - transition: color 0.15s ease; - letter-spacing: -0.2px; + transition: color 0.2s ease; } .contributorCard:hover .contributorName { - color: var(--interactive-hover); + color: var(--brand-experiment); } .contributorInfo { - font-size: 13px; + font-size: 11px; color: var(--text-muted); margin: 0; text-align: center; font-weight: 400; - line-height: 1.4; + line-height: 1.3; +} + +.contributorBadge { + display: inline-block; + font-size: 12px; + font-weight: 500; + color: var(--text-muted); + background: var(--background-tertiary); + border: 1px solid var(--background-modifier-accent); + border-radius: 20px; + padding: 3px 12px; + margin-bottom: 16px; + letter-spacing: 0.2px; +} + +.techSection { + margin-top: 24px; + padding: 20px; + background: var(--background-secondary); + border: 1px solid var(--background-modifier-accent); + border-radius: 12px; +} + +.techList { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.techTag { + display: inline-block; + font-size: 12px; + font-weight: 500; + color: var(--text-normal); + background: var(--background-primary); + border: 1px solid var(--background-modifier-accent); + border-radius: 6px; + padding: 4px 10px; + transition: all 0.15s ease; +} + +.techTag:hover { + border-color: var(--brand-experiment); + color: var(--brand-experiment); + transform: translateY(-1px); +} + +.aboutFooter { + margin-top: 24px; + text-align: center; + padding: 8px 0 4px; + font-size: 12px; + color: var(--text-muted); + opacity: 0.7; + letter-spacing: 0.1px; } .loadingState, @@ -348,6 +433,25 @@ border-radius: 8px; margin: 16px; } -.modal { - max-height: 500px; + +.loadingState::after { + content: ""; + display: block; + width: 24px; + height: 24px; + margin: 16px auto 0; + border: 3px solid var(--background-modifier-accent); + border-top-color: var(--brand-experiment); + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.modal { + max-height: 640px; }