├── .browserslistrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
└── workflows
│ └── gh-pages.yml
├── .gitignore
├── .npmrc
├── .vscode
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── index.html
├── locales
├── en.yml
└── zh-CN.yml
├── package.json
├── public
├── CNAME
├── audio
│ ├── fan.wav
│ └── switch.mp3
├── favicon.ico
├── favicon.png
├── img
│ └── icons
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── pwa-192x192.png
│ │ └── pwa-512x512.png
├── logo.png
└── robots.txt
├── src
├── App.vue
├── components
│ ├── BaseFooter.vue
│ ├── BaseHeader.vue
│ ├── Fan.vue
│ └── FanSwitch.vue
├── composables
│ ├── dark.ts
│ └── index.ts
├── layouts
│ └── default.vue
├── main.ts
├── modules
│ ├── gtm.ts
│ ├── i18n.ts
│ ├── nprogress.ts
│ └── pwa.ts
├── pages
│ └── index.vue
├── shim.d.ts
├── stores
│ └── index.ts
├── styles
│ ├── fan.scss
│ ├── index.scss
│ ├── main.scss
│ └── vars.scss
└── types
│ └── index.ts
├── tsconfig.json
├── vite.config.ts
└── windi.config.ts
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 8
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | public
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "@antfu"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Pages
2 | on:
3 | push:
4 | branches:
5 | - master
6 | jobs:
7 | deploy:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | with:
12 | submodules: true
13 |
14 | - name: Setup Node
15 | uses: actions/setup-node@v2
16 | with:
17 | node-version: "14.x"
18 |
19 | - run: yarn
20 | - run: yarn build
21 |
22 | - name: Deploy
23 | uses: peaceiris/actions-gh-pages@v3
24 | with:
25 | github_token: ${{ secrets.GITHUB_TOKEN }}
26 | publish_dir: ./dist
27 | force_orphan: true
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # vitesse
2 | .vite-ssg-dist
3 | .vite-ssg-temp
4 | components.d.ts
5 | auto-imports.d.ts
6 |
7 | yarn.lock
8 | package-lock.json
9 | pnpm-lock.yaml
10 |
11 | .DS_Store
12 | node_modules
13 | /dist
14 |
15 | # local env files
16 | .env.local
17 | .env.*.local
18 |
19 | # Log files
20 | *.log
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | # Editor directories and files
26 | .idea
27 | *.suo
28 | *.ntvs*
29 | *.njsproj
30 | *.sln
31 | *.sw*
32 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist=true
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "i18n-ally.keystyle": "nested",
3 | "i18n-ally.localesPaths": "locales",
4 | "i18n-ally.sortKeys": true,
5 | "prettier.enable": false,
6 | "typescript.tsdk": "node_modules/typescript/lib",
7 | "editor.codeActionsOnSave": {
8 | "source.fixAll.eslint": true,
9 | },
10 | "files.associations": {
11 | "*.css": "postcss",
12 | },
13 | "editor.formatOnSave": false,
14 | }
15 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 2019-02-19
4 |
5 | - transfer to [ElpsyCN](https://github.com/elpsycn)
6 |
7 | ## 2019-01-05
8 |
9 | - remove miniprogram
10 | - add fan sound effect
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 云游君
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # electric-fan
2 |
3 | > [Electric Fan](https://fan.elpsy.cn)
4 |
5 | [未来道具研究所](https://elpsy.cn)
6 |
7 | 一个智障小工具
8 |
9 | 电风扇! 为你的夏日带去清凉!
10 |
11 | - [Web Demo](https://fan.elpsy.cn)
12 |
13 | ## Feature
14 |
15 | ### 优势
16 |
17 | - 随时随地打开风扇
18 | - 便携
19 | - 低功耗(使用 HTML CSS 而非 Canvas 绘制)
20 | - 操作简单
21 | - 安装便捷
22 |
23 | ### 劣势
24 |
25 | - 没有风
26 |
27 | ## Intend
28 |
29 | ……这是我在小空调之前写的,因为写的太丑甚至没好意思宣传,没想到小空调意外的火了。
30 |
31 | - [x] [空调](https://github.com/YunYouJun/air-conditioner)
32 | - [ ] 温度计 🌡️
33 |
34 | ## About
35 |
36 | 
37 |
38 | ## [Change Log](CHANGELOG.md)
39 |
40 | ## Dev 开发
41 |
42 | ```sh
43 | # install dependencies
44 | yarn
45 | ```
46 |
47 | ```sh
48 | # 启动
49 | # http://localhost:3000/
50 | yarn dev
51 | ```
52 |
53 | ```sh
54 | # 构建
55 | yarn build
56 | ```
57 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 夏日清凉小风扇~
10 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/locales/en.yml:
--------------------------------------------------------------------------------
1 | button:
2 | toggle_dark: Toggle dark mode
3 |
--------------------------------------------------------------------------------
/locales/zh-CN.yml:
--------------------------------------------------------------------------------
1 | button:
2 | toggle_dark: 切换深色模式
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electric-fan",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "Electric Fan",
6 | "author": {
7 | "email": "me@yunyoujun.cn",
8 | "name": "YunYouJun",
9 | "url": "https://www.yunyoujun.cn"
10 | },
11 | "scripts": {
12 | "dev": "vite",
13 | "build": "vite build",
14 | "preview": "vite preview",
15 | "lint": "eslint --ext .js,.ts,.json,.vue --fix ."
16 | },
17 | "dependencies": {
18 | "@gtm-support/vue-gtm": "^1.2.3",
19 | "@vueuse/core": "^7.1.2",
20 | "@vueuse/head": "^0.7.2",
21 | "nprogress": "^0.2.0",
22 | "vue": "^3.2.17",
23 | "vue-about-me": "^1.2.0",
24 | "vue-i18n": "^9.2.0-beta.6"
25 | },
26 | "devDependencies": {
27 | "@antfu/eslint-config": "^0.12.0",
28 | "@iconify-json/carbon": "^1.0.12",
29 | "@intlify/vite-plugin-vue-i18n": "^3.2.1",
30 | "@types/markdown-it-link-attributes": "^3.0.1",
31 | "@types/nprogress": "^0.2.0",
32 | "@vitejs/plugin-vue": "^1.10.1",
33 | "@vue/compiler-sfc": "^3.2.23",
34 | "critters": "^0.0.15",
35 | "eslint": "^8.4.0",
36 | "markdown-it-link-attributes": "^3.0.0",
37 | "markdown-it-prism": "^2.2.1",
38 | "sass": "^1.44.0",
39 | "typescript": "^4.5.2",
40 | "unplugin-auto-import": "^0.5.1",
41 | "unplugin-icons": "^0.12.22",
42 | "unplugin-vue-components": "^0.17.4",
43 | "vite": "^2.6.14",
44 | "vite-plugin-inspect": "^0.3.11",
45 | "vite-plugin-md": "^0.11.4",
46 | "vite-plugin-pages": "^0.18.2",
47 | "vite-plugin-pwa": "0.11.10",
48 | "vite-plugin-style-import": "^1.4.0",
49 | "vite-plugin-vue-layouts": "^0.5.0",
50 | "vite-plugin-windicss": "^1.5.4",
51 | "vite-ssg": "^0.16.2"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/public/CNAME:
--------------------------------------------------------------------------------
1 | fan.elpsy.cn
2 |
--------------------------------------------------------------------------------
/public/audio/fan.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElpsyCN/electric-fan/ffcb523dfd4ce09e1b1c7d105fcd52b3b39b17ec/public/audio/fan.wav
--------------------------------------------------------------------------------
/public/audio/switch.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElpsyCN/electric-fan/ffcb523dfd4ce09e1b1c7d105fcd52b3b39b17ec/public/audio/switch.mp3
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElpsyCN/electric-fan/ffcb523dfd4ce09e1b1c7d105fcd52b3b39b17ec/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElpsyCN/electric-fan/ffcb523dfd4ce09e1b1c7d105fcd52b3b39b17ec/public/favicon.png
--------------------------------------------------------------------------------
/public/img/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElpsyCN/electric-fan/ffcb523dfd4ce09e1b1c7d105fcd52b3b39b17ec/public/img/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/img/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElpsyCN/electric-fan/ffcb523dfd4ce09e1b1c7d105fcd52b3b39b17ec/public/img/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/img/icons/pwa-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElpsyCN/electric-fan/ffcb523dfd4ce09e1b1c7d105fcd52b3b39b17ec/public/img/icons/pwa-192x192.png
--------------------------------------------------------------------------------
/public/img/icons/pwa-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElpsyCN/electric-fan/ffcb523dfd4ce09e1b1c7d105fcd52b3b39b17ec/public/img/icons/pwa-512x512.png
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElpsyCN/electric-fan/ffcb523dfd4ce09e1b1c7d105fcd52b3b39b17ec/public/logo.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/BaseFooter.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
31 |
--------------------------------------------------------------------------------
/src/components/BaseHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
12 |
--------------------------------------------------------------------------------
/src/components/Fan.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
17 |
20 |
21 |
24 |
--------------------------------------------------------------------------------
/src/components/FanSwitch.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
20 |
21 |
98 |
--------------------------------------------------------------------------------
/src/composables/dark.ts:
--------------------------------------------------------------------------------
1 | // import { useDark, useToggle } from '@vueuse/core'
2 |
3 | export const isDark = useDark()
4 | export const toggleDark = useToggle(isDark)
5 |
--------------------------------------------------------------------------------
/src/composables/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dark'
2 |
--------------------------------------------------------------------------------
/src/layouts/default.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { ViteSSG } from 'vite-ssg'
2 | import generatedRoutes from 'virtual:generated-pages'
3 | import { setupLayouts } from 'virtual:generated-layouts'
4 | import App from './App.vue'
5 |
6 | // windicss layers
7 | import 'virtual:windi-base.css'
8 | import 'virtual:windi-components.css'
9 |
10 | // your custom styles here
11 | import './styles/vars.scss'
12 | import './styles/main.scss'
13 | import './styles/index.scss'
14 |
15 | // windicss utilities should be the last style import
16 | import 'virtual:windi-utilities.css'
17 | // windicss devtools support (dev only)
18 | import 'virtual:windi-devtools'
19 |
20 | const routes = setupLayouts(generatedRoutes)
21 |
22 | // https://github.com/antfu/vite-ssg
23 | export const createApp = ViteSSG(
24 | App,
25 | { routes },
26 | (ctx) => {
27 | // install all modules under `modules/`
28 | Object.values(import.meta.globEager('./modules/*.ts')).map(i => i.install?.(ctx))
29 | },
30 | )
31 |
--------------------------------------------------------------------------------
/src/modules/gtm.ts:
--------------------------------------------------------------------------------
1 | import { createGtm } from '@gtm-support/vue-gtm'
2 | import { UserModule } from '~/types'
3 |
4 | // https://github.com/antfu/vite-plugin-pwa#automatic-reload-when-new-content-available
5 | export const install: UserModule = ({ isClient, app }) => {
6 | if (!isClient) return
7 |
8 | app.use(
9 | createGtm({
10 | id: 'GTM-NMD3456',
11 | }),
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/modules/i18n.ts:
--------------------------------------------------------------------------------
1 | import { createI18n } from 'vue-i18n'
2 | import { UserModule } from '~/types'
3 |
4 | // Import i18n resources
5 | // https://vitejs.dev/guide/features.html#glob-import
6 | //
7 | // Don't need this? Try vitesse-lite: https://github.com/antfu/vitesse-lite
8 | const messages = Object.fromEntries(
9 | Object.entries(
10 | import.meta.globEager('../../locales/*.y(a)?ml'))
11 | .map(([key, value]) => {
12 | const yaml = key.endsWith('.yaml')
13 | return [key.slice(14, yaml ? -5 : -4), value.default]
14 | }),
15 | )
16 |
17 | export const install: UserModule = ({ app }) => {
18 | const i18n = createI18n({
19 | legacy: false,
20 | locale: 'en',
21 | messages,
22 | })
23 |
24 | app.use(i18n)
25 | }
26 |
--------------------------------------------------------------------------------
/src/modules/nprogress.ts:
--------------------------------------------------------------------------------
1 | import NProgress from 'nprogress'
2 | import { UserModule } from '~/types'
3 |
4 | export const install: UserModule = ({ isClient, router }) => {
5 | if (isClient) {
6 | router.beforeEach(() => { NProgress.start() })
7 | router.afterEach(() => { NProgress.done() })
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/modules/pwa.ts:
--------------------------------------------------------------------------------
1 | import { UserModule } from '~/types'
2 |
3 | // https://github.com/antfu/vite-plugin-pwa#automatic-reload-when-new-content-available
4 | export const install: UserModule = ({ isClient, router }) => {
5 | if (!isClient) { return }
6 |
7 | router.isReady().then(async() => {
8 | const { registerSW } = await import('virtual:pwa-register')
9 | registerSW({ immediate: true })
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/src/pages/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ title }}
4 |
5 |
6 | {{ description }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
--------------------------------------------------------------------------------
/src/shim.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.vue" {
2 | import { defineComponent } from "vue";
3 | const component: ReturnType;
4 | export default component;
5 | }
6 |
--------------------------------------------------------------------------------
/src/stores/index.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from 'vue'
2 | export const store = {
3 | debug: true,
4 |
5 | state: reactive({
6 | /**
7 | * 风力级别
8 | */
9 | level: 0,
10 | }),
11 |
12 | setLevel(value: number) {
13 | this.state.level = value
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/src/styles/fan.scss:
--------------------------------------------------------------------------------
1 | @use "sass:math";
2 | $background-color: var(--fan-color);
3 | $border-color: var(--fan-color);
4 |
5 | $fan-width: 220px;
6 |
7 | $header-width: $fan-width;
8 | $header-border-width: 10px;
9 |
10 | $leaf-border-width: 8px;
11 | $leaf-width: 200px;
12 |
13 | $neck-width: 10px;
14 | $neck-height: 60px;
15 |
16 | $footer-height: 10px;
17 | $footer-width: 150px;
18 |
19 | $circle-width: 10px;
20 | $circle-border-width: 8px;
21 | $circle-position: math.div($leaf-width, 2) -
22 | (math.div($circle-width, 2) + $circle-border-width);
23 |
24 | @keyframes leafsRotate {
25 | 0% {
26 | transform: rotate(0deg);
27 | }
28 | 100% {
29 | transform: rotate(1080deg);
30 | }
31 | }
32 |
33 | .fan-btn {
34 | display: inline-flex;
35 | justify-content: center;
36 | align-items: center;
37 | border-radius: 50%;
38 | width: 3rem;
39 | height: 3rem;
40 | border: 2px solid var(--fan-color);
41 | border-bottom-width: 0.25rem;
42 | margin: 0.5rem;
43 | font-size: 1rem;
44 |
45 | outline: none;
46 |
47 | font-family: monospace;
48 | font-weight: bold;
49 |
50 | transition: 0.2s;
51 |
52 | &:hover {
53 | color: var(--fan-bg-color);
54 | background-color: var(--fan-color);
55 | }
56 |
57 | &:focus {
58 | outline: none;
59 | }
60 |
61 | &.is-active,
62 | &:active {
63 | color: var(--fan-bg-color);
64 | background-color: var(--fan-color);
65 | transform: translateY(0.32rem);
66 | border-bottom-width: 1px;
67 | }
68 | }
69 |
70 | #fan {
71 | width: $fan-width;
72 | height: $header-width + $neck-height + $footer-height;
73 | margin: 2rem auto;
74 | position: relative;
75 | z-index: 4;
76 | .fan-header {
77 | box-sizing: content-box;
78 |
79 | width: $header-width;
80 | height: $header-width;
81 | position: absolute;
82 | left: -$header-border-width;
83 | top: -$header-border-width;
84 | border-radius: 50%;
85 | z-index: 1;
86 | border: solid $header-border-width $border-color;
87 |
88 | .leafs {
89 | z-index: 2;
90 | position: absolute;
91 | animation: leafsRotate 0s infinite linear;
92 | transform-origin: center center;
93 | width: $leaf-width;
94 | height: $leaf-width;
95 | top: $header-border-width;
96 | left: $header-border-width;
97 |
98 | .circle {
99 | box-sizing: content-box;
100 |
101 | width: $circle-width;
102 | height: $circle-width;
103 | border: solid $circle-border-width $border-color;
104 | border-radius: 50%;
105 | position: absolute;
106 | left: $circle-position;
107 | top: $circle-position;
108 | }
109 |
110 | .leaf {
111 | box-sizing: content-box;
112 |
113 | width: 72px;
114 | height: 60px;
115 | border-radius: 20% 50%;
116 | border: $leaf-border-width solid $border-color;
117 | position: absolute;
118 | left: math.div($leaf-width, 2);
119 | top: math.div($leaf-width, 2);
120 | transform-origin: 0% 0%;
121 | }
122 |
123 | .leaf-1 {
124 | @extend .leaf;
125 | }
126 | .leaf-2 {
127 | @extend .leaf;
128 | transform: rotate(120deg);
129 | }
130 | .leaf-3 {
131 | @extend .leaf;
132 | transform: rotate(240deg);
133 | }
134 | }
135 |
136 | .leafs-0 {
137 | @extend .leafs;
138 | animation-duration: 3s;
139 | animation-timing-function: ease-out;
140 | animation-delay: 0s;
141 | animation-iteration-count: 1;
142 | animation-fill-mode: forwards;
143 | }
144 | .leafs-1 {
145 | @extend .leafs;
146 | animation-duration: 2s;
147 | }
148 | .leafs-2 {
149 | @extend .leafs;
150 | animation-duration: 1.5s;
151 | }
152 | .leafs-3 {
153 | @extend .leafs;
154 | animation-duration: 0.8s;
155 | }
156 | }
157 |
158 | .fan-neck {
159 | width: $neck-width;
160 | height: $neck-height;
161 | background: $background-color;
162 | position: absolute;
163 | top: $header-width + 8px;
164 | left: math.div($header-width, 2) -
165 | math.div($neck-width, 2);
166 | z-index: 2;
167 | }
168 |
169 | .fan-footer {
170 | width: $footer-width;
171 | height: $footer-height;
172 | border-radius: 5%;
173 | background: $background-color;
174 | position: absolute;
175 | top: $header-width + $neck-height + 8px;
176 | left: math.div($fan-width, 2) -
177 | math.div($footer-width, 2);
178 | z-index: 3;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | #app {
2 | font-family: "Avenir", Helvetica, Arial, sans-serif;
3 | -webkit-font-smoothing: antialiased;
4 | -moz-osx-font-smoothing: grayscale;
5 | text-align: center;
6 | color: #2c3e50;
7 | }
8 |
9 | .animate-logo {
10 | animation: iconAnimate 1.5s ease-in-out infinite;
11 | }
12 |
--------------------------------------------------------------------------------
/src/styles/main.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | #app {
4 | height: 100%;
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | html.dark {
10 | background: #121212;
11 | }
12 |
13 | #nprogress {
14 | pointer-events: none;
15 | }
16 |
17 | #nprogress .bar {
18 | @apply bg-blue-600 opacity-75;
19 |
20 | position: fixed;
21 | z-index: 1031;
22 | top: 0;
23 | left: 0;
24 |
25 | width: 100%;
26 | height: 2px;
27 | }
28 |
29 | .btn {
30 | @apply px-4 py-1 rounded inline-block
31 | bg-blue-600 text-white cursor-pointer
32 | hover:bg-blue-700
33 | disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50;
34 | }
35 |
36 | .icon-btn {
37 | @apply inline-block cursor-pointer select-none
38 | opacity-75 transition duration-200 ease-in-out
39 | hover:opacity-100 hover:text-blue-600;
40 | font-size: 0.9em;
41 | }
42 |
--------------------------------------------------------------------------------
/src/styles/vars.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | --fan-color: #333;
3 | --fan-bg-color: white;
4 | }
5 |
6 | html.dark {
7 | --fan-color: white;
8 | --fan-bg-color: #333;
9 | }
10 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import { ViteSSGContext } from 'vite-ssg'
2 |
3 | export type UserModule = (ctx: ViteSSGContext) => void
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "module": "ESNext",
5 | "target": "es2016",
6 | "lib": ["DOM", "ESNext"],
7 | "strict": true,
8 | "esModuleInterop": true,
9 | "incremental": false,
10 | "skipLibCheck": true,
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "noUnusedLocals": true,
14 | "strictNullChecks": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "types": [
17 | "vite/client",
18 | "vite-plugin-pages/client",
19 | "vite-plugin-vue-layouts/client"
20 | ],
21 | "paths": {
22 | "~/*": ["src/*"]
23 | }
24 | },
25 | "exclude": ["dist", "node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import { defineConfig } from 'vite'
3 | import Vue from '@vitejs/plugin-vue'
4 | import Pages from 'vite-plugin-pages'
5 | import Layouts from 'vite-plugin-vue-layouts'
6 | import Icons from 'unplugin-icons/vite'
7 | import IconsResolver from 'unplugin-icons/resolver'
8 | import Components from 'unplugin-vue-components/vite'
9 | import AutoImport from 'unplugin-auto-import/vite'
10 | import Markdown from 'vite-plugin-md'
11 | import WindiCSS from 'vite-plugin-windicss'
12 | import { VitePWA } from 'vite-plugin-pwa'
13 | import VueI18n from '@intlify/vite-plugin-vue-i18n'
14 | import Inspect from 'vite-plugin-inspect'
15 | import Prism from 'markdown-it-prism'
16 | import LinkAttributes from 'markdown-it-link-attributes'
17 |
18 | const markdownWrapperClasses = 'prose prose-sm m-auto text-left'
19 |
20 | export default defineConfig({
21 | resolve: {
22 | alias: {
23 | '~/': `${path.resolve(__dirname, 'src')}/`,
24 | },
25 | },
26 | plugins: [
27 | Vue({
28 | include: [/\.vue$/, /\.md$/],
29 | }),
30 |
31 | // https://github.com/hannoeru/vite-plugin-pages
32 | Pages({
33 | extensions: ['vue', 'md'],
34 | }),
35 |
36 | // https://github.com/JohnCampionJr/vite-plugin-vue-layouts
37 | Layouts(),
38 |
39 | // https://github.com/antfu/unplugin-auto-import
40 | AutoImport({
41 | imports: [
42 | 'vue',
43 | 'vue-router',
44 | 'vue-i18n',
45 | '@vueuse/head',
46 | '@vueuse/core',
47 | ],
48 | dts: 'src/auto-imports.d.ts',
49 | }),
50 |
51 | // https://github.com/antfu/unplugin-vue-components
52 | Components({
53 | // allow auto load markdown components under `./src/components/`
54 | extensions: ['vue', 'md'],
55 |
56 | // allow auto import and register components used in markdown
57 | include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
58 |
59 | // custom resolvers
60 | resolvers: [
61 | // auto import icons
62 | // https://github.com/antfu/unplugin-icons
63 | IconsResolver({
64 | // componentPrefix: '',
65 | // enabledCollections: ['carbon']
66 | }),
67 | ],
68 |
69 | dts: 'src/components.d.ts',
70 | }),
71 |
72 | // https://github.com/antfu/unplugin-icons
73 | Icons({
74 | autoInstall: true
75 | }),
76 |
77 | // https://github.com/antfu/vite-plugin-windicss
78 | WindiCSS({
79 | safelist: markdownWrapperClasses,
80 | }),
81 |
82 | // https://github.com/antfu/vite-plugin-md
83 | // Don't need this? Try vitesse-lite: https://github.com/antfu/vitesse-lite
84 | Markdown({
85 | wrapperClasses: markdownWrapperClasses,
86 | headEnabled: true,
87 | markdownItSetup(md) {
88 | // https://prismjs.com/
89 | // @ts-expect-error types mismatch
90 | md.use(Prism)
91 | // @ts-expect-error types mismatch
92 | md.use(LinkAttributes, {
93 | pattern: /^https?:\/\//,
94 | attrs: {
95 | target: '_blank',
96 | rel: 'noopener',
97 | },
98 | })
99 | },
100 | }),
101 |
102 | // https://github.com/antfu/vite-plugin-pwa
103 | VitePWA({
104 | registerType: 'autoUpdate',
105 | includeAssets: ['favicon.svg', 'robots.txt', 'safari-pinned-tab.svg'],
106 | manifest: {
107 | name: 'Electric Fan',
108 | short_name: 'Fan',
109 | theme_color: '#ffffff',
110 | icons: [
111 | {
112 | src: '/img/icons/pwa-192x192.png',
113 | sizes: '192x192',
114 | type: 'image/png',
115 | },
116 | {
117 | src: '/img/icons/pwa-512x512.png',
118 | sizes: '512x512',
119 | type: 'image/png',
120 | },
121 | {
122 | src: '/img/icons/pwa-512x512.png',
123 | sizes: '512x512',
124 | type: 'image/png',
125 | purpose: 'any maskable',
126 | },
127 | ],
128 | },
129 | }),
130 |
131 | // https://github.com/intlify/bundle-tools/tree/main/packages/vite-plugin-vue-i18n
132 | VueI18n({
133 | runtimeOnly: true,
134 | compositionOnly: true,
135 | include: [path.resolve(__dirname, 'locales/**')],
136 | }),
137 |
138 | // https://github.com/antfu/vite-plugin-inspect
139 | Inspect({
140 | // change this to enable inspect for debugging
141 | enabled: false,
142 | }),
143 | ],
144 |
145 | server: {
146 | fs: {
147 | strict: true,
148 | },
149 | },
150 |
151 | // https://github.com/antfu/vite-ssg
152 | ssgOptions: {
153 | script: 'async',
154 | formatting: 'minify',
155 | },
156 |
157 | optimizeDeps: {
158 | include: [
159 | 'vue',
160 | 'vue-router',
161 | '@vueuse/core',
162 | '@vueuse/head',
163 | ],
164 | exclude: [
165 | 'vue-demi',
166 | ],
167 | },
168 | })
169 |
--------------------------------------------------------------------------------
/windi.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'windicss/helpers'
2 | import colors from 'windicss/colors'
3 | import typography from 'windicss/plugin/typography'
4 |
5 | export default defineConfig({
6 | darkMode: 'class',
7 | // https://windicss.org/posts/v30.html#attributify-mode
8 | attributify: true,
9 |
10 | plugins: [
11 | typography(),
12 | ],
13 | theme: {
14 | extend: {
15 | typography: {
16 | DEFAULT: {
17 | css: {
18 | maxWidth: '65ch',
19 | color: 'inherit',
20 | a: {
21 | 'color': 'inherit',
22 | 'opacity': 0.75,
23 | 'fontWeight': '500',
24 | 'textDecoration': 'underline',
25 | '&:hover': {
26 | opacity: 1,
27 | color: colors.teal[600],
28 | },
29 | },
30 | b: { color: 'inherit' },
31 | strong: { color: 'inherit' },
32 | em: { color: 'inherit' },
33 | h1: { color: 'inherit' },
34 | h2: { color: 'inherit' },
35 | h3: { color: 'inherit' },
36 | h4: { color: 'inherit' },
37 | code: { color: 'inherit' },
38 | },
39 | },
40 | },
41 | },
42 | },
43 | })
44 |
--------------------------------------------------------------------------------