diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7cd54cb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules/.bin/acorn
+node_modules/
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..7cfdc70
--- /dev/null
+++ b/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "uva-preact",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "eslint src && preact test",
+ "start": "if-env NODE_ENV=production && npm run -s serve || npm run -s dev",
+ "build": "preact build",
+ "serve": "preact build && preact serve",
+ "dev": "preact watch"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "eslintConfig": {
+ "extends": "eslint-config-synacor"
+ },
+ "devDependencies": {
+ "eslint": "^4.15.0",
+ "eslint-config-synacor": "^2.0.4",
+ "if-env": "^1.0.0",
+ "node-sass": "^4.7.2",
+ "preact-cli": "^2.1.0",
+ "sass-loader": "^6.0.6"
+ },
+ "dependencies": {
+ "preact": "^8.2.7",
+ "preact-compat": "^3.17.0",
+ "preact-router": "^2.6.0"
+ }
+}
diff --git a/src/assets/favicon.ico b/src/assets/favicon.ico
new file mode 100644
index 0000000..0741914
Binary files /dev/null and b/src/assets/favicon.ico differ
diff --git a/src/assets/icons/android-chrome-192x192.png b/src/assets/icons/android-chrome-192x192.png
new file mode 100644
index 0000000..93ebe2e
Binary files /dev/null and b/src/assets/icons/android-chrome-192x192.png differ
diff --git a/src/assets/icons/android-chrome-512x512.png b/src/assets/icons/android-chrome-512x512.png
new file mode 100644
index 0000000..52d1623
Binary files /dev/null and b/src/assets/icons/android-chrome-512x512.png differ
diff --git a/src/assets/icons/apple-touch-icon.png b/src/assets/icons/apple-touch-icon.png
new file mode 100644
index 0000000..254e4bb
Binary files /dev/null and b/src/assets/icons/apple-touch-icon.png differ
diff --git a/src/assets/icons/favicon-16x16.png b/src/assets/icons/favicon-16x16.png
new file mode 100644
index 0000000..e81177d
Binary files /dev/null and b/src/assets/icons/favicon-16x16.png differ
diff --git a/src/assets/icons/favicon-32x32.png b/src/assets/icons/favicon-32x32.png
new file mode 100644
index 0000000..40e9b5b
Binary files /dev/null and b/src/assets/icons/favicon-32x32.png differ
diff --git a/src/assets/icons/mstile-150x150.png b/src/assets/icons/mstile-150x150.png
new file mode 100644
index 0000000..9cfb889
Binary files /dev/null and b/src/assets/icons/mstile-150x150.png differ
diff --git a/src/components/app.js b/src/components/app.js
new file mode 100644
index 0000000..f3c4a84
--- /dev/null
+++ b/src/components/app.js
@@ -0,0 +1,31 @@
+import { h, Component } from 'preact';
+import { Router } from 'preact-router';
+
+import Header from './header';
+import Home from '../routes/home';
+import Profile from '../routes/profile';
+// import Home from 'async!./home';
+// import Profile from 'async!./profile';
+
+export default class App extends Component {
+ /** Gets fired when the route changes.
+ * @param {Object} event "change" event from [preact-router](http://git.io/preact-router)
+ * @param {string} event.url The newly routed URL
+ */
+ handleRoute = e => {
+ this.currentUrl = e.url;
+ };
+
+ render() {
+ return (
+
+ );
+ }
+}
diff --git a/src/components/header/index.js b/src/components/header/index.js
new file mode 100644
index 0000000..c79e433
--- /dev/null
+++ b/src/components/header/index.js
@@ -0,0 +1,18 @@
+import { h, Component } from 'preact';
+import { Link } from 'preact-router/match';
+import style from './style';
+
+export default class Header extends Component {
+ render() {
+ return (
+
+ );
+ }
+}
diff --git a/src/components/header/style.scss b/src/components/header/style.scss
new file mode 100644
index 0000000..f08fda7
--- /dev/null
+++ b/src/components/header/style.scss
@@ -0,0 +1,48 @@
+.header {
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 56px;
+ padding: 0;
+ background: #673AB7;
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
+ z-index: 50;
+}
+
+.header h1 {
+ float: left;
+ margin: 0;
+ padding: 0 15px;
+ font-size: 24px;
+ line-height: 56px;
+ font-weight: 400;
+ color: #FFF;
+}
+
+.header nav {
+ float: right;
+ font-size: 100%;
+}
+
+.header nav a {
+ display: inline-block;
+ height: 56px;
+ line-height: 56px;
+ padding: 0 15px;
+ min-width: 50px;
+ text-align: center;
+ background: rgba(255,255,255,0);
+ text-decoration: none;
+ color: #FFF;
+ will-change: background-color;
+}
+
+.header nav a:hover,
+.header nav a:active {
+ background: rgba(0,0,0,0.2);
+}
+
+.header nav a.active {
+ background: rgba(0,0,0,0.4);
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..009d937
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,4 @@
+import './style';
+import App from './components/app';
+
+export default App;
diff --git a/src/manifest.json b/src/manifest.json
new file mode 100644
index 0000000..55ca625
--- /dev/null
+++ b/src/manifest.json
@@ -0,0 +1,19 @@
+{
+ "name": "Preact PWA",
+ "short_name": "Preact PWA",
+ "start_url": "/",
+ "display": "standalone",
+ "orientation": "portrait",
+ "background_color": "#fff",
+ "theme_color": "#673ab8",
+ "icons": [{
+ "src": "/assets/icons/android-chrome-192x192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "/assets/icons/android-chrome-512x512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ }]
+}
diff --git a/src/routes/home/index.js b/src/routes/home/index.js
new file mode 100644
index 0000000..009daaf
--- /dev/null
+++ b/src/routes/home/index.js
@@ -0,0 +1,13 @@
+import { h, Component } from 'preact';
+import style from './style';
+
+export default class Home extends Component {
+ render() {
+ return (
+
+
Home
+
This is the Home component.
+
+ );
+ }
+}
diff --git a/src/routes/home/style.scss b/src/routes/home/style.scss
new file mode 100644
index 0000000..f052d25
--- /dev/null
+++ b/src/routes/home/style.scss
@@ -0,0 +1,5 @@
+.home {
+ padding: 56px 20px;
+ min-height: 100%;
+ width: 100%;
+}
diff --git a/src/routes/profile/index.js b/src/routes/profile/index.js
new file mode 100644
index 0000000..164b259
--- /dev/null
+++ b/src/routes/profile/index.js
@@ -0,0 +1,47 @@
+import { h, Component } from 'preact';
+import style from './style';
+
+export default class Profile extends Component {
+ state = {
+ time: Date.now(),
+ count: 10
+ };
+
+ // gets called when this route is navigated to
+ componentDidMount() {
+ // start a timer for the clock:
+ this.timer = setInterval(this.updateTime, 1000);
+ }
+
+ // gets called just before navigating away from the route
+ componentWillUnmount() {
+ clearInterval(this.timer);
+ }
+
+ // update the current time
+ updateTime = () => {
+ this.setState({ time: Date.now() });
+ };
+
+ increment = () => {
+ this.setState({ count: this.state.count+1 });
+ };
+
+ // Note: `user` comes from the URL, courtesy of our router
+ render({ user }, { time, count }) {
+ return (
+
+
Profile: {user}
+
This is the user profile for a user named { user }.
+
+
Current time: {new Date(time).toLocaleString()}
+
+
+
+ {' '}
+ Clicked {count} times.
+
+
+ );
+ }
+}
diff --git a/src/routes/profile/style.scss b/src/routes/profile/style.scss
new file mode 100644
index 0000000..fcb1296
--- /dev/null
+++ b/src/routes/profile/style.scss
@@ -0,0 +1,5 @@
+.profile {
+ padding: 56px 20px;
+ min-height: 100%;
+ width: 100%;
+}
diff --git a/src/style/index.scss b/src/style/index.scss
new file mode 100644
index 0000000..5fe95de
--- /dev/null
+++ b/src/style/index.scss
@@ -0,0 +1,20 @@
+html, body {
+ height: 100%;
+ width: 100%;
+ padding: 0;
+ margin: 0;
+ background: #FAFAFA;
+ font-family: 'Helvetica Neue', arial, sans-serif;
+ font-weight: 400;
+ color: #444;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+#app {
+ height: 100%;
+}