diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2a66d0d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.vscode
+node_modules/
+package-lock.json
+dist/
+.DS_Store
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..4f437f7
--- /dev/null
+++ b/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "portfolio",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "start": "node ./server.js",
+ "build": "NODE_ENV=production webpack --config webpack.prod.js",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "devDependencies": {
+ "@babel/core": "^7.17.5",
+ "@babel/preset-env": "^7.16.11",
+ "@babel/preset-react": "^7.16.7",
+ "babel-loader": "^8.2.3",
+ "clean-webpack-plugin": "^4.0.0",
+ "css-minimizer-webpack-plugin": "^3.4.1",
+ "express": "^4.17.3",
+ "html-critical-webpack-plugin": "^2.1.0",
+ "html-loader": "^3.1.0",
+ "html-webpack-plugin": "^5.5.0",
+ "image-minimizer-webpack-plugin": "^3.2.3",
+ "mini-css-extract-plugin": "^2.6.0",
+ "sass": "^1.49.9",
+ "sass-loader": "^12.6.0",
+ "style-loader": "^3.3.1",
+ "webpack": "^5.70.0",
+ "webpack-cli": "^4.9.2",
+ "webpack-dev-middleware": "^5.3.1",
+ "webpack-hot-middleware": "^2.25.1",
+ "webpack-merge": "^5.8.0"
+ },
+ "dependencies": {
+ "@react-three/fiber": "^7.0.26",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2",
+ "three": "^0.138.3"
+ }
+}
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..5831149
--- /dev/null
+++ b/server.js
@@ -0,0 +1,32 @@
+const path = require( 'path' );
+const express = require( 'express' );
+const webpack = require( 'webpack' );
+const webpackDevMiddleware = require( 'webpack-dev-middleware' );
+const webpackHotMiddleware = require( 'webpack-hot-middleware' );
+
+const app = express();
+const config = require('./webpack.dev.js');
+const compiler = webpack(config);
+
+// Tell express to use the webpack-dev-middleware and use the webpack.config.js
+// configuration file as a base.
+app.use(
+ webpackDevMiddleware(compiler, {
+ publicPath: config.output.publicPath,
+ writeToDisk: true,
+ })
+);
+
+app.use( webpackHotMiddleware(compiler, {
+ noInfo: true,
+ quiet: true
+}) );
+
+app.get( '/*', function(req, res) {
+ res.sendFile( 'index.html', {root: path.join( __dirname, './dist/')});
+} );
+
+// Serve the files on port 3000.
+app.listen(3000, function () {
+ console.log('Example app listening on port 3000!\n');
+});
diff --git a/src/components/App.jsx b/src/components/App.jsx
new file mode 100644
index 0000000..c5d2f6b
--- /dev/null
+++ b/src/components/App.jsx
@@ -0,0 +1,39 @@
+import '../styles/styles.scss';
+import { useRef, useState } from 'react';
+import { Canvas, useFrame } from '@react-three/fiber';
+
+const Box = (props) => {
+ // This reference gives us direct access to the THREE.Mesh object
+ const ref = useRef()
+ // Hold state for hovered and clicked events
+ const [hovered, hover] = useState(false)
+ const [clicked, click] = useState(false)
+ // Subscribe this component to the render-loop, rotate the mesh every frame
+ useFrame((state, delta) => (ref.current.rotation.x += 0.01))
+ // Return the view, these are regular Threejs elements expressed in JSX
+ return (
+ click(!clicked)}
+ onPointerOver={(event) => hover(true)}
+ onPointerOut={(event) => hover(false)}>
+
+
+
+ )
+};
+
+const App = () => {
+ return (
+
+ );
+};
+
+export default App;
\ No newline at end of file
diff --git a/src/scripts/app.js b/src/scripts/app.js
new file mode 100644
index 0000000..be89ccc
--- /dev/null
+++ b/src/scripts/app.js
@@ -0,0 +1,10 @@
+import { render } from 'react-dom';
+import App from '../components/App';
+
+render(
+ ,
+ document.getElementById("root") );
+
+if (module['hot']) {
+ module['hot'].accept();
+}
diff --git a/src/styles/styles.scss b/src/styles/styles.scss
new file mode 100644
index 0000000..69c38a8
--- /dev/null
+++ b/src/styles/styles.scss
@@ -0,0 +1,3 @@
+main {
+ background-color: black;
+}
\ No newline at end of file
diff --git a/src/views/index.html b/src/views/index.html
new file mode 100644
index 0000000..1fa7e21
--- /dev/null
+++ b/src/views/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
\ No newline at end of file
diff --git a/webpack.common.js b/webpack.common.js
new file mode 100644
index 0000000..88b8295
--- /dev/null
+++ b/webpack.common.js
@@ -0,0 +1,125 @@
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
+const path = require('path');
+
+// let htmlPageNames = [];
+// let multipleHtmlPlugins = htmlPageNames.map(name => {
+// return new HtmlWebpackPlugin({
+// template: `./src/views/${name}.html`, // relative path to the HTML files
+// filename: `${name}.html`, // output HTML files
+// chunks: [`${name}`] // respective JS files
+// })
+// });
+
+module.exports = {
+ resolve: {
+ extensions: ['.js', '.jsx'],
+ },
+ module: {
+ rules: [
+ // HTML
+ {
+ test: /\.html$/,
+ use: {
+ loader: 'html-loader',
+ },
+ },
+
+ // JS
+ {
+ test: /\.jsx?$/i,
+ exclude: /node_modules/,
+ include: path.resolve(__dirname, 'src'),
+ use: {
+ loader: 'babel-loader',
+ options: {
+ presets: [
+ '@babel/preset-env',
+ ['@babel/preset-react', { runtime: 'automatic' }],
+ ],
+ },
+ },
+ },
+
+ // CSS
+ {
+ test: /\.s[ac]ss$/i,
+ use: [
+ MiniCSSExtractPlugin.loader,
+ 'css-loader',
+ 'postcss-loader',
+ 'resolve-url-loader',
+ 'sass-loader',
+ ],
+ },
+
+ // Images
+
+ {
+ test: /\.(png|svg|jpg|jpeg|gif)$/i,
+ type: 'asset',
+ },
+
+ // {
+ // test: /\.(jpg|png|gif|svg)$/,
+ // use: {
+ // loader: 'url-loader'
+ // }
+ // },
+
+ // {
+ // test: /\.(jpg|png|gif|svg)$/,
+ // use:
+ // [
+ // {
+ // loader: 'file-loader',
+ // options:
+ // {
+ // outputPath: './assets/images/'
+ // }
+ // }
+ // ]
+ // },
+
+ // Fonts
+
+ {
+ test: /\.(woff|woff2|eot|ttf|otf)$/i,
+ type: 'asset/resource',
+ },
+
+ // {
+ // test: /\.(ttf|eot|woff|woff2)$/,
+ // use:
+ // [
+ // {
+ // loader: 'file-loader',
+ // options:
+ // {
+ // outputPath: './assets/fonts/'
+ // }
+ // }
+ // ]
+ // }
+ ],
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: path.resolve(__dirname, './src/views/index.html'),
+ inject: 'body',
+ }),
+ ],
+ optimization: {
+ moduleIds: 'deterministic',
+ runtimeChunk: 'single',
+ splitChunks: {
+ cacheGroups: {
+ vendor: {
+ test: /[\\/]node_modules[\\/]/,
+ name: 'vendors',
+ chunks: 'all',
+ },
+ },
+ },
+ },
+};
diff --git a/webpack.dev.js b/webpack.dev.js
new file mode 100644
index 0000000..14b674a
--- /dev/null
+++ b/webpack.dev.js
@@ -0,0 +1,60 @@
+const { merge } = require('webpack-merge');
+const webpack = require( 'webpack' );
+const commonConfiguration = require('./webpack.common.js');
+const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
+const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
+const path = require('path');
+
+const hotMiddlewareScript =
+ 'webpack-hot-middleware/client?reload=true';
+
+module.exports = merge(commonConfiguration, {
+ mode: 'development',
+ entry: {
+ portfolio: [hotMiddlewareScript, './src/scripts/app.js'],
+ },
+ output: {
+ filename: 'js/[name].[contenthash].js',
+ path: path.resolve(__dirname, './dist'),
+ publicPath: '/',
+ clean: true,
+ // hotUpdateChunkFilename: 'hot/hot-update.js',
+ // hotUpdateMainFilename: 'hot/hot-update.json'
+ },
+ devtool: 'source-map',
+ plugins: [
+ new webpack.HotModuleReplacementPlugin(),
+ new MiniCSSExtractPlugin({
+ filename: 'css/[name].[contenthash].css',
+ }),
+ // new ImageMinimizerPlugin({
+ // minimizerOptions: {
+ // // Lossless optimization with custom option
+ // // Feel free to experiment with options for better result for you
+ // plugins: [
+ // ['gifsicle', { interlaced: true }],
+ // ['jpegtran', { progressive: true }],
+ // ['optipng', { optimizationLevel: 5 }],
+ // // Svgo configuration here https://github.com/svg/svgo#configuration
+ // [
+ // 'svgo',
+ // {
+ // plugins: [
+ // {
+ // name: 'removeViewBox',
+ // active: false,
+ // },
+ // {
+ // name: 'addAttributesToSVGElement',
+ // params: {
+ // attributes: [{ xmlns: 'http://www.w3.org/2000/svg' }],
+ // },
+ // },
+ // ],
+ // },
+ // ],
+ // ],
+ // },
+ // }),
+ ]
+});
diff --git a/webpack.prod.js b/webpack.prod.js
new file mode 100644
index 0000000..3e99312
--- /dev/null
+++ b/webpack.prod.js
@@ -0,0 +1,78 @@
+const { merge } = require('webpack-merge');
+const commonConfiguration = require('./webpack.common.js');
+const { CleanWebpackPlugin } = require('clean-webpack-plugin');
+const path = require('path');
+const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
+const HtmlCriticalPlugin = require('html-critical-webpack-plugin');
+const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
+const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
+
+module.exports = merge(commonConfiguration, {
+ mode: 'production',
+ entry: {
+ popshop: path.resolve(__dirname, './src/scripts/app.js'),
+ },
+ devtool: 'source-map',
+ output: {
+ filename: 'js/[name].[contenthash].js',
+ path: path.resolve(__dirname, './dist'),
+ clean: true,
+ assetModuleFilename: 'assets/images/[name][ext][query]',
+ },
+ plugins: [
+ new CleanWebpackPlugin(),
+ new MiniCSSExtractPlugin({
+ filename: 'css/[name].[contenthash].css',
+ }),
+ // new ImageMinimizerPlugin({
+ // minimizerOptions: {
+ // // Lossless optimization with custom option
+ // // Feel free to experiment with options for better result for you
+ // plugins: [
+ // ['gifsicle', { interlaced: true }],
+ // ['jpegtran', { progressive: true }],
+ // ['optipng', { optimizationLevel: 5 }],
+ // // Svgo configuration here https://github.com/svg/svgo#configuration
+ // [
+ // 'svgo',
+ // {
+ // plugins: [
+ // {
+ // name: 'removeViewBox',
+ // active: false,
+ // },
+ // {
+ // name: 'addAttributesToSVGElement',
+ // params: {
+ // attributes: [{ xmlns: 'http://www.w3.org/2000/svg' }],
+ // },
+ // },
+ // ],
+ // },
+ // ],
+ // ],
+ // },
+ // }),
+ new HtmlCriticalPlugin({
+ base: path.join(path.resolve(__dirname), 'dist/'),
+ src: 'index.html',
+ dest: 'index.html',
+ inline: true,
+ minify: true,
+ extract: true,
+ width: 375,
+ height: 565,
+ penthouse: {
+ blockJSRequests: false,
+ },
+ }),
+ ],
+ optimization: {
+ minimize: true,
+ minimizer: [
+ new CssMinimizerPlugin({
+ parallel: true,
+ }),
+ ],
+ },
+});