├── public ├── CNAME ├── favicon-16x16.png ├── favicon-32x32.png ├── live2d │ ├── moc │ │ ├── pio.moc │ │ └── tia.moc │ └── mtn │ │ ├── Breath1.mtn │ │ ├── Breath2.mtn │ │ ├── Breath7.mtn │ │ └── Breath4.mtn └── apple-icon-180x180.png ├── .eslintignore ├── src ├── pages │ ├── 404.js │ ├── inspiration │ │ ├── index.less │ │ └── index.js │ ├── archive │ │ ├── index.less │ │ └── index.js │ ├── post │ │ ├── index.less │ │ └── $number.js │ ├── document.ejs │ ├── index.less │ ├── tag │ │ ├── index.less │ │ └── index.js │ ├── about │ │ ├── index.less │ │ └── index.js │ ├── friend │ │ ├── index.less │ │ └── index.js │ ├── book │ │ ├── index.less │ │ └── index.js │ ├── category │ │ ├── index.less │ │ └── index.js │ └── index.js ├── images │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── pattern.png │ └── ImagesByMarkSebastian.txt ├── components │ ├── Loading │ │ ├── index.less │ │ └── index.js │ ├── Quote │ │ ├── index.js │ │ └── index.less │ ├── Pagination │ │ ├── index.js │ │ └── index.less │ ├── Reward │ │ ├── index.js │ │ └── index.less │ ├── index.js │ ├── Segment │ │ ├── index.js │ │ └── index.less │ ├── Archive │ │ ├── index.less │ │ └── index.js │ ├── PostCard │ │ ├── index.js │ │ └── index.less │ ├── PostPV │ │ ├── index.js │ │ └── index.less │ ├── MarkDown │ │ └── index.js │ ├── PostBody │ │ ├── index.js │ │ └── index.less │ ├── LazyImage │ │ ├── index.js │ │ └── index.less │ ├── Header │ │ ├── index.less │ │ └── index.js │ ├── Transition │ │ └── index.js │ └── SKPlayer │ │ └── index.less ├── assets │ ├── cursor │ │ ├── cursor.png │ │ └── cursor_link.png │ ├── fonts │ │ ├── Fontello.woff2 │ │ ├── GuDianMingChaoTi.ttf │ │ └── FiraCode-Medium.woff2 │ ├── live2d │ │ ├── waifu.json │ │ └── tips.json │ ├── prism │ │ ├── prism.js │ │ └── prism.less │ └── lib │ │ └── fireworks.js ├── layouts │ ├── index.less │ └── index.js ├── dva.js ├── utils.js ├── services.js ├── global.less ├── config.js └── models │ └── global.js ├── .webpackrc.js ├── .prettierrc ├── .eslintrc ├── .gitignore ├── .umirc.js ├── package.json └── README.md /public/CNAME: -------------------------------------------------------------------------------- 1 | chanshiyu.com -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | src/assets 4 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | export default () =>
404 Page
2 | -------------------------------------------------------------------------------- /src/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/src/images/1.jpg -------------------------------------------------------------------------------- /src/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/src/images/2.jpg -------------------------------------------------------------------------------- /src/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/src/images/3.jpg -------------------------------------------------------------------------------- /src/images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/src/images/4.jpg -------------------------------------------------------------------------------- /src/images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/src/images/5.jpg -------------------------------------------------------------------------------- /src/images/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/src/images/6.jpg -------------------------------------------------------------------------------- /src/components/Loading/index.less: -------------------------------------------------------------------------------- 1 | .loading { 2 | margin: 0 auto; 3 | width: 1rem; 4 | } 5 | -------------------------------------------------------------------------------- /src/images/pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/src/images/pattern.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/live2d/moc/pio.moc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/public/live2d/moc/pio.moc -------------------------------------------------------------------------------- /public/live2d/moc/tia.moc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/public/live2d/moc/tia.moc -------------------------------------------------------------------------------- /public/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/public/apple-icon-180x180.png -------------------------------------------------------------------------------- /src/assets/cursor/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/src/assets/cursor/cursor.png -------------------------------------------------------------------------------- /src/assets/fonts/Fontello.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/src/assets/fonts/Fontello.woff2 -------------------------------------------------------------------------------- /src/assets/cursor/cursor_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/src/assets/cursor/cursor_link.png -------------------------------------------------------------------------------- /src/assets/fonts/GuDianMingChaoTi.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/src/assets/fonts/GuDianMingChaoTi.ttf -------------------------------------------------------------------------------- /.webpackrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | publicPath: '/static/', 3 | extraBabelPlugins: ['lodash', 'transform-remove-console'] 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/fonts/FiraCode-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chanshiyucx/heart-beat/HEAD/src/assets/fonts/FiraCode-Medium.woff2 -------------------------------------------------------------------------------- /src/layouts/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | margin: 0 auto; 4 | min-height: 100%; 5 | max-width: 9rem; 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "useTabs": false, 4 | "printWidth": 100, 5 | "tabWidth": 2, 6 | "semi": false, 7 | "bracketSpacing": true 8 | } 9 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-umi", 3 | "rules": { 4 | "jsx-a11y/href-no-hash": 0, 5 | "linebreak-style": 0, 6 | "react/no-deprecated": 0, 7 | "no-unused-expressions": 0 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/images/ImagesByMarkSebastian.txt: -------------------------------------------------------------------------------- 1 | Images by Mark Sebastian: http://www.flickr.com/photos/markjsebastian/ 2 | http://www.markjsebastian.com/ 3 | 4 | License: 5 | http://creativecommons.org/licenses/by-sa/2.0/deed.en 6 | Attribution-ShareAlike 2.0 Generic (CC BY-SA 2.0) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # production 5 | /dist 6 | 7 | # misc 8 | .DS_Store 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | 17 | # umi 18 | .umi 19 | .umi-production -------------------------------------------------------------------------------- /src/components/Loading/index.js: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames/bind' 2 | 3 | import config from '../../config' 4 | import styles from './index.less' 5 | 6 | const { loadingImg } = config 7 | const cx = classNames.bind(styles) 8 | 9 | const Loading = ({ className }) => { 10 | return 11 | } 12 | 13 | export default Loading 14 | -------------------------------------------------------------------------------- /src/components/Quote/index.js: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames/bind' 2 | import styles from './index.less' 3 | 4 | const cx = classNames.bind(styles) 5 | 6 | const Quote = ({ text }) => { 7 | return ( 8 |
9 | 10 | {text} 11 | 12 |
13 | ) 14 | } 15 | 16 | export default Quote 17 | -------------------------------------------------------------------------------- /src/pages/inspiration/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 0 auto; 3 | padding-bottom: 1rem; 4 | @media (max-width: 900px) { 5 | width: 96%; 6 | } 7 | .body { 8 | padding: 0.16rem; 9 | border-radius: 0.03rem; 10 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 11 | background-color: rgba(255, 255, 255, 0.6); 12 | } 13 | .loading { 14 | margin-top: 1rem; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/dva.js: -------------------------------------------------------------------------------- 1 | import AV from 'leancloud-storage' 2 | 3 | import globalConfig from './config' 4 | 5 | // Init Leancloud 6 | AV.init(globalConfig.leancloud) 7 | 8 | export function config() { 9 | return { 10 | onError(err) { 11 | err.preventDefault() 12 | } 13 | } 14 | } 15 | 16 | window.publicPath = '/' 17 | if (process.env.NODE_ENV !== 'development') { 18 | console.log = function() {} 19 | console.warn = function() {} 20 | console.error = function() {} 21 | } 22 | -------------------------------------------------------------------------------- /.umirc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | hash: true, 3 | history: 'hash', 4 | publicPath: process.env.NODE_ENV === 'production' ? '/treasure/heartbeat/' : '/', 5 | plugins: [ 6 | [ 7 | 'umi-plugin-react', 8 | { 9 | dva: true, 10 | routes: { 11 | exclude: [/models\//] 12 | }, 13 | library: 'preact', 14 | dynamicImport: { 15 | webpackChunkName: true 16 | }, 17 | fastClick: true, 18 | title: '蝉時雨' 19 | } 20 | ] 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Quote/index.less: -------------------------------------------------------------------------------- 1 | .quote { 2 | position: relative; 3 | margin-bottom: 0.26rem; 4 | padding: 0.32rem 0; 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: center; 8 | align-items: center; 9 | font-size: 0.16rem; 10 | span { 11 | letter-spacing: 1px; 12 | } 13 | i { 14 | position: absolute; 15 | font-size: 0.24rem; 16 | color: #888; 17 | } 18 | i:first-child { 19 | top: 0.16rem; 20 | left: 0.16rem; 21 | } 22 | i:last-child { 23 | bottom: 0; 24 | right: 0.16rem; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Pagination/index.js: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames/bind' 2 | import styles from './index.less' 3 | 4 | const cx = classNames.bind(styles) 5 | 6 | const Pagination = ({ page, maxPage, prev, next }) => { 7 | return ( 8 |
9 | 12 | {page} 13 | 16 |
17 | ) 18 | } 19 | 20 | export default Pagination 21 | -------------------------------------------------------------------------------- /src/assets/live2d/waifu.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "layout": { 4 | "center_x": 0.0, 5 | "center_y": -0.05, 6 | "width": 1.9 7 | }, 8 | "hit_areas_custom": { 9 | "head_x": [-0.35, 0.6], 10 | "head_y": [0.19, -0.2], 11 | "body_x": [-0.3, -0.25], 12 | "body_y": [0.3, -0.9] 13 | }, 14 | "motions": { 15 | "idle": [ 16 | { "file": "mtn/WakeUp.mtn" }, 17 | { "file": "mtn/Breath1.mtn" }, 18 | { "file": "mtn/Breath2.mtn" }, 19 | { "file": "mtn/Breath4.mtn" }, 20 | { "file": "mtn/Breath7.mtn" }, 21 | { "file": "mtn/Breath8.mtn" } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/pages/archive/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 0 auto; 3 | padding-bottom: 1rem; 4 | @media (max-width: 900px) { 5 | width: 96%; 6 | } 7 | .body { 8 | padding: 0.16rem; 9 | border-radius: 0.03rem; 10 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 11 | background-color: rgba(255, 255, 255, 0.6); 12 | .content { 13 | display: flex; 14 | flex-wrap: wrap; 15 | justify-content: space-between; 16 | align-items: center; 17 | @media (max-width: 900px) { 18 | justify-content: space-around; 19 | } 20 | } 21 | } 22 | .loading { 23 | margin-top: 1rem; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/assets/prism/prism.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 代码高亮 3 | * @author: 蝉時雨 4 | * @date: 2018-07-10 5 | */ 6 | 7 | import Prism from 'prismjs/components/prism-core' 8 | 9 | import 'prismjs/plugins/line-numbers/prism-line-numbers.js' 10 | 11 | import 'prismjs/components/prism-clike' 12 | import 'prismjs/components/prism-c' 13 | import 'prismjs/components/prism-objectivec' 14 | import 'prismjs/components/prism-markup' 15 | import 'prismjs/components/prism-markdown' 16 | import 'prismjs/components/prism-bash' 17 | import 'prismjs/components/prism-javascript' 18 | import 'prismjs/components/prism-css' 19 | import 'prismjs/components/prism-json' 20 | import 'prismjs/components/prism-java' 21 | import 'prismjs/components/prism-python' 22 | 23 | export default Prism 24 | -------------------------------------------------------------------------------- /src/components/Reward/index.js: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames/bind' 2 | import config from '../../config' 3 | import styles from './index.less' 4 | 5 | const { reward } = config 6 | const cx = classNames.bind(styles) 7 | 8 | const Reward = () => { 9 | return ( 10 |
11 | 12 |
13 | 23 |
24 |
25 | ) 26 | } 27 | 28 | export default Reward 29 | 30 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Archive } from './Archive' 2 | export { default as Footer } from './Footer' 3 | export { default as Header } from './Header' 4 | export { default as LazyImage } from './LazyImage' 5 | export { default as Loading } from './Loading' 6 | export { default as MarkDown } from './MarkDown' 7 | export { default as Pagination } from './Pagination' 8 | export { default as PostBody } from './PostBody' 9 | export { default as PostCard } from './PostCard' 10 | export { default as PostPV } from './PostPV' 11 | export { default as Quote } from './Quote' 12 | export { default as Reward } from './Reward' 13 | export { default as Segment } from './Segment' 14 | export { default as SKPlayer } from './SKPlayer' 15 | export { default as Transition } from './Transition' 16 | -------------------------------------------------------------------------------- /src/pages/post/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | margin: 0 auto; 6 | padding-bottom: 1rem; 7 | width: 100%; 8 | .wapper { 9 | width: 100%; 10 | text-align: center; 11 | .post { 12 | width: 100%; 13 | border-radius: 0.03rem; 14 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 15 | background-color: rgba(255, 255, 255, 0.6); 16 | } 17 | .loading { 18 | margin-top: 1rem; 19 | } 20 | .lincenses { 21 | margin: 0.16rem auto; 22 | } 23 | .post-squares { 24 | display: flex; 25 | overflow: hidden; 26 | border-radius: 0 0 0.03rem 0.03rem; 27 | @media (max-width: 900px) { 28 | display: block; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/Segment/index.js: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames/bind' 2 | 3 | import MarkeDown from '../MarkDown' 4 | import styles from './index.less' 5 | 6 | const cx = classNames.bind(styles) 7 | 8 | const Segment = ({ color, title, content }) => { 9 | return ( 10 |
16 | 22 | 27 | {title} 28 | 29 | {!!content && } 30 |
31 | ) 32 | } 33 | 34 | export default Segment 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HeartBeat", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "description": "An awesome blog generator", 6 | "author": "蝉時雨 ", 7 | "license": "MIT", 8 | "scripts": { 9 | "start": "umi dev", 10 | "build": "umi build", 11 | "lint": "eslint --ext .js src test", 12 | "precommit": "yarn lint -- --fix" 13 | }, 14 | "dependencies": { 15 | "baguettebox.js": "^1.11.0", 16 | "classnames": "^2.2.6", 17 | "fontfaceobserver": "^2.1.0", 18 | "gitalk": "^1.4.1", 19 | "leancloud-storage": "^3.11.1", 20 | "lodash": "^4.17.11", 21 | "marked": "^0.6.0", 22 | "prismjs": "^1.15.0", 23 | "smooth-scroll": "^15.0.0", 24 | "timeago.js": "^3.0.2", 25 | "umi": "^2.3.2" 26 | }, 27 | "devDependencies": { 28 | "babel-plugin-lodash": "^3.3.4", 29 | "babel-plugin-transform-remove-console": "^6.9.4", 30 | "eslint": "^5.12.0", 31 | "eslint-config-umi": "^1.3.0", 32 | "eslint-plugin-flowtype": "^3.2.1", 33 | "eslint-plugin-import": "^2.14.0", 34 | "eslint-plugin-jsx-a11y": "^6.1.2", 35 | "eslint-plugin-react": "^7.12.3", 36 | "umi-plugin-react": "^1.3.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/Archive/index.less: -------------------------------------------------------------------------------- 1 | .archive { 2 | display: inline-block; 3 | margin: 0.06rem 0; 4 | width: 49.2%; 5 | @media (max-width: 900px) { 6 | width: 96%; 7 | } 8 | .segment { 9 | border-top-width: 0.02rem; 10 | border-top-style: solid; 11 | border-radius: 0.03rem; 12 | background: rgba(255, 255, 255, 0.4); 13 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 14 | transition: all 0.25s ease 0s, transform 0.5s cubic-bezier(0.6, 0.2, 0.1, 1) 0s; 15 | &:hover { 16 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2), 0 6px 6px rgba(0, 0, 0, 0.24); 17 | transform: translateY(-0.04rem); 18 | } 19 | .title { 20 | padding: 0.12rem 0.16rem; 21 | overflow: hidden; 22 | } 23 | .meta { 24 | padding: 0.12rem 0.16rem; 25 | border-top: 1px solid rgba(0, 0, 0, 0.06); 26 | & > span { 27 | margin-right: 0.06rem; 28 | color: #888; 29 | i { 30 | margin-right: 0.04rem; 31 | } 32 | span { 33 | padding-right: 0.04rem; 34 | } 35 | } 36 | .tag { 37 | margin-right: 0.08rem; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Pagination/index.less: -------------------------------------------------------------------------------- 1 | .pagination { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | margin-top: 0.12rem; 6 | button { 7 | padding: 0.12rem 0.2rem; 8 | font-size: 0.14rem; 9 | font-weight: 700; 10 | text-align: center; 11 | text-decoration: none; 12 | outline: 0; 13 | border: none; 14 | box-shadow: 0 0 0.1rem rgba(0, 0, 0, 0.2); 15 | border-radius: 0.03rem; 16 | background: rgba(255, 255, 255, 0.6); 17 | transition: opacity 0.1s ease, background-color 0.1s ease, color 0.1s ease, box-shadow 0.1s ease; 18 | &:hover { 19 | box-shadow: 0 0 0.4rem #888 inset; 20 | } 21 | &:disabled { 22 | color: #888; 23 | } 24 | &:disabled:hover { 25 | box-shadow: 0 0 0.1rem rgba(0, 0, 0, 0.2); 26 | } 27 | &.prevBtn { 28 | margin-right: -0.1rem; 29 | } 30 | &.nextBtn { 31 | margin-left: -0.1rem; 32 | } 33 | } 34 | span { 35 | display: inline-block; 36 | width: 0.26rem; 37 | height: 0.26rem; 38 | line-height: 0.26rem; 39 | text-align: center; 40 | border-radius: 50%; 41 | background: #fff; 42 | z-index: 10; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import router from 'umi/router' 3 | import FontFaceObserver from 'fontfaceobserver' 4 | 5 | import Header from '../components/Header' 6 | import Footer from '../components/Footer' 7 | import Home from '../pages' 8 | import fireworks from '../assets/lib/fireworks' 9 | import { isMobile } from '../utils' 10 | import styles from './index.less' 11 | ;(function() { 12 | const font = new FontFaceObserver('Noto Serif SC', { 13 | weight: '400' 14 | }) 15 | 16 | font.load().then(() => { 17 | document.body.style.fontFamily = 'Noto Serif SC, Helvetica, PingFang SC, sans-serif' 18 | }) 19 | })() 20 | 21 | class App extends PureComponent { 22 | constructor(props) { 23 | super(props) 24 | this.state = { 25 | renderBg: false 26 | } 27 | } 28 | 29 | componentDidMount() { 30 | if (!isMobile) { 31 | fireworks() 32 | } 33 | const { pathname } = this.props.location 34 | if (pathname !== '/') { 35 | router.push(pathname) 36 | } 37 | } 38 | 39 | render({ children }) { 40 | return ( 41 |
42 |
43 | {children || } 44 |
(this.footer = c)} /> 45 |
46 | ) 47 | } 48 | } 49 | 50 | export default App 51 | -------------------------------------------------------------------------------- /src/components/Archive/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'umi/link' 3 | import classNames from 'classnames/bind' 4 | 5 | import styles from './index.less' 6 | 7 | const cx = classNames.bind(styles) 8 | 9 | const Archive = ({ number, created_at, milestone, labels, title, color }) => { 10 | const date = created_at.slice(0, 10) 11 | 12 | return ( 13 |
14 | 15 |
16 |
{title}
17 |
18 | 19 | 20 | {date} 21 | 22 | 23 | 24 | {milestone && milestone.title ? milestone.title : '未分类'} 25 | 26 | 27 | 28 | {labels.slice(0, 2).map(o => { 29 | return ( 30 | 31 | {o.name} 32 | 33 | ) 34 | })} 35 | 36 |
37 |
38 | 39 |
40 | ) 41 | } 42 | 43 | export default Archive 44 | -------------------------------------------------------------------------------- /src/pages/document.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= context.title %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 29 |
30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/components/PostCard/index.js: -------------------------------------------------------------------------------- 1 | import Link from 'umi/link' 2 | import classNames from 'classnames/bind' 3 | 4 | import MarkeDown from '../MarkDown' 5 | import styles from './index.less' 6 | 7 | const cx = classNames.bind(styles) 8 | 9 | const PostCard = ({ number, title, date, cover, desc, filterLabels, milestone, times }) => { 10 | return ( 11 |
12 | 13 |
14 | 15 |

{title}

16 |
17 | 18 |
19 | 20 | 21 | {date} 22 | 23 | 24 | 25 | 热度{times}℃ 26 | 27 | 28 | 29 | {milestone && milestone.title ? milestone.title : '未分类'} 30 | 31 | 32 | 33 | 34 | {filterLabels.slice(0, 2).map(o => ( 35 | 36 | {o.name} 37 | 38 | ))} 39 | 40 | 41 |
42 | 43 |
44 | ) 45 | } 46 | 47 | export default PostCard 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HeartBeat - A SPA Blog Theme 2 | 3 | [![Author](https://img.shields.io/badge/author-chanshiyucx-blue.svg?style=flat-square)](https://chanshiyu.com) [![QQ](https://img.shields.io/badge/QQ-1124590931-blue.svg?style=flat-square)](http://wpa.qq.com/msgrd?v=3&uin=&site=qq&menu=yes) [![Email](https://img.shields.io/badge/Emali%20me-me@chanshiyu.com-green.svg?style=flat-square)](me@chanshiyu.com) 4 | 5 | ![蝉时雨](https://i.loli.net/2018/12/15/5c15047d6d235.png) 6 | 7 | HeartBeat 是一个基于 [UmiJS](https://umijs.org/) 开发的 SPA 单页面博客应用程序,后台数据源依托于 [Github Issues](https://developer.github.com/v3/issues/) ,使用开源项目 [Gitalk](https://github.com/gitalk/gitalk) 作为博客评论系统。该主题基于 Github 全家桶,脱离服务器与数据库,关注内容本身,免费食用。 8 | 9 | 技术栈:UmiJS + Github Issues + Gitalk 10 | 11 | 在线演示:[蝉時雨](https://chanshiyu.com/treasure/heartbeat/) 12 | 13 | ## Getting Started 14 | 15 | ### Installing 16 | 17 | ```bash 18 | git@github.com:chanshiyucx/heart-beat.git 19 | cd heart-beat 20 | npm install # or yarn 21 | ``` 22 | 23 | ### Configuration 24 | 25 | 修改目录 `src/config.js` 的配置文件,每个配置项都有详细说明。 26 | 27 | 注意修改项目目录下 `.umirc.js` 的 `title` 为自己的站点标题! 28 | 29 | 页面模板参考: [文章、关于、标签、分类、书单等模板](https://github.com/chanshiyucx/Blog/issues) 30 | 31 | ### Preview 32 | 33 | ```bash 34 | npm start 35 | ``` 36 | 37 | 浏览器打开 `http://localhost:8000` 便可访问新的博客! 38 | 39 | ### Deployment 40 | 41 | ```bash 42 | umi build 43 | ``` 44 | 45 | 打包完毕,将 `dist` 目录下生成的静态文件发布 `Github Pages` 或 `Coding Pages` 即可。 46 | 47 | Just enjoy it ฅ●ω●ฅ 48 | 49 | ## License 50 | 51 | This project is licensed under the MIT License. 52 | -------------------------------------------------------------------------------- /src/components/PostPV/index.js: -------------------------------------------------------------------------------- 1 | import Link from 'umi/link' 2 | import classNames from 'classnames/bind' 3 | 4 | import MarkeDown from '../MarkDown' 5 | import styles from './index.less' 6 | 7 | const cx = classNames.bind(styles) 8 | 9 | const PostPV = ({ number, title, date, cover, desc, filterLabels, milestone, times }) => { 10 | return ( 11 |
12 | 13 | 14 |
15 |
16 |

{title}

17 |
18 | 19 | 20 | {date} 21 | 22 | 23 | 24 | 热度{times}℃ 25 | 26 | 27 | 28 | {milestone && milestone.title ? milestone.title : '未分类'} 29 | 30 | 31 | 32 | 33 | {filterLabels.slice(0, 2).map(o => ( 34 | {o.name} 35 | ))} 36 | 37 | 38 |
39 |
40 | 41 |
42 | 43 |
44 | ) 45 | } 46 | 47 | export default PostPV 48 | -------------------------------------------------------------------------------- /src/pages/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | padding-bottom: 1rem; 3 | @media (max-width: 1200px) { 4 | padding-bottom: 1.6rem; 5 | } 6 | .content { 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | position: relative; 11 | min-height: 7.91rem; 12 | } 13 | .page-btn { 14 | display: flex; 15 | justify-content: center; 16 | position: absolute; 17 | top: 50%; 18 | margin: 0; 19 | width: 1.5rem; 20 | outline: 0; 21 | border: none; 22 | background: transparent; 23 | i { 24 | font-size: 1.2rem; 25 | color: rgba(255, 255, 255, 0.6); 26 | } 27 | &.prev { 28 | left: 0; 29 | transform: translate(-100%, -50%); 30 | } 31 | &.next { 32 | right: 0; 33 | transform: translate(100%, -50%); 34 | } 35 | @media (max-width: 1200px) { 36 | display: none; 37 | } 38 | } 39 | .mobile-btn { 40 | position: absolute; 41 | display: none; 42 | bottom: 0; 43 | width: 1.5rem; 44 | height: 0.8rem; 45 | outline: 0; 46 | border: none; 47 | background: transparent; 48 | transform: translate(0, 100%); 49 | i { 50 | font-size: 0.8rem; 51 | color: rgba(255, 255, 255, 0.6); 52 | } 53 | @media (max-width: 1200px) { 54 | display: inline-block; 55 | } 56 | } 57 | .post-list { 58 | display: flex; 59 | flex-wrap: wrap; 60 | justify-content: space-between; 61 | align-items: center; 62 | width: 100%; 63 | @media (max-width: 1200px) { 64 | justify-content: space-around; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/components/MarkDown/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import marked from 'marked' 3 | 4 | import Prism from '../../assets/prism/prism.js' 5 | 6 | const renderer = new marked.Renderer() 7 | renderer.heading = function(text, level) { 8 | const icon = ['e80e', 'f299', 'f292'][level - 2] 9 | return `&#x${icon};${text}` 10 | } 11 | 12 | renderer.link = function(href, title, text) { 13 | // 只显示一个图标 14 | if (text.includes('aria-hidden="true"')) { 15 | return `${text}` 16 | } 17 | return `${text}` 18 | } 19 | 20 | renderer.image = function(href, title, text) { 21 | let clazz = `img-box ${href.endsWith('#full') ? 'full-box' : ''}` 22 | //判断是否全宽渲染 23 | return ` 24 | ${text}${text ? `◭ ${text}` : ''}` 25 | } 26 | 27 | marked.setOptions({ 28 | renderer, 29 | highlight: (code, lang) => { 30 | return Prism.highlight(code, Prism.languages[lang || 'markup'], lang) 31 | } 32 | }) 33 | 34 | class MarkeDown extends PureComponent { 35 | componentDidMount() { 36 | Prism.highlightAll() 37 | } 38 | 39 | componentDidUpdate(prevProps) { 40 | if (prevProps.content !== this.props.content) { 41 | Prism.highlightAll() 42 | } 43 | } 44 | 45 | render({ className, content }) { 46 | return ( 47 |
48 | ) 49 | } 50 | } 51 | 52 | export default MarkeDown 53 | -------------------------------------------------------------------------------- /src/pages/tag/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 0 auto; 3 | padding-bottom: 1rem; 4 | @media (max-width: 900px) { 5 | width: 96%; 6 | } 7 | .body { 8 | padding: 0.16rem; 9 | border-radius: 0.03rem; 10 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 11 | background-color: rgba(255, 255, 255, 0.6); 12 | .content { 13 | display: flex; 14 | flex-wrap: wrap; 15 | justify-content: space-between; 16 | align-items: center; 17 | button { 18 | margin: 0 0.04rem 0.1rem; 19 | padding: 0.08rem 0.12rem; 20 | font-size: 0.16rem; 21 | text-align: center; 22 | text-decoration: none; 23 | text-shadow: 0 0 1px currentColor; 24 | outline: 0; 25 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 26 | border: none; 27 | border-radius: 0.03rem; 28 | background: rgba(0, 0, 0, 0.16); 29 | &:hover { 30 | background: rgba(0, 0, 0, 0.2); 31 | } 32 | } 33 | } 34 | .filter-post { 35 | margin-top: 0.08rem; 36 | } 37 | .menu-btn { 38 | margin: 0 0.04rem 0.1rem 0.12rem; 39 | padding: 0.08rem 0.12rem; 40 | font-size: 0.16rem; 41 | text-align: center; 42 | text-decoration: none; 43 | outline: 0; 44 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 45 | border: none; 46 | border-radius: 0.03rem; 47 | background: rgba(0, 0, 0, 0.16); 48 | i { 49 | color: #f6f; 50 | margin-left: 0.06rem; 51 | } 52 | &:hover { 53 | background: rgba(0, 0, 0, 0.2); 54 | } 55 | } 56 | } 57 | .loading { 58 | margin-top: 1rem; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/PostBody/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import classNames from 'classnames/bind' 3 | import baguetteBox from 'baguettebox.js' 4 | 5 | import MarkeDown from '../MarkDown' 6 | import styles from './index.less' 7 | 8 | const cx = classNames.bind(styles) 9 | 10 | class PostBody extends PureComponent { 11 | componentDidMount() { 12 | this.initLightBox() 13 | } 14 | 15 | componentDidUpdate(prevProps) { 16 | if (prevProps.title !== this.props.title) { 17 | this.initLightBox() 18 | } 19 | } 20 | 21 | initLightBox = () => { 22 | baguetteBox.run('#post-body') 23 | } 24 | 25 | render({ title, date, cover, content, filterLabels, milestone, times }) { 26 | return ( 27 |
28 |
29 | 30 |
31 |

{title}

32 |
33 | 34 | 35 | {date} 36 | 37 | 38 | 39 | 热度{times}℃ 40 | 41 | 42 | 43 | {milestone && milestone.title ? milestone.title : '未分类'} 44 | 45 | 46 | 47 | 48 | {filterLabels && filterLabels.map(o => {o.name})} 49 | 50 | 51 |
52 |
53 |
54 | {!!content && } 55 |
56 | ) 57 | } 58 | } 59 | 60 | export default PostBody 61 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import timeago from 'timeago.js' 2 | 3 | const t = timeago() 4 | 5 | // 是否为移动端 6 | export const isMobile = 7 | /mobile/i.test(window.navigator.userAgent) || document.body.clientWidth < 1200 8 | 9 | // 延时 10 | export const delay = time => new Promise(resolve => setTimeout(resolve, time)) 11 | 12 | // 文章格式化 13 | export const formatPost = post => { 14 | const { created_at, body, labels } = post 15 | const temp = body.split('\r\n') 16 | const regex = /^\[(.+)\].*(http.*(?:jpg|jpeg|png|gif))/g 17 | const cover = regex.exec(temp[0]) 18 | post.cover = { 19 | title: cover[1], 20 | src: cover[2] 21 | } 22 | post.desc = temp[2] 23 | post.content = body 24 | post.date = t.format(created_at, 'zh_CN') 25 | post.filterLabels = labels.sort((a, b) => a.name.length >= b.name.length) 26 | return post 27 | } 28 | 29 | // 预加载图片 30 | export const loadImg = async ({ images }) => { 31 | return new Promise(resolve => { 32 | const seq = images.map(img => { 33 | return new Promise(resolve => { 34 | let imgObj = new Image() 35 | imgObj.onload = () => { 36 | resolve() 37 | } 38 | imgObj.onerror = () => { 39 | resolve() 40 | } 41 | imgObj.src = img 42 | }) 43 | }) 44 | Promise.all(seq) 45 | .then(() => { 46 | resolve() 47 | }) 48 | .catch(console.error) 49 | }).catch(console.error) 50 | } 51 | 52 | /** 53 | * @description 绑定事件 on(element, event, handler) 54 | */ 55 | export const on = (function() { 56 | if (document.addEventListener) { 57 | return function(element, event, handler) { 58 | if (element && event && handler) { 59 | element.addEventListener(event, handler, false) 60 | } 61 | } 62 | } else { 63 | return function(element, event, handler) { 64 | if (element && event && handler) { 65 | element.attachEvent('on' + event, handler) 66 | } 67 | } 68 | } 69 | })() 70 | -------------------------------------------------------------------------------- /src/components/PostPV/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 50%; 3 | position: relative; 4 | overflow: hidden; 5 | @media (max-width: 900px) { 6 | width: 100%; 7 | } 8 | &:hover { 9 | color: #666; 10 | img { 11 | transform: scale(1.06); 12 | } 13 | .content { 14 | transform: translateY(-1.6rem); 15 | } 16 | } 17 | img { 18 | display: block; 19 | width: 100%; 20 | height: 1.6rem; 21 | object-fit: cover; 22 | transition: transform 0.6s ease-out; 23 | } 24 | .content { 25 | position: absolute; 26 | top: 0; 27 | width: 100%; 28 | height: 100%; 29 | transition: transform 0.5s cubic-bezier(0.6, 0.2, 0.1, 1) 0s; 30 | } 31 | .info { 32 | display: flex; 33 | justify-content: center; 34 | align-items: flex-start; 35 | flex-direction: column; 36 | padding: 0.24rem; 37 | height: 100%; 38 | line-height: 1.7; 39 | color: #eee; 40 | box-sizing: border-box; 41 | background: rgba(0, 0, 0, 0.4); 42 | h3 { 43 | margin: 0.06rem 0; 44 | font-size: 0.2rem; 45 | font-weight: normal; 46 | color: #eee; 47 | } 48 | .meta { 49 | text-align: left; 50 | } 51 | .meta > span { 52 | display: inline-block; 53 | margin-right: 0.06rem; 54 | width: 46%; 55 | i { 56 | margin-right: 0.04rem; 57 | } 58 | span { 59 | padding-right: 0.06rem; 60 | } 61 | } 62 | } 63 | .desc { 64 | display: flex; 65 | justify-content: center; 66 | align-items: center; 67 | padding: 0.24rem 0.36rem; 68 | height: 100%; 69 | box-sizing: border-box; 70 | background: rgba(255, 255, 255, 0.6); 71 | p { 72 | display: -webkit-box; 73 | -webkit-box-orient: vertical; 74 | -webkit-line-clamp: 4; 75 | line-height: 1.7; 76 | font-size: 0.16rem; 77 | text-align: justify; 78 | text-overflow: ellipsis; 79 | overflow: hidden; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/components/Segment/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 0.16rem 0; 3 | padding: 0.16rem 0; 4 | border-radius: 0.03rem; 5 | background: rgba(255, 255, 255, 0.4); 6 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 7 | transition: transform 0.6s cubic-bezier(0.6, 0.2, 0.1, 1) 0s; 8 | &:hover { 9 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2), 0 6px 6px rgba(0, 0, 0, 0.24); 10 | transform: translateY(-4px); 11 | } 12 | .label { 13 | position: relative; 14 | left: -0.304rem; 15 | padding: 0.06rem 0.144rem 0.06rem 0.304rem; 16 | font-size: 0.14rem; 17 | color: #fff; 18 | border-radius: 0 0.03rem 0.03rem 0; 19 | i { 20 | content: ''; 21 | position: absolute; 22 | top: 100%; 23 | left: 0; 24 | width: 0; 25 | height: 0; 26 | background-color: transparent; 27 | border-style: solid; 28 | border-width: 0 0.144rem 0.144rem 0; 29 | border-color: transparent; 30 | border-right-color: currentColor; 31 | filter: brightness(120%); 32 | } 33 | } 34 | .content { 35 | margin: 0 auto; 36 | user-select: text; 37 | text-align: justify; 38 | img { 39 | width: calc(~'100% + .32rem'); 40 | margin-left: -0.16rem; 41 | box-shadow: 0 0 10px #888; 42 | } 43 | p, 44 | ul, 45 | ol, 46 | blockquote { 47 | line-height: 1.6; 48 | font-size: 0.16rem; 49 | } 50 | p { 51 | margin: 0.14rem 0.16rem 0; 52 | } 53 | ol, 54 | ul { 55 | margin: 0.06rem 0.24rem 0.06rem 0.42rem; 56 | } 57 | h2 { 58 | margin: 0.12rem 0.16rem; 59 | padding: 0.08rem 0; 60 | font-size: 0.22rem; 61 | border-bottom: 1px dashed rgba(0, 0, 0, 0.2); 62 | } 63 | h3 { 64 | margin: 0.12rem 0.16rem -0.08rem; 65 | font-size: 0.18rem; 66 | i { 67 | font-size: 0.16rem; 68 | } 69 | } 70 | a { 71 | border-bottom: 1px solid #888; 72 | &:hover { 73 | border-bottom-color: #f6f; 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/pages/about/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 0 auto; 3 | padding-bottom: 1rem; 4 | @media (max-width: 900px) { 5 | width: 96%; 6 | .header { 7 | display: block !important; 8 | img { 9 | margin: 0 auto; 10 | } 11 | .info { 12 | margin-top: 0.16rem; 13 | } 14 | } 15 | .concat a { 16 | margin: 0 0.1rem !important; 17 | } 18 | } 19 | .body { 20 | padding: 0.16rem; 21 | border-radius: 0.03rem; 22 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 23 | background-color: rgba(255, 255, 255, 0.6); 24 | .header { 25 | display: flex; 26 | justify-content: flex-start; 27 | align-items: center; 28 | margin-bottom: 0.16rem; 29 | img { 30 | width: 1rem; 31 | height: 1rem; 32 | border-radius: 50%; 33 | border: 0.04rem solid rgba(255, 255, 255, 0.6); 34 | box-shadow: 0 0 10px 2px #888; 35 | transition: transform 1s ease-out; 36 | &:hover { 37 | animation-play-state: paused; 38 | transform: rotateZ(360deg); 39 | } 40 | } 41 | .info { 42 | margin-left: 0.16rem; 43 | & > span { 44 | display: block; 45 | margin: 0.05rem 0.03rem; 46 | i { 47 | margin-right: 0.03rem; 48 | width: 0.16rem; 49 | } 50 | } 51 | } 52 | } 53 | .concat { 54 | display: flex; 55 | justify-content: center; 56 | a { 57 | display: flex; 58 | justify-content: center; 59 | align-items: center; 60 | margin: 0 0.3rem; 61 | width: 0.54rem; 62 | height: 0.54rem; 63 | border-radius: 50%; 64 | border: 2px solid rgba(0, 0, 0, 0.08); 65 | box-shadow: 0 0 10px 0px rgba(0, 0, 0, 0.4); 66 | background: rgba(0, 0, 0, 0.24); 67 | &:hover { 68 | background-color: #faf; 69 | } 70 | img { 71 | width: 0.3rem; 72 | height: 0.3rem; 73 | } 74 | } 75 | } 76 | } 77 | .loading { 78 | margin-top: 1rem; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/components/PostCard/index.less: -------------------------------------------------------------------------------- 1 | .card { 2 | display: inline-block; 3 | margin: 0.06rem 0; 4 | width: 444px; 5 | overflow: hidden; 6 | border-radius: 0.03rem; 7 | background-color: rgba(255, 255, 255, 0.6); 8 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 9 | transition: all 0.25s ease 0s, transform 0.5s cubic-bezier(0.6, 0.2, 0.1, 1) 0s; 10 | &:hover { 11 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2), 0 6px 6px rgba(0, 0, 0, 0.24); 12 | transform: translateY(-6px); 13 | img { 14 | transform: scale(1.06); 15 | } 16 | } 17 | a:hover { 18 | color: #666; 19 | } 20 | .header { 21 | position: relative; 22 | overflow: hidden; 23 | img { 24 | display: block; 25 | width: 100%; 26 | transition: transform 0.6s ease-out; 27 | } 28 | h3 { 29 | position: absolute; 30 | bottom: 0; 31 | padding: 0.12rem 0.14rem; 32 | width: 100%; 33 | font-size: 0.18rem; 34 | font-weight: normal; 35 | color: #eee; 36 | background: rgba(0, 0, 0, 0.4); 37 | } 38 | } 39 | .desc { 40 | padding: 0.12rem 0.14rem; 41 | a > i { 42 | display: none; 43 | } 44 | p { 45 | display: -webkit-box; 46 | -webkit-box-orient: vertical; 47 | -webkit-line-clamp: 3; 48 | line-height: 1.6; 49 | font-size: 0.14rem; 50 | text-align: justify; 51 | text-overflow: ellipsis; 52 | overflow: hidden; 53 | letter-spacing: 0.4px; 54 | } 55 | } 56 | .meta { 57 | padding: 0.12rem 0.14rem; 58 | font-size: 0.14rem; 59 | border-top: 1px solid rgba(0, 0, 0, 0.06); 60 | & > span { 61 | margin-right: 0.06rem; 62 | color: #777; 63 | i { 64 | margin-right: 0.04rem; 65 | padding: 0; 66 | } 67 | span { 68 | padding-right: 0.02rem; 69 | } 70 | .tag { 71 | margin-right: 0.04rem; 72 | } 73 | :last-child { 74 | margin-right: 0; 75 | padding-right: 0; 76 | } 77 | } 78 | :last-child { 79 | margin-right: 0; 80 | padding-right: 0; 81 | } 82 | } 83 | @media (max-width: 1200px) { 84 | width: 96%; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/pages/friend/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 0 auto; 3 | padding-bottom: 1rem; 4 | @media (max-width: 900px) { 5 | width: 96%; 6 | } 7 | .body { 8 | padding: 0.16rem; 9 | border-radius: 0.03rem; 10 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 11 | background-color: rgba(255, 255, 255, 0.6); 12 | animation-duration: 0.6s; 13 | animation-fill-mode: forwards; 14 | .content { 15 | display: flex; 16 | flex-wrap: wrap; 17 | justify-content: space-around; 18 | align-items: center; 19 | padding: 0.16rem 1% 0; 20 | width: 100%; 21 | a { 22 | position: relative; 23 | margin-bottom: 0.16rem; 24 | width: 2rem; 25 | height: 1.12rem; 26 | overflow: hidden; 27 | border-radius: 3px; 28 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.16); 29 | &:hover { 30 | .cover { 31 | transform: scale(1.1); 32 | } 33 | .info { 34 | transform: translateY(-120px); 35 | } 36 | } 37 | .cover { 38 | width: 2rem; 39 | height: 1.12rem; 40 | transition: transform 0.6s ease-out; 41 | } 42 | .info { 43 | display: flex; 44 | flex-direction: column; 45 | justify-content: center; 46 | align-items: center; 47 | position: absolute; 48 | top: 0; 49 | width: 2rem; 50 | height: 1.12rem; 51 | background: rgba(255, 255, 255, 0.4); 52 | transition: transform 0.5s cubic-bezier(0.6, 0.2, 0.1, 1) 0s; 53 | img { 54 | margin-bottom: -0.16rem; 55 | width: 0.5rem; 56 | height: 0.5rem; 57 | border-radius: 50%; 58 | box-shadow: 0 0 9px #666; 59 | z-index: 1; 60 | } 61 | span { 62 | padding: 0.2rem 0 0.08rem; 63 | width: 100%; 64 | text-align: center; 65 | font-size: 0.16rem; 66 | box-shadow: 0 0 6px #888; 67 | background: rgba(255, 255, 255, 0.6); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | .loading { 74 | margin-top: 1rem; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/components/Reward/index.less: -------------------------------------------------------------------------------- 1 | .reward { 2 | position: relative; 3 | margin: 0 auto 0.12rem; 4 | width: 0.4rem; 5 | text-align: center; 6 | &:hover { 7 | width: 3rem; 8 | .reward-body { 9 | animation-name: flip-in-y; 10 | } 11 | } 12 | .reward-icon { 13 | display: inline-block; 14 | width: 0.4rem; 15 | height: 0.4rem; 16 | line-height: 0.4rem; 17 | text-align: center; 18 | font-size: 0.2rem; 19 | border-radius: 50%; 20 | color: #fff; 21 | background-image: linear-gradient(to right, rgba(135, 206, 250, 0.4), rgba(255, 192, 203, 0.8)); 22 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 23 | &:hover { 24 | color: #faf; 25 | } 26 | } 27 | .reward-body { 28 | position: absolute; 29 | top: 0.4rem; 30 | left: 50%; 31 | margin-left: -1.82rem; 32 | padding-top: 0.12rem; 33 | width: 3.64rem; 34 | z-index: 1; 35 | box-sizing: border-box; 36 | animation-duration: 0.6s; 37 | animation-fill-mode: forwards; 38 | animation-name: flip-out-y; 39 | ul { 40 | display: flex; 41 | justify-content: space-between; 42 | padding: 0.12rem 0.16rem; 43 | border-radius: 0.03rem; 44 | background: rgba(255, 255, 255, 0.88); 45 | li { 46 | display: flex; 47 | flex-direction: column; 48 | justify-content: center; 49 | align-items: center; 50 | img { 51 | width: 1.6rem; 52 | height: 1.6rem; 53 | border-radius: 0.03rem; 54 | } 55 | span { 56 | margin-top: 0.1rem; 57 | } 58 | } 59 | &::before { 60 | content: ''; 61 | position: absolute; 62 | top: 0; 63 | left: 0; 64 | right: 0; 65 | margin: 0 auto; 66 | width: 0; 67 | height: 0; 68 | border-left: 0.12rem solid transparent; 69 | border-right: 0.12rem solid transparent; 70 | border-bottom: 0.12rem solid rgba(255, 255, 255, 0.6); 71 | } 72 | } 73 | } 74 | } 75 | 76 | // 打赏动画 77 | @keyframes flip-in-y { 78 | 0% { 79 | transform: perspective(500px) rotateY(70deg); 80 | opacity: 0.2; 81 | } 82 | 30% { 83 | transform: perspective(500px) rotateY(-50deg); 84 | } 85 | 45% { 86 | transform: perspective(500px) rotateY(30deg); 87 | } 88 | 65% { 89 | transform: perspective(500px) rotateY(-20deg); 90 | } 91 | 85% { 92 | transform: perspective(500px) rotateY(10deg); 93 | } 94 | 100% { 95 | transform: perspective(500px) rotateY(0deg); 96 | } 97 | } 98 | 99 | @keyframes flip-out-y { 100 | 0% { 101 | transform: perspective(500px) rotateY(0deg); 102 | } 103 | 50% { 104 | transform: perspective(500px) rotateY(-30deg); 105 | opacity: 1; 106 | } 107 | 100% { 108 | transform: perspective(500px) rotateY(70deg); 109 | opacity: 0; 110 | pointer-events: none; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/components/LazyImage/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import classNames from 'classnames/bind' 3 | import styles from './index.less' 4 | 5 | const cx = classNames.bind(styles) 6 | 7 | const Flower = () => ( 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ) 16 | 17 | const Fingerprint = () => ( 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | ) 30 | 31 | const Trinity = () => ( 32 |
33 |
34 |
35 |
36 |
37 | ) 38 | 39 | const Atom = () => ( 40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | ) 49 | 50 | class LazyImage extends PureComponent { 51 | constructor(props) { 52 | super(props) 53 | this.state = { 54 | loading: true 55 | } 56 | } 57 | 58 | componentDidMount() { 59 | this.loadImg(this.props.src) 60 | } 61 | 62 | componentWillReceiveProps(nextProps) { 63 | if (nextProps.src !== this.props.src) { 64 | this.loadImg(nextProps.src) 65 | } 66 | } 67 | 68 | loadImg = src => { 69 | let imgObj = new Image() 70 | imgObj.onload = () => { 71 | this.setState({ loading: false }) 72 | } 73 | imgObj.src = src 74 | } 75 | 76 | getLoading = index => { 77 | const row = ~~(index / 4) 78 | const remain = row % 4 79 | let loading 80 | switch (remain) { 81 | case 0: 82 | loading = 83 | break 84 | case 1: 85 | loading = 86 | break 87 | case 2: 88 | loading = 89 | break 90 | case 3: 91 | loading = 92 | break 93 | default: 94 | loading = 95 | break 96 | } 97 | return loading 98 | } 99 | 100 | render({ className, src, alt, index }, { loading }) { 101 | return loading ? ( 102 |
{this.getLoading(index)}
103 | ) : ( 104 | {alt} 105 | ) 106 | } 107 | } 108 | 109 | export default LazyImage 110 | -------------------------------------------------------------------------------- /src/pages/post/$number.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { connect } from 'dva' 3 | import Gitalk from 'gitalk' 4 | import _ from 'lodash' 5 | import classNames from 'classnames/bind' 6 | 7 | import { Transition, PostBody, PostPV, Reward, Loading } from '../../components' 8 | import config from '../../config' 9 | import styles from './index.less' 10 | 11 | const { gitalkOption } = config 12 | const cx = classNames.bind(styles) 13 | 14 | class Post extends PureComponent { 15 | componentDidMount() { 16 | const { dispatch, match } = this.props 17 | const { number } = match.params 18 | dispatch({ 19 | type: 'app/queryPost', 20 | payload: { number } 21 | }) 22 | } 23 | 24 | componentWillReceiveProps(nextProps) { 25 | const { dispatch, post } = this.props 26 | if (post.number && +post.number !== +nextProps.match.params.number) { 27 | dispatch({ 28 | type: 'app/queryPost', 29 | payload: { number: nextProps.match.params.number } 30 | }) 31 | } 32 | } 33 | 34 | componentWillUnmount() { 35 | this.props.dispatch({ 36 | type: 'app/updateState', 37 | payload: { 38 | post: {} 39 | } 40 | }) 41 | } 42 | 43 | // 渲染评论 44 | renderGitalk = () => { 45 | const { post } = this.props 46 | const gitalk = new Gitalk({ 47 | ...gitalkOption, 48 | title: post.title 49 | }) 50 | gitalk.render(`gitalk-${post.id}`) 51 | } 52 | 53 | render({ post, prevPost, nextPost, loading }) { 54 | const showLoading = loading || _.isEmpty(post) 55 | return ( 56 |
57 |
58 | 64 |
65 | 66 | 67 | 76 |
77 | 78 | 79 |
80 |
81 |
82 | {showLoading && } 83 |
84 | 85 | {!showLoading && post.id &&
} 86 |
87 | ) 88 | } 89 | } 90 | 91 | export default connect(({ app, loading }) => { 92 | const { post, prevPost, nextPost } = app 93 | return { 94 | post, 95 | prevPost, 96 | nextPost, 97 | loading: loading.effects['app/queryPost'] 98 | } 99 | })(Post) 100 | -------------------------------------------------------------------------------- /src/pages/book/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 0 auto; 3 | padding-bottom: 1rem; 4 | @media (max-width: 900px) { 5 | width: 100%; 6 | } 7 | .body { 8 | padding: 0.16rem; 9 | border-radius: 0.03rem; 10 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 11 | background-color: rgba(255, 255, 255, 0.6); 12 | .content { 13 | display: flex; 14 | flex-wrap: wrap; 15 | justify-content: space-between; 16 | align-items: center; 17 | width: 100%; 18 | @media (max-width: 900px) { 19 | justify-content: space-around; 20 | } 21 | } 22 | .book { 23 | display: inline-block; 24 | margin: 0.06rem 0; 25 | width: 49.4%; 26 | text-align: justify; 27 | border-radius: 0.03rem; 28 | background: rgba(255, 255, 255, 0.4); 29 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 30 | transition: all 0.25s ease 0s, transform 0.5s cubic-bezier(0.6, 0.2, 0.1, 1) 0s; 31 | &:hover { 32 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2), 0 6px 6px rgba(0, 0, 0, 0.24); 33 | transform: translateY(-4px); 34 | } 35 | h2 { 36 | display: none; 37 | @media (max-width: 900px) { 38 | display: block; 39 | padding: 0.12rem 0.16rem 0; 40 | font-size: 0.18rem; 41 | } 42 | } 43 | .header { 44 | display: flex; 45 | justify-content: flex-start; 46 | padding: 0.1rem 0.08rem 0; 47 | img { 48 | width: 1.2rem; 49 | height: 1.6rem; 50 | margin-right: 0.08rem; 51 | box-shadow: 4px 6px 10px rgba(0, 0, 0, 0.2); 52 | } 53 | .info { 54 | width: calc(100% - 1.28rem); 55 | a { 56 | display: inline; 57 | padding-top: 0.04rem; 58 | i { 59 | margin-top: 0.03rem; 60 | } 61 | @media (max-width: 900px) { 62 | display: none; 63 | } 64 | } 65 | i { 66 | font-size: 0.16rem; 67 | } 68 | h2 { 69 | display: block; 70 | font-size: 0.18rem; 71 | font-weight: normal; 72 | letter-spacing: 0; 73 | } 74 | p { 75 | margin-top: 0.06rem; 76 | overflow: hidden; 77 | text-overflow: ellipsis; 78 | white-space: nowrap; 79 | } 80 | p:first-child { 81 | margin-top: 0.1rem; 82 | } 83 | p:last-child { 84 | margin-top: 0.05rem; 85 | i { 86 | margin-right: 0.02rem; 87 | color: #888; 88 | } 89 | } 90 | } 91 | } 92 | .desc { 93 | padding: 0.12rem; 94 | line-height: 1.6; 95 | font-size: 0.16rem; 96 | } 97 | @media (max-width: 900px) { 98 | width: 96%; 99 | } 100 | } 101 | } 102 | .loading { 103 | margin-top: 1rem; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/components/Header/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | button { 4 | display: none; 5 | position: fixed; 6 | padding: 0.1rem; 7 | color: #666; 8 | font-size: 0.26rem; 9 | font-weight: 700; 10 | text-align: center; 11 | text-decoration: none; 12 | outline: 0; 13 | border: none; 14 | background: transparent; 15 | z-index: 100; 16 | transition: all 0.6s cubic-bezier(0.6, 0.2, 0.1, 1) 0s; 17 | @media (max-width: 600px) { 18 | display: block; 19 | } 20 | } 21 | .inner { 22 | display: flex; 23 | flex-direction: column; 24 | justify-content: center; 25 | align-items: center; 26 | margin: 0 auto; 27 | padding: 0.5rem 0; 28 | overflow: hidden; 29 | transition: all 0.6s cubic-bezier(0.6, 0.2, 0.1, 1) 0s; 30 | .title, 31 | .sub-title { 32 | font-family: GuDianMingChaoTi; 33 | animation-name: fadeInDown; 34 | animation-duration: 0.8s; 35 | transform-origin: top center; 36 | } 37 | .title { 38 | margin-bottom: 0.06rem; 39 | font-size: 0.52rem; 40 | line-height: 0.6rem; 41 | letter-spacing: 0.02rem; 42 | } 43 | .sub-title { 44 | font-size: 0.22rem; 45 | line-height: 0.26rem; 46 | letter-spacing: 0.04rem; 47 | } 48 | .menu { 49 | margin-top: 0.14rem; 50 | padding: 0 0.32rem; 51 | height: 0.6rem; 52 | display: flex; 53 | justify-content: center; 54 | z-index: 999; 55 | background: rgba(0, 0, 0, 0.1); 56 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2) inset; 57 | transition: all 0.6s cubic-bezier(0.6, 0.2, 0.1, 1) 0s; 58 | animation-name: fadeIn; 59 | animation-duration: 0.8s; 60 | li { 61 | width: 0.48rem; 62 | font-size: 0.16rem; 63 | list-style: none; 64 | } 65 | a { 66 | height: 100%; 67 | display: flex; 68 | flex-direction: column; 69 | justify-content: space-around; 70 | align-items: center; 71 | } 72 | i { 73 | margin: 0.03rem; 74 | font-size: 0.16rem; 75 | } 76 | @media (max-width: 600px) { 77 | position: fixed; 78 | top: -1rem; 79 | left: 0; 80 | margin: 0; 81 | padding: 0; 82 | width: 100%; 83 | height: 0.8rem; 84 | flex-wrap: wrap; 85 | background: rgba(0, 0, 0, 0.4); 86 | li { 87 | width: 25%; 88 | } 89 | a { 90 | flex-direction: row; 91 | justify-content: center; 92 | color: #eee; 93 | } 94 | &.dropMenu { 95 | top: 0; 96 | } 97 | } 98 | } 99 | } 100 | } 101 | 102 | /* InDown */ 103 | @keyframes fadeInDown { 104 | 0% { 105 | opacity: 0; 106 | transform: translateY(-14%); 107 | } 108 | 109 | 100% { 110 | opacity: 1; 111 | transform: translateY(0%); 112 | } 113 | } 114 | 115 | /* In */ 116 | @keyframes fadeIn { 117 | 0% { 118 | opacity: 0; 119 | transform: scale(0.86); 120 | } 121 | 122 | 100% { 123 | opacity: 1; 124 | transform: scale(1); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /public/live2d/mtn/Breath1.mtn: -------------------------------------------------------------------------------- 1 | # Live2D Animator Motion Data 2 | $fps=30 3 | 4 | $fadein=0 5 | 6 | $fadeout=0 7 | 8 | PARAM_ANGLE_X=0 9 | PARAM_ANGLE_Y=0 10 | PARAM_ANGLE_Z=0 11 | PARAM_EMOTION=-1 12 | PARAM_EYE_L_OPEN=0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55 13 | PARAM_EYE_R_OPEN=0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55 14 | PARAM_EYE_L_OPEN2=-1 15 | PARAM_EYE_R_OPEN2=-1 16 | PARAM_EYE_BALL_X=0 17 | PARAM_EYE_BALL_Y=0 18 | PARAM_BROW_L_Y=0 19 | PARAM_BROW_R_Y=0 20 | PARAM_BROW_ANGLE=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 21 | PARAM_BROW_SELECT=-0.5 22 | PARAM_MOUTH_OPEN_Y=0 23 | PARAM_MOUTH_OPEN2=0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9 24 | PARAM_MOUTH_EMO=0 25 | PARAM_CHEEK=0 26 | PARAM_BODY_ANGLE_X=0 27 | PARAM_BODY_ANGLE_Z=0 28 | PARAM_BODY_Y=0 29 | PARAM_BREATH=0.5,0.5,0.502,0.505,0.509,0.513,0.519,0.525,0.533,0.541,0.549,0.559,0.568,0.579,0.59,0.601,0.613,0.625,0.637,0.65,0.663,0.676,0.688,0.702,0.714,0.727,0.74,0.753,0.765,0.777,0.789,0.8,0.811,0.822,0.831,0.841,0.849,0.857,0.865,0.871,0.877,0.881,0.885,0.888,0.889,0.89,0.889,0.887,0.884,0.88,0.875,0.869,0.862,0.854,0.845,0.836,0.826,0.815,0.804,0.792,0.78,0.768,0.756,0.743,0.73,0.716,0.703,0.69,0.677,0.664,0.651,0.638,0.626,0.613,0.601,0.59,0.579,0.569,0.559,0.549,0.541,0.533,0.525,0.519,0.513,0.509,0.505,0.502,0.501,0.5 30 | PARAM_BOING=0 31 | PARAM_HAIR_FRONT=0 32 | PARAM_HAIR_SIDE_R=0 33 | PARAM_HAIR_SIDE_L=0 34 | PARAM_TWIN_RIBBON_D=0 35 | PARAM_HAIR_BACK=0 36 | PARAM_WING_ANGLE=0 37 | PARAM_WING_DEFORM=0 38 | VISIBLE:PSD=1 39 | VISIBLE:PARTS_01_HAT=1 40 | VISIBLE:PARTS_01_HAIR_FRONT_001=1 41 | VISIBLE:PARTS_01_HAIR_SIDE_001=1 42 | VISIBLE:PARTS_01_HAIR_BACK_001=1 43 | VISIBLE:PARTS_01_FACE_001=1 44 | VISIBLE:PARTS_01_BROW_001=1 45 | VISIBLE:PARTS_01_EMOTION=1 46 | VISIBLE:PARTS_01_EYE_001=1 47 | VISIBLE:PARTS_01_EYE_BALL_001=1 48 | VISIBLE:PARTS_01_NOSE_001=1 49 | VISIBLE:PARTS_01_MOUTH_001=1 50 | VISIBLE:PARTS_01_EAR_001=1 51 | VISIBLE:PARTS_01_BUST=1 52 | VISIBLE:PARTS_01_BODY=1 53 | VISIBLE:PARTS_01_WING=1 -------------------------------------------------------------------------------- /src/pages/category/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 0 auto; 3 | padding-bottom: 1rem; 4 | @media (max-width: 900px) { 5 | width: 96%; 6 | } 7 | .body { 8 | padding: 0.16rem; 9 | border-radius: 0.03rem; 10 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 11 | background-color: rgba(255, 255, 255, 0.6); 12 | .content { 13 | display: flex; 14 | flex-wrap: wrap; 15 | justify-content: space-between; 16 | align-items: center; 17 | @media (max-width: 900px) { 18 | justify-content: space-around; 19 | } 20 | .cat { 21 | position: relative; 22 | margin-bottom: 0.16rem; 23 | width: 32.4%; 24 | height: 1.6rem; 25 | overflow: hidden; 26 | border-radius: 0.03rem; 27 | background: rgba(255, 255, 255, 0.4); 28 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.16); 29 | transition: all 0.25s ease 0s, transform 0.5s cubic-bezier(0.6, 0.2, 0.1, 1) 0s; 30 | &:hover { 31 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2), 0 6px 6px rgba(0, 0, 0, 0.24); 32 | transform: translateY(-4px); 33 | .avatar { 34 | animation-play-state: paused; 35 | transform: rotateZ(360deg); 36 | } 37 | } 38 | .bg { 39 | position: absolute; 40 | top: 0; 41 | left: 0; 42 | width: 100%; 43 | height: 100%; 44 | object-fit: cover; 45 | } 46 | .meta { 47 | position: relative; 48 | padding: 0 0.16rem; 49 | width: 100%; 50 | height: 100%; 51 | box-sizing: border-box; 52 | background-color: rgba(255, 255, 255, 0.6); 53 | text-shadow: 0 0 1px #666; 54 | .header { 55 | display: flex; 56 | justify-content: space-between; 57 | align-items: center; 58 | height: 80%; 59 | } 60 | .avatar { 61 | width: 0.8rem; 62 | height: 0.8rem; 63 | border-radius: 50%; 64 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4); 65 | transition: transform 1s ease-out; 66 | } 67 | span { 68 | padding: 0 0.1rem; 69 | height: 0.4rem; 70 | line-height: 0.4rem; 71 | border-radius: 0.03rem; 72 | background: rgba(255, 255, 255, 0.8); 73 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4); 74 | } 75 | } 76 | .desc { 77 | position: absolute; 78 | bottom: 0; 79 | padding: 0.12rem 0.16rem; 80 | } 81 | @media (max-width: 900px) { 82 | width: 96%; 83 | } 84 | } 85 | } 86 | .menu-btn { 87 | margin: 0 0.04rem 0.1rem 0.12rem; 88 | padding: 0.08rem 0.12rem; 89 | font-size: 0.16rem; 90 | text-align: center; 91 | text-decoration: none; 92 | outline: 0; 93 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 94 | border: none; 95 | border-radius: 0.03rem; 96 | background: rgba(0, 0, 0, 0.16); 97 | i { 98 | color: #f6f; 99 | margin-left: 0.06rem; 100 | } 101 | &:hover { 102 | background: rgba(0, 0, 0, 0.2); 103 | } 104 | } 105 | } 106 | .loading { 107 | margin-top: 1rem; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/pages/archive/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { connect } from 'dva' 3 | import _ from 'lodash' 4 | import Gitalk from 'gitalk' 5 | import classNames from 'classnames/bind' 6 | 7 | import { Transition, Archive, Quote, Pagination, Loading } from '../../components' 8 | import config from '../../config' 9 | import styles from './index.less' 10 | 11 | const { gitalkOption, archivesOption, themeColors } = config 12 | const { enableGitalk, qoute } = archivesOption 13 | const cx = classNames.bind(styles) 14 | const colors = _.shuffle(themeColors) 15 | 16 | class Archives extends PureComponent { 17 | constructor(props) { 18 | super(props) 19 | this.state = { 20 | showLoading: true, 21 | renderGitalk: false, 22 | archives: [], 23 | currList: [], 24 | pageSize: 12, 25 | page: 1, 26 | maxPage: 1 27 | } 28 | } 29 | 30 | componentDidMount() { 31 | this.queryArchives() 32 | } 33 | 34 | // 获取文章列表 35 | queryArchives() { 36 | this.props 37 | .dispatch({ 38 | type: 'app/queryArchives' 39 | }) 40 | .then(v => { 41 | const currList = v.slice(0, this.state.pageSize) 42 | const maxPage = Math.ceil(v.length / this.state.pageSize) 43 | this.setState({ 44 | showLoading: false, 45 | archives: v, 46 | currList, 47 | page: 1, 48 | maxPage 49 | }) 50 | }) 51 | .catch(console.error) 52 | } 53 | 54 | // 前一页 55 | prev = () => { 56 | const { archives, page, pageSize } = this.state 57 | const prevPage = page - 1 58 | const currList = archives.slice((prevPage - 1) * pageSize, (page - 1) * pageSize) 59 | this.setState({ 60 | currList, 61 | page: prevPage 62 | }) 63 | } 64 | 65 | // 后一页 66 | next = () => { 67 | const { archives, page, pageSize } = this.state 68 | const nextPage = page + 1 69 | const currList = archives.slice(page * pageSize, nextPage * pageSize) 70 | this.setState({ 71 | currList, 72 | page: nextPage 73 | }) 74 | } 75 | 76 | // 渲染评论 77 | renderGitalk = () => { 78 | if (enableGitalk && !this.state.renderGitalk) { 79 | setTimeout(() => { 80 | const gitalk = new Gitalk({ 81 | ...gitalkOption, 82 | title: '归档' 83 | }) 84 | gitalk.render('gitalk') 85 | }, 100) 86 | this.setState({ renderGitalk: true }) 87 | } 88 | } 89 | 90 | render({}, { showLoading, currList, page, maxPage }) { 91 | return ( 92 |
93 | 99 |
100 | 101 |
102 | {currList.map((o, i) => { 103 | const color = colors[i] 104 | return 105 | })} 106 |
107 | 108 |
109 |
110 | 111 | {enableGitalk &&
} 112 | {showLoading && } 113 |
114 | ) 115 | } 116 | } 117 | 118 | export default connect(() => ({}))(Archives) 119 | -------------------------------------------------------------------------------- /src/pages/inspiration/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { connect } from 'dva' 3 | import _ from 'lodash' 4 | import Gitalk from 'gitalk' 5 | import classNames from 'classnames/bind' 6 | 7 | import { Transition, Segment, Pagination, Quote, Loading } from '../../components' 8 | import config from '../../config' 9 | import styles from './index.less' 10 | 11 | const { gitalkOption, inspirationOption, themeColors } = config 12 | const { enableGitalk, qoute } = inspirationOption 13 | const cx = classNames.bind(styles) 14 | const colors = _.shuffle(themeColors) 15 | 16 | class Inspiration extends PureComponent { 17 | constructor(props) { 18 | super(props) 19 | this.state = { 20 | showLoading: true, 21 | renderGitalk: false, 22 | inspiration: [], 23 | currList: [], 24 | pageSize: 10, 25 | page: 1, 26 | maxPage: 1 27 | } 28 | } 29 | 30 | componentDidMount() { 31 | this.queryInspiration() 32 | } 33 | 34 | // 获取灵感列表 35 | queryInspiration() { 36 | this.props 37 | .dispatch({ 38 | type: 'app/queryInspiration' 39 | }) 40 | .then(v => { 41 | const currList = v.slice(0, this.state.pageSize) 42 | const maxPage = Math.ceil(v.length / this.state.pageSize) 43 | this.setState({ 44 | showLoading: false, 45 | inspiration: v, 46 | currList, 47 | page: 1, 48 | maxPage 49 | }) 50 | }) 51 | .catch(console.error) 52 | } 53 | 54 | // 前一页 55 | prev = () => { 56 | const { inspiration, page, pageSize } = this.state 57 | const prevPage = page - 1 58 | const currList = inspiration.slice((prevPage - 1) * pageSize, (page - 1) * pageSize) 59 | this.setState({ 60 | currList, 61 | page: prevPage 62 | }) 63 | } 64 | 65 | // 后一页 66 | next = () => { 67 | const { inspiration, page, pageSize } = this.state 68 | const nextPage = page + 1 69 | const currList = inspiration.slice(page * pageSize, nextPage * pageSize) 70 | this.setState({ 71 | currList, 72 | page: nextPage 73 | }) 74 | } 75 | 76 | // 渲染评论 77 | renderGitalk = () => { 78 | if (enableGitalk && !this.state.renderGitalk) { 79 | setTimeout(() => { 80 | const gitalk = new Gitalk({ 81 | ...gitalkOption, 82 | title: '灵感' 83 | }) 84 | gitalk.render('gitalk') 85 | }, 100) 86 | this.setState({ renderGitalk: true }) 87 | } 88 | } 89 | 90 | render({}, { showLoading, currList, page, maxPage }) { 91 | return ( 92 |
93 | 99 |
100 | 101 |
102 | {currList.map((o, i) => { 103 | const date = o.created_at.slice(0, 10) 104 | const color = colors[i] 105 | return 106 | })} 107 |
108 | 109 |
110 |
111 | 112 | {enableGitalk &&
} 113 | {showLoading && } 114 |
115 | ) 116 | } 117 | } 118 | 119 | export default connect(() => ({}))(Inspiration) 120 | -------------------------------------------------------------------------------- /src/pages/friend/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { connect } from 'dva' 3 | import _ from 'lodash' 4 | import Gitalk from 'gitalk' 5 | import classNames from 'classnames/bind' 6 | 7 | import { Transition, Quote, Loading } from '../../components' 8 | import config from '../../config' 9 | import styles from './index.less' 10 | 11 | const { gitalkOption, friendsOption } = config 12 | const { enableGitalk, qoute } = friendsOption 13 | const cx = classNames.bind(styles) 14 | 15 | class Friends extends PureComponent { 16 | constructor(props) { 17 | super(props) 18 | this.state = { 19 | showLoading: true, 20 | renderGitalk: false 21 | } 22 | } 23 | 24 | componentDidMount() { 25 | this.props.dispatch({ 26 | type: 'app/queryPage', 27 | payload: { type: 'friend' } 28 | }) 29 | } 30 | 31 | componentWillReceiveProps(nextProps) { 32 | if (!nextProps.loading && !_.isEmpty(nextProps.friends)) { 33 | this.setState({ showLoading: false }) 34 | } 35 | } 36 | 37 | componentWillUnmount() { 38 | this.props.dispatch({ 39 | type: 'app/updateState', 40 | payload: { friends: {} } 41 | }) 42 | } 43 | 44 | // 渲染评论 45 | renderGitalk = () => { 46 | if (enableGitalk && !this.state.renderGitalk) { 47 | setTimeout(() => { 48 | const gitalk = new Gitalk({ 49 | ...gitalkOption, 50 | title: '友链' 51 | }) 52 | gitalk.render('gitalk') 53 | }, 100) 54 | this.setState({ renderGitalk: true }) 55 | } 56 | } 57 | 58 | render({ friends, loading }, { showLoading }) { 59 | const section = 60 | friends.body && 61 | friends.body 62 | .trim() 63 | .split('## ') 64 | .filter(o => o.length > 0) 65 | .map(o => { 66 | const content = o.split('\r\n').filter(o => o.length) 67 | return { 68 | name: content[0], 69 | link: content[1].split('link:')[1], 70 | cover: content[2].split('cover:')[1], 71 | avatar: content[3].split('avatar:')[1] 72 | } 73 | }) 74 | 75 | return ( 76 |
77 | 83 |
84 | 85 |
86 | {section && 87 | section.map((o, i) => { 88 | return ( 89 | 90 | 91 |
92 | 93 | {o.name} 94 |
95 |
96 | ) 97 | })} 98 | {/* 添加四个空项目 */} 99 | {_.range(4).map((o, i) => { 100 | return ( 101 | 109 | ) 110 | })} 111 |
112 |
113 |
114 | 115 | {enableGitalk &&
} 116 | {showLoading && } 117 |
118 | ) 119 | } 120 | } 121 | 122 | export default connect(({ app, loading }) => ({ 123 | friends: app.friend, 124 | loading: loading.effects['app/queryPage'] 125 | }))(Friends) 126 | -------------------------------------------------------------------------------- /src/pages/about/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { connect } from 'dva' 3 | import _ from 'lodash' 4 | import Gitalk from 'gitalk' 5 | import classNames from 'classnames/bind' 6 | 7 | import { Transition, Quote, Segment, Loading } from '../../components' 8 | import config from '../../config' 9 | import styles from './index.less' 10 | 11 | const { gitalkOption, aboutOption, themeColors } = config 12 | const { enableGitalk, qoute, avatar, info, contact, project } = aboutOption 13 | const cx = classNames.bind(styles) 14 | const colors = _.shuffle(themeColors) 15 | 16 | class About extends PureComponent { 17 | constructor(props) { 18 | super(props) 19 | this.state = { 20 | showLoading: true, 21 | renderGitalk: false 22 | } 23 | } 24 | 25 | componentDidMount() { 26 | this.props.dispatch({ 27 | type: 'app/queryPage', 28 | payload: { type: 'about' } 29 | }) 30 | } 31 | 32 | componentWillReceiveProps(nextProps) { 33 | if (!nextProps.loading && !_.isEmpty(nextProps.about)) { 34 | this.setState({ showLoading: false }) 35 | } 36 | } 37 | 38 | componentWillUnmount() { 39 | this.props.dispatch({ 40 | type: 'app/updateState', 41 | payload: { about: {} } 42 | }) 43 | } 44 | 45 | // 渲染评论 46 | renderGitalk = () => { 47 | if (enableGitalk) { 48 | setTimeout(() => { 49 | const gitalk = new Gitalk({ 50 | ...gitalkOption, 51 | title: '关于' 52 | }) 53 | gitalk.render('gitalk') 54 | }, 100) 55 | this.setState({ renderGitalk: true }) 56 | } 57 | } 58 | 59 | render({ about, loading }, { showLoading }) { 60 | const section = 61 | about.body && 62 | about.body 63 | .trim() 64 | .split('## ') 65 | .filter(o => o.length) 66 | .map(o => { 67 | const title = o.match(/.+?\r\n/)[0] 68 | return { 69 | title, 70 | content: o.slice(title.length) 71 | } 72 | }) 73 | 74 | return ( 75 |
76 | 82 | 124 | 125 | 126 | {enableGitalk &&
} 127 | {showLoading && } 128 |
129 | ) 130 | } 131 | } 132 | 133 | export default connect(({ app, loading }) => ({ 134 | about: app.about, 135 | loading: loading.effects['app/queryPage'] 136 | }))(About) 137 | -------------------------------------------------------------------------------- /src/assets/lib/fireworks.js: -------------------------------------------------------------------------------- 1 | function updateCoords(e) { 2 | ;(pointerX = (e.clientX || e.touches[0].clientX) - canvasEl.getBoundingClientRect().left), 3 | (pointerY = e.clientY || e.touches[0].clientY - canvasEl.getBoundingClientRect().top) 4 | } 5 | function setParticuleDirection(e) { 6 | const t = (anime.random(0, 360) * Math.PI) / 180, 7 | a = anime.random(50, 180), 8 | n = [-1, 1][anime.random(0, 1)] * a 9 | return { 10 | x: e.x + n * Math.cos(t), 11 | y: e.y + n * Math.sin(t) 12 | } 13 | } 14 | function createParticule(e, t) { 15 | const a = {} 16 | return ( 17 | (a.x = e), 18 | (a.y = t), 19 | (a.color = colors[anime.random(0, colors.length - 1)]), 20 | (a.radius = anime.random(16, 32)), 21 | (a.endPos = setParticuleDirection(a)), 22 | (a.draw = function() { 23 | ctx.beginPath(), 24 | ctx.arc(a.x, a.y, a.radius, 0, 2 * Math.PI, !0), 25 | (ctx.fillStyle = a.color), 26 | ctx.fill() 27 | }), 28 | a 29 | ) 30 | } 31 | function createCircle(e, t) { 32 | var a = {} 33 | return ( 34 | (a.x = e), 35 | (a.y = t), 36 | (a.color = '#F00'), 37 | (a.radius = 0.1), 38 | (a.alpha = 0.5), 39 | (a.lineWidth = 6), 40 | (a.draw = function() { 41 | ;(ctx.globalAlpha = a.alpha), 42 | ctx.beginPath(), 43 | ctx.arc(a.x, a.y, a.radius, 0, 2 * Math.PI, !0), 44 | (ctx.lineWidth = a.lineWidth), 45 | (ctx.strokeStyle = a.color), 46 | ctx.stroke(), 47 | (ctx.globalAlpha = 1) 48 | }), 49 | a 50 | ) 51 | } 52 | function renderParticule(e) { 53 | for (var t = 0; t < e.animatables.length; t++) e.animatables[t].target.draw() 54 | } 55 | function animateParticules(e, t) { 56 | for (var a = createCircle(e, t), n = [], i = 0; i < numberOfParticules; i++) 57 | n.push(createParticule(e, t)) 58 | anime 59 | .timeline() 60 | .add({ 61 | targets: n, 62 | x: function(e) { 63 | return e.endPos.x 64 | }, 65 | y: function(e) { 66 | return e.endPos.y 67 | }, 68 | radius: 0.1, 69 | duration: anime.random(1200, 1800), 70 | easing: 'easeOutExpo', 71 | update: renderParticule 72 | }) 73 | .add({ 74 | targets: a, 75 | radius: anime.random(80, 160), 76 | lineWidth: 0, 77 | alpha: { 78 | value: 0, 79 | easing: 'linear', 80 | duration: anime.random(600, 800) 81 | }, 82 | duration: anime.random(1200, 1800), 83 | easing: 'easeOutExpo', 84 | update: renderParticule, 85 | offset: 0 86 | }) 87 | } 88 | function debounce(fn, delay) { 89 | var timer 90 | return function() { 91 | var context = this 92 | var args = arguments 93 | clearTimeout(timer) 94 | timer = setTimeout(function() { 95 | fn.apply(context, args) 96 | }, delay) 97 | } 98 | } 99 | var canvasEl, ctx, numberOfParticules, pointerX, pointerY, tap, colors, setCanvasSize, render 100 | 101 | function fireworks() { 102 | canvasEl = document.querySelector('.fireworks') 103 | if (canvasEl) { 104 | ;(ctx = canvasEl.getContext('2d')), 105 | (numberOfParticules = 30), 106 | (pointerX = 0), 107 | (pointerY = 0), 108 | (tap = 'mousedown'), 109 | (colors = ['#FF1461', '#18FF92', '#5A87FF', '#FBF38C']), 110 | (setCanvasSize = debounce(function() { 111 | ;(canvasEl.width = 2 * window.innerWidth), 112 | (canvasEl.height = 2 * window.innerHeight), 113 | (canvasEl.style.width = window.innerWidth + 'px'), 114 | (canvasEl.style.height = window.innerHeight + 'px'), 115 | canvasEl.getContext('2d').scale(2, 2) 116 | }, 500)), 117 | (render = anime({ 118 | duration: 1 / 0, 119 | update: function() { 120 | ctx.clearRect(0, 0, canvasEl.width, canvasEl.height) 121 | } 122 | })) 123 | document.addEventListener( 124 | tap, 125 | function(e) { 126 | 'sidebar' !== e.target.id && 127 | 'toggle-sidebar' !== e.target.id && 128 | 'A' !== e.target.nodeName && 129 | 'IMG' !== e.target.nodeName && 130 | (render.play(), updateCoords(e), animateParticules(pointerX, pointerY)) 131 | }, 132 | !1 133 | ), 134 | setCanvasSize(), 135 | window.addEventListener('resize', setCanvasSize, !1) 136 | } 137 | } 138 | 139 | export default fireworks 140 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { connect } from 'dva' 3 | import Link from 'umi/link' 4 | import classNames from 'classnames/bind' 5 | 6 | import { on } from '../../utils' 7 | import styles from './index.less' 8 | import config from '../../config' 9 | 10 | const cx = classNames.bind(styles) 11 | const { title, subtitle } = config 12 | 13 | class Header extends PureComponent { 14 | constructor(props) { 15 | super(props) 16 | this.state = { 17 | dropMenu: false 18 | } 19 | } 20 | 21 | componentDidMount() { 22 | this.bind() 23 | } 24 | 25 | // 绑定事件 26 | bind = () => { 27 | on(this.menuRef, 'mouseover', this.handleMouseOver) 28 | } 29 | 30 | // 监听: 菜单悬停并触发对话 31 | handleMouseOver = e => { 32 | let target 33 | if (e.target.tagName.toUpperCase() === 'LI') { 34 | target = e.target 35 | } else if (e.target.parentElement.tagName.toUpperCase() === 'LI') { 36 | target = e.target.parentElement 37 | } else { 38 | return 39 | } 40 | const menu = target.getAttribute('data-menu') 41 | if (this.menu === menu) return 42 | this.menu = menu 43 | let tips 44 | switch (menu) { 45 | case 'home': 46 | tips = '要回首页看看么~' 47 | break 48 | case 'archive': 49 | tips = '去看看主人的所有文章吧' 50 | break 51 | case 'categorie': 52 | tips = '去看看主人的文章吧' 53 | break 54 | case 'tag': 55 | tips = '去看看主人的文章吧' 56 | break 57 | case 'inspiration': 58 | tips = '主人最近又在发什么牢骚呢' 59 | break 60 | case 'book': 61 | tips = '主人最近再读什么书呢' 62 | break 63 | case 'friend': 64 | tips = '去看看主人的小伙伴吧' 65 | break 66 | case 'about': 67 | tips = '想要了解更多关于主人的故事么' 68 | break 69 | default: 70 | return 71 | } 72 | this.props.dispatch({ 73 | type: 'app/showTips', 74 | payload: { tips } 75 | }) 76 | } 77 | 78 | // 移动端展开菜单 79 | toggleMenu = () => { 80 | this.setState({ dropMenu: !this.state.dropMenu }) 81 | } 82 | 83 | render(props, { dropMenu }) { 84 | return ( 85 |
86 | 89 |
90 | 91 | {title} 92 | 93 | {subtitle} 94 |
    (this.menuRef = c)} class={cx('menu', dropMenu && 'dropMenu')}> 95 |
  • 96 | 97 | 98 | 首页 99 | 100 |
  • 101 |
  • 102 | 103 | 104 | 归档 105 | 106 |
  • 107 |
  • 108 | 109 | 110 | 分类 111 | 112 |
  • 113 |
  • 114 | 115 | 116 | 标签 117 | 118 |
  • 119 |
  • 120 | 121 | 122 | 灵感 123 | 124 |
  • 125 |
  • 126 | 127 | 128 | 书单 129 | 130 |
  • 131 |
  • 132 | 133 | 134 | 友链 135 | 136 |
  • 137 |
  • 138 | 139 | 140 | 关于 141 | 142 |
  • 143 |
