[
  {
    "path": ".gitignore",
    "content": "\n# Created by https://www.toptal.com/developers/gitignore/api/macos,node\n# Edit at https://www.toptal.com/developers/gitignore?templates=macos,node\n\n### macOS ###\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n.env.production\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n# End of https://www.toptal.com/developers/gitignore/api/macos,node"
  },
  {
    "path": "bundler/webpack.common.js",
    "content": "const CopyWebpackPlugin = require('copy-webpack-plugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst MiniCSSExtractPlugin = require('mini-css-extract-plugin')\nconst path = require('path')\n\nmodule.exports = {\n    entry: path.resolve(__dirname, '../src/script.js'),\n    output:\n    {\n        filename: 'bundle.[contenthash].js',\n        path: path.resolve(__dirname, '../dist')\n    },\n    devtool: 'source-map',\n    plugins:\n    [\n        new CopyWebpackPlugin({\n            patterns: [\n                { from: path.resolve(__dirname, '../static') }\n            ]\n        }),\n        new HtmlWebpackPlugin({\n            template: path.resolve(__dirname, '../src/index.html'),\n            minify: true\n        }),\n        new MiniCSSExtractPlugin()\n    ],\n    module:\n    {\n        rules:\n        [\n            // HTML\n            {\n                test: /\\.(html)$/,\n                use: ['html-loader']\n            },\n\n            // JS\n            {\n                test: /\\.js$/,\n                exclude: /node_modules/,\n                use:\n                [\n                    'babel-loader'\n                ]\n            },\n\n            // CSS\n            {\n                test: /\\.css$/,\n                use:\n                [\n                    MiniCSSExtractPlugin.loader,\n                    'css-loader'\n                ]\n            },\n\n            // Images\n            {\n                test: /\\.(jpg|png|gif|svg)$/,\n                use:\n                [\n                    {\n                        loader: 'file-loader',\n                        options:\n                        {\n                            outputPath: 'assets/images/'\n                        }\n                    }\n                ]\n            },\n\n            // Fonts\n            {\n                test: /\\.(ttf|eot|woff|woff2)$/,\n                use:\n                [\n                    {\n                        loader: 'file-loader',\n                        options:\n                        {\n                            outputPath: 'assets/fonts/'\n                        }\n                    }\n                ]\n            },\n\n            // Shaders\n            {\n                test: /\\.(glsl|vs|fs|vert|frag)$/,\n                exclude: /node_modules/,\n                use: [\n                    'raw-loader',\n                    'glslify-loader'\n                ]\n            }\n        ]\n    }\n}\n"
  },
  {
    "path": "bundler/webpack.dev.js",
    "content": "const { merge } = require('webpack-merge')\nconst commonConfiguration = require('./webpack.common.js')\nconst ip = require('internal-ip')\nconst portFinderSync = require('portfinder-sync')\n\nconst infoColor = (_message) =>\n{\n    return `\\u001b[1m\\u001b[34m${_message}\\u001b[39m\\u001b[22m`\n}\n\nmodule.exports = merge(\n    commonConfiguration,\n    {\n        mode: 'development',\n        devServer:\n        {\n            host: '0.0.0.0',\n            port: portFinderSync.getPort(8080),\n            contentBase: './dist',\n            watchContentBase: true,\n            open: true,\n            https: false,\n            useLocalIp: true,\n            disableHostCheck: true,\n            overlay: true,\n            noInfo: true,\n            after: function(app, server, compiler)\n            {\n                const port = server.options.port\n                const https = server.options.https ? 's' : ''\n                const localIp = ip.v4.sync()\n                const domain1 = `http${https}://${localIp}:${port}`\n                const domain2 = `http${https}://localhost:${port}`\n                \n                console.log(`Project running at:\\n  - ${infoColor(domain1)}\\n  - ${infoColor(domain2)}`)\n            }\n        }\n    }\n)\n"
  },
  {
    "path": "bundler/webpack.prod.js",
    "content": "const { merge } = require('webpack-merge')\nconst commonConfiguration = require('./webpack.common.js')\nconst { CleanWebpackPlugin } = require('clean-webpack-plugin')\n\nmodule.exports = merge(\n    commonConfiguration,\n    {\n        mode: 'production',\n        plugins:\n        [\n            new CleanWebpackPlugin()\n        ]\n    }\n)\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"repository\": \"#\",\n  \"license\": \"UNLICENSED\",\n  \"scripts\": {\n    \"build\": \"webpack --config ./bundler/webpack.prod.js\",\n    \"dev\": \"webpack serve --config ./bundler/webpack.dev.js\"\n  },\n  \"dependencies\": {\n    \"@babel/core\": \"^7.14.6\",\n    \"@babel/preset-env\": \"^7.14.7\",\n    \"babel-loader\": \"^8.2.2\",\n    \"clean-webpack-plugin\": \"^3.0.0\",\n    \"copy-webpack-plugin\": \"^9.0.1\",\n    \"css-loader\": \"^5.2.6\",\n    \"file-loader\": \"^6.2.0\",\n    \"glslify-loader\": \"^2.0.0\",\n    \"html-loader\": \"^2.1.2\",\n    \"html-webpack-plugin\": \"^5.3.2\",\n    \"mini-css-extract-plugin\": \"^2.1.0\",\n    \"portfinder-sync\": \"0.0.2\",\n    \"raw-loader\": \"^4.0.2\",\n    \"style-loader\": \"^3.0.0\",\n    \"three\": \"^0.130.1\",\n    \"webpack\": \"^5.42.1\",\n    \"webpack-cli\": \"^4.7.2\",\n    \"webpack-dev-server\": \"^3.11.2\",\n    \"webpack-merge\": \"^5.8.0\"\n  }\n}\n"
  },
  {
    "path": "readme.md",
    "content": "# Three.js - Template - Simple\n\n## Setup\nDownload [Node.js](https://nodejs.org/en/download/).\nRun this followed commands:\n\n``` bash\n# Install dependencies (only the first time)\nnpm install\n\n# Run the local server at localhost:8080\nnpm run dev\n\n# Build for production in the dist/ directory\nnpm run build\n```\n"
  },
  {
    "path": "src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Three.js - Template - Simple</title>\n</head>\n<body>\n    <canvas class=\"webgl\"></canvas>\n</body>\n</html>"
  },
  {
    "path": "src/script.js",
    "content": "import './style.css'\nimport * as THREE from 'three'\nimport { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'\n\n/**\n * Base\n */\n// Canvas\nconst canvas = document.querySelector('canvas.webgl')\n\n// Scene\nconst scene = new THREE.Scene()\n\n/**\n * Sizes\n */\nconst sizes = {\n    width: window.innerWidth,\n    height: window.innerHeight\n}\n\nwindow.addEventListener('resize', () =>\n{\n    // Update sizes\n    sizes.width = window.innerWidth\n    sizes.height = window.innerHeight\n\n    // Update camera\n    camera.aspect = sizes.width / sizes.height\n    camera.updateProjectionMatrix()\n\n    // Update renderer\n    renderer.setSize(sizes.width, sizes.height)\n    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))\n})\n\n/**\n * Camera\n */\n// Base camera\nconst camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)\ncamera.position.x = 1\ncamera.position.y = 1\ncamera.position.z = 1\nscene.add(camera)\n\n// Controls\nconst controls = new OrbitControls(camera, canvas)\ncontrols.enableDamping = true\n\n/**\n * Cube\n */\nconst cube = new THREE.Mesh(\n    new THREE.BoxGeometry(1, 1, 1),\n    new THREE.MeshBasicMaterial({ color: 0xff0000 })\n)\nscene.add(cube)\n\n/**\n * Renderer\n */\nconst renderer = new THREE.WebGLRenderer({\n    canvas: canvas,\n    antialias: true,\n})\nrenderer.setSize(sizes.width, sizes.height)\nrenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))\n\n/**\n * Animate\n */\nconst clock = new THREE.Clock()\nlet lastElapsedTime = 0\n\nconst tick = () =>\n{\n    const elapsedTime = clock.getElapsedTime()\n    const deltaTime = elapsedTime - lastElapsedTime\n    lastElapsedTime = elapsedTime\n\n    // Update controls\n    controls.update()\n\n    // Render\n    renderer.render(scene, camera)\n\n    // Call tick again on the next frame\n    window.requestAnimationFrame(tick)\n}\n\ntick()"
  },
  {
    "path": "src/style.css",
    "content": "*\n{\n    margin: 0;\n    padding: 0;\n}\n\nhtml,\nbody\n{\n    overflow: hidden;\n}\n\n.webgl\n{\n    position: fixed;\n    top: 0;\n    left: 0;\n    outline: none;\n}\n"
  },
  {
    "path": "static/.gitkeep",
    "content": ""
  }
]