├── .eslintrc.cjs
├── .gitignore
├── .prettierrc.json
├── .vscode
└── extensions.json
├── README.md
├── env.d.ts
├── extension
├── .eslintrc.cjs
├── .prettierrc.json
├── README.md
├── env.d.ts
├── index.html
├── package-lock.json
├── package.json
├── release
│ ├── content-scripts
│ │ └── index-68d6334d.js
│ ├── icons
│ │ ├── favicon128.png
│ │ ├── favicon16.png
│ │ ├── favicon32.png
│ │ └── favicon48.png
│ └── manifest.json
├── src
│ ├── main.ts
│ ├── newExam.ts
│ ├── plugins
│ │ ├── apiAccess.ts
│ │ ├── mooc.ts
│ │ ├── react.ts
│ │ └── tool.ts
│ └── type
│ │ ├── api.ts
│ │ └── mooc.ts
├── tsconfig.config.json
├── tsconfig.json
└── vite.config.ts
├── index.html
├── package-lock.json
├── package.json
├── public
├── Arcaea_2-1.png
├── Arcaea_Story_Last2_epilogue_cg.jpg
├── Arcaea_Story_Last2_epilogue_cg_part.png
├── Last1_epilogue.png
├── Last1_epilogue_part.png
├── background.html
├── css
│ ├── fontawesome-all.min.css
│ ├── images
│ │ ├── bg.jpg
│ │ ├── ie
│ │ │ ├── footer.png
│ │ │ └── footer.svg
│ │ ├── overlay-pattern.png
│ │ └── overlay.svg
│ ├── main.css
│ └── noscript.css
├── download
│ └── GinsMoocExtension_v2.0.2.zip
├── extension-auto-evaluate-1.png
├── extension-auto-evaluate-2.png
├── extension-completion.png
├── extension-developer-mode.png
├── extension-homework.png
├── extension-load-decompression.png
├── extension-multiple-choice.png
├── extension-single-choice.png
├── extension-updating.png
├── favicon.png
├── guess.java
├── headicon.png
├── new-course-help.png
├── question.png
├── sass
│ ├── libs
│ │ ├── _breakpoints.scss
│ │ ├── _functions.scss
│ │ ├── _mixins.scss
│ │ ├── _vars.scss
│ │ └── _vendor.scss
│ ├── main.scss
│ └── noscript.scss
└── webfonts
│ ├── fa-brands-400.eot
│ ├── fa-brands-400.svg
│ ├── fa-brands-400.ttf
│ ├── fa-brands-400.woff
│ ├── fa-brands-400.woff2
│ ├── fa-regular-400.eot
│ ├── fa-regular-400.svg
│ ├── fa-regular-400.ttf
│ ├── fa-regular-400.woff
│ ├── fa-regular-400.woff2
│ ├── fa-solid-900.eot
│ ├── fa-solid-900.svg
│ ├── fa-solid-900.ttf
│ ├── fa-solid-900.woff
│ └── fa-solid-900.woff2
├── src
├── App.vue
├── components
│ ├── CourseCard.vue
│ ├── QuestionCard.vue
│ ├── icon
│ │ ├── Extension.vue
│ │ ├── Github.vue
│ │ └── index.ts
│ ├── index.ts
│ └── question
│ │ ├── Completion.vue
│ │ ├── Homework.vue
│ │ ├── MultipleChoice.vue
│ │ ├── OnlineJudge.vue
│ │ ├── SingleChoice.vue
│ │ └── index.ts
├── main.ts
├── plugins
│ ├── apiAccess.ts
│ └── tool.ts
├── router
│ └── index.ts
├── type
│ ├── api.ts
│ ├── globleProperties.ts
│ └── mooc.ts
└── views
│ ├── BlogView.vue
│ ├── HomeView.vue
│ ├── MoocAside.vue
│ ├── MoocCourseDetail.vue
│ ├── MoocHeader.vue
│ ├── MoocTest.vue
│ ├── MoocView.vue
│ ├── VideoView.vue
│ └── index.ts
├── tsconfig.config.json
├── tsconfig.json
└── vite.config.ts
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | require("@rushstack/eslint-patch/modern-module-resolution")
3 |
4 | module.exports = {
5 | root: true,
6 | extends: [
7 | "plugin:vue/vue3-essential",
8 | "eslint:recommended",
9 | "@vue/eslint-config-typescript",
10 | "@vue/eslint-config-prettier"
11 | ],
12 | parserOptions: {
13 | ecmaVersion: "latest"
14 | },
15 | rules: {
16 | "no-unused-vars": ["off", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | /cypress/videos/
18 | /cypress/screenshots/
19 |
20 | # Editor directories and files
21 | **/.vscode/*
22 | !.vscode/extensions.json
23 | .idea
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 | **/*.pem
30 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "useTabs": false,
4 | "semi": false,
5 | "trailingComma": "none",
6 | "bracketSameLine": false,
7 | "printWidth": 120
8 | }
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
3 | }
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 插件简介
2 |
3 | 实现对于中国大学MOOC的
4 | - 非在线测评题的自动答案查询,包括单选题、多选题、判断题、填空题、简答题,支持测验与作业及考试
5 | - 互评阶段的自动评分、自动点评
6 |
7 | 下载地址:[Github release v2.2.0](https://github.com/ginnnnnn666/GinsMooc/releases/tag/v2.2.0)
8 |
9 | 在线使用:[GinsMooc](https://ginnnnnn.top/mooc/)
10 |
11 | # 功能介绍
12 |
13 | 在测试的准备页面,将会自动检查是否准备就绪,若为否将自动更新课程
14 |
15 | 
16 |
17 | 进入测验后,将显示“获取答案”按钮,点击即可
18 |
19 | 
20 | 
21 | 
22 | 
23 |
24 | 作业的互评阶段支持自动评分、自动点评
25 |
26 | 
27 | 
28 |
29 |
30 | # 安装介绍
31 |
32 | 下载安装包后,将其解压至文件夹内
33 |
34 | 在浏览器地址栏中输入`edge://extensions`(谷歌浏览器为`chrome://extensions`)
35 |
36 | 打开开发者模式
37 |
38 | 
39 |
40 | 点击“加载解压缩的扩展”,选择刚刚解压到的文件夹,即可开始使用
41 |
42 | 
43 |
--------------------------------------------------------------------------------
/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/extension/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | require('@rushstack/eslint-patch/modern-module-resolution')
3 |
4 | module.exports = {
5 | root: true,
6 | 'extends': [
7 | 'plugin:vue/vue3-essential',
8 | 'eslint:recommended',
9 | '@vue/eslint-config-typescript',
10 | '@vue/eslint-config-prettier'
11 | ],
12 | parserOptions: {
13 | ecmaVersion: 'latest'
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/extension/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "useTabs": false,
4 | "semi": false,
5 | "trailingComma": "none",
6 | "bracketSameLine": false,
7 | "printWidth": 120
8 | }
--------------------------------------------------------------------------------
/extension/README.md:
--------------------------------------------------------------------------------
1 | # GinsMooc Extention
2 |
3 | This template should help get you started developing with Vue 3 in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
8 |
9 | ## Type Support for `.vue` Imports in TS
10 |
11 | TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
12 |
13 | If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
14 |
15 | 1. Disable the built-in TypeScript Extension
16 | 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
17 | 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
18 | 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
19 |
20 | ## Customize configuration
21 |
22 | See [Vite Configuration Reference](https://vitejs.dev/config/).
23 |
24 | ## Project Setup
25 |
26 | ```sh
27 | npm install
28 | ```
29 |
30 | ### Compile and Hot-Reload for Development
31 |
32 | ```sh
33 | npm run dev
34 | ```
35 |
36 | ### Type-Check, Compile and Minify for Production
37 |
38 | ```sh
39 | npm run build
40 | ```
41 |
42 | ### Lint with [ESLint](https://eslint.org/)
43 |
44 | ```sh
45 | npm run lint
46 | ```
47 |
--------------------------------------------------------------------------------
/extension/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/extension/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/extension/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ginsmooc-extention",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "run-p type-check build-only",
8 | "preview": "vite preview",
9 | "build-only": "vite build",
10 | "type-check": "vue-tsc --noEmit",
11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
12 | },
13 | "dependencies": {
14 | "axios": "^1.3.2",
15 | "element-plus": "^2.2.29",
16 | "jssha": "^3.3.0",
17 | "vue": "^3.2.45",
18 | "vue-router": "^4.1.6"
19 | },
20 | "devDependencies": {
21 | "@rushstack/eslint-patch": "^1.1.4",
22 | "@types/chrome": "^0.0.212",
23 | "@types/node": "^18.11.12",
24 | "@vitejs/plugin-vue": "^4.0.0",
25 | "@vue/eslint-config-prettier": "^7.0.0",
26 | "@vue/eslint-config-typescript": "^11.0.0",
27 | "@vue/tsconfig": "^0.1.3",
28 | "eslint": "^8.22.0",
29 | "eslint-plugin-vue": "^9.3.0",
30 | "npm-run-all": "^4.1.5",
31 | "prettier": "^2.7.1",
32 | "typescript": "~4.7.4",
33 | "vite": "^4.0.0",
34 | "vue-tsc": "^1.0.12"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/extension/release/content-scripts/index-68d6334d.js:
--------------------------------------------------------------------------------
1 | var lt=Object.defineProperty;var ut=(e,t,n)=>t in e?lt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var k=(e,t,n)=>(ut(e,typeof t!="symbol"?t+"":t,n),n);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&r(i)}).observe(document,{childList:!0,subtree:!0});function n(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function r(s){if(s.ep)return;s.ep=!0;const o=n(s);fetch(s.href,o)}})();const Fe=async e=>new Promise(t=>{setTimeout(()=>{t("")},e)}),Ee=async e=>new Promise(t=>{const n=setInterval(()=>{console.log("check wait for"),e()&&(clearInterval(n),t(""))},50)}),W=e=>{const t=new RegExp("(^|&)"+e+"=([^&]*)(&|$)"),n=window.location.search.substring(1).match(t)||window.location.hash.substring(window.location.hash.search(/\?/)+1).match(t);return n?decodeURIComponent(n[2]):null},_e=e=>{const t="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";let n="";for(let r=0;rt=>{const n=pt.call(t);return e[n]||(e[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),T=e=>(e=e.toLowerCase(),t=>ee(t)===e),te=e=>t=>typeof t===e,{isArray:q}=Array,$=te("undefined");function ht(e){return e!==null&&!$(e)&&e.constructor!==null&&!$(e.constructor)&&S(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}const De=T("ArrayBuffer");function mt(e){let t;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?t=ArrayBuffer.isView(e):t=e&&e.buffer&&De(e.buffer),t}const yt=te("string"),S=te("function"),Ue=te("number"),ne=e=>e!==null&&typeof e=="object",wt=e=>e===!0||e===!1,V=e=>{if(ee(e)!=="object")return!1;const t=he(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)},gt=T("Date"),Et=T("File"),bt=T("Blob"),St=T("FileList"),xt=e=>ne(e)&&S(e.pipe),Ot=e=>{let t;return e&&(typeof FormData=="function"&&e instanceof FormData||S(e.append)&&((t=ee(e))==="formdata"||t==="object"&&S(e.toString)&&e.toString()==="[object FormData]"))},Tt=T("URLSearchParams"),At=e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function z(e,t,{allOwnKeys:n=!1}={}){if(e===null||typeof e>"u")return;let r,s;if(typeof e!="object"&&(e=[e]),q(e))for(r=0,s=e.length;r0;)if(s=n[r],t===s.toLowerCase())return s;return null}const Ie=(()=>typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global)(),ve=e=>!$(e)&&e!==Ie;function le(){const{caseless:e}=ve(this)&&this||{},t={},n=(r,s)=>{const o=e&&qe(t,s)||s;V(t[o])&&V(r)?t[o]=le(t[o],r):V(r)?t[o]=le({},r):q(r)?t[o]=r.slice():t[o]=r};for(let r=0,s=arguments.length;r(z(t,(s,o)=>{n&&S(s)?e[o]=je(s,n):e[o]=s},{allOwnKeys:r}),e),Nt=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),Ct=(e,t,n,r)=>{e.prototype=Object.create(t.prototype,r),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),n&&Object.assign(e.prototype,n)},Lt=(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&&he(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},Pt=(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},kt=e=>{if(!e)return null;if(q(e))return e;let t=e.length;if(!Ue(t))return null;const n=new Array(t);for(;t-- >0;)n[t]=e[t];return n},Bt=(e=>t=>e&&t instanceof e)(typeof Uint8Array<"u"&&he(Uint8Array)),Ft=(e,t)=>{const r=(e&&e[Symbol.iterator]).call(e);let s;for(;(s=r.next())&&!s.done;){const o=s.value;t.call(e,o[0],o[1])}},_t=(e,t)=>{let n;const r=[];for(;(n=e.exec(t))!==null;)r.push(n);return r},jt=T("HTMLFormElement"),Dt=e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(n,r,s){return r.toUpperCase()+s}),be=(({hasOwnProperty:e})=>(t,n)=>e.call(t,n))(Object.prototype),Ut=T("RegExp"),He=(e,t)=>{const n=Object.getOwnPropertyDescriptors(e),r={};z(n,(s,o)=>{let i;(i=t(s,o,e))!==!1&&(r[o]=i||s)}),Object.defineProperties(e,r)},qt=e=>{He(e,(t,n)=>{if(S(e)&&["arguments","caller","callee"].indexOf(n)!==-1)return!1;const r=e[n];if(S(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+"'")})}})},It=(e,t)=>{const n={},r=s=>{s.forEach(o=>{n[o]=!0})};return q(e)?r(e):r(String(e).split(t)),n},vt=()=>{},Ht=(e,t)=>(e=+e,Number.isFinite(e)?e:t),oe="abcdefghijklmnopqrstuvwxyz",Se="0123456789",Me={DIGIT:Se,ALPHA:oe,ALPHA_DIGIT:oe+oe.toUpperCase()+Se},Mt=(e=16,t=Me.ALPHA_DIGIT)=>{let n="";const{length:r}=t;for(;e--;)n+=t[Math.random()*r|0];return n};function $t(e){return!!(e&&S(e.append)&&e[Symbol.toStringTag]==="FormData"&&e[Symbol.iterator])}const zt=e=>{const t=new Array(10),n=(r,s)=>{if(ne(r)){if(t.indexOf(r)>=0)return;if(!("toJSON"in r)){t[s]=r;const o=q(r)?[]:{};return z(r,(i,c)=>{const d=n(i,s+1);!$(d)&&(o[c]=d)}),t[s]=void 0,o}}return r};return n(e,0)},Jt=T("AsyncFunction"),Vt=e=>e&&(ne(e)||S(e))&&S(e.then)&&S(e.catch),a={isArray:q,isArrayBuffer:De,isBuffer:ht,isFormData:Ot,isArrayBufferView:mt,isString:yt,isNumber:Ue,isBoolean:wt,isObject:ne,isPlainObject:V,isUndefined:$,isDate:gt,isFile:Et,isBlob:bt,isRegExp:Ut,isFunction:S,isStream:xt,isURLSearchParams:Tt,isTypedArray:Bt,isFileList:St,forEach:z,merge:le,extend:Rt,trim:At,stripBOM:Nt,inherits:Ct,toFlatObject:Lt,kindOf:ee,kindOfTest:T,endsWith:Pt,toArray:kt,forEachEntry:Ft,matchAll:_t,isHTMLForm:jt,hasOwnProperty:be,hasOwnProp:be,reduceDescriptors:He,freezeMethods:qt,toObjectSet:It,toCamelCase:Dt,noop:vt,toFiniteNumber:Ht,findKey:qe,global:Ie,isContextDefined:ve,ALPHABET:Me,generateString:Mt,isSpecCompliantForm:$t,toJSONObject:zt,isAsyncFn:Jt,isThenable:Vt};function m(e,t,n,r,s){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack,this.message=e,this.name="AxiosError",t&&(this.code=t),n&&(this.config=n),r&&(this.request=r),s&&(this.response=s)}a.inherits(m,Error,{toJSON:function(){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:a.toJSONObject(this.config),code:this.code,status:this.response&&this.response.status?this.response.status:null}}});const $e=m.prototype,ze={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach(e=>{ze[e]={value:e}});Object.defineProperties(m,ze);Object.defineProperty($e,"isAxiosError",{value:!0});m.from=(e,t,n,r,s,o)=>{const i=Object.create($e);return a.toFlatObject(e,i,function(d){return d!==Error.prototype},c=>c!=="isAxiosError"),m.call(i,e.message,t,n,r,s),i.cause=e,i.name=e.name,o&&Object.assign(i,o),i};const Gt=null;function ue(e){return a.isPlainObject(e)||a.isArray(e)}function Je(e){return a.endsWith(e,"[]")?e.slice(0,-2):e}function xe(e,t,n){return e?e.concat(t).map(function(s,o){return s=Je(s),!n&&o?"["+s+"]":s}).join(n?".":""):t}function Kt(e){return a.isArray(e)&&!e.some(ue)}const Wt=a.toFlatObject(a,{},null,function(t){return/^is[A-Z]/.test(t)});function re(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(h,g){return!a.isUndefined(g[h])});const r=n.metaTokens,s=n.visitor||u,o=n.dots,i=n.indexes,d=(n.Blob||typeof Blob<"u"&&Blob)&&a.isSpecCompliantForm(t);if(!a.isFunction(s))throw new TypeError("visitor must be a function");function f(p){if(p===null)return"";if(a.isDate(p))return p.toISOString();if(!d&&a.isBlob(p))throw new m("Blob is not supported. Use a Buffer instead.");return a.isArrayBuffer(p)||a.isTypedArray(p)?d&&typeof Blob=="function"?new Blob([p]):Buffer.from(p):p}function u(p,h,g){let E=p;if(p&&!g&&typeof p=="object"){if(a.endsWith(h,"{}"))h=r?h:h.slice(0,-2),p=JSON.stringify(p);else if(a.isArray(p)&&Kt(p)||(a.isFileList(p)||a.endsWith(h,"[]"))&&(E=a.toArray(p)))return h=Je(h),E.forEach(function(L,ct){!(a.isUndefined(L)||L===null)&&t.append(i===!0?xe([h],ct,o):i===null?h:h+"[]",f(L))}),!1}return ue(p)?!0:(t.append(xe(g,h,o),f(p)),!1)}const l=[],w=Object.assign(Wt,{defaultVisitor:u,convertValue:f,isVisitable:ue});function b(p,h){if(!a.isUndefined(p)){if(l.indexOf(p)!==-1)throw Error("Circular reference detected in "+h.join("."));l.push(p),a.forEach(p,function(E,C){(!(a.isUndefined(E)||E===null)&&s.call(t,E,a.isString(C)?C.trim():C,h,w))===!0&&b(E,h?h.concat(C):[C])}),l.pop()}}if(!a.isObject(e))throw new TypeError("data must be an object");return b(e),t}function Oe(e){const t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(e).replace(/[!'()~]|%20|%00/g,function(r){return t[r]})}function me(e,t){this._pairs=[],e&&re(e,this,t)}const Ve=me.prototype;Ve.append=function(t,n){this._pairs.push([t,n])};Ve.toString=function(t){const n=t?function(r){return t.call(this,r,Oe)}:Oe;return this._pairs.map(function(s){return n(s[0])+"="+n(s[1])},"").join("&")};function Qt(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function Ge(e,t,n){if(!t)return e;const r=n&&n.encode||Qt,s=n&&n.serialize;let o;if(s?o=s(t,n):o=a.isURLSearchParams(t)?t.toString():new me(t,n).toString(r),o){const i=e.indexOf("#");i!==-1&&(e=e.slice(0,i)),e+=(e.indexOf("?")===-1?"?":"&")+o}return e}class Xt{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 Te=Xt,Ke={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},Yt=typeof URLSearchParams<"u"?URLSearchParams:me,Zt=typeof FormData<"u"?FormData:null,en=typeof Blob<"u"?Blob:null,tn={isBrowser:!0,classes:{URLSearchParams:Yt,FormData:Zt,Blob:en},protocols:["http","https","file","blob","url","data"]},We=typeof window<"u"&&typeof document<"u",nn=(e=>We&&["ReactNative","NativeScript","NS"].indexOf(e)<0)(typeof navigator<"u"&&navigator.product),rn=(()=>typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function")(),sn=Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:We,hasStandardBrowserEnv:nn,hasStandardBrowserWebWorkerEnv:rn},Symbol.toStringTag,{value:"Module"})),x={...sn,...tn};function on(e,t){return re(e,new x.classes.URLSearchParams,Object.assign({visitor:function(n,r,s,o){return x.isNode&&a.isBuffer(n)?(this.append(r,n.toString("base64")),!1):o.defaultVisitor.apply(this,arguments)}},t))}function an(e){return a.matchAll(/\w+|\[(\w*)]/g,e).map(t=>t[0]==="[]"?"":t[1]||t[0])}function cn(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,d?(a.hasOwnProp(s,i)?s[i]=[s[i],r]:s[i]=r,!c):((!s[i]||!a.isObject(s[i]))&&(s[i]=[]),t(n,r,s[i],o)&&a.isArray(s[i])&&(s[i]=cn(s[i])),!c)}if(a.isFormData(e)&&a.isFunction(e.entries)){const n={};return a.forEachEntry(e,(r,s)=>{t(an(r),s,n,0)}),n}return null}function ln(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 ye={transitional:Ke,adapter:["xhr","http"],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(Qe(t)):t;if(a.isArrayBuffer(t)||a.isBuffer(t)||a.isStream(t)||a.isFile(t)||a.isBlob(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){if(r.indexOf("application/x-www-form-urlencoded")>-1)return on(t,this.formSerializer).toString();if((c=a.isFileList(t))||r.indexOf("multipart/form-data")>-1){const d=this.env&&this.env.FormData;return re(c?{"files[]":t}:t,d&&new d,this.formSerializer)}}return o||s?(n.setContentType("application/json",!1),ln(t)):t}],transformResponse:[function(t){const n=this.transitional||ye.transitional,r=n&&n.forcedJSONParsing,s=this.responseType==="json";if(t&&a.isString(t)&&(r&&!this.responseType||s)){const i=!(n&&n.silentJSONParsing)&&s;try{return JSON.parse(t)}catch(c){if(i)throw c.name==="SyntaxError"?m.from(c,m.ERR_BAD_RESPONSE,this,null,this.response):c}}return t}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:x.classes.FormData,Blob:x.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"],e=>{ye.headers[e]={}});const we=ye,un=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"]),dn=e=>{const t={};let n,r,s;return e&&e.split(`
2 | `).forEach(function(i){s=i.indexOf(":"),n=i.substring(0,s).trim().toLowerCase(),r=i.substring(s+1).trim(),!(!n||t[n]&&un[n])&&(n==="set-cookie"?t[n]?t[n].push(r):t[n]=[r]:t[n]=t[n]?t[n]+", "+r:r)}),t},Ae=Symbol("internals");function H(e){return e&&String(e).trim().toLowerCase()}function G(e){return e===!1||e==null?e:a.isArray(e)?e.map(G):String(e)}function fn(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 pn=e=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim());function ie(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 hn(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(t,n,r)=>n.toUpperCase()+r)}function mn(e,t){const n=a.toCamelCase(" "+t);["get","set","has"].forEach(r=>{Object.defineProperty(e,r+n,{value:function(s,o,i){return this[r].call(this,t,s,o,i)},configurable:!0})})}let se=class{constructor(t){t&&this.set(t)}set(t,n,r){const s=this;function o(c,d,f){const u=H(d);if(!u)throw new Error("header name must be a non-empty string");const l=a.findKey(s,u);(!l||s[l]===void 0||f===!0||f===void 0&&s[l]!==!1)&&(s[l||d]=G(c))}const i=(c,d)=>a.forEach(c,(f,u)=>o(f,u,d));return a.isPlainObject(t)||t instanceof this.constructor?i(t,n):a.isString(t)&&(t=t.trim())&&!pn(t)?i(dn(t),n):t!=null&&o(n,t,r),this}get(t,n){if(t=H(t),t){const r=a.findKey(this,t);if(r){const s=this[r];if(!n)return s;if(n===!0)return fn(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=H(t),t){const r=a.findKey(this,t);return!!(r&&this[r]!==void 0&&(!n||ie(this,this[r],r,n)))}return!1}delete(t,n){const r=this;let s=!1;function o(i){if(i=H(i),i){const c=a.findKey(r,i);c&&(!n||ie(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||ie(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]=G(s),delete n[o];return}const c=t?hn(o):String(o).trim();c!==o&&delete n[o],n[c]=G(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(`
3 | `)}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[Ae]=this[Ae]={accessors:{}}).accessors,s=this.prototype;function o(i){const c=H(i);r[c]||(mn(s,i),r[c]=!0)}return a.isArray(t)?t.forEach(o):o(t),this}};se.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);a.reduceDescriptors(se.prototype,({value:e},t)=>{let n=t[0].toUpperCase()+t.slice(1);return{get:()=>e,set(r){this[n]=r}}});a.freezeMethods(se);const R=se;function ae(e,t){const n=this||we,r=t||n,s=R.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 Xe(e){return!!(e&&e.__CANCEL__)}function J(e,t,n){m.call(this,e??"canceled",m.ERR_CANCELED,t,n),this.name="CanceledError"}a.inherits(J,m,{__CANCEL__:!0});function yn(e,t,n){const r=n.config.validateStatus;!n.status||!r||r(n.status)?e(n):t(new m("Request failed with status code "+n.status,[m.ERR_BAD_REQUEST,m.ERR_BAD_RESPONSE][Math.floor(n.status/100)-4],n.config,n.request,n))}const wn=x.hasStandardBrowserEnv?{write(e,t,n,r,s,o){const i=[e+"="+encodeURIComponent(t)];a.isNumber(n)&&i.push("expires="+new Date(n).toGMTString()),a.isString(r)&&i.push("path="+r),a.isString(s)&&i.push("domain="+s),o===!0&&i.push("secure"),document.cookie=i.join("; ")},read(e){const t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove(e){this.write(e,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function gn(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}function En(e,t){return t?e.replace(/\/?\/$/,"")+"/"+t.replace(/^\/+/,""):e}function Ye(e,t){return e&&!gn(t)?En(e,t):t}const bn=x.hasStandardBrowserEnv?function(){const t=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");let r;function s(o){let i=o;return t&&(n.setAttribute("href",i),i=n.href),n.setAttribute("href",i),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:n.pathname.charAt(0)==="/"?n.pathname:"/"+n.pathname}}return r=s(window.location.href),function(i){const c=a.isString(i)?s(i):i;return c.protocol===r.protocol&&c.host===r.host}}():function(){return function(){return!0}}();function Sn(e){const t=/^([-+\w]{1,25})(:?\/\/|:)/.exec(e);return t&&t[1]||""}function xn(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(d){const f=Date.now(),u=r[o];i||(i=f),n[s]=d,r[s]=f;let l=o,w=0;for(;l!==s;)w+=n[l++],l=l%e;if(s=(s+1)%e,s===o&&(o=(o+1)%e),f-i{const o=s.loaded,i=s.lengthComputable?s.total:void 0,c=o-n,d=r(c),f=o<=i;n=o;const u={loaded:o,total:i,progress:i?o/i:void 0,bytes:c,rate:d||void 0,estimated:d&&i&&f?(i-o)/d:void 0,event:s};u[t?"download":"upload"]=!0,e(u)}}const On=typeof XMLHttpRequest<"u",Tn=On&&function(e){return new Promise(function(n,r){let s=e.data;const o=R.from(e.headers).normalize();let{responseType:i,withXSRFToken:c}=e,d;function f(){e.cancelToken&&e.cancelToken.unsubscribe(d),e.signal&&e.signal.removeEventListener("abort",d)}let u;if(a.isFormData(s)){if(x.hasStandardBrowserEnv||x.hasStandardBrowserWebWorkerEnv)o.setContentType(!1);else if((u=o.getContentType())!==!1){const[h,...g]=u?u.split(";").map(E=>E.trim()).filter(Boolean):[];o.setContentType([h||"multipart/form-data",...g].join("; "))}}let l=new XMLHttpRequest;if(e.auth){const h=e.auth.username||"",g=e.auth.password?unescape(encodeURIComponent(e.auth.password)):"";o.set("Authorization","Basic "+btoa(h+":"+g))}const w=Ye(e.baseURL,e.url);l.open(e.method.toUpperCase(),Ge(w,e.params,e.paramsSerializer),!0),l.timeout=e.timeout;function b(){if(!l)return;const h=R.from("getAllResponseHeaders"in l&&l.getAllResponseHeaders()),E={data:!i||i==="text"||i==="json"?l.responseText:l.response,status:l.status,statusText:l.statusText,headers:h,config:e,request:l};yn(function(L){n(L),f()},function(L){r(L),f()},E),l=null}if("onloadend"in l?l.onloadend=b:l.onreadystatechange=function(){!l||l.readyState!==4||l.status===0&&!(l.responseURL&&l.responseURL.indexOf("file:")===0)||setTimeout(b)},l.onabort=function(){l&&(r(new m("Request aborted",m.ECONNABORTED,e,l)),l=null)},l.onerror=function(){r(new m("Network Error",m.ERR_NETWORK,e,l)),l=null},l.ontimeout=function(){let g=e.timeout?"timeout of "+e.timeout+"ms exceeded":"timeout exceeded";const E=e.transitional||Ke;e.timeoutErrorMessage&&(g=e.timeoutErrorMessage),r(new m(g,E.clarifyTimeoutError?m.ETIMEDOUT:m.ECONNABORTED,e,l)),l=null},x.hasStandardBrowserEnv&&(c&&a.isFunction(c)&&(c=c(e)),c||c!==!1&&bn(w))){const h=e.xsrfHeaderName&&e.xsrfCookieName&&wn.read(e.xsrfCookieName);h&&o.set(e.xsrfHeaderName,h)}s===void 0&&o.setContentType(null),"setRequestHeader"in l&&a.forEach(o.toJSON(),function(g,E){l.setRequestHeader(E,g)}),a.isUndefined(e.withCredentials)||(l.withCredentials=!!e.withCredentials),i&&i!=="json"&&(l.responseType=e.responseType),typeof e.onDownloadProgress=="function"&&l.addEventListener("progress",Re(e.onDownloadProgress,!0)),typeof e.onUploadProgress=="function"&&l.upload&&l.upload.addEventListener("progress",Re(e.onUploadProgress)),(e.cancelToken||e.signal)&&(d=h=>{l&&(r(!h||h.type?new J(null,e,l):h),l.abort(),l=null)},e.cancelToken&&e.cancelToken.subscribe(d),e.signal&&(e.signal.aborted?d():e.signal.addEventListener("abort",d)));const p=Sn(w);if(p&&x.protocols.indexOf(p)===-1){r(new m("Unsupported protocol "+p+":",m.ERR_BAD_REQUEST,e));return}l.send(s||null)})},de={http:Gt,xhr:Tn};a.forEach(de,(e,t)=>{if(e){try{Object.defineProperty(e,"name",{value:t})}catch{}Object.defineProperty(e,"adapterName",{value:t})}});const Ne=e=>`- ${e}`,An=e=>a.isFunction(e)||e===null||e===!1,Ze={getAdapter:e=>{e=a.isArray(e)?e:[e];const{length:t}=e;let n,r;const s={};for(let o=0;o`adapter ${c} `+(d===!1?"is not supported by the environment":"is not available in the build"));let i=t?o.length>1?`since :
4 | `+o.map(Ne).join(`
5 | `):" "+Ne(o[0]):"as no adapter specified";throw new m("There is no suitable adapter to dispatch the request "+i,"ERR_NOT_SUPPORT")}return r},adapters:de};function ce(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new J(null,e)}function Ce(e){return ce(e),e.headers=R.from(e.headers),e.data=ae.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),Ze.getAdapter(e.adapter||we.adapter)(e).then(function(r){return ce(e),r.data=ae.call(e,e.transformResponse,r),r.headers=R.from(r.headers),r},function(r){return Xe(r)||(ce(e),r&&r.response&&(r.response.data=ae.call(e,e.transformResponse,r.response),r.response.headers=R.from(r.response.headers))),Promise.reject(r)})}const Le=e=>e instanceof R?e.toJSON():e;function _(e,t){t=t||{};const n={};function r(f,u,l){return a.isPlainObject(f)&&a.isPlainObject(u)?a.merge.call({caseless:l},f,u):a.isPlainObject(u)?a.merge({},u):a.isArray(u)?u.slice():u}function s(f,u,l){if(a.isUndefined(u)){if(!a.isUndefined(f))return r(void 0,f,l)}else return r(f,u,l)}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,l){if(l in t)return r(f,u);if(l in e)return r(void 0,f)}const d={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,responseEncoding:i,validateStatus:c,headers:(f,u)=>s(Le(f),Le(u),!0)};return a.forEach(Object.keys(Object.assign({},e,t)),function(u){const l=d[u]||s,w=l(e[u],t[u],u);a.isUndefined(w)&&l!==c||(n[u]=w)}),n}const et="1.6.7",ge={};["object","boolean","number","function","string","symbol"].forEach((e,t)=>{ge[e]=function(r){return typeof r===e||"a"+(t<1?"n ":" ")+e}});const Pe={};ge.transitional=function(t,n,r){function s(o,i){return"[Axios v"+et+"] Transitional option '"+o+"'"+i+(r?". "+r:"")}return(o,i,c)=>{if(t===!1)throw new m(s(i," has been removed"+(n?" in "+n:"")),m.ERR_DEPRECATED);return n&&!Pe[i]&&(Pe[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}};function Rn(e,t,n){if(typeof e!="object")throw new m("options must be an object",m.ERR_BAD_OPTION_VALUE);const r=Object.keys(e);let s=r.length;for(;s-- >0;){const o=r[s],i=t[o];if(i){const c=e[o],d=c===void 0||i(c,o,e);if(d!==!0)throw new m("option "+o+" must be "+d,m.ERR_BAD_OPTION_VALUE);continue}if(n!==!0)throw new m("Unknown option "+o,m.ERR_BAD_OPTION)}}const fe={assertOptions:Rn,validators:ge},P=fe.validators;let Q=class{constructor(t){this.defaults=t,this.interceptors={request:new Te,response:new Te}}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=s.stack?s.stack.replace(/^.+\n/,""):"";r.stack?o&&!String(r.stack).endsWith(o.replace(/^.+\n.+\n/,""))&&(r.stack+=`
6 | `+o):r.stack=o}throw r}}_request(t,n){typeof t=="string"?(n=n||{},n.url=t):n=t||{},n=_(this.defaults,n);const{transitional:r,paramsSerializer:s,headers:o}=n;r!==void 0&&fe.assertOptions(r,{silentJSONParsing:P.transitional(P.boolean),forcedJSONParsing:P.transitional(P.boolean),clarifyTimeoutError:P.transitional(P.boolean)},!1),s!=null&&(a.isFunction(s)?n.paramsSerializer={serialize:s}:fe.assertOptions(s,{encode:P.function,serialize:P.function},!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","common"],p=>{delete o[p]}),n.headers=R.concat(i,o);const c=[];let d=!0;this.interceptors.request.forEach(function(h){typeof h.runWhen=="function"&&h.runWhen(n)===!1||(d=d&&h.synchronous,c.unshift(h.fulfilled,h.rejected))});const f=[];this.interceptors.response.forEach(function(h){f.push(h.fulfilled,h.rejected)});let u,l=0,w;if(!d){const p=[Ce.bind(this),void 0];for(p.unshift.apply(p,c),p.push.apply(p,f),w=p.length,u=Promise.resolve(n);l{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 J(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)}static source(){let t;return{token:new tt(function(s){t=s}),cancel:t}}};const Cn=Nn;function Ln(e){return function(n){return e.apply(null,n)}}function Pn(e){return a.isObject(e)&&e.isAxiosError===!0}const pe={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};Object.entries(pe).forEach(([e,t])=>{pe[t]=e});const kn=pe;function nt(e){const t=new K(e),n=je(K.prototype.request,t);return a.extend(n,K.prototype,t,{allOwnKeys:!0}),a.extend(n,t,null,{allOwnKeys:!0}),n.create=function(s){return nt(_(e,s))},n}const y=nt(we);y.Axios=K;y.CanceledError=J;y.CancelToken=Cn;y.isCancel=Xe;y.VERSION=et;y.toFormData=re;y.AxiosError=m;y.Cancel=y.CanceledError;y.all=function(t){return Promise.all(t)};y.spread=Ln;y.isAxiosError=Pn;y.mergeConfig=_;y.AxiosHeaders=R;y.formToJSON=e=>Qe(a.isHTMLForm(e)?new FormData(e):e);y.getAdapter=Ze.getAdapter;y.HttpStatusCode=kn;y.default=y;const rt=y,{Axios:Vn,AxiosError:Gn,CanceledError:Kn,isCancel:Wn,CancelToken:Qn,VERSION:Xn,all:Yn,Cancel:Zn,isAxiosError:Bn,spread:er,toFormData:tr,AxiosHeaders:nr,HttpStatusCode:rr,formToJSON:sr,getAdapter:or,mergeConfig:ir}=rt,ke={checkTestExist:{url:"/mooc/test/:tid",method:"GET"},selectQustion:{url:"/mooc/test/:tid",method:"POST"},getAnnouncement:{url:"/mooc/announcement",method:"GET"},getNewExamInfo:{url:"https://www.icourse163.org/mm-tiku/web/j/mocExamBean.getPaper.rpc",method:"POST"},getNotice:{url:"/mooc/notice/extension",method:"GET"}},Fn="https://ginnnnnn.top/api";async function _n(e,t,n){try{return await new Promise((r,s)=>{let o=ke[e].url;if(t)for(const[i,c]of Object.entries(t)){const d=RegExp(`(/):${i}(/)?`,"g");d.test(o)&&(o=o.replaceAll(d,`$1${c}$2`),Reflect.deleteProperty(t,i))}if(o.indexOf("http")!=0&&(o=`${Fn}${o}`),n)for(const[i,c]of Object.entries(n))typeof c=="object"&&Reflect.set(n,i,JSON.stringify(c));rt({url:o,method:ke[e].method,params:t||{},data:n||{},headers:{"Content-Type":"application/x-www-form-urlencoded"}}).then(i=>{let c="",d=!1;i.status!==200||!i.data?c="请求出错":i.data.msg&&(c=i.data.msg,i.data.status===200&&(d=!0)),c&&console.log(c),r(i.data)}).catch(i=>{let c=i;Bn(i)&&(c=i.message),console.log(c),s(i)})})}catch{return{}}}const st=()=>_n,jn=()=>{const[e,t]=[new Array,new Array],n=document.querySelectorAll('input[id^="op_"]');for(let s=0;s{for(const r of e)document.querySelector(`input[id*="_${r}"]`).classList.add("gin-answer");const n=document.getElementsByClassName("m-FillBlank examMode u-questionItem");for(let r=0;r{var n,r;const t=document.getElementsByClassName("f-richEditorText j-richTxt f-fl");for(let s=0;s{var t,n,r,s;const e=document.getElementsByClassName("u-questionItem u-analysisQuestion analysisMode");for(let o=0;o{const n=document.querySelector(".u-homework-evaAction .bottombtnwrap .j-submitbtn"),r=document.querySelector(".u-homework-evaAction .xlinfo"),s=document.querySelector(".u-homework-evaAction .xlinfo .j-gotonext");for(let o=0;or.style.display==="none"),ot(),await Fe(1e3),console.log(new Date),n.click(),await Ee(()=>r.style.display!=="none"),t(o+1,e),s.click()},In=async()=>{var s,o;const e=st();let t=(s=document.getElementById("app"))==null?void 0:s.getElementsByTagName("form").item(0);for(;!t;)await Fe(1e3),t=(o=document.getElementById("app"))==null?void 0:o.getElementsByTagName("form").item(0);const n=async()=>{const i=await e("getNewExamInfo",{csrfKey:document.cookie.match(/NTESSTUDYSI=([a-z0-9]+);/)[1]},{answerformId:W("aid"),examId:W("eid")});let c=[];for(let u of i.result.questions)for(let l of u.optionDtos)c.push(l.id);const d=await e("selectQustion",{tid:i.result.tid},{oidList:c}),f=document.querySelectorAll(".ant-checkbox-group>div, .ant-radio-group>div");for(let u of d.data.choiceAns)f[c.indexOf(u)].classList.add("gin-answer-item")},r=document.createElement("button");r.className="ant-btn ant-btn-primary",r.setAttribute("style","margin-bottom: 16px"),r.onclick=n,r.innerText="获取答案",t==null||t.before(r)},X=st(),M=new dt,N=M.add(void 0),A=M.add(-1);let B=null;const[Y,F,Z]=[M.add(!1),M.add(!1),M.add(!1)];location.href.indexOf("newExam")!==-1&&In();const vn=async()=>{if(O.innerText!=="正在获取答案,请稍后..."){if(O.innerText="正在获取答案,请稍后...",N.get()==="quiz"){const e=await X("selectQustion",{tid:B},jn());console.log(e),Dn(e.data.choiceAns,e.data.completionAns)}else if(N.get()==="homework"){const e=await X("selectQustion",{tid:B},{});Un(e.data.homeworkAns)}O.innerText=""}},it=document.createElement("style");it.innerText=`
7 | input.gin-answer:not(:checked) + label, #GinsMooc, .gin-answer-item {
8 | background-color: #d9ecff;
9 | }
10 | .learnPageContentLeft {
11 | background: rgb(240, 242, 245);
12 | }
13 | #GinsMooc {
14 | margin-bottom: 12px !important;
15 | }
16 | .gin-function {
17 | display: flex;
18 | align-items: center;
19 | }
20 | .gin-function .u-btn {
21 | margin-right: 16px;
22 | }
23 | .gin-state-tips {
24 | font-size: 14px;
25 | }
26 | `;document.head.append(it);const I=document.createElement("div");I.id="GinsMooc";I.classList.add("m-learnbox");var Be;(Be=document.querySelector(".learnPageContentLeft"))==null||Be.prepend(I);if(location.href.indexOf("/spoc")!==-1){const e=document.createElement("div");e.innerHTML="当前课程为 SPOC 课程,可能无法获取答案。SPOC 课程可能会有关联的对外课程,你可以尝试搜索并加入,题目大概率是一样的",I.prepend()}const Hn=async()=>{var t;const e=(await X("getNotice",{version:"v2.2.0"},void 0)).data;if(console.log(e),!((t=localStorage.getItem("Gins-ignore-notice"))!=null&&t.split(",").find(n=>Number.parseInt(n)===e.id))){const n=document.createElement("div");n.innerHTML=e.content;const r=document.createElement("a");r.innerText="不再提醒",r.onclick=()=>{const s=localStorage.getItem("Gins-ignore-notice");localStorage.setItem("Gins-ignore-notice",s?`${s},${e.id}`:`${e.id}`),n.remove(),r.remove()},r.style.marginLeft="16px",n.append(r),I.prepend(n)}};Hn();const v=document.createElement("div");v.classList.add("gin-function");I.append(v);const j=document.createElement("button");j.classList.add("u-btn","u-btn-default","f-dn");j.onclick=vn;j.innerText="获取答案";v.append(j);const D=document.createElement("button");D.classList.add("u-btn","u-btn-default","f-dn");D.onclick=()=>{ot(),window.scroll({top:document.documentElement.scrollHeight,behavior:"smooth"})};D.innerText="一键互评";v.append(D);const U=document.createElement("button");U.classList.add("u-btn","u-btn-default","f-dn");U.onclick=()=>{qn(5,(e,t)=>{e>=t?O.innerText=`已完成 ${t} 次互评`:O.innerText=`自动互评中(${e} / ${t})`})};U.innerText="自动互评";v.append(U);const O=document.createElement("div");O.classList.add("gin-state-tips");v.append(O);window.addEventListener("hashchange",()=>{B=W("id"),location.hash.indexOf("quiz")!==-1||location.hash.indexOf("examObject")!==-1?N.set("quiz"):location.hash.indexOf("hw")!==-1||location.hash.indexOf("examSubjective")!==-1?N.set("homework"):N.set(void 0)});window.setInterval(()=>{Z.set(location.hash.indexOf("examlist")!==-1);const e=document.querySelector(".j-prepare.prepare");F.set(e&&!e.classList.contains("f-dn")||document.querySelector(".j-homework-paper")!==null||Z.get()),Y.set(document.querySelector(".u-questionItem.u-analysisQuestion.analysisMode")!==null)},100);const at=async()=>{if(console.log("onTestChange",N.get(),F.get(),Z.get(),B),N.get()==="quiz"&&!F.get()||N.get()==="homework"?j.classList.remove("f-dn"):j.classList.add("f-dn"),A.set(-2),F.get()&&B)if((await X("checkTestExist",{tid:B,type:"isExisting"},void 0)).data.existing){A.set(-1);return}else{const t=new EventSource(`https://ginnnnnn.top/api/mooc/course/refresh/${W("tid")}`);t.onmessage=n=>{console.log(n.data);const r=JSON.parse(n.data);r&&r.total>0&&A.set(Math.round(r.finished/r.total*100)),(A.value===100||r.status===400)&&(t.close(),r.msg&&A.set(-1))}}else if(!Z.get()){A.set(-1);return}},Mn=()=>{console.log("onModeChange",Y.get()),Y.get()?(D.classList.remove("f-dn"),U.classList.remove("f-dn")):(D.classList.add("f-dn"),U.classList.add("f-dn"))};Y.addEventListenr("change",Mn);F.addEventListenr("change",at);N.addEventListenr("change",at);A.addEventListenr("set",()=>{switch(A.get()){case-2:O.innerText="正在检查课程...";break;case-1:O.innerText=F.get()?"已准备就绪":"";break;default:O.innerText=`正在更新课程...${A.get()}%`;break}});
27 |
--------------------------------------------------------------------------------
/extension/release/icons/favicon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/extension/release/icons/favicon128.png
--------------------------------------------------------------------------------
/extension/release/icons/favicon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/extension/release/icons/favicon16.png
--------------------------------------------------------------------------------
/extension/release/icons/favicon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/extension/release/icons/favicon32.png
--------------------------------------------------------------------------------
/extension/release/icons/favicon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/extension/release/icons/favicon48.png
--------------------------------------------------------------------------------
/extension/release/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "GinsMooc Extension",
4 | "version": "2.2.0",
5 | "description": "A Chrome extension to get the mooc answers and evaluate auto automatically.",
6 | "icons": {
7 | "16": "icons/favicon16.png",
8 | "32": "icons/favicon32.png",
9 | "48": "icons/favicon48.png",
10 | "128": "icons/favicon128.png"
11 | },
12 | "author": { "email": "ginnnnnn@qq.com" },
13 | "homepage_url": "https://ginnnnnn.top/mooc",
14 | "content_scripts": [
15 | {
16 | "matches": ["https://www.icourse163.org/*learn/*?tid=*", "https://www.icourse163.org/mooc/main/newExam*"],
17 | "js": ["content-scripts/index-68d6334d.js"],
18 | "run_at": "document_end"
19 | }
20 | ]
21 | }
--------------------------------------------------------------------------------
/extension/src/main.ts:
--------------------------------------------------------------------------------
1 | import { CustomRefList } from "./plugins/react"
2 | import { useApiAccess } from "./plugins/apiAccess"
3 | import { sleep, getUrlParam } from "./plugins/tool"
4 | import { getQuizQuestionKeys, setQuizAnswer, setHomeworkAnswer, autoEvaluate, batchEvaluate } from "./plugins/mooc"
5 | import { newExamHandle } from "./newExam"
6 |
7 | const apiAccess = useApiAccess()
8 | const refList = new CustomRefList()
9 | const testType = refList.add(<"quiz" | "homework" | undefined>undefined)
10 | const newCourseState = refList.add(-1)
11 | let testId: string | null = null
12 | const [analysis, prepare, examlist] = [refList.add(false), refList.add(false), refList.add(false)]
13 |
14 | if (location.href.indexOf("newExam") !== -1) {
15 | newExamHandle()
16 | }
17 |
18 | const getAnswer = async () => {
19 | if (stateTips.innerText === "正在获取答案,请稍后...") {
20 | return
21 | }
22 | stateTips.innerText = "正在获取答案,请稍后..."
23 | if (testType.get() === "quiz") {
24 | const answers = await apiAccess("selectQustion", { tid: testId as string }, getQuizQuestionKeys())
25 | console.log(answers)
26 | setQuizAnswer(answers.data.choiceAns as number[], answers.data.completionAns as Object)
27 | } else if (testType.get() === "homework") {
28 | const answers = await apiAccess("selectQustion", { tid: testId as string }, {})
29 | setHomeworkAnswer(answers.data.homeworkAns as Object)
30 | }
31 | stateTips.innerText = ""
32 | }
33 |
34 | const styleNode = document.createElement("style")
35 | styleNode.innerText = `
36 | input.gin-answer:not(:checked) + label, #GinsMooc, .gin-answer-item {
37 | background-color: #d9ecff;
38 | }
39 | .learnPageContentLeft {
40 | background: rgb(240, 242, 245);
41 | }
42 | #GinsMooc {
43 | margin-bottom: 12px !important;
44 | }
45 | .gin-function {
46 | display: flex;
47 | align-items: center;
48 | }
49 | .gin-function .u-btn {
50 | margin-right: 16px;
51 | }
52 | .gin-state-tips {
53 | font-size: 14px;
54 | }
55 | `
56 | document.head.append(styleNode)
57 |
58 | const wrapperNode = document.createElement("div")
59 | wrapperNode.id = "GinsMooc"
60 | wrapperNode.classList.add("m-learnbox")
61 | document.querySelector(".learnPageContentLeft")?.prepend(wrapperNode)
62 |
63 | if (location.href.indexOf("/spoc") !== -1) {
64 | const spocTipsNode = document.createElement("div")
65 | spocTipsNode.innerHTML = "当前课程为 SPOC 课程,可能无法获取答案。SPOC 课程可能会有关联的对外课程,你可以尝试搜索并加入,题目大概率是一样的"
66 | wrapperNode.prepend()
67 | }
68 |
69 | const setNotice = async () => {
70 | const notice = (await apiAccess("getNotice", { version: "v2.2.0" }, undefined)).data
71 | console.log(notice)
72 | if (
73 | !localStorage
74 | .getItem("Gins-ignore-notice")
75 | ?.split(",")
76 | .find((item) => Number.parseInt(item) === notice.id)
77 | ) {
78 | const noticeNode = document.createElement("div")
79 | noticeNode.innerHTML = notice.content
80 | const closeBtn = document.createElement("a")
81 | closeBtn.innerText = "不再提醒"
82 | closeBtn.onclick = () => {
83 | const origin = localStorage.getItem("Gins-ignore-notice")
84 | localStorage.setItem("Gins-ignore-notice", origin ? `${origin},${notice.id}` : `${notice.id}`)
85 | noticeNode.remove()
86 | closeBtn.remove()
87 | }
88 | closeBtn.style.marginLeft = "16px"
89 | noticeNode.append(closeBtn)
90 | wrapperNode.prepend(noticeNode)
91 | }
92 | }
93 | setNotice()
94 |
95 | const functionNode = document.createElement("div")
96 | functionNode.classList.add("gin-function")
97 | wrapperNode.append(functionNode)
98 |
99 | const getAnswerBtn = document.createElement("button")
100 | getAnswerBtn.classList.add("u-btn", "u-btn-default", "f-dn")
101 | getAnswerBtn.onclick = getAnswer
102 | getAnswerBtn.innerText = "获取答案"
103 | functionNode.append(getAnswerBtn)
104 |
105 | const evaluateBtn = document.createElement("button")
106 | evaluateBtn.classList.add("u-btn", "u-btn-default", "f-dn")
107 | evaluateBtn.onclick = () => {
108 | autoEvaluate()
109 | window.scroll({ top: document.documentElement.scrollHeight, behavior: "smooth" })
110 | }
111 | evaluateBtn.innerText = "一键互评"
112 | functionNode.append(evaluateBtn)
113 |
114 | const batchEvaluateBtn = document.createElement("button")
115 | batchEvaluateBtn.classList.add("u-btn", "u-btn-default", "f-dn")
116 | batchEvaluateBtn.onclick = () => {
117 | batchEvaluate(5, (finish: number, total: number) => {
118 | if (finish >= total) {
119 | stateTips.innerText = `已完成 ${total} 次互评`
120 | } else {
121 | stateTips.innerText = `自动互评中(${finish} / ${total})`
122 | }
123 | })
124 | }
125 | batchEvaluateBtn.innerText = "自动互评"
126 | functionNode.append(batchEvaluateBtn)
127 |
128 |
129 | const stateTips = document.createElement("div")
130 | stateTips.classList.add("gin-state-tips")
131 | functionNode.append(stateTips)
132 |
133 | window.addEventListener("hashchange", () => {
134 | testId = getUrlParam("id")
135 | if (location.hash.indexOf("quiz") !== -1 || location.hash.indexOf("examObject") !== -1) {
136 | testType.set("quiz")
137 | } else if (location.hash.indexOf("hw") !== -1 || location.hash.indexOf("examSubjective") !== -1) {
138 | testType.set("homework")
139 | } else {
140 | testType.set(undefined)
141 | }
142 | })
143 |
144 | window.setInterval(() => {
145 | examlist.set(location.hash.indexOf("examlist") !== -1)
146 | const prepareNode = document.querySelector(".j-prepare.prepare")
147 | prepare.set(
148 | (prepareNode && !prepareNode.classList.contains("f-dn")) ||
149 | document.querySelector(".j-homework-paper") !== null ||
150 | examlist.get()
151 | )
152 | analysis.set(document.querySelector(".u-questionItem.u-analysisQuestion.analysisMode") !== null)
153 | }, 100);
154 |
155 | const onTestChange = async () => {
156 | console.log("onTestChange", testType.get(), prepare.get(), examlist.get(), testId)
157 | if ((testType.get() === "quiz" && !prepare.get()) || testType.get() === "homework") {
158 | getAnswerBtn.classList.remove("f-dn")
159 | } else {
160 | getAnswerBtn.classList.add("f-dn")
161 | }
162 |
163 | newCourseState.set(-2)
164 | if (prepare.get() && testId) {
165 | const res = await apiAccess("checkTestExist", { tid: testId, type: "isExisting" }, undefined)
166 | if (res.data.existing) {
167 | newCourseState.set(-1)
168 | return
169 | } else {
170 | const eventSource = new EventSource(`https://ginnnnnn.top/api/mooc/course/refresh/${getUrlParam("tid")}`)
171 | eventSource.onmessage = (event) => {
172 | console.log(event.data)
173 | const state = JSON.parse(event.data)
174 | if (state && state.total > 0) {
175 | newCourseState.set(Math.round((state.finished / state.total) * 100))
176 | }
177 | if (newCourseState.value === 100 || state.status === 400) {
178 | eventSource.close()
179 | if (state.msg) {
180 | newCourseState.set(-1)
181 | }
182 | }
183 | }
184 | }
185 | } else if (!examlist.get()) {
186 | newCourseState.set(-1)
187 | return
188 | }
189 | }
190 |
191 | const onModeChange = () => {
192 | console.log("onModeChange", analysis.get())
193 | if (analysis.get()) {
194 | evaluateBtn.classList.remove("f-dn")
195 | batchEvaluateBtn.classList.remove("f-dn")
196 | } else {
197 | evaluateBtn.classList.add("f-dn")
198 | batchEvaluateBtn.classList.add("f-dn")
199 | }
200 | }
201 |
202 | analysis.addEventListenr("change", onModeChange)
203 | prepare.addEventListenr("change", onTestChange)
204 | testType.addEventListenr("change", onTestChange)
205 | newCourseState.addEventListenr("set", () => {
206 | switch (newCourseState.get()) {
207 | case -2:
208 | stateTips.innerText = "正在检查课程..."
209 | break
210 | case -1:
211 | stateTips.innerText = prepare.get() ? "已准备就绪" : ""
212 | break
213 | default:
214 | stateTips.innerText = `正在更新课程...${newCourseState.get()}%`
215 | break
216 | }
217 | })
218 |
--------------------------------------------------------------------------------
/extension/src/newExam.ts:
--------------------------------------------------------------------------------
1 | import { useApiAccess } from "./plugins/apiAccess"
2 | import { sleep, getUrlParam } from "./plugins/tool"
3 |
4 | export const newExamHandle = async () => {
5 | const apiAccess = useApiAccess()
6 | let form = document.getElementById("app")?.getElementsByTagName("form").item(0)
7 |
8 | while (!form) {
9 | await sleep(1000)
10 | form = document.getElementById("app")?.getElementsByTagName("form").item(0)
11 | }
12 |
13 | const getAnswer = async () => {
14 | const info = await apiAccess(
15 | "getNewExamInfo",
16 | { csrfKey: document.cookie.match(/NTESSTUDYSI=([a-z0-9]+);/)![1] },
17 | { answerformId: getUrlParam("aid")!, examId: getUrlParam("eid")! }
18 | )
19 | let oidList: Array = []
20 | for (let question of info.result.questions) {
21 | for (let option of question.optionDtos) {
22 | oidList.push(option.id)
23 | }
24 | }
25 | const answers = await apiAccess("selectQustion", { tid: info.result.tid }, { oidList: oidList })
26 | const optionElements = document.querySelectorAll(
27 | ".ant-checkbox-group>div, .ant-radio-group>div"
28 | ) as NodeListOf
29 | // console.log(optionElements)
30 | for (let id of answers.data.choiceAns!) {
31 | optionElements[oidList.indexOf(id)].classList.add("gin-answer-item")
32 | }
33 | }
34 |
35 | const getAnswerBtn = document.createElement("button")
36 | getAnswerBtn.className = "ant-btn ant-btn-primary"
37 | getAnswerBtn.setAttribute("style", "margin-bottom: 16px")
38 | getAnswerBtn.onclick = getAnswer
39 | getAnswerBtn.innerText = "获取答案"
40 | form?.before(getAnswerBtn)
41 | }
42 |
--------------------------------------------------------------------------------
/extension/src/plugins/apiAccess.ts:
--------------------------------------------------------------------------------
1 | import type { ApiKeyType, ApiResponseType, ApiRequestType } from "../type/api"
2 | import type { App } from "vue"
3 | import { isAxiosError } from "axios"
4 | import apiInfo from "../type/api"
5 | import axios from "axios"
6 |
7 | const baseUrl = "https://ginnnnnn.top/api"
8 |
9 | async function apiAccess(api: T): Promise
10 | async function apiAccess(
11 | api: T,
12 | params: ApiRequestType[T]["params"],
13 | data: ApiRequestType[T]["data"]
14 | ): Promise
15 |
16 | /** 函数重载 */
17 | async function apiAccess(
18 | api: T,
19 | params?: ApiRequestType[T]["params"],
20 | data?: ApiRequestType[T]["data"]
21 | ) {
22 | /** 错误处理,主要catch 404,调用者不再需要try-catch */
23 | try {
24 | return await new Promise((resolve, reject) => {
25 | /** 查询参数转动态路由参数 */
26 | let url = apiInfo[api].url
27 | if (params) {
28 | for (const [key, val] of Object.entries(params)) {
29 | const reg = RegExp(`(/):${key}(/)?`, "g")
30 | if (reg.test(url)) {
31 | url = url.replaceAll(reg, `$1${val}$2`)
32 | Reflect.deleteProperty(params, key)
33 | }
34 | }
35 | }
36 | if (url.indexOf("http") != 0) {
37 | url = `${baseUrl}${url}`
38 | }
39 | /** 将对象转为json字符串 */
40 | if (data) {
41 | for (const [key, val] of Object.entries(data)) {
42 | if (typeof val === "object") {
43 | Reflect.set(data, key, JSON.stringify(val))
44 | }
45 | }
46 | }
47 | /** 异步发送请求 */
48 | axios({
49 | url: url,
50 | method: apiInfo[api].method,
51 | params: params || {},
52 | data: data || {},
53 | headers: { "Content-Type": "application/x-www-form-urlencoded" }
54 | })
55 | .then((res) => {
56 | let message = "",
57 | success = false
58 | if (res.status !== 200 || !res.data) {
59 | message = "请求出错"
60 | } else if (res.data.msg) {
61 | message = res.data.msg
62 | if (res.data.status === 200) {
63 | success = true
64 | }
65 | }
66 | if (message) {
67 | console.log(message)
68 | }
69 | resolve(res.data)
70 | })
71 | .catch((error) => {
72 | let message = error
73 | if (isAxiosError(error)) {
74 | message = error.message
75 | }
76 | console.log(message)
77 | reject(error)
78 | })
79 | })
80 | } catch {
81 | return {}
82 | }
83 | }
84 |
85 | export const useApiAccess = () => apiAccess
86 |
87 | export default {
88 | install: (app: App) => {
89 | app.config.globalProperties.$apiAccess = apiAccess
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/extension/src/plugins/mooc.ts:
--------------------------------------------------------------------------------
1 | import type { quiz, option } from "../type/mooc"
2 | import { sleep, waitFor } from "./tool"
3 |
4 | const getQuizQuestionKeys = () => {
5 | const [oidList, titleList] = [new Array(), new Array()]
6 | const choices = document.querySelectorAll('input[id^="op_"]')
7 | for (let i = 0; i < choices.length; i++) {
8 | const choice = choices.item(i) as HTMLInputElement
9 | oidList.push(Number.parseInt(choice.id.split("_")[2].slice(0, -13)))
10 | }
11 | const completions = document.getElementsByClassName("m-FillBlank examMode u-questionItem")
12 | for (let i = 0; i < completions.length; i++) {
13 | const questionNode = completions.item(i) as HTMLDivElement
14 | const titleNode = questionNode.querySelector(".j-richTxt") as HTMLDivElement
15 | titleList.push(titleNode.innerText)
16 | }
17 | return { oidList, titleList }
18 | }
19 |
20 | const setQuizAnswer = (choiceAns: number[], completionAns: Object) => {
21 | for (const id of choiceAns) {
22 | const node = document.querySelector(`input[id*="_${id}"]`) as HTMLInputElement
23 | node.classList.add("gin-answer")
24 | }
25 | const completions = document.getElementsByClassName("m-FillBlank examMode u-questionItem")
26 | for (let i = 0; i < completions.length; i++) {
27 | const questionNode = completions.item(i) as HTMLDivElement
28 | const titleNode = questionNode.querySelector(".j-richTxt") as HTMLDivElement
29 | const question = Reflect.get(completionAns, titleNode.innerText) as quiz
30 |
31 | const newTitleNode = document.createElement("div")
32 | newTitleNode.innerHTML = question.title
33 | titleNode.appendChild(newTitleNode)
34 |
35 | const answerList = (question.stdAnswer).split("##%_YZPRLFH_%##")
36 | const answerListNode = document.createElement("div")
37 | for (let j = 0; j < answerList.length; j++) {
38 | const answerNode = document.createElement("span")
39 | answerNode.classList.add("gin-answer-item")
40 | answerNode.innerHTML = answerList[j]
41 | answerListNode.append(answerNode)
42 | if (j !== answerList.length - 1) {
43 | answerListNode.append(" / ")
44 | }
45 | }
46 | titleNode.append(answerListNode)
47 | }
48 | }
49 |
50 | const setHomeworkAnswer = (homeworkAns: Object) => {
51 | const homeworks = document.getElementsByClassName("f-richEditorText j-richTxt f-fl")
52 | for (let i = 0; i < homeworks.length; i++) {
53 | const answerNode = document.createElement("div")
54 | answerNode.classList.add('gin-answer-item')
55 | answerNode.innerHTML = Reflect.get(homeworkAns, `${i}`)?.answer as string
56 | homeworks.item(i)?.append(answerNode)
57 | }
58 | }
59 |
60 | const autoEvaluate = () => {
61 | const analysis = document.getElementsByClassName("u-questionItem u-analysisQuestion analysisMode")
62 | for (let i = 0; i < analysis.length; i++) {
63 | const radioGroup = analysis.item(i)?.getElementsByClassName("s") as HTMLCollection
64 | for (let j = 0; j < radioGroup.length; j++) {
65 | const radio = radioGroup.item(j)?.lastElementChild?.querySelector("input") as HTMLInputElement
66 | radio.checked = true
67 | }
68 | const textarea = analysis.item(i)?.querySelector("textarea") as HTMLTextAreaElement
69 | textarea.value = "666"
70 | }
71 | }
72 |
73 | const batchEvaluate = async (times: number, updateCaller: (finish: number, total: number) => any) => {
74 | const submitBtn = document.querySelector(".u-homework-evaAction .bottombtnwrap .j-submitbtn") as HTMLDivElement
75 | const xlinfo = document.querySelector(".u-homework-evaAction .xlinfo") as HTMLDivElement
76 | const nextBtn = document.querySelector(".u-homework-evaAction .xlinfo .j-gotonext") as HTMLDivElement
77 | for (let i = 0; i < times; i++) {
78 | await waitFor(() => xlinfo.style.display === 'none')
79 | autoEvaluate()
80 | await sleep(1000)
81 | console.log(new Date())
82 | submitBtn.click()
83 | await waitFor(() => xlinfo.style.display !== 'none')
84 | updateCaller(i + 1, times)
85 | nextBtn.click()
86 | }
87 | }
88 |
89 | export { getQuizQuestionKeys, setHomeworkAnswer, setQuizAnswer, autoEvaluate, batchEvaluate }
90 |
--------------------------------------------------------------------------------
/extension/src/plugins/react.ts:
--------------------------------------------------------------------------------
1 | import { randomString } from "./tool"
2 |
3 | class CustomRefList extends Array> {
4 | id: string
5 | node: HTMLElement
6 |
7 | constructor() {
8 | super()
9 | this.node = document.createElement("gin")
10 | this.id = randomString(8)
11 | this.node.id = `gin-auto-${this.id}`
12 | document.body.appendChild(this.node)
13 | }
14 |
15 | add(value: T): CustomRef {
16 | const add = new CustomRef(this, value)
17 | super.push(add)
18 | return add
19 | }
20 | }
21 |
22 | class CustomRef {
23 | id: string
24 | node: HTMLElement
25 | parent: CustomRefList
26 | value: T
27 |
28 | constructor(parent: CustomRefList, value: T) {
29 | this.parent = parent
30 | this.node = document.createElement("gin")
31 | this.id = randomString(8)
32 | this.node.id = this.id
33 | this.parent.node.appendChild(this.node)
34 | this.value = value
35 | }
36 |
37 | get(): T {
38 | return this.value
39 | }
40 |
41 | set(value: T): void {
42 | if (this.value !== value) {
43 | const oldValue = this.value
44 | this.value = value
45 | this.node.dispatchEvent(new CustomEvent("change", { detail: { oldValue, newValue: this.value } }))
46 | }
47 | this.node.dispatchEvent(new CustomEvent("set"))
48 | }
49 |
50 | addEventListenr(eventName: "change" | "set", callback: (ev: Event) => void): void {
51 | this.node.addEventListener(eventName, callback)
52 | }
53 | }
54 |
55 | export { CustomRefList }
--------------------------------------------------------------------------------
/extension/src/plugins/tool.ts:
--------------------------------------------------------------------------------
1 | const sleep = async (ms: number) => {
2 | return new Promise((resolve) => {
3 | setTimeout(() => {
4 | resolve('')
5 | }, ms)
6 | })
7 | }
8 |
9 | const waitFor = async (checker: any) => {
10 | return new Promise((resolve) => {
11 | const checkDisplay = setInterval(() => {
12 | console.log('check wait for')
13 | if (checker()) {
14 | clearInterval(checkDisplay);
15 | resolve('');
16 | }
17 | }, 50);
18 | });
19 | }
20 |
21 | const getUrlParam = (key: string) => {
22 | const reg = new RegExp('(^|&)' + key + '=([^&]*)(&|$)')
23 | const result = window.location.search.substring(1).match(reg)
24 | || window.location.hash.substring((window.location.hash.search(/\?/)) + 1).match(reg)
25 | return result ? decodeURIComponent(result[2]) : null
26 | }
27 |
28 | const randomString = (length: number) => {
29 | const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
30 | let result = ''
31 | for (let i = 0; i < length; i++) {
32 | result += chars[Math.floor(Math.random() * chars.length)]
33 | }
34 | return result
35 | }
36 |
37 | export { sleep, getUrlParam, randomString, waitFor }
--------------------------------------------------------------------------------
/extension/src/type/api.ts:
--------------------------------------------------------------------------------
1 | import type { homework, quiz, notice } from "./mooc"
2 |
3 | type RequestType = {
4 | params?: Object
5 | data?: Object
6 | }
7 |
8 | type Response = {
9 | status: number
10 | data: T
11 | msg: string
12 | }
13 |
14 | const apiInfo = {
15 | checkTestExist: {
16 | url: "/mooc/test/:tid",
17 | method: "GET"
18 | },
19 | selectQustion: {
20 | url: "/mooc/test/:tid",
21 | method: "POST"
22 | },
23 | getAnnouncement: {
24 | url: "/mooc/announcement",
25 | method: "GET"
26 | },
27 | getNewExamInfo: {
28 | url: "https://www.icourse163.org/mm-tiku/web/j/mocExamBean.getPaper.rpc",
29 | method: "POST"
30 | },
31 | getNotice: {
32 | url: "/mooc/notice/extension",
33 | method: "GET"
34 | }
35 | }
36 |
37 | export type ApiKeyType = keyof typeof apiInfo
38 |
39 | export interface ApiResponseType {
40 | checkTestExist: Response<{
41 | existing: boolean
42 | }>
43 | selectQustion: Response<{
44 | choiceAns?: number[]
45 | completionAns?: Object
46 | homeworkAns?: Object
47 | }>
48 | getAnnouncement: Response
49 | getNotice: Response
50 | getNewExamInfo: {
51 | code: number
52 | result: {
53 | tid: number
54 | questions: { optionDtos: { id: number }[] }[]
55 | }
56 | }
57 | }
58 |
59 | export interface ApiRequestType {
60 | checkTestExist: RequestType & {
61 | params: { tid: number | string; type: "isExisting" }
62 | }
63 | selectQustion: RequestType & {
64 | params: { tid: number | string }
65 | data: {
66 | oidList?: number[]
67 | titleList?: string[]
68 | }
69 | }
70 | getNotice: RequestType & {
71 | params: { version: string }
72 | }
73 | getNewExamInfo: RequestType & {
74 | params: { csrfKey: string }
75 | data: {
76 | answerformId: number | string
77 | examId: number | string
78 | }
79 | }
80 | }
81 |
82 | export default apiInfo
83 |
--------------------------------------------------------------------------------
/extension/src/type/mooc.ts:
--------------------------------------------------------------------------------
1 | export enum QuestionTypeEnumList {
2 | SingleChoice = "SINGLE_CHOICE",
3 | MultipleChoice = "MULTIPLE_CHOICE",
4 | Completion = "COMPLETION",
5 | Judge = "JUDGEMENT",
6 | Homework = "HOMEWORK",
7 | OnlineJudge = "ONLINE_JUDGE"
8 | }
9 |
10 | interface course extends Object {
11 | id: number
12 | name: string
13 | school: string
14 | imageUrl: string
15 | }
16 |
17 | interface test extends Object {
18 | id: number
19 | name: string
20 | objective: boolean
21 | releaseTime: string
22 | deadline: string
23 | chapterId: number
24 | chapterName: string
25 | }
26 |
27 | interface option extends Object {
28 | id: number
29 | content: string
30 | answer: boolean
31 | }
32 |
33 | interface quiz extends Object {
34 | id: number
35 | type:
36 | | QuestionTypeEnumList.SingleChoice
37 | | QuestionTypeEnumList.MultipleChoice
38 | | QuestionTypeEnumList.Completion
39 | | QuestionTypeEnumList.Judge
40 | title: string
41 | stdAnswer: string | null
42 | optionList: option[] | null
43 | }
44 |
45 | interface homework extends Object {
46 | id: number
47 | type: QuestionTypeEnumList.Homework | QuestionTypeEnumList.OnlineJudge
48 | title: string
49 | answer: string | null
50 | description: string | null
51 | memoryLimit: number | null
52 | timeLimit: number | null
53 | }
54 |
55 | interface notice extends Object {
56 | id: number
57 | content: string
58 | }
59 |
60 | export type QuestionTypeEnum = typeof QuestionTypeEnumList
61 | export type { course, test, option, quiz, homework, notice }
62 |
--------------------------------------------------------------------------------
/extension/tsconfig.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.node.json",
3 | "include": [
4 | "vite.config.*",
5 | "vitest.config.*",
6 | "cypress.config.*",
7 | "playwright.config.*"
8 | ],
9 | "compilerOptions": {
10 | "composite": true,
11 | "types": [
12 | "node"
13 | ]
14 | }
15 | }
--------------------------------------------------------------------------------
/extension/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.web.json",
3 | "include": [
4 | "env.d.ts",
5 | "src/**/*",
6 | "src/**/*.vue"
7 | ],
8 | "compilerOptions": {
9 | "baseUrl": ".",
10 | "paths": {
11 | "@/*": [
12 | "./src/*"
13 | ]
14 | }
15 | },
16 | "references": [
17 | {
18 | "path": "./tsconfig.config.json"
19 | }
20 | ]
21 | }
--------------------------------------------------------------------------------
/extension/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 |
3 | import { defineConfig } from 'vite'
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [],
8 | resolve: {
9 | alias: {
10 | '@': fileURLToPath(new URL('./src', import.meta.url))
11 | }
12 | },
13 | build: {
14 | "outDir": "./release/content-scripts",
15 | "assetsDir": "./"
16 | }
17 | })
18 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Gins
11 |
14 |
23 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ginsmooc",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "run-p type-check build-only",
8 | "preview": "vite preview",
9 | "build-only": "vite build",
10 | "type-check": "vue-tsc --noEmit",
11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
12 | },
13 | "dependencies": {
14 | "@element-plus/icons-vue": "^2.1.0",
15 | "@jridgewell/sourcemap-codec": "^1.4.14",
16 | "@vueuse/core": "^9.11.1",
17 | "axios": "^1.2.3",
18 | "d3-cloud": "^1.2.5",
19 | "element-plus": "^2.2.28",
20 | "fuzzysort": "^2.0.4",
21 | "vue": "^3.2.45",
22 | "vue-router": "^4.1.6"
23 | },
24 | "devDependencies": {
25 | "@rushstack/eslint-patch": "^1.1.4",
26 | "@types/node": "^18.15.5",
27 | "@vitejs/plugin-vue": "^4.0.0",
28 | "@vue/eslint-config-prettier": "^7.0.0",
29 | "@vue/eslint-config-typescript": "^11.0.0",
30 | "@vue/tsconfig": "^0.1.3",
31 | "eslint": "^8.22.0",
32 | "eslint-plugin-vue": "^9.3.0",
33 | "npm-run-all": "^4.1.5",
34 | "prettier": "^2.7.1",
35 | "typescript": "~4.7.4",
36 | "vite": "^4.0.0",
37 | "vue-tsc": "^1.0.12"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/Arcaea_2-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/Arcaea_2-1.png
--------------------------------------------------------------------------------
/public/Arcaea_Story_Last2_epilogue_cg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/Arcaea_Story_Last2_epilogue_cg.jpg
--------------------------------------------------------------------------------
/public/Arcaea_Story_Last2_epilogue_cg_part.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/Arcaea_Story_Last2_epilogue_cg_part.png
--------------------------------------------------------------------------------
/public/Last1_epilogue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/Last1_epilogue.png
--------------------------------------------------------------------------------
/public/Last1_epilogue_part.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/Last1_epilogue_part.png
--------------------------------------------------------------------------------
/public/background.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
37 |
38 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/public/css/images/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/css/images/bg.jpg
--------------------------------------------------------------------------------
/public/css/images/ie/footer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/css/images/ie/footer.png
--------------------------------------------------------------------------------
/public/css/images/ie/footer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/css/images/overlay-pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/css/images/overlay-pattern.png
--------------------------------------------------------------------------------
/public/css/images/overlay.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/css/main.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,900");
2 | @import url("fontawesome-all.min.css");
3 |
4 | /*
5 | Aerial by HTML5 UP
6 | html5up.net | @ajlkn
7 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
8 | */
9 |
10 | html, body, div, span, applet, object,
11 | iframe, h1, h2, h3, h4, h5, h6, p, blockquote,
12 | pre, a, abbr, acronym, address, big, cite,
13 | code, del, dfn, em, img, ins, kbd, q, s, samp,
14 | small, strike, strong, sub, sup, tt, var, b,
15 | u, i, center, dl, dt, dd, ol, ul, li, fieldset,
16 | form, label, legend, table, caption, tbody,
17 | tfoot, thead, tr, th, td, article, aside,
18 | canvas, details, embed, figure, figcaption,
19 | footer, header, hgroup, menu, nav, output, ruby,
20 | section, summary, time, mark, audio, video {
21 | margin: 0;
22 | padding: 0;
23 | border: 0;
24 | font-size: 100%;
25 | font: inherit;
26 | vertical-align: baseline;}
27 |
28 | article, aside, details, figcaption, figure,
29 | footer, header, hgroup, menu, nav, section {
30 | display: block;}
31 |
32 | body {
33 | line-height: 1;
34 | }
35 |
36 | ol, ul {
37 | list-style: none;
38 | }
39 |
40 | blockquote, q {
41 | quotes: none;
42 | }
43 |
44 | blockquote:before, blockquote:after, q:before, q:after {
45 | content: '';
46 | content: none;
47 | }
48 |
49 | table {
50 | border-collapse: collapse;
51 | border-spacing: 0;
52 | }
53 |
54 | body {
55 | -webkit-text-size-adjust: none;
56 | }
57 |
58 | mark {
59 | background-color: transparent;
60 | color: inherit;
61 | }
62 |
63 | input::-moz-focus-inner {
64 | border: 0;
65 | padding: 0;
66 | }
67 |
68 | input, select, textarea {
69 | -moz-appearance: none;
70 | -webkit-appearance: none;
71 | -ms-appearance: none;
72 | appearance: none;
73 | }
74 |
75 | /* Basic */
76 |
77 | html {
78 | box-sizing: border-box;
79 | }
80 |
81 | *, *:before, *:after {
82 | box-sizing: inherit;
83 | }
84 |
85 | body {
86 | background: #fff;
87 | overflow: hidden;
88 | }
89 |
90 | body.is-preload *, body.is-preload *:before, body.is-preload *:after {
91 | -moz-animation: none !important;
92 | -webkit-animation: none !important;
93 | -ms-animation: none !important;
94 | animation: none !important;
95 | -moz-transition: none !important;
96 | -webkit-transition: none !important;
97 | -ms-transition: none !important;
98 | transition: none !important;
99 | }
100 |
101 | body, input, select, textarea {
102 | color: #fff;
103 | font-family: 'Source Sans Pro', sans-serif;
104 | font-size: 15pt;
105 | font-weight: 300 !important;
106 | letter-spacing: -0.025em;
107 | line-height: 1.75em;
108 | }
109 |
110 | a {
111 | -moz-transition: border-color 0.2s ease-in-out;
112 | -webkit-transition: border-color 0.2s ease-in-out;
113 | -ms-transition: border-color 0.2s ease-in-out;
114 | transition: border-color 0.2s ease-in-out;
115 | border-bottom: dotted 1px;
116 | color: inherit;
117 | outline: 0;
118 | text-decoration: none;
119 | }
120 |
121 | a:hover {
122 | border-color: transparent;
123 | }
124 |
125 | /* Icon */
126 |
127 | .icon {
128 | text-decoration: none;
129 | position: relative;
130 | }
131 |
132 | .icon:before {
133 | -moz-osx-font-smoothing: grayscale;
134 | -webkit-font-smoothing: antialiased;
135 | display: inline-block;
136 | font-style: normal;
137 | font-variant: normal;
138 | text-rendering: auto;
139 | line-height: 1;
140 | text-transform: none !important;
141 | font-family: 'Font Awesome 5 Free';
142 | font-weight: 400;
143 | }
144 |
145 | .icon > .label {
146 | display: none;
147 | }
148 |
149 | .icon.solid:before {
150 | font-weight: 900;
151 | }
152 |
153 | .icon.brands:before {
154 | font-family: 'Font Awesome 5 Brands';
155 | }
156 |
157 | /* Wrapper */
158 |
159 | @-moz-keyframes wrapper {
160 | 0% {
161 | opacity: 0;
162 | }
163 |
164 | 100% {
165 | opacity: 1;
166 | }
167 | }
168 |
169 | @-webkit-keyframes wrapper {
170 | 0% {
171 | opacity: 0;
172 | }
173 |
174 | 100% {
175 | opacity: 1;
176 | }
177 | }
178 |
179 | @-ms-keyframes wrapper {
180 | 0% {
181 | opacity: 0;
182 | }
183 |
184 | 100% {
185 | opacity: 1;
186 | }
187 | }
188 |
189 | @keyframes wrapper {
190 | 0% {
191 | opacity: 0;
192 | }
193 |
194 | 100% {
195 | opacity: 1;
196 | }
197 | }
198 |
199 | #wrapper {
200 | -moz-animation: wrapper 3s forwards;
201 | -webkit-animation: wrapper 3s forwards;
202 | -ms-animation: wrapper 3s forwards;
203 | animation: wrapper 3s forwards;
204 | height: 100%;
205 | left: 0;
206 | opacity: 0;
207 | position: fixed;
208 | top: 0;
209 | width: 100%;
210 | }
211 |
212 | /* BG */
213 |
214 | #bg {
215 | -moz-animation: bg 60s linear infinite;
216 | -webkit-animation: bg 60s linear infinite;
217 | -ms-animation: bg 60s linear infinite;
218 | animation: bg 60s linear infinite;
219 | -moz-backface-visibility: hidden;
220 | -webkit-backface-visibility: hidden;
221 | -ms-backface-visibility: hidden;
222 | backface-visibility: hidden;
223 | -moz-transform: translate3d(0,0,0);
224 | -webkit-transform: translate3d(0,0,0);
225 | -ms-transform: translate3d(0,0,0);
226 | transform: translate3d(0,0,0);
227 | /* Set your background with this */
228 |
229 | background: #348cb2 url("https://gins-1255964181.cos.ap-beijing.myqcloud.com/bg.jpg") bottom left;
230 | background-repeat: repeat-x;
231 | height: 100%;
232 | left: 0;
233 | opacity: 1;
234 | position: fixed;
235 | top: 0;
236 | }
237 |
238 | @-moz-keyframes bg {
239 | 0% {
240 | -moz-transform: translate3d(0,0,0);
241 | -webkit-transform: translate3d(0,0,0);
242 | -ms-transform: translate3d(0,0,0);
243 | transform: translate3d(0,0,0);
244 | }
245 |
246 | 100% {
247 | -moz-transform: translate3d(-2250px,0,0);
248 | -webkit-transform: translate3d(-2250px,0,0);
249 | -ms-transform: translate3d(-2250px,0,0);
250 | transform: translate3d(-2250px,0,0);
251 | }
252 | }
253 |
254 | @-webkit-keyframes bg {
255 | 0% {
256 | -moz-transform: translate3d(0,0,0);
257 | -webkit-transform: translate3d(0,0,0);
258 | -ms-transform: translate3d(0,0,0);
259 | transform: translate3d(0,0,0);
260 | }
261 |
262 | 100% {
263 | -moz-transform: translate3d(-2250px,0,0);
264 | -webkit-transform: translate3d(-2250px,0,0);
265 | -ms-transform: translate3d(-2250px,0,0);
266 | transform: translate3d(-2250px,0,0);
267 | }
268 | }
269 |
270 | @-ms-keyframes bg {
271 | 0% {
272 | -moz-transform: translate3d(0,0,0);
273 | -webkit-transform: translate3d(0,0,0);
274 | -ms-transform: translate3d(0,0,0);
275 | transform: translate3d(0,0,0);
276 | }
277 |
278 | 100% {
279 | -moz-transform: translate3d(-2250px,0,0);
280 | -webkit-transform: translate3d(-2250px,0,0);
281 | -ms-transform: translate3d(-2250px,0,0);
282 | transform: translate3d(-2250px,0,0);
283 | }
284 | }
285 |
286 | @keyframes bg {
287 | 0% {
288 | -moz-transform: translate3d(0,0,0);
289 | -webkit-transform: translate3d(0,0,0);
290 | -ms-transform: translate3d(0,0,0);
291 | transform: translate3d(0,0,0);
292 | }
293 |
294 | 100% {
295 | -moz-transform: translate3d(-2250px,0,0);
296 | -webkit-transform: translate3d(-2250px,0,0);
297 | -ms-transform: translate3d(-2250px,0,0);
298 | transform: translate3d(-2250px,0,0);
299 | }
300 | }
301 |
302 | #bg {
303 | background-size: auto 100%;
304 | width: 6750px;
305 | }
306 |
307 | /* Overlay */
308 |
309 | @-moz-keyframes overlay {
310 | 0% {
311 | opacity: 0;
312 | }
313 |
314 | 100% {
315 | opacity: 1;
316 | }
317 | }
318 |
319 | @-webkit-keyframes overlay {
320 | 0% {
321 | opacity: 0;
322 | }
323 |
324 | 100% {
325 | opacity: 1;
326 | }
327 | }
328 |
329 | @-ms-keyframes overlay {
330 | 0% {
331 | opacity: 0;
332 | }
333 |
334 | 100% {
335 | opacity: 1;
336 | }
337 | }
338 |
339 | @keyframes overlay {
340 | 0% {
341 | opacity: 0;
342 | }
343 |
344 | 100% {
345 | opacity: 1;
346 | }
347 | }
348 |
349 | #overlay {
350 | -moz-animation: overlay 1.5s 1.5s forwards;
351 | -webkit-animation: overlay 1.5s 1.5s forwards;
352 | -ms-animation: overlay 1.5s 1.5s forwards;
353 | animation: overlay 1.5s 1.5s forwards;
354 | background-attachment: fixed, fixed;
355 | background-image: url("images/overlay-pattern.png"), url("images/overlay.svg");
356 | background-position: top left, center center;
357 | background-repeat: repeat, no-repeat;
358 | background-size: auto, cover;
359 | height: 100%;
360 | left: 0;
361 | opacity: 0;
362 | position: fixed;
363 | top: 0;
364 | width: 100%;
365 | }
366 |
367 | /* Main */
368 |
369 | #main {
370 | height: 100%;
371 | left: 0;
372 | position: fixed;
373 | text-align: center;
374 | top: 0;
375 | width: 100%;
376 | }
377 |
378 | #main:before {
379 | content: '';
380 | display: inline-block;
381 | height: 100%;
382 | margin-right: 0;
383 | vertical-align: middle;
384 | width: 1px;
385 | }
386 |
387 | /* Header */
388 |
389 | @-moz-keyframes header {
390 | 0% {
391 | -moz-transform: translate3d(0,1em,0);
392 | -webkit-transform: translate3d(0,1em,0);
393 | -ms-transform: translate3d(0,1em,0);
394 | transform: translate3d(0,1em,0);
395 | opacity: 0;
396 | }
397 |
398 | 100% {
399 | -moz-transform: translate3d(0,0,0);
400 | -webkit-transform: translate3d(0,0,0);
401 | -ms-transform: translate3d(0,0,0);
402 | transform: translate3d(0,0,0);
403 | opacity: 1;
404 | }
405 | }
406 |
407 | @-webkit-keyframes header {
408 | 0% {
409 | -moz-transform: translate3d(0,1em,0);
410 | -webkit-transform: translate3d(0,1em,0);
411 | -ms-transform: translate3d(0,1em,0);
412 | transform: translate3d(0,1em,0);
413 | opacity: 0;
414 | }
415 |
416 | 100% {
417 | -moz-transform: translate3d(0,0,0);
418 | -webkit-transform: translate3d(0,0,0);
419 | -ms-transform: translate3d(0,0,0);
420 | transform: translate3d(0,0,0);
421 | opacity: 1;
422 | }
423 | }
424 |
425 | @-ms-keyframes header {
426 | 0% {
427 | -moz-transform: translate3d(0,1em,0);
428 | -webkit-transform: translate3d(0,1em,0);
429 | -ms-transform: translate3d(0,1em,0);
430 | transform: translate3d(0,1em,0);
431 | opacity: 0;
432 | }
433 |
434 | 100% {
435 | -moz-transform: translate3d(0,0,0);
436 | -webkit-transform: translate3d(0,0,0);
437 | -ms-transform: translate3d(0,0,0);
438 | transform: translate3d(0,0,0);
439 | opacity: 1;
440 | }
441 | }
442 |
443 | @keyframes header {
444 | 0% {
445 | -moz-transform: translate3d(0,1em,0);
446 | -webkit-transform: translate3d(0,1em,0);
447 | -ms-transform: translate3d(0,1em,0);
448 | transform: translate3d(0,1em,0);
449 | opacity: 0;
450 | }
451 |
452 | 100% {
453 | -moz-transform: translate3d(0,0,0);
454 | -webkit-transform: translate3d(0,0,0);
455 | -ms-transform: translate3d(0,0,0);
456 | transform: translate3d(0,0,0);
457 | opacity: 1;
458 | }
459 | }
460 |
461 | @-moz-keyframes nav-icons {
462 | 0% {
463 | -moz-transform: translate3d(0,1em,0);
464 | -webkit-transform: translate3d(0,1em,0);
465 | -ms-transform: translate3d(0,1em,0);
466 | transform: translate3d(0,1em,0);
467 | opacity: 0;
468 | }
469 |
470 | 100% {
471 | -moz-transform: translate3d(0,0,0);
472 | -webkit-transform: translate3d(0,0,0);
473 | -ms-transform: translate3d(0,0,0);
474 | transform: translate3d(0,0,0);
475 | opacity: 1;
476 | }
477 | }
478 |
479 | @-webkit-keyframes nav-icons {
480 | 0% {
481 | -moz-transform: translate3d(0,1em,0);
482 | -webkit-transform: translate3d(0,1em,0);
483 | -ms-transform: translate3d(0,1em,0);
484 | transform: translate3d(0,1em,0);
485 | opacity: 0;
486 | }
487 |
488 | 100% {
489 | -moz-transform: translate3d(0,0,0);
490 | -webkit-transform: translate3d(0,0,0);
491 | -ms-transform: translate3d(0,0,0);
492 | transform: translate3d(0,0,0);
493 | opacity: 1;
494 | }
495 | }
496 |
497 | @-ms-keyframes nav-icons {
498 | 0% {
499 | -moz-transform: translate3d(0,1em,0);
500 | -webkit-transform: translate3d(0,1em,0);
501 | -ms-transform: translate3d(0,1em,0);
502 | transform: translate3d(0,1em,0);
503 | opacity: 0;
504 | }
505 |
506 | 100% {
507 | -moz-transform: translate3d(0,0,0);
508 | -webkit-transform: translate3d(0,0,0);
509 | -ms-transform: translate3d(0,0,0);
510 | transform: translate3d(0,0,0);
511 | opacity: 1;
512 | }
513 | }
514 |
515 | @keyframes nav-icons {
516 | 0% {
517 | -moz-transform: translate3d(0,1em,0);
518 | -webkit-transform: translate3d(0,1em,0);
519 | -ms-transform: translate3d(0,1em,0);
520 | transform: translate3d(0,1em,0);
521 | opacity: 0;
522 | }
523 |
524 | 100% {
525 | -moz-transform: translate3d(0,0,0);
526 | -webkit-transform: translate3d(0,0,0);
527 | -ms-transform: translate3d(0,0,0);
528 | transform: translate3d(0,0,0);
529 | opacity: 1;
530 | }
531 | }
532 |
533 | #header {
534 | -moz-animation: header 1s 2.25s forwards;
535 | -webkit-animation: header 1s 2.25s forwards;
536 | -ms-animation: header 1s 2.25s forwards;
537 | animation: header 1s 2.25s forwards;
538 | -moz-backface-visibility: hidden;
539 | -webkit-backface-visibility: hidden;
540 | -ms-backface-visibility: hidden;
541 | backface-visibility: hidden;
542 | -moz-transform: translate3d(0,0,0);
543 | -webkit-transform: translate3d(0,0,0);
544 | -ms-transform: translate3d(0,0,0);
545 | transform: translate3d(0,0,0);
546 | cursor: default;
547 | display: inline-block;
548 | opacity: 0;
549 | position: relative;
550 | text-align: center;
551 | top: -1em;
552 | vertical-align: middle;
553 | width: 90%;
554 | }
555 |
556 | #header h1 {
557 | font-size: 4.35em;
558 | font-weight: 900;
559 | letter-spacing: -0.035em;
560 | line-height: 1em;
561 | }
562 |
563 | #header p {
564 | font-size: 1.25em;
565 | margin: 0.75em 0 0.25em 0;
566 | opacity: 0.75;
567 | }
568 |
569 | #header nav {
570 | margin: 1.5em 0 0 0;
571 | }
572 |
573 | #header nav li {
574 | -moz-animation: nav-icons 0.5s ease-in-out forwards;
575 | -webkit-animation: nav-icons 0.5s ease-in-out forwards;
576 | -ms-animation: nav-icons 0.5s ease-in-out forwards;
577 | animation: nav-icons 0.5s ease-in-out forwards;
578 | -moz-backface-visibility: hidden;
579 | -webkit-backface-visibility: hidden;
580 | -ms-backface-visibility: hidden;
581 | backface-visibility: hidden;
582 | -moz-transform: translate3d(0,0,0);
583 | -webkit-transform: translate3d(0,0,0);
584 | -ms-transform: translate3d(0,0,0);
585 | transform: translate3d(0,0,0);
586 | display: inline-block;
587 | height: 5.35em;
588 | line-height: 5.885em;
589 | opacity: 0;
590 | position: relative;
591 | top: 0;
592 | width: 5.35em;
593 | }
594 |
595 | #header nav li:nth-child(1) {
596 | -moz-animation-delay: 2.5s;
597 | -webkit-animation-delay: 2.5s;
598 | -ms-animation-delay: 2.5s;
599 | animation-delay: 2.5s;
600 | }
601 |
602 | #header nav li:nth-child(2) {
603 | -moz-animation-delay: 2.75s;
604 | -webkit-animation-delay: 2.75s;
605 | -ms-animation-delay: 2.75s;
606 | animation-delay: 2.75s;
607 | }
608 |
609 | #header nav li:nth-child(3) {
610 | -moz-animation-delay: 3s;
611 | -webkit-animation-delay: 3s;
612 | -ms-animation-delay: 3s;
613 | animation-delay: 3s;
614 | }
615 |
616 | #header nav li:nth-child(4) {
617 | -moz-animation-delay: 3.25s;
618 | -webkit-animation-delay: 3.25s;
619 | -ms-animation-delay: 3.25s;
620 | animation-delay: 3.25s;
621 | }
622 |
623 | #header nav li:nth-child(5) {
624 | -moz-animation-delay: 3.5s;
625 | -webkit-animation-delay: 3.5s;
626 | -ms-animation-delay: 3.5s;
627 | animation-delay: 3.5s;
628 | }
629 |
630 | #header nav li:nth-child(6) {
631 | -moz-animation-delay: 3.75s;
632 | -webkit-animation-delay: 3.75s;
633 | -ms-animation-delay: 3.75s;
634 | animation-delay: 3.75s;
635 | }
636 |
637 | #header nav li:nth-child(7) {
638 | -moz-animation-delay: 4s;
639 | -webkit-animation-delay: 4s;
640 | -ms-animation-delay: 4s;
641 | animation-delay: 4s;
642 | }
643 |
644 | #header nav li:nth-child(8) {
645 | -moz-animation-delay: 4.25s;
646 | -webkit-animation-delay: 4.25s;
647 | -ms-animation-delay: 4.25s;
648 | animation-delay: 4.25s;
649 | }
650 |
651 | #header nav li:nth-child(9) {
652 | -moz-animation-delay: 4.5s;
653 | -webkit-animation-delay: 4.5s;
654 | -ms-animation-delay: 4.5s;
655 | animation-delay: 4.5s;
656 | }
657 |
658 | #header nav li:nth-child(10) {
659 | -moz-animation-delay: 4.75s;
660 | -webkit-animation-delay: 4.75s;
661 | -ms-animation-delay: 4.75s;
662 | animation-delay: 4.75s;
663 | }
664 |
665 | #header nav a {
666 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
667 | -webkit-touch-callout: none;
668 | border: 0;
669 | display: inline-block;
670 | }
671 |
672 | #header nav a:before {
673 | -moz-transition: all 0.2s ease-in-out;
674 | -webkit-transition: all 0.2s ease-in-out;
675 | -ms-transition: all 0.2s ease-in-out;
676 | transition: all 0.2s ease-in-out;
677 | border-radius: 100%;
678 | border: solid 1px #fff;
679 | display: block;
680 | font-size: 1.75em;
681 | height: 2.5em;
682 | line-height: 2.5em;
683 | position: relative;
684 | text-align: center;
685 | top: 0;
686 | width: 2.5em;
687 | }
688 |
689 | #header nav a:hover {
690 | font-size: 1.1em;
691 | }
692 |
693 | #header nav a:hover:before {
694 | background-color: rgba(255, 255, 255, 0.175);
695 | color: #fff;
696 | }
697 |
698 | #header nav a:active {
699 | font-size: 0.95em;
700 | background: none;
701 | }
702 |
703 | #header nav a:active:before {
704 | background-color: rgba(255, 255, 255, 0.35);
705 | color: #fff;
706 | }
707 |
708 | #header nav a span {
709 | display: none;
710 | }
711 |
712 | /* Footer */
713 |
714 | #footer {
715 | background-image: -moz-linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.5) 75%);
716 | background-image: -webkit-linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.5) 75%);
717 | background-image: -ms-linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.5) 75%);
718 | background-image: linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.5) 75%);
719 | bottom: 0;
720 | cursor: default;
721 | height: 6em;
722 | left: 0;
723 | line-height: 8em;
724 | position: absolute;
725 | text-align: center;
726 | width: 100%;
727 | }
728 |
729 | /* Wide */
730 |
731 | @media screen and (max-width: 1680px) {
732 |
733 | /* Basic */
734 |
735 | body, input, select, textarea {
736 | font-size: 13pt;
737 | }
738 |
739 | /* BG */
740 |
741 | @-moz-keyframes bg {
742 | 0% {
743 | -moz-transform: translate3d(0,0,0);
744 | -webkit-transform: translate3d(0,0,0);
745 | -ms-transform: translate3d(0,0,0);
746 | transform: translate3d(0,0,0);
747 | }
748 |
749 | 100% {
750 | -moz-transform: translate3d(-1500px,0,0);
751 | -webkit-transform: translate3d(-1500px,0,0);
752 | -ms-transform: translate3d(-1500px,0,0);
753 | transform: translate3d(-1500px,0,0);
754 | }
755 |
756 | }
757 |
758 | @-webkit-keyframes bg {
759 | 0% {
760 | -moz-transform: translate3d(0,0,0);
761 | -webkit-transform: translate3d(0,0,0);
762 | -ms-transform: translate3d(0,0,0);
763 | transform: translate3d(0,0,0);
764 | }
765 |
766 | 100% {
767 | -moz-transform: translate3d(-1500px,0,0);
768 | -webkit-transform: translate3d(-1500px,0,0);
769 | -ms-transform: translate3d(-1500px,0,0);
770 | transform: translate3d(-1500px,0,0);
771 | }
772 | }
773 |
774 | @-ms-keyframes bg {
775 | 0% {
776 | -moz-transform: translate3d(0,0,0);
777 | -webkit-transform: translate3d(0,0,0);
778 | -ms-transform: translate3d(0,0,0);
779 | transform: translate3d(0,0,0);
780 | }
781 |
782 | 100% {
783 | -moz-transform: translate3d(-1500px,0,0);
784 | -webkit-transform: translate3d(-1500px,0,0);
785 | -ms-transform: translate3d(-1500px,0,0);
786 | transform: translate3d(-1500px,0,0);
787 | }
788 | }
789 |
790 | @keyframes bg {
791 | 0% {
792 | -moz-transform: translate3d(0,0,0);
793 | -webkit-transform: translate3d(0,0,0);
794 | -ms-transform: translate3d(0,0,0);
795 | transform: translate3d(0,0,0);
796 | }
797 |
798 | 100% {
799 | -moz-transform: translate3d(-1500px,0,0);
800 | -webkit-transform: translate3d(-1500px,0,0);
801 | -ms-transform: translate3d(-1500px,0,0);
802 | transform: translate3d(-1500px,0,0);
803 | }
804 | }
805 |
806 | #bg {
807 | background-size: 1500px auto;
808 | width: 4500px;
809 | } }
810 |
811 | /* Normal */
812 |
813 | @media screen and (max-width: 1280px) {
814 |
815 | /* Basic */
816 |
817 | body, input, select, textarea {
818 | font-size: 12pt;
819 | }
820 |
821 | /* BG */
822 |
823 | @-moz-keyframes bg {
824 | 0% {
825 | -moz-transform: translate3d(0,0,0);
826 | -webkit-transform: translate3d(0,0,0);
827 | -ms-transform: translate3d(0,0,0);
828 | transform: translate3d(0,0,0);
829 | }
830 |
831 | 100% {
832 | -moz-transform: translate3d(-750px,0,0);
833 | -webkit-transform: translate3d(-750px,0,0);
834 | -ms-transform: translate3d(-750px,0,0);
835 | transform: translate3d(-750px,0,0);
836 | }
837 |
838 | }
839 |
840 | @-webkit-keyframes bg {
841 | 0% {
842 | -moz-transform: translate3d(0,0,0);
843 | -webkit-transform: translate3d(0,0,0);
844 | -ms-transform: translate3d(0,0,0);
845 | transform: translate3d(0,0,0);
846 | }
847 |
848 | 100% {
849 | -moz-transform: translate3d(-750px,0,0);
850 | -webkit-transform: translate3d(-750px,0,0);
851 | -ms-transform: translate3d(-750px,0,0);
852 | transform: translate3d(-750px,0,0);
853 | }
854 | }
855 |
856 | @-ms-keyframes bg {
857 | 0% {
858 | -moz-transform: translate3d(0,0,0);
859 | -webkit-transform: translate3d(0,0,0);
860 | -ms-transform: translate3d(0,0,0);
861 | transform: translate3d(0,0,0);
862 | }
863 |
864 | 100% {
865 | -moz-transform: translate3d(-750px,0,0);
866 | -webkit-transform: translate3d(-750px,0,0);
867 | -ms-transform: translate3d(-750px,0,0);
868 | transform: translate3d(-750px,0,0);
869 | }
870 | }
871 |
872 | @keyframes bg {
873 | 0% {
874 | -moz-transform: translate3d(0,0,0);
875 | -webkit-transform: translate3d(0,0,0);
876 | -ms-transform: translate3d(0,0,0);
877 | transform: translate3d(0,0,0);
878 | }
879 |
880 | 100% {
881 | -moz-transform: translate3d(-750px,0,0);
882 | -webkit-transform: translate3d(-750px,0,0);
883 | -ms-transform: translate3d(-750px,0,0);
884 | transform: translate3d(-750px,0,0);
885 | }
886 | }
887 |
888 | #bg {
889 | background-size: 750px auto;
890 | width: 2250px;
891 | } }
892 |
893 | /* Mobile */
894 |
895 | @media screen and (max-width: 736px) {
896 |
897 | /* Basic */
898 |
899 | body {
900 | min-width: 320px;
901 | }
902 |
903 | body, input, select, textarea {
904 | font-size: 11pt;
905 | }
906 |
907 | /* BG */
908 |
909 | @-moz-keyframes bg {
910 | 0% {
911 | -moz-transform: translate3d(0,0,0);
912 | -webkit-transform: translate3d(0,0,0);
913 | -ms-transform: translate3d(0,0,0);
914 | transform: translate3d(0,0,0);
915 | }
916 |
917 | 100% {
918 | -moz-transform: translate3d(-300px,0,0);
919 | -webkit-transform: translate3d(-300px,0,0);
920 | -ms-transform: translate3d(-300px,0,0);
921 | transform: translate3d(-300px,0,0);
922 | }
923 |
924 | }
925 |
926 | @-webkit-keyframes bg {
927 | 0% {
928 | -moz-transform: translate3d(0,0,0);
929 | -webkit-transform: translate3d(0,0,0);
930 | -ms-transform: translate3d(0,0,0);
931 | transform: translate3d(0,0,0);
932 | }
933 |
934 | 100% {
935 | -moz-transform: translate3d(-300px,0,0);
936 | -webkit-transform: translate3d(-300px,0,0);
937 | -ms-transform: translate3d(-300px,0,0);
938 | transform: translate3d(-300px,0,0);
939 | }
940 | }
941 |
942 | @-ms-keyframes bg {
943 | 0% {
944 | -moz-transform: translate3d(0,0,0);
945 | -webkit-transform: translate3d(0,0,0);
946 | -ms-transform: translate3d(0,0,0);
947 | transform: translate3d(0,0,0);
948 | }
949 |
950 | 100% {
951 | -moz-transform: translate3d(-300px,0,0);
952 | -webkit-transform: translate3d(-300px,0,0);
953 | -ms-transform: translate3d(-300px,0,0);
954 | transform: translate3d(-300px,0,0);
955 | }
956 | }
957 |
958 | @keyframes bg {
959 | 0% {
960 | -moz-transform: translate3d(0,0,0);
961 | -webkit-transform: translate3d(0,0,0);
962 | -ms-transform: translate3d(0,0,0);
963 | transform: translate3d(0,0,0);
964 | }
965 |
966 | 100% {
967 | -moz-transform: translate3d(-300px,0,0);
968 | -webkit-transform: translate3d(-300px,0,0);
969 | -ms-transform: translate3d(-300px,0,0);
970 | transform: translate3d(-300px,0,0);
971 | }
972 | }
973 |
974 | #bg {
975 | background-size: 300px auto;
976 | width: 900px;
977 | }
978 |
979 | /* Header */
980 |
981 | #header h1 {
982 | font-size: 2.5em;
983 | }
984 |
985 | #header p {
986 | font-size: 1em;
987 | }
988 |
989 | #header nav {
990 | font-size: 1em;
991 | }
992 |
993 | #header nav a:hover {
994 | font-size: 1em;
995 | }
996 |
997 | #header nav a:active {
998 | font-size: 1em;
999 | } }
1000 |
1001 | /* Mobile (Portrait) */
1002 |
1003 | @media screen and (max-width: 480px) {
1004 |
1005 | /* BG */
1006 |
1007 | @-moz-keyframes bg {
1008 | 0% {
1009 | -moz-transform: translate3d(0,0,0);
1010 | -webkit-transform: translate3d(0,0,0);
1011 | -ms-transform: translate3d(0,0,0);
1012 | transform: translate3d(0,0,0);
1013 | }
1014 |
1015 | 100% {
1016 | -moz-transform: translate3d(-412.5px,0,0);
1017 | -webkit-transform: translate3d(-412.5px,0,0);
1018 | -ms-transform: translate3d(-412.5px,0,0);
1019 | transform: translate3d(-412.5px,0,0);
1020 | }
1021 |
1022 | }
1023 |
1024 | @-webkit-keyframes bg {
1025 | 0% {
1026 | -moz-transform: translate3d(0,0,0);
1027 | -webkit-transform: translate3d(0,0,0);
1028 | -ms-transform: translate3d(0,0,0);
1029 | transform: translate3d(0,0,0);
1030 | }
1031 |
1032 | 100% {
1033 | -moz-transform: translate3d(-412.5px,0,0);
1034 | -webkit-transform: translate3d(-412.5px,0,0);
1035 | -ms-transform: translate3d(-412.5px,0,0);
1036 | transform: translate3d(-412.5px,0,0);
1037 | }
1038 | }
1039 |
1040 | @-ms-keyframes bg {
1041 | 0% {
1042 | -moz-transform: translate3d(0,0,0);
1043 | -webkit-transform: translate3d(0,0,0);
1044 | -ms-transform: translate3d(0,0,0);
1045 | transform: translate3d(0,0,0);
1046 | }
1047 |
1048 | 100% {
1049 | -moz-transform: translate3d(-412.5px,0,0);
1050 | -webkit-transform: translate3d(-412.5px,0,0);
1051 | -ms-transform: translate3d(-412.5px,0,0);
1052 | transform: translate3d(-412.5px,0,0);
1053 | }
1054 | }
1055 |
1056 | @keyframes bg {
1057 | 0% {
1058 | -moz-transform: translate3d(0,0,0);
1059 | -webkit-transform: translate3d(0,0,0);
1060 | -ms-transform: translate3d(0,0,0);
1061 | transform: translate3d(0,0,0);
1062 | }
1063 |
1064 | 100% {
1065 | -moz-transform: translate3d(-412.5px,0,0);
1066 | -webkit-transform: translate3d(-412.5px,0,0);
1067 | -ms-transform: translate3d(-412.5px,0,0);
1068 | transform: translate3d(-412.5px,0,0);
1069 | }
1070 | }
1071 |
1072 | #bg {
1073 | background-size: 412.5px auto;
1074 | width: 1237.5px;
1075 | }
1076 |
1077 | /* Header */
1078 |
1079 | #header nav {
1080 | padding: 0 1em;
1081 | } }
--------------------------------------------------------------------------------
/public/css/noscript.css:
--------------------------------------------------------------------------------
1 | /*
2 | Aerial by HTML5 UP
3 | html5up.net | @ajlkn
4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
5 | */
6 |
7 | /* Wrapper */
8 |
9 | #wrapper {
10 | opacity: 1 !important;
11 | }
12 |
13 | /* Overlay */
14 |
15 | #overlay {
16 | opacity: 1 !important;
17 | }
18 |
19 | /* Header */
20 |
21 | #header {
22 | opacity: 1 !important;
23 | }
24 |
25 | #header nav li {
26 | opacity: 1 !important;
27 | }
--------------------------------------------------------------------------------
/public/download/GinsMoocExtension_v2.0.2.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/download/GinsMoocExtension_v2.0.2.zip
--------------------------------------------------------------------------------
/public/extension-auto-evaluate-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/extension-auto-evaluate-1.png
--------------------------------------------------------------------------------
/public/extension-auto-evaluate-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/extension-auto-evaluate-2.png
--------------------------------------------------------------------------------
/public/extension-completion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/extension-completion.png
--------------------------------------------------------------------------------
/public/extension-developer-mode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/extension-developer-mode.png
--------------------------------------------------------------------------------
/public/extension-homework.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/extension-homework.png
--------------------------------------------------------------------------------
/public/extension-load-decompression.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/extension-load-decompression.png
--------------------------------------------------------------------------------
/public/extension-multiple-choice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/extension-multiple-choice.png
--------------------------------------------------------------------------------
/public/extension-single-choice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/extension-single-choice.png
--------------------------------------------------------------------------------
/public/extension-updating.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/extension-updating.png
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/favicon.png
--------------------------------------------------------------------------------
/public/guess.java:
--------------------------------------------------------------------------------
1 | package top.ginnnnnn.mooc.service.implement;
2 |
3 | import java.security.SecureRandom;
4 | import java.util.HashMap;
5 |
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.data.redis.core.HashOperations;
8 | import org.springframework.data.redis.core.StringRedisTemplate;
9 | import org.springframework.stereotype.Service;
10 |
11 | import top.ginnnnnn.mooc.service.GameService;
12 |
13 | @Service
14 | public class GameServiceImpl implements GameService {
15 |
16 | private static final SecureRandom RNG = new SecureRandom();
17 | private static final String GUESS_GAME_PREFIX = "GinsGame-guess-";
18 | @Autowired
19 | private StringRedisTemplate stringRedisTemplate;
20 |
21 | public HashMap guess(String token, Double guess) {
22 | final String key = GUESS_GAME_PREFIX + token;
23 | init(key);
24 | HashOperations hashOperations = stringRedisTemplate.opsForHash();
25 |
26 | Double number = Double.valueOf((String)hashOperations.get(key, "number"));
27 | boolean isLess = number > guess + 1e-6 / 2;
28 | boolean isMore = number < guess - 1e-6 / 2;
29 |
30 | boolean isPassed = !isLess && !isMore;
31 | boolean isTalented = isPassed && !hashOperations.hasKey(key, "previous");
32 |
33 | HashMap state = new HashMap<>(5);
34 | state.put("less", isLess);
35 | state.put("more", isMore);
36 |
37 | hashOperations.putIfAbsent(key, "previous", "0");
38 | hashOperations.increment(key, "previous", 1);
39 | if (isPassed) {
40 | hashOperations.put(key, "number", String.format("%f", RNG.nextInt(1, 1000000) * 1e-6));
41 | hashOperations.delete(key, "previous");
42 | hashOperations.increment(key, "passed", 1);
43 | }
44 | if (isTalented) {
45 | hashOperations.increment(key, "talented", 1);
46 | state.put("reward", Boolean.TRUE);
47 | } else {
48 | state.put("reward", Boolean.FALSE);
49 | }
50 |
51 | HashMap ret = new HashMap<>(3);
52 | ret.put("info", infoBuild(key));
53 | ret.put("state", state);
54 | return ret;
55 |
56 | }
57 |
58 | public HashMap getState(String token) {
59 | final String key = GUESS_GAME_PREFIX + token;
60 | init(key);
61 | return infoBuild(key);
62 | }
63 |
64 | public boolean refresh(String token) {
65 | final String key = GUESS_GAME_PREFIX + token;
66 | init(key);
67 | HashOperations hashOperations = stringRedisTemplate.opsForHash();
68 |
69 | hashOperations.put(key, "passed", "0");
70 | hashOperations.put(key, "talented", "0");
71 | hashOperations.put(key, "number", String.format("%f", RNG.nextInt(1, 1000000) * 1e-6));
72 | hashOperations.delete(key, "previous");
73 | return true;
74 | }
75 |
76 | private void init(String key) {
77 | HashOperations hashOperations = stringRedisTemplate.opsForHash();
78 | hashOperations.putIfAbsent(key, "passed", "0");
79 | hashOperations.putIfAbsent(key, "talented", "0");
80 | hashOperations.putIfAbsent(key, "number", String.format("%f", RNG.nextInt(1, 1000000) * 1e-6));
81 | }
82 |
83 | private HashMap infoBuild(String key) {
84 | HashOperations hashOperations = stringRedisTemplate.opsForHash();
85 | HashMap info = new HashMap<>(3);
86 | info.put("passed", hashOperations.get(key, "passed"));
87 | info.put("talented", hashOperations.get(key, "talented"));
88 | return info;
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/public/headicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/headicon.png
--------------------------------------------------------------------------------
/public/new-course-help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/new-course-help.png
--------------------------------------------------------------------------------
/public/question.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/question.png
--------------------------------------------------------------------------------
/public/sass/libs/_breakpoints.scss:
--------------------------------------------------------------------------------
1 | // breakpoints.scss v1.0 | @ajlkn | MIT licensed */
2 |
3 | // Vars.
4 |
5 | /// Breakpoints.
6 | /// @var {list}
7 | $breakpoints: () !global;
8 |
9 | // Mixins.
10 |
11 | /// Sets breakpoints.
12 | /// @param {map} $x Breakpoints.
13 | @mixin breakpoints($x: ()) {
14 | $breakpoints: $x !global;
15 | }
16 |
17 | /// Wraps @content in a @media block targeting a specific orientation.
18 | /// @param {string} $orientation Orientation.
19 | @mixin orientation($orientation) {
20 | @media screen and (orientation: #{$orientation}) {
21 | @content;
22 | }
23 | }
24 |
25 | /// Wraps @content in a @media block using a given query.
26 | /// @param {string} $query Query.
27 | @mixin breakpoint($query: null) {
28 |
29 | $breakpoint: null;
30 | $op: null;
31 | $media: null;
32 |
33 | // Determine operator, breakpoint.
34 |
35 | // Greater than or equal.
36 | @if (str-slice($query, 0, 2) == '>=') {
37 |
38 | $op: 'gte';
39 | $breakpoint: str-slice($query, 3);
40 |
41 | }
42 |
43 | // Less than or equal.
44 | @elseif (str-slice($query, 0, 2) == '<=') {
45 |
46 | $op: 'lte';
47 | $breakpoint: str-slice($query, 3);
48 |
49 | }
50 |
51 | // Greater than.
52 | @elseif (str-slice($query, 0, 1) == '>') {
53 |
54 | $op: 'gt';
55 | $breakpoint: str-slice($query, 2);
56 |
57 | }
58 |
59 | // Less than.
60 | @elseif (str-slice($query, 0, 1) == '<') {
61 |
62 | $op: 'lt';
63 | $breakpoint: str-slice($query, 2);
64 |
65 | }
66 |
67 | // Not.
68 | @elseif (str-slice($query, 0, 1) == '!') {
69 |
70 | $op: 'not';
71 | $breakpoint: str-slice($query, 2);
72 |
73 | }
74 |
75 | // Equal.
76 | @else {
77 |
78 | $op: 'eq';
79 | $breakpoint: $query;
80 |
81 | }
82 |
83 | // Build media.
84 | @if ($breakpoint and map-has-key($breakpoints, $breakpoint)) {
85 |
86 | $a: map-get($breakpoints, $breakpoint);
87 |
88 | // Range.
89 | @if (type-of($a) == 'list') {
90 |
91 | $x: nth($a, 1);
92 | $y: nth($a, 2);
93 |
94 | // Max only.
95 | @if ($x == null) {
96 |
97 | // Greater than or equal (>= 0 / anything)
98 | @if ($op == 'gte') {
99 | $media: 'screen';
100 | }
101 |
102 | // Less than or equal (<= y)
103 | @elseif ($op == 'lte') {
104 | $media: 'screen and (max-width: ' + $y + ')';
105 | }
106 |
107 | // Greater than (> y)
108 | @elseif ($op == 'gt') {
109 | $media: 'screen and (min-width: ' + ($y + 1) + ')';
110 | }
111 |
112 | // Less than (< 0 / invalid)
113 | @elseif ($op == 'lt') {
114 | $media: 'screen and (max-width: -1px)';
115 | }
116 |
117 | // Not (> y)
118 | @elseif ($op == 'not') {
119 | $media: 'screen and (min-width: ' + ($y + 1) + ')';
120 | }
121 |
122 | // Equal (<= y)
123 | @else {
124 | $media: 'screen and (max-width: ' + $y + ')';
125 | }
126 |
127 | }
128 |
129 | // Min only.
130 | @else if ($y == null) {
131 |
132 | // Greater than or equal (>= x)
133 | @if ($op == 'gte') {
134 | $media: 'screen and (min-width: ' + $x + ')';
135 | }
136 |
137 | // Less than or equal (<= inf / anything)
138 | @elseif ($op == 'lte') {
139 | $media: 'screen';
140 | }
141 |
142 | // Greater than (> inf / invalid)
143 | @elseif ($op == 'gt') {
144 | $media: 'screen and (max-width: -1px)';
145 | }
146 |
147 | // Less than (< x)
148 | @elseif ($op == 'lt') {
149 | $media: 'screen and (max-width: ' + ($x - 1) + ')';
150 | }
151 |
152 | // Not (< x)
153 | @elseif ($op == 'not') {
154 | $media: 'screen and (max-width: ' + ($x - 1) + ')';
155 | }
156 |
157 | // Equal (>= x)
158 | @else {
159 | $media: 'screen and (min-width: ' + $x + ')';
160 | }
161 |
162 | }
163 |
164 | // Min and max.
165 | @else {
166 |
167 | // Greater than or equal (>= x)
168 | @if ($op == 'gte') {
169 | $media: 'screen and (min-width: ' + $x + ')';
170 | }
171 |
172 | // Less than or equal (<= y)
173 | @elseif ($op == 'lte') {
174 | $media: 'screen and (max-width: ' + $y + ')';
175 | }
176 |
177 | // Greater than (> y)
178 | @elseif ($op == 'gt') {
179 | $media: 'screen and (min-width: ' + ($y + 1) + ')';
180 | }
181 |
182 | // Less than (< x)
183 | @elseif ($op == 'lt') {
184 | $media: 'screen and (max-width: ' + ($x - 1) + ')';
185 | }
186 |
187 | // Not (< x and > y)
188 | @elseif ($op == 'not') {
189 | $media: 'screen and (max-width: ' + ($x - 1) + '), screen and (min-width: ' + ($y + 1) + ')';
190 | }
191 |
192 | // Equal (>= x and <= y)
193 | @else {
194 | $media: 'screen and (min-width: ' + $x + ') and (max-width: ' + $y + ')';
195 | }
196 |
197 | }
198 |
199 | }
200 |
201 | // String.
202 | @else {
203 |
204 | // Missing a media type? Prefix with "screen".
205 | @if (str-slice($a, 0, 1) == '(') {
206 | $media: 'screen and ' + $a;
207 | }
208 |
209 | // Otherwise, use as-is.
210 | @else {
211 | $media: $a;
212 | }
213 |
214 | }
215 |
216 | }
217 |
218 | // Output.
219 | @media #{$media} {
220 | @content;
221 | }
222 |
223 | }
--------------------------------------------------------------------------------
/public/sass/libs/_functions.scss:
--------------------------------------------------------------------------------
1 | /// Removes a specific item from a list.
2 | /// @author Hugo Giraudel
3 | /// @param {list} $list List.
4 | /// @param {integer} $index Index.
5 | /// @return {list} Updated list.
6 | @function remove-nth($list, $index) {
7 |
8 | $result: null;
9 |
10 | @if type-of($index) != number {
11 | @warn "$index: #{quote($index)} is not a number for `remove-nth`.";
12 | }
13 | @else if $index == 0 {
14 | @warn "List index 0 must be a non-zero integer for `remove-nth`.";
15 | }
16 | @else if abs($index) > length($list) {
17 | @warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`.";
18 | }
19 | @else {
20 |
21 | $result: ();
22 | $index: if($index < 0, length($list) + $index + 1, $index);
23 |
24 | @for $i from 1 through length($list) {
25 |
26 | @if $i != $index {
27 | $result: append($result, nth($list, $i));
28 | }
29 |
30 | }
31 |
32 | }
33 |
34 | @return $result;
35 |
36 | }
37 |
38 | /// Gets a value from a map.
39 | /// @author Hugo Giraudel
40 | /// @param {map} $map Map.
41 | /// @param {string} $keys Key(s).
42 | /// @return {string} Value.
43 | @function val($map, $keys...) {
44 |
45 | @if nth($keys, 1) == null {
46 | $keys: remove-nth($keys, 1);
47 | }
48 |
49 | @each $key in $keys {
50 | $map: map-get($map, $key);
51 | }
52 |
53 | @return $map;
54 |
55 | }
56 |
57 | /// Gets a duration value.
58 | /// @param {string} $keys Key(s).
59 | /// @return {string} Value.
60 | @function _duration($keys...) {
61 | @return val($duration, $keys...);
62 | }
63 |
64 | /// Gets a font value.
65 | /// @param {string} $keys Key(s).
66 | /// @return {string} Value.
67 | @function _font($keys...) {
68 | @return val($font, $keys...);
69 | }
70 |
71 | /// Gets a misc value.
72 | /// @param {string} $keys Key(s).
73 | /// @return {string} Value.
74 | @function _misc($keys...) {
75 | @return val($misc, $keys...);
76 | }
77 |
78 | /// Gets a palette value.
79 | /// @param {string} $keys Key(s).
80 | /// @return {string} Value.
81 | @function _palette($keys...) {
82 | @return val($palette, $keys...);
83 | }
84 |
85 | /// Gets a size value.
86 | /// @param {string} $keys Key(s).
87 | /// @return {string} Value.
88 | @function _size($keys...) {
89 | @return val($size, $keys...);
90 | }
--------------------------------------------------------------------------------
/public/sass/libs/_mixins.scss:
--------------------------------------------------------------------------------
1 | /// Makes an element's :before pseudoelement a FontAwesome icon.
2 | /// @param {string} $content Optional content value to use.
3 | /// @param {string} $category Optional category to use.
4 | /// @param {string} $where Optional pseudoelement to target (before or after).
5 | @mixin icon($content: false, $category: regular, $where: before) {
6 |
7 | text-decoration: none;
8 |
9 | &:#{$where} {
10 |
11 | @if $content {
12 | content: $content;
13 | }
14 |
15 | -moz-osx-font-smoothing: grayscale;
16 | -webkit-font-smoothing: antialiased;
17 | display: inline-block;
18 | font-style: normal;
19 | font-variant: normal;
20 | text-rendering: auto;
21 | line-height: 1;
22 | text-transform: none !important;
23 |
24 | @if ($category == brands) {
25 | font-family: 'Font Awesome 5 Brands';
26 | }
27 | @elseif ($category == solid) {
28 | font-family: 'Font Awesome 5 Free';
29 | font-weight: 900;
30 | }
31 | @else {
32 | font-family: 'Font Awesome 5 Free';
33 | font-weight: 400;
34 | }
35 |
36 | }
37 |
38 | }
39 |
40 | /// Applies padding to an element, taking the current element-margin value into account.
41 | /// @param {mixed} $tb Top/bottom padding.
42 | /// @param {mixed} $lr Left/right padding.
43 | /// @param {list} $pad Optional extra padding (in the following order top, right, bottom, left)
44 | /// @param {bool} $important If true, adds !important.
45 | @mixin padding($tb, $lr, $pad: (0,0,0,0), $important: null) {
46 |
47 | @if $important {
48 | $important: '!important';
49 | }
50 |
51 | $x: 0.1em;
52 |
53 | @if unit(_size(element-margin)) == 'rem' {
54 | $x: 0.1rem;
55 | }
56 |
57 | padding: ($tb + nth($pad,1)) ($lr + nth($pad,2)) max($x, $tb - _size(element-margin) + nth($pad,3)) ($lr + nth($pad,4)) #{$important};
58 |
59 | }
60 |
61 | /// Encodes a SVG data URL so IE doesn't choke (via codepen.io/jakob-e/pen/YXXBrp).
62 | /// @param {string} $svg SVG data URL.
63 | /// @return {string} Encoded SVG data URL.
64 | @function svg-url($svg) {
65 |
66 | $svg: str-replace($svg, '"', '\'');
67 | $svg: str-replace($svg, '%', '%25');
68 | $svg: str-replace($svg, '<', '%3C');
69 | $svg: str-replace($svg, '>', '%3E');
70 | $svg: str-replace($svg, '&', '%26');
71 | $svg: str-replace($svg, '#', '%23');
72 | $svg: str-replace($svg, '{', '%7B');
73 | $svg: str-replace($svg, '}', '%7D');
74 | $svg: str-replace($svg, ';', '%3B');
75 |
76 | @return url("data:image/svg+xml;charset=utf8,#{$svg}");
77 |
78 | }
--------------------------------------------------------------------------------
/public/sass/libs/_vars.scss:
--------------------------------------------------------------------------------
1 | // Misc.
2 | $misc: (
3 | bg: #348cb2 url("images/bg.jpg") bottom left,
4 | bg-width: 1500px
5 | );
6 |
7 | // Duration.
8 | $duration: (
9 | bg: 60s,
10 | wrapper: 3s,
11 | overlay: 1.5s,
12 | header: 1s,
13 | nav-icons: 0.5s
14 | );
15 |
16 | // Size.
17 | $size: (
18 | nav-icon-wrapper: 5.35em,
19 | nav-icon: 1.75em
20 | );
21 |
22 | // Font.
23 | $font: (
24 | );
25 |
26 | // Palette.
27 | $palette: (
28 | bg: #fff,
29 | fg: #fff,
30 |
31 | nav-icon: (
32 | hover-bg: rgba(255,255,255,0.175),
33 | hover-fg: #fff,
34 | active-bg: rgba(255,255,255,0.35),
35 | active-fg: #fff
36 | )
37 | );
--------------------------------------------------------------------------------
/public/sass/libs/_vendor.scss:
--------------------------------------------------------------------------------
1 | // vendor.scss v1.0 | @ajlkn | MIT licensed */
2 |
3 | // Vars.
4 |
5 | /// Vendor prefixes.
6 | /// @var {list}
7 | $vendor-prefixes: (
8 | '-moz-',
9 | '-webkit-',
10 | '-ms-',
11 | ''
12 | );
13 |
14 | /// Properties that should be vendorized.
15 | /// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org
16 | /// @var {list}
17 | $vendor-properties: (
18 |
19 | // Animation.
20 | 'animation',
21 | 'animation-delay',
22 | 'animation-direction',
23 | 'animation-duration',
24 | 'animation-fill-mode',
25 | 'animation-iteration-count',
26 | 'animation-name',
27 | 'animation-play-state',
28 | 'animation-timing-function',
29 |
30 | // Appearance.
31 | 'appearance',
32 |
33 | // Backdrop filter.
34 | 'backdrop-filter',
35 |
36 | // Background image options.
37 | 'background-clip',
38 | 'background-origin',
39 | 'background-size',
40 |
41 | // Box sizing.
42 | 'box-sizing',
43 |
44 | // Clip path.
45 | 'clip-path',
46 |
47 | // Filter effects.
48 | 'filter',
49 |
50 | // Flexbox.
51 | 'align-content',
52 | 'align-items',
53 | 'align-self',
54 | 'flex',
55 | 'flex-basis',
56 | 'flex-direction',
57 | 'flex-flow',
58 | 'flex-grow',
59 | 'flex-shrink',
60 | 'flex-wrap',
61 | 'justify-content',
62 | 'order',
63 |
64 | // Font feature.
65 | 'font-feature-settings',
66 | 'font-language-override',
67 | 'font-variant-ligatures',
68 |
69 | // Font kerning.
70 | 'font-kerning',
71 |
72 | // Fragmented borders and backgrounds.
73 | 'box-decoration-break',
74 |
75 | // Grid layout.
76 | 'grid-column',
77 | 'grid-column-align',
78 | 'grid-column-end',
79 | 'grid-column-start',
80 | 'grid-row',
81 | 'grid-row-align',
82 | 'grid-row-end',
83 | 'grid-row-start',
84 | 'grid-template-columns',
85 | 'grid-template-rows',
86 |
87 | // Hyphens.
88 | 'hyphens',
89 | 'word-break',
90 |
91 | // Masks.
92 | 'mask',
93 | 'mask-border',
94 | 'mask-border-outset',
95 | 'mask-border-repeat',
96 | 'mask-border-slice',
97 | 'mask-border-source',
98 | 'mask-border-width',
99 | 'mask-clip',
100 | 'mask-composite',
101 | 'mask-image',
102 | 'mask-origin',
103 | 'mask-position',
104 | 'mask-repeat',
105 | 'mask-size',
106 |
107 | // Multicolumn.
108 | 'break-after',
109 | 'break-before',
110 | 'break-inside',
111 | 'column-count',
112 | 'column-fill',
113 | 'column-gap',
114 | 'column-rule',
115 | 'column-rule-color',
116 | 'column-rule-style',
117 | 'column-rule-width',
118 | 'column-span',
119 | 'column-width',
120 | 'columns',
121 |
122 | // Object fit.
123 | 'object-fit',
124 | 'object-position',
125 |
126 | // Regions.
127 | 'flow-from',
128 | 'flow-into',
129 | 'region-fragment',
130 |
131 | // Scroll snap points.
132 | 'scroll-snap-coordinate',
133 | 'scroll-snap-destination',
134 | 'scroll-snap-points-x',
135 | 'scroll-snap-points-y',
136 | 'scroll-snap-type',
137 |
138 | // Shapes.
139 | 'shape-image-threshold',
140 | 'shape-margin',
141 | 'shape-outside',
142 |
143 | // Tab size.
144 | 'tab-size',
145 |
146 | // Text align last.
147 | 'text-align-last',
148 |
149 | // Text decoration.
150 | 'text-decoration-color',
151 | 'text-decoration-line',
152 | 'text-decoration-skip',
153 | 'text-decoration-style',
154 |
155 | // Text emphasis.
156 | 'text-emphasis',
157 | 'text-emphasis-color',
158 | 'text-emphasis-position',
159 | 'text-emphasis-style',
160 |
161 | // Text size adjust.
162 | 'text-size-adjust',
163 |
164 | // Text spacing.
165 | 'text-spacing',
166 |
167 | // Transform.
168 | 'transform',
169 | 'transform-origin',
170 |
171 | // Transform 3D.
172 | 'backface-visibility',
173 | 'perspective',
174 | 'perspective-origin',
175 | 'transform-style',
176 |
177 | // Transition.
178 | 'transition',
179 | 'transition-delay',
180 | 'transition-duration',
181 | 'transition-property',
182 | 'transition-timing-function',
183 |
184 | // Unicode bidi.
185 | 'unicode-bidi',
186 |
187 | // User select.
188 | 'user-select',
189 |
190 | // Writing mode.
191 | 'writing-mode',
192 |
193 | );
194 |
195 | /// Values that should be vendorized.
196 | /// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org
197 | /// @var {list}
198 | $vendor-values: (
199 |
200 | // Cross fade.
201 | 'cross-fade',
202 |
203 | // Element function.
204 | 'element',
205 |
206 | // Filter function.
207 | 'filter',
208 |
209 | // Flexbox.
210 | 'flex',
211 | 'inline-flex',
212 |
213 | // Grab cursors.
214 | 'grab',
215 | 'grabbing',
216 |
217 | // Gradients.
218 | 'linear-gradient',
219 | 'repeating-linear-gradient',
220 | 'radial-gradient',
221 | 'repeating-radial-gradient',
222 |
223 | // Grid layout.
224 | 'grid',
225 | 'inline-grid',
226 |
227 | // Image set.
228 | 'image-set',
229 |
230 | // Intrinsic width.
231 | 'max-content',
232 | 'min-content',
233 | 'fit-content',
234 | 'fill',
235 | 'fill-available',
236 | 'stretch',
237 |
238 | // Sticky position.
239 | 'sticky',
240 |
241 | // Transform.
242 | 'transform',
243 |
244 | // Zoom cursors.
245 | 'zoom-in',
246 | 'zoom-out',
247 |
248 | );
249 |
250 | // Functions.
251 |
252 | /// Removes a specific item from a list.
253 | /// @author Hugo Giraudel
254 | /// @param {list} $list List.
255 | /// @param {integer} $index Index.
256 | /// @return {list} Updated list.
257 | @function remove-nth($list, $index) {
258 |
259 | $result: null;
260 |
261 | @if type-of($index) != number {
262 | @warn "$index: #{quote($index)} is not a number for `remove-nth`.";
263 | }
264 | @else if $index == 0 {
265 | @warn "List index 0 must be a non-zero integer for `remove-nth`.";
266 | }
267 | @else if abs($index) > length($list) {
268 | @warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`.";
269 | }
270 | @else {
271 |
272 | $result: ();
273 | $index: if($index < 0, length($list) + $index + 1, $index);
274 |
275 | @for $i from 1 through length($list) {
276 |
277 | @if $i != $index {
278 | $result: append($result, nth($list, $i));
279 | }
280 |
281 | }
282 |
283 | }
284 |
285 | @return $result;
286 |
287 | }
288 |
289 | /// Replaces a substring within another string.
290 | /// @author Hugo Giraudel
291 | /// @param {string} $string String.
292 | /// @param {string} $search Substring.
293 | /// @param {string} $replace Replacement.
294 | /// @return {string} Updated string.
295 | @function str-replace($string, $search, $replace: '') {
296 |
297 | $index: str-index($string, $search);
298 |
299 | @if $index {
300 | @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
301 | }
302 |
303 | @return $string;
304 |
305 | }
306 |
307 | /// Replaces a substring within each string in a list.
308 | /// @param {list} $strings List of strings.
309 | /// @param {string} $search Substring.
310 | /// @param {string} $replace Replacement.
311 | /// @return {list} Updated list of strings.
312 | @function str-replace-all($strings, $search, $replace: '') {
313 |
314 | @each $string in $strings {
315 | $strings: set-nth($strings, index($strings, $string), str-replace($string, $search, $replace));
316 | }
317 |
318 | @return $strings;
319 |
320 | }
321 |
322 | // Mixins.
323 |
324 | /// Wraps @content in vendorized keyframe blocks.
325 | /// @param {string} $name Name.
326 | @mixin keyframes($name) {
327 |
328 | @-moz-keyframes #{$name} { @content; }
329 | @-webkit-keyframes #{$name} { @content; }
330 | @-ms-keyframes #{$name} { @content; }
331 | @keyframes #{$name} { @content; }
332 |
333 | }
334 |
335 | /// Vendorizes a declaration's property and/or value(s).
336 | /// @param {string} $property Property.
337 | /// @param {mixed} $value String/list of value(s).
338 | @mixin vendor($property, $value) {
339 |
340 | // Determine if property should expand.
341 | $expandProperty: index($vendor-properties, $property);
342 |
343 | // Determine if value should expand (and if so, add '-prefix-' placeholder).
344 | $expandValue: false;
345 |
346 | @each $x in $value {
347 | @each $y in $vendor-values {
348 | @if $y == str-slice($x, 1, str-length($y)) {
349 |
350 | $value: set-nth($value, index($value, $x), '-prefix-' + $x);
351 | $expandValue: true;
352 |
353 | }
354 | }
355 | }
356 |
357 | // Expand property?
358 | @if $expandProperty {
359 | @each $vendor in $vendor-prefixes {
360 | #{$vendor}#{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
361 | }
362 | }
363 |
364 | // Expand just the value?
365 | @elseif $expandValue {
366 | @each $vendor in $vendor-prefixes {
367 | #{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
368 | }
369 | }
370 |
371 | // Neither? Treat them as a normal declaration.
372 | @else {
373 | #{$property}: #{$value};
374 | }
375 |
376 | }
--------------------------------------------------------------------------------
/public/sass/main.scss:
--------------------------------------------------------------------------------
1 | @import 'libs/vars';
2 | @import 'libs/functions';
3 | @import 'libs/mixins';
4 | @import 'libs/vendor';
5 | @import 'libs/breakpoints';
6 | @import url("https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,900");
7 | @import url("fontawesome-all.min.css");
8 |
9 | /*
10 | Aerial by HTML5 UP
11 | html5up.net | @ajlkn
12 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
13 | */
14 |
15 | // Breakpoints.
16 |
17 | @include breakpoints((
18 | wide: ( 1281px, 1680px ),
19 | normal: ( 737px, 1280px ),
20 | mobile: ( 481px, 736px ),
21 | mobilep: ( null, 480px )
22 | ));
23 |
24 | // Mixins.
25 |
26 | @mixin bg($width) {
27 | @include keyframes('bg') {
28 | 0% { @include vendor('transform', 'translate3d(0,0,0)'); }
29 | 100% { @include vendor('transform', 'translate3d(#{$width * -1},0,0)'); }
30 | }
31 |
32 | #bg {
33 | background-size: $width auto;
34 | width: ($width * 3);
35 | }
36 | }
37 |
38 | $delay-wrapper: _duration(wrapper) - 1s;
39 | $delay-overlay: $delay-wrapper - 0.5s;
40 | $delay-header: $delay-overlay + _duration(overlay) - 0.75s;
41 | $delay-nav-icons: $delay-header + _duration(header) - 1s;
42 | $delay-nav-icon: 0.25s;
43 |
44 | // Reset.
45 | // Based on meyerweb.com/eric/tools/css/reset (v2.0 | 20110126 | License: public domain)
46 |
47 | html, body, div, span, applet, object,
48 | iframe, h1, h2, h3, h4, h5, h6, p, blockquote,
49 | pre, a, abbr, acronym, address, big, cite,
50 | code, del, dfn, em, img, ins, kbd, q, s, samp,
51 | small, strike, strong, sub, sup, tt, var, b,
52 | u, i, center, dl, dt, dd, ol, ul, li, fieldset,
53 | form, label, legend, table, caption, tbody,
54 | tfoot, thead, tr, th, td, article, aside,
55 | canvas, details, embed, figure, figcaption,
56 | footer, header, hgroup, menu, nav, output, ruby,
57 | section, summary, time, mark, audio, video {
58 | margin: 0;
59 | padding: 0;
60 | border: 0;
61 | font-size: 100%;
62 | font: inherit;
63 | vertical-align: baseline;
64 | }
65 |
66 | article, aside, details, figcaption, figure,
67 | footer, header, hgroup, menu, nav, section {
68 | display: block;
69 | }
70 |
71 | body {
72 | line-height: 1;
73 | }
74 |
75 | ol, ul {
76 | list-style:none;
77 | }
78 |
79 | blockquote, q {
80 | quotes: none;
81 |
82 | &:before,
83 | &:after {
84 | content: '';
85 | content: none;
86 | }
87 | }
88 |
89 | table {
90 | border-collapse: collapse;
91 | border-spacing: 0;
92 | }
93 |
94 | body {
95 | -webkit-text-size-adjust: none;
96 | }
97 |
98 | mark {
99 | background-color: transparent;
100 | color: inherit;
101 | }
102 |
103 | input::-moz-focus-inner {
104 | border: 0;
105 | padding: 0;
106 | }
107 |
108 | input, select, textarea {
109 | -moz-appearance: none;
110 | -webkit-appearance: none;
111 | -ms-appearance: none;
112 | appearance: none;
113 | }
114 |
115 | /* Basic */
116 |
117 | // Set box model to border-box.
118 | // Based on css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice
119 | html {
120 | box-sizing: border-box;
121 | }
122 |
123 | *, *:before, *:after {
124 | box-sizing: inherit;
125 | }
126 |
127 | body {
128 | background: _palette(bg);
129 | overflow: hidden;
130 |
131 | // Stops initial animations until page loads.
132 | &.is-preload {
133 | *, *:before, *:after {
134 | @include vendor('animation', 'none !important');
135 | @include vendor('transition', 'none !important');
136 | }
137 | }
138 |
139 | }
140 |
141 | body, input, select, textarea {
142 | color: _palette(fg);
143 | font-family: 'Source Sans Pro', sans-serif;
144 | font-size: 15pt;
145 | font-weight: 300 !important;
146 | letter-spacing: -0.025em;
147 | line-height: 1.75em;
148 | }
149 |
150 | a {
151 | @include vendor('transition', 'border-color 0.2s ease-in-out');
152 | border-bottom: dotted 1px;
153 | color: inherit;
154 | outline: 0;
155 | text-decoration: none;
156 |
157 | &:hover {
158 | border-color: transparent;
159 | }
160 | }
161 |
162 | /* Icon */
163 |
164 | .icon {
165 | @include icon;
166 | position: relative;
167 |
168 | > .label {
169 | display: none;
170 | }
171 |
172 | &.solid {
173 | &:before {
174 | font-weight: 900;
175 | }
176 | }
177 |
178 | &.brands {
179 | &:before {
180 | font-family: 'Font Awesome 5 Brands';
181 | }
182 | }
183 | }
184 |
185 | /* Wrapper */
186 |
187 | @include keyframes('wrapper') {
188 | 0% { opacity: 0; }
189 | 100% { opacity: 1; }
190 | }
191 |
192 | #wrapper {
193 | @include vendor('animation', 'wrapper #{_duration(wrapper)} forwards');
194 | height: 100%;
195 | left: 0;
196 | opacity: 0;
197 | position: fixed;
198 | top: 0;
199 | width: 100%;
200 | }
201 |
202 | /* BG */
203 |
204 | #bg {
205 | @include vendor('animation', 'bg #{_duration(bg)} linear infinite');
206 | @include vendor('backface-visibility', 'hidden');
207 | @include vendor('transform', 'translate3d(0,0,0)');
208 |
209 | /* Set your background with this */
210 | background: _misc(bg);
211 |
212 | background-repeat: repeat-x;
213 | height: 100%;
214 | left: 0;
215 | opacity: 1;
216 | position: fixed;
217 | top: 0;
218 | }
219 |
220 | @include bg(_misc(bg-width) * 1.5);
221 |
222 | /* Overlay */
223 |
224 | @include keyframes('overlay') {
225 | 0% { opacity: 0; }
226 | 100% { opacity: 1; }
227 | }
228 |
229 | #overlay {
230 | @include vendor('animation', 'overlay #{_duration(overlay)} #{$delay-overlay} forwards');
231 | background-attachment: fixed, fixed;
232 | background-image: url('images/overlay-pattern.png'), url('images/overlay.svg');
233 | background-position: top left, center center;
234 | background-repeat: repeat, no-repeat;
235 | background-size: auto, cover;
236 | height: 100%;
237 | left: 0;
238 | opacity: 0;
239 | position: fixed;
240 | top: 0;
241 | width: 100%;
242 | }
243 |
244 | /* Main */
245 |
246 | #main {
247 | height: 100%;
248 | left: 0;
249 | position: fixed;
250 | text-align: center;
251 | top: 0;
252 | width: 100%;
253 |
254 | &:before {
255 | content: '';
256 | display: inline-block;
257 | height: 100%;
258 | margin-right: 0;
259 | vertical-align: middle;
260 | width: 1px;
261 | }
262 | }
263 |
264 | /* Header */
265 |
266 | @include keyframes('header') {
267 | 0% { @include vendor('transform', 'translate3d(0,1em,0)'); opacity: 0; }
268 | 100% { @include vendor('transform', 'translate3d(0,0,0)'); opacity: 1; }
269 | }
270 |
271 | @include keyframes('nav-icons') {
272 | 0% { @include vendor('transform', 'translate3d(0,1em,0)'); opacity: 0; }
273 | 100% { @include vendor('transform', 'translate3d(0,0,0)'); opacity: 1; }
274 | }
275 |
276 | #header {
277 | @include vendor('animation', 'header #{_duration(header)} #{$delay-header} forwards');
278 | @include vendor('backface-visibility', 'hidden');
279 | @include vendor('transform', 'translate3d(0,0,0)');
280 | cursor: default;
281 | display: inline-block;
282 | opacity: 0;
283 | position: relative;
284 | text-align: center;
285 | top: -1em;
286 | vertical-align: middle;
287 | width: 90%;
288 |
289 | h1 {
290 | font-size: 4.35em;
291 | font-weight: 900;
292 | letter-spacing: -0.035em;
293 | line-height: 1em;
294 | }
295 |
296 | p {
297 | font-size: 1.25em;
298 | margin: 0.75em 0 0.25em 0;
299 | opacity: 0.75;
300 | }
301 |
302 | nav {
303 | margin: 1.5em 0 0 0;
304 |
305 | li {
306 | @include vendor('animation', 'nav-icons #{_duration(nav-icons)} ease-in-out forwards');
307 | @include vendor('backface-visibility', 'hidden');
308 | @include vendor('transform', 'translate3d(0,0,0)');
309 | display: inline-block;
310 | height: _size(nav-icon-wrapper);
311 | line-height: _size(nav-icon-wrapper) * 1.1;
312 | opacity: 0;
313 | position: relative;
314 | top: 0;
315 | width: _size(nav-icon-wrapper);
316 |
317 | @for $x from 1 through 10 {
318 | &:nth-child(#{$x}) {
319 | @include vendor('animation-delay', ($delay-nav-icons + ($x * $delay-nav-icon)) + '');
320 | }
321 | }
322 | }
323 |
324 | a {
325 | -webkit-tap-highlight-color: rgba(0,0,0,0);
326 | -webkit-touch-callout: none;
327 | border: 0;
328 | display: inline-block;
329 |
330 | &:before {
331 | @include vendor('transition', 'all 0.2s ease-in-out');
332 | border-radius: 100%;
333 | border: solid 1px _palette(fg);
334 | display: block;
335 | font-size: _size(nav-icon);
336 | height: 2.5em;
337 | line-height: 2.5em;
338 | position: relative;
339 | text-align: center;
340 | top: 0;
341 | width: 2.5em;
342 | }
343 |
344 | &:hover {
345 | font-size: 1.1em;
346 |
347 | &:before {
348 | background-color: _palette(nav-icon, hover-bg);
349 | color: _palette(nav-icon, hover-fg);
350 | }
351 | }
352 |
353 | &:active {
354 | font-size: 0.95em;
355 | background: none;
356 |
357 | &:before {
358 | background-color: _palette(nav-icon, active-bg);
359 | color: _palette(nav-icon, active-fg);
360 | }
361 | }
362 |
363 | span {
364 | display: none;
365 | }
366 | }
367 | }
368 | }
369 |
370 | /* Footer */
371 |
372 | #footer {
373 | @include vendor('background-image', 'linear-gradient(top, rgba(0,0,0,0), rgba(0,0,0,0.5) 75%)');
374 | bottom: 0;
375 | cursor: default;
376 | height: 6em;
377 | left: 0;
378 | line-height: 8em;
379 | position: absolute;
380 | text-align: center;
381 | width: 100%;
382 | }
383 |
384 | /* Wide */
385 |
386 | @include breakpoint('<=wide') {
387 |
388 | /* Basic */
389 |
390 | body, input, select, textarea {
391 | font-size: 13pt;
392 | }
393 |
394 | /* BG */
395 |
396 | @include bg(_misc(bg-width));
397 |
398 | }
399 |
400 | /* Normal */
401 |
402 | @include breakpoint('<=normal') {
403 |
404 | /* Basic */
405 |
406 | body, input, select, textarea {
407 | font-size: 12pt;
408 | }
409 |
410 | /* BG */
411 |
412 | @include bg(_misc(bg-width) * 0.5);
413 |
414 | }
415 |
416 | /* Mobile */
417 |
418 | @include breakpoint('<=mobile') {
419 |
420 | /* Basic */
421 |
422 | body {
423 | min-width: 320px;
424 | }
425 |
426 | body, input, select, textarea {
427 | font-size: 11pt;
428 | }
429 |
430 | /* BG */
431 |
432 | @include bg(_misc(bg-width) * 0.2);
433 |
434 | /* Header */
435 |
436 | #header {
437 | h1 {
438 | font-size: 2.5em;
439 | }
440 |
441 | p {
442 | font-size: 1em;
443 | }
444 |
445 | nav {
446 | font-size: 1em;
447 |
448 | a {
449 | &:hover {
450 | font-size: 1em;
451 | }
452 |
453 | &:active {
454 | font-size: 1em;
455 | }
456 | }
457 | }
458 | }
459 |
460 | }
461 |
462 | /* Mobile (Portrait) */
463 |
464 | @include breakpoint('<=mobilep') {
465 |
466 | /* BG */
467 |
468 | @include bg(_misc(bg-width) * 0.275);
469 |
470 | /* Header */
471 |
472 | #header {
473 | nav {
474 | padding: 0 1em;
475 | }
476 | }
477 |
478 | }
--------------------------------------------------------------------------------
/public/sass/noscript.scss:
--------------------------------------------------------------------------------
1 | @import 'libs/vars';
2 | @import 'libs/functions';
3 | @import 'libs/mixins';
4 | @import 'libs/vendor';
5 | @import 'libs/breakpoints';
6 |
7 | /*
8 | Aerial by HTML5 UP
9 | html5up.net | @ajlkn
10 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
11 | */
12 |
13 | /* Wrapper */
14 |
15 | #wrapper {
16 | opacity: 1 !important;
17 | }
18 |
19 | /* Overlay */
20 |
21 | #overlay {
22 | opacity: 1 !important;
23 | }
24 |
25 | /* Header */
26 |
27 | #header {
28 | opacity: 1 !important;
29 |
30 | nav {
31 | li {
32 | opacity: 1 !important;
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/public/webfonts/fa-brands-400.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/webfonts/fa-brands-400.eot
--------------------------------------------------------------------------------
/public/webfonts/fa-brands-400.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/webfonts/fa-brands-400.ttf
--------------------------------------------------------------------------------
/public/webfonts/fa-brands-400.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/webfonts/fa-brands-400.woff
--------------------------------------------------------------------------------
/public/webfonts/fa-brands-400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/webfonts/fa-brands-400.woff2
--------------------------------------------------------------------------------
/public/webfonts/fa-regular-400.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/webfonts/fa-regular-400.eot
--------------------------------------------------------------------------------
/public/webfonts/fa-regular-400.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/webfonts/fa-regular-400.ttf
--------------------------------------------------------------------------------
/public/webfonts/fa-regular-400.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/webfonts/fa-regular-400.woff
--------------------------------------------------------------------------------
/public/webfonts/fa-regular-400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/webfonts/fa-regular-400.woff2
--------------------------------------------------------------------------------
/public/webfonts/fa-solid-900.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/webfonts/fa-solid-900.eot
--------------------------------------------------------------------------------
/public/webfonts/fa-solid-900.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/webfonts/fa-solid-900.ttf
--------------------------------------------------------------------------------
/public/webfonts/fa-solid-900.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/webfonts/fa-solid-900.woff
--------------------------------------------------------------------------------
/public/webfonts/fa-solid-900.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginnnnnncc/GinsMooc/0ee1a57ec764cddcb19d5554e1aedfdf0ca5518b/public/webfonts/fa-solid-900.woff2
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
28 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
129 |
130 |
175 |
--------------------------------------------------------------------------------
/src/components/CourseCard.vue:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
{{ course.name }}
19 |
{{ course.school }}
20 |
21 |
22 |
23 |
24 |
82 |
--------------------------------------------------------------------------------
/src/components/QuestionCard.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
139 |
--------------------------------------------------------------------------------
/src/components/icon/Extension.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/src/components/icon/Github.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/src/components/icon/index.ts:
--------------------------------------------------------------------------------
1 | import Github from "./Github.vue"
2 | import Extension from "./Extension.vue"
3 |
4 | export { Github, Extension }
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | import CourseCard from "./CourseCard.vue"
2 | import QuestionCard from "./QuestionCard.vue"
3 |
4 | export {CourseCard, QuestionCard}
--------------------------------------------------------------------------------
/src/components/question/Completion.vue:
--------------------------------------------------------------------------------
1 |
39 |
40 |
41 |
45 |
46 |
53 |
54 |
55 |
56 |
67 |
--------------------------------------------------------------------------------
/src/components/question/Homework.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/question/MultipleChoice.vue:
--------------------------------------------------------------------------------
1 |
39 |
40 |
41 |
45 |
46 |
47 |
57 |
58 |
59 |
60 |
61 |
75 |
--------------------------------------------------------------------------------
/src/components/question/OnlineJudge.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
23 |
24 |
25 | 描述
26 |
27 |
28 |
29 | 时间限制{{ timeLimit }}
32 | 内存限制{{ memoryLimit }}
35 |
36 |
37 |
38 |
39 |
41 |
--------------------------------------------------------------------------------
/src/components/question/SingleChoice.vue:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
37 |
38 |
39 |
49 |
50 |
51 |
52 |
53 |
70 |
--------------------------------------------------------------------------------
/src/components/question/index.ts:
--------------------------------------------------------------------------------
1 | import SingleChoice from "./SingleChoice.vue"
2 | import MultipleChoice from "./MultipleChoice.vue"
3 | import Completion from "./Completion.vue"
4 | import Homework from "./Homework.vue"
5 | import OnlineJudge from "./OnlineJudge.vue"
6 |
7 | export { SingleChoice, MultipleChoice, Completion, Homework, OnlineJudge }
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from "vue"
2 | import ElementPlus from 'element-plus'
3 | import router from "./router"
4 | import App from "./App.vue"
5 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'
6 | import 'element-plus/dist/index.css'
7 | import 'element-plus/theme-chalk/dark/css-vars.css'
8 | import 'element-plus/theme-chalk/display.css'
9 |
10 | const app = createApp(App)
11 |
12 | app.use(ElementPlus)
13 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
14 | app.component(key, component)
15 | }
16 |
17 | app.use(router)
18 |
19 | app.mount("body")
20 |
--------------------------------------------------------------------------------
/src/plugins/apiAccess.ts:
--------------------------------------------------------------------------------
1 | import type { ApiKeyType, ApiResponseType, ApiRequestType } from "../type/api"
2 | import type { App } from "vue"
3 | import { isAxiosError } from "axios"
4 | import { ElMessage } from "element-plus"
5 | import apiInfo from "../type/api"
6 | import axios from "axios"
7 |
8 | async function apiAccess(api: T): Promise
9 | async function apiAccess(
10 | api: T,
11 | params: ApiRequestType[T]["params"],
12 | data: ApiRequestType[T]["data"]
13 | ): Promise
14 |
15 | /** 函数重载 */
16 | async function apiAccess(
17 | api: T,
18 | params?: ApiRequestType[T]["params"],
19 | data?: ApiRequestType[T]["data"]
20 | ) {
21 | /** 错误处理,主要catch 404,调用者不再需要try-catch */
22 | try {
23 | return await new Promise((resolve, reject) => {
24 | /** 查询参数转动态路由参数 */
25 | let url = apiInfo[api].url
26 | if (params) {
27 | for (const [key, val] of Object.entries(params)) {
28 | const reg = RegExp(`(/):${key}(/)?`, "g")
29 | if (reg.test(url)) {
30 | url = url.replaceAll(reg, `$1${val}$2`)
31 | Reflect.deleteProperty(params, key)
32 | }
33 | }
34 | }
35 | /** 将对象转为json字符串 */
36 | if (data) {
37 | for (const [key, val] of Object.entries(data)) {
38 | if (typeof val === "object") {
39 | Reflect.set(data, key, JSON.stringify(val))
40 | }
41 | }
42 | }
43 | /** 异步发送请求 */
44 | let urlPrefix = "https://ginnnnnn.top/api"
45 | axios({
46 | url: urlPrefix + url,
47 | method: apiInfo[api].method,
48 | params: params || {},
49 | data: data || {},
50 | headers: { "Content-Type": "application/x-www-form-urlencoded" }
51 | })
52 | .then((res) => {
53 | let message = "",
54 | success = false
55 | if (res.status !== 200 || !res.data) {
56 | message = "请求出错"
57 | } else if (res.data.msg) {
58 | message = res.data.msg
59 | if (res.data.status === 200) {
60 | success = true
61 | }
62 | }
63 | if (message) {
64 | ElMessage({ message, type: success ? "success" : "error" })
65 | }
66 | resolve(res.data)
67 | })
68 | .catch((error) => {
69 | let message = error
70 | if (isAxiosError(error)) {
71 | message = error.message
72 | }
73 | ElMessage({ message, type: "error" })
74 | reject(error)
75 | })
76 | })
77 | } catch {
78 | return {}
79 | }
80 | }
81 |
82 | export const useApiAccess = () => apiAccess
83 |
84 | export default {
85 | install: (app: App) => {
86 | app.config.globalProperties.$apiAccess = apiAccess
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/plugins/tool.ts:
--------------------------------------------------------------------------------
1 | const sleep = async (ms: number) => {
2 | return new Promise((resolve) => {
3 | setTimeout(() => {
4 | resolve('');
5 | }, ms)
6 | })
7 | }
8 |
9 | export { sleep }
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from "vue-router"
2 | import { HomeView, MoocView, MoocHeader, MoocCourseDetail, MoocTest, VideoView } from "@/views"
3 | import { ElMessage } from "element-plus"
4 |
5 | const router = createRouter({
6 | history: createWebHistory(import.meta.env.BASE_URL),
7 | routes: [
8 | {
9 | path: "/",
10 | name: "Home",
11 | meta: { title: "Gins" },
12 | component: HomeView
13 | },
14 | {
15 | path: "/ginnnnnn/video",
16 | name: "Video",
17 | meta: { title: "GinsVideo" },
18 | component: VideoView
19 | },
20 | {
21 | path: "/mooc",
22 | name: "Mooc",
23 | components: {
24 | default: MoocView,
25 | header: MoocHeader
26 | },
27 | meta: { title: "GinsMooc" },
28 | children: [
29 | { path: "course/:cid", name: "MoocCourse", component: MoocCourseDetail, props: true },
30 | { path: "test/:tid", name: "MoocTest", component: MoocTest, props: true }
31 | ]
32 | }
33 | ]
34 | })
35 |
36 | router.beforeEach((to, from) => {
37 | if (!to.name) {
38 | ElMessage.error("路由错误")
39 | return { name: "Home" }
40 | }
41 | if (to.meta.title) {
42 | document.title = to.meta.title
43 | }
44 | return true
45 | })
46 |
47 | export default router
48 |
49 | const checkMoocItemId = (id: string) => /[0-9]/.test(id)
--------------------------------------------------------------------------------
/src/type/api.ts:
--------------------------------------------------------------------------------
1 | import type { course, homework, notice, quiz, test } from "./mooc"
2 |
3 | type RequestType = {
4 | params?: Object
5 | data?: Object
6 | }
7 |
8 | type Response = {
9 | status: number
10 | data: T
11 | msg: string
12 | }
13 |
14 | export interface courseList extends Object {
15 | courseList: course[]
16 | totalPages: number
17 | currentPage: number
18 | }
19 |
20 | export interface courseDetail extends Object {
21 | course: course
22 | testList: test[]
23 | }
24 |
25 | export interface testDetail extends Object {
26 | course: course
27 | test: test
28 | questionList: quiz[] | homework[]
29 | totalPages: number
30 | }
31 |
32 | const apiInfo = {
33 | getCourseList: {
34 | url: "/mooc/course",
35 | method: "GET"
36 | },
37 | getCourseDetail: {
38 | url: "/mooc/course/:cid",
39 | method: "GET"
40 | },
41 | getTestDetail: {
42 | url: "/mooc/test/:tid",
43 | method: "GET"
44 | },
45 | getNotice: {
46 | url: "/mooc/notice/website",
47 | method: "GET"
48 | }
49 | }
50 |
51 | export type ApiKeyType = keyof typeof apiInfo
52 |
53 | export interface ApiResponseType {
54 | getCourseList: Response
55 | getCourseDetail: Response
56 | getTestDetail: Response
57 | getNotice: Response
58 | }
59 |
60 | export interface ApiRequestType {
61 | getCourseList: RequestType & {
62 | params: {
63 | page?: number | string
64 | search?: string
65 | cid?: number | string
66 | }
67 | }
68 | getCourseDetail: RequestType & {
69 | params: { cid: number | string }
70 | }
71 | getTestDetail: RequestType & {
72 | params: {
73 | tid: number | string
74 | page: number | string
75 | search?: string
76 | }
77 | }
78 | getNotice: RequestType & {
79 | params: { version: string }
80 | }
81 | }
82 |
83 | export default apiInfo
84 |
--------------------------------------------------------------------------------
/src/type/globleProperties.ts:
--------------------------------------------------------------------------------
1 | import { useApiAccess } from "@/plugins/apiAccess"
2 |
3 | const apiAccess = useApiAccess()
4 | declare module 'vue' {
5 | interface ComponentCustomProperties {
6 | $apiAccess: typeof apiAccess
7 | }
8 | }
--------------------------------------------------------------------------------
/src/type/mooc.ts:
--------------------------------------------------------------------------------
1 | export enum QuestionTypeEnumList {
2 | SingleChoice = "SINGLE_CHOICE",
3 | MultipleChoice = "MULTIPLE_CHOICE",
4 | Completion = "COMPLETION",
5 | Judge = "JUDGEMENT",
6 | Homework = "HOMEWORK",
7 | OnlineJudge = "ONLINE_JUDGE"
8 | }
9 |
10 | interface course extends Object {
11 | id: number
12 | name: string
13 | school: string
14 | imageUrl: string
15 | }
16 |
17 | interface test extends Object {
18 | id: number
19 | name: string
20 | objective: boolean
21 | releaseTime: string
22 | deadline: string
23 | chapterId: number
24 | chapterName: string
25 | }
26 |
27 | interface option extends Object {
28 | id: number
29 | content: string
30 | answer: boolean
31 | }
32 |
33 | interface quiz extends Object {
34 | id: number
35 | type:
36 | | QuestionTypeEnumList.SingleChoice
37 | | QuestionTypeEnumList.MultipleChoice
38 | | QuestionTypeEnumList.Completion
39 | | QuestionTypeEnumList.Judge
40 | title: string
41 | stdAnswer: string | null
42 | optionList: option[] | null
43 | }
44 |
45 | interface homework extends Object {
46 | id: number
47 | type: QuestionTypeEnumList.Homework | QuestionTypeEnumList.OnlineJudge
48 | title: string
49 | answer: string | null
50 | description: string | null
51 | memoryLimit: number | null
52 | timeLimit: number | null
53 | }
54 |
55 | interface notice extends Object {
56 | id: number
57 | content: string
58 | }
59 |
60 | export type QuestionTypeEnum = typeof QuestionTypeEnumList
61 | export type { course, test, option, quiz, homework, notice }
62 |
--------------------------------------------------------------------------------
/src/views/BlogView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/views/HomeView.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
23 |
24 |
25 |
56 |
--------------------------------------------------------------------------------
/src/views/MoocAside.vue:
--------------------------------------------------------------------------------
1 |
153 |
154 |
155 |
156 |
157 |
158 |
160 |
163 |
164 |
165 |
166 |
173 |
174 |
176 |
177 |
179 |
182 |
183 |
184 |
185 |
187 |
188 |
189 |
190 |
191 |
192 |
194 | 添加新课程
195 |
196 |
197 |
199 |
200 |
201 | 确认
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
243 |
244 |
265 |
--------------------------------------------------------------------------------
/src/views/MoocCourseDetail.vue:
--------------------------------------------------------------------------------
1 |
52 |
53 |
54 |
55 |
56 |
{{ chapter[0].chapterName }}
57 |
58 |
59 | {{ test.name }}
60 |
61 | {{ test.releaseTime }}
62 | {{ test.deadline }}
63 |
64 |
65 |
66 |
67 |
68 |
69 |
153 |
--------------------------------------------------------------------------------
/src/views/MoocHeader.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
35 |
36 | GinsMooc Extension Helper
37 |
38 |
39 | 实现对于中国大学MOOC的
40 |
41 | - 非在线测评题的自动答案查询,包括单选题、多选题、判断题、填空题、简答题,支持测验与作业及考试
42 | - 互评阶段的自动评分、自动点评
43 |
44 | 下载地址:
45 | https://ginnnnnn.top/download/GinsMoocExtension_v2.2.0.zip
46 |
47 |
48 |
49 |
50 | 在测试的准备页面,将会自动检查是否准备就绪,若为否将自动更新课程
51 |
52 | 进入测验后,将显示“获取答案”按钮,点击即可
53 |
54 |
55 |
56 |
57 | 作业的互评阶段支持自动评分、自动点评
58 |
59 |
60 |
61 |
62 | 下载安装包后,将其解压至文件夹内
63 | 在浏览器地址栏中输入edge://extensions(谷歌浏览器为chrome://extensions)
64 | 打开开发者模式
65 |
66 | 点击“加载解压缩的扩展”,选择刚刚解压到的文件夹,即可开始使用
67 |
68 |
69 |
70 |
71 |
72 |
73 |
107 |
--------------------------------------------------------------------------------
/src/views/MoocTest.vue:
--------------------------------------------------------------------------------
1 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
132 |
--------------------------------------------------------------------------------
/src/views/MoocView.vue:
--------------------------------------------------------------------------------
1 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
68 |
69 |
70 |
73 |
74 |
75 |
83 |
84 |
86 | 显示答案
88 |
89 |
90 |
91 |
93 | 显示答案
95 |
96 |
97 |
98 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
171 |
--------------------------------------------------------------------------------
/src/views/VideoView.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
29 |
30 |
31 |
37 | {{ api.name }}
38 |
39 |
40 |
41 |
42 |
43 |
50 |
51 |
52 | 本系统只为内部交流学习,不以盈利为目的
53 | 所有资源均来源第三方资源,并不提供影片资源存储,录制、上传相关视频等,视频版权归属其合法持有人所有,本站不对使用者的行为负担任何法律责任
54 | 如果有因为本站而导致您的权益受到损害,请与我们联系,我们将理性对待,协助你解决相关问题
55 |
56 |
57 |
58 |
59 |
60 |
110 |
--------------------------------------------------------------------------------
/src/views/index.ts:
--------------------------------------------------------------------------------
1 | import HomeView from "./HomeView.vue"
2 | import MoocView from "./MoocView.vue"
3 | import MoocHeader from "./MoocHeader.vue"
4 | import MoocAside from "./MoocAside.vue"
5 | import MoocCourseDetail from "./MoocCourseDetail.vue"
6 | import MoocTest from "./MoocTest.vue"
7 | import VideoView from "./VideoView.vue"
8 |
9 | export { HomeView, MoocView, MoocHeader, MoocAside, MoocCourseDetail, MoocTest, VideoView }
--------------------------------------------------------------------------------
/tsconfig.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.node.json",
3 | "include": [
4 | "vite.config.*",
5 | "vitest.config.*",
6 | "cypress.config.*",
7 | "playwright.config.*"
8 | ],
9 | "compilerOptions": {
10 | "composite": true,
11 | "types": [
12 | "node"
13 | ]
14 | }
15 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.web.json",
3 | "include": [
4 | "env.d.ts",
5 | "src/**/*",
6 | "src/**/*.vue"
7 | ],
8 | "compilerOptions": {
9 | "baseUrl": ".",
10 | "paths": {
11 | "@/*": [
12 | "./src/*"
13 | ]
14 | },
15 | "types": ["element-plus/global"]
16 | },
17 | "references": [
18 | {
19 | "path": "./tsconfig.config.json"
20 | }
21 | ]
22 | }
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from "node:url"
2 |
3 | import { defineConfig } from "vite"
4 | import vue from "@vitejs/plugin-vue"
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [ vue() ],
9 | resolve: {
10 | alias: {
11 | "@": fileURLToPath(new URL("./src", import.meta.url))
12 | }
13 | }
14 | })
15 |
--------------------------------------------------------------------------------