Initial commit for Github

This commit is contained in:
2025-10-16 04:59:04 +08:00
commit dbe18e51bd
43 changed files with 3308 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
{
"presets": [
["es2015", { "modules": false }],
"stage-3"
],
"plugins": ["transform-runtime"],
"comments": false,
"env": {
"test": {
"plugins": [ "istanbul" ]
}
}
}
+9
View File
@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
+56
View File
@@ -0,0 +1,56 @@
# Dependencies
node_modules/
.pnp
.pnp.js
# Production builds
/dist
/public/static/js
/public/static/css
# Development
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Vite
.vite/
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
Thumbs.db
.DS_Store
# Environment
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
logs
*.log
# Coverage directory used by tools like istanbul
coverage
*.lcov
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Package backup files (if you want to ignore them)
package-backup.json
package-modern.json
package-updated-conservative.json
package-vue2-working.json
+117
View File
@@ -0,0 +1,117 @@
# Mr. Hoelee Resume - Animated Resume
This project is inspired by: [Animating Resume](https://github.com/jirengu-inc/animating-resume)
Deep respect to the original author.
An interactive animated resume built with Vue 3 and Vite that showcases your professional profile in a unique and engaging way. Features responsive design with separate mobile and desktop experiences.
## Prerequisites
- **Node.js**: >= 18.0.0 ([Download](https://nodejs.org/))
- **npm**: >= 9.0.0
- **Git**: [Download](https://git-scm.com/)
## Usage
1. Clone this repository:
```bash
git clone <your-repository-url>
cd hoelee-profile2
npm install
npm run dev
```
2. The development server will automatically open in your browser at `http://localhost:8080`
## Build and Deployment
### Building for Production
1. Build the project:
```bash
npm run build
```
2. The built files will be generated in the `dist/` directory
### Deployment Options
#### Option 1: Static Web Server
Upload the contents of the `dist/` directory to any static web server (Netlify, Vercel, GitHub Pages, etc.)
#### Option 2: Preview Locally
Test the production build locally:
```bash
npm run preview
```
## Development
- `npm run dev` - Start development server with hot reload (opens at <http://localhost:8080>)
- `npm run build` - Build for production (outputs to `dist/` directory)
- `npm run preview` - Preview production build locally
## Project Structure
```text
src/
├── App.vue # Desktop version of the resume
├── Mobile.vue # Mobile-optimized version
├── main.js # Entry point with responsive logic
├── assets/ # Stylesheets and static assets
│ └── reset.css # CSS reset styles
└── components/ # Resume components
├── ResumeEditor.vue # Resume content editor
├── StyleEditor.vue # Style/CSS editor
└── ThankEditor.vue # Thank you section
```
## Features
- **Responsive Design**: Automatically switches between desktop and mobile versions based on screen width (500px breakpoint)
- **Interactive Animation**: Type-writer effect for resume content
- **Live Preview**: Real-time preview of resume as it's being "typed"
- **Modern Stack**: Built with Vue 3 composition API and Vite for fast development
- **Syntax Highlighting**: Integrated Prism.js for code highlighting
- **Markdown Support**: Uses marked.js for markdown parsing
## Customization
### Personalizing Your Resume
1. **Content**: Edit the resume content in `src/components/ResumeEditor.vue`
2. **Styling**: Modify CSS styles in `src/components/StyleEditor.vue`
3. **Layout**: Adjust the main layout in `src/App.vue` (desktop) or `src/Mobile.vue` (mobile)
4. **Title**: Update the page title in `index.html`
### Configuration
- **Port**: Development server port is set to 8080 in `vite.config.js`
- **Build Output**: Configured to output to `dist/` directory with organized asset structure
- **Aliases**: Path aliases are set up for easy imports (`@`, `src`, `assets`, `components`)
## Technologies Used
- **Vue 3** - Progressive JavaScript framework with Composition API
- **Vite** - Next-generation frontend build tool
- **Prism.js** - Syntax highlighting library
- **Marked** - Markdown parser and compiler
- **CSS3 Animations** - Smooth typing animations and transitions
- **PostCSS & Autoprefixer** - CSS processing and vendor prefixing
## Browser Support
This project uses modern JavaScript features and is compatible with:
- Chrome/Edge 88+
- Firefox 85+
- Safari 14+
## License
This project is for personal/educational use. Please respect the original author's work.
+1
View File
@@ -0,0 +1 @@
theme: jekyll-theme-cayman
+40
View File
@@ -0,0 +1,40 @@
// https://github.com/shelljs/shelljs
require('./check-versions')()
process.env.NODE_ENV = 'production'
var ora = require('ora')
var path = require('path')
var chalk = require('chalk')
var shell = require('shelljs')
var webpack = require('webpack')
var config = require('../config')
var webpackConfig = require('./webpack.prod.conf')
var spinner = ora('building for production...')
spinner.start()
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
shell.rm('-rf', assetsPath)
shell.mkdir('-p', assetsPath)
shell.config.silent = true
shell.cp('-R', 'static/*', assetsPath)
shell.config.silent = false
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
+45
View File
@@ -0,0 +1,45 @@
var chalk = require('chalk')
var semver = require('semver')
var packageConfig = require('../package.json')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
},
{
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
}
]
module.exports = function () {
var warnings = []
for (var i = 0; i < versionRequirements.length; i++) {
var mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (var i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}
+9
View File
@@ -0,0 +1,9 @@
/* eslint-disable */
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})
+81
View File
@@ -0,0 +1,81 @@
require('./check-versions')()
var config = require('../config')
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}
var opn = require('opn')
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig = require('./webpack.dev.conf')
// default port where dev server listens for incoming traffic
var port = process.env.PORT || config.dev.port
// automatically open browser, if not set will be false
var autoOpenBrowser = !!config.dev.autoOpenBrowser
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
var proxyTable = config.dev.proxyTable
var app = express()
var compiler = webpack(webpackConfig)
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
})
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: () => {}
})
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(options.filter || context, options))
})
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware)
// serve pure static assets
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))
var uri = 'http://localhost:' + port
devMiddleware.waitUntilValid(function () {
console.log('> Listening at ' + uri + '\n')
})
module.exports = app.listen(port, function (err) {
if (err) {
console.log(err)
return
}
// when env is testing, don't need open it
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri)
}
})
+64
View File
@@ -0,0 +1,64 @@
var path = require('path')
var config = require('../config')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
exports.assetsPath = function (_path) {
var assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
// generate loader string to be used with extract text plugin
function generateLoaders (loaders) {
var sourceLoader = loaders.map(function (loader) {
var extraParamChar
if (/\?/.test(loader)) {
loader = loader.replace(/\?/, '-loader?')
extraParamChar = '&'
} else {
loader = loader + '-loader'
extraParamChar = '?'
}
return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '')
}).join('!')
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: sourceLoader,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader', sourceLoader].join('!')
}
}
// http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
return {
css: generateLoaders(['css']),
postcss: generateLoaders(['css']),
less: generateLoaders(['css', 'less']),
sass: generateLoaders(['css', 'sass?indentedSyntax']),
scss: generateLoaders(['css', 'sass']),
stylus: generateLoaders(['css', 'stylus']),
styl: generateLoaders(['css', 'stylus'])
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
var output = []
var loaders = exports.cssLoaders(options)
for (var extension in loaders) {
var loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
loader: loader
})
}
return output
}
+17
View File
@@ -0,0 +1,17 @@
var utils = require('./utils')
var config = require('../config')
var isProduction = process.env.NODE_ENV === 'production'
module.exports = {
loaders: utils.cssLoaders({
sourceMap: isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap,
extract: isProduction
}),
postcss: [
require('autoprefixer')({
browsers: ['last 2 versions']
})
]
}
+63
View File
@@ -0,0 +1,63 @@
var path = require('path')
var utils = require('./utils')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
modules: [
resolve('src'),
resolve('node_modules')
],
alias: {
'src': resolve('src'),
'assets': resolve('src/assets'),
'components': resolve('src/components')
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}
}
+35
View File
@@ -0,0 +1,35 @@
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
module.exports = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
// cheap-module-eval-source-map is faster for development
devtool: '#cheap-module-eval-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
new FriendlyErrorsPlugin()
]
})
+102
View File
@@ -0,0 +1,102 @@
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var env = config.build.env
var webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
})
]
})
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
+6
View File
@@ -0,0 +1,6 @@
var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})
+38
View File
@@ -0,0 +1,38 @@
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../public/index.html'),
assetsRoot: path.resolve(__dirname, '../public'),
assetsSubDirectory: 'static',
assetsPublicPath: '/anires/public/',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
},
dev: {
env: require('./dev.env'),
port: 8080,
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false
}
}
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
NODE_ENV: '"production"'
}
+15
View File
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>Mr Hoelee Resume</title>
<link rel="icon" type="image/x-icon" href="./favicon.ico">
<link rel="shortcut icon" type="image/x-icon" href="./favicon.ico">
<script type="module" crossorigin src="/static/js/index-BF7mfwu9.js"></script>
<link rel="stylesheet" crossorigin href="/static/css/index-D2xCV1gI.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>Mr Hoelee Resume</title>
<link rel="icon" type="image/x-icon" href="./favicon.ico">
<link rel="shortcut icon" type="image/x-icon" href="./favicon.ico">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
+1502
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
{
"name": "animation-resume",
"version": "2.0.0",
"description": "animation resume - modernized",
"author": " <>",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"marked": "^12.0.2",
"prismjs": "^1.30.0",
"vue": "^3.5.22"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"autoprefixer": "^10.4.21",
"postcss": "^8.4.49",
"vite": "^6.0.0"
},
"engines": {
"node": ">= 18.0.0",
"npm": ">= 9.0.0"
}
}
+54
View File
@@ -0,0 +1,54 @@
{
"name": "animation-resume",
"version": "1.0.0",
"description": "animation resume",
"author": " <>",
"private": true,
"scripts": {
"dev": "node build/dev-server.js",
"build": "node build/build.js"
},
"dependencies": {
"marked": "^2.1.3",
"prismjs": "^1.30.0",
"vue": "^2.7.16"
},
"devDependencies": {
"autoprefixer": "^6.7.7",
"babel-core": "^6.26.3",
"babel-loader": "^6.4.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"babel-preset-stage-3": "^6.24.1",
"babel-register": "^6.26.0",
"chalk": "^1.1.3",
"connect-history-api-fallback": "^1.6.0",
"css-loader": "^0.28.11",
"eventsource-polyfill": "^0.9.6",
"express": "^4.21.2",
"extract-text-webpack-plugin": "^2.1.2",
"file-loader": "^0.11.2",
"friendly-errors-webpack-plugin": "^1.7.0",
"function-bind": "^1.1.2",
"html-webpack-plugin": "^2.30.1",
"http-proxy-middleware": "^0.17.4",
"opn": "^4.0.2",
"ora": "^1.4.0",
"semver": "^5.7.2",
"shelljs": "^0.7.8",
"url-loader": "^0.6.2",
"vue-loader": "^10.3.0",
"vue-style-loader": "^2.0.5",
"vue-template-compiler": "^2.7.16",
"webpack": "^2.7.0",
"webpack-bundle-analyzer": "^2.13.1",
"webpack-dev-middleware": "^1.12.2",
"webpack-hot-middleware": "^2.26.1",
"webpack-merge": "^2.6.1"
},
"engines": {
"node": ">= 12.0.0",
"npm": ">= 6.0.0"
}
}
+27
View File
@@ -0,0 +1,27 @@
{
"name": "hoelee-profile",
"version": "2.0.0",
"description": "Hoelee's animated resume - modernized",
"author": "Hoelee <>",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"marked": "^12.0.2",
"prismjs": "^1.30.0",
"vue": "^3.5.22"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"autoprefixer": "^10.4.21",
"postcss": "^8.4.49",
"vite": "^6.0.0"
},
"engines": {
"node": ">= 18.0.0",
"npm": ">= 9.0.0"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+1
View File
@@ -0,0 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><title>个人简历</title><meta name=viewport content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link href=/anires/public/static/css/app.97f24077373dbd5f86744ecd100255e7.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/anires/public/static/js/manifest.22a4ae013e2ceb48b7dd.js></script><script type=text/javascript src=/anires/public/static/js/vendor.045b08f12500b7884e7e.js></script><script type=text/javascript src=/anires/public/static/js/app.c4b2e14682ed119846b5.js></script></body></html>
@@ -0,0 +1,74 @@
#app[data-v-22ce9519] {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
html[data-v-22ce9519] {
min-height: 100vh;
}
*[data-v-22ce9519] {
box-sizing: border-box;
}
pre[data-v-35c047fd]{
}
@media (max-width:500px){
pre[data-v-35c047fd]{
}
}
.code[data-v-35c047fd] {
display: none;
}
@media (max-width:500px){
.resumeEditor[data-v-647e008b]{
}
}
.htmlMode[data-v-647e008b] {
animation: flip 2s;
}
@keyframes flip {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@media (max-width:500px){
.thankEditor[data-v-29ad1df6]{
}
}
.htmlMode[data-v-29ad1df6] {
animation: flip 2s;
}
@keyframes flip {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
*{margin: 0; padding: 0;}
*{box-sizing: border-box;}
*::before{box-sizing: border-box;}
*::after{box-sizing: border-box;}
#app[data-v-53c8d54c] {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-height: 100vh; position: relative;
}
html[data-v-53c8d54c] {
min-height: 100vh;
}
*[data-v-53c8d54c]{
box-sizing: border-box;
}
/*# sourceMappingURL=app.97f24077373dbd5f86744ecd100255e7.css.map*/
@@ -0,0 +1 @@
{"version":3,"sources":["webpack:///./src/App.vue","webpack:///./src/components/StyleEditor.vue","webpack:///./src/components/ResumeEditor.vue","webpack:///./src/components/ThankEditor.vue","webpack:///./src/assets/reset.css","webpack:///./src/Mobile.vue"],"names":[],"mappings":";AACA;EACE,oDAAoD;EACpD,oCAAoC;EACpC,mCAAmC;CACpC;AACD;EACE,kBAAkB;CACnB;AACD;EACE,uBAAuB;CACxB;;ACVD;CACC;AACD;AACA;CACC;CACA;AACD;EACE,cAAc;CACf;;ACRD;AACA;CACC;CACA;AACD;EACE,mBAAmB;CACpB;AACD;AACA;IACI,WAAW;CACd;AACD;IACI,WAAW;CACd;CACA;;ACdD;AACA;CACC;CACA;AACD;EACE,mBAAmB;CACpB;AACD;AACA;IACI,WAAW;CACd;AACD;IACI,WAAW;CACd;CACA;ACfD,EAAE,UAAU,CAAC,WAAW,CAAC;AACzB,EAAE,uBAAuB,CAAC;AAC1B,UAAU,uBAAuB,CAAC;AAClC,SAAS,uBAAuB,CAAC;;ACFjC;EACE,oDAAoD;EACpD,oCAAoC;EACpC,mCAAmC;EACnC,kBAAkB,CAAC,mBAAmB;CACvC;AACD;EACE,kBAAkB;CACnB;AACD;EACE,uBAAuB;CACxB","file":"static/css/app.97f24077373dbd5f86744ecd100255e7.css","sourcesContent":["\n#app[data-v-22ce9519] {\n font-family: 'Avenir', Helvetica, Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\nhtml[data-v-22ce9519] {\n min-height: 100vh;\n}\n*[data-v-22ce9519] {\n box-sizing: border-box;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/App.vue","\npre[data-v-35c047fd]{\n}\n@media (max-width:500px){\npre[data-v-35c047fd]{\n}\n}\n.code[data-v-35c047fd] {\n display: none;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/components/StyleEditor.vue","\n@media (max-width:500px){\n.resumeEditor[data-v-647e008b]{\n}\n}\n.htmlMode[data-v-647e008b] {\n animation: flip 2s;\n}\n@keyframes flip {\nfrom {\n opacity: 0;\n}\nto {\n opacity: 1;\n}\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/components/ResumeEditor.vue","\n@media (max-width:500px){\n.thankEditor[data-v-29ad1df6]{\n}\n}\n.htmlMode[data-v-29ad1df6] {\n animation: flip 2s;\n}\n@keyframes flip {\nfrom {\n opacity: 0;\n}\nto {\n opacity: 1;\n}\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/components/ThankEditor.vue","*{margin: 0; padding: 0;}\n*{box-sizing: border-box;}\n*::before{box-sizing: border-box;}\n*::after{box-sizing: border-box;}\n\n\n\n// WEBPACK FOOTER //\n// ./src/assets/reset.css","\n#app[data-v-53c8d54c] {\n font-family: 'Avenir', Helvetica, Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n min-height: 100vh; position: relative;\n}\nhtml[data-v-53c8d54c] {\n min-height: 100vh;\n}\n*[data-v-53c8d54c]{\n box-sizing: border-box;\n}\n\n\n\n\n// WEBPACK FOOTER //\n// ./src/Mobile.vue"],"sourceRoot":""}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,i){for(var u,a,f,s=0,l=[];s<t.length;s++)a=t[s],o[a]&&l.push(o[a][0]),o[a]=0;for(u in c)Object.prototype.hasOwnProperty.call(c,u)&&(e[u]=c[u]);for(r&&r(t,c,i);l.length;)l.shift()();if(i)for(s=0;s<i.length;s++)f=n(n.s=i[s]);return f};var t={},o={2:0};n.e=function(e){function r(){u.onerror=u.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var i=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,n.nc&&u.setAttribute("nonce",n.nc),u.src=n.p+"static/js/"+e+"."+{0:"045b08f12500b7884e7e",1:"c4b2e14682ed119846b5"}[e]+".js";var a=setTimeout(r,12e4);return u.onerror=u.onload=r,i.appendChild(u),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="/anires/public/",n.oe=function(e){throw console.error(e),e}}([]);
//# sourceMappingURL=manifest.22a4ae013e2ceb48b7dd.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+384
View File
@@ -0,0 +1,384 @@
<template>
<div id="app">
<div id="content">
<StyleEditor ref="styleEditor" :code="currentStyle"></StyleEditor>
<ResumeEditor ref="resumeEditor" :markdown="currentMarkdown" :enableHtml="enableHtml"></ResumeEditor>
</div>
<div id="foot">
<ThankEditor ref="thankEditor" :markdown="currentThankMarkdown" :enableHtml="enableHtml"></ThankEditor>
</div>
</div>
</template>
<script>
import StyleEditor from './components/StyleEditor.vue'
import ResumeEditor from './components/ResumeEditor.vue'
import ThankEditor from './components/ThankEditor.vue'
import './assets/reset.css'
export default {
name: 'app',
components: {
StyleEditor,
ResumeEditor,
ThankEditor
},
data() {
return {
interval: 1,
currentStyle: '',
enableHtml: false,
fullStyle: [
`/*
* GitHub: https://github.com/hoelee/
* Hi, I'm Mr. Hoelee — Web3 Developer & Full Stack Engineer
* Let me create an animated resume!
*/
/* Add transition effects to all elements */
* {
transition: all .1s;
}
/* Set background color */
html {
color: rgb(222,222,222); background: rgb(0,64,64);
}
#content{
height:70vh;
margin:0;
}
#foot{
height:29vh;
margin:0;
}
/* Set border */
.styleEditor {
padding: .5em;
border: 1px solid;
margin: .5em;
overflow: auto;
width: 50vw; height: 70vh;
background: rgb(20,20,20);
}
/* Code highlighting */
.token.selector{ color: rgb(130,150,0); }
.token.property{ color: rgb(190,140,0); }
.token.punctuation{ color: yellow; }
.token.function{ color: rgb(40,160,150); }
/* Add 3D effect */
html{
perspective: 1000px;
}
.styleEditor {
position: fixed; left: 0; top: 0;
-webkit-transition: none;
transition: none;
-webkit-transform: rotateY(10deg) translateZ(-100px);
transform: rotateY(10deg) translateZ(-100px);
}
/* Prepare an editor */
.resumeEditor{
position: fixed; right: 0; top: 0;
padding: .5em; margin: .5em;
width: 50vw; height: 70vh;
border: 1px solid;
background: rgb(200,200,200); color: #222;
overflow: auto;
-webkit-transition: none;
transition: none;
-webkit-transform: rotateY(-10deg) translateZ(-100px);
transform: rotateY(-10deg) translateZ(-100px);
}
/* Start writing resume */
`, `
/* Convert Markdown to HTML
* then add some styles to HTML
*/
.resumeEditor{
padding: 0.5em;
}
.resumeEditor h1{
font-size: 1.3em;
}
.resumeEditor h2{
font-size: 1.2em;
display: inline-block;
border-bottom: 1px solid;
margin: 1em 0 .5em;
}
.resumeEditor ul,.resumeEditor ol{
list-style: none;
}
.resumeEditor ul> li::before{
content: '•';
margin-right: .5em;
}
.resumeEditor ol {
counter-reset: section;
}
.resumeEditor ol li::before {
counter-increment: section;
content: counters(section, ".") " ";
margin-right: .5em;
}
.resumeEditor blockquote {
margin: 1em;
padding: .5em;
background: #ddd;
}
`, `/* Write a thank you note.
* Thanks for everyone's attention.
*/
.styleEditor{
width:49vw;
height:70vh;
margin-left:1.2vw;
}
.resumeEditor{
width:49vw;
height:70vh;
margin-right:1.2em;
}
.thankEditor{
position: relative; left: 0; top: 0;
background: #E9D9BB;
color: #001C42;
overflow: auto;
}
.thankEditor {
width: 99vw; height: 45vh;
border: 1px solid #ccc;
font-size: .9em;
}
`,`
.thankEditor{
padding: .5em; margin: .5em; margin-top:1em;
}
.thankEditor ul,.thankEditor ol{
list-style: none;
}
.thankEditor ul> li::before{
content: '☞'; color: red;
margin-right: .5em;
}
.thankEditor ol {
counter-reset: section;
}
.thankEditor ol li::before {
counter-increment: section;
content: counters(section, "☞") " ";
margin-right: .5em;
}
.thankEditor{
width: 95vw;
height: 45vh;
margin: 0 auto;
-webkit-transition: none;
transition: none;
-webkit-transform: rotateX(-10deg) translateZ(-200px);
transform: rotateX(-10deg) translateZ(-200px);
}
`],
currentMarkdown: '',
currentThankMarkdown: '',
fullMarkdown: `Mr. Hoelee
====
Hi, I'm Mr. Hoelee — a Web3 Developer & Experience Full Stack Engineer.
I design and build bridges between Web2 and Web3 — crafting scalable backend systems, gas-optimized smart contracts, and immersive decentralized frontends.
My passion lies at the intersection of blockchain innovation, AI automation, and full-stack engineering — shaping how the next internet will work.
About Me
----
I'm a passionate Web3 developer with hands-on experience creating secure, upgradeable, and gas-efficient ERC-20 / ERC-721 smart contracts using Solidity, Hardhat, and Foundry.
Beyond the blockchain, I bring an enterprise-level backend mindset — leveraging Java Spring Boot, CodeIgniter, and Dockerized infrastructure to scale decentralized apps with the reliability of traditional systems.
When I'm not building, I explore the evolving Web3 landscape — from Layer 2 protocols and DAO governance to DeFi innovation and on-chain analytics.
My mission: to merge creativity, code, and cryptography into meaningful digital experiences.
Web3 & Blockchain
----
- Solidity smart contracts (ERC-20 / ERC-721 / upgradeable patterns)
- Hardhat, Foundry testing, OpenZeppelin & Chainlink Oracles
- Alchemy SDK, Polygon, IPFS integration
Frontend Development
----
- Web3.js / Ethers.js integration
- JavaScript, jQuery, HTML5, CSS3
- UI/UX prototyping with Photoshop, Illustrator, Canva
Backend & DevOps
----
- PHP CodeIgniter (Web2↔Web3 bridge)
- Java Spring Boot + Hibernate for scalable data layers
- Databases: MySQL, MongoDB, Memcached, Elasticsearch
- Docker, Nginx, IIS, OpenLiteSpeed, Apache Tomcat, WordPress
- Server management (Windows, Ubuntu, unRaid, DSM)
Work Ethic
----
- Effective Communicator: Strong written & verbal clarity in remote teams
- Problem-Solver: Adaptive mindset for complex, high-speed development
- Continuous Learner: Always exploring blockchain advancements
- AI-Driven: Using AI to accelerate workflows and creative tasks
- Client-Focused: Prioritizing real solutions and transparent delivery
Education - Bachelor of Information System (Hons)
----
- Universiti Tunku Abdul Rahman (UTAR), Kampar, Malaysia
- March 2016 | CGPA: 3.06 / 4.00 - Degree In Merit
- Download Certificate → [hoelee.com/cert](https://hoelee.com/cert)
Languages
----
English | Malay | Mandarin | Cantonese | Hokkien
Interests
----
- DeFi Innovation
- DAO Governance
- AI-assisted Development
- Cryptography
- On-Chain Analytics
`, thanksMarkdown: `
Thank you for reading!
----
I'm open to remote collaborations and Web3-driven projects.
Let's build something that connects the decentralized future with real-world impact.
* WhatsApp Mr Hoelee Now → [+6012-7972969](https://wa.me/60127972969)
* Email Mr Hoelee Now → [[email protected]](mailto:[email protected])
* Passionate about bridging the gap between traditional systems and decentralized technologies
* Ready to contribute to innovative Web3 projects that solve real-world problems
* Available for remote collaboration with forward-thinking teams and startups
* Let's discuss how we can leverage blockchain technology to create meaningful digital experiences
Get in touch to explore opportunities in:
* DeFi protocol development
* Smart contract architecture and optimization
* Web3 frontend integration
* Scalable backend systems for dApps
* Cross-chain solutions and Layer 2 implementations
`
}
},
created() {
this.makeResume()
},
methods: {
makeResume: async function () {
await this.progressivelyShowStyle(0);
await this.progressivelyShowResume();
await this.progressivelyShowStyle(1);
await this.showHtml();
await this.progressivelyShowStyle(1);
await this.progressivelyShowThanks();
await this.progressivelyShowStyle(1)
},
showHtml() {
return new Promise((resolve, reject) => {
this.enableHtml = true
resolve()
})
},
progressivelyShowStyle(n) {
return new Promise((resolve, reject) => {
let interval = this.interval
let showStyle = (async function () {
let style = this.fullStyle[n]
if (!style) {
return
}
// Calculate total character count of first n styles
let length = this.fullStyle.filter((_, index) => index <= n).map((item) => item.length).reduce((p, c) => p + c, 0)
let prefixLength = length - style.length
if (this.currentStyle.length < length) {
let l = this.currentStyle.length - prefixLength
let char = style.substring(l, l + 1) || ' '
this.currentStyle += char
if (style.substring(l - 1, l) === '\n' && this.$refs.styleEditor) {
this.$nextTick(() => {
this.$refs.styleEditor.goBottom()
})
}
setTimeout(showStyle, interval)
} else {
resolve()
}
}).bind(this)
showStyle()
})
},
progressivelyShowResume() {
return new Promise((resolve, reject) => {
let length = this.fullMarkdown.length
let interval = this.interval
let showResume = () => {
if (this.currentMarkdown.length < length) {
this.currentMarkdown = this.fullMarkdown.substring(0, this.currentMarkdown.length + 1)
let lastChar = this.currentMarkdown[this.currentMarkdown.length - 1]
let prevChar = this.currentMarkdown[this.currentMarkdown.length - 2]
if (prevChar === '\n' && this.$refs.resumeEditor) {
this.$nextTick(() => this.$refs.resumeEditor.goBottom())
}
setTimeout(showResume, interval)
} else {
resolve()
}
}
showResume()
})
},
progressivelyShowThanks() {
return new Promise((resolve, reject) => {
let length = this.thanksMarkdown.length
let interval = this.interval
let showThanks = () => {
if (this.currentThankMarkdown.length < length) {
this.currentThankMarkdown = this.thanksMarkdown.substring(0, this.currentThankMarkdown.length + 1)
let lastChar = this.currentThankMarkdown[this.currentThankMarkdown.length - 1]
let prevChar = this.currentThankMarkdown[this.currentThankMarkdown.length - 2]
if (prevChar === '\n' && this.$refs.thankEditor) {
this.$nextTick(() => this.$refs.thankEditor.goBottom())
}
setTimeout(showThanks, interval)
} else {
resolve()
}
}
showThanks()
})
}
}
}
</script>
<style scoped>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
html {
min-height: 100vh;
}
* {
box-sizing: border-box;
}
</style>
+318
View File
@@ -0,0 +1,318 @@
<template>
<div id="app">
<StyleEditor ref="styleEditor" :code="currentStyle"></StyleEditor>
<ResumeEditor ref="resumeEditor" :markdown="currentMarkdown" :enableHtml="enableHtml"></ResumeEditor>
</div>
</template>
<script>
import StyleEditor from './components/StyleEditor.vue'
import ResumeEditor from './components/ResumeEditor.vue'
import './assets/reset.css'
export default {
name: 'app',
components: {
StyleEditor,
ResumeEditor
},
data() {
return {
interval: 1,
currentStyle: '',
enableHtml: false,
fullStyle: [
`/*
* GitHub: https://github.com/hoelee/
* Hi, I'm Mr. HoeleeWeb3 Developer & Full Stack Engineer
* Let me create an animated resume!
*/
/* Add transition effects to all elements */
* {
transition: all .2s;
}
/* Set background color */
html {
color: rgb(222,222,222);
background: rgb(0,43,54);
}
/* Set border */
.styleEditor {
padding: .5em;
border: 1px solid;
overflow: auto;
width: 96vw;
margin: 2.3vh 2vw;
height: 90vh;
}
/* Too short */
.styleEditor {
height: 47vh;
}
/* Code highlighting */
.token.selector{
color: rgb(133,153,0);
}
.token.property{
color: rgb(187,137,0);
}
.token.punctuation{
color: yellow;
}
.token.function{
color: rgb(42,161,152);
}
/* Add 3D effect */
html{
perspective: 1000px;
}
.styleEditor {
position: fixed; left: 0; top: 0;
transform: rotateX(-10deg) translateZ(-50px) ;
}
/* Prepare an editor */
.resumeEditor{
position: fixed;
top: 48%; left: 0;
padding: .5em; margin: 1.5vh;
width: 94vw; height: 48.5vh;
border: 1px solid;
background: white; color: #222;
overflow: auto;
}
/* Start writing resume */
`,
`
/* Convert Markdown to HTML
* then add some styles to HTML
*/
.resumeEditor{
padding: 1em;
}
.resumeEditor h1{
font-size: 1.3em;
}
.resumeEditor h2{
font-size: 1.2em;
display: inline-block;
border-bottom: 1px solid;
margin: 1em 0 .5em;
}
.resumeEditor ul,.resumeEditor ol{
list-style: none;
}
.resumeEditor ul> li::before{
content: '•';
margin-right: .5em;
}
.resumeEditor ol {
counter-reset: section;
}
.resumeEditor ol li::before {
counter-increment: section;
content: counters(section, ".") " ";
margin-right: .5em;
}
.resumeEditor blockquote {
margin: 1em;
padding: .5em;
background: #ddd;
}
`, `
/* Add mobile-optimized styles */
.resumeEditor {
font-size: 14px;
line-height: 1.4;
}
.resumeEditor h1 {
color: #0066cc;
text-align: center;
margin-bottom: 0.5em;
}
.resumeEditor a {
color: #0066cc;
text-decoration: none;
}
.resumeEditor a:hover {
text-decoration: underline;
}
`],
currentMarkdown: '',
fullMarkdown: `Mr. Hoelee
====
Hi, I'm Mr. Hoelee — a Web3 Developer & Experience Full Stack Engineer.
I design and build bridges between Web2 and Web3 — crafting scalable backend systems, gas-optimized smart contracts, and immersive decentralized frontends.
My passion lies at the intersection of blockchain innovation, AI automation, and full-stack engineering — shaping how the next internet will work.
About Me
----
I'm a passionate Web3 developer with hands-on experience creating secure, upgradeable, and gas-efficient ERC-20 / ERC-721 smart contracts using Solidity, Hardhat, and Foundry.
Beyond the blockchain, I bring an enterprise-level backend mindset — leveraging Java Spring Boot, CodeIgniter, and Dockerized infrastructure to scale decentralized apps with the reliability of traditional systems.
When I'm not building, I explore the evolving Web3 landscape — from Layer 2 protocols and DAO governance to DeFi innovation and on-chain analytics.
My mission: to merge creativity, code, and cryptography into meaningful digital experiences.
Web3 & Blockchain
----
- Solidity smart contracts (ERC-20 / ERC-721 / upgradeable patterns)
- Hardhat, Foundry testing, OpenZeppelin & Chainlink Oracles
- Alchemy SDK, Polygon, IPFS integration
Frontend Development
----
- Web3.js / Ethers.js integration
- JavaScript, jQuery, HTML5, CSS3
- UI/UX prototyping with Photoshop, Illustrator, Canva
Backend & DevOps
----
- PHP CodeIgniter (Web2↔Web3 bridge)
- Java Spring Boot + Hibernate for scalable data layers
- Databases: MySQL, MongoDB, Memcached, Elasticsearch
- Docker, Nginx, IIS, OpenLiteSpeed, Apache Tomcat, WordPress
- Server management (Windows, Ubuntu, unRaid, DSM)
Work Ethic
----
- Effective Communicator: Strong written & verbal clarity in remote teams
- Problem-Solver: Adaptive mindset for complex, high-speed development
- Continuous Learner: Always exploring blockchain advancements
- AI-Driven: Using AI to accelerate workflows and creative tasks
- Client-Focused: Prioritizing real solutions and transparent delivery
Education - Bachelor of Information System (Hons)
----
- Universiti Tunku Abdul Rahman (UTAR), Kampar, Malaysia
- March 2016 | CGPA: 3.06 / 4.00 - Degree In Merit
- Download Certificate → [hoelee.com/cert](https://hoelee.com/cert)
Languages
----
English | Malay | Mandarin | Cantonese | Hokkien
Interests
----
- DeFi Innovation
- DAO Governance
- AI-assisted Development
- Cryptography
- On-Chain Analytics
Contact Me
----
I'm open to remote collaborations and Web3-driven projects.
Let's build something that connects the decentralized future with real-world impact.
* WhatsApp Mr Hoelee Now → [+6012-7972969](https://wa.me/60127972969)
* Email Mr Hoelee Now → [[email protected]](mailto:[email protected])
* Ready to contribute to innovative Web3 projects that solve real-world problems
* Available for remote collaboration with forward-thinking teams and startups
Get in touch to explore opportunities in:
* DeFi protocol development
* Smart contract architecture and optimization
* Web3 frontend integration
* Scalable backend systems for dApps
* Cross-chain solutions and Layer 2 implementations
`
}
},
created() {
this.makeResume()
},
methods: {
makeResume: async function () {
await this.progressivelyShowStyle(0)
await this.progressivelyShowResume()
await this.progressivelyShowStyle(1)
await this.showHtml()
await this.progressivelyShowStyle(1)
},
showHtml: function () {
return new Promise((resolve, reject) => {
this.enableHtml = true
this.$nextTick(() => {
this.$refs.resumeEditor.goTop()
})
resolve()
})
},
progressivelyShowStyle(n) {
return new Promise((resolve, reject) => {
let interval = this.interval
let showStyle = (async function () {
let style = this.fullStyle[n]
if (!style) { return }
// Calculate total character count of first n styles
let length = this.fullStyle.filter((_, index) => index <= n).map((item) => item.length).reduce((p, c) => p + c, 0)
let prefixLength = length - style.length
if (this.currentStyle.length < length) {
let l = this.currentStyle.length - prefixLength
let char = style.substring(l, l + 1) || ' '
this.currentStyle += char
if (style.substring(l - 1, l) === '\n' && this.$refs.styleEditor) {
this.$nextTick(() => {
this.$refs.styleEditor.goBottom()
})
}
setTimeout(showStyle, interval)
} else {
resolve()
}
}).bind(this)
showStyle()
})
},
progressivelyShowResume() {
return new Promise((resolve, reject) => {
let length = this.fullMarkdown.length
let interval = this.interval
let showResume = () => {
if (this.currentMarkdown.length < length) {
this.currentMarkdown = this.fullMarkdown.substring(0, this.currentMarkdown.length + 1)
let lastChar = this.currentMarkdown[this.currentMarkdown.length - 1]
let prevChar = this.currentMarkdown[this.currentMarkdown.length - 2]
if (prevChar === '\n' && this.$refs.resumeEditor) {
this.$nextTick(() => this.$refs.resumeEditor.goBottom())
}
setTimeout(showResume, interval)
} else {
resolve()
}
}
showResume()
})
}
}
}
</script>
<style scoped>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-height: 100vh; position: relative;
}
html {
min-height: 100vh;
}
*{
box-sizing: border-box;
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

+4
View File
@@ -0,0 +1,4 @@
*{margin: 0; padding: 0;}
*{box-sizing: border-box;}
*::before{box-sizing: border-box;}
*::after{box-sizing: border-box;}
+48
View File
@@ -0,0 +1,48 @@
<template>
<div class="resumeEditor" :class="{htmlMode:enableHtml}" ref="container">
<div v-if="enableHtml" v-html="result"></div>
<pre v-else>{{result}}</pre>
</div>
</template>
<script>
import { marked } from 'marked'
export default {
props: ['markdown', 'enableHtml'],
name: 'ResumeEditor',
computed: {
result: function () {
return this.enableHtml ? marked(this.markdown) : this.markdown
}
},
methods: {
goBottom: function () {
this.$refs.container.scrollTop = 100000
},
goTop: function(){
this.$refs.container.scrollTop = 0
}
}
}
</script>
<style scoped>
@media (max-width:500px){
.resumeEditor{
}
}
.htmlMode {
animation: flip 2s;
}
@keyframes flip {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>
+40
View File
@@ -0,0 +1,40 @@
<template>
<div class="styleEditor" ref="container">
<div class="code" v-html="codeInStyleTag"></div>
<pre class="" v-html="highlightedCode"></pre>
</div>
</template>
<script>
import Prism from 'prismjs'
export default {
name: 'Editor',
props: ['code'],
computed: {
highlightedCode: function () {
return Prism.highlight(this.code, Prism.languages.css)
},
codeInStyleTag: function () {
return `<style>${this.code}</style>`
}
},
methods: {
goBottom() {
this.$refs.container.scrollTop = 100000
}
}
}
</script>
<style scoped>
pre{
}
@media (max-width:500px){
pre{
}
}
.code {
display: none;
}
</style>
+48
View File
@@ -0,0 +1,48 @@
<template>
<div class="thankEditor" :class="{htmlMode:enableHtml}" ref="container">
<div v-if="enableHtml" v-html="result"></div>
<pre v-else>{{result}}</pre>
</div>
</template>
<script>
import { marked } from 'marked'
export default {
props: ['markdown', 'enableHtml'],
name: 'ThankEditor',
computed: {
result: function () {
return this.enableHtml ? marked(this.markdown) : this.markdown
}
},
methods: {
goBottom: function () {
this.$refs.container.scrollTop = 100000
},
goTop: function(){
this.$refs.container.scrollTop = 0
}
}
}
</script>
<style scoped>
@media (max-width:500px){
.thankEditor{
}
}
.htmlMode {
animation: flip 2s;
}
@keyframes flip {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>
+7
View File
@@ -0,0 +1,7 @@
import { createApp } from 'vue'
import App from './App.vue'
import Mobile from './Mobile.vue'
const width = document.documentElement.clientWidth
const app = createApp(width > 500 ? App : Mobile)
app.mount('#app')
View File
+31
View File
@@ -0,0 +1,31 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'src': resolve(__dirname, 'src'),
'assets': resolve(__dirname, 'src/assets'),
'components': resolve(__dirname, 'src/components')
}
},
server: {
port: 8080,
open: true
},
build: {
outDir: 'dist',
assetsDir: 'static',
rollupOptions: {
output: {
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
}
}
}
})