New repo who dis
This commit is contained in:
commit
21dbefab2d
10 changed files with 1291 additions and 0 deletions
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
/elm-stuff/
|
||||||
|
/downloads/
|
||||||
|
/guidelines/
|
||||||
|
assets/images/420/
|
||||||
|
assets/images/full/
|
||||||
|
assets/other/
|
||||||
|
assets/javascripts/main.js
|
18
add-iamge.sh
Executable file
18
add-iamge.sh
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
IMG=$1
|
||||||
|
|
||||||
|
if ! [ -f $IMG ]; then
|
||||||
|
echo "Please specify a path to an image"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
PATHFULL=assets/images/full
|
||||||
|
PATH420=assets/images/420
|
||||||
|
|
||||||
|
mkdir -p $PATHFULL $PATH420
|
||||||
|
|
||||||
|
file=$(basename $IMG)
|
||||||
|
|
||||||
|
convert $IMG -resize 420x420 "${PATH420}/${file%.*}.png"
|
||||||
|
convert $IMG "${PATHFULL}/${file%.*}.png"
|
BIN
assets/images/favicon/favicon.png
Normal file
BIN
assets/images/favicon/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
2
assets/javascripts/detect_mobile.js
Normal file
2
assets/javascripts/detect_mobile.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
function isMobile(){return (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))){return true;}else{return false;}})(navigator.userAgent||navigator.vendor||window.opera);}
|
||||||
|
|
402
assets/styles/styles.css
Normal file
402
assets/styles/styles.css
Normal file
|
@ -0,0 +1,402 @@
|
||||||
|
body {
|
||||||
|
background-color: #ffffff;
|
||||||
|
margin: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav .dropdown {
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
color: #999999;
|
||||||
|
background-color: #292929;
|
||||||
|
border-color: #101010;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-header {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-header {
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-header .show-nav {
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-header .show-nav.active {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #2266ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-header .show-nav p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carat {
|
||||||
|
display: inline-block;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
margin-left: 2px;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-top: 4px dashed;
|
||||||
|
border-top-color: currentcolor;
|
||||||
|
border-top: 4px solid;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
background-color: #292929;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-nav {
|
||||||
|
margin: 0 auto;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-wrapper {
|
||||||
|
background-color: #292929;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-wrapper ul {
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-wrapper > ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-nav-wrapper > ul {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-header .show-nav,
|
||||||
|
.nav-wrapper > ul > li {
|
||||||
|
-webkit-touch-callout: none; /* iOS Safari */
|
||||||
|
-webkit-user-select: none; /* Safari */
|
||||||
|
-khtml-user-select: none; /* Konqueror HTML */
|
||||||
|
-moz-user-select: none; /* Firefox */
|
||||||
|
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||||
|
user-select: none; /* Non-prefixed version, currently
|
||||||
|
supported by Chrome and Opera */
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-nav-wrapper > ul > li {
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-wrapper > ul > li:hover,
|
||||||
|
.nav-wrapper > ul > li .dropdown-active {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #2266ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-nav-wrapper > ul > li:hover,
|
||||||
|
.desktop-nav-wrapper > ul > li .dropdown-active {
|
||||||
|
border-color: #2266ff;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-wrapper > ul > li .button-wrapper {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-nav-wrapper > ul > li .button-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-nav-wrapper > ul > li .button-wrapper p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav-wrapper > ul > li .button-wrapper p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 1em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-wrapper > ul > li .nav-link {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav .nav-item > div {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-nav-wrapper > ul > li.home-item .button-wrapper p {
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav-wrapper > ul > li.home-item .button-wrapper p {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-wrapper > ul > li.nav-item .button-wrapper p {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-nav-wrapper > ul > li .dropdown {
|
||||||
|
color: #292929;
|
||||||
|
background-color: #ffffff;
|
||||||
|
text-align: left;
|
||||||
|
min-width: 10em;
|
||||||
|
border-bottom: 1px solid #999;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav-wrapper > ul > li .dropdown {
|
||||||
|
color: #cccccc;
|
||||||
|
background-color: #404040;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav-wrapper > ul > li .dropdown-active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-nav-wrapper > ul > li .dropdown,
|
||||||
|
.desktop-nav-wrapper > ul > li ul.dropdown > li.sub-item:last-child p {
|
||||||
|
border-bottom-left-radius: 0.4em;
|
||||||
|
border-bottom-right-radius: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-nav-wrapper > ul > li .dropdown li.sub-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-wrapper > ul > li .dropdown li.sub-item p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav-wrapper > ul > li .dropdown li.sub-item p {
|
||||||
|
padding: 0.8em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop-nav-wrapper > ul > li .dropdown li.sub-item p {
|
||||||
|
padding: 0.4em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-wrapper > ul > li .dropdown li.sub-item p:hover {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #2266ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome {
|
||||||
|
background-color: #444444;
|
||||||
|
background-image: url('/assets/images/full/pexels-photo-downscale.png');
|
||||||
|
background-attachment: local;
|
||||||
|
background-size: cover;
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
height: 100vh;
|
||||||
|
max-height: 500px;
|
||||||
|
margin-top: 51px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome .dimmer {
|
||||||
|
opacity: 0.7;
|
||||||
|
background-color: #000000;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome .centered {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome .content {
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome .content h1 {
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome .content p {
|
||||||
|
padding: 0.5em 0;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome .content h1,
|
||||||
|
.welcome .content p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery {
|
||||||
|
padding: 1em;
|
||||||
|
background-color: #efefef;
|
||||||
|
border-bottom: 1px solid #dbdbdb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery .gallery-wrapper {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery .gallery-info {
|
||||||
|
margin: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery .gallery-columns {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery .gallery-column {
|
||||||
|
margin: 0 auto;
|
||||||
|
flex: 1;
|
||||||
|
max-width: 465px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery .gallery-image {
|
||||||
|
margin: 1em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery .gallery-image-wrapper {
|
||||||
|
padding: 1em;
|
||||||
|
background-color: #ffffff;
|
||||||
|
box-shadow: 0px 2px 3px #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery .gallery-image img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #00b0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
color: #5555f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:active {
|
||||||
|
color: #00b0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .footer-wrapper {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .footer-column {
|
||||||
|
margin: 0 auto;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .footer-column-wrapper {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 632px) {
|
||||||
|
.desktop-nav {
|
||||||
|
width: 900px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery .gallery-wrapper {
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .footer-wrapper {
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .footer-column {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 932px) {
|
||||||
|
.desktop-nav {
|
||||||
|
width: 900px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome .centered {
|
||||||
|
width: 900px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery .gallery-wrapper {
|
||||||
|
width: 900px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .footer-wrapper {
|
||||||
|
width: 900px
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .footer-column {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.desktop-nav {
|
||||||
|
width: 1170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome .centered {
|
||||||
|
width: 1170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery .gallery-wrapper {
|
||||||
|
width: 1170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .footer-wrapper {
|
||||||
|
width: 1170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .footer-column {
|
||||||
|
width: 390px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
28
elm.json
Normal file
28
elm.json
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"type": "application",
|
||||||
|
"source-directories": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"elm-version": "0.19.0",
|
||||||
|
"dependencies": {
|
||||||
|
"direct": {
|
||||||
|
"elm/browser": "1.0.1",
|
||||||
|
"elm/core": "1.0.2",
|
||||||
|
"elm/html": "1.0.0",
|
||||||
|
"hercules-ci/elm-dropdown": "1.0.1"
|
||||||
|
},
|
||||||
|
"indirect": {
|
||||||
|
"elm/json": "1.1.3",
|
||||||
|
"elm/parser": "1.1.0",
|
||||||
|
"elm/time": "1.0.0",
|
||||||
|
"elm/url": "1.0.0",
|
||||||
|
"elm/virtual-dom": "1.0.2",
|
||||||
|
"elm-community/json-extra": "4.2.0",
|
||||||
|
"rtfeldman/elm-iso8601-date-strings": "1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test-dependencies": {
|
||||||
|
"direct": {},
|
||||||
|
"indirect": {}
|
||||||
|
}
|
||||||
|
}
|
25
index.html
Normal file
25
index.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Werefox Software</title>
|
||||||
|
<link rel="shortcut icon" href="/assets/images/favicon/favicon.png">
|
||||||
|
<link href="/assets/styles/styles.css" rel="stylesheet" type="text/css">
|
||||||
|
<script src="/assets/javascripts/detect_mobile.js"></script>
|
||||||
|
<script src="/assets/javascripts/main.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="elm-app">
|
||||||
|
<h1>Please enable javascript to view this website</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var node = document.getElementById("elm-app");
|
||||||
|
var app = Elm.Main.init({
|
||||||
|
node: node,
|
||||||
|
flags: { mobile: isMobile() }
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
index.php
Normal file
1
index.php
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?php header( 'Location: /index.html' ) ; ?>
|
3
make.sh
Executable file
3
make.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
elm make --output="assets/javascripts/main.js" src/Main.elm
|
805
src/Main.elm
Normal file
805
src/Main.elm
Normal file
|
@ -0,0 +1,805 @@
|
||||||
|
module Main exposing (main)
|
||||||
|
|
||||||
|
{-| This is the main module of the application.
|
||||||
|
|
||||||
|
|
||||||
|
# main function
|
||||||
|
|
||||||
|
@docs main
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
import Html exposing (Attribute, Html, a, article, button, div, footer, h1, h2, h3, h4, header, img, li, nav, p, section, span, text, ul)
|
||||||
|
import Html.Attributes exposing (class, href, rel, src, title)
|
||||||
|
import Html.Events exposing (onClick)
|
||||||
|
import Array exposing (Array)
|
||||||
|
import Dropdown exposing (ToggleEvent(..), drawer, dropdown, toggle)
|
||||||
|
import Browser.Events exposing (onResize)
|
||||||
|
import Browser.Dom exposing (getViewport)
|
||||||
|
import Browser exposing (document)
|
||||||
|
import Task exposing (perform)
|
||||||
|
|
||||||
|
|
||||||
|
-- MAIN
|
||||||
|
|
||||||
|
|
||||||
|
{-| The main function of the application
|
||||||
|
-}
|
||||||
|
main : Program Flags Model Msg
|
||||||
|
main =
|
||||||
|
document
|
||||||
|
{ init = init
|
||||||
|
, view = (\model -> { title = "Your Mom", body = view model })
|
||||||
|
, update = update
|
||||||
|
, subscriptions = subscriptions
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- MODEL
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ flags : Flags
|
||||||
|
, nav : Navigation
|
||||||
|
, welcome : WelcomeBanner
|
||||||
|
, gallery : Gallery
|
||||||
|
, footer : List FooterSection
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Navigation =
|
||||||
|
{ smallNav : Bool
|
||||||
|
, showNav : Bool
|
||||||
|
, navText : String
|
||||||
|
, items : Array NavItem
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type NavItem
|
||||||
|
= HomeLink String String
|
||||||
|
| InitDropdown String (Array NavItem)
|
||||||
|
| Dropdown String DropdownConfig (Array NavItem)
|
||||||
|
| NavLink String String
|
||||||
|
|
||||||
|
|
||||||
|
type alias DropdownConfig =
|
||||||
|
{ name : String
|
||||||
|
, event : ToggleEvent
|
||||||
|
, attribute : Attribute Msg
|
||||||
|
, state : Dropdown.State
|
||||||
|
, message : Bool -> Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias WelcomeBanner =
|
||||||
|
{ title : String
|
||||||
|
, description : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Gallery =
|
||||||
|
{ title : String
|
||||||
|
, description : String
|
||||||
|
, url : String
|
||||||
|
, urltext : String
|
||||||
|
, columns : Int
|
||||||
|
, images : List GalleryItem
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type GalleryItem
|
||||||
|
= Image GalleryImage
|
||||||
|
| Project GalleryProject
|
||||||
|
| ProjectLink GalleryLink
|
||||||
|
|
||||||
|
|
||||||
|
type alias GalleryImage =
|
||||||
|
{ url : String
|
||||||
|
, mouseoverText : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias GalleryProject =
|
||||||
|
{ url : String
|
||||||
|
, mouseoverText : String
|
||||||
|
, title : String
|
||||||
|
, description : List String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias GalleryLink =
|
||||||
|
{ url : String
|
||||||
|
, mouseoverText : String
|
||||||
|
, title : String
|
||||||
|
, description : List String
|
||||||
|
, links : List ( String, String )
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias FooterSection =
|
||||||
|
{ title : String
|
||||||
|
, description : String
|
||||||
|
, link : String
|
||||||
|
, url : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Flags =
|
||||||
|
{ mobile : Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- INIT
|
||||||
|
|
||||||
|
|
||||||
|
init : Flags -> ( Model, Cmd Msg )
|
||||||
|
init flags =
|
||||||
|
( { flags = flags
|
||||||
|
, nav =
|
||||||
|
{ showNav = False
|
||||||
|
, smallNav = False
|
||||||
|
, navText = "Navigation"
|
||||||
|
, items =
|
||||||
|
initDropdowns
|
||||||
|
[ HomeLink "Home" "/"
|
||||||
|
, InitDropdown
|
||||||
|
"Social Media"
|
||||||
|
(initDropdowns
|
||||||
|
[ NavLink "Facebook" "https://facebook.com/shadow8t4"
|
||||||
|
, NavLink "Twitter" "https://twitter.com/Shadow8t4"
|
||||||
|
, NavLink "Google+" "https://plus.google.com/u/2/118253409016956205819"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
, InitDropdown
|
||||||
|
"Games"
|
||||||
|
(initDropdowns
|
||||||
|
[ NavLink "Angels and Demons" "https://gitea.werefoxsoftware.com/shadow8t4/angels-and-demons"
|
||||||
|
, NavLink "Monster Chase" "https://gitea.werefoxsoftware.com/shadow8t4/MochaPine64Backup"
|
||||||
|
, NavLink "Monster Chase Server" "https://gitea.werefoxsoftware.com/shadow8t4/MochaServerPine64Backup"
|
||||||
|
, NavLink "Revival Survival" "/revival-survival"
|
||||||
|
, NavLink "Project Undercover" "/project-undercover"
|
||||||
|
, NavLink "Project Undercover Git Repo" "https://gitea.werefoxsoftware.com/shadow8t4/Project-Undercover"
|
||||||
|
, NavLink "So Bow-y Cute" "https://double-darling-duo-deluxe.itch.io/so-bow-y-cute"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
, InitDropdown
|
||||||
|
"Other Projects"
|
||||||
|
(initDropdowns
|
||||||
|
[ NavLink "Re-Procedural City" "https://gitea.werefoxsoftware.com/shadow8t4/Re-ProceduralCity"
|
||||||
|
, NavLink "Procedural City" "https://gitea.werefoxsoftware.com/shadow8t4/ProceduralCity"
|
||||||
|
, NavLink "PSVR Git Repo" "https://gitea.werefoxsoftware.com/shadow8t4/Public-Speaking-VR"
|
||||||
|
, NavLink "PSVR Research Paper" "/assets/other/Public_Speaking_in_VR_Research_Paper.pdf"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
, welcome = welcomeBanner "Werefox Software" "A self-hosted portfolio site for and by Alex Huddleston"
|
||||||
|
, gallery =
|
||||||
|
initGallery
|
||||||
|
"Portfolio Gallery"
|
||||||
|
"A few projects I've worked on. The source code for many of these can be viewed on my self-hosted git service (hosted through Gitea)."
|
||||||
|
"https://gitea.werefoxsoftware.com"
|
||||||
|
"Git subdomain"
|
||||||
|
[ galleryLink
|
||||||
|
"angels-and-demons.png"
|
||||||
|
"A completed game board."
|
||||||
|
"Angels and Demons"
|
||||||
|
[ "Angels and Demons is a board game concept I created during my time in college, specifically"
|
||||||
|
++ " during my Game History class. I decided on my own time to take up converting it to a"
|
||||||
|
++ " video game. Right now, it's focused for mainly Android and WebGL builds. It's currently"
|
||||||
|
++ " (as of June 2018) in very early development. The basic mechanics and logic work, but "
|
||||||
|
++ " it can only be played locally with very little indication of turns or progress. There"
|
||||||
|
++ " are also no instructions aside from a rough document I made for the class, which has"
|
||||||
|
++ " been converted to the repository's readme."
|
||||||
|
]
|
||||||
|
[ ( "Git Repo"
|
||||||
|
, "https://gitea.werefoxsoftware.com/shadow8t4/angels-and-demons"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
, galleryLink
|
||||||
|
"projectundercover-guard.png"
|
||||||
|
"The guard's view in ProjectUndercover."
|
||||||
|
"ProjectUndercover - guard view"
|
||||||
|
[ "Project Undercover is a game about pretending to be an AI. Or at least -- that's part of it."
|
||||||
|
++ " Players will compete against one another, with one side trying to blend in with a crowd"
|
||||||
|
++ " of non-player characters, and the other side attempting to identify them. It takes place"
|
||||||
|
++ " at a party, and the undercover players are agents trying to infiltrate and complete"
|
||||||
|
++ " several missions before the guard, or overseer, catches them."
|
||||||
|
, "The overseer is limited"
|
||||||
|
++ " by a set of cameras, and slowly receives information over the course of the game to"
|
||||||
|
++ " help identify the agents, thus putting the heat on them."
|
||||||
|
, "This picture is from the perspective of one of the guard's cameras."
|
||||||
|
]
|
||||||
|
[ ( "Git Repo"
|
||||||
|
, "https://gitea.werefoxsoftware.com/shadow8t4/Project-Undercover"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
, galleryLink
|
||||||
|
"revival-survival.png"
|
||||||
|
"A game where you save a soul you accidentally reaped as the grim reaper."
|
||||||
|
"Revival Survival"
|
||||||
|
[ "This was my entry for Chillennium 2017, a game jam held and hosted by students at Texas A&M."
|
||||||
|
++ " I was in a group with 3 other students, and mostly worked on the sound design and"
|
||||||
|
++ " game mechanics from the game. The game was made using Unity."
|
||||||
|
, "In Revival Survival, you play as the Grim Reaper in a 2D platformer that has to return"
|
||||||
|
++ " the soul of someone they accidentally reaped. Throughout the game you are met with"
|
||||||
|
++ " adversaries - hellhounds that want to eat the soul you reaped and paladins who wish to avenge"
|
||||||
|
++ " that soul you reaped."
|
||||||
|
, "You can find a link to play this game in browser on the nav bar at the top of this page, as well"
|
||||||
|
++ " as the link below. I've also provided a link to the itch.io submission page, where you"
|
||||||
|
++ " can download a Windows standalone copy of the game."
|
||||||
|
]
|
||||||
|
[ ( "itch.io Page Link"
|
||||||
|
, "https://d4-team.itch.io/revival-survival"
|
||||||
|
)
|
||||||
|
, ( " / "
|
||||||
|
, "/"
|
||||||
|
)
|
||||||
|
, ( "In-browser Game Link"
|
||||||
|
, "/revival-survival"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
, galleryLink
|
||||||
|
"mocha_bordered.png"
|
||||||
|
"The status screen of the game."
|
||||||
|
"Monster Chase"
|
||||||
|
[ "Monster Chase was a fitness game/app developed during my senior capstone class in college."
|
||||||
|
++ " The development team consisted of me and 4 other computer science majors, and the"
|
||||||
|
++ " objective was to create a fitness game designed to track users' steps through engaging"
|
||||||
|
++ " gameplay of the user being virtually chased by a monster that they could customize and"
|
||||||
|
++ " use to challenge other players online. The game makes use of the FitBit API if a device"
|
||||||
|
++ " and subsequent account are available to help track data and utilizes and separate server"
|
||||||
|
++ " to keep track of points for an online leaderboard functionality. While the finaly build"
|
||||||
|
++ "is still in many cases a prototype compared to what we initially intended to develop, it"
|
||||||
|
++ "is functional and the source code for both the app and server are available in their"
|
||||||
|
++ "subsequent repositories."
|
||||||
|
, "During the development of the game, I worked mainly on the front-end design and functionality"
|
||||||
|
++ " of the game through the Unity editor. Most scripted events and transitions in the front end"
|
||||||
|
++ " were also programmed by me, or I at least had a hand in."
|
||||||
|
]
|
||||||
|
[ ( "Git Repo "
|
||||||
|
, "https://gitea.werefoxsoftware.com/shadow8t4/MochaPine64Backup"
|
||||||
|
)
|
||||||
|
, ( " / "
|
||||||
|
, "/"
|
||||||
|
)
|
||||||
|
, ( "Server Git Repo"
|
||||||
|
, "https://gitea.werefoxsoftware.com/shadow8t4/MochaServerPine64Backup"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
, galleryProject
|
||||||
|
"projectundercover-spy.png"
|
||||||
|
"A spy's view in ProjectUndercover."
|
||||||
|
"Project Undercover - spy view"
|
||||||
|
[ "This is another picture from Project Undercover, this time from the perspective"
|
||||||
|
++ " of a spy. In this picture, the spy is finishing a waving interaction with an AI spy."
|
||||||
|
]
|
||||||
|
, galleryLink
|
||||||
|
"public-speaking-vr.png"
|
||||||
|
"A Case Study on Public Speaking in Virtual Reality."
|
||||||
|
"Public Speaking in VR"
|
||||||
|
[ "In this project, and the resulting research paper, a group I was assigned in and I show the results of"
|
||||||
|
++ " a case study on the effectiveness and realism of using Virtual Reality technology to simulate the"
|
||||||
|
++ " experience of public speaking in an effort to practice one’s speech skills."
|
||||||
|
, "We used a Development"
|
||||||
|
++ " Kit 2 version of the Oculus Rift and the Unity editor to create and run our simulation. In the"
|
||||||
|
++ " simulation, we presented users a large classroom environment with a handful of characters to"
|
||||||
|
++ " present a mock speech in front of. We provided the users with an Xbox game controller to allow"
|
||||||
|
++ " for easier camera movement as well as a way to switch through the given slides in the mock presentation."
|
||||||
|
, "The resulting research paper from the case study and a link to the project's github page can be found"
|
||||||
|
++ " below."
|
||||||
|
]
|
||||||
|
[ ( "Git Repo "
|
||||||
|
, "https://gitea.werefoxsoftware.com/shadow8t4/Public-Speaking-VR"
|
||||||
|
)
|
||||||
|
, ( " / "
|
||||||
|
, "/"
|
||||||
|
)
|
||||||
|
, ( "Research Paper"
|
||||||
|
, "/assets/other/Public_Speaking_in_VR_Research_Paper.pdf"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
, galleryLink
|
||||||
|
"re-procedural-city.png"
|
||||||
|
"An image of the re-implementation's output."
|
||||||
|
"Re:Procedural City"
|
||||||
|
[ "A Rust implementation of the Procedural City project. This was done to gain some insight"
|
||||||
|
++ " into the programming language and to refamiliarize myself with the work itself. In"
|
||||||
|
++ " addition, the project is done through solely open-source libraries as opposed to the"
|
||||||
|
++ " ones used in the original project provided by my professor, meaning that documentation"
|
||||||
|
++ " of the library functions is much more accessible."
|
||||||
|
]
|
||||||
|
[ ( "Git Repo"
|
||||||
|
, "https://gitea.werefoxsoftware.com/shadow8t4/Re-ProceduralCity"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
, galleryLink
|
||||||
|
"procedural-city.png"
|
||||||
|
"A picture of some output from ProceduralCity"
|
||||||
|
"Procedural City"
|
||||||
|
[ "In this project my partner, Jeremy Martin, and I presented a way of procedurally generating a basic city by"
|
||||||
|
++ " creating an .obj mesh file using some given template meshes. The goal was that when given a"
|
||||||
|
++ " user input population density and some template meshes, the program would output"
|
||||||
|
++ " a resulting mesh of a procedurally generated “city” using the template"
|
||||||
|
++ " meshes along with some defined rules. The program takes the input files and duplicates it at"
|
||||||
|
++ " procedurally determined intersections, creating a basic city structure in the"
|
||||||
|
++ " resulting output .obj mesh file."
|
||||||
|
, " Unfortunately, the program was unable to be fully"
|
||||||
|
++ " completed before our deadline, and currently takes one input mesh file and duplicates it"
|
||||||
|
++ " based on user-defined amounts of layers from a central point with user-defined spacing."
|
||||||
|
, "Specific documentation on how to operate the program is detailed on the respective github page."
|
||||||
|
]
|
||||||
|
[ ( "Git Repo"
|
||||||
|
, "https://gitea.werefoxsoftware.com/shadow8t4/ProceduralCity"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
, galleryLink
|
||||||
|
"so-bow-y-cute.png"
|
||||||
|
"A game where you become cute."
|
||||||
|
"So Bow-y Cute"
|
||||||
|
[ "This was my entry for Chillennium 2016, a game jam held and hosted by students at Texas A&M."
|
||||||
|
++ " This was my first time at the game jam and I was randomly patnered with one other person,"
|
||||||
|
++ " who did the art for this game. Everything else was done by me. The game was made using Unity."
|
||||||
|
, "In So Bow-y Cute - When your parent decides to limit you cuteness potential by prohibiting all bows, you must"
|
||||||
|
++ " rebel against their tyranny and become the cutest you possibly can. Be careful not to get"
|
||||||
|
++ " caught! or else you're out of luck."
|
||||||
|
, "You can find a link to the itch.io page below."
|
||||||
|
]
|
||||||
|
[ ( "itch.io Page Link"
|
||||||
|
, "https://double-darling-duo-deluxe.itch.io/so-bow-y-cute"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, footer =
|
||||||
|
[ initFooterSection
|
||||||
|
""
|
||||||
|
""
|
||||||
|
""
|
||||||
|
"/"
|
||||||
|
, initFooterSection
|
||||||
|
""
|
||||||
|
"This website was programmed in elm with the help of my friend Riley."
|
||||||
|
"source code"
|
||||||
|
"https://gitea.werefox.dev/shadow8t4/portfolio-site"
|
||||||
|
, initFooterSection
|
||||||
|
""
|
||||||
|
""
|
||||||
|
""
|
||||||
|
"/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
, Task.perform WindowWidth (getViewport
|
||||||
|
|> Task.map (\viewport -> floor viewport.scene.width))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
smallImagePath : String -> String
|
||||||
|
smallImagePath filename =
|
||||||
|
"/assets/images/420/" ++ filename
|
||||||
|
|
||||||
|
|
||||||
|
fullImagePath : String -> String
|
||||||
|
fullImagePath filename =
|
||||||
|
"/assets/images/full/" ++ filename
|
||||||
|
|
||||||
|
|
||||||
|
defaultDropdownConfig : DropdownConfig
|
||||||
|
defaultDropdownConfig =
|
||||||
|
{ name = "dropdown1"
|
||||||
|
, event = OnClick
|
||||||
|
, attribute = class "dropdown-active"
|
||||||
|
, state = False
|
||||||
|
, message = ToggleDropdown 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
initDropdown : Int -> NavItem -> NavItem
|
||||||
|
initDropdown index item =
|
||||||
|
case item of
|
||||||
|
Dropdown name config items ->
|
||||||
|
Dropdown
|
||||||
|
name
|
||||||
|
{ config
|
||||||
|
| name = "dropdown" ++ (String.fromInt index)
|
||||||
|
, message = ToggleDropdown index
|
||||||
|
}
|
||||||
|
items
|
||||||
|
|
||||||
|
InitDropdown name items ->
|
||||||
|
Dropdown
|
||||||
|
name
|
||||||
|
{ defaultDropdownConfig
|
||||||
|
| name = "dropdown" ++ (String.fromInt index)
|
||||||
|
, message = ToggleDropdown index
|
||||||
|
}
|
||||||
|
items
|
||||||
|
|
||||||
|
other ->
|
||||||
|
other
|
||||||
|
|
||||||
|
|
||||||
|
initDropdowns : List NavItem -> Array NavItem
|
||||||
|
initDropdowns =
|
||||||
|
Array.indexedMap initDropdown << Array.fromList
|
||||||
|
|
||||||
|
|
||||||
|
welcomeBanner : String -> String -> WelcomeBanner
|
||||||
|
welcomeBanner title description =
|
||||||
|
{ title = title, description = description }
|
||||||
|
|
||||||
|
|
||||||
|
galleryImage : String -> String -> GalleryItem
|
||||||
|
galleryImage url text =
|
||||||
|
Image { url = url, mouseoverText = text }
|
||||||
|
|
||||||
|
|
||||||
|
galleryProject : String -> String -> String -> List String -> GalleryItem
|
||||||
|
galleryProject url text title description =
|
||||||
|
Project { url = url, mouseoverText = text, title = title, description = description }
|
||||||
|
|
||||||
|
|
||||||
|
galleryLink : String -> String -> String -> List String -> List ( String, String ) -> GalleryItem
|
||||||
|
galleryLink url text title description links =
|
||||||
|
ProjectLink { url = url, mouseoverText = text, title = title, description = description, links = links }
|
||||||
|
|
||||||
|
|
||||||
|
initGallery : String -> String -> String -> String -> List GalleryItem -> Gallery
|
||||||
|
initGallery title description url urltext images =
|
||||||
|
{ title = title, description = description, url = url, urltext = urltext, columns = 1, images = images }
|
||||||
|
|
||||||
|
|
||||||
|
initFooterSection : String -> String -> String -> String -> FooterSection
|
||||||
|
initFooterSection title description link url =
|
||||||
|
{ title = title, description = description, link = link, url = url }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view : Model -> List (Html Msg)
|
||||||
|
view model =
|
||||||
|
[ headerBar model
|
||||||
|
, welcome model.welcome
|
||||||
|
, viewGallery model.gallery
|
||||||
|
, footerSections model.footer
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
welcome : WelcomeBanner -> Html Msg
|
||||||
|
welcome banner =
|
||||||
|
section [ class "welcome" ]
|
||||||
|
[ div [ class "welcome-wrapper" ]
|
||||||
|
[ div [ class "dimmer" ]
|
||||||
|
[ div [ class "centered" ]
|
||||||
|
[ article [ class "content" ]
|
||||||
|
[ h1 [ class "title" ] [ text banner.title ]
|
||||||
|
, p [ class "description" ] [ text banner.description ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
headerBar : Model -> Html Msg
|
||||||
|
headerBar model =
|
||||||
|
if model.flags.mobile || model.nav.smallNav then
|
||||||
|
mobileHeaderbar model
|
||||||
|
else
|
||||||
|
desktopHeaderBar model
|
||||||
|
|
||||||
|
|
||||||
|
desktopHeaderBar : Model -> Html Msg
|
||||||
|
desktopHeaderBar model =
|
||||||
|
header [ class "desktop-header" ]
|
||||||
|
[ nav [ class "desktop-nav" ]
|
||||||
|
[ div [ class "desktop-nav-wrapper", class "nav-wrapper" ]
|
||||||
|
[ ul [] (Array.map navItem model.nav.items |> Array.toList)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
mobileHeaderbar : Model -> Html Msg
|
||||||
|
mobileHeaderbar model =
|
||||||
|
header [ class "mobile-header" ]
|
||||||
|
[ div
|
||||||
|
([ class "show-nav", onClick (ToggleNav (not model.nav.showNav)) ]
|
||||||
|
++ if model.nav.showNav then
|
||||||
|
[ class "active" ]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
[ p []
|
||||||
|
[ text model.nav.navText
|
||||||
|
, span [ class "carat" ] []
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, nav
|
||||||
|
([ class "mobile-nav" ]
|
||||||
|
++ if model.nav.showNav then
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[ class "hidden" ]
|
||||||
|
)
|
||||||
|
[ div [ class "mobile-nav-wrapper", class "nav-wrapper" ]
|
||||||
|
[ ul [] (Array.map navItem model.nav.items |> Array.toList)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
homeLink : String -> String -> Html Msg
|
||||||
|
homeLink name url =
|
||||||
|
li [ class "home-item" ]
|
||||||
|
[ div []
|
||||||
|
[ a [ class "nav-link", href url ]
|
||||||
|
[ div [ class "button-wrapper" ]
|
||||||
|
[ p [] [ text name ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
navItem : NavItem -> Html Msg
|
||||||
|
navItem item =
|
||||||
|
case item of
|
||||||
|
NavLink name url ->
|
||||||
|
navLink name url
|
||||||
|
|
||||||
|
Dropdown name config items ->
|
||||||
|
navDropdown name config items
|
||||||
|
|
||||||
|
HomeLink name url ->
|
||||||
|
homeLink name url
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
text ""
|
||||||
|
|
||||||
|
|
||||||
|
navLink : String -> String -> Html Msg
|
||||||
|
navLink name url =
|
||||||
|
li [ class "nav-item" ]
|
||||||
|
[ div []
|
||||||
|
[ a [ class "nav-link", href url ]
|
||||||
|
[ div [ class "button-wrapper" ]
|
||||||
|
[ p [] [ text name ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
navDropdown : String -> DropdownConfig -> Array NavItem -> Html Msg
|
||||||
|
navDropdown name config items =
|
||||||
|
li [ class "nav-item" ]
|
||||||
|
[ dropdown div []
|
||||||
|
[
|
||||||
|
(toggle div
|
||||||
|
([ class "button-wrapper" ]
|
||||||
|
++ if config.state then
|
||||||
|
[ class "dropdown-active" ]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
[ p []
|
||||||
|
[ text name
|
||||||
|
, span [ class "carat" ] []
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
, (drawer ul [ class "dropdown" ] (Array.map subItem items |> Array.toList))
|
||||||
|
]
|
||||||
|
config.state
|
||||||
|
(dropdownConfig config)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
subItem : NavItem -> Html Msg
|
||||||
|
subItem item =
|
||||||
|
case item of
|
||||||
|
NavLink name url ->
|
||||||
|
li [ class "sub-item" ]
|
||||||
|
[ a [ class "nav-link", href url ]
|
||||||
|
[ p [] [ text name ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
text ""
|
||||||
|
|
||||||
|
|
||||||
|
dropdownConfig : DropdownConfig -> Dropdown.Config Msg
|
||||||
|
dropdownConfig config =
|
||||||
|
Dropdown.Config
|
||||||
|
config.name
|
||||||
|
config.event
|
||||||
|
config.attribute
|
||||||
|
config.message
|
||||||
|
|
||||||
|
|
||||||
|
viewGallery : Gallery -> Html Msg
|
||||||
|
viewGallery gallery =
|
||||||
|
let
|
||||||
|
arr : Array (List GalleryItem)
|
||||||
|
arr =
|
||||||
|
gallery.images
|
||||||
|
|> List.indexedMap (\index item -> ( index, item ))
|
||||||
|
|> List.foldl
|
||||||
|
(\( index, item ) acc ->
|
||||||
|
if (remainderBy gallery.columns index) < Array.length acc then
|
||||||
|
case
|
||||||
|
acc
|
||||||
|
|> Array.get (remainderBy gallery.columns index)
|
||||||
|
|> Maybe.map ((::) item)
|
||||||
|
of
|
||||||
|
Just column ->
|
||||||
|
Array.set (remainderBy gallery.columns index) column acc
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
acc
|
||||||
|
else
|
||||||
|
Array.push [ item ] acc
|
||||||
|
)
|
||||||
|
Array.empty
|
||||||
|
in
|
||||||
|
div [ class "gallery" ]
|
||||||
|
[ section [ class "gallery-wrapper" ]
|
||||||
|
[ article [ class "gallery-info" ]
|
||||||
|
[ h2 [] [ text gallery.title ]
|
||||||
|
, p [] [ text gallery.description ]
|
||||||
|
, a [ href gallery.url ] [ text gallery.urltext ]
|
||||||
|
]
|
||||||
|
, div [ class "gallery-columns" ]
|
||||||
|
(arr
|
||||||
|
|> Array.map (div [ class "gallery-column" ] << List.map displayImage << List.reverse)
|
||||||
|
|> Array.toList
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
displayImage : GalleryItem -> Html Msg
|
||||||
|
displayImage item =
|
||||||
|
case item of
|
||||||
|
Image image ->
|
||||||
|
article [ class "gallery-image" ]
|
||||||
|
[ div [ class "gallery-image-wrapper" ]
|
||||||
|
[ a [ href (fullImagePath image.url) ]
|
||||||
|
[ img [ src (smallImagePath image.url), title image.mouseoverText ] []
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
Project project ->
|
||||||
|
article [ class "gallery-image" ]
|
||||||
|
[ div [ class "gallery-image-wrapper" ]
|
||||||
|
[ a [ href (fullImagePath project.url) ]
|
||||||
|
[ img [ src (smallImagePath project.url), title project.mouseoverText ] []
|
||||||
|
]
|
||||||
|
, div [ class "gallery-image-info" ]
|
||||||
|
[ h4 [] [ text project.title ]
|
||||||
|
, div [] (List.map (\t -> p [] [ text t ]) project.description)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
ProjectLink projectlink ->
|
||||||
|
article [ class "gallery-image" ]
|
||||||
|
[ div [ class "gallery-image-wrapper" ]
|
||||||
|
[ a [ href (fullImagePath projectlink.url) ]
|
||||||
|
[ img [ src (smallImagePath projectlink.url), title projectlink.mouseoverText ] []
|
||||||
|
]
|
||||||
|
, div [ class "gallery-image-info" ]
|
||||||
|
[ h4 [] [ text projectlink.title ]
|
||||||
|
, div [] (List.map (\t -> p [] [ text t ]) projectlink.description)
|
||||||
|
, p [] (List.map (\( t, url ) -> a [ href url ] [ text t ]) projectlink.links)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
footerSection : FooterSection -> Html Msg
|
||||||
|
footerSection item =
|
||||||
|
div [ class "footer-column" ]
|
||||||
|
[ article [ class "footer-column-wrapper" ]
|
||||||
|
[ h3 [] [ text item.title ]
|
||||||
|
, p [] [ text item.description ]
|
||||||
|
, p []
|
||||||
|
[ a [ href item.url ]
|
||||||
|
[ text item.link ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
footerSections : List FooterSection -> Html Msg
|
||||||
|
footerSections items =
|
||||||
|
footer []
|
||||||
|
[ section [ class "footer-wrapper" ] (List.map footerSection items)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= WindowWidth Int
|
||||||
|
| ToggleDropdown Int Bool
|
||||||
|
| ToggleNav Bool
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
|
update msg model =
|
||||||
|
case msg of
|
||||||
|
WindowWidth x ->
|
||||||
|
( { model
|
||||||
|
| gallery = updateGalleryWidth model.flags.mobile x model.gallery
|
||||||
|
, nav = updateNavWidth x model.nav
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
ToggleDropdown index state ->
|
||||||
|
( { model | nav = updateDropdown index state model.nav }
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
ToggleNav state ->
|
||||||
|
( { model | nav = toggleNav state model.nav }
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
toggleNav : Bool -> Navigation -> Navigation
|
||||||
|
toggleNav state nav =
|
||||||
|
{ nav | showNav = state }
|
||||||
|
|
||||||
|
|
||||||
|
updateGalleryWidth : Bool -> Int -> Gallery -> Gallery
|
||||||
|
updateGalleryWidth mobile width gallery =
|
||||||
|
if mobile then
|
||||||
|
gallery
|
||||||
|
else if width < 632 then
|
||||||
|
{ gallery | columns = 1 }
|
||||||
|
else if width < 932 then
|
||||||
|
{ gallery | columns = 2 }
|
||||||
|
else
|
||||||
|
{ gallery | columns = 3 }
|
||||||
|
|
||||||
|
|
||||||
|
updateNavWidth : Int -> Navigation -> Navigation
|
||||||
|
updateNavWidth width nav =
|
||||||
|
if width < 700 then
|
||||||
|
{ nav | smallNav = True }
|
||||||
|
else
|
||||||
|
{ nav | smallNav = False, showNav = False }
|
||||||
|
|
||||||
|
|
||||||
|
updateDropdown : Int -> Bool -> Navigation -> Navigation
|
||||||
|
updateDropdown index state nav =
|
||||||
|
case Array.get index nav.items of
|
||||||
|
Just dropdown ->
|
||||||
|
case dropdown of
|
||||||
|
Dropdown name config items ->
|
||||||
|
{ nav | items = Array.set index (Dropdown name { config | state = state } items) nav.items }
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nav
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
nav
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- SUBSCRIPTIONS
|
||||||
|
|
||||||
|
|
||||||
|
subscriptions : Model -> Sub Msg
|
||||||
|
subscriptions model =
|
||||||
|
onResize (\width _ -> WindowWidth width)
|
Reference in a new issue