144 |
145 |
146 | ) 147 | } 148 | } 149 | 150 | export default connect()(Header) 151 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { connect } from 'dva' 3 | import _ from 'lodash' 4 | import classNames from 'classnames/bind' 5 | 6 | import { Transition, PostCard, Loading } from '../components' 7 | import { isMobile, on } from '../utils' 8 | import styles from './index.less' 9 | 10 | const cx = classNames.bind(styles) 11 | 12 | class Home extends PureComponent { 13 | constructor(props) { 14 | super(props) 15 | this.state = { 16 | showLoading: true, 17 | disabledGif: false 18 | } 19 | } 20 | 21 | componentDidMount() { 22 | this.queryList(this.props.postList.length ? '' : 'next') 23 | } 24 | 25 | componentWillReceiveProps(nextProps) { 26 | if (!nextProps.loading && nextProps.postList.length) { 27 | this.setState({ showLoading: false }) 28 | } 29 | } 30 | 31 | queryList = queryType => { 32 | if (this.props.loading) return 33 | this.props.dispatch({ 34 | type: 'app/queryList', 35 | payload: { queryType } 36 | }) 37 | } 38 | 39 | // 卡片隐藏展示 Loading 40 | onHide = () => { 41 | this.setState({ showLoading: true }) 42 | } 43 | 44 | // 获取文章列表 45 | getPostListNode = () => { 46 | // 对于移动端只展示第一次动画 47 | if (isMobile) { 48 | this.setState({ disabledGif: true }) 49 | } 50 | if (this.TPostListNodeMouseOver) return 51 | this.TPostListNodeMouseOver = _.throttle(this.postListNodeMouseOver, 400, { trailing: true }) 52 | on(this.postListNode, 'mouseover', this.TPostListNodeMouseOver) 53 | } 54 | 55 | // 监听:文章卡片触发看板娘对话 56 | postListNodeMouseOver = e => { 57 | let target 58 | if (e.target.tagName.toUpperCase() === 'A') { 59 | target = e.target 60 | } else if (e.target.parentElement && e.target.parentElement.tagName.toUpperCase() === 'A') { 61 | target = e.target.parentElement 62 | } else if ( 63 | e.target.parentElement && 64 | e.target.parentElement.parentElement.parentElement && 65 | e.target.parentElement.parentElement.tagName.toUpperCase() === 'A' 66 | ) { 67 | target = e.target.parentElement.parentElement 68 | } else { 69 | return 70 | } 71 | const title = target.getAttribute('data-title') 72 | const tips = `要去看看 ${title} 吗?` 73 | this.props.dispatch({ 74 | type: 'app/showTips', 75 | payload: { tips } 76 | }) 77 | } 78 | 79 | // 前后翻页触发看板娘对话 80 | handleMouseOver = type => { 81 | const tips = `要到${type === 'prev' ? '上' : '下'}一页看看吗?(●'◡'●)` 82 | this.props.dispatch({ 83 | type: 'app/showTips', 84 | payload: { tips } 85 | }) 86 | } 87 | 88 | render({ totalList, postList, loading }, { showLoading, disabledGif }) { 89 | return ( 90 |
91 |
92 | 99 | 100 | 107 |
108 |
(this.postListNode = c)}> 109 | {postList.map(post => { 110 | return 111 | })} 112 |
113 |
114 |
115 | {showLoading && !disabledGif && } 116 | 117 | 124 | {totalList.length !== postList.length && ( 125 | 128 | )} 129 |
130 |
131 | ) 132 | } 133 | } 134 | 135 | export default connect(({ app, loading }) => ({ 136 | totalList: app.totalList, 137 | postList: app.postList, 138 | loading: loading.effects['app/queryList'] 139 | }))(Home) 140 | -------------------------------------------------------------------------------- /src/components/PostBody/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | border-radius: 0.03rem; 3 | .header { 4 | position: relative; 5 | text-align: left; 6 | border-radius: 0.03rem 0.03rem 0 0; 7 | overflow: hidden; 8 | &:hover { 9 | img { 10 | transform: scale(1.06); 11 | } 12 | } 13 | img { 14 | width: 100%; 15 | transition: transform 0.6s ease-out; 16 | } 17 | .info { 18 | position: absolute; 19 | bottom: 0; 20 | padding: 0.12rem 0.16rem; 21 | width: 100%; 22 | color: #eee; 23 | box-sizing: border-box; 24 | background: rgba(0, 0, 0, 0.4); 25 | } 26 | h1 { 27 | font-weight: normal; 28 | font-size: 0.22rem; 29 | color: #eee; 30 | letter-spacing: 1px; 31 | } 32 | .meta { 33 | padding-top: 0.06rem; 34 | & > span { 35 | margin-right: 0.06rem; 36 | i { 37 | margin-right: 0.04rem; 38 | } 39 | span { 40 | padding-right: 0.06rem; 41 | } 42 | } 43 | } 44 | } 45 | .content { 46 | margin: 0 auto; 47 | padding-bottom: 0.12rem; 48 | user-select: text; 49 | text-align: justify; 50 | span[class*='img-box'] { 51 | display: block; 52 | text-align: center; 53 | a { 54 | display: inline-block; 55 | padding: 0.12rem; 56 | border: 1px dashed rgba(0, 0, 0, 0.2); 57 | border-radius: 0.03rem; 58 | } 59 | span { 60 | display: block; 61 | font-style: italic; 62 | text-align: center; 63 | } 64 | img { 65 | max-width: 100%; 66 | margin: 0 auto; 67 | box-shadow: 0 0 10px #999; 68 | } 69 | } 70 | span[class*='full-box'] { 71 | a { 72 | display: inline-block; 73 | padding: 0; 74 | border: none; 75 | border-radius: 0; 76 | } 77 | img { 78 | max-width: calc(~'100% + .32rem'); 79 | margin-left: -0.16rem; 80 | box-shadow: 0 0 10px #999; 81 | } 82 | } 83 | 84 | p, 85 | ul, 86 | ol, 87 | blockquote { 88 | line-height: 1.6; 89 | font-size: 0.16rem; 90 | } 91 | p { 92 | margin: 0.14rem 0.16rem 0; 93 | } 94 | blockquote { 95 | position: relative; 96 | padding: 0.3rem 0; 97 | margin: 1em 0; 98 | font-style: italic; 99 | box-shadow: #999 0px 11px 8px -10px inset, #999 0px -11px 8px -10px inset; 100 | & > p { 101 | margin: 0.12rem 0.4rem; 102 | } 103 | &::before, 104 | &::after { 105 | position: absolute; 106 | font-family: Fontello; 107 | font-style: normal; 108 | font-weight: normal; 109 | text-decoration: inherit; 110 | /*--adjust as necessary--*/ 111 | color: #666; 112 | font-size: 0.24rem; 113 | } 114 | &::before { 115 | content: '\f10d'; 116 | top: 0.06rem; 117 | left: 0.16rem; 118 | } 119 | &::after { 120 | content: '\f10e'; 121 | bottom: 0.06rem; 122 | right: 0.16rem; 123 | } 124 | } 125 | ol, 126 | ul { 127 | margin: 0.06rem 0.24rem 0.06rem 0.42rem; 128 | } 129 | h2, 130 | h3, 131 | h4 { 132 | margin: 0.14rem 0.16rem; 133 | } 134 | h2 { 135 | padding: 0.08rem 0; 136 | font-size: 0.26rem; 137 | border-bottom: 1px dashed rgba(0, 0, 0, 0.2); 138 | i { 139 | padding-right: 0.04rem; 140 | font-size: 0.24rem; 141 | } 142 | } 143 | h3 { 144 | font-size: 0.22rem; 145 | i { 146 | padding-right: 0.04rem; 147 | font-size: 0.16rem; 148 | } 149 | } 150 | h4 { 151 | font-size: 0.18rem; 152 | i { 153 | padding-right: 0.04rem; 154 | font-size: 0.16rem; 155 | } 156 | } 157 | a { 158 | border-bottom: 1px solid #888; 159 | &:hover { 160 | &:hover { 161 | color: #f6f; 162 | } 163 | border-bottom-color: #f6f; 164 | } 165 | } 166 | table { 167 | margin: 0.14rem auto; 168 | border: 1px solid rgba(0, 0, 0, 0.08); 169 | border-radius: 0.03rem; 170 | border-collapse: separate; 171 | border-spacing: 0; 172 | tr { 173 | height: 0.32rem; 174 | } 175 | tr:nth-child(2n), 176 | thead > tr { 177 | background-color: rgba(0, 0, 0, 0.1); 178 | } 179 | td, 180 | th { 181 | padding: 0 0.36rem; 182 | border-right: 1px solid rgba(0, 0, 0, 0.08); 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/assets/prism/prism.less: -------------------------------------------------------------------------------- 1 | code[class*='language-'], 2 | pre[class*='language-'] { 3 | color: #666; 4 | /* base00 */ 5 | font-family: 'FiraCode', 'Helvetica', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 6 | text-shadow: 0 0 1px rgba(0, 0, 0, 0.4); 7 | text-align: left; 8 | white-space: pre; 9 | word-spacing: normal; 10 | word-break: normal; 11 | word-wrap: normal; 12 | line-height: 1.5; 13 | -moz-tab-size: 4; 14 | -o-tab-size: 4; 15 | tab-size: 4; 16 | -webkit-hyphens: none; 17 | -moz-hyphens: none; 18 | -ms-hyphens: none; 19 | hyphens: none; 20 | } 21 | 22 | pre[class*='language-']::-moz-selection, 23 | pre[class*='language-'] ::-moz-selection, 24 | code[class*='language-']::-moz-selection, 25 | code[class*='language-'] ::-moz-selection { 26 | color: currentColor; 27 | background: rgba(0, 0, 0, 0.2); 28 | /* base02 */ 29 | } 30 | 31 | pre[class*='language-']::selection, 32 | pre[class*='language-'] ::selection, 33 | code[class*='language-']::selection, 34 | code[class*='language-'] ::selection { 35 | color: currentColor; 36 | background: rgba(0, 0, 0, 0.2); 37 | /* base02 */ 38 | } 39 | 40 | /* Code blocks */ 41 | 42 | pre[class*='language-'] { 43 | padding: 1em; 44 | margin: 1em 0; 45 | overflow: auto; 46 | } 47 | 48 | :not(pre) > code[class*='language-'], 49 | pre[class*='language-'] { 50 | box-shadow: rgb(153, 153, 153) 0px 11px 8px -10px inset, 51 | rgb(153, 153, 153) 0px -11px 8px -10px inset; 52 | padding: 10px 16px; 53 | background: rgba(0, 0, 0, 0.06); 54 | overflow: scroll; 55 | max-height: 6rem; 56 | line-height: 1.5; 57 | &::-webkit-scrollbar { 58 | width: 4px; 59 | height: 4px; 60 | background-color: transparent; 61 | } 62 | &::-webkit-scrollbar-thumb { 63 | background-color: #888; 64 | background-image: -webkit-linear-gradient( 65 | 45deg, 66 | rgba(255, 255, 255, 0.6) 25%, 67 | transparent 25%, 68 | transparent 50%, 69 | rgba(255, 255, 255, 0.6) 50%, 70 | rgba(255, 255, 255, 0.6) 75%, 71 | transparent 75%, 72 | transparent 73 | ); 74 | border-radius: 4px; 75 | } 76 | &::-webkit-scrollbar-track { 77 | background-color: transparent; 78 | } 79 | code { 80 | padding: 0; 81 | color: currentColor; 82 | background-color: transparent; 83 | } 84 | /* base3 */ 85 | } 86 | 87 | /* Inline code */ 88 | 89 | :not(pre) > code[class*='language-'] { 90 | padding: 0.1em; 91 | border-radius: 0.3em; 92 | } 93 | 94 | .token.comment, 95 | .token.prolog, 96 | .token.doctype, 97 | .token.cdata { 98 | color: #9c9491; 99 | /* base1 */ 100 | } 101 | 102 | .token.punctuation { 103 | color: #586e75; 104 | /* base01 */ 105 | } 106 | 107 | .namespace { 108 | opacity: 0.7; 109 | } 110 | 111 | .token.property, 112 | .token.tag, 113 | .token.boolean, 114 | .token.number, 115 | .token.constant, 116 | .token.symbol, 117 | .token.deleted { 118 | color: #268bd2; 119 | /* blue */ 120 | } 121 | 122 | .token.selector, 123 | .token.attr-name, 124 | .token.string, 125 | .token.char, 126 | .token.builtin, 127 | .token.url, 128 | .token.inserted { 129 | color: #2aa198; 130 | /* cyan */ 131 | } 132 | 133 | .token.entity { 134 | color: #657b83; 135 | /* base00 */ 136 | background: #eee8d5; 137 | /* base2 */ 138 | } 139 | 140 | .token.atrule, 141 | .token.attr-value, 142 | .token.keyword { 143 | color: #859900; 144 | /* green */ 145 | } 146 | 147 | .token.function, 148 | .token.class-name { 149 | color: #b58900; 150 | /* yellow */ 151 | } 152 | 153 | .token.regex, 154 | .token.important, 155 | .token.variable { 156 | color: #cb4b16; 157 | /* orange */ 158 | } 159 | 160 | .token.important, 161 | .token.bold { 162 | font-weight: bold; 163 | } 164 | 165 | .token.italic { 166 | font-style: italic; 167 | } 168 | 169 | .token.entity { 170 | cursor: help; 171 | } 172 | 173 | /* 行号 */ 174 | 175 | pre[class*='language-'].line-numbers { 176 | position: relative; 177 | padding-left: 3em; 178 | counter-reset: linenumber; 179 | } 180 | 181 | pre[class*='language-'].line-numbers > code { 182 | position: relative; 183 | white-space: inherit; 184 | } 185 | 186 | .line-numbers .line-numbers-rows { 187 | position: absolute; 188 | pointer-events: none; 189 | top: -0.024rem; 190 | left: -3em; 191 | width: 2.6em; 192 | font-size: 100%; 193 | line-height: 1.6; 194 | /* works for line-numbers below 1000 lines */ 195 | letter-spacing: -1px; 196 | user-select: none; 197 | } 198 | 199 | .line-numbers-rows > span { 200 | pointer-events: none; 201 | display: block; 202 | counter-increment: linenumber; 203 | } 204 | 205 | .line-numbers-rows > span:before { 206 | content: counter(linenumber); 207 | display: block; 208 | padding-right: 0.8em; 209 | color: #888; 210 | text-align: right; 211 | } 212 | -------------------------------------------------------------------------------- /src/pages/tag/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { connect } from 'dva' 3 | import _ from 'lodash' 4 | import Gitalk from 'gitalk' 5 | import classNames from 'classnames/bind' 6 | 7 | import { Transition, Archive, Pagination, Quote, Loading } from '../../components' 8 | import config from '../../config' 9 | import styles from './index.less' 10 | 11 | const { gitalkOption, tagsOption, themeColors } = config 12 | const { enableGitalk, qoute } = tagsOption 13 | const cx = classNames.bind(styles) 14 | const colors = _.shuffle(themeColors) 15 | 16 | class Tags extends PureComponent { 17 | constructor(props) { 18 | super(props) 19 | this.state = { 20 | showLoading: true, 21 | renderGitalk: false, 22 | filterTitle: '', 23 | filterPost: [], 24 | currList: [], 25 | pageSize: 10, 26 | page: 1, 27 | maxPage: 1 28 | } 29 | } 30 | 31 | componentDidMount() { 32 | this.props.dispatch({ 33 | type: 'app/queryTags' 34 | }) 35 | } 36 | componentWillReceiveProps(nextProps) { 37 | if (!nextProps.loading && nextProps.tags.length) { 38 | this.setState({ showLoading: false }) 39 | } 40 | } 41 | 42 | componentWillUnmount() { 43 | this.props.dispatch({ 44 | type: 'app/updateState', 45 | payload: { tags: [] } 46 | }) 47 | } 48 | 49 | // 筛选文章 50 | filterPost = tag => { 51 | this.props 52 | .dispatch({ 53 | type: 'app/filterPost', 54 | payload: { 55 | type: 'labels', 56 | filter: tag 57 | } 58 | }) 59 | .then(v => { 60 | const currList = v.slice(0, this.state.pageSize) 61 | const maxPage = Math.ceil(v.length / this.state.pageSize) 62 | this.setState({ 63 | filterTitle: tag, 64 | filterPost: v, 65 | currList, 66 | page: 1, 67 | maxPage 68 | }) 69 | }) 70 | .catch(console.error) 71 | } 72 | 73 | // 清空文章 74 | clearFilter = () => { 75 | this.setState({ 76 | filterTitle: '', 77 | filterPost: [], 78 | currList: [], 79 | page: 1, 80 | maxPage: 1 81 | }) 82 | } 83 | 84 | // 前一页 85 | prev = () => { 86 | const { filterPost, page, pageSize } = this.state 87 | const prevPage = page - 1 88 | const currList = filterPost.slice((prevPage - 1) * pageSize, (page - 1) * pageSize) 89 | this.setState({ 90 | currList, 91 | page: prevPage 92 | }) 93 | } 94 | 95 | // 后一页 96 | next = () => { 97 | const { filterPost, page, pageSize } = this.state 98 | const nextPage = page + 1 99 | const currList = filterPost.slice(page * pageSize, nextPage * pageSize) 100 | this.setState({ 101 | currList, 102 | page: nextPage 103 | }) 104 | } 105 | 106 | // 渲染评论 107 | renderGitalk = () => { 108 | if (enableGitalk && !this.state.renderGitalk) { 109 | setTimeout(() => { 110 | const gitalk = new Gitalk({ 111 | ...gitalkOption, 112 | title: '标签' 113 | }) 114 | gitalk.render('gitalk') 115 | }, 100) 116 | this.setState({ renderGitalk: true }) 117 | } 118 | } 119 | 120 | render({ tags, loading }, { showLoading, filterTitle, currList, page, maxPage }) { 121 | return ( 122 |
123 | 129 |
130 | 131 |
132 | {tags.map((o, i) => { 133 | const color = colors[i % colors.length] 134 | return ( 135 | 138 | ) 139 | })} 140 |
141 | 142 |
143 |
144 | Tag: 145 | 149 |
150 |
151 | {currList.map((o, i) => { 152 | const color = colors[i] 153 | return 154 | })} 155 |
156 | 157 |
158 |
159 |
160 |
161 | 162 | {enableGitalk &&
} 163 | {showLoading && } 164 |
165 | ) 166 | } 167 | } 168 | 169 | export default connect(({ app, loading }) => ({ 170 | tags: app.tags, 171 | loading: loading.effects['app/queryTags'] 172 | }))(Tags) 173 | -------------------------------------------------------------------------------- /src/pages/book/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { connect } from 'dva' 3 | import _ from 'lodash' 4 | import Gitalk from 'gitalk' 5 | import classNames from 'classnames/bind' 6 | 7 | import { Transition, Quote, Loading } from '../../components' 8 | import config from '../../config' 9 | import styles from './index.less' 10 | 11 | const { gitalkOption, booksOption } = config 12 | const { enableGitalk, qoute } = booksOption 13 | const cx = classNames.bind(styles) 14 | 15 | class Books extends PureComponent { 16 | constructor(props) { 17 | super(props) 18 | this.state = { 19 | showLoading: true, 20 | renderGitalk: false 21 | } 22 | } 23 | 24 | componentDidMount() { 25 | this.props.dispatch({ 26 | type: 'app/queryPage', 27 | payload: { type: 'book' } 28 | }) 29 | } 30 | 31 | componentWillReceiveProps(nextProps) { 32 | if (!nextProps.loading && !_.isEmpty(nextProps.books)) { 33 | this.setState({ showLoading: false }) 34 | } 35 | } 36 | 37 | componentWillUnmount() { 38 | this.props.dispatch({ 39 | type: 'app/updateState', 40 | payload: { books: {} } 41 | }) 42 | } 43 | 44 | // 渲染评论 45 | renderGitalk = () => { 46 | if (enableGitalk) { 47 | setTimeout(() => { 48 | const gitalk = new Gitalk({ 49 | ...gitalkOption, 50 | title: '书单' 51 | }) 52 | gitalk.render('gitalk') 53 | }, 100) 54 | this.setState({ renderGitalk: true }) 55 | } 56 | } 57 | 58 | render({ books, loading }, { showLoading }) { 59 | const section = 60 | books.body && 61 | books.body 62 | .trim() 63 | .split('## ') 64 | .filter(o => o.length) 65 | .map(o => { 66 | const content = o.split('\r\n').filter(o => o.length) 67 | return { 68 | name: content[0], 69 | author: content[1].split('author:')[1].trim(), 70 | published: content[2].split('published:')[1].trim(), 71 | progress: content[3].split('progress:')[1].trim(), 72 | rating: content[4].split('rating:')[1].trim(), 73 | postTitle: content[5].split('postTitle:')[1].trim(), 74 | postLink: content[6].split('postLink:')[1].trim(), 75 | cover: content[7].split('cover:')[1].trim(), 76 | desc: content[9].split('desc:')[1].trim() 77 | } 78 | }) 79 | 80 | return ( 81 |
82 | 88 |
89 | 90 |
91 | {section && 92 | section.map((o, i) => { 93 | const { 94 | name, 95 | author, 96 | published, 97 | progress, 98 | rating, 99 | postTitle, 100 | postLink, 101 | cover, 102 | desc 103 | } = o 104 | const rateList = new Array(10).fill(1).map((o, i) => { 105 | return ( 106 | 107 |  108 | 109 | ) 110 | }) 111 | const rate = [].slice.call( 112 | rateList, 113 | 5 - parseInt(rating, 10), 114 | 10 - parseInt(rating, 10) 115 | ) 116 | return ( 117 |
118 |

{name}

119 |
120 | 121 |
122 |

{name}

123 |

作者:{author}

124 |

出版时间:{published}

125 |

阅读进度:{progress}

126 |

127 | 读书笔记: 128 | {!!postLink.trim().length ? ( 129 | 130 | {postTitle} 131 | 132 | ) : ( 133 | '暂无' 134 | )} 135 |

136 |

推荐指数:{rate}

137 |
138 |
139 |
{desc}
140 |
141 | ) 142 | })} 143 |
144 |
145 |
146 | 147 | {enableGitalk &&
} 148 | {showLoading && } 149 |
150 | ) 151 | } 152 | } 153 | 154 | export default connect(({ app, loading }) => ({ 155 | books: app.book, 156 | loading: loading.effects['app/queryPage'] 157 | }))(Books) 158 | -------------------------------------------------------------------------------- /src/pages/category/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { connect } from 'dva' 3 | import _ from 'lodash' 4 | import Gitalk from 'gitalk' 5 | import classNames from 'classnames/bind' 6 | 7 | import { Transition, Archive, Quote, Pagination, Loading } from '../../components' 8 | import config from '../../config' 9 | import styles from './index.less' 10 | 11 | const { gitalkOption, catsOption, themeColors } = config 12 | const { enableGitalk, qoute, list } = catsOption 13 | const cx = classNames.bind(styles) 14 | const colors = _.shuffle(themeColors) 15 | 16 | class Categories extends PureComponent { 17 | constructor(props) { 18 | super(props) 19 | this.state = { 20 | showLoading: true, 21 | renderGitalk: false, 22 | filterTitle: '', 23 | filterPost: [], 24 | currList: [], 25 | pageSize: 10, 26 | page: 1, 27 | maxPage: 1 28 | } 29 | } 30 | 31 | componentDidMount() { 32 | this.props.dispatch({ 33 | type: 'app/queryCats' 34 | }) 35 | } 36 | 37 | componentWillReceiveProps(nextProps) { 38 | if (!nextProps.loading && nextProps.cats.length) { 39 | this.setState({ showLoading: false }) 40 | } 41 | } 42 | 43 | componentWillUnmount() { 44 | this.props.dispatch({ 45 | type: 'app/updateState', 46 | payload: { cats: [] } 47 | }) 48 | } 49 | 50 | // 筛选文章 51 | filterPost = cat => { 52 | this.props 53 | .dispatch({ 54 | type: 'app/filterPost', 55 | payload: { 56 | type: 'milestone', 57 | filter: cat.number 58 | } 59 | }) 60 | .then(v => { 61 | const currList = v.slice(0, this.state.pageSize) 62 | const maxPage = Math.ceil(v.length / this.state.pageSize) 63 | this.setState({ 64 | filterTitle: cat.title, 65 | filterPost: v, 66 | currList, 67 | page: 1, 68 | maxPage 69 | }) 70 | }) 71 | .catch(console.error) 72 | } 73 | 74 | // 清空文章 75 | clearFilter = () => { 76 | this.setState({ 77 | filterTitle: '', 78 | filterPost: [], 79 | currList: [], 80 | page: 1, 81 | maxPage: 1 82 | }) 83 | } 84 | 85 | // 前一页 86 | prev = () => { 87 | const { filterPost, page, pageSize } = this.state 88 | const prevPage = page - 1 89 | const currList = filterPost.slice((prevPage - 1) * pageSize, (page - 1) * pageSize) 90 | this.setState({ 91 | currList, 92 | page: prevPage 93 | }) 94 | } 95 | 96 | // 后一页 97 | next = () => { 98 | const { filterPost, page, pageSize } = this.state 99 | const nextPage = page + 1 100 | const currList = filterPost.slice(page * pageSize, nextPage * pageSize) 101 | this.setState({ 102 | currList, 103 | page: nextPage 104 | }) 105 | } 106 | 107 | // 渲染评论 108 | renderGitalk = () => { 109 | if (enableGitalk && !this.state.renderGitalk) { 110 | setTimeout(() => { 111 | const gitalk = new Gitalk({ 112 | ...gitalkOption, 113 | title: '分类' 114 | }) 115 | gitalk.render('gitalk') 116 | }, 100) 117 | this.setState({ renderGitalk: true }) 118 | } 119 | } 120 | 121 | render({ cats, loading }, { showLoading, filterTitle, currList, page, maxPage }) { 122 | return ( 123 |
124 | 130 |
131 | 132 |
133 | {cats.map((o, i) => { 134 | const info = list.find(cat => cat.name === o.title) 135 | const catText = info.text 136 | const catImg = info.img 137 | return ( 138 |
{ 142 | this.filterPost(o) 143 | }} 144 | > 145 | 146 |
147 |
148 | 149 | 150 | {o.title} ({o.open_issues}) 151 | 152 |
153 |

{catText}

154 |
155 |
156 | ) 157 | })} 158 |
159 | 160 |
161 |
162 | Category: 163 | 167 |
168 |
169 | {currList.map((o, i) => { 170 | const color = colors[i] 171 | return 172 | })} 173 |
174 | 175 |
176 |
177 |
178 |
179 | 180 | {enableGitalk &&
} 181 | {showLoading && } 182 |
183 | ) 184 | } 185 | } 186 | 187 | export default connect(({ app, loading }) => ({ 188 | cats: app.cats, 189 | loading: loading.effects['app/queryCats'] 190 | }))(Categories) 191 | -------------------------------------------------------------------------------- /public/live2d/mtn/Breath2.mtn: -------------------------------------------------------------------------------- 1 | # Live2D Animator Motion Data 2 | $fps=30 3 | 4 | $fadein=0 5 | 6 | $fadeout=0 7 | 8 | PARAM_ANGLE_X=0 9 | PARAM_ANGLE_Y=0 10 | PARAM_ANGLE_Z=0,0.021,0.08,0.17,0.3,0.45,0.62,0.82,1.03,1.25,1.49,1.73,1.97,2.21,2.45,2.68,2.9,3.11,3.3,3.47,3.63,3.76,3.86,3.94,3.98,4,3.983,3.93,3.85,3.74,3.59,3.43,3.23,3.02,2.78,2.53,2.25,1.97,1.66,1.35,1.03,0.7,0.36,0.02,-0.33,-0.67,-1.02,-1.36,-1.7,-2.03,-2.35,-2.66,-2.97,-3.25,-3.53,-3.78,-4.02,-4.23,-4.43,-4.59,-4.74,-4.85,-4.93,-4.98,-5,-4.97,-4.9,-4.79,-4.63,-4.44,-4.22,-3.98,-3.71,-3.43,-3.14,-2.84,-2.54,-2.24,-1.94,-1.65,-1.37,-1.12,-0.88,-0.66,-0.47,-0.31,-0.18,-0.08,-0.02,0 11 | PARAM_EMOTION=-1 12 | PARAM_EYE_L_OPEN=0.55,0.55,0.55,0.55,0.55,0.549,0.55,0.55,0.547,0.547,0.55,0.545,0.545,0.544,0.54,0.542,0.541,0.54,0.539,0.538,0.537,0.536,0.536,0.534,0.534,0.533,0.532,0.531,0.53,0.529,0.528,0.527,0.53,0.525,0.525,0.524,0.52,0.523,0.522,0.52,0.52,0.521,0.52,0.52,0.52,0.52,0.52,0.52,0.52,0.52,0.521,0.52,0.522,0.52,0.523,0.524,0.52,0.526,0.527,0.528,0.528,0.529,0.53,0.531,0.532,0.533,0.534,0.535,0.536,0.537,0.538,0.539,0.54,0.541,0.542,0.543,0.54,0.545,0.545,0.546,0.55,0.547,0.548,0.55,0.55,0.549,0.55,0.55,0.55,0.55 13 | PARAM_EYE_R_OPEN=0.55,0.55,0.55,0.55,0.55,0.549,0.55,0.55,0.547,0.547,0.55,0.545,0.545,0.544,0.54,0.542,0.541,0.54,0.539,0.538,0.537,0.536,0.536,0.534,0.534,0.533,0.532,0.531,0.53,0.529,0.528,0.527,0.53,0.525,0.525,0.524,0.52,0.523,0.522,0.52,0.52,0.521,0.52,0.52,0.52,0.52,0.52,0.52,0.52,0.52,0.521,0.52,0.522,0.52,0.523,0.524,0.52,0.526,0.527,0.528,0.528,0.529,0.53,0.531,0.532,0.533,0.534,0.535,0.536,0.537,0.538,0.539,0.54,0.541,0.542,0.543,0.54,0.545,0.545,0.546,0.55,0.547,0.548,0.55,0.55,0.549,0.55,0.55,0.55,0.55 14 | PARAM_EYE_L_OPEN2=-1 15 | PARAM_EYE_R_OPEN2=-1 16 | PARAM_EYE_BALL_X=0 17 | PARAM_EYE_BALL_Y=0 18 | PARAM_BROW_L_Y=0 19 | PARAM_BROW_R_Y=0 20 | PARAM_BROW_ANGLE=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 21 | PARAM_BROW_SELECT=-0.5 22 | PARAM_MOUTH_OPEN_Y=0 23 | PARAM_MOUTH_OPEN2=0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9 24 | PARAM_MOUTH_EMO=0 25 | PARAM_CHEEK=0 26 | PARAM_BODY_ANGLE_X=0,-0.01,-0.04,-0.09,-0.15,-0.22,-0.31,-0.41,-0.52,-0.63,-0.74,-0.86,-0.99,-1.11,-1.22,-1.34,-1.45,-1.55,-1.65,-1.74,-1.81,-1.88,-1.93,-1.97,-1.99,-2,-1.992,-1.97,-1.93,-1.88,-1.82,-1.75,-1.66,-1.56,-1.46,-1.35,-1.22,-1.1,-0.96,-0.82,-0.68,-0.53,-0.38,-0.23,-0.08,0.08,0.23,0.38,0.53,0.68,0.82,0.96,1.1,1.22,1.35,1.46,1.56,1.66,1.75,1.82,1.88,1.93,1.97,1.99,2,1.99,1.96,1.91,1.85,1.78,1.69,1.59,1.48,1.37,1.26,1.14,1.01,0.89,0.78,0.66,0.55,0.45,0.35,0.26,0.19,0.12,0.07,0.03,0.01,0 27 | PARAM_BODY_ANGLE_Z=0 28 | PARAM_BODY_Y=0,-0.002,-0.007,-0.015,-0.025,-0.038,-0.053,-0.07,-0.088,-0.107,-0.127,-0.147,-0.17,-0.188,-0.21,-0.228,-0.247,-0.264,-0.28,-0.295,-0.308,-0.319,-0.328,-0.335,-0.339,-0.34,-0.339,-0.335,-0.329,-0.321,-0.312,-0.3,-0.286,-0.271,-0.255,-0.237,-0.218,-0.198,-0.18,-0.15,-0.13,-0.11,-0.09,-0.06,-0.04,-0.01,0.01,0.04,0.06,0.08,0.1,0.13,0.15,0.168,0.187,0.205,0.221,0.236,0.25,0.262,0.271,0.279,0.285,0.289,0.29,0.289,0.284,0.278,0.269,0.258,0.245,0.231,0.215,0.199,0.182,0.165,0.147,0.13,0.113,0.096,0.08,0.065,0.051,0.038,0.027,0.018,0.01,0.005,0.001,0 29 | PARAM_BREATH=0.5,0.5,0.5,0.502,0.504,0.506,0.508,0.51,0.513,0.517,0.52,0.524,0.528,0.532,0.537,0.542,0.546,0.551,0.556,0.562,0.567,0.572,0.577,0.583,0.588,0.593,0.598,0.604,0.609,0.614,0.618,0.623,0.628,0.632,0.636,0.64,0.643,0.647,0.65,0.652,0.654,0.656,0.658,0.659,0.66,0.66,0.66,0.659,0.658,0.656,0.654,0.651,0.648,0.645,0.642,0.638,0.634,0.629,0.625,0.62,0.615,0.61,0.605,0.6,0.594,0.589,0.583,0.578,0.573,0.567,0.562,0.557,0.552,0.546,0.542,0.537,0.532,0.528,0.524,0.52,0.517,0.513,0.51,0.508,0.506,0.504,0.502,0.501,0.5,0.5 30 | PARAM_BOING=0 31 | PARAM_HAIR_FRONT=0,0,0.002,0.005,0.009,0.013,0.019,0.025,0.031,0.038,0.045,0.052,0.059,0.066,0.073,0.08,0.087,0.093,0.099,0.104,0.109,0.113,0.116,0.118,0.12,0.12,0.119,0.118,0.115,0.112,0.108,0.103,0.097,0.091,0.083,0.076,0.068,0.059,0.05,0.041,0.031,0.021,0.011,0.001,-0.01,-0.02,-0.031,-0.041,-0.051,-0.061,-0.071,-0.08,-0.089,-0.098,-0.106,-0.113,-0.121,-0.127,-0.133,-0.138,-0.142,-0.145,-0.148,-0.149,-0.15,-0.149,-0.147,-0.144,-0.139,-0.133,-0.127,-0.119,-0.111,-0.103,-0.094,-0.085,-0.076,-0.067,-0.058,-0.05,-0.041,-0.033,-0.026,-0.02,-0.014,-0.009,-0.005,-0.002,-0.001,0 32 | PARAM_HAIR_SIDE_R=0,0.002,0.008,0.018,0.031,0.047,0.065,0.09,0.11,0.13,0.16,0.18,0.21,0.23,0.26,0.28,0.3,0.33,0.346,0.365,0.381,0.394,0.405,0.413,0.418,0.42,0.418,0.414,0.406,0.396,0.383,0.368,0.35,0.331,0.31,0.29,0.26,0.23,0.21,0.18,0.15,0.12,0.09,0.06,0.03,-0.01,-0.04,-0.07,-0.1,-0.13,-0.16,-0.19,-0.21,-0.24,-0.27,-0.29,-0.31,-0.33,-0.348,-0.363,-0.376,-0.386,-0.394,-0.398,-0.4,-0.398,-0.392,-0.383,-0.37,-0.355,-0.338,-0.318,-0.3,-0.27,-0.25,-0.23,-0.2,-0.18,-0.16,-0.13,-0.11,-0.089,-0.07,-0.053,-0.037,-0.024,-0.014,-0.006,-0.002,0 33 | PARAM_HAIR_SIDE_L=0,-0.003,-0.01,-0.021,-0.036,-0.055,-0.08,-0.1,-0.13,-0.15,-0.18,-0.21,-0.24,-0.27,-0.3,-0.33,-0.36,-0.38,-0.4,-0.43,-0.444,-0.46,-0.473,-0.482,-0.488,-0.49,-0.488,-0.484,-0.476,-0.466,-0.452,-0.437,-0.419,-0.4,-0.38,-0.35,-0.33,-0.3,-0.27,-0.25,-0.22,-0.19,-0.15,-0.12,-0.09,-0.06,-0.03,0,0.04,0.07,0.1,0.12,0.15,0.18,0.2,0.23,0.25,0.269,0.287,0.302,0.316,0.326,0.334,0.338,0.34,0.338,0.333,0.325,0.315,0.302,0.287,0.27,0.252,0.233,0.213,0.193,0.17,0.152,0.13,0.112,0.093,0.076,0.06,0.045,0.032,0.021,0.012,0.005,0.001,0 34 | PARAM_TWIN_RIBBON_D=0 35 | PARAM_HAIR_BACK=0 36 | PARAM_WING_ANGLE=0 37 | PARAM_WING_DEFORM=0 38 | VISIBLE:PSD=1 39 | VISIBLE:PARTS_01_HAT=1 40 | VISIBLE:PARTS_01_HAIR_FRONT_001=1 41 | VISIBLE:PARTS_01_HAIR_SIDE_001=1 42 | VISIBLE:PARTS_01_HAIR_BACK_001=1 43 | VISIBLE:PARTS_01_FACE_001=1 44 | VISIBLE:PARTS_01_BROW_001=1 45 | VISIBLE:PARTS_01_EMOTION=1 46 | VISIBLE:PARTS_01_EYE_001=1 47 | VISIBLE:PARTS_01_EYE_BALL_001=1 48 | VISIBLE:PARTS_01_NOSE_001=1 49 | VISIBLE:PARTS_01_MOUTH_001=1 50 | VISIBLE:PARTS_01_EAR_001=1 51 | VISIBLE:PARTS_01_BUST=1 52 | VISIBLE:PARTS_01_BODY=1 53 | VISIBLE:PARTS_01_WING=1 -------------------------------------------------------------------------------- /src/services.js: -------------------------------------------------------------------------------- 1 | import AV from 'leancloud-storage' 2 | import fetch from 'dva/fetch' 3 | import config from './config' 4 | 5 | const { blog, pre, suf, creator } = config 6 | const token = `access_token=${pre}${suf}` 7 | const open = `creator=${creator}&state=open&${token}` 8 | const closed = `creator=${creator}&state=closed&${token}` 9 | 10 | // 状态检测 11 | const checkStatus = response => { 12 | if (response.status >= 200 && response.status < 300) return response 13 | const error = new Error(response.statusText) 14 | error.response = response 15 | throw error 16 | } 17 | 18 | // 文章总数,一次获取全部文章,先以 200 做限制 19 | export const queryTotal = async () => { 20 | try { 21 | const url = `${blog}/issues?${open}&page=1&per_page=200&` 22 | const response = await fetch(url) 23 | checkStatus(response) 24 | const data = await response.json() 25 | return data 26 | } catch (err) { 27 | console.log(err) 28 | } 29 | } 30 | 31 | // 分类 32 | export const queryCats = async () => { 33 | try { 34 | const url = `${blog}/milestones?${token}` 35 | const response = await fetch(url) 36 | checkStatus(response) 37 | const data = await response.json() 38 | return data 39 | } catch (err) { 40 | console.log(err) 41 | } 42 | } 43 | 44 | // 标签 45 | export const queryTags = async () => { 46 | try { 47 | const url = `${blog}/labels?${token}` 48 | const response = await fetch(url) 49 | checkStatus(response) 50 | const data = await response.json() 51 | return data 52 | } catch (err) { 53 | console.log(err) 54 | } 55 | } 56 | 57 | // 筛选文章 58 | export const queryFilterPost = async ({ type, filter }) => { 59 | try { 60 | const url = `${blog}/issues?${type}=${filter}&${token}` 61 | const response = await fetch(url) 62 | checkStatus(response) 63 | const data = await response.json() 64 | return data 65 | } catch (err) { 66 | console.log(err) 67 | } 68 | } 69 | 70 | // 说说总数 71 | export const queryInspirationTotal = async () => { 72 | try { 73 | const url = `${blog}/issues?${closed}&labels=inspiration&page=1&per_page=200` 74 | const response = await fetch(url) 75 | checkStatus(response) 76 | const data = await response.json() 77 | return data 78 | } catch (err) { 79 | console.log(err) 80 | } 81 | } 82 | 83 | // 书单 && 友链 && 关于 84 | export const queryPage = async ({ type }) => { 85 | const upperType = type.replace(/^\S/, s => s.toUpperCase()) 86 | try { 87 | const url = `${blog}/issues?${closed}&labels=${upperType}` 88 | const response = await fetch(url) 89 | checkStatus(response) 90 | const data = await response.json() 91 | return data[0] 92 | } catch (err) { 93 | console.log(err) 94 | } 95 | } 96 | 97 | // 文章热度 98 | export const queryHot = async ({ postList }) => { 99 | return new Promise(resolve => { 100 | if (window.location.href.includes('http://localhost')) { 101 | resolve(postList) 102 | } 103 | const seq = postList.map(o => { 104 | return new Promise(resolve => { 105 | const query = new AV.Query('Counter') 106 | const Counter = AV.Object.extend('Counter') 107 | const { title, id } = o 108 | query.equalTo('id', id) 109 | query 110 | .find() 111 | .then(res => { 112 | if (res.length > 0) { 113 | // 已存在则返回热度 114 | const counter = res[0] 115 | o.times = counter.get('time') 116 | resolve(o) 117 | } else { 118 | // 不存在则新建 119 | const newcounter = new Counter() 120 | newcounter.set('title', title) 121 | newcounter.set('id', id) 122 | newcounter.set('time', 1) 123 | newcounter 124 | .save() 125 | .then(() => resolve(o)) 126 | .catch(console.error) 127 | } 128 | }) 129 | .catch(console.error) 130 | }).catch(console.error) 131 | }) 132 | 133 | Promise.all(seq) 134 | .then(data => resolve(data)) 135 | .catch(console.error) 136 | }).catch(console.error) 137 | } 138 | 139 | // 增加热度 140 | export const queryPostHot = async ({ post, add = true }) => { 141 | return new Promise(resolve => { 142 | if (window.location.href.includes('http://localhost')) { 143 | add = false 144 | } 145 | const query = new AV.Query('Counter') 146 | const Counter = AV.Object.extend('Counter') 147 | const { title, id } = post 148 | query.equalTo('id', id) 149 | query 150 | .find() 151 | .then(res => { 152 | if (res.length > 0) { 153 | // 已存在则加热度 154 | const counter = res[0] 155 | counter 156 | .increment('time', add ? 1 : 0) 157 | .save(null, { fetchWhenSave: true }) 158 | .then(counter => { 159 | post.times = counter.get('time') 160 | resolve(post) 161 | }) 162 | .catch(console.error) 163 | } else { 164 | // 不存在则新建 165 | const newcounter = new Counter() 166 | newcounter.set('title', title) 167 | newcounter.set('id', id) 168 | newcounter.set('time', 1) 169 | newcounter 170 | .save() 171 | .then(() => { 172 | post.times = 1 173 | resolve(post) 174 | }) 175 | .catch(console.error) 176 | } 177 | }) 178 | .catch(console.error) 179 | }).catch(console.error) 180 | } 181 | 182 | // 喜欢小站 183 | export const likeSite = async params => { 184 | return new Promise(resolve => { 185 | const query = new AV.Query('Counter') 186 | const Counter = AV.Object.extend('Counter') 187 | query.equalTo('title', 'site') 188 | query 189 | .first() 190 | .then(res => { 191 | if (res) { 192 | if (params && params.type === 'getTime') { 193 | resolve(res.get('time')) 194 | } else { 195 | res 196 | .increment('time', 1) 197 | .save(null, { fetchWhenSave: true }) 198 | .then(counter => resolve(counter.get('time'))) 199 | .catch(console.error) 200 | } 201 | } else { 202 | // 不存在则新建 203 | const newcounter = new Counter() 204 | newcounter.set('title', 'site') 205 | newcounter.set('time', 1) 206 | newcounter 207 | .save() 208 | .then(counter => resolve(counter.get('time'))) 209 | .catch(console.error) 210 | } 211 | }) 212 | .catch(console.error) 213 | }).catch(console.error) 214 | } 215 | -------------------------------------------------------------------------------- /src/components/Transition/index.js: -------------------------------------------------------------------------------- 1 | import { cloneElement, Component } from 'react' 2 | import _ from 'lodash' 3 | import classNames from 'classnames/bind' 4 | 5 | import styles from './index.less' 6 | 7 | const cx = classNames.bind(styles) 8 | 9 | const useKeyOnly = (val, key) => val && key 10 | 11 | const normalizeTransitionDuration = (duration, type) => 12 | typeof duration === 'number' || typeof duration === 'string' ? duration : duration[type] 13 | 14 | const DIRECTIONAL_TRANSITIONS = [ 15 | 'browse', 16 | 'browse right', 17 | 'drop', 18 | 'fade', 19 | 'fade up', 20 | 'fade down', 21 | 'fade left', 22 | 'fade right', 23 | 'fly up', 24 | 'fly down', 25 | 'fly left', 26 | 'fly right', 27 | 'horizontal flip', 28 | 'vertical flip', 29 | 'scale', 30 | 'slide up', 31 | 'slide down', 32 | 'slide left', 33 | 'slide right', 34 | 'swing up', 35 | 'swing down', 36 | 'swing left', 37 | 'swing right', 38 | 'zoom' 39 | ] 40 | 41 | // const STATIC_TRANSITIONS = ['jiggle', 'flash', 'shake', 'pulse', 'tada', 'bounce', 'glow'] 42 | // const TRANSITIONS = [...DIRECTIONAL_TRANSITIONS, ...STATIC_TRANSITIONS] 43 | 44 | const TRANSITION_TYPE = { 45 | ENTERING: 'show', 46 | EXITING: 'hide' 47 | } 48 | 49 | /** 50 | * A transition is an animation usually used to move content in or out of view. 51 | */ 52 | export default class Transition extends Component { 53 | static defaultProps = { 54 | animation: 'fade', 55 | duration: 500, 56 | visible: true, 57 | mountOnShow: true, 58 | transitionOnMount: false, 59 | unmountOnHide: false 60 | } 61 | 62 | static _meta = { 63 | name: 'Transition', 64 | type: 'module' 65 | } 66 | 67 | static ENTERED = 'ENTERED' 68 | static ENTERING = 'ENTERING' 69 | static EXITED = 'EXITED' 70 | static EXITING = 'EXITING' 71 | static UNMOUNTED = 'UNMOUNTED' 72 | 73 | constructor(...args) { 74 | super(...args) 75 | 76 | const { initial: status, next } = this.computeInitialStatuses() 77 | this.nextStatus = next 78 | this.state = { status } 79 | } 80 | 81 | // ---------------------------------------- 82 | // Lifecycle 83 | // ---------------------------------------- 84 | 85 | componentDidMount() { 86 | this.mounted = true 87 | this.updateStatus() 88 | } 89 | 90 | componentWillReceiveProps(nextProps) { 91 | const { current: status, next } = this.computeStatuses(nextProps) 92 | 93 | this.nextStatus = next 94 | if (status) this.setSafeState({ status }) 95 | } 96 | 97 | componentDidUpdate() { 98 | this.updateStatus() 99 | } 100 | 101 | componentWillUnmount() { 102 | this.mounted = false 103 | } 104 | 105 | // ---------------------------------------- 106 | // Callback handling 107 | // ---------------------------------------- 108 | 109 | handleStart = () => { 110 | const { duration } = this.props 111 | const status = this.nextStatus 112 | 113 | this.nextStatus = null 114 | this.setSafeState({ status, animating: true }, () => { 115 | const durationType = TRANSITION_TYPE[status] 116 | const durationValue = normalizeTransitionDuration(duration, durationType) 117 | 118 | _.invoke(this.props, 'onStart', null, { ...this.props, status }) 119 | setTimeout(this.handleComplete, durationValue) 120 | }) 121 | } 122 | 123 | handleComplete = () => { 124 | const { status: current } = this.state 125 | 126 | _.invoke(this.props, 'onComplete', null, { ...this.props, status: current }) 127 | 128 | if (this.nextStatus) { 129 | this.handleStart() 130 | return 131 | } 132 | 133 | const status = this.computeCompletedStatus() 134 | const callback = current === Transition.ENTERING ? 'onShow' : 'onHide' 135 | 136 | this.setSafeState({ status, animating: false }, () => { 137 | _.invoke(this.props, callback, null, { ...this.props, status }) 138 | }) 139 | } 140 | 141 | updateStatus = () => { 142 | const { animating } = this.state 143 | 144 | if (this.nextStatus) { 145 | this.nextStatus = this.computeNextStatus() 146 | if (!animating) this.handleStart() 147 | } 148 | } 149 | 150 | // ---------------------------------------- 151 | // Helpers 152 | // ---------------------------------------- 153 | 154 | computeClasses = () => { 155 | const { animation, children } = this.props 156 | const { animating, status } = this.state 157 | 158 | const childClasses = _.get(children, 'props.className') 159 | const directional = _.includes(DIRECTIONAL_TRANSITIONS, animation) 160 | 161 | if (directional) { 162 | return cx( 163 | animation, 164 | childClasses, 165 | useKeyOnly(animating, 'animating'), 166 | useKeyOnly(status === Transition.ENTERING, 'in'), 167 | useKeyOnly(status === Transition.EXITING, 'out'), 168 | useKeyOnly(status === Transition.EXITED, 'hidden'), 169 | useKeyOnly(status !== Transition.EXITED, 'visible'), 170 | 'transition' 171 | ) 172 | } 173 | 174 | return cx( 175 | animation, 176 | childClasses, 177 | useKeyOnly(animating, 'animating'), 178 | useKeyOnly(animating, 'transition') 179 | ) 180 | } 181 | 182 | computeCompletedStatus = () => { 183 | const { unmountOnHide } = this.props 184 | const { status } = this.state 185 | 186 | if (status === Transition.ENTERING) return Transition.ENTERED 187 | return unmountOnHide ? Transition.UNMOUNTED : Transition.EXITED 188 | } 189 | 190 | computeInitialStatuses = () => { 191 | const { visible, mountOnShow, transitionOnMount, unmountOnHide } = this.props 192 | 193 | if (visible) { 194 | if (transitionOnMount) { 195 | return { 196 | initial: Transition.EXITED, 197 | next: Transition.ENTERING 198 | } 199 | } 200 | return { initial: Transition.ENTERED } 201 | } 202 | 203 | if (mountOnShow || unmountOnHide) return { initial: Transition.UNMOUNTED } 204 | return { initial: Transition.EXITED } 205 | } 206 | 207 | computeNextStatus = () => { 208 | const { animating, status } = this.state 209 | 210 | if (animating) return status === Transition.ENTERING ? Transition.EXITING : Transition.ENTERING 211 | return status === Transition.ENTERED ? Transition.EXITING : Transition.ENTERING 212 | } 213 | 214 | computeStatuses = props => { 215 | const { status } = this.state 216 | const { visible } = props 217 | 218 | if (visible) { 219 | return { 220 | current: status === Transition.UNMOUNTED && Transition.EXITED, 221 | next: status !== Transition.ENTERING && status !== Transition.ENTERED && Transition.ENTERING 222 | } 223 | } 224 | 225 | return { 226 | next: (status === Transition.ENTERING || status === Transition.ENTERED) && Transition.EXITING 227 | } 228 | } 229 | 230 | computeStyle = () => { 231 | const { children, duration } = this.props 232 | const { status } = this.state 233 | 234 | const childStyle = _.get(children, 'props.style') 235 | const type = TRANSITION_TYPE[status] 236 | const animationDuration = type && `${normalizeTransitionDuration(duration, type)}ms` 237 | 238 | return { ...childStyle, animationDuration } 239 | } 240 | 241 | setSafeState = (...args) => this.mounted && this.setState(...args) 242 | 243 | // ---------------------------------------- 244 | // Render 245 | // ---------------------------------------- 246 | 247 | render() { 248 | const { children } = this.props 249 | const { status } = this.state 250 | 251 | if (status === Transition.UNMOUNTED) return null 252 | return cloneElement(children, { 253 | className: this.computeClasses(), 254 | style: this.computeStyle() 255 | }) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/assets/live2d/tips.json: -------------------------------------------------------------------------------- 1 | { 2 | "waifuClick": [ 3 | "是…是不小心碰到了吧", 4 | "萝莉控是什么呀", 5 | "你看到我的小熊了吗", 6 | "再摸的话我可要报警了!⌇●﹏●⌇", 7 | "110吗,这里有个变态一直在摸我(ó﹏ò。)" 8 | ], 9 | "hoverTips": { 10 | "home": "回首页看看吧 (つ´ω`)つ", 11 | "dressup": "要看看我的新衣服嘛 (●'◡'●)", 12 | "takephoto": "要给我拍张照嘛(<ゝω・)☆", 13 | "talk": "要听我讲故事么 ٩(๑•̀ω•́๑)۶", 14 | "info": "要了解更多关于我的故事么 (*´∀`)~♥", 15 | "close": "到了说再见的时候了么 (。ŏ_ŏ)", 16 | "scrollBtn": "唰地一下就跑上面去了哦 (o゚v゚)ノ", 17 | "playerBtn": "来听听歌吧 ♪(^∇^*)", 18 | "likeBtn": "点赞什么的,人家...人家才不稀罕呢 o(≧口≦)o" 19 | }, 20 | "clickTips": { 21 | "dressup": "我的新衣服漂亮么 (●'◡'●)", 22 | "takephoto": "我的照片要好好收藏哦(<ゝω・)☆", 23 | "close": "人生若只如初见,和你在一起的这段时间很开心 (▰˘◡˘▰)" 24 | }, 25 | "hitokotos": [ 26 | { 27 | "id": "160", 28 | "hitokoto": "我只是做了我能做的事,没有时间想将来。", 29 | "from": "秒速五厘米" 30 | }, 31 | { 32 | "id": "942", 33 | "hitokoto": "就算是自私…我也希望那些人能够永远都有笑容…", 34 | "from": "夏目友人帐" 35 | }, 36 | { 37 | "id": "112", 38 | "hitokoto": "我到底要以怎么样的速度生活才能与你再次相遇?", 39 | "from": "秒速五厘米" 40 | }, 41 | { 42 | "id": "1116", 43 | "hitokoto": "我在时光斑驳深处,聆听到花开的声音。", 44 | "from": "未闻花名" 45 | }, 46 | { 47 | "id": "514", 48 | "hitokoto": "不管是怎样的回忆,都是我们活过的人生。", 49 | "from": "angel beats" 50 | }, 51 | { 52 | "id": "204", 53 | "hitokoto": "一直注视着你,似近似远,总是触碰不到。", 54 | "from": "来自风平浪静的明天" 55 | }, 56 | { 57 | "id": "121", 58 | "hitokoto": "你的那双手呢,是为了紧紧抓住什么而存在的哦。", 59 | "from": "仰望半月的夜空" 60 | }, 61 | { 62 | "id": "783", 63 | "hitokoto": "有伤害人的人存在的话,也会有能抚慰伤痕的人", 64 | "from": "水果篮子" 65 | }, 66 | { 67 | "id": "19", 68 | "hitokoto": "当你想做一件事,却无能为力的时候,是最痛苦的。", 69 | "from": "高达SEED" 70 | }, 71 | { 72 | "id": "107", 73 | "hitokoto": "即使从梦中醒来,还会有回忆留下。", 74 | "from": "AIR" 75 | }, 76 | { 77 | "id": "202", 78 | "hitokoto": "我们的心就像那天空一样,永不分离。", 79 | "from": "缘之空" 80 | }, 81 | { 82 | "id": "274", 83 | "hitokoto": "时间是伟大的作家,总会写下完美的结局。", 84 | "from": "秋之回忆" 85 | }, 86 | { 87 | "id": "714", 88 | "hitokoto": "一定要保护自己的梦想,即使牺牲一切。", 89 | "from": "NANA" 90 | }, 91 | { 92 | "id": "1224", 93 | "hitokoto": "正因为生来什么都没有,因此我们能拥有一切。", 94 | "from": "游戏人生" 95 | }, 96 | { 97 | "id": "210", 98 | "hitokoto": "我的愿望是—幸福地活着,幸福地死去。", 99 | "from": "神不在的星期天" 100 | }, 101 | { 102 | "id": "657", 103 | "hitokoto": "假如我们相遇,肯定一眼就能认出彼此", 104 | "from": "你的名字" 105 | }, 106 | { 107 | "id": "52", 108 | "hitokoto": "从小好女色的男人的想像力比不上狗。", 109 | "from": "寒蝉鸣泣之时" 110 | }, 111 | { 112 | "id": "536", 113 | "hitokoto": "即使你忘记了我,我也不会遗忘你。", 114 | "from": "Re:从零开始的异世界生活" 115 | }, 116 | { 117 | "id": "887", 118 | "hitokoto": "所谓的奇迹就是要发生之后才会有价值存在的吧", 119 | "from": "EVA" 120 | }, 121 | { 122 | "id": "135", 123 | "hitokoto": "爱,其实很简单,困难的是去接受它。", 124 | "from": "通灵王" 125 | }, 126 | { 127 | "id": "254", 128 | "hitokoto": "做出一副温柔的样子来折磨人不是更令人难受吗?", 129 | "from": "我的青春恋爱物语果然有问题" 130 | }, 131 | { 132 | "id": "95", 133 | "hitokoto": "你的心可以属于耶稣,但你的屁股永远属于陆战队!", 134 | "from": "魔法少女陆战队" 135 | }, 136 | { 137 | "id": "1040", 138 | "hitokoto": "无论最终的结果是什么,只要这是自己选择的道路。", 139 | "from": "龙与虎" 140 | }, 141 | { 142 | "id": "97", 143 | "hitokoto": "我们走过风走过雨,就是没能走进彼此的内心。", 144 | "from": "自分" 145 | }, 146 | { 147 | "id": "66", 148 | "hitokoto": "已经无法回到过去了。也不知道将来会是什么模样。", 149 | "from": "文学少女" 150 | }, 151 | { 152 | "id": "1112", 153 | "hitokoto": "不论是过去还是未来,我都会保护你!", 154 | "from": "旋风管家" 155 | }, 156 | { 157 | "id": "1129", 158 | "hitokoto": "人生最糟糕的事,一个是饿肚子,一个是孤独。", 159 | "from": "夏日大作战" 160 | }, 161 | { 162 | "id": "990", 163 | "hitokoto": "即使想放弃,也没法放弃最想要的东西,这就是人", 164 | "from": "悠久之翼" 165 | }, 166 | { 167 | "id": "524", 168 | "hitokoto": "真正重要的东西,总是没有的人比拥有的人清楚。", 169 | "from": "银魂" 170 | }, 171 | { 172 | "id": "523", 173 | "hitokoto": "死亡只要在人生的终点尝试一次就够了~", 174 | "from": "Re:从零开始的异世界生活" 175 | }, 176 | { 177 | "id": "138", 178 | "hitokoto": "因为我喜欢你,喜欢得想吃掉你啊!", 179 | "from": "有顶天家族" 180 | }, 181 | { 182 | "id": "708", 183 | "hitokoto": "彼方为谁,无我有问 ;九月露湿,待君之前", 184 | "from": "你的名字" 185 | }, 186 | { 187 | "id": "741", 188 | "hitokoto": "命运的红线一旦断了,就再也不会接上。", 189 | "from": "犬夜叉" 190 | }, 191 | { 192 | "id": "109", 193 | "hitokoto": "我会继续等着你,就算是一万二千年。", 194 | "from": "创圣的大天使EVOL" 195 | }, 196 | { 197 | "id": "842", 198 | "hitokoto": "红茶的温度和女人心在任何时代都是难以琢磨呢。", 199 | "from": "海猫鸣泣之时" 200 | }, 201 | { 202 | "id": "701", 203 | "hitokoto": "前天是小兔子,昨天是小鹿,今天是你", 204 | "from": "CLANNAD" 205 | }, 206 | { 207 | "id": "1087", 208 | "hitokoto": "即使你已经习惯了受伤害,也有人看了会心疼的。", 209 | "from": "我的恋爱物语果然有问题" 210 | }, 211 | { 212 | "id": "273", 213 | "hitokoto": "Time waits for no one.", 214 | "from": "穿越时空的少女" 215 | }, 216 | { 217 | "id": "1206", 218 | "hitokoto": "自杀是没有理由的,只是今天没有飞起来罢了。", 219 | "from": "空之境界" 220 | }, 221 | { 222 | "id": "11", 223 | "hitokoto": "努力是不会背叛自己的,虽然梦想有时会背叛自己。", 224 | "from": "我的青春恋爱物语果然有问题" 225 | }, 226 | { 227 | "id": "34", 228 | "hitokoto": "别人恋爱不成功,你连暗恋都不成功!", 229 | "from": "灌篮高手" 230 | }, 231 | { 232 | "id": "67", 233 | "hitokoto": "不要哀求,学会争取;若是如此,终有所获。", 234 | "from": "交响诗篇" 235 | }, 236 | { 237 | "id": "789", 238 | "hitokoto": "我觉得只要这样继续加油,总有一天能赶上他们的。", 239 | "from": "刀剑神域" 240 | }, 241 | { 242 | "id": "184", 243 | "hitokoto": "美丽的不是这个世界,而是看世界的你的眼神。", 244 | "from": "吸血鬼骑士" 245 | }, 246 | { 247 | "id": "1226", 248 | "hitokoto": "只要努力活下去,总有一天会笑着回忆。", 249 | "from": "不可思议游戏" 250 | }, 251 | { 252 | "id": "80", 253 | "hitokoto": "比自己,比梦想更重要的东西永远都存在着...", 254 | "from": "钢之炼金术师" 255 | }, 256 | { 257 | "id": "1090", 258 | "hitokoto": "只要一天活著,难过的事总有一天会让你笑著说出来。", 259 | "from": "天空之城" 260 | }, 261 | { 262 | "id": "834", 263 | "hitokoto": "时间可以治愈?如果时间也病了怎么办", 264 | "from": "寒蝉明泣之时" 265 | }, 266 | { 267 | "id": "521", 268 | "hitokoto": "人就是要以自卑为跳板才能跳得更高。", 269 | "from": "银魂" 270 | }, 271 | { 272 | "id": "984", 273 | "hitokoto": "我动身踏上旅程,是为了与你道别。", 274 | "from": "追逐繁星的孩子" 275 | }, 276 | { 277 | "id": "10", 278 | "hitokoto": "我是一个经常笑的人,可我不是经常开心的人。", 279 | "from": "未闻花名" 280 | }, 281 | { 282 | "id": "1003", 283 | "hitokoto": "干燥的冷气,尘埃的味道,我在其中……踏上旅途。", 284 | "from": "四月是你的谎言" 285 | }, 286 | { 287 | "id": "246", 288 | "hitokoto": "时间可以治愈?如果时间也病了怎么办?", 289 | "from": "寒蝉鸣泣之时" 290 | }, 291 | { 292 | "id": "44", 293 | "hitokoto": "只要有想见面的人,自己就不再是孤单一人。", 294 | "from": "夏目友人帐" 295 | }, 296 | { 297 | "id": "116", 298 | "hitokoto": "呐,知道么,樱花飘落的速度,是每秒五厘米哦~", 299 | "from": "秒速五厘米" 300 | }, 301 | { 302 | "id": "624", 303 | "hitokoto": "男生送的礼物要方便拿来换钱才好吧!", 304 | "from": "来栖加奈子" 305 | }, 306 | { 307 | "id": "1167", 308 | "hitokoto": "大部分人并不想长大,只是没办法继续当一个小孩子", 309 | "from": "小林家的龙女仆" 310 | }, 311 | { 312 | "id": "176", 313 | "hitokoto": "贫乳是社会地位的象征。是具有稀有价值的!", 314 | "from": "幸运星" 315 | }, 316 | { 317 | "id": "852", 318 | "hitokoto": "MAKE OUR DREAMS ALIVE", 319 | "from": "ラブライブ!" 320 | }, 321 | { 322 | "id": "233", 323 | "hitokoto": "能够轻易就放弃的梦想,有存在的价值么?", 324 | "from": "守护甜心" 325 | }, 326 | { 327 | "id": "867", 328 | "hitokoto": "真正重要的东西,永远都是非常简单的。", 329 | "from": "Clannad" 330 | }, 331 | { 332 | "id": "84", 333 | "hitokoto": "因为无法再见面,所以要笑着说再见。", 334 | "from": "AIR" 335 | }, 336 | { 337 | "id": "998", 338 | "hitokoto": "追逐梦想的人比抓住梦想的人更能发挥实力。", 339 | "from": "银魂" 340 | } 341 | ] 342 | } 343 | -------------------------------------------------------------------------------- /src/global.less: -------------------------------------------------------------------------------- 1 | @import './assets/prism/prism'; 2 | @import '../node_modules/baguettebox.js/dist/baguetteBox.min.css'; 3 | 4 | @font-face { 5 | font-family: GuDianMingChaoTi; 6 | font-weight: bold; 7 | src: url('./assets/fonts/GuDianMingChaoTi.ttf'); 8 | } 9 | 10 | @font-face { 11 | font-family: FiraCode; 12 | font-weight: bold; 13 | src: url('./assets/fonts/FiraCode-Medium.woff2'); 14 | } 15 | 16 | @font-face { 17 | font-family: Fontello; 18 | font-weight: bold; 19 | src: url('./assets/fonts/Fontello.woff2'); 20 | } 21 | 22 | a, 23 | button { 24 | cursor: url('./assets/cursor/cursor_link.png'), auto; 25 | } 26 | 27 | html, 28 | body, 29 | :global(#root) { 30 | height: 100%; 31 | } 32 | 33 | html { 34 | font-size: 625%; 35 | } 36 | 37 | body, 38 | p, 39 | blockquote, 40 | h1, 41 | h2, 42 | h3, 43 | ul, 44 | ol, 45 | button, 46 | table, 47 | code { 48 | margin: 0; 49 | padding: 0; 50 | font-size: 0.16rem; 51 | color: #666; 52 | } 53 | 54 | body { 55 | user-select: none; 56 | text-shadow: 0 0 1px rgba(0, 0, 0, 0.2); 57 | scroll-behavior: smooth; 58 | font-family: 'Helvetica', 'PingFang SC', sans-serif; 59 | cursor: url('./assets/cursor/cursor.png'), auto; 60 | &::before { 61 | content: ''; 62 | position: fixed; 63 | top: 0; 64 | left: 0; 65 | width: 100%; 66 | height: 100%; 67 | z-index: -1; 68 | background: rgba(255, 255, 255, 0.5); 69 | } 70 | } 71 | 72 | code { 73 | font-size: 0.15rem; 74 | font-family: 'FiraCode', 'Noto Serif SC', 'Helvetica', 'PingFang SC', sans-serif, serif; 75 | } 76 | 77 | .icon { 78 | font-family: 'Fontello'; 79 | font-style: normal; 80 | } 81 | 82 | h2, 83 | h3 { 84 | letter-spacing: 0.01rem; 85 | } 86 | 87 | img { 88 | display: block; 89 | } 90 | 91 | a { 92 | color: #666; 93 | text-decoration: none; 94 | transition: all 0.25s ease; 95 | &:hover { 96 | color: #faf; 97 | } 98 | i { 99 | padding: 0 0.04rem; 100 | } 101 | } 102 | 103 | code { 104 | padding: 0.02rem 0.04rem; 105 | color: #f6f; 106 | word-wrap: break-word; 107 | border-radius: 0.03rem; 108 | background-color: rgba(0, 0, 0, 0.1); 109 | } 110 | 111 | /* 隐藏滚动条 */ 112 | html::-webkit-scrollbar { 113 | display: none; 114 | } 115 | 116 | ::selection { 117 | color: #fff; 118 | background: #888; 119 | } 120 | 121 | .fireworks { 122 | position: fixed; 123 | top: 0; 124 | left: 0; 125 | } 126 | 127 | /* gitalk */ 128 | .gt-comment-content, 129 | .gt-container .gt-avatar img { 130 | background: rgba(255, 255, 255, 0.6) !important; 131 | border-radius: 3px; 132 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.24); 133 | } 134 | .gt-container { 135 | @media (max-width: 900px) { 136 | padding: 0 0.16rem; 137 | } 138 | a { 139 | color: #faf !important; 140 | &:hover { 141 | color: #f6f !important; 142 | border-color: #f6f !important; 143 | } 144 | } 145 | svg { 146 | fill: #faf !important; 147 | } 148 | .gt-header-textarea { 149 | background: rgba(255, 255, 255, 0.6) !important; 150 | &:hover { 151 | background: rgba(255, 255, 255, 0.6) !important; 152 | } 153 | } 154 | .gt-btn { 155 | color: #eee; 156 | border: 1px solid #faf !important; 157 | background: #faf !important; 158 | &:hover { 159 | border-color: #f6f; 160 | background: #f6f; 161 | } 162 | } 163 | .gt-btn-preview { 164 | color: #fff !important; 165 | } 166 | .gt-meta { 167 | border-bottom: 1px solid #e9e9e9; 168 | } 169 | .gt-link { 170 | border-bottom: 1px dotted #faf !important; 171 | } 172 | .gt-header-controls-tip { 173 | color: #faf; 174 | } 175 | .gt-ico-github svg { 176 | fill: #666 !important; 177 | } 178 | .gt-comment-username { 179 | color: #f6f; 180 | } 181 | .gt-comment-body { 182 | font-size: 0.16rem; 183 | max-height: 2.6rem; 184 | overflow-y: scroll; 185 | &::-webkit-scrollbar { 186 | width: 4px; 187 | height: 4px; 188 | background-color: transparent; 189 | } 190 | &::-webkit-scrollbar-thumb { 191 | background-color: #888; 192 | background-image: -webkit-linear-gradient( 193 | 45deg, 194 | rgba(255, 255, 255, 0.6) 25%, 195 | transparent 25%, 196 | transparent 50%, 197 | rgba(255, 255, 255, 0.6) 50%, 198 | rgba(255, 255, 255, 0.6) 75%, 199 | transparent 75%, 200 | transparent 201 | ); 202 | border-radius: 4px; 203 | } 204 | &::-webkit-scrollbar-track { 205 | background-color: transparent; 206 | } 207 | } 208 | .gt-comment-content { 209 | transition: all 0.25s ease 0s, transform 0.5s cubic-bezier(0.6, 0.2, 0.1, 1) 0s, 210 | opacity 0.5s cubic-bezier(0.6, 0.2, 0.1, 1) 0s; 211 | background: rgba(255, 255, 255, 0.6); 212 | } 213 | .gt-comment-content:hover { 214 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23) !important; 215 | transform: translateY(-4px); 216 | } 217 | } 218 | .gt-container .gt-comment-admin .gt-comment-content { 219 | background-color: rgba(255, 255, 255, 0.6); 220 | } 221 | 222 | /* 灯箱 */ 223 | #baguetteBox-overlay { 224 | animation: elastic 0.5s; 225 | } 226 | #baguetteBox-overlay .full-image { 227 | img { 228 | max-height: 80%; 229 | max-width: 80%; 230 | box-shadow: 0 0 1.6rem #eee; 231 | } 232 | figcaption { 233 | line-height: 3; 234 | } 235 | } 236 | .baguetteBox-button { 237 | background: transparent; 238 | &:hover { 239 | background: transparent; 240 | } 241 | } 242 | @keyframes elastic { 243 | 0% { 244 | transform: scale(0); 245 | } 246 | 55% { 247 | transform: scale(1); 248 | } 249 | 70% { 250 | transform: scale(0.98); 251 | } 252 | 100% { 253 | transform: scale(1); 254 | } 255 | } 256 | 257 | .backstretch { 258 | position: fixed; 259 | top: 0; 260 | left: 0; 261 | width: 100%; 262 | height: 100%; 263 | z-index: -1; 264 | color: transparent; 265 | &::after { 266 | content: ''; 267 | position: absolute; 268 | top: 0; 269 | left: 0; 270 | width: 100%; 271 | height: 100%; 272 | background-color: rgba(255, 255, 255, 0.5); 273 | } 274 | } 275 | 276 | // 背景轮播 277 | .backstretch li span { 278 | width: 100%; 279 | height: 100%; 280 | position: absolute; 281 | top: 0px; 282 | left: 0px; 283 | font-size: 0; 284 | color: transparent; 285 | background-size: cover; 286 | background-position: 50% 50%; 287 | background-repeat: no-repeat; 288 | opacity: 0; 289 | z-index: 0; 290 | animation: imageAnimation 80s linear infinite 0s; 291 | } 292 | .backstretch li:nth-child(1) span { 293 | background-image: url(https://i.loli.net/2018/12/09/5c0cc0f409d63.jpg); 294 | } 295 | .backstretch li:nth-child(2) span { 296 | background-image: url(https://i.loli.net/2018/12/16/5c164034ac74c.jpg); 297 | animation-delay: 8s; 298 | } 299 | .backstretch li:nth-child(3) span { 300 | background-image: url(https://i.loli.net/2018/12/09/5c0cc1010f9e5.jpg); 301 | animation-delay: 16s; 302 | } 303 | .backstretch li:nth-child(4) span { 304 | background-image: url(https://i.loli.net/2018/12/09/5c0cc108c3b5c.jpg); 305 | animation-delay: 24s; 306 | } 307 | .backstretch li:nth-child(5) span { 308 | background-image: url(https://i.loli.net/2018/12/09/5c0cc10d3d771.jpg); 309 | animation-delay: 32s; 310 | } 311 | .backstretch li:nth-child(6) span { 312 | background-image: url(https://i.loli.net/2018/12/09/5c0cc111f30a3.jpg); 313 | animation-delay: 40s; 314 | } 315 | .backstretch li:nth-child(7) span { 316 | background-image: url(https://i.loli.net/2018/12/09/5c0cc1159ea42.jpg); 317 | animation-delay: 48s; 318 | } 319 | .backstretch li:nth-child(8) span { 320 | background-image: url(https://i.loli.net/2018/12/09/5c0cc11b0691b.jpg); 321 | animation-delay: 56s; 322 | } 323 | .backstretch li:nth-child(9) span { 324 | background-image: url(https://i.loli.net/2018/12/09/5c0cc12115c2e.jpg); 325 | animation-delay: 64s; 326 | } 327 | .backstretch li:nth-child(10) span { 328 | background-image: url(https://i.loli.net/2018/12/09/5c0cc1268cd42.jpg); 329 | animation-delay: 72s; 330 | } 331 | /* Animation for the slideshow images */ 332 | @keyframes imageAnimation { 333 | 0% { 334 | opacity: 0; 335 | animation-timing-function: ease-in; 336 | } 337 | 1.5% { 338 | opacity: 1; 339 | } 340 | 12% { 341 | opacity: 1; 342 | animation-timing-function: ease-out; 343 | } 344 | 13% { 345 | opacity: 0; 346 | } 347 | 100% { 348 | opacity: 0; 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/components/LazyImage/index.less: -------------------------------------------------------------------------------- 1 | .box { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | } 6 | 7 | // flower-spinner 8 | .flower-spinner, 9 | .flower-spinner * { 10 | box-sizing: border-box; 11 | } 12 | 13 | .flower-spinner { 14 | height: 70px; 15 | width: 70px; 16 | display: flex; 17 | flex-direction: row; 18 | align-items: center; 19 | justify-content: center; 20 | } 21 | 22 | .flower-spinner .dots-container { 23 | height: calc(70px / 7); 24 | width: calc(70px / 7); 25 | } 26 | 27 | .flower-spinner .smaller-dot { 28 | background: #faf; 29 | height: 100%; 30 | width: 100%; 31 | border-radius: 50%; 32 | animation: flower-spinner-smaller-dot-animation 2.5s 0s infinite both; 33 | } 34 | 35 | .flower-spinner .bigger-dot { 36 | background: #faf; 37 | height: 100%; 38 | width: 100%; 39 | padding: 10%; 40 | border-radius: 50%; 41 | animation: flower-spinner-bigger-dot-animation 2.5s 0s infinite both; 42 | } 43 | 44 | @keyframes flower-spinner-bigger-dot-animation { 45 | 0%, 46 | 100% { 47 | box-shadow: #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px, 48 | #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px; 49 | } 50 | 51 | 50% { 52 | transform: rotate(180deg); 53 | } 54 | 55 | 25%, 56 | 75% { 57 | box-shadow: #faf 26px 0px 0px, #faf -26px 0px 0px, #faf 0px 26px 0px, #faf 0px -26px 0px, 58 | #faf 19px -19px 0px, #faf 19px 19px 0px, #faf -19px -19px 0px, #faf -19px 19px 0px; 59 | } 60 | 61 | 100% { 62 | transform: rotate(360deg); 63 | box-shadow: #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px, 64 | #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px; 65 | } 66 | } 67 | 68 | @keyframes flower-spinner-smaller-dot-animation { 69 | 0%, 70 | 100% { 71 | box-shadow: #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px, 72 | #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px; 73 | } 74 | 75 | 25%, 76 | 75% { 77 | box-shadow: #faf 14px 0px 0px, #faf -14px 0px 0px, #faf 0px 14px 0px, #faf 0px -14px 0px, 78 | #faf 10px -10px 0px, #faf 10px 10px 0px, #faf -10px -10px 0px, #faf -10px 10px 0px; 79 | } 80 | 81 | 100% { 82 | box-shadow: #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px, 83 | #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px, #faf 0px 0px 0px; 84 | } 85 | } 86 | 87 | // fingerprint-spinner 88 | .fingerprint-spinner, 89 | .fingerprint-spinner * { 90 | box-sizing: border-box; 91 | } 92 | 93 | .fingerprint-spinner { 94 | height: 64px; 95 | width: 64px; 96 | padding: 2px; 97 | overflow: hidden; 98 | position: relative; 99 | } 100 | 101 | .fingerprint-spinner .spinner-ring { 102 | position: absolute; 103 | border-radius: 50%; 104 | border: 2px solid transparent; 105 | border-top-color: #faf; 106 | animation: fingerprint-spinner-animation 1500ms cubic-bezier(0.68, -0.75, 0.265, 1.75) infinite 107 | forwards; 108 | margin: auto; 109 | bottom: 0; 110 | left: 0; 111 | right: 0; 112 | top: 0; 113 | } 114 | 115 | .fingerprint-spinner .spinner-ring:nth-child(1) { 116 | height: calc(60px / 9 + 0 * 60px / 9); 117 | width: calc(60px / 9 + 0 * 60px / 9); 118 | animation-delay: calc(50ms * 1); 119 | } 120 | 121 | .fingerprint-spinner .spinner-ring:nth-child(2) { 122 | height: calc(60px / 9 + 1 * 60px / 9); 123 | width: calc(60px / 9 + 1 * 60px / 9); 124 | animation-delay: calc(50ms * 2); 125 | } 126 | 127 | .fingerprint-spinner .spinner-ring:nth-child(3) { 128 | height: calc(60px / 9 + 2 * 60px / 9); 129 | width: calc(60px / 9 + 2 * 60px / 9); 130 | animation-delay: calc(50ms * 3); 131 | } 132 | 133 | .fingerprint-spinner .spinner-ring:nth-child(4) { 134 | height: calc(60px / 9 + 3 * 60px / 9); 135 | width: calc(60px / 9 + 3 * 60px / 9); 136 | animation-delay: calc(50ms * 4); 137 | } 138 | 139 | .fingerprint-spinner .spinner-ring:nth-child(5) { 140 | height: calc(60px / 9 + 4 * 60px / 9); 141 | width: calc(60px / 9 + 4 * 60px / 9); 142 | animation-delay: calc(50ms * 5); 143 | } 144 | 145 | .fingerprint-spinner .spinner-ring:nth-child(6) { 146 | height: calc(60px / 9 + 5 * 60px / 9); 147 | width: calc(60px / 9 + 5 * 60px / 9); 148 | animation-delay: calc(50ms * 6); 149 | } 150 | 151 | .fingerprint-spinner .spinner-ring:nth-child(7) { 152 | height: calc(60px / 9 + 6 * 60px / 9); 153 | width: calc(60px / 9 + 6 * 60px / 9); 154 | animation-delay: calc(50ms * 7); 155 | } 156 | 157 | .fingerprint-spinner .spinner-ring:nth-child(8) { 158 | height: calc(60px / 9 + 7 * 60px / 9); 159 | width: calc(60px / 9 + 7 * 60px / 9); 160 | animation-delay: calc(50ms * 8); 161 | } 162 | 163 | .fingerprint-spinner .spinner-ring:nth-child(9) { 164 | height: calc(60px / 9 + 8 * 60px / 9); 165 | width: calc(60px / 9 + 8 * 60px / 9); 166 | animation-delay: calc(50ms * 9); 167 | } 168 | 169 | @keyframes fingerprint-spinner-animation { 170 | 100% { 171 | transform: rotate(360deg); 172 | } 173 | } 174 | 175 | // trinity-rings-spinner 176 | .trinity-rings-spinner, 177 | .trinity-rings-spinner * { 178 | box-sizing: border-box; 179 | } 180 | 181 | .trinity-rings-spinner { 182 | height: 66px; 183 | width: 66px; 184 | padding: 3px; 185 | position: relative; 186 | display: flex; 187 | justify-content: center; 188 | align-items: center; 189 | flex-direction: row; 190 | overflow: hidden; 191 | box-sizing: border-box; 192 | } 193 | .trinity-rings-spinner .circle { 194 | position: absolute; 195 | display: block; 196 | border-radius: 50%; 197 | border: 3px solid #faf; 198 | opacity: 1; 199 | } 200 | 201 | .trinity-rings-spinner .circle:nth-child(1) { 202 | height: 60px; 203 | width: 60px; 204 | animation: trinity-rings-spinner-circle1-animation 1.5s infinite linear; 205 | border-width: 3px; 206 | } 207 | .trinity-rings-spinner .circle:nth-child(2) { 208 | height: calc(60px * 0.65); 209 | width: calc(60px * 0.65); 210 | animation: trinity-rings-spinner-circle2-animation 1.5s infinite linear; 211 | border-width: 2px; 212 | } 213 | .trinity-rings-spinner .circle:nth-child(3) { 214 | height: calc(60px * 0.1); 215 | width: calc(60px * 0.1); 216 | animation: trinity-rings-spinner-circle3-animation 1.5s infinite linear; 217 | border-width: 1px; 218 | } 219 | 220 | @keyframes trinity-rings-spinner-circle1-animation { 221 | 0% { 222 | transform: rotateZ(20deg) rotateY(0deg); 223 | } 224 | 100% { 225 | transform: rotateZ(100deg) rotateY(360deg); 226 | } 227 | } 228 | @keyframes trinity-rings-spinner-circle2-animation { 229 | 0% { 230 | transform: rotateZ(100deg) rotateX(0deg); 231 | } 232 | 100% { 233 | transform: rotateZ(0deg) rotateX(360deg); 234 | } 235 | } 236 | @keyframes trinity-rings-spinner-circle3-animation { 237 | 0% { 238 | transform: rotateZ(100deg) rotateX(-360deg); 239 | } 240 | 100% { 241 | transform: rotateZ(-360deg) rotateX(360deg); 242 | } 243 | } 244 | 245 | // atom-spinner 246 | .atom-spinner, 247 | .atom-spinner * { 248 | box-sizing: border-box; 249 | } 250 | 251 | .atom-spinner { 252 | height: 60px; 253 | width: 60px; 254 | overflow: hidden; 255 | } 256 | 257 | .atom-spinner .spinner-inner { 258 | position: relative; 259 | display: block; 260 | height: 100%; 261 | width: 100%; 262 | } 263 | 264 | .atom-spinner .spinner-circle { 265 | display: block; 266 | position: absolute; 267 | color: #faf; 268 | font-size: calc(60px * 0.24); 269 | top: 50%; 270 | left: 50%; 271 | transform: translate(-50%, -50%); 272 | } 273 | 274 | .atom-spinner .spinner-line { 275 | position: absolute; 276 | width: 100%; 277 | height: 100%; 278 | border-radius: 50%; 279 | animation-duration: 1s; 280 | border-left-width: calc(60px / 25); 281 | border-top-width: calc(60px / 25); 282 | border-left-color: #faf; 283 | border-left-style: solid; 284 | border-top-style: solid; 285 | border-top-color: transparent; 286 | } 287 | 288 | .atom-spinner .spinner-line:nth-child(1) { 289 | animation: atom-spinner-animation-1 1s linear infinite; 290 | transform: rotateZ(120deg) rotateX(66deg) rotateZ(0deg); 291 | } 292 | 293 | .atom-spinner .spinner-line:nth-child(2) { 294 | animation: atom-spinner-animation-2 1s linear infinite; 295 | transform: rotateZ(240deg) rotateX(66deg) rotateZ(0deg); 296 | } 297 | 298 | .atom-spinner .spinner-line:nth-child(3) { 299 | animation: atom-spinner-animation-3 1s linear infinite; 300 | transform: rotateZ(360deg) rotateX(66deg) rotateZ(0deg); 301 | } 302 | 303 | @keyframes atom-spinner-animation-1 { 304 | 100% { 305 | transform: rotateZ(120deg) rotateX(66deg) rotateZ(360deg); 306 | } 307 | } 308 | 309 | @keyframes atom-spinner-animation-2 { 310 | 100% { 311 | transform: rotateZ(240deg) rotateX(66deg) rotateZ(360deg); 312 | } 313 | } 314 | 315 | @keyframes atom-spinner-animation-3 { 316 | 100% { 317 | transform: rotateZ(360deg) rotateX(66deg) rotateZ(360deg); 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /public/live2d/mtn/Breath7.mtn: -------------------------------------------------------------------------------- 1 | # Live2D Animator Motion Data 2 | $fps=30 3 | 4 | $fadein=0 5 | 6 | $fadeout=0 7 | 8 | PARAM_ANGLE_X=0,-0.1,-0.37,-0.81,-1.36,-2.04,-2.8,-3.63,-4.52,-5.46,-6.4,-7.38,-8.34,-9.29,-10.21,-11.08,-11.89,-12.64,-13.31,-13.88,-14.36,-14.71,-14.92,-15,-15.001,-15.003,-15,-14.999,-14.989,-14.97,-14.94,-14.9,-14.84,-14.77,-14.68,-14.57,-14.44,-14.28,-14.11,-13.9,-13.67,-13.4,-13.1,-12.78,-12.41,-12.01,-11.57,-11.09,-10.57,-10,-9.27,-8.4,-7.37,-6.23,-4.98,-3.66,-2.23,-0.78,0.71,2.23,3.72,5.23,6.69,8.11,9.48,10.78,11.97,13.06,14.05,14.92,15.65,16.23,16.65,16.91,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,16.89,16.56,16.06,15.39,14.56,13.62,12.6,11.48,10.31,9.1,7.9,6.69,5.52,4.4,3.38,2.44,1.61,0.94,0.44,0.11,0 9 | PARAM_ANGLE_Y=0,-0.006,-0.025,-0.05,-0.09,-0.14,-0.19,-0.24,-0.3,-0.36,-0.43,-0.49,-0.56,-0.62,-0.68,-0.74,-0.79,-0.84,-0.89,-0.93,-0.96,-0.98,-0.995,-1,-0.96,-0.86,-0.71,-0.5,-0.25,0.04,0.36,0.71,1.07,1.46,1.85,2.25,2.65,3.05,3.43,3.81,4.17,4.51,4.83,5.11,5.37,5.58,5.76,5.89,5.97,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,5.96,5.85,5.67,5.43,5.14,4.81,4.45,4.05,3.64,3.21,2.79,2.36,1.95,1.55,1.19,0.86,0.57,0.33,0.15,0.04,0 10 | PARAM_ANGLE_Z=0,-0.07,-0.27,-0.59,-1,-1.49,-2.05,-2.66,-3.32,-4,-4.7,-5.41,-6.12,-6.81,-7.49,-8.12,-8.72,-9.27,-9.76,-10.18,-10.53,-10.79,-10.94,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-10.84,-10.38,-9.63,-8.63,-7.42,-6.04,-4.45,-2.76,-0.96,0.92,2.8,4.77,6.68,8.58,10.43,12.21,13.86,15.39,16.78,18,19.05,19.88,20.49,20.87,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,20.86,20.46,19.84,19.01,17.99,16.83,15.56,14.18,12.73,11.24,9.76,8.27,6.82,5.44,4.17,3.01,1.99,1.16,0.54,0.14,0 11 | PARAM_EMOTION=-1 12 | PARAM_EYE_L_OPEN=0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55 13 | PARAM_EYE_R_OPEN=0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55 14 | PARAM_EYE_L_OPEN2=-1 15 | PARAM_EYE_R_OPEN2=-1 16 | PARAM_EYE_BALL_X=0,-0.005,-0.018,-0.04,-0.07,-0.1,-0.14,-0.18,-0.22,-0.27,-0.31,-0.36,-0.41,-0.45,-0.5,-0.54,-0.58,-0.62,-0.65,-0.68,-0.7,-0.716,-0.726,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.73,-0.722,-0.7,-0.67,-0.62,-0.56,-0.5,-0.42,-0.34,-0.26,-0.17,-0.08,0.01,0.1,0.19,0.28,0.37,0.44,0.52,0.58,0.64,0.69,0.73,0.76,0.774,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.78,0.775,0.76,0.74,0.71,0.67,0.62,0.58,0.53,0.47,0.42,0.36,0.31,0.25,0.2,0.16,0.11,0.07,0.04,0.02,0.005,0 17 | PARAM_EYE_BALL_Y=0,0,0.002,0.004,0.007,0.011,0.015,0.019,0.024,0.029,0.034,0.039,0.045,0.05,0.054,0.059,0.063,0.067,0.071,0.074,0.077,0.078,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.08,0.079,0.078,0.076,0.072,0.069,0.064,0.059,0.054,0.048,0.043,0.037,0.032,0.026,0.021,0.016,0.011,0.008,0.004,0.002,0.001,0 18 | PARAM_BROW_L_Y=0 19 | PARAM_BROW_R_Y=0 20 | PARAM_BROW_ANGLE=0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 21 | PARAM_BROW_SELECT=-0.5 22 | PARAM_MOUTH_OPEN_Y=0 23 | PARAM_MOUTH_OPEN2=0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9,0.9 24 | PARAM_MOUTH_EMO=0 25 | PARAM_CHEEK=0 26 | PARAM_BODY_ANGLE_X=0,-0.04,-0.15,-0.32,-0.54,-0.82,-1.12,-1.45,-1.81,-2.18,-2.56,-2.95,-3.34,-3.71,-4.09,-4.43,-4.76,-5.06,-5.32,-5.55,-5.74,-5.88,-5.97,-6,-6,-5.998,-5.993,-5.985,-5.971,-5.952,-5.93,-5.89,-5.85,-5.8,-5.74,-5.66,-5.58,-5.48,-5.37,-5.24,-5.1,-4.94,-4.77,-4.58,-4.37,-4.14,-3.89,-3.61,-3.32,-3,-2.62,-2.23,-1.79,-1.35,-0.88,-0.4,0.09,0.58,1.08,1.57,2.04,2.51,2.96,3.4,3.81,4.19,4.55,4.87,5.16,5.41,5.61,5.78,5.9,5.98,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,5.96,5.85,5.67,5.43,5.14,4.81,4.45,4.05,3.64,3.21,2.79,2.36,1.95,1.55,1.19,0.86,0.57,0.33,0.15,0.04,0 27 | PARAM_BODY_ANGLE_Z=0 28 | PARAM_BODY_Y=0 29 | PARAM_BREATH=0.5 30 | PARAM_BOING=0 31 | PARAM_HAIR_FRONT=0,-0.004,-0.016,-0.034,-0.06,-0.09,-0.13,-0.17,-0.21,-0.26,-0.31,-0.36,-0.42,-0.47,-0.53,-0.58,-0.64,-0.69,-0.74,-0.79,-0.83,-0.87,-0.91,-0.94,-0.97,-0.984,-0.996,-1,-0.984,-0.94,-0.89,-0.83,-0.76,-0.7,-0.64,-0.59,-0.55,-0.52,-0.51,-0.514,-0.522,-0.533,-0.544,-0.554,-0.562,-0.568,-0.57,-0.57,-0.568,-0.565,-0.561,-0.556,-0.549,-0.541,-0.531,-0.519,-0.506,-0.491,-0.473,-0.454,-0.43,-0.41,-0.38,-0.36,-0.33,-0.29,-0.26,-0.22,-0.18,-0.14,-0.1,-0.05,0.01,0.1,0.2,0.3,0.4,0.49,0.57,0.62,0.64,0.637,0.627,0.614,0.597,0.578,0.557,0.538,0.519,0.503,0.489,0.479,0.472,0.47,0.47,0.467,0.458,0.444,0.425,0.4,0.38,0.35,0.32,0.28,0.25,0.22,0.19,0.15,0.12,0.09,0.07,0.04,0.026,0.012,0.003,0 32 | PARAM_HAIR_SIDE_R=0,-0.001,-0.005,-0.011,-0.019,-0.03,-0.042,-0.055,-0.07,-0.086,-0.104,-0.122,-0.141,-0.16,-0.18,-0.2,-0.22,-0.24,-0.259,-0.278,-0.296,-0.314,-0.33,-0.345,-0.358,-0.37,-0.381,-0.389,-0.395,-0.399,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.4,-0.397,-0.389,-0.375,-0.357,-0.33,-0.31,-0.28,-0.24,-0.21,-0.17,-0.13,-0.08,-0.04,0.01,0.05,0.1,0.15,0.19,0.24,0.28,0.32,0.36,0.4,0.43,0.47,0.5,0.52,0.54,0.555,0.563,0.567,0.568,0.567,0.565,0.562,0.561,0.56,0.558,0.553,0.545,0.533,0.52,0.504,0.486,0.466,0.44,0.42,0.4,0.37,0.35,0.32,0.3,0.27,0.24,0.22,0.19,0.17,0.15,0.12,0.1,0.083,0.065,0.049,0.035,0.023,0.013,0.006,0.002,0 33 | PARAM_HAIR_SIDE_L=0,0,0.003,0.007,0.012,0.018,0.025,0.034,0.044,0.054,0.066,0.079,0.092,0.106,0.121,0.137,0.153,0.169,0.186,0.204,0.221,0.239,0.257,0.275,0.293,0.312,0.33,0.348,0.366,0.383,0.4,0.417,0.431,0.443,0.453,0.461,0.467,0.471,0.475,0.477,0.479,0.48,0.48,0.48,0.48,0.48,0.48,0.477,0.467,0.451,0.43,0.4,0.37,0.34,0.29,0.25,0.2,0.15,0.1,0.04,-0.02,-0.08,-0.15,-0.21,-0.28,-0.34,-0.41,-0.48,-0.54,-0.61,-0.67,-0.74,-0.8,-0.86,-0.92,-0.96,-0.98,-0.998,-1,-1,-1,-1,-1,-1,-0.997,-0.987,-0.972,-0.952,-0.93,-0.9,-0.87,-0.83,-0.79,-0.75,-0.71,-0.67,-0.62,-0.58,-0.53,-0.48,-0.44,-0.39,-0.35,-0.3,-0.26,-0.22,-0.18,-0.15,-0.12,-0.09,-0.06,-0.04,-0.023,-0.011,-0.003,0 34 | PARAM_TWIN_RIBBON_D=0,0.002,0.007,0.017,0.032,0.052,0.08,0.11,0.15,0.19,0.25,0.31,0.38,0.46,0.54,0.61,0.68,0.74,0.79,0.84,0.88,0.92,0.95,0.97,0.986,0.996,1,0.96,0.86,0.71,0.54,0.37,0.21,0.07,-0.04,-0.11,-0.13,-0.121,-0.103,-0.08,-0.06,-0.03,-0.016,-0.004,0,-0.002,-0.006,-0.013,-0.023,-0.035,-0.048,-0.064,-0.081,-0.099,-0.119,-0.139,-0.16,-0.18,-0.2,-0.23,-0.25,-0.27,-0.29,-0.31,-0.33,-0.355,-0.374,-0.392,-0.409,-0.424,-0.438,-0.45,-0.461,-0.469,-0.475,-0.479,-0.48,-0.475,-0.46,-0.44,-0.41,-0.37,-0.33,-0.28,-0.24,-0.19,-0.14,-0.1,-0.06,-0.02,0.02,0.05,0.08,0.095,0.106,0.11,0.109,0.106,0.102,0.097,0.091,0.084,0.076,0.068,0.06,0.052,0.044,0.036,0.029,0.022,0.016,0.01,0.006,0.003,0.001,0 35 | PARAM_HAIR_BACK=0 36 | PARAM_WING_ANGLE=0 37 | PARAM_WING_DEFORM=0 38 | VISIBLE:PSD=1 39 | VISIBLE:PARTS_01_HAT=1 40 | VISIBLE:PARTS_01_HAIR_FRONT_001=1 41 | VISIBLE:PARTS_01_HAIR_SIDE_001=1 42 | VISIBLE:PARTS_01_HAIR_BACK_001=1 43 | VISIBLE:PARTS_01_FACE_001=1 44 | VISIBLE:PARTS_01_BROW_001=1 45 | VISIBLE:PARTS_01_EMOTION=1 46 | VISIBLE:PARTS_01_EYE_001=1 47 | VISIBLE:PARTS_01_EYE_BALL_001=1 48 | VISIBLE:PARTS_01_NOSE_001=1 49 | VISIBLE:PARTS_01_MOUTH_001=1 50 | VISIBLE:PARTS_01_EAR_001=1 51 | VISIBLE:PARTS_01_BUST=1 52 | VISIBLE:PARTS_01_BODY=1 53 | VISIBLE:PARTS_01_WING=1 -------------------------------------------------------------------------------- /public/live2d/mtn/Breath4.mtn: -------------------------------------------------------------------------------- 1 | # Live2D Animator Motion Data 2 | $fps=30 3 | 4 | $fadein=0 5 | 6 | $fadeout=0 7 | 8 | PARAM_ANGLE_X=0,-0.04,-0.17,-0.35,-0.59,-0.88,-1.2,-1.54,-1.9,-2.27,-2.64,-3,-3.36,-3.69,-4,-4.28,-4.53,-4.72,-4.87,-4.97,-5,-5,-5,-5,-5,-5,-5,-4.61,-3.64,-2.43,-1.21,-0.17,0.59,1,1.3,1.53,1.71,1.83,1.91,1.96,1.98,1.996,2,2,1.998,1.992,1.982,1.969,1.952,1.932,1.91,1.88,1.85,1.82,1.79,1.75,1.71,1.67,1.63,1.59,1.54,1.5,1.45,1.4,1.35,1.3,1.25,1.19,1.14,1.09,1.03,0.98,0.93,0.87,0.82,0.77,0.72,0.67,0.62,0.57,0.52,0.47,0.43,0.39,0.34,0.3,0.27,0.23,0.19,0.16,0.13,0.11,0.08,0.06,0.044,0.028,0.016,0.007,0.002,0 9 | PARAM_ANGLE_Y=0,0.09,0.33,0.7,1.19,1.75,2.4,3.08,3.79,4.53,5.28,6,6.71,7.39,8,8.57,9.05,9.45,9.74,9.93,10,10,10,10,10,10,10,7.57,1.67,-5.53,-12.56,-18.2,-21.79,-23,-21.93,-19.14,-15.1,-10.48,-5.69,-1.22,2.6,5.52,7.35,8,7.992,7.97,7.93,7.88,7.81,7.73,7.64,7.53,7.42,7.29,7.16,7.01,6.86,6.7,6.53,6.35,6.17,5.98,5.79,5.59,5.39,5.19,4.98,4.77,4.56,4.35,4.13,3.92,3.71,3.49,3.28,3.08,2.87,2.67,2.47,2.27,2.08,1.9,1.72,1.54,1.37,1.21,1.06,0.92,0.78,0.65,0.54,0.43,0.33,0.25,0.17,0.11,0.06,0.03,0.01,0 10 | PARAM_ANGLE_Z=0,0.017,0.07,0.15,0.27,0.42,0.6,0.8,1.04,1.3,1.59,1.91,2.26,2.63,3.03,3.46,3.91,4.39,4.89,5.43,6,6.53,6.87,7,6.91,6.61,6,5.07,4.14,3.25,2.37,1.54,0.75,0,-0.99,-1.88,-2.66,-3.32,-3.86,-4.29,-4.61,-4.83,-4.96,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-4.97,-4.87,-4.72,-4.53,-4.28,-4.01,-3.71,-3.38,-3.03,-2.68,-2.32,-1.97,-1.62,-1.29,-0.99,-0.72,-0.47,-0.28,-0.13,-0.03,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 11 | PARAM_EMOTION=-1 12 | PARAM_EYE_L_OPEN=0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.52,0.45,0.37,0.28,0.22,0.17,0.16,0.159,0.159,0.158,0.16,0.157,0.16,0.156,0.16,0.155,0.15,0.154,0.15,0.15,0.153,0.15,0.15,0.15,0.152,0.15,0.15,0.15,0.151,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.25,0.41,0.51,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55 13 | PARAM_EYE_R_OPEN=0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.52,0.45,0.37,0.28,0.22,0.17,0.16,0.159,0.159,0.158,0.16,0.157,0.16,0.156,0.16,0.155,0.15,0.154,0.15,0.15,0.153,0.15,0.15,0.15,0.152,0.15,0.15,0.15,0.151,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.25,0.41,0.51,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55,0.55 14 | PARAM_EYE_L_OPEN2=-1 15 | PARAM_EYE_R_OPEN2=-1 16 | PARAM_EYE_BALL_X=0,-0.009,-0.03,-0.07,-0.11,-0.17,-0.22,-0.27,-0.32,-0.36,-0.4,-0.43,-0.444,-0.45,-0.441,-0.42,-0.38,-0.34,-0.28,-0.23,-0.18,-0.13,-0.09,-0.05,-0.02,-0.006,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 17 | PARAM_EYE_BALL_Y=0,0.002,0.006,0.012,0.02,0.029,0.039,0.048,0.057,0.065,0.071,0.076,0.079,0.08,0.078,0.074,0.068,0.06,0.051,0.041,0.032,0.023,0.015,0.009,0.004,0.001,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 18 | PARAM_BROW_L_Y=0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-0.04,-0.14,-0.26,-0.38,-0.47,-0.53,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.549,-0.55,-0.55,-0.55,-0.55,-0.548,-0.55,-0.55,-0.547,-0.55,-0.55,-0.546,-0.55,-0.55,-0.545,-0.54,-0.544,-0.54,-0.543,-0.54,-0.542,-0.54,-0.541,-0.54,-0.41,-0.19,-0.05,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 19 | PARAM_BROW_R_Y=0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-0.04,-0.14,-0.26,-0.38,-0.47,-0.53,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.55,-0.549,-0.55,-0.55,-0.55,-0.55,-0.548,-0.55,-0.55,-0.547,-0.55,-0.55,-0.546,-0.55,-0.55,-0.545,-0.54,-0.544,-0.54,-0.543,-0.54,-0.542,-0.54,-0.541,-0.54,-0.41,-0.19,-0.05,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 20 | PARAM_BROW_ANGLE=0.2,0.2,0.203,0.207,0.212,0.219,0.226,0.235,0.244,0.255,0.266,0.278,0.29,0.303,0.316,0.33,0.343,0.357,0.37,0.384,0.397,0.41,0.422,0.434,0.445,0.456,0.465,0.474,0.481,0.488,0.493,0.497,0.499,0.5,0.5,0.499,0.497,0.495,0.492,0.489,0.485,0.481,0.476,0.471,0.466,0.46,0.454,0.447,0.44,0.433,0.426,0.418,0.41,0.402,0.394,0.386,0.377,0.369,0.36,0.352,0.343,0.335,0.326,0.318,0.31,0.301,0.293,0.286,0.278,0.271,0.263,0.256,0.25,0.244,0.238,0.233,0.228,0.224,0.221,0.217,0.214,0.212,0.209,0.207,0.205,0.204,0.203,0.202,0.2,0.201,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2,0.2 21 | PARAM_BROW_SELECT=-0.5 22 | PARAM_MOUTH_OPEN_Y=0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.94,0.91,0.87,0.83,0.78,0.74,0.7,0.67,0.66,0.66,0.66,0.66,0.66,0.66,0.66,0.66,0.66,0.66,0.66,0.66,0.66,0.68,0.73,0.8,0.86,0.91,0.94,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95,0.95 23 | PARAM_MOUTH_OPEN2=-1 24 | PARAM_MOUTH_EMO=0 25 | PARAM_CHEEK=0 26 | PARAM_BODY_ANGLE_X=0 27 | PARAM_BODY_ANGLE_Z=0 28 | PARAM_BODY_Y=0,-0.001,-0.005,-0.011,-0.018,-0.026,-0.036,-0.046,-0.057,-0.068,-0.079,-0.09,-0.101,-0.111,-0.12,-0.129,-0.136,-0.142,-0.146,-0.149,-0.15,-0.15,-0.15,-0.15,-0.15,-0.15,-0.15,-0.15,-0.15,-0.15,-0.149,-0.148,-0.15,-0.146,-0.145,-0.144,-0.142,-0.141,-0.139,-0.137,-0.135,-0.133,-0.131,-0.129,-0.127,-0.124,-0.122,-0.119,-0.117,-0.114,-0.111,-0.108,-0.106,-0.103,-0.1,-0.097,-0.094,-0.091,-0.088,-0.085,-0.082,-0.078,-0.075,-0.072,-0.069,-0.066,-0.063,-0.06,-0.057,-0.054,-0.051,-0.048,-0.045,-0.042,-0.04,-0.037,-0.034,-0.032,-0.029,-0.027,-0.024,-0.022,-0.02,-0.018,-0.016,-0.014,-0.012,-0.01,-0.009,-0.007,-0.006,-0.005,-0.004,-0.003,-0.002,0,-0.001,0,0,0 29 | PARAM_BREATH=0.5,0.501,0.505,0.511,0.519,0.528,0.539,0.551,0.563,0.577,0.591,0.606,0.621,0.636,0.65,0.665,0.679,0.692,0.705,0.716,0.727,0.736,0.745,0.751,0.756,0.759,0.76,0.757,0.748,0.735,0.716,0.69,0.67,0.64,0.61,0.58,0.55,0.52,0.49,0.46,0.43,0.4,0.38,0.35,0.335,0.32,0.309,0.302,0.3,0.3,0.3,0.302,0.304,0.305,0.308,0.31,0.313,0.316,0.32,0.324,0.328,0.332,0.337,0.342,0.347,0.352,0.357,0.363,0.368,0.374,0.38,0.385,0.391,0.397,0.403,0.409,0.415,0.42,0.426,0.432,0.437,0.443,0.448,0.453,0.458,0.463,0.468,0.472,0.476,0.48,0.484,0.487,0.49,0.492,0.495,0.496,0.498,0.499,0.5,0.5 30 | PARAM_BOING=0 31 | PARAM_HAIR_FRONT=0,0.002,0.007,0.016,0.028,0.041,0.057,0.075,0.093,0.113,0.132,0.153,0.172,0.191,0.21,0.227,0.242,0.256,0.268,0.277,0.284,0.289,0.29,0.28,0.25,0.21,0.16,0.11,0.05,-0.01,-0.07,-0.11,-0.15,-0.18,-0.203,-0.21,-0.196,-0.16,-0.11,-0.05,0.01,0.07,0.12,0.16,0.18,0.19,0.16,0.1,0.02,-0.06,-0.12,-0.15,-0.139,-0.11,-0.08,-0.05,-0.02,-0.006,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 32 | PARAM_HAIR_SIDE_R=0,0.002,0.008,0.017,0.028,0.043,0.059,0.077,0.096,0.116,0.14,0.158,0.178,0.2,0.217,0.235,0.25,0.265,0.277,0.287,0.294,0.298,0.3,0.295,0.282,0.263,0.24,0.21,0.18,0.14,0.11,0.07,0.04,0,-0.03,-0.07,-0.1,-0.13,-0.15,-0.18,-0.192,-0.204,-0.214,-0.221,-0.225,-0.228,-0.229,-0.23,-0.23,-0.23,-0.227,-0.224,-0.219,-0.213,-0.206,-0.198,-0.19,-0.18,-0.17,-0.16,-0.149,-0.138,-0.126,-0.115,-0.104,-0.092,-0.081,-0.07,-0.06,-0.05,-0.04,-0.032,-0.024,-0.017,-0.011,-0.006,-0.003,-0.001,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 33 | PARAM_HAIR_SIDE_L=0,-0.002,-0.007,-0.015,-0.026,-0.038,-0.053,-0.07,-0.087,-0.105,-0.123,-0.142,-0.16,-0.178,-0.195,-0.211,-0.225,-0.238,-0.249,-0.258,-0.265,-0.269,-0.27,-0.268,-0.263,-0.256,-0.245,-0.232,-0.217,-0.199,-0.179,-0.16,-0.13,-0.11,-0.08,-0.04,-0.01,0.03,0.06,0.09,0.12,0.14,0.16,0.179,0.194,0.205,0.213,0.218,0.22,0.219,0.217,0.214,0.209,0.204,0.197,0.19,0.181,0.173,0.163,0.153,0.143,0.132,0.121,0.11,0.099,0.088,0.077,0.067,0.057,0.047,0.039,0.03,0.023,0.016,0.011,0.006,0.003,0.001,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 34 | PARAM_TWIN_RIBBON_D=0 35 | PARAM_HAIR_BACK=0 36 | PARAM_WING_ANGLE=0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-0.003,-0.01,-0.022,-0.037,-0.055,-0.074,-0.095,-0.12,-0.136,-0.155,-0.173,-0.188,-0.2,-0.207,-0.21,-0.21,-0.208,-0.206,-0.203,-0.199,-0.194,-0.189,-0.184,-0.177,-0.171,-0.163,-0.156,-0.148,-0.14,-0.132,-0.123,-0.115,-0.106,-0.097,-0.089,-0.081,-0.072,-0.064,-0.057,-0.049,-0.042,-0.036,-0.029,-0.023,-0.018,-0.014,-0.01,-0.006,-0.004,-0.002,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 37 | PARAM_WING_DEFORM=0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-0.006,-0.022,-0.05,-0.08,-0.12,-0.16,-0.2,-0.25,-0.29,-0.33,-0.37,-0.4,-0.43,-0.444,-0.45,-0.449,-0.446,-0.441,-0.435,-0.426,-0.417,-0.406,-0.393,-0.38,-0.365,-0.35,-0.334,-0.317,-0.3,-0.282,-0.264,-0.246,-0.227,-0.209,-0.191,-0.173,-0.155,-0.138,-0.121,-0.106,-0.09,-0.076,-0.063,-0.05,-0.039,-0.029,-0.021,-0.013,-0.008,-0.003,-0.001,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 38 | VISIBLE:PSD=1 39 | VISIBLE:PARTS_01_HAT=1 40 | VISIBLE:PARTS_01_HAIR_FRONT_001=1 41 | VISIBLE:PARTS_01_HAIR_SIDE_001=1 42 | VISIBLE:PARTS_01_HAIR_BACK_001=1 43 | VISIBLE:PARTS_01_FACE_001=1 44 | VISIBLE:PARTS_01_BROW_001=1 45 | VISIBLE:PARTS_01_EMOTION=1 46 | VISIBLE:PARTS_01_EYE_001=1 47 | VISIBLE:PARTS_01_EYE_BALL_001=1 48 | VISIBLE:PARTS_01_NOSE_001=1 49 | VISIBLE:PARTS_01_MOUTH_001=1 50 | VISIBLE:PARTS_01_EAR_001=1 51 | VISIBLE:PARTS_01_BUST=1 52 | VISIBLE:PARTS_01_BODY=1 53 | VISIBLE:PARTS_01_WING=1 -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * ==================================== 4 | * 站点功能【必需】 5 | * ==================================== 6 | */ 7 | 8 | /** 9 | * 站点标题 10 | */ 11 | title: '蝉時雨', 12 | subtitle: '蝉鸣如雨 花宵道中', 13 | 14 | /** 15 | * Github Issues 配置【文章、说说、书单、友链】, Github Issues api: https://developer.github.com/v3/issues/ 16 | */ 17 | // 博客仓库 18 | blog: 'https://api.github.com/repos/chanshiyucx/Blog', 19 | // token 从中间任意位置拆开成两部分,避免 github 代码检测失效 20 | pre: '0ad1a0539c5b96fd18fa', 21 | suf: 'aaafba9c7d1362a5746c', 22 | // 发布者 23 | creator: 'chanshiyucx', 24 | 25 | /** 26 | * Gittalk 配置【评论功能】,详细文档参见:https://github.com/gitalk/gitalk 27 | */ 28 | gitalkOption: { 29 | clientID: '864b1c2cbc4e4aad9ed8', 30 | clientSecret: '6ca16373efa03347e11a96ff92e355c5cea189bb', 31 | repo: 'Comment', 32 | owner: 'chanshiyucx', 33 | admin: ['chanshiyucx'], 34 | distractionFreeMode: false // 是否开始无干扰模式【背景遮罩】 35 | }, 36 | 37 | /** 38 | * leancloud 配置 【文章浏览次数】 39 | */ 40 | leancloud: { 41 | appId: 'b6DWxsCOWuhurfp4YqbD5cDE-gzGzoHsz', 42 | appKey: 'h564RR5uVuJV5uSeP7oFTBye' 43 | }, 44 | 45 | /** 46 | * 文章打赏 47 | */ 48 | reward: [ 49 | { 50 | type: '支付宝', 51 | qr: 'https://i.loli.net/2018/12/09/5c0cc4646388f.png' 52 | }, 53 | { 54 | type: '微信', 55 | qr: 'https://i.loli.net/2018/12/09/5c0cc46309b68.png' 56 | } 57 | ], 58 | 59 | /** 60 | * ==================================== 61 | * 页面配置【自定义】 62 | * ==================================== 63 | */ 64 | 65 | /** 66 | * 归档配置 67 | */ 68 | archivesOption: { 69 | enableGitalk: false, 70 | qoute: '文章千古事,得失寸心知' 71 | }, 72 | 73 | /** 74 | * 分类页面【与 Github Issues 的 Milestone 对应】 75 | */ 76 | catsOption: { 77 | enableGitalk: false, 78 | qoute: '行云流水,落笔生花', 79 | list: [ 80 | { 81 | name: '事件簿', // name 和 Milestone 必须一致 82 | text: '今天又是和平的一天~', 83 | img: 'https://i.loli.net/2018/12/09/5c0cc2e59a322.jpg' 84 | }, 85 | { 86 | name: '技术向', 87 | text: '技术什么的真是不懂啦', 88 | img: 'https://i.loli.net/2018/12/09/5c0cc2e8305b1.jpg' 89 | }, 90 | { 91 | name: '笔记本', 92 | text: '拾花集', 93 | img: 'https://i.loli.net/2018/12/09/5c0cc2eabee8e.jpg' 94 | }, 95 | { 96 | name: '代码库', 97 | text: 'Life is short, Code is long', 98 | img: 'https://i.loli.net/2018/12/09/5c0cc2ed1ef66.jpg' 99 | }, 100 | { 101 | name: '分享境', 102 | text: '偷偷给你看点东西', 103 | img: 'https://i.loli.net/2018/12/09/5c0cc2efb6814.jpg' 104 | }, 105 | { 106 | name: '自言语', 107 | text: '欲言又止,止言又欲', 108 | img: 'https://i.loli.net/2018/12/09/5c0cc2f1f254f.jpg' 109 | } 110 | ] 111 | }, 112 | 113 | /** 114 | * 标签配置 115 | */ 116 | tagsOption: { 117 | enableGitalk: false, 118 | qoute: '列卒周匝,星罗云布' 119 | }, 120 | 121 | //【Inspiration, Books, Friends, About】打上 Labels 分别对应灵感、书单、友链、关于页面 122 | // 模板查看: https://github.com/chanshiyucx/Blog/issues?q=is%3Aissue+is%3Aclosed 123 | 124 | /** 125 | * 灵感页面 126 | */ 127 | inspirationOption: { 128 | enableGitalk: true, 129 | qoute: '欲言又止,止言又欲' 130 | }, 131 | 132 | /** 133 | * 书单页面 134 | */ 135 | booksOption: { 136 | enableGitalk: true, 137 | qoute: '吾生也有涯,而知也无涯' 138 | }, 139 | 140 | /** 141 | * 友链页面 142 | */ 143 | friendsOption: { 144 | enableGitalk: true, 145 | qoute: '青青子衿,悠悠我心' 146 | }, 147 | 148 | /** 149 | * 关于页面 150 | */ 151 | aboutOption: { 152 | enableGitalk: true, 153 | qoute: '蝉鸣如雨,花宵道中', 154 | avatar: 'https://i.loli.net/2018/12/09/5c0cc2b4e0195.png', 155 | // 联系方式 156 | contact: [ 157 | { 158 | icon: 'https://i.loli.net/2018/12/09/5c0cc5147e2e5.png', 159 | link: 'http://mail.qq.com/cgi-bin/qm_share?t=qm_mailme&email=tNnR9Nfc1drH3N3NwZrX29k' 160 | }, 161 | { 162 | icon: 'https://i.loli.net/2018/12/09/5c0cc516d9d5f.png', 163 | link: 'https://github.com/chanshiyucx' 164 | }, 165 | { 166 | icon: 'https://i.loli.net/2018/12/09/5c0cc518dc4f4.png', 167 | link: 'https://www.zhihu.com/people/ichanshiyu/activities' 168 | }, 169 | { 170 | icon: 'https://i.loli.net/2018/12/09/5c0cc51ae4f0c.png', 171 | link: 'https://music.163.com/#/user/home?id=103060582' 172 | } 173 | ] 174 | }, 175 | 176 | /** 177 | * 音乐播放器, 参考 skPlayer 重构 178 | */ 179 | skPlayerOption: { 180 | bgImg: 'https://i.loli.net/2018/12/09/5c0cc2b905841.png', 181 | autoplay: false, // 自动播放, 默认为 false 182 | listshow: true, // 列表显示, 默认为 true 183 | mode: 'listloop', // 循环模式, 默认为 'listloop', 【'listloop', 列表循环; 'singleloop', 单曲循环】 184 | br: 198000, // 码率,[64000, 128000, 198000, 320000] 185 | source: [ 186 | { 187 | name: 'うたかたの风と蝉时雨', 188 | author: 'Little Planet', 189 | id: '729434', //网易云音乐 id 190 | cover: 'https://i.loli.net/2018/12/09/5c0cc3ca1081b.jpg' 191 | }, 192 | { 193 | name: '春の凑に', 194 | author: 'TUMENECO', 195 | id: '852318', 196 | cover: 'https://i.loli.net/2018/12/11/5c0f196d01a3a.jpg' 197 | }, 198 | { 199 | name: '夏阳炎', 200 | author: '天威梦方', 201 | id: '803557', 202 | cover: 'https://i.loli.net/2018/12/09/5c0cc3cee372a.jpg' 203 | }, 204 | { 205 | name: '秋风のとおり道', 206 | author: '风神华伝', 207 | id: '766272', 208 | cover: 'https://i.loli.net/2018/12/09/5c0cc3d13844a.jpg' 209 | }, 210 | { 211 | name: '冬のわすれもの', 212 | author: 'ハルノカゼ', 213 | id: '729461', 214 | cover: 'https://i.loli.net/2018/12/09/5c0cc3d36349c.jpg' 215 | } 216 | ] 217 | }, 218 | 219 | /** 220 | * 加载动画 221 | */ 222 | loadingImg: 'https://i.loli.net/2018/12/15/5c14be28964d2.gif', 223 | 224 | /** 225 | * 主题配色,目前主要用于文章、说说、关于等卡片配色,以后可能会有其他用途 226 | * 推荐一个好看的取色站,日本の伝統色:http://nipponcolors.com/ 227 | */ 228 | themeColors: [ 229 | '#DC9FB4', // 撫子 230 | '#E16B8C', // 紅梅 231 | '#3A8FB7', // 千草 232 | '#8F77B5', // 紫苑 233 | '#6A4C9C', // 桔梗 234 | '#60373E', // 紫鳶 235 | '#6F3381', // 菖蒲 236 | '#005CAF', // 瑠璃 237 | '#855B32', // 煎茶 238 | '#D05A6E', // 今様 239 | '#E79460', // 洗柿 240 | '#91AD70', // 柳染 241 | '#516E41', // 青丹 242 | '#1B813E', // 常磐 243 | '#33A6B8', // 浅葱 244 | '#2EA9DF', // 露草 245 | '#E03C8A' // 躑躅 246 | ], 247 | 248 | /** 249 | * 文章封面配图【顺序匹配】 250 | * 注:将文章顶图单独提取是为了重点关注文章内容,方便将来移植时不需要修改文章本身 251 | */ 252 | covers: [ 253 | 'https://i.loli.net/2018/12/09/5c0cc1bbbb6d0.jpg', 254 | 'https://i.loli.net/2018/12/09/5c0cc1bf5191e.jpg', 255 | 'https://i.loli.net/2018/12/09/5c0cc1c2ea2e4.jpg', 256 | 'https://i.loli.net/2018/12/09/5c0cc1c6e89f2.jpg', 257 | 'https://i.loli.net/2018/12/09/5c0cc1cb11699.jpg', 258 | 'https://i.loli.net/2018/12/09/5c0cc1d069835.jpg', 259 | 'https://i.loli.net/2018/12/09/5c0cc1d4c3000.jpg', 260 | 'https://i.loli.net/2018/12/09/5c0cc1d915ab6.jpg', 261 | 'https://i.loli.net/2018/12/09/5c0cc1dd58fb2.jpg', 262 | 'https://i.loli.net/2018/12/09/5c0cc1e157dc6.jpg', 263 | 264 | 'https://i.loli.net/2018/12/09/5c0cc23a8d17c.jpg', 265 | 'https://i.loli.net/2018/12/09/5c0cc23d0edde.jpg', 266 | 'https://i.loli.net/2018/12/09/5c0cc240c44b4.jpg', 267 | 'https://i.loli.net/2018/12/09/5c0cc2447b075.jpg', 268 | 'https://i.loli.net/2018/12/09/5c0cc2487465c.jpg', 269 | 'https://i.loli.net/2018/12/09/5c0cc24e1809d.jpg', 270 | 'https://i.loli.net/2018/12/09/5c0cc2575b058.jpg', 271 | 'https://i.loli.net/2018/12/16/5c1653911d1ec.jpg', 272 | 'https://i.loli.net/2018/12/16/5c16554058b72.jpg', 273 | 'https://i.loli.net/2018/12/17/5c17a601b22d5.jpg', 274 | 275 | 'https://i.loli.net/2018/12/16/5c1655c83313b.jpg', 276 | 'https://i.loli.net/2018/12/16/5c1655c8387a0.jpg', 277 | 'https://i.loli.net/2018/12/17/5c17b11680a7c.jpg', 278 | 'https://i.loli.net/2018/12/17/5c17a8b6c43db.jpg', 279 | 'https://i.loli.net/2018/12/17/5c17ae05b3176.jpg', 280 | 'https://i.loli.net/2018/12/17/5c17b29cd8a1b.jpg', 281 | 'https://i.loli.net/2018/12/16/5c16565631780.jpg', 282 | 'https://i.loli.net/2018/12/16/5c16565655b46.jpg', 283 | 'https://i.loli.net/2018/12/23/5c1f95c528395.jpg', 284 | 'https://i.loli.net/2018/12/23/5c1f976001a65.jpg', 285 | 286 | 'https://i.loli.net/2018/12/23/5c1fa29f60035.jpg', 287 | 'https://i.loli.net/2018/12/23/5c1fa29f7a9e5.jpg', 288 | 'https://i.loli.net/2018/12/23/5c1fa29f7f7d8.jpg', 289 | 'https://i.loli.net/2018/12/23/5c1fa29f81efe.jpg', 290 | 'https://i.loli.net/2018/12/23/5c1fa29f7cfbd.jpg', 291 | 'https://i.loli.net/2018/12/23/5c1fa304a69f4.jpg', 292 | 'https://i.loli.net/2018/12/23/5c1fa30493080.jpg', 293 | 'https://i.loli.net/2018/12/23/5c1fa304d5e7b.jpg', 294 | 'https://i.loli.net/2018/12/23/5c1fa304ecb2e.jpg', 295 | 'https://i.loli.net/2018/12/23/5c1fa304ea7eb.jpg', 296 | 297 | 'https://i.loli.net/2018/12/23/5c1fac185c8c4.jpg', 298 | 'https://i.loli.net/2018/12/23/5c1fac185b40f.jpg', 299 | 'https://i.loli.net/2018/12/23/5c1fac185fdec.jpg', 300 | 'https://i.loli.net/2018/12/23/5c1fac185dd91.jpg', 301 | 'https://i.loli.net/2018/12/23/5c1fac1860f3c.jpg', 302 | 'https://i.loli.net/2018/12/23/5c1facb902fe1.jpg', 303 | 'https://i.loli.net/2018/12/23/5c1facb96ab51.jpg', 304 | 'https://i.loli.net/2018/12/23/5c1facb9807ea.jpg', 305 | 'https://i.loli.net/2018/12/23/5c1facb97e2c5.jpg', 306 | 'https://i.loli.net/2018/12/23/5c1facb97bf47.jpg' 307 | ] 308 | } 309 | -------------------------------------------------------------------------------- /src/models/global.js: -------------------------------------------------------------------------------- 1 | import router from 'umi/router' 2 | import _ from 'lodash' 3 | 4 | import { 5 | queryTotal, 6 | queryHot, 7 | queryPostHot, 8 | queryCats, 9 | queryTags, 10 | queryFilterPost, 11 | queryInspirationTotal, 12 | queryPage, 13 | likeSite 14 | } from '../services' 15 | import { delay, formatPost, loadImg } from '../utils' 16 | 17 | const minDelay = 1000 18 | let lastTipsUpdateAt 19 | 20 | export default { 21 | namespace: 'app', 22 | state: { 23 | totalList: [], // 所有文章列表 24 | postList: [], // 当前文章列表 25 | post: {}, // 当前文章内容 26 | prevPost: {}, // 前篇文章 27 | nextPost: {}, // 后篇文章 28 | 29 | cats: [], // 分类 30 | tags: [], // 标签 31 | inspiration: [], // 灵感 32 | 33 | about: {}, // 关于 34 | book: {}, // 书单 35 | friend: {}, // 友链 36 | 37 | tips: '', // live2d 聊天 38 | lastTipsUpdateAt: '', // 最后一次更新 tips 39 | 40 | isLikeSite: false, // 是否已点赞 41 | likeTimes: 0 // 点赞次数 42 | }, 43 | reducers: { 44 | updateState(state, { payload }) { 45 | return { ...state, ...payload } 46 | } 47 | }, 48 | effects: { 49 | // 所有文章 50 | *queryTotal({ payload }, { call, put }) { 51 | const totalList = yield call(queryTotal, payload) 52 | const length = totalList.length 53 | _.forEach(totalList, (post, index) => formatPost(post, index, length)) 54 | yield put({ type: 'updateState', payload: { totalList } }) 55 | }, 56 | 57 | // 首页文章 58 | *queryList({ payload }, { select, take, call, put }) { 59 | const startTime = new Date() 60 | const state = yield select(state => state.app) 61 | let { totalList, postList } = state 62 | // 文章列表不存在先获取文章 63 | if (!totalList.length) { 64 | yield put({ type: 'queryTotal' }) 65 | yield take('queryTotal/@@end') 66 | totalList = yield select(state => state.app.totalList) 67 | } 68 | const { queryType } = payload 69 | const length = totalList.length 70 | let nextPostList = [] 71 | // 直接根据当前文章的角标来截取 72 | if (postList.length) { 73 | if (queryType === 'prev') { 74 | const endInx = totalList.findIndex(o => o.id === postList[0].id) 75 | for (let i = 1; i < 5; i++) { 76 | const addInx = endInx - i < 0 ? length + endInx - i : endInx - i 77 | nextPostList.unshift(totalList[addInx]) 78 | } 79 | } else if (queryType === 'next') { 80 | const startInx = totalList.findIndex(o => o.id === postList[3].id) 81 | for (let i = 1; i < 5; i++) { 82 | const addInx = startInx + i < length ? startInx + i : startInx + i - length 83 | nextPostList.push(totalList[addInx]) 84 | } 85 | } else if (queryType === 'add') { 86 | const endInx = totalList.findIndex(o => o.id === postList[postList.length - 1].id) 87 | for (let i = 1; i < 5; i++) { 88 | const addInx = endInx + i 89 | if (addInx >= totalList.length) continue 90 | nextPostList.push(totalList[addInx]) 91 | } 92 | nextPostList = postList.concat(nextPostList) 93 | } else { 94 | nextPostList = postList 95 | } 96 | } else { 97 | nextPostList = totalList.slice(0, 4) 98 | } 99 | nextPostList = yield call(queryHot, { postList: nextPostList }) // 获取热度 100 | const images = _.map(nextPostList, post => post.cover.src) 101 | yield call(loadImg, { images }) // 加载预览图 102 | const delayTime = new Date() - startTime 103 | if (delayTime < minDelay && queryType !== 'add') { 104 | yield call(delay, minDelay - delayTime) 105 | } 106 | yield put({ type: 'updateState', payload: { postList: nextPostList } }) 107 | }, 108 | 109 | // 文章内容 110 | *queryPost({ payload }, { select, take, call, put }) { 111 | const startTime = new Date() 112 | let totalList = yield select(state => state.app.totalList) 113 | // 文章列表不存在先获取文章 114 | if (!totalList.length) { 115 | yield put({ type: 'queryTotal' }) 116 | yield take('queryTotal/@@end') 117 | totalList = yield select(state => state.app.totalList) 118 | } 119 | const index = totalList.findIndex(post => post.number === parseInt(payload.number, 10)) 120 | // 若文章不存在则跳转首页 121 | if (!totalList[index]) { 122 | router.push('/') 123 | return 124 | } 125 | // 前篇和后篇 126 | let post = totalList[index] 127 | const prev = index - 1 > 0 ? index - 1 : totalList.length - 1 128 | const next = index + 1 < totalList.length ? index + 1 : 0 129 | let prevPost = totalList[prev] 130 | let nextPost = totalList[next] 131 | post = yield call(queryPostHot, { post }) 132 | prevPost = yield call(queryPostHot, { post: prevPost, add: false }) 133 | nextPost = yield call(queryPostHot, { post: nextPost, add: false }) 134 | const delayTime = new Date() - startTime 135 | if (delayTime < minDelay) { 136 | yield call(delay, minDelay - delayTime) 137 | } 138 | yield put({ type: 'updateState', payload: { post, prevPost, nextPost } }) 139 | }, 140 | 141 | // 归档文章 142 | *queryArchives({}, { select, take, call, put }) { 143 | const startTime = new Date() 144 | const state = yield select(state => state.app) 145 | let { totalList } = state 146 | if (!totalList.length) { 147 | yield put({ type: 'queryTotal' }) 148 | yield take('queryTotal/@@end') 149 | totalList = yield select(state => state.app.totalList) 150 | } 151 | const delayTime = new Date() - startTime 152 | if (delayTime < minDelay) { 153 | yield call(delay, minDelay - delayTime) 154 | } 155 | return totalList 156 | }, 157 | 158 | // 分类列表 159 | *queryCats({ payload }, { call, put }) { 160 | const startTime = new Date() 161 | const cats = yield call(queryCats, payload) 162 | const delayTime = new Date() - startTime 163 | if (delayTime < minDelay) { 164 | yield call(delay, minDelay - delayTime) 165 | } 166 | yield put({ type: 'updateState', payload: { cats } }) 167 | }, 168 | 169 | // 标签列表 170 | *queryTags({ payload }, { call, put }) { 171 | const startTime = new Date() 172 | const tags = yield call(queryTags, payload) 173 | // 筛选 tags 【Friends, Books, About, Inspiration】 174 | const filterTags = tags.filter(o => { 175 | const { name } = o 176 | return !( 177 | name === 'Friends' || 178 | name === 'Books' || 179 | name === 'About' || 180 | name === 'Inspiration' 181 | ) 182 | }) 183 | const delayTime = new Date() - startTime 184 | if (delayTime < minDelay) { 185 | yield call(delay, minDelay - delayTime) 186 | } 187 | yield put({ type: 'updateState', payload: { tags: filterTags } }) 188 | }, 189 | 190 | // 根据分类和标签筛选文章 191 | *filterPost({ payload }, { call }) { 192 | const filterPost = yield call(queryFilterPost, payload) 193 | return filterPost 194 | }, 195 | 196 | // 说说列表 197 | *queryInspirationTotal({ payload }, { call, put }) { 198 | const inspiration = yield call(queryInspirationTotal, payload) 199 | yield put({ type: 'updateState', payload: { inspiration } }) 200 | }, 201 | 202 | // 当前说说 203 | *queryInspiration({}, { select, take, call, put }) { 204 | const startTime = new Date() 205 | const state = yield select(state => state.app) 206 | let { inspiration } = state 207 | // 说说列表不存在先获取说说 208 | if (!inspiration.length) { 209 | yield put({ type: 'queryInspirationTotal' }) 210 | yield take('queryInspirationTotal/@@end') 211 | inspiration = yield select(state => state.app.inspiration) 212 | } 213 | const delayTime = new Date() - startTime 214 | if (delayTime < minDelay) { 215 | yield call(delay, minDelay - delayTime) 216 | } 217 | return inspiration 218 | }, 219 | 220 | // 书单/友链/关于 221 | *queryPage({ payload }, { call, put }) { 222 | const startTime = new Date() 223 | const { type } = payload 224 | const data = yield call(queryPage, payload) 225 | const delayTime = new Date() - startTime 226 | if (delayTime < minDelay) { 227 | yield call(delay, minDelay - delayTime) 228 | } 229 | yield put({ type: 'updateState', payload: { [type]: data } }) 230 | }, 231 | 232 | // 看板娘文字 233 | *showTips({ payload }, { select, call, put }) { 234 | lastTipsUpdateAt = new Date() 235 | yield put({ type: 'updateState', payload: { tips: payload.tips } }) 236 | yield call(delay, 6000) 237 | // 6s 内未再更新则隐藏 238 | if (new Date() - lastTipsUpdateAt > 6000) { 239 | yield put({ 240 | type: 'updateState', 241 | payload: { tips: '', lastTipsUpdateAt: new Date() } 242 | }) 243 | } 244 | }, 245 | 246 | // 加载缓存 247 | *loadStorage({ payload }, { call, put }) { 248 | const isLikeSite = window.localStorage.getItem('isLikeSite', true) 249 | const likeTimes = yield call(likeSite, { type: 'getTime' }) 250 | yield put({ type: 'updateState', payload: { isLikeSite, likeTimes } }) 251 | }, 252 | 253 | // 喜欢小站 254 | *likeSite({ payload }, { call, put }) { 255 | const likeTimes = yield call(likeSite) 256 | window.localStorage.setItem('isLikeSite', true) 257 | yield put({ 258 | type: 'updateState', 259 | payload: { likeTimes, isLikeSite: true } 260 | }) 261 | } 262 | }, 263 | 264 | // 启动 265 | subscriptions: { 266 | setup({ dispatch }) { 267 | dispatch({ type: 'loadStorage' }) // 加载本地缓存 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/components/SKPlayer/index.less: -------------------------------------------------------------------------------- 1 | .skPlayer { 2 | position: relative; 3 | user-select: none; 4 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 5 | 0 1px 5px 0 rgba(0, 0, 0, 0.16); 6 | * { 7 | margin: 0; 8 | padding: 0; 9 | box-sizing: content-box; 10 | &::before, 11 | &::after { 12 | box-sizing: content-box; 13 | } 14 | } 15 | .skPlayer-body { 16 | width: 3.8rem; 17 | height: 1rem; 18 | background-color: rgba(255, 255, 255, 0.6); 19 | border-bottom-left-radius: 0.03rem; 20 | border-bottom-right-radius: 0.03rem; 21 | } 22 | .skPlayer-picture { 23 | position: relative; 24 | float: left; 25 | width: 1rem; 26 | height: 1rem; 27 | border-radius: 0.07rem 0 0 0.05rem; 28 | z-index: 3; 29 | } 30 | .skPlayer-cover { 31 | width: 100%; 32 | border-radius: 0.03rem; 33 | transition: 0.37s; 34 | &.skPlayer-pause { 35 | filter: blur(3px); 36 | } 37 | } 38 | .skPlayer-play-btn { 39 | position: absolute; 40 | top: 50%; 41 | left: 0; 42 | display: block; 43 | width: 0.6rem; 44 | height: 0.6rem; 45 | background-color: rgba(255, 170, 255, 0.8); 46 | border-radius: 50%; 47 | overflow: hidden; 48 | z-index: 2; 49 | transition: 0.37s; 50 | transform: translate(-50%, -50%) translateZ(0); 51 | &.skPlayer-pause { 52 | left: 50%; 53 | .skPlayer-left { 54 | border: 0.14rem solid #fff; 55 | border-left: 0.02rem solid #fff; 56 | border-right: 0.02rem solid #fff; 57 | } 58 | .skPlayer-right { 59 | top: 0.16rem; 60 | } 61 | } 62 | span { 63 | display: block; 64 | width: 0; 65 | height: 0; 66 | position: absolute; 67 | transition: 0.37s; 68 | transform: translateZ(0); 69 | &.skPlayer-left { 70 | left: 0.21rem; 71 | top: 0.16rem; 72 | border-left: 0.24rem solid #fff; 73 | border-top: 0.14rem solid transparent; 74 | border-bottom: 0.14rem solid transparent; 75 | } 76 | &.skPlayer-right { 77 | right: 0.21rem; 78 | top: -0.76rem; 79 | border: 0.14rem solid #fff; 80 | border-left: 0.02rem solid #fff; 81 | border-right: 0.02rem solid #fff; 82 | } 83 | } 84 | } 85 | .skPlayer-control { 86 | position: relative; 87 | float: right; 88 | padding: 0.1rem 0.15rem 0.15rem; 89 | width: 2.5rem; 90 | height: 0.75rem; 91 | background-color: transparent; 92 | border-radius: 0 0.07rem 0.07rem 0; 93 | text-align: left; 94 | z-index: 2; 95 | color: #666; 96 | p { 97 | line-height: 1.2; 98 | overflow: hidden; 99 | &.skPlayer-name { 100 | font-size: 0.18rem; 101 | font-weight: 700; 102 | } 103 | &.skPlayer-author { 104 | max-width: 90%; 105 | font-size: 0.14rem; 106 | } 107 | } 108 | > .skPlayer-percent { 109 | position: relative; 110 | margin: 0.09rem 0; 111 | width: 2.5rem; 112 | height: 0.06rem; 113 | background-color: #ebebf2; 114 | border-radius: 0.03rem; 115 | cursor: pointer; 116 | overflow: hidden; 117 | .skPlayer-line { 118 | position: absolute; 119 | left: 0; 120 | top: 0; 121 | width: 0; 122 | height: 100%; 123 | background-color: rgba(255, 170, 255, 0.8); 124 | z-index: 2; 125 | } 126 | .skPlayer-line-loading { 127 | position: absolute; 128 | left: 0; 129 | top: 0; 130 | width: 0; 131 | height: 100%; 132 | background-color: rgba(255, 255, 255, 0.8); 133 | z-index: 1; 134 | transition: 0.7s; 135 | transform: translateZ(0); 136 | } 137 | } 138 | .skPlayer-time { 139 | float: left; 140 | font-size: 0.16rem; 141 | color: #666; 142 | } 143 | .skPlayer-volume { 144 | width: 1rem; 145 | height: 0.14rem; 146 | float: right; 147 | position: relative; 148 | margin: 0.01rem 0.24rem 0 0; 149 | .skPlayer-icon { 150 | position: absolute; 151 | top: 0.04rem; 152 | left: -0.2rem; 153 | display: block; 154 | width: 0.05rem; 155 | height: 0.06rem; 156 | background-color: #6a6b6f; 157 | cursor: pointer; 158 | &.skPlayer-quiet::before { 159 | display: block; 160 | } 161 | &::before { 162 | content: ''; 163 | position: absolute; 164 | top: -0.08rem; 165 | left: 0.05rem; 166 | display: none; 167 | width: 0.02rem; 168 | height: 0.22rem; 169 | transform: rotate(58deg); 170 | background-color: #6a6b6f; 171 | } 172 | &::after { 173 | content: ''; 174 | position: absolute; 175 | top: -0.04rem; 176 | left: -0.05rem; 177 | display: block; 178 | width: 0.05rem; 179 | height: 0.06rem; 180 | border-width: 0.04rem 0.05rem; 181 | border-style: solid; 182 | border-color: transparent #6a6b6f transparent transparent; 183 | } 184 | } 185 | .skPlayer-percent { 186 | position: absolute; 187 | top: 0; 188 | left: 0; 189 | width: 1rem; 190 | height: 0.14rem; 191 | background-color: #ebebf2; 192 | border-radius: 0.06rem; 193 | overflow: hidden; 194 | cursor: pointer; 195 | } 196 | .skPlayer-line { 197 | height: 100%; 198 | width: 100%; 199 | background-color: rgba(255, 170, 255, 0.8); 200 | transition: 0.37s; 201 | } 202 | } 203 | .skPlayer-list-switch { 204 | position: absolute; 205 | right: 0.15rem; 206 | bottom: 0.12rem; 207 | display: block; 208 | width: 0.16rem; 209 | height: 0.15rem; 210 | cursor: pointer; 211 | i::before { 212 | font-size: 0.18rem; 213 | color: #6a6b6f; 214 | } 215 | } 216 | .skPlayer-mode { 217 | position: absolute; 218 | right: 0.15rem; 219 | bottom: 0.51rem; 220 | display: block; 221 | width: 0.16rem; 222 | height: 0.17rem; 223 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAARCAYAAADUryzEAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsSAAALEgHS3X78AAABVklEQVQ4y5XTvY6NURQG4OdsnElMFFqFiCgk/htBaFAoRq0QHYO9JTsSVyB6+STfx4hO5QJMZTQkU0gw4gJM4wpIxH8x6+QMxndy3mZl7bXf911r/wwgl/oCJ/2Ll13bnNKDpB8nJtTXBMJlBi1+TjLIpW7+Y0MudYinKPiOeziObRuQL2Epl7oVRkp3cRYfMde1zZv/OA9xBzuDc3WQS92HlXA+1rXN276Zc6mH8CrMjyRcxibcn0SO81rBQwwwn3Auak8mkdfhccQzCbsjeT2FwKjTXQk/IpmdQmBLxG8Jq5HsnUJgf8TVhOeRXJhC4GLEZwkL+IVrcUW9yKUexJUY/VHq2uZdiMxgMZd6tId8GIuxd6Frm/ejl1ixx9prvG18tXKpszgQbc9jGGPfZPyZvuI8HuDWX8afsIwbQYbTWGL8F3Rt8wXXN+j8Mz5gO3asWx/AbzMQVi2KT5GSAAAAAElFTkSuQmCC); 224 | cursor: pointer; 225 | &.skPlayer-mode-loop { 226 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAARCAYAAADUryzEAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsSAAALEgHS3X78AAABa0lEQVQ4y5XTu2pVURAG4O9s9QgGC1sLEbEQvMXGG7FRCwutLcROIq4lLASfQOxlC3trbK18AFMZm0QEhcSID2Aan0BB4gWLM8cT48455G+GWWv++WfWzOpBymURM/7HUtvUF4xBZTzOT7gfJAiV3Wjwe5JAymXnPwEplz5eIuMnHuMc9naQb2Ih5bIHhpke4TK+4Grb1CtbKPfxEAeCc7uXcjmK1VA+2zb1h3E9p1xO4n2In6pwCzvwZDM55bIYE/qLtqlX8Qw9zFa4EncvOgRndI/3edhLFQ6FszxpZBswrPRghV/hTG0jwa6wPyqshXNkGwmOhV2r8Dqc6x2BS3jTcX4j7KteyuVE9LSOM/HKWyLi3xmMcbpqm/oj5gxWeT7lcnoMeRrzETvXNvWn4SYWHDbYxgdGo5VymcLxKHsW/Wj7HqPPtI5reIr7m4S/4i3uBhkuYoHRX9A29Xfc6aj8Gz5jH/ZvOO/BH1b7XciLVLbaAAAAAElFTkSuQmCC); 227 | } 228 | } 229 | } 230 | .skPlayer-list { 231 | display: none; 232 | max-height: 1.71rem; 233 | width: 100%; 234 | background-color: rgba(255, 255, 255, 0.6); 235 | background-image: url(https://i.loli.net/2018/12/09/5c0cc2b905841.png); 236 | background-size: contain; 237 | background-repeat: no-repeat; 238 | background-position: center center; 239 | border-top-left-radius: 0.03rem; 240 | border-top-right-radius: 0.03rem; 241 | overflow: hidden; 242 | list-style: none; 243 | overflow-y: auto; 244 | color: #666; 245 | li { 246 | position: relative; 247 | height: 0.32rem; 248 | line-height: 0.32rem; 249 | padding: 0 0.15rem; 250 | font-size: 0.14rem; 251 | cursor: pointer; 252 | transition: 0.2s; 253 | overflow: hidden; 254 | &:hover { 255 | background-color: rgba(255, 170, 255, 0.6); 256 | } 257 | &.skPlayer-curMusic { 258 | background-color: rgba(255, 170, 255, 0.6); 259 | .skPlayer-list-sign { 260 | display: block; 261 | } 262 | } 263 | } 264 | &::-webkit-scrollbar { 265 | width: 0.14rem; 266 | } 267 | &::-webkit-scrollbar-track { 268 | border-left: 0.01rem solid #e9e9e9; 269 | } 270 | &::-webkit-scrollbar-thumb { 271 | background-color: rgba(255, 102, 255, 1); 272 | box-shadow: inset 0 0 0.07rem rgba(0, 0, 0, 0.3); 273 | } 274 | .skPlayer-list-sign { 275 | position: absolute; 276 | left: 0; 277 | top: 0.05rem; 278 | display: none; 279 | width: 0.03rem; 280 | height: 0.22rem; 281 | background-color: rgba(255, 102, 255, 1); 282 | } 283 | .skPlayer-list-index { 284 | position: absolute; 285 | left: 0.15rem; 286 | top: 0; 287 | } 288 | .skPlayer-list-name { 289 | float: left; 290 | margin-left: 0.24rem; 291 | max-width: 2rem; 292 | } 293 | .skPlayer-list-author { 294 | float: right; 295 | max-width: 1.07rem; 296 | overflow: hidden; 297 | } 298 | } 299 | &.skPlayer-list-on { 300 | .skPlayer-list { 301 | display: block; 302 | } 303 | } 304 | } 305 | --------------------------------------------------------------------------------