├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── azure.yml │ └── node.js.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── app.js ├── azure-pipelines-2.yml ├── azure-pipelines.yml ├── azuredeploy.json ├── config.js ├── debug-solution.txt ├── node-express-azure.code-workspace ├── package-lock.json ├── package.json ├── prompt.txt ├── public ├── assets │ ├── css │ │ ├── font-awesome.min.css │ │ ├── images │ │ │ └── intro.svg │ │ ├── main.css │ │ └── noscript.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── js │ │ ├── breakpoints.min.js │ │ ├── browser.min.js │ │ ├── jquery.min.js │ │ ├── jquery.scrollex.min.js │ │ ├── jquery.scrolly.min.js │ │ ├── main.js │ │ └── util.js │ └── sass │ │ ├── base │ │ ├── _page.scss │ │ ├── _reset.scss │ │ └── _typography.scss │ │ ├── components │ │ ├── _actions.scss │ │ ├── _box.scss │ │ ├── _button.scss │ │ ├── _contact.scss │ │ ├── _features.scss │ │ ├── _form.scss │ │ ├── _icon.scss │ │ ├── _icons.scss │ │ ├── _image.scss │ │ ├── _list.scss │ │ ├── _menu.scss │ │ ├── _row.scss │ │ ├── _section.scss │ │ ├── _split.scss │ │ ├── _spotlights.scss │ │ ├── _table.scss │ │ └── _wrapper.scss │ │ ├── layout │ │ ├── _footer.scss │ │ ├── _header.scss │ │ ├── _intro.scss │ │ ├── _sidebar.scss │ │ └── _wrapper.scss │ │ ├── libs │ │ ├── _breakpoints.scss │ │ ├── _functions.scss │ │ ├── _html-grid.scss │ │ ├── _mixins.scss │ │ ├── _vars.scss │ │ └── _vendor.scss │ │ ├── main.scss │ │ └── noscript.scss └── images │ ├── pic01.jpg │ ├── pic02.jpg │ ├── pic03.jpg │ ├── pic04.jpg │ ├── pic05.jpg │ └── pic06.jpg ├── routes ├── contact.js ├── index.js └── who.js ├── sqltest.js ├── test ├── contact_test.js ├── index_test.js └── who_test.js └── views ├── contact.handlebars ├── index.handlebars ├── layouts └── main.handlebars └── who.handlebars /.gitattributes: -------------------------------------------------------------------------------- 1 | public/* linguist-vendored 2 | views/* linguist-vendored -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" -------------------------------------------------------------------------------- /.github/workflows/azure.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build and push a node.js application to an Azure Web App when a release is created. 2 | # 3 | # This workflow assumes you have already created the target Azure App Service web app. 4 | # For instructions see https://docs.microsoft.com/azure/app-service/app-service-plan-manage#create-an-app-service-plan 5 | # 6 | # To configure this workflow: 7 | # 8 | # 1. For Linux apps, add an app setting called WEBSITE_WEBDEPLOY_USE_SCM and set it to true in your app **before downloading the file**. 9 | # For more instructions see: https://docs.microsoft.com/azure/app-service/configure-common#configure-app-settings 10 | # 11 | # 2. Set up a secret in your repository named AZURE_WEBAPP_PUBLISH_PROFILE with the value of your Azure publish profile. 12 | # For instructions on obtaining the publish profile see: https://docs.microsoft.com/azure/app-service/deploy-github-actions#configure-the-github-secret 13 | # 14 | # 3. Change the values for the AZURE_WEBAPP_NAME, AZURE_WEBAPP_PACKAGE_PATH and NODE_VERSION environment variables (below). 15 | # 16 | # For more information on GitHub Actions for Azure, refer to https://github.com/Azure/Actions 17 | # For more samples to get started with GitHub Action workflows to deploy to Azure, refer to https://github.com/Azure/actions-workflow-samples 18 | name: azure-deploy 19 | 20 | on: 21 | # release: 22 | # types: [created] 23 | 24 | env: 25 | AZURE_WEBAPP_NAME: twaz104webapp1 # set this to your application's name 26 | AZURE_WEBAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root 27 | NODE_VERSION: '10.x' # set this to the node version to use 28 | 29 | jobs: 30 | build-and-deploy: 31 | name: Build and Deploy 32 | runs-on: ubuntu-latest 33 | environment: production 34 | steps: 35 | - uses: actions/checkout@v2 36 | - name: Use Node.js ${{ env.NODE_VERSION }} 37 | uses: actions/setup-node@v1 38 | with: 39 | node-version: ${{ env.NODE_VERSION }} 40 | - name: npm install, build, and test 41 | run: | 42 | # Build and test the project, then 43 | # deploy to Azure Web App. 44 | npm install 45 | npm run build --if-present 46 | npm run test --if-present 47 | - name: 'Deploy to Azure WebApp' 48 | uses: azure/webapps-deploy@v2 49 | with: 50 | app-name: ${{ env.AZURE_WEBAPP_NAME }} 51 | publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} 52 | package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} 53 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x, 12.x, 14.x, 15.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: npm ci 29 | - run: npm run build --if-present 30 | - run: npm test 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Mac Folder Junk 15 | .DS_Store 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # next.js build output 64 | .next 65 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "appService.deploySubpath": ".", 3 | "appService.defaultWebAppToDeploy": "/subscriptions/2fbf906e-1101-4bc0-b64f-adc44e462fff/resourceGroups/appsvc_linux_centralus_basic/providers/Microsoft.Web/sites/twnodetestapp" 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node & Express Demo App for Azure DevOps 2 | 3 | ## Last edited by Tim Warner 4 | 5 | > Build Your First CI/CD Pipeline using Azure DevOps with this Demo App. 6 | 7 | This is a Node and Express web application used to demonstrate CI/CD with Azure DevOps. You can clone this repo and use it within Azure DevOps to build, test, and release to an Azure App Service web app. 8 | 9 | ## Running and Testing Locally: 10 | 11 | You can use these commands to install, test, and run the app locally. (Not Required) 12 | 13 | ### Install 14 | 15 | ``` 16 | npm install 17 | ``` 18 | 19 | ### Test 20 | 21 | ``` 22 | npm test 23 | ``` 24 | 25 | ![alt text](https://user-images.githubusercontent.com/5126491/51065379-c1743280-15c1-11e9-80fd-6a3d7ab4ac1b.jpg "Unit Test") 26 | 27 | Navigate to the `/test` folder to review the unit tests for this project. These tests will run as part of your Azure DevOps Build pipeline. See `azure-pipelines.yml` in this repo. 28 | 29 | ### Start 30 | 31 | ``` 32 | npm start 33 | ``` 34 | 35 | 36 | ### License 37 | 38 | This project is licensed under the Apache License 2.0 39 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const config = require('./config'); 2 | const express = require('express'); 3 | const path = require('path'); 4 | const { engine } = require('express-handlebars'); 5 | 6 | const index = require('./routes/index'); 7 | const who = require('./routes/who'); 8 | const contact = require('./routes/contact'); 9 | 10 | const app = express(); 11 | 12 | // Set the directory where the view templates are located 13 | app.set('views', path.join(__dirname, 'views')); 14 | 15 | // Initialize and set the view engine to handlebars using express-handlebars 16 | app.engine('handlebars', engine({ defaultLayout: 'main' })); 17 | app.set('view engine', 'handlebars'); 18 | 19 | // Set the port from the configuration 20 | app.set('port', config.port); 21 | 22 | // Middleware to serve static files from the 'public' directory 23 | app.use('/', express.static('public')); 24 | 25 | // Route middleware 26 | app.use('/', index); 27 | app.use('/who', who); 28 | app.use('/contact', contact); 29 | 30 | // Start the server 31 | app.listen(app.get('port'), () => { 32 | console.log(`Server running on port ${app.get('port')}`); 33 | }); -------------------------------------------------------------------------------- /azure-pipelines-2.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | 4 | variables: 5 | azureSubscription: '' 6 | webAppName: '' 7 | environmentName: 'production' 8 | nodeVersion: '14.x' 9 | workingDirectory: '.' # update this to the path to your web app project, if applicable 10 | 11 | stages: 12 | - stage: Build 13 | displayName: Build stage 14 | jobs: 15 | - job: Build 16 | displayName: Build 17 | pool: 18 | vmImage: 'ubuntu-latest' 19 | steps: 20 | - task: NodeTool@0 21 | inputs: 22 | versionSpec: $(nodeVersion) 23 | displayName: 'Install Node.js' 24 | 25 | - script: | 26 | npm install 27 | npm run build --if-present 28 | displayName: 'npm install and build' 29 | workingDirectory: $(workingDirectory) 30 | 31 | - task: ArchiveFiles@2 32 | displayName: 'Archive files' 33 | inputs: 34 | rootFolderOrFile: '$(System.DefaultWorkingDirectory)' 35 | includeRootFolder: false 36 | archiveType: 'zip' 37 | archiveFile: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip 38 | replaceExistingArchive: true 39 | 40 | - upload: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip 41 | artifact: drop 42 | 43 | - stage: Deploy 44 | displayName: Deploy stage 45 | dependsOn: Build 46 | condition: succeeded() 47 | jobs: 48 | - deployment: Deploy 49 | displayName: Deploy 50 | environment: $(environmentName) 51 | pool: 52 | vmImage: 'ubuntu-latest' 53 | strategy: 54 | runOnce: 55 | deploy: 56 | steps: 57 | - task: AzureRmWebAppDeployment@4 58 | inputs: 59 | ConnectionType: 'AzureRM' 60 | azureSubscription: $(azureSubscription) 61 | appType: 'webApp' 62 | WebAppName: $(webAppName) 63 | packageForLinux: '$(Pipeline.Workspace)/drop/$(Build.BuildId).zip' -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | 4 | pool: 5 | vmImage: 'Ubuntu-16.04' 6 | 7 | steps: 8 | - task: NodeTool@0 9 | inputs: 10 | versionSpec: '8.x' 11 | displayName: 'Install Node.js' 12 | 13 | - script: | 14 | npm install 15 | displayName: 'npm install' 16 | 17 | - script: | 18 | npm test 19 | displayName: 'npm test' 20 | 21 | - task: ArchiveFiles@2 22 | displayName: 'Archive files' 23 | inputs: 24 | rootFolderOrFile: '$(System.DefaultWorkingDirectory)' 25 | includeRootFolder: false 26 | 27 | - task: PublishBuildArtifacts@1 28 | displayName: 'Publish artifacts: drop' 29 | 30 | -------------------------------------------------------------------------------- /azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "site_host_name": { 6 | "defaultValue": "", 7 | "type": "String", 8 | "metadata": { 9 | "description": "The host portion for the web app url (must be unique)." 10 | } 11 | }, 12 | "app_svc_plan_name": { 13 | "defaultValue": "", 14 | "type": "String", 15 | "metadata": { 16 | "description": "The name of your app service plan." 17 | } 18 | } 19 | }, 20 | "variables": {}, 21 | "resources": [{ 22 | "type": "Microsoft.Web/serverfarms", 23 | "sku": { 24 | "name": "S1", 25 | "tier": "Standard", 26 | "size": "S1", 27 | "family": "S", 28 | "capacity": 1 29 | }, 30 | "kind": "linux", 31 | "name": "[parameters('app_svc_plan_name')]", 32 | "apiVersion": "2016-09-01", 33 | "location": "[resourceGroup().location]", 34 | "scale": null, 35 | "properties": { 36 | "name": "[parameters('app_svc_plan_name')]", 37 | "workerTierName": null, 38 | "adminSiteName": null, 39 | "hostingEnvironmentProfile": null, 40 | "perSiteScaling": false, 41 | "reserved": true, 42 | "targetWorkerCount": 0, 43 | "targetWorkerSizeId": 0 44 | }, 45 | "dependsOn": [] 46 | }, 47 | { 48 | "type": "Microsoft.Web/sites", 49 | "kind": "app,linux", 50 | "name": "[parameters('site_host_name')]", 51 | "apiVersion": "2016-08-01", 52 | "location": "[resourceGroup().location]", 53 | "scale": null, 54 | "properties": { 55 | "enabled": true, 56 | "hostNameSslStates": [{ 57 | "name": "[concat(parameters('site_host_name'),'.azurewebsites.net')]", 58 | "sslState": "Disabled", 59 | "virtualIP": null, 60 | "thumbprint": null, 61 | "toUpdate": null, 62 | "hostType": "Standard" 63 | }, 64 | { 65 | "name": "[concat(parameters('site_host_name'),'.scm.azurewebsites.net')]", 66 | "sslState": "Disabled", 67 | "virtualIP": null, 68 | "thumbprint": null, 69 | "toUpdate": null, 70 | "hostType": "Repository" 71 | } 72 | ], 73 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('app_svc_plan_name'))]", 74 | "reserved": true, 75 | "siteConfig": null, 76 | "scmSiteAlsoStopped": false, 77 | "hostingEnvironmentProfile": null, 78 | "clientAffinityEnabled": true, 79 | "clientCertEnabled": false, 80 | "hostNamesDisabled": false, 81 | "containerSize": 0, 82 | "dailyMemoryTimeQuota": 0, 83 | "cloningInfo": null, 84 | "httpsOnly": false 85 | }, 86 | "dependsOn": [ 87 | "[resourceId('Microsoft.Web/serverfarms', parameters('app_svc_plan_name'))]" 88 | ] 89 | }, 90 | { 91 | "type": "Microsoft.Web/sites/config", 92 | "name": "[concat(parameters('site_host_name'), '/', 'web')]", 93 | "apiVersion": "2016-08-01", 94 | "location": "[resourceGroup().location]", 95 | "scale": null, 96 | "properties": { 97 | "numberOfWorkers": 1, 98 | "defaultDocuments": [ 99 | "Default.htm", 100 | "Default.html", 101 | "Default.asp", 102 | "index.htm", 103 | "index.html", 104 | "iisstart.htm", 105 | "default.aspx", 106 | "index.php", 107 | "hostingstart.html" 108 | ], 109 | "netFrameworkVersion": "v4.0", 110 | "phpVersion": "", 111 | "pythonVersion": "", 112 | "nodeVersion": "", 113 | "linuxFxVersion": "NODE|8.0", 114 | "windowsFxVersion": null, 115 | "requestTracingEnabled": false, 116 | "remoteDebuggingEnabled": false, 117 | "remoteDebuggingVersion": null, 118 | "httpLoggingEnabled": false, 119 | "logsDirectorySizeLimit": 35, 120 | "detailedErrorLoggingEnabled": false, 121 | "publishingUsername": "[concat('$', parameters('site_host_name'))]", 122 | "publishingPassword": null, 123 | "appSettings": null, 124 | "azureStorageAccounts": {}, 125 | "metadata": null, 126 | "connectionStrings": null, 127 | "machineKey": null, 128 | "handlerMappings": null, 129 | "documentRoot": null, 130 | "scmType": "None", 131 | "use32BitWorkerProcess": true, 132 | "webSocketsEnabled": false, 133 | "alwaysOn": false, 134 | "javaVersion": null, 135 | "javaContainer": null, 136 | "javaContainerVersion": null, 137 | "appCommandLine": "", 138 | "managedPipelineMode": "Integrated", 139 | "virtualApplications": [{ 140 | "virtualPath": "/", 141 | "physicalPath": "site\\wwwroot", 142 | "preloadEnabled": false, 143 | "virtualDirectories": null 144 | }], 145 | "winAuthAdminState": 0, 146 | "winAuthTenantState": 0, 147 | "customAppPoolIdentityAdminState": false, 148 | "customAppPoolIdentityTenantState": false, 149 | "runtimeADUser": null, 150 | "runtimeADUserPassword": null, 151 | "loadBalancing": "LeastRequests", 152 | "routingRules": [], 153 | "experiments": { 154 | "rampUpRules": [] 155 | }, 156 | "limits": null, 157 | "autoHealEnabled": false, 158 | "autoHealRules": null, 159 | "tracingOptions": null, 160 | "vnetName": "", 161 | "siteAuthEnabled": false, 162 | "siteAuthSettings": { 163 | "enabled": null, 164 | "unauthenticatedClientAction": null, 165 | "tokenStoreEnabled": null, 166 | "allowedExternalRedirectUrls": null, 167 | "defaultProvider": null, 168 | "clientId": null, 169 | "clientSecret": null, 170 | "clientSecretCertificateThumbprint": null, 171 | "issuer": null, 172 | "allowedAudiences": null, 173 | "additionalLoginParams": null, 174 | "isAadAutoProvisioned": false, 175 | "googleClientId": null, 176 | "googleClientSecret": null, 177 | "googleOAuthScopes": null, 178 | "facebookAppId": null, 179 | "facebookAppSecret": null, 180 | "facebookOAuthScopes": null, 181 | "twitterConsumerKey": null, 182 | "twitterConsumerSecret": null, 183 | "microsoftAccountClientId": null, 184 | "microsoftAccountClientSecret": null, 185 | "microsoftAccountOAuthScopes": null 186 | }, 187 | "cors": null, 188 | "push": null, 189 | "apiDefinition": null, 190 | "autoSwapSlotName": null, 191 | "localMySqlEnabled": false, 192 | "managedServiceIdentityId": null, 193 | "xManagedServiceIdentityId": null, 194 | "ipSecurityRestrictions": null, 195 | "http20Enabled": false, 196 | "minTlsVersion": "1.2", 197 | "ftpsState": "AllAllowed", 198 | "reservedInstanceCount": 0 199 | }, 200 | "dependsOn": [ 201 | "[resourceId('Microsoft.Web/sites', parameters('site_host_name'))]" 202 | ] 203 | }, 204 | { 205 | "type": "Microsoft.Web/sites/slots", 206 | "kind": "app,linux", 207 | "name": "[concat(parameters('site_host_name'), '/', 'dev')]", 208 | "apiVersion": "2016-08-01", 209 | "location": "[resourceGroup().location]", 210 | "scale": null, 211 | "properties": { 212 | "enabled": true, 213 | "hostNameSslStates": [{ 214 | "name": "[concat(parameters('site_host_name'), '-', 'dev','.azurewebsites.net')]", 215 | "sslState": "Disabled", 216 | "virtualIP": null, 217 | "thumbprint": null, 218 | "toUpdate": null, 219 | "hostType": "Standard" 220 | }, 221 | { 222 | "name": "[concat(parameters('site_host_name'), '-', 'dev','.scm.azurewebsites.net')]", 223 | "sslState": "Disabled", 224 | "virtualIP": null, 225 | "thumbprint": null, 226 | "toUpdate": null, 227 | "hostType": "Repository" 228 | } 229 | ], 230 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('app_svc_plan_name'))]", 231 | "reserved": true, 232 | "siteConfig": null, 233 | "scmSiteAlsoStopped": false, 234 | "hostingEnvironmentProfile": null, 235 | "clientAffinityEnabled": true, 236 | "clientCertEnabled": false, 237 | "hostNamesDisabled": false, 238 | "containerSize": 0, 239 | "dailyMemoryTimeQuota": 0, 240 | "cloningInfo": null, 241 | "httpsOnly": false 242 | }, 243 | "dependsOn": [ 244 | "[resourceId('Microsoft.Web/sites', parameters('site_host_name'))]", 245 | "[resourceId('Microsoft.Web/serverfarms', parameters('app_svc_plan_name'))]" 246 | ] 247 | }, 248 | { 249 | "type": "Microsoft.Web/sites/slots/config", 250 | "name": "[concat(parameters('site_host_name'), '/', 'dev', '/', 'web')]", 251 | "apiVersion": "2016-08-01", 252 | "location": "[resourceGroup().location]", 253 | "scale": null, 254 | "properties": { 255 | "numberOfWorkers": 1, 256 | "defaultDocuments": [ 257 | "Default.htm", 258 | "Default.html", 259 | "Default.asp", 260 | "index.htm", 261 | "index.html", 262 | "iisstart.htm", 263 | "default.aspx", 264 | "index.php", 265 | "hostingstart.html" 266 | ], 267 | "netFrameworkVersion": "v4.0", 268 | "phpVersion": "", 269 | "pythonVersion": "", 270 | "nodeVersion": "", 271 | "linuxFxVersion": "NODE|8.0", 272 | "windowsFxVersion": null, 273 | "requestTracingEnabled": false, 274 | "remoteDebuggingEnabled": false, 275 | "remoteDebuggingVersion": null, 276 | "httpLoggingEnabled": false, 277 | "logsDirectorySizeLimit": 35, 278 | "detailedErrorLoggingEnabled": false, 279 | "publishingUsername": "[concat('$', parameters('site_host_name'), '__dev')]", 280 | "publishingPassword": null, 281 | "appSettings": null, 282 | "azureStorageAccounts": {}, 283 | "metadata": null, 284 | "connectionStrings": null, 285 | "machineKey": null, 286 | "handlerMappings": null, 287 | "documentRoot": null, 288 | "scmType": "None", 289 | "use32BitWorkerProcess": true, 290 | "webSocketsEnabled": false, 291 | "alwaysOn": false, 292 | "javaVersion": null, 293 | "javaContainer": null, 294 | "javaContainerVersion": null, 295 | "appCommandLine": "", 296 | "managedPipelineMode": "Integrated", 297 | "virtualApplications": [{ 298 | "virtualPath": "/", 299 | "physicalPath": "site\\wwwroot", 300 | "preloadEnabled": false, 301 | "virtualDirectories": null 302 | }], 303 | "winAuthAdminState": 0, 304 | "winAuthTenantState": 0, 305 | "customAppPoolIdentityAdminState": false, 306 | "customAppPoolIdentityTenantState": false, 307 | "runtimeADUser": null, 308 | "runtimeADUserPassword": null, 309 | "loadBalancing": "LeastRequests", 310 | "routingRules": [], 311 | "experiments": { 312 | "rampUpRules": [] 313 | }, 314 | "limits": null, 315 | "autoHealEnabled": false, 316 | "autoHealRules": null, 317 | "tracingOptions": null, 318 | "vnetName": "", 319 | "siteAuthEnabled": false, 320 | "siteAuthSettings": { 321 | "enabled": null, 322 | "unauthenticatedClientAction": null, 323 | "tokenStoreEnabled": null, 324 | "allowedExternalRedirectUrls": null, 325 | "defaultProvider": null, 326 | "clientId": null, 327 | "clientSecret": null, 328 | "clientSecretCertificateThumbprint": null, 329 | "issuer": null, 330 | "allowedAudiences": null, 331 | "additionalLoginParams": null, 332 | "isAadAutoProvisioned": false, 333 | "googleClientId": null, 334 | "googleClientSecret": null, 335 | "googleOAuthScopes": null, 336 | "facebookAppId": null, 337 | "facebookAppSecret": null, 338 | "facebookOAuthScopes": null, 339 | "twitterConsumerKey": null, 340 | "twitterConsumerSecret": null, 341 | "microsoftAccountClientId": null, 342 | "microsoftAccountClientSecret": null, 343 | "microsoftAccountOAuthScopes": null 344 | }, 345 | "cors": null, 346 | "push": null, 347 | "apiDefinition": null, 348 | "autoSwapSlotName": null, 349 | "localMySqlEnabled": false, 350 | "managedServiceIdentityId": null, 351 | "xManagedServiceIdentityId": null, 352 | "ipSecurityRestrictions": null, 353 | "http20Enabled": false, 354 | "minTlsVersion": "1.2", 355 | "ftpsState": "AllAllowed", 356 | "reservedInstanceCount": 0 357 | }, 358 | "dependsOn": [ 359 | "[resourceId('Microsoft.Web/sites', parameters('site_host_name'))]", 360 | "[resourceId('Microsoft.Web/sites/slots', parameters('site_host_name'), 'dev')]" 361 | ] 362 | } 363 | ] 364 | } -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 443, 3 | }; -------------------------------------------------------------------------------- /debug-solution.txt: -------------------------------------------------------------------------------- 1 | To introduce common Node.js debugging scenarios into your application for teaching purposes, here are several examples that simulate typical issues developers might encounter. These scenarios are designed to be instructional without breaking the application entirely: 2 | 3 | ### 1. Dependency Version Conflict 4 | 5 | **Scenario**: Intentionally downgrade a package version in [`package.json`](command:_github.copilot.openRelativePath?%5B%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2FC%3A%2Fgithub%2Farchive%2Fnode-express-azure%2Fpackage.json%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%5D "c:\github\archive\node-express-azure\package.json") to simulate a dependency version conflict. For example, change the version of `express` to an older one that might lack some features used in the app. 6 | 7 | ```json 8 | "dependencies": { 9 | "express": "4.16.0" // Downgrade from a newer version 10 | } 11 | ``` 12 | 13 | **Teaching Point**: Demonstrate how to identify and resolve dependency version conflicts using `npm list`, `npm outdated`, and reading the package's changelog to understand breaking changes. 14 | 15 | ### 2. Async/Await Misuse 16 | 17 | **Scenario**: Introduce a bug by removing `await` from an asynchronous function call, causing unexpected behavior due to the promise not being resolved. 18 | 19 | ```javascript 20 | // Original 21 | const result = await someAsyncFunction(); 22 | 23 | // Modified (introduce bug) 24 | const result = someAsyncFunction(); 25 | ``` 26 | 27 | **Teaching Point**: Highlight the importance of correctly using `async/await` for handling asynchronous operations and how to debug issues related to promises and asynchronous code. 28 | 29 | ### 3. Environment Configuration Error 30 | 31 | **Scenario**: Misconfigure an environment variable in a way that doesn't crash the app but causes it to behave unexpectedly. For example, set the port number to a string that can't be converted to a number. 32 | 33 | ```javascript 34 | // In config.js or .env 35 | PORT="notAPort" 36 | ``` 37 | 38 | **Teaching Point**: Teach how to debug configuration issues, including checking environment variables and ensuring they are correctly parsed and validated. 39 | 40 | ### 4. Incorrect Middleware Order 41 | 42 | **Scenario**: Rearrange middleware in `app.js` so that a middleware that should run after another runs before it, such as placing error handling middleware too early in the stack. 43 | 44 | ```javascript 45 | // Before 46 | app.use('/api', apiRoutes); 47 | app.use(errorHandler); 48 | 49 | // After (introduce bug) 50 | app.use(errorHandler); 51 | app.use('/api', apiRoutes); 52 | ``` 53 | 54 | **Teaching Point**: Explain the importance of middleware order in Express applications and how to trace and resolve issues arising from incorrect middleware sequencing. 55 | 56 | ### 5. Callback Error Handling 57 | 58 | **Scenario**: Introduce a subtle bug in a callback function by not properly handling an error scenario, which might cause unhandled exceptions or the application to hang. 59 | 60 | ```javascript 61 | // Original 62 | fs.readFile('somefile.txt', (err, data) => { 63 | if (err) throw err; 64 | console.log(data); 65 | }); 66 | 67 | // Modified (introduce bug) 68 | fs.readFile('somefile.txt', (err, data) => { 69 | console.log(data); 70 | }); 71 | ``` 72 | 73 | **Teaching Point**: Discuss the importance of error handling in callbacks and how to properly handle errors to prevent crashes and ensure application robustness. 74 | 75 | These scenarios cover a range of common issues from simple syntax and logical errors to more complex asynchronous programming and configuration challenges, providing a comprehensive debugging learning experience. 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /node-express-azure.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-express-azure", 3 | "version": "2.0.1", 4 | "description": "> Sample Node app to demonstrate GitHub Copilot-assisted debugging.", 5 | "main": "app.js", 6 | "scripts": { 7 | "build": "npm install", 8 | "test": "mocha --exit", 9 | "start": "node app.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "express": "4.16.0", 15 | "express-handlebars": "^7.1.3", 16 | "tedious": "^18.2.4" 17 | }, 18 | "devDependencies": { 19 | "chai": "4.2.0", 20 | "chai-http": "4.2.0", 21 | "mocha": "^10.7.0" 22 | }, 23 | "directories": { 24 | "test": "test" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/timothywarner/node-express-azure.git" 29 | }, 30 | "keywords": [], 31 | "bugs": { 32 | "url": "https://github.com/timothywarner/node-express-azure/issues" 33 | }, 34 | "homepage": "https://github.com/timothywarner/node-express-azure#readme" 35 | } 36 | -------------------------------------------------------------------------------- /prompt.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepfeiffer/node-express-azure/8a14daa1bcd45d126b4cf6fb30b5650aa0fba10d/prompt.txt -------------------------------------------------------------------------------- /public/assets/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} 5 | -------------------------------------------------------------------------------- /public/assets/css/images/intro.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/assets/css/noscript.css: -------------------------------------------------------------------------------- 1 | /* 2 | Hyperspace by HTML5 UP 3 | html5up.net | @ajlkn 4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | */ 6 | 7 | /* Spotlights */ 8 | 9 | .spotlights > section > .image:before { 10 | opacity: 0 !important; 11 | } 12 | 13 | .spotlights > section > .content > .inner { 14 | -moz-transform: none !important; 15 | -webkit-transform: none !important; 16 | -ms-transform: none !important; 17 | transform: none !important; 18 | opacity: 1 !important; 19 | } 20 | 21 | /* Wrapper */ 22 | 23 | .wrapper > .inner { 24 | opacity: 1 !important; 25 | -moz-transform: none !important; 26 | -webkit-transform: none !important; 27 | -ms-transform: none !important; 28 | transform: none !important; 29 | } 30 | 31 | /* Sidebar */ 32 | 33 | #sidebar > .inner { 34 | opacity: 1 !important; 35 | } 36 | 37 | #sidebar nav > ul > li { 38 | -moz-transform: none !important; 39 | -webkit-transform: none !important; 40 | -ms-transform: none !important; 41 | transform: none !important; 42 | opacity: 1 !important; 43 | } -------------------------------------------------------------------------------- /public/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepfeiffer/node-express-azure/8a14daa1bcd45d126b4cf6fb30b5650aa0fba10d/public/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepfeiffer/node-express-azure/8a14daa1bcd45d126b4cf6fb30b5650aa0fba10d/public/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepfeiffer/node-express-azure/8a14daa1bcd45d126b4cf6fb30b5650aa0fba10d/public/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepfeiffer/node-express-azure/8a14daa1bcd45d126b4cf6fb30b5650aa0fba10d/public/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepfeiffer/node-express-azure/8a14daa1bcd45d126b4cf6fb30b5650aa0fba10d/public/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /public/assets/js/breakpoints.min.js: -------------------------------------------------------------------------------- 1 | /* breakpoints.js v1.0 | @ajlkn | MIT licensed */ 2 | var breakpoints=function(){"use strict";function e(e){t.init(e)}var t={list:null,media:{},events:[],init:function(e){t.list=e,window.addEventListener("resize",t.poll),window.addEventListener("orientationchange",t.poll),window.addEventListener("load",t.poll),window.addEventListener("fullscreenchange",t.poll)},active:function(e){var n,a,s,i,r,d,c;if(!(e in t.media)){if(">="==e.substr(0,2)?(a="gte",n=e.substr(2)):"<="==e.substr(0,2)?(a="lte",n=e.substr(2)):">"==e.substr(0,1)?(a="gt",n=e.substr(1)):"<"==e.substr(0,1)?(a="lt",n=e.substr(1)):"!"==e.substr(0,1)?(a="not",n=e.substr(1)):(a="eq",n=e),n&&n in t.list)if(i=t.list[n],Array.isArray(i)){if(r=parseInt(i[0]),d=parseInt(i[1]),isNaN(r)){if(isNaN(d))return;c=i[1].substr(String(d).length)}else c=i[0].substr(String(r).length);if(isNaN(r))switch(a){case"gte":s="screen";break;case"lte":s="screen and (max-width: "+d+c+")";break;case"gt":s="screen and (min-width: "+(d+1)+c+")";break;case"lt":s="screen and (max-width: -1px)";break;case"not":s="screen and (min-width: "+(d+1)+c+")";break;default:s="screen and (max-width: "+d+c+")"}else if(isNaN(d))switch(a){case"gte":s="screen and (min-width: "+r+c+")";break;case"lte":s="screen";break;case"gt":s="screen and (max-width: -1px)";break;case"lt":s="screen and (max-width: "+(r-1)+c+")";break;case"not":s="screen and (max-width: "+(r-1)+c+")";break;default:s="screen and (min-width: "+r+c+")"}else switch(a){case"gte":s="screen and (min-width: "+r+c+")";break;case"lte":s="screen and (max-width: "+d+c+")";break;case"gt":s="screen and (min-width: "+(d+1)+c+")";break;case"lt":s="screen and (max-width: "+(r-1)+c+")";break;case"not":s="screen and (max-width: "+(r-1)+c+"), screen and (min-width: "+(d+1)+c+")";break;default:s="screen and (min-width: "+r+c+") and (max-width: "+d+c+")"}}else s="("==i.charAt(0)?"screen and "+i:i;t.media[e]=!!s&&s}return t.media[e]!==!1&&window.matchMedia(t.media[e]).matches},on:function(e,n){t.events.push({query:e,handler:n,state:!1}),t.active(e)&&n()},poll:function(){var e,n;for(e=0;e0:!!("ontouchstart"in window),e.mobile="wp"==e.os||"android"==e.os||"ios"==e.os||"bb"==e.os}};return e.init(),e}();!function(e,n){"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?module.exports=n():e.browser=n()}(this,function(){return browser}); 3 | -------------------------------------------------------------------------------- /public/assets/js/jquery.scrollex.min.js: -------------------------------------------------------------------------------- 1 | /* jquery.scrollex v0.2.1 | (c) @ajlkn | github.com/ajlkn/jquery.scrollex | MIT licensed */ 2 | !function(t){function e(t,e,n){return"string"==typeof t&&("%"==t.slice(-1)?t=parseInt(t.substring(0,t.length-1))/100*e:"vh"==t.slice(-2)?t=parseInt(t.substring(0,t.length-2))/100*n:"px"==t.slice(-2)&&(t=parseInt(t.substring(0,t.length-2)))),t}var n=t(window),i=1,o={};n.on("scroll",function(){var e=n.scrollTop();t.map(o,function(t){window.clearTimeout(t.timeoutId),t.timeoutId=window.setTimeout(function(){t.handler(e)},t.options.delay)})}).on("load",function(){n.trigger("scroll")}),jQuery.fn.scrollex=function(l){var s=t(this);if(0==this.length)return s;if(this.length>1){for(var r=0;r=i&&o>=t};break;case"bottom":h=function(t,e,n,i,o){return n>=i&&o>=n};break;case"middle":h=function(t,e,n,i,o){return e>=i&&o>=e};break;case"top-only":h=function(t,e,n,i,o){return i>=t&&n>=i};break;case"bottom-only":h=function(t,e,n,i,o){return n>=o&&o>=t};break;default:case"default":h=function(t,e,n,i,o){return n>=i&&o>=t}}return c=function(t){var i,o,l,s,r,a,u=this.state,h=!1,c=this.$element.offset();i=n.height(),o=t+i/2,l=t+i,s=this.$element.outerHeight(),r=c.top+e(this.options.top,s,i),a=c.top+s-e(this.options.bottom,s,i),h=this.test(t,o,l,r,a),h!=u&&(this.state=h,h?this.options.enter&&this.options.enter.apply(this.element):this.options.leave&&this.options.leave.apply(this.element)),this.options.scroll&&this.options.scroll.apply(this.element,[(o-r)/(a-r)])},p={id:a,options:u,test:h,handler:c,state:null,element:this,$element:s,timeoutId:null},o[a]=p,s.data("_scrollexId",p.id),p.options.initialize&&p.options.initialize.apply(this),s},jQuery.fn.unscrollex=function(){var e=t(this);if(0==this.length)return e;if(this.length>1){for(var n=0;n1){for(o=0;o 0) { 49 | 50 | var $sidebar_a = $sidebar.find('a'); 51 | 52 | $sidebar_a 53 | .addClass('scrolly') 54 | .on('click', function() { 55 | 56 | var $this = $(this); 57 | 58 | // External link? Bail. 59 | if ($this.attr('href').charAt(0) != '#') 60 | return; 61 | 62 | // Deactivate all links. 63 | $sidebar_a.removeClass('active'); 64 | 65 | // Activate link *and* lock it (so Scrollex doesn't try to activate other links as we're scrolling to this one's section). 66 | $this 67 | .addClass('active') 68 | .addClass('active-locked'); 69 | 70 | }) 71 | .each(function() { 72 | 73 | var $this = $(this), 74 | id = $this.attr('href'), 75 | $section = $(id); 76 | 77 | // No section for this link? Bail. 78 | if ($section.length < 1) 79 | return; 80 | 81 | // Scrollex. 82 | $section.scrollex({ 83 | mode: 'middle', 84 | top: '-20vh', 85 | bottom: '-20vh', 86 | initialize: function() { 87 | 88 | // Deactivate section. 89 | $section.addClass('inactive'); 90 | 91 | }, 92 | enter: function() { 93 | 94 | // Activate section. 95 | $section.removeClass('inactive'); 96 | 97 | // No locked links? Deactivate all links and activate this section's one. 98 | if ($sidebar_a.filter('.active-locked').length == 0) { 99 | 100 | $sidebar_a.removeClass('active'); 101 | $this.addClass('active'); 102 | 103 | } 104 | 105 | // Otherwise, if this section's link is the one that's locked, unlock it. 106 | else if ($this.hasClass('active-locked')) 107 | $this.removeClass('active-locked'); 108 | 109 | } 110 | }); 111 | 112 | }); 113 | 114 | } 115 | 116 | // Scrolly. 117 | $('.scrolly').scrolly({ 118 | speed: 1000, 119 | offset: function() { 120 | 121 | // If <=large, >small, and sidebar is present, use its height as the offset. 122 | if (breakpoints.active('<=large') 123 | && !breakpoints.active('<=small') 124 | && $sidebar.length > 0) 125 | return $sidebar.height(); 126 | 127 | return 0; 128 | 129 | } 130 | }); 131 | 132 | // Spotlights. 133 | $('.spotlights > section') 134 | .scrollex({ 135 | mode: 'middle', 136 | top: '-10vh', 137 | bottom: '-10vh', 138 | initialize: function() { 139 | 140 | // Deactivate section. 141 | $(this).addClass('inactive'); 142 | 143 | }, 144 | enter: function() { 145 | 146 | // Activate section. 147 | $(this).removeClass('inactive'); 148 | 149 | } 150 | }) 151 | .each(function() { 152 | 153 | var $this = $(this), 154 | $image = $this.find('.image'), 155 | $img = $image.find('img'), 156 | x; 157 | 158 | // Assign image. 159 | $image.css('background-image', 'url(' + $img.attr('src') + ')'); 160 | 161 | // Set background position. 162 | if (x = $img.data('position')) 163 | $image.css('background-position', x); 164 | 165 | // Hide . 166 | $img.hide(); 167 | 168 | }); 169 | 170 | // Features. 171 | $('.features') 172 | .scrollex({ 173 | mode: 'middle', 174 | top: '-20vh', 175 | bottom: '-20vh', 176 | initialize: function() { 177 | 178 | // Deactivate section. 179 | $(this).addClass('inactive'); 180 | 181 | }, 182 | enter: function() { 183 | 184 | // Activate section. 185 | $(this).removeClass('inactive'); 186 | 187 | } 188 | }); 189 | 190 | })(jQuery); -------------------------------------------------------------------------------- /public/assets/js/util.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | /** 4 | * Generate an indented list of links from a nav. Meant for use with panel(). 5 | * @return {jQuery} jQuery object. 6 | */ 7 | $.fn.navList = function() { 8 | 9 | var $this = $(this); 10 | $a = $this.find('a'), 11 | b = []; 12 | 13 | $a.each(function() { 14 | 15 | var $this = $(this), 16 | indent = Math.max(0, $this.parents('li').length - 1), 17 | href = $this.attr('href'), 18 | target = $this.attr('target'); 19 | 20 | b.push( 21 | '' + 26 | '' + 27 | $this.text() + 28 | '' 29 | ); 30 | 31 | }); 32 | 33 | return b.join(''); 34 | 35 | }; 36 | 37 | /** 38 | * Panel-ify an element. 39 | * @param {object} userConfig User config. 40 | * @return {jQuery} jQuery object. 41 | */ 42 | $.fn.panel = function(userConfig) { 43 | 44 | // No elements? 45 | if (this.length == 0) 46 | return $this; 47 | 48 | // Multiple elements? 49 | if (this.length > 1) { 50 | 51 | for (var i=0; i < this.length; i++) 52 | $(this[i]).panel(userConfig); 53 | 54 | return $this; 55 | 56 | } 57 | 58 | // Vars. 59 | var $this = $(this), 60 | $body = $('body'), 61 | $window = $(window), 62 | id = $this.attr('id'), 63 | config; 64 | 65 | // Config. 66 | config = $.extend({ 67 | 68 | // Delay. 69 | delay: 0, 70 | 71 | // Hide panel on link click. 72 | hideOnClick: false, 73 | 74 | // Hide panel on escape keypress. 75 | hideOnEscape: false, 76 | 77 | // Hide panel on swipe. 78 | hideOnSwipe: false, 79 | 80 | // Reset scroll position on hide. 81 | resetScroll: false, 82 | 83 | // Reset forms on hide. 84 | resetForms: false, 85 | 86 | // Side of viewport the panel will appear. 87 | side: null, 88 | 89 | // Target element for "class". 90 | target: $this, 91 | 92 | // Class to toggle. 93 | visibleClass: 'visible' 94 | 95 | }, userConfig); 96 | 97 | // Expand "target" if it's not a jQuery object already. 98 | if (typeof config.target != 'jQuery') 99 | config.target = $(config.target); 100 | 101 | // Panel. 102 | 103 | // Methods. 104 | $this._hide = function(event) { 105 | 106 | // Already hidden? Bail. 107 | if (!config.target.hasClass(config.visibleClass)) 108 | return; 109 | 110 | // If an event was provided, cancel it. 111 | if (event) { 112 | 113 | event.preventDefault(); 114 | event.stopPropagation(); 115 | 116 | } 117 | 118 | // Hide. 119 | config.target.removeClass(config.visibleClass); 120 | 121 | // Post-hide stuff. 122 | window.setTimeout(function() { 123 | 124 | // Reset scroll position. 125 | if (config.resetScroll) 126 | $this.scrollTop(0); 127 | 128 | // Reset forms. 129 | if (config.resetForms) 130 | $this.find('form').each(function() { 131 | this.reset(); 132 | }); 133 | 134 | }, config.delay); 135 | 136 | }; 137 | 138 | // Vendor fixes. 139 | $this 140 | .css('-ms-overflow-style', '-ms-autohiding-scrollbar') 141 | .css('-webkit-overflow-scrolling', 'touch'); 142 | 143 | // Hide on click. 144 | if (config.hideOnClick) { 145 | 146 | $this.find('a') 147 | .css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)'); 148 | 149 | $this 150 | .on('click', 'a', function(event) { 151 | 152 | var $a = $(this), 153 | href = $a.attr('href'), 154 | target = $a.attr('target'); 155 | 156 | if (!href || href == '#' || href == '' || href == '#' + id) 157 | return; 158 | 159 | // Cancel original event. 160 | event.preventDefault(); 161 | event.stopPropagation(); 162 | 163 | // Hide panel. 164 | $this._hide(); 165 | 166 | // Redirect to href. 167 | window.setTimeout(function() { 168 | 169 | if (target == '_blank') 170 | window.open(href); 171 | else 172 | window.location.href = href; 173 | 174 | }, config.delay + 10); 175 | 176 | }); 177 | 178 | } 179 | 180 | // Event: Touch stuff. 181 | $this.on('touchstart', function(event) { 182 | 183 | $this.touchPosX = event.originalEvent.touches[0].pageX; 184 | $this.touchPosY = event.originalEvent.touches[0].pageY; 185 | 186 | }) 187 | 188 | $this.on('touchmove', function(event) { 189 | 190 | if ($this.touchPosX === null 191 | || $this.touchPosY === null) 192 | return; 193 | 194 | var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX, 195 | diffY = $this.touchPosY - event.originalEvent.touches[0].pageY, 196 | th = $this.outerHeight(), 197 | ts = ($this.get(0).scrollHeight - $this.scrollTop()); 198 | 199 | // Hide on swipe? 200 | if (config.hideOnSwipe) { 201 | 202 | var result = false, 203 | boundary = 20, 204 | delta = 50; 205 | 206 | switch (config.side) { 207 | 208 | case 'left': 209 | result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta); 210 | break; 211 | 212 | case 'right': 213 | result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta)); 214 | break; 215 | 216 | case 'top': 217 | result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta); 218 | break; 219 | 220 | case 'bottom': 221 | result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta)); 222 | break; 223 | 224 | default: 225 | break; 226 | 227 | } 228 | 229 | if (result) { 230 | 231 | $this.touchPosX = null; 232 | $this.touchPosY = null; 233 | $this._hide(); 234 | 235 | return false; 236 | 237 | } 238 | 239 | } 240 | 241 | // Prevent vertical scrolling past the top or bottom. 242 | if (($this.scrollTop() < 0 && diffY < 0) 243 | || (ts > (th - 2) && ts < (th + 2) && diffY > 0)) { 244 | 245 | event.preventDefault(); 246 | event.stopPropagation(); 247 | 248 | } 249 | 250 | }); 251 | 252 | // Event: Prevent certain events inside the panel from bubbling. 253 | $this.on('click touchend touchstart touchmove', function(event) { 254 | event.stopPropagation(); 255 | }); 256 | 257 | // Event: Hide panel if a child anchor tag pointing to its ID is clicked. 258 | $this.on('click', 'a[href="#' + id + '"]', function(event) { 259 | 260 | event.preventDefault(); 261 | event.stopPropagation(); 262 | 263 | config.target.removeClass(config.visibleClass); 264 | 265 | }); 266 | 267 | // Body. 268 | 269 | // Event: Hide panel on body click/tap. 270 | $body.on('click touchend', function(event) { 271 | $this._hide(event); 272 | }); 273 | 274 | // Event: Toggle. 275 | $body.on('click', 'a[href="#' + id + '"]', function(event) { 276 | 277 | event.preventDefault(); 278 | event.stopPropagation(); 279 | 280 | config.target.toggleClass(config.visibleClass); 281 | 282 | }); 283 | 284 | // Window. 285 | 286 | // Event: Hide on ESC. 287 | if (config.hideOnEscape) 288 | $window.on('keydown', function(event) { 289 | 290 | if (event.keyCode == 27) 291 | $this._hide(event); 292 | 293 | }); 294 | 295 | return $this; 296 | 297 | }; 298 | 299 | /** 300 | * Apply "placeholder" attribute polyfill to one or more forms. 301 | * @return {jQuery} jQuery object. 302 | */ 303 | $.fn.placeholder = function() { 304 | 305 | // Browser natively supports placeholders? Bail. 306 | if (typeof (document.createElement('input')).placeholder != 'undefined') 307 | return $(this); 308 | 309 | // No elements? 310 | if (this.length == 0) 311 | return $this; 312 | 313 | // Multiple elements? 314 | if (this.length > 1) { 315 | 316 | for (var i=0; i < this.length; i++) 317 | $(this[i]).placeholder(); 318 | 319 | return $this; 320 | 321 | } 322 | 323 | // Vars. 324 | var $this = $(this); 325 | 326 | // Text, TextArea. 327 | $this.find('input[type=text],textarea') 328 | .each(function() { 329 | 330 | var i = $(this); 331 | 332 | if (i.val() == '' 333 | || i.val() == i.attr('placeholder')) 334 | i 335 | .addClass('polyfill-placeholder') 336 | .val(i.attr('placeholder')); 337 | 338 | }) 339 | .on('blur', function() { 340 | 341 | var i = $(this); 342 | 343 | if (i.attr('name').match(/-polyfill-field$/)) 344 | return; 345 | 346 | if (i.val() == '') 347 | i 348 | .addClass('polyfill-placeholder') 349 | .val(i.attr('placeholder')); 350 | 351 | }) 352 | .on('focus', function() { 353 | 354 | var i = $(this); 355 | 356 | if (i.attr('name').match(/-polyfill-field$/)) 357 | return; 358 | 359 | if (i.val() == i.attr('placeholder')) 360 | i 361 | .removeClass('polyfill-placeholder') 362 | .val(''); 363 | 364 | }); 365 | 366 | // Password. 367 | $this.find('input[type=password]') 368 | .each(function() { 369 | 370 | var i = $(this); 371 | var x = $( 372 | $('
') 373 | .append(i.clone()) 374 | .remove() 375 | .html() 376 | .replace(/type="password"/i, 'type="text"') 377 | .replace(/type=password/i, 'type=text') 378 | ); 379 | 380 | if (i.attr('id') != '') 381 | x.attr('id', i.attr('id') + '-polyfill-field'); 382 | 383 | if (i.attr('name') != '') 384 | x.attr('name', i.attr('name') + '-polyfill-field'); 385 | 386 | x.addClass('polyfill-placeholder') 387 | .val(x.attr('placeholder')).insertAfter(i); 388 | 389 | if (i.val() == '') 390 | i.hide(); 391 | else 392 | x.hide(); 393 | 394 | i 395 | .on('blur', function(event) { 396 | 397 | event.preventDefault(); 398 | 399 | var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]'); 400 | 401 | if (i.val() == '') { 402 | 403 | i.hide(); 404 | x.show(); 405 | 406 | } 407 | 408 | }); 409 | 410 | x 411 | .on('focus', function(event) { 412 | 413 | event.preventDefault(); 414 | 415 | var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']'); 416 | 417 | x.hide(); 418 | 419 | i 420 | .show() 421 | .focus(); 422 | 423 | }) 424 | .on('keypress', function(event) { 425 | 426 | event.preventDefault(); 427 | x.val(''); 428 | 429 | }); 430 | 431 | }); 432 | 433 | // Events. 434 | $this 435 | .on('submit', function() { 436 | 437 | $this.find('input[type=text],input[type=password],textarea') 438 | .each(function(event) { 439 | 440 | var i = $(this); 441 | 442 | if (i.attr('name').match(/-polyfill-field$/)) 443 | i.attr('name', ''); 444 | 445 | if (i.val() == i.attr('placeholder')) { 446 | 447 | i.removeClass('polyfill-placeholder'); 448 | i.val(''); 449 | 450 | } 451 | 452 | }); 453 | 454 | }) 455 | .on('reset', function(event) { 456 | 457 | event.preventDefault(); 458 | 459 | $this.find('select') 460 | .val($('option:first').val()); 461 | 462 | $this.find('input,textarea') 463 | .each(function() { 464 | 465 | var i = $(this), 466 | x; 467 | 468 | i.removeClass('polyfill-placeholder'); 469 | 470 | switch (this.type) { 471 | 472 | case 'submit': 473 | case 'reset': 474 | break; 475 | 476 | case 'password': 477 | i.val(i.attr('defaultValue')); 478 | 479 | x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]'); 480 | 481 | if (i.val() == '') { 482 | i.hide(); 483 | x.show(); 484 | } 485 | else { 486 | i.show(); 487 | x.hide(); 488 | } 489 | 490 | break; 491 | 492 | case 'checkbox': 493 | case 'radio': 494 | i.attr('checked', i.attr('defaultValue')); 495 | break; 496 | 497 | case 'text': 498 | case 'textarea': 499 | i.val(i.attr('defaultValue')); 500 | 501 | if (i.val() == '') { 502 | i.addClass('polyfill-placeholder'); 503 | i.val(i.attr('placeholder')); 504 | } 505 | 506 | break; 507 | 508 | default: 509 | i.val(i.attr('defaultValue')); 510 | break; 511 | 512 | } 513 | }); 514 | 515 | }); 516 | 517 | return $this; 518 | 519 | }; 520 | 521 | /** 522 | * Moves elements to/from the first positions of their respective parents. 523 | * @param {jQuery} $elements Elements (or selector) to move. 524 | * @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations. 525 | */ 526 | $.prioritize = function($elements, condition) { 527 | 528 | var key = '__prioritize'; 529 | 530 | // Expand $elements if it's not already a jQuery object. 531 | if (typeof $elements != 'jQuery') 532 | $elements = $($elements); 533 | 534 | // Step through elements. 535 | $elements.each(function() { 536 | 537 | var $e = $(this), $p, 538 | $parent = $e.parent(); 539 | 540 | // No parent? Bail. 541 | if ($parent.length == 0) 542 | return; 543 | 544 | // Not moved? Move it. 545 | if (!$e.data(key)) { 546 | 547 | // Condition is false? Bail. 548 | if (!condition) 549 | return; 550 | 551 | // Get placeholder (which will serve as our point of reference for when this element needs to move back). 552 | $p = $e.prev(); 553 | 554 | // Couldn't find anything? Means this element's already at the top, so bail. 555 | if ($p.length == 0) 556 | return; 557 | 558 | // Move element to top of parent. 559 | $e.prependTo($parent); 560 | 561 | // Mark element as moved. 562 | $e.data(key, $p); 563 | 564 | } 565 | 566 | // Moved already? 567 | else { 568 | 569 | // Condition is true? Bail. 570 | if (condition) 571 | return; 572 | 573 | $p = $e.data(key); 574 | 575 | // Move element back to its original location (using our placeholder). 576 | $e.insertAfter($p); 577 | 578 | // Unmark element as moved. 579 | $e.removeData(key); 580 | 581 | } 582 | 583 | }); 584 | 585 | }; 586 | 587 | })(jQuery); -------------------------------------------------------------------------------- /public/assets/sass/base/_page.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Basic */ 8 | 9 | // MSIE: Required for IEMobile. 10 | @-ms-viewport { 11 | width: device-width; 12 | } 13 | 14 | // MSIE: Prevents scrollbar from overlapping content. 15 | body { 16 | -ms-overflow-style: scrollbar; 17 | } 18 | 19 | // Ensures page width is always >=320px. 20 | @include breakpoint('<=xsmall') { 21 | html, body { 22 | min-width: 320px; 23 | } 24 | } 25 | 26 | // Set box model to border-box. 27 | // Based on css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice 28 | html { 29 | box-sizing: border-box; 30 | } 31 | 32 | *, *:before, *:after { 33 | box-sizing: inherit; 34 | } 35 | 36 | body { 37 | background: _palette(bg); 38 | 39 | // Stops initial animations until page loads. 40 | &.is-preload { 41 | *, *:before, *:after { 42 | @include vendor('animation', 'none !important'); 43 | @include vendor('transition', 'none !important'); 44 | } 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /public/assets/sass/base/_reset.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | // Reset. 8 | // Based on meyerweb.com/eric/tools/css/reset (v2.0 | 20110126 | License: public domain) 9 | 10 | html, body, div, span, applet, object, 11 | iframe, h1, h2, h3, h4, h5, h6, p, blockquote, 12 | pre, a, abbr, acronym, address, big, cite, 13 | code, del, dfn, em, img, ins, kbd, q, s, samp, 14 | small, strike, strong, sub, sup, tt, var, b, 15 | u, i, center, dl, dt, dd, ol, ul, li, fieldset, 16 | form, label, legend, table, caption, tbody, 17 | tfoot, thead, tr, th, td, article, aside, 18 | canvas, details, embed, figure, figcaption, 19 | footer, header, hgroup, menu, nav, output, ruby, 20 | section, summary, time, mark, audio, video { 21 | margin: 0; 22 | padding: 0; 23 | border: 0; 24 | font-size: 100%; 25 | font: inherit; 26 | vertical-align: baseline; 27 | } 28 | 29 | article, aside, details, figcaption, figure, 30 | footer, header, hgroup, menu, nav, section { 31 | display: block; 32 | } 33 | 34 | body { 35 | line-height: 1; 36 | } 37 | 38 | ol, ul { 39 | list-style:none; 40 | } 41 | 42 | blockquote, q { 43 | quotes: none; 44 | 45 | &:before, 46 | &:after { 47 | content: ''; 48 | content: none; 49 | } 50 | } 51 | 52 | table { 53 | border-collapse: collapse; 54 | border-spacing: 0; 55 | } 56 | 57 | body { 58 | -webkit-text-size-adjust: none; 59 | } 60 | 61 | mark { 62 | background-color: transparent; 63 | color: inherit; 64 | } 65 | 66 | input::-moz-focus-inner { 67 | border: 0; 68 | padding: 0; 69 | } 70 | 71 | input, select, textarea { 72 | -moz-appearance: none; 73 | -webkit-appearance: none; 74 | -ms-appearance: none; 75 | appearance: none; 76 | } -------------------------------------------------------------------------------- /public/assets/sass/base/_typography.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Type */ 8 | 9 | body, input, select, textarea { 10 | color: _palette(fg); 11 | font-family: _font(family); 12 | font-size: 16.5pt; 13 | font-weight: _font(weight); 14 | line-height: 1.75; 15 | 16 | @include breakpoint('<=xlarge') { 17 | font-size: 13pt; 18 | } 19 | 20 | @include breakpoint('<=large') { 21 | font-size: 12pt; 22 | } 23 | 24 | @include breakpoint('<=xxsmall') { 25 | font-size: 11pt; 26 | } 27 | } 28 | 29 | a { 30 | @include vendor('transition', ( 31 | 'color #{_duration(transition)} ease', 32 | 'border-bottom-color #{_duration(transition)} ease' 33 | )); 34 | border-bottom: dotted 1px _palette(fg-light); 35 | color: inherit; 36 | text-decoration: none; 37 | 38 | &:hover { 39 | border-bottom-color: transparent; 40 | color: _palette(fg-bold); 41 | } 42 | } 43 | 44 | strong, b { 45 | color: _palette(fg-bold); 46 | font-weight: _font(weight-bold); 47 | } 48 | 49 | em, i { 50 | font-style: italic; 51 | } 52 | 53 | p { 54 | margin: 0 0 _size(element-margin) 0; 55 | } 56 | 57 | h1, h2, h3, h4, h5, h6 { 58 | color: _palette(fg-bold); 59 | font-weight: _font(weight-bold); 60 | line-height: 1.5; 61 | margin: 0 0 (_size(element-margin) * 0.25) 0; 62 | 63 | a { 64 | color: inherit; 65 | text-decoration: none; 66 | } 67 | } 68 | 69 | h1 { 70 | font-size: 2.75em; 71 | 72 | &.major { 73 | margin: 0 0 (_size(element-margin) * 0.65) 0; 74 | position: relative; 75 | padding-bottom: 0.35em; 76 | 77 | &:after { 78 | @include vendor('background-image', 'linear-gradient(to right, #{_palette(accent1)}, #{_palette(accent3)})'); 79 | @include vendor('transition', 'max-width #{_duration(transition)} ease'); 80 | border-radius: 0.2em; 81 | bottom: 0; 82 | content: ''; 83 | height: 0.05em; 84 | position: absolute; 85 | right: 0; 86 | width: 100%; 87 | } 88 | } 89 | } 90 | 91 | h2 { 92 | font-size: 1.75em; 93 | } 94 | 95 | h3 { 96 | font-size: 1.1em; 97 | } 98 | 99 | h4 { 100 | font-size: 1em; 101 | } 102 | 103 | h5 { 104 | font-size: 0.8em; 105 | } 106 | 107 | h6 { 108 | font-size: 0.6em; 109 | } 110 | 111 | @include breakpoint('<=small') { 112 | h1 { 113 | font-size: 2em; 114 | } 115 | 116 | h2 { 117 | font-size: 1.25em; 118 | } 119 | 120 | h3 { 121 | font-size: 1em; 122 | } 123 | 124 | h4 { 125 | font-size: 0.8em; 126 | } 127 | 128 | h5 { 129 | font-size: 0.6em; 130 | } 131 | 132 | h6 { 133 | font-size: 0.6em; 134 | } 135 | } 136 | 137 | sub { 138 | font-size: 0.8em; 139 | position: relative; 140 | top: 0.5em; 141 | } 142 | 143 | sup { 144 | font-size: 0.8em; 145 | position: relative; 146 | top: -0.5em; 147 | } 148 | 149 | blockquote { 150 | border-left: solid (_size(border-width) * 4) _palette(border); 151 | font-style: italic; 152 | margin: 0 0 _size(element-margin) 0; 153 | padding: (_size(element-margin) / 4) 0 (_size(element-margin) / 4) _size(element-margin); 154 | } 155 | 156 | code { 157 | background: _palette(border-bg); 158 | border-radius: _size(border-radius); 159 | border: solid _size(border-width) _palette(border); 160 | font-family: _font(family-fixed); 161 | font-size: 0.9em; 162 | margin: 0 0.25em; 163 | padding: 0.25em 0.65em; 164 | } 165 | 166 | pre { 167 | -webkit-overflow-scrolling: touch; 168 | font-family: _font(family-fixed); 169 | font-size: 0.9em; 170 | margin: 0 0 _size(element-margin) 0; 171 | 172 | code { 173 | display: block; 174 | line-height: 1.75em; 175 | padding: 1em 1.5em; 176 | overflow-x: auto; 177 | } 178 | } 179 | 180 | hr { 181 | border: 0; 182 | border-bottom: solid _size(border-width) _palette(border); 183 | margin: _size(element-margin) 0; 184 | 185 | &.major { 186 | margin: (_size(element-margin) * 1.5) 0; 187 | } 188 | } 189 | 190 | .align-left { 191 | text-align: left; 192 | } 193 | 194 | .align-center { 195 | text-align: center; 196 | } 197 | 198 | .align-right { 199 | text-align: right; 200 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_actions.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Actions */ 8 | 9 | ul.actions { 10 | @include vendor('display', 'flex'); 11 | cursor: default; 12 | list-style: none; 13 | margin-left: (_size(element-margin) * -0.5); 14 | padding-left: 0; 15 | 16 | li { 17 | padding: 0 0 0 (_size(element-margin) * 0.5); 18 | vertical-align: middle; 19 | } 20 | 21 | &.special { 22 | @include vendor('justify-content', 'center'); 23 | width: 100%; 24 | margin-left: 0; 25 | 26 | li { 27 | &:first-child { 28 | padding-left: 0; 29 | } 30 | } 31 | } 32 | 33 | &.stacked { 34 | @include vendor('flex-direction', 'column'); 35 | margin-left: 0; 36 | 37 | li { 38 | padding: (_size(element-margin) * 0.65) 0 0 0; 39 | 40 | &:first-child { 41 | padding-top: 0; 42 | } 43 | } 44 | } 45 | 46 | &.fit { 47 | width: calc(100% + #{_size(element-margin) * 0.5}); 48 | 49 | li { 50 | @include vendor('flex-grow', '1'); 51 | @include vendor('flex-shrink', '1'); 52 | width: 100%; 53 | 54 | > * { 55 | width: 100%; 56 | } 57 | } 58 | 59 | &.stacked { 60 | width: 100%; 61 | } 62 | } 63 | 64 | @include breakpoint('<=xsmall') { 65 | &:not(.fixed) { 66 | @include vendor('flex-direction', 'column'); 67 | margin-left: 0; 68 | width: 100% !important; 69 | 70 | li { 71 | @include vendor('flex-grow', '1'); 72 | @include vendor('flex-shrink', '1'); 73 | padding: (_size(element-margin) * 0.5) 0 0 0; 74 | text-align: center; 75 | width: 100%; 76 | 77 | > * { 78 | width: 100%; 79 | } 80 | 81 | &:first-child { 82 | padding-top: 0; 83 | } 84 | 85 | input[type="submit"], 86 | input[type="reset"], 87 | input[type="button"], 88 | button, 89 | .button { 90 | width: 100%; 91 | 92 | &.icon { 93 | &:before { 94 | margin-left: -0.5rem; 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_box.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Box */ 8 | 9 | .box { 10 | border-radius: _size(border-radius); 11 | border: solid _size(border-width) _palette(border); 12 | margin-bottom: _size(element-margin); 13 | padding: 1.5em; 14 | 15 | > :last-child, 16 | > :last-child > :last-child, 17 | > :last-child > :last-child > :last-child { 18 | margin-bottom: 0; 19 | } 20 | 21 | &.alt { 22 | border: 0; 23 | border-radius: 0; 24 | padding: 0; 25 | } 26 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_button.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Button */ 8 | 9 | input[type="submit"], 10 | input[type="reset"], 11 | input[type="button"], 12 | button, 13 | .button { 14 | @include vendor('appearance', 'none'); 15 | @include vendor('transition', ( 16 | 'border-color #{_duration(transition)} ease' 17 | )); 18 | background-color: transparent; 19 | border: solid 1px !important; 20 | border-color: _palette(border) !important; 21 | border-radius: 3em; 22 | color: _palette(fg-bold) !important; 23 | cursor: pointer; 24 | display: inline-block; 25 | font-size: 0.6em; 26 | font-weight: _font(weight-bold); 27 | height: calc(4.75em + 2px); 28 | letter-spacing: _font(kerning-alt); 29 | line-height: 4.75em; 30 | outline: 0; 31 | padding: 0 3.75em; 32 | position: relative; 33 | text-align: center; 34 | text-decoration: none; 35 | text-transform: uppercase; 36 | white-space: nowrap; 37 | 38 | &:after { 39 | @include vendor('transform', 'scale(0.25)'); 40 | @include vendor('pointer-events', 'none'); 41 | @include vendor('transition', ( 42 | 'opacity #{_duration(transition)} ease', 43 | 'transform #{_duration(transition)} ease' 44 | )); 45 | background: _palette(fg-bold); 46 | border-radius: 3em; 47 | content: ''; 48 | height: 100%; 49 | left: 0; 50 | opacity: 0; 51 | position: absolute; 52 | top: 0; 53 | width: 100%; 54 | } 55 | 56 | &.icon { 57 | &:before { 58 | margin-right: 0.75em; 59 | } 60 | } 61 | 62 | &.fit { 63 | width: 100%; 64 | } 65 | 66 | &.small { 67 | font-size: 0.4em; 68 | } 69 | 70 | &.large { 71 | font-size: 0.8em; 72 | } 73 | 74 | &.primary { 75 | background-color: _palette(fg-bold); 76 | color: _palette(bg) !important; 77 | 78 | &:after { 79 | display: none; 80 | } 81 | } 82 | 83 | &.disabled, 84 | &:disabled { 85 | cursor: default; 86 | opacity: 0.5; 87 | @include vendor('pointer-events', 'none'); 88 | } 89 | 90 | &:hover { 91 | border-color: _palette(fg) !important; 92 | 93 | &:after { 94 | opacity: 0.05; 95 | @include vendor('transform', 'scale(1)'); 96 | } 97 | 98 | &:active { 99 | border-color: _palette(fg-bold) !important; 100 | 101 | &:after { 102 | opacity: 0.1; 103 | } 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_contact.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Contact */ 8 | 9 | ul.contact { 10 | list-style: none; 11 | padding: 0; 12 | 13 | > li { 14 | padding: 0; 15 | margin: 1.5em 0 0 0; 16 | 17 | &:first-child { 18 | margin-top: 0; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_features.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Features */ 8 | 9 | .features { 10 | @include vendor('display', 'flex'); 11 | @include vendor('flex-wrap', 'wrap'); 12 | border-radius: _size(border-radius); 13 | border: solid 1px _palette(border); 14 | background: _palette(border-bg); 15 | margin: 0 0 _size(element-margin) 0; 16 | 17 | section { 18 | @include padding(3em, 3em, (0.5em, 0, 0, 4em)); 19 | width: 50%; 20 | border-top: solid 1px _palette(border); 21 | position: relative; 22 | 23 | &:nth-child(-n + 2) { 24 | border-top-width: 0; 25 | } 26 | 27 | &:nth-child(2n) { 28 | border-left: solid 1px _palette(border); 29 | } 30 | 31 | .icon { 32 | @include vendor('transition', ( 33 | 'opacity #{_duration(activation) * 0.5} ease', 34 | 'transform #{_duration(activation) * 0.5} ease' 35 | )); 36 | @include vendor('transition-delay', '1s'); 37 | @include vendor('transform', 'scale(1)'); 38 | position: absolute; 39 | left: 3em; 40 | top: 3em; 41 | opacity: 1; 42 | } 43 | 44 | @for $i from 1 through _misc(max-features) { 45 | &:nth-child(#{$i}) { 46 | .icon { 47 | @include vendor('transition-delay', '#{(_duration(transition) * 0.75 * $i)}'); 48 | } 49 | } 50 | } 51 | } 52 | 53 | &.inactive { 54 | section { 55 | .icon { 56 | @include vendor('transform', 'scale(0.5)'); 57 | opacity: 0; 58 | } 59 | } 60 | } 61 | 62 | @include breakpoint('<=medium') { 63 | display: block; 64 | 65 | section { 66 | border-top-width: 1px !important; 67 | border-left-width: 0 !important; 68 | width: 100%; 69 | 70 | &:first-child { 71 | border-top-width: 0 !important; 72 | } 73 | } 74 | } 75 | 76 | @include breakpoint('<=small') { 77 | section { 78 | @include padding(2em, 1.5em, (0.5em, 0, 0, 4em)); 79 | 80 | .icon { 81 | left: 1.5em; 82 | top: 2em; 83 | } 84 | } 85 | } 86 | 87 | @include breakpoint('<=xsmall') { 88 | section { 89 | @include padding(2em, 1.5em, (0, 0, 0, 0)); 90 | 91 | .icon { 92 | left: 0; 93 | position: relative; 94 | top: 0; 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_form.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Form */ 8 | 9 | form { 10 | margin: 0 0 _size(element-margin) 0; 11 | 12 | > :last-child { 13 | margin-bottom: 0; 14 | } 15 | 16 | > .fields { 17 | $gutter: (_size(element-margin) * 0.75); 18 | 19 | @include vendor('display', 'flex'); 20 | @include vendor('flex-wrap', 'wrap'); 21 | width: calc(100% + #{$gutter * 2}); 22 | margin: ($gutter * -1) 0 _size(element-margin) ($gutter * -1); 23 | 24 | > .field { 25 | @include vendor('flex-grow', '0'); 26 | @include vendor('flex-shrink', '0'); 27 | padding: $gutter 0 0 $gutter; 28 | width: calc(100% - #{$gutter * 1}); 29 | 30 | &.half { 31 | width: calc(50% - #{$gutter * 0.5}); 32 | } 33 | 34 | &.third { 35 | width: calc(#{100% / 3} - #{$gutter * (1 / 3)}); 36 | } 37 | 38 | &.quarter { 39 | width: calc(25% - #{$gutter * 0.25}); 40 | } 41 | } 42 | } 43 | 44 | @include breakpoint('<=xsmall') { 45 | > .fields { 46 | $gutter: (_size(element-margin) * 0.75); 47 | 48 | width: calc(100% + #{$gutter * 2}); 49 | margin: ($gutter * -1) 0 _size(element-margin) ($gutter * -1); 50 | 51 | > .field { 52 | padding: $gutter 0 0 $gutter; 53 | width: calc(100% - #{$gutter * 1}); 54 | 55 | &.half { 56 | width: calc(100% - #{$gutter * 1}); 57 | } 58 | 59 | &.third { 60 | width: calc(100% - #{$gutter * 1}); 61 | } 62 | 63 | &.quarter { 64 | width: calc(100% - #{$gutter * 1}); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | label { 72 | color: _palette(fg-bold); 73 | font-weight: _font(weight-bold); 74 | line-height: 1.5; 75 | margin: 0 0 (_size(element-margin) * 0.35) 0; 76 | display: block; 77 | font-size: 1.1em; 78 | } 79 | 80 | input[type="text"], 81 | input[type="password"], 82 | input[type="email"], 83 | input[type="tel"], 84 | select, 85 | textarea { 86 | @include vendor('appearance', 'none'); 87 | background: _palette(border-bg); 88 | border-radius: _size(border-radius); 89 | border: none; 90 | border: solid _size(border-width) _palette(border); 91 | color: inherit; 92 | display: block; 93 | outline: 0; 94 | padding: 0 1em; 95 | text-decoration: none; 96 | width: 100%; 97 | 98 | &:invalid { 99 | box-shadow: none; 100 | } 101 | 102 | &:focus { 103 | border-color: _palette(fg-bold); 104 | box-shadow: 0 0 0 _size(border-width) _palette(fg-bold); 105 | } 106 | } 107 | 108 | select { 109 | background-image: svg-url(""); 110 | background-size: 1.25rem; 111 | background-repeat: no-repeat; 112 | background-position: calc(100% - 1rem) center; 113 | height: _size(element-height); 114 | padding-right: _size(element-height); 115 | text-overflow: ellipsis; 116 | 117 | option { 118 | color: _palette(fg-bold); 119 | background: _palette(bg); 120 | } 121 | 122 | &:focus { 123 | &::-ms-value { 124 | background-color: transparent; 125 | } 126 | } 127 | 128 | &::-ms-expand { 129 | display: none; 130 | } 131 | } 132 | 133 | input[type="text"], 134 | input[type="password"], 135 | input[type="email"], 136 | select { 137 | height: _size(element-height); 138 | } 139 | 140 | textarea { 141 | padding: 0.75em 1em; 142 | 143 | body.is-ie & { 144 | min-height: 10em; 145 | } 146 | } 147 | 148 | input[type="checkbox"], 149 | input[type="radio"], { 150 | @include vendor('appearance', 'none'); 151 | display: block; 152 | float: left; 153 | margin-right: -2em; 154 | opacity: 0; 155 | width: 1em; 156 | z-index: -1; 157 | 158 | & + label { 159 | @include icon; 160 | color: _palette(fg); 161 | cursor: pointer; 162 | display: inline-block; 163 | font-size: 1em; 164 | font-weight: _font(weight); 165 | padding-left: (_size(element-height) * 0.6) + 0.75em; 166 | padding-right: 0.75em; 167 | position: relative; 168 | 169 | &:before { 170 | background: _palette(border-bg); 171 | border-radius: _size(border-radius); 172 | border: solid _size(border-width) _palette(border); 173 | content: ''; 174 | display: inline-block; 175 | height: (_size(element-height) * 0.6); 176 | left: 0; 177 | line-height: (_size(element-height) * 0.575); 178 | position: absolute; 179 | text-align: center; 180 | top: 0; 181 | width: (_size(element-height) * 0.6); 182 | } 183 | } 184 | 185 | &:checked + label { 186 | &:before { 187 | background: _palette(fg-bold); 188 | border-color: _palette(fg-bold); 189 | color: _palette(accent3); 190 | content: '\f00c'; 191 | } 192 | } 193 | 194 | &:focus + label { 195 | &:before { 196 | border-color: _palette(fg-bold); 197 | box-shadow: 0 0 0 _size(border-width) _palette(fg-bold); 198 | } 199 | } 200 | } 201 | 202 | input[type="checkbox"] { 203 | & + label { 204 | &:before { 205 | border-radius: _size(border-radius); 206 | } 207 | } 208 | } 209 | 210 | input[type="radio"] { 211 | & + label { 212 | &:before { 213 | border-radius: 100%; 214 | } 215 | } 216 | } 217 | 218 | ::-webkit-input-placeholder { 219 | color: _palette(fg-light) !important; 220 | opacity: 1.0; 221 | } 222 | 223 | :-moz-placeholder { 224 | color: _palette(fg-light) !important; 225 | opacity: 1.0; 226 | } 227 | 228 | ::-moz-placeholder { 229 | color: _palette(fg-light) !important; 230 | opacity: 1.0; 231 | } 232 | 233 | :-ms-input-placeholder { 234 | color: _palette(fg-light) !important; 235 | opacity: 1.0; 236 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_icon.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Icon */ 8 | 9 | .icon { 10 | @include icon; 11 | border-bottom: none; 12 | position: relative; 13 | 14 | > .label { 15 | display: none; 16 | } 17 | 18 | &.major { 19 | width: 2.5em; 20 | height: 2.5em; 21 | display: block; 22 | background: _palette(fg-bold); 23 | border-radius: 100%; 24 | color: _palette(bg); 25 | text-align: center; 26 | line-height: 2.5em; 27 | margin: 0 0 (_size(element-margin) * 0.65) 0; 28 | 29 | &:before { 30 | font-size: 1.25em; 31 | 32 | .wrapper.style1 & { 33 | color: _palette(accent1); 34 | } 35 | 36 | .wrapper.style1-alt & { 37 | color: _palette(accent1-alt); 38 | } 39 | 40 | .wrapper.style2 & { 41 | color: _palette(accent2); 42 | } 43 | 44 | .wrapper.style2-alt & { 45 | color: _palette(accent2-alt); 46 | } 47 | 48 | .wrapper.style3 & { 49 | color: _palette(accent3); 50 | } 51 | 52 | .wrapper.style3-alt & { 53 | color: _palette(accent3-alt); 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_icons.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Icons */ 8 | 9 | ul.icons { 10 | cursor: default; 11 | list-style: none; 12 | padding-left: 0; 13 | 14 | li { 15 | display: inline-block; 16 | padding: 0 0.75em 0 0; 17 | 18 | &:last-child { 19 | padding-right: 0; 20 | } 21 | 22 | > a, > span { 23 | border: 0; 24 | @include icon; 25 | 26 | .label { 27 | display: none; 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_image.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Image */ 8 | 9 | .image { 10 | border-radius: _size(border-radius); 11 | border: 0; 12 | display: inline-block; 13 | position: relative; 14 | 15 | img { 16 | border-radius: _size(border-radius); 17 | display: block; 18 | } 19 | 20 | &.left, 21 | &.right { 22 | max-width: 40%; 23 | 24 | img { 25 | width: 100%; 26 | } 27 | } 28 | 29 | &.left { 30 | float: left; 31 | margin: 0 1.5em 1em 0; 32 | top: 0.25em; 33 | } 34 | 35 | &.right { 36 | float: right; 37 | margin: 0 0 1em 1.5em; 38 | top: 0.25em; 39 | } 40 | 41 | &.fit { 42 | display: block; 43 | margin: 0 0 _size(element-margin) 0; 44 | width: 100%; 45 | 46 | img { 47 | width: 100%; 48 | } 49 | } 50 | 51 | &.main { 52 | display: block; 53 | margin: 0 0 (_size(element-margin) * 1.5) 0; 54 | width: 100%; 55 | 56 | img { 57 | width: 100%; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_list.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* List */ 8 | 9 | ol { 10 | list-style: decimal; 11 | margin: 0 0 _size(element-margin) 0; 12 | padding-left: 1.25em; 13 | 14 | li { 15 | padding-left: 0.25em; 16 | } 17 | } 18 | 19 | ul { 20 | list-style: disc; 21 | margin: 0 0 _size(element-margin) 0; 22 | padding-left: 1em; 23 | 24 | li { 25 | padding-left: 0.5em; 26 | } 27 | 28 | &.alt { 29 | list-style: none; 30 | padding-left: 0; 31 | 32 | li { 33 | border-top: solid _size(border-width) _palette(border); 34 | padding: 0.5em 0; 35 | 36 | &:first-child { 37 | border-top: 0; 38 | padding-top: 0; 39 | } 40 | } 41 | } 42 | } 43 | 44 | dl { 45 | margin: 0 0 _size(element-margin) 0; 46 | 47 | dt { 48 | display: block; 49 | font-weight: _font(weight-bold); 50 | margin: 0 0 (_size(element-margin) * 0.5) 0; 51 | } 52 | 53 | dd { 54 | margin-left: _size(element-margin); 55 | } 56 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_menu.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Menu */ 8 | 9 | ul.menu { 10 | list-style: none; 11 | padding: 0; 12 | 13 | > li { 14 | border-left: solid 1px _palette(border); 15 | display: inline-block; 16 | line-height: 1; 17 | margin-left: 1.5em; 18 | padding: 0 0 0 1.5em; 19 | 20 | &:first-child { 21 | border-left: 0; 22 | margin: 0; 23 | padding-left: 0; 24 | } 25 | } 26 | 27 | @include breakpoint('<=xsmall') { 28 | > li { 29 | border-left: 0; 30 | display: block; 31 | line-height: inherit; 32 | margin: 0.5em 0 0 0; 33 | padding-left: 0; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_row.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Row */ 8 | 9 | .row { 10 | @include html-grid(1.5em); 11 | 12 | @include breakpoint('<=xlarge') { 13 | @include html-grid(1.5em, 'xlarge'); 14 | } 15 | 16 | @include breakpoint('<=large') { 17 | @include html-grid(1.5em, 'large'); 18 | } 19 | 20 | @include breakpoint('<=medium') { 21 | @include html-grid(1.5em, 'medium'); 22 | } 23 | 24 | @include breakpoint('<=small') { 25 | @include html-grid(1.5em, 'small'); 26 | } 27 | 28 | @include breakpoint('<=xsmall') { 29 | @include html-grid(1.5em, 'xsmall'); 30 | } 31 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_section.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Section/Article */ 8 | 9 | section, article { 10 | &.special { 11 | text-align: center; 12 | } 13 | } 14 | 15 | header { 16 | p { 17 | color: _palette(fg-light); 18 | position: relative; 19 | margin: 0 0 (_size(element-margin) * 0.75) 0; 20 | } 21 | 22 | h2 + p { 23 | font-size: 1.25em; 24 | margin-top: (_size(element-margin) * -0.5); 25 | line-height: 1.5em; 26 | } 27 | 28 | h3 + p { 29 | font-size: 1.1em; 30 | margin-top: (_size(element-margin) * -0.4); 31 | line-height: 1.5em; 32 | } 33 | 34 | h4 + p, 35 | h5 + p, 36 | h6 + p { 37 | font-size: 0.9em; 38 | margin-top: (_size(element-margin) * -0.3); 39 | line-height: 1.5em; 40 | } 41 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_split.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Split */ 8 | 9 | .split { 10 | @include vendor('display', 'flex'); 11 | 12 | > * { 13 | width: calc(50% - 2.5em); 14 | } 15 | 16 | > :nth-child(2n - 1) { 17 | padding-right: 2.5em; 18 | border-right: solid 1px _palette(border); 19 | } 20 | 21 | > :nth-child(2n) { 22 | padding-left: 2.5em; 23 | } 24 | 25 | &.style1 { 26 | > :nth-child(2n - 1) { 27 | width: calc(66.66666% - 2.5em); 28 | } 29 | 30 | > :nth-child(2n) { 31 | width: calc(33.33333% - 2.5em); 32 | } 33 | } 34 | 35 | @include breakpoint('<=xlarge') { 36 | > * { 37 | width: calc(50% - 2em); 38 | } 39 | 40 | > :nth-child(2n - 1) { 41 | padding-right: 2em; 42 | } 43 | 44 | > :nth-child(2n) { 45 | padding-left: 2em; 46 | } 47 | 48 | &.style1 { 49 | > :nth-child(2n - 1) { 50 | width: calc(66.66666% - 2em); 51 | } 52 | 53 | > :nth-child(2n) { 54 | width: calc(33.33333% - 2em); 55 | } 56 | } 57 | } 58 | 59 | @include breakpoint('<=medium') { 60 | display: block; 61 | 62 | > * { 63 | border-top: solid 1px _palette(border); 64 | margin: 4em 0 0 0; 65 | padding: 4em 0 0 0; 66 | width: 100% !important; 67 | } 68 | 69 | > :nth-child(2n - 1) { 70 | border-right: 0; 71 | padding-right: 0; 72 | } 73 | 74 | > :nth-child(2n) { 75 | padding-left: 0; 76 | } 77 | 78 | > :first-child { 79 | border-top: 0; 80 | margin-top: 0; 81 | padding-top: 0; 82 | } 83 | } 84 | 85 | @include breakpoint('<=small') { 86 | > * { 87 | margin: 3em 0 0 0; 88 | padding: 3em 0 0 0; 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_spotlights.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Spotlights */ 8 | 9 | .spotlights { 10 | > section { 11 | @include vendor('display', 'flex'); 12 | @include vendor('flex-direction', 'row'); 13 | min-height: 22.5em; 14 | 15 | body.is-ie & { 16 | min-height: 0; 17 | } 18 | 19 | > .image { 20 | background-position: center center; 21 | background-size: cover; 22 | border-radius: 0; 23 | display: block; 24 | position: relative; 25 | width: 25em; 26 | 27 | img { 28 | border-radius: 0; 29 | display: block; 30 | } 31 | 32 | &:before { 33 | @include vendor('transition', 'opacity #{_duration(activation)} ease'); 34 | background: transparentize(_palette(bg), 0.1); 35 | content: ''; 36 | display: block; 37 | height: 100%; 38 | left: 0; 39 | opacity: 0; 40 | position: absolute; 41 | top: 0; 42 | width: 100%; 43 | } 44 | } 45 | 46 | > .content { 47 | @include padding(4em, 5em); 48 | @include vendor('display', 'flex'); 49 | @include vendor('flex-direction', 'column'); 50 | @include vendor('justify-content', 'center'); 51 | width: #{_size(inner-width) - 25em}; 52 | -ms-flex: 1; 53 | 54 | > .inner { 55 | @include vendor('transform', 'translateX(0) translateY(0)'); 56 | @include vendor('transition', ( 57 | 'opacity #{_duration(activation)} ease', 58 | 'transform #{_duration(activation)} ease' 59 | )); 60 | opacity: 1; 61 | } 62 | } 63 | 64 | &:nth-child(1) { 65 | } 66 | 67 | &:nth-child(2) { 68 | background-color: rgba(0,0,0,0.05); 69 | } 70 | 71 | &:nth-child(3) { 72 | background-color: rgba(0,0,0,0.1); 73 | } 74 | 75 | &.inactive, 76 | body.is-preload & { 77 | > .image { 78 | &:before { 79 | opacity: 1; 80 | } 81 | } 82 | 83 | > .content { 84 | > .inner { 85 | @include vendor('transform', 'translateX(-1em)'); 86 | opacity: 0; 87 | } 88 | } 89 | } 90 | 91 | @include breakpoint('<=xlarge') { 92 | > .content { 93 | @include padding(4em, 4em); 94 | } 95 | } 96 | 97 | @include breakpoint('<=medium') { 98 | display: block; 99 | 100 | > .image { 101 | width: 100%; 102 | height: 50vh; 103 | } 104 | 105 | > .content { 106 | width: 100%; 107 | } 108 | 109 | &.inactive, 110 | body.is-preload & { 111 | > .content { 112 | > .inner { 113 | @include vendor('transform', 'translateY(1em)'); 114 | } 115 | } 116 | } 117 | } 118 | 119 | @include breakpoint('<=small') { 120 | > .image { 121 | height: 50vh; 122 | min-height: 15em; 123 | } 124 | 125 | > .content { 126 | @include padding(3em, 2em); 127 | } 128 | } 129 | } 130 | } 131 | 132 | -------------------------------------------------------------------------------- /public/assets/sass/components/_table.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Table */ 8 | 9 | .table-wrapper { 10 | -webkit-overflow-scrolling: touch; 11 | overflow-x: auto; 12 | } 13 | 14 | table { 15 | margin: 0 0 _size(element-margin) 0; 16 | width: 100%; 17 | 18 | tbody { 19 | tr { 20 | border: solid _size(border-width) _palette(border); 21 | border-left: 0; 22 | border-right: 0; 23 | 24 | &:nth-child(2n + 1) { 25 | background-color: _palette(border-bg); 26 | } 27 | } 28 | } 29 | 30 | td { 31 | padding: 0.75em 0.75em; 32 | } 33 | 34 | th { 35 | color: _palette(fg-bold); 36 | font-size: 1em; 37 | font-weight: _font(weight-bold); 38 | padding: 0 0.75em 0.75em 0.75em; 39 | text-align: left; 40 | } 41 | 42 | thead { 43 | border-bottom: solid (_size(border-width) * 2) _palette(border); 44 | } 45 | 46 | tfoot { 47 | border-top: solid (_size(border-width) * 2) _palette(border); 48 | } 49 | 50 | &.alt { 51 | border-collapse: separate; 52 | 53 | tbody { 54 | tr { 55 | td { 56 | border: solid _size(border-width) _palette(border); 57 | border-left-width: 0; 58 | border-top-width: 0; 59 | 60 | &:first-child { 61 | border-left-width: _size(border-width); 62 | } 63 | } 64 | 65 | &:first-child { 66 | td { 67 | border-top-width: _size(border-width); 68 | } 69 | } 70 | } 71 | } 72 | 73 | thead { 74 | border-bottom: 0; 75 | } 76 | 77 | tfoot { 78 | border-top: 0; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /public/assets/sass/components/_wrapper.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Wrapper */ 8 | 9 | .wrapper { 10 | position: relative; 11 | 12 | > .inner { 13 | @include padding(5em, 5em); 14 | max-width: 100%; 15 | width: _size(inner-width); 16 | 17 | @include breakpoint('<=xlarge') { 18 | @include padding(4em, 4em); 19 | } 20 | 21 | @include breakpoint('<=large') { 22 | width: 100%; 23 | } 24 | 25 | @include breakpoint('<=small') { 26 | @include padding(3em, 2em); 27 | } 28 | } 29 | 30 | &.alt { 31 | background-color: _palette(bg-alt); 32 | } 33 | 34 | &.style1 { 35 | background-color: _palette(accent1); 36 | } 37 | 38 | &.style1-alt { 39 | background-color: _palette(accent1-alt); 40 | } 41 | 42 | &.style2 { 43 | background-color: _palette(accent2); 44 | } 45 | 46 | &.style2-alt { 47 | background-color: _palette(accent2-alt); 48 | } 49 | 50 | &.style3 { 51 | background-color: _palette(accent3); 52 | } 53 | 54 | &.style3-alt { 55 | background-color: _palette(accent3-alt); 56 | } 57 | 58 | &.fullscreen { 59 | @include vendor('display', 'flex'); 60 | @include vendor('flex-direction', 'column'); 61 | @include vendor('justify-content', 'center'); 62 | min-height: 100vh; 63 | 64 | body.is-ie & { 65 | height: 100vh; 66 | } 67 | 68 | @include breakpoint('<=large') { 69 | min-height: calc(100vh - 2.5em); 70 | 71 | body.is-ie & { 72 | height: calc(100vh - 2.5em); 73 | } 74 | } 75 | 76 | @include breakpoint('<=small') { 77 | padding: 2em 0; 78 | min-height: 0; 79 | 80 | body.is-ie & { 81 | height: auto; 82 | } 83 | } 84 | } 85 | 86 | &.fade-up { 87 | > .inner { 88 | @include vendor('transform', 'translateY(0)'); 89 | @include vendor('transition', ( 90 | 'opacity #{_duration(activation)} ease', 91 | 'transform #{_duration(activation)} ease' 92 | )); 93 | opacity: 1.0; 94 | } 95 | 96 | &.inactive, 97 | body.is-preload & { 98 | > .inner { 99 | opacity: 0; 100 | @include vendor('transform', 'translateY(1em)'); 101 | } 102 | } 103 | } 104 | 105 | &.fade-down { 106 | > .inner { 107 | @include vendor('transform', 'translateY(0)'); 108 | @include vendor('transition', ( 109 | 'opacity #{_duration(activation)} ease', 110 | 'transform #{_duration(activation)} ease' 111 | )); 112 | opacity: 1.0; 113 | } 114 | 115 | &.inactive, 116 | body.is-preload & { 117 | > .inner { 118 | opacity: 0; 119 | @include vendor('transform', 'translateY(-1em)'); 120 | } 121 | } 122 | } 123 | 124 | &.fade { 125 | > .inner { 126 | @include vendor('transition', ( 127 | 'opacity #{_duration(activation)} ease' 128 | )); 129 | opacity: 1.0; 130 | } 131 | 132 | &.inactive, 133 | body.is-preload & { 134 | > .inner { 135 | opacity: 0; 136 | } 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /public/assets/sass/layout/_footer.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Footer */ 8 | 9 | #footer { 10 | #sidebar + #wrapper + & { 11 | margin-left: _size(sidebar-width); 12 | 13 | @include breakpoint('<=large') { 14 | margin-left: 0; 15 | } 16 | } 17 | 18 | > .inner { 19 | a { 20 | border-bottom-color: _palette(border); 21 | 22 | &:hover { 23 | border-bottom-color: transparent; 24 | } 25 | } 26 | 27 | .menu { 28 | font-size: 0.8em; 29 | color: _palette(border); 30 | } 31 | } 32 | 33 | #header + #wrapper + & { 34 | > .inner { 35 | margin: 0 auto; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /public/assets/sass/layout/_header.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Header */ 8 | 9 | #header { 10 | @include vendor('display', 'flex'); 11 | background-color: _palette(accent1); 12 | cursor: default; 13 | padding: 1.75em 2em; 14 | 15 | > .title { 16 | border: 0; 17 | color: _palette(fg-bold); 18 | display: block; 19 | font-size: 1.25em; 20 | font-weight: _font(weight-bold); 21 | } 22 | 23 | > nav { 24 | @include vendor('flex', '1'); 25 | text-align: right; 26 | 27 | > ul { 28 | margin: 0; 29 | padding: 0; 30 | 31 | > li { 32 | display: inline-block; 33 | margin-left: 1.75em; 34 | padding: 0; 35 | vertical-align: middle; 36 | 37 | &:first-child { 38 | margin-left: 0; 39 | } 40 | 41 | a { 42 | border: 0; 43 | color: _palette(fg-light); 44 | display: inline-block; 45 | font-size: 0.6em; 46 | font-weight: _font(weight-bold); 47 | letter-spacing: _font(kerning-alt); 48 | text-transform: uppercase; 49 | 50 | &:hover { 51 | color: _palette(fg); 52 | } 53 | 54 | &.active { 55 | color: _palette(fg-bold); 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | @include breakpoint('<=small') { 63 | padding: 1em 2em; 64 | } 65 | 66 | @include breakpoint('<=xsmall') { 67 | display: block; 68 | padding: 0 2em; 69 | text-align: left; 70 | 71 | .title { 72 | font-size: 1.25em; 73 | padding: 1em 0; 74 | } 75 | 76 | > nav { 77 | border-top: solid 1px _palette(border); 78 | text-align: inherit; 79 | 80 | > ul { 81 | > li { 82 | margin-left: 1.5em; 83 | 84 | a { 85 | height: 6em; 86 | line-height: 6em; 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /public/assets/sass/layout/_intro.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Intro */ 8 | 9 | #intro { 10 | background-attachment: fixed; 11 | background-image: url('images/intro.svg'); 12 | background-position: top right; 13 | background-repeat: no-repeat; 14 | background-size: 100% 100%; 15 | 16 | p { 17 | font-size: 1.25em; 18 | 19 | @include breakpoint('<=medium') { 20 | br { 21 | display: none; 22 | } 23 | } 24 | 25 | @include breakpoint('<=small') { 26 | font-size: 1em; 27 | } 28 | } 29 | 30 | @include breakpoint('<=large') { 31 | background-attachment: scroll; 32 | } 33 | } -------------------------------------------------------------------------------- /public/assets/sass/layout/_sidebar.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Sidebar */ 8 | 9 | #sidebar { 10 | @include padding(2.5em, 2.5em); 11 | background: _palette(bg); 12 | cursor: default; 13 | height: 100vh; 14 | left: 0; 15 | overflow-x: hidden; 16 | overflow-y: auto; 17 | position: fixed; 18 | text-align: right; 19 | top: 0; 20 | width: _size(sidebar-width); 21 | z-index: _misc(z-index-base); 22 | 23 | > .inner { 24 | @include vendor('display', 'flex'); 25 | @include vendor('flex-direction', 'column'); 26 | @include vendor('justify-content', 'center'); 27 | @include vendor('transform', 'translateY(0)'); 28 | @include vendor('transition', ( 29 | 'opacity #{_duration(activation)} ease', 30 | )); 31 | min-height: 100%; 32 | opacity: 1; 33 | width: 100%; 34 | 35 | body.is-ie & { 36 | height: 100%; 37 | } 38 | } 39 | 40 | nav { 41 | > ul { 42 | list-style: none; 43 | padding: 0; 44 | 45 | > li { 46 | @include vendor('transform', 'translateY(0)'); 47 | @include vendor('transition', ( 48 | 'opacity #{_duration(activation) * 0.15} ease', 49 | 'transform #{_duration(activation) * 0.75} ease' 50 | )); 51 | margin: 1.5em 0 0 0; 52 | opacity: 1; 53 | padding: 0; 54 | position: relative; 55 | 56 | &:first-child { 57 | margin: 0; 58 | } 59 | 60 | @for $i from 1 through _misc(max-sidebar-links) { 61 | &:nth-child(#{$i}) { 62 | @include vendor('transition-delay', '#{(_duration(activation) * 0.2 * $i) + (_duration(activation) * 0.25)}'); 63 | } 64 | } 65 | } 66 | } 67 | 68 | a { 69 | @include vendor('transition', 'color #{_duration(transition)} ease'); 70 | border: 0; 71 | color: _palette(fg-light); 72 | display: block; 73 | font-size: 0.6em; 74 | font-weight: _font(weight-bold); 75 | letter-spacing: _font(kerning-alt); 76 | line-height: 1.75; 77 | outline: 0; 78 | padding: 1.35em 0; 79 | position: relative; 80 | text-decoration: none; 81 | text-transform: uppercase; 82 | 83 | &:before, 84 | &:after { 85 | border-radius: 0.2em; 86 | bottom: 0; 87 | content: ''; 88 | height: 0.2em; 89 | position: absolute; 90 | right: 0; 91 | width: 100%; 92 | } 93 | 94 | &:before { 95 | background: lighten(_palette(bg), 5); 96 | } 97 | 98 | &:after { 99 | @include vendor('background-image', 'linear-gradient(to right, #{_palette(accent1)}, #{_palette(accent3)})'); 100 | @include vendor('transition', 'max-width #{_duration(transition)} ease'); 101 | max-width: 0; 102 | } 103 | 104 | &:hover { 105 | color: _palette(fg); 106 | } 107 | 108 | &.active { 109 | color: _palette(fg-bold); 110 | 111 | &:after { 112 | max-width: 100%; 113 | } 114 | } 115 | } 116 | } 117 | 118 | body.is-preload & { 119 | > .inner { 120 | opacity: 0; 121 | } 122 | 123 | nav { 124 | ul { 125 | li { 126 | @include vendor('transform', 'translateY(2em)'); 127 | opacity: 0; 128 | } 129 | } 130 | } 131 | } 132 | 133 | @include breakpoint('<=large') { 134 | height: _size(sidebar-height); 135 | left: 0; 136 | line-height: _size(sidebar-height); 137 | overflow: hidden; 138 | padding: 0; 139 | text-align: center; 140 | top: 0; 141 | width: 100%; 142 | 143 | > .inner { 144 | @include vendor('flex-direction', 'row'); 145 | @include vendor('align-items', 'stretch'); 146 | height: inherit; 147 | line-height: inherit; 148 | } 149 | 150 | nav { 151 | height: inherit; 152 | line-height: inherit; 153 | 154 | ul { 155 | @include vendor('display', 'flex'); 156 | height: inherit; 157 | line-height: inherit; 158 | margin: 0; 159 | 160 | li { 161 | display: block; 162 | height: inherit; 163 | line-height: inherit; 164 | margin: 0 0 0 2em; 165 | padding: 0; 166 | } 167 | } 168 | 169 | a { 170 | height: inherit; 171 | line-height: inherit; 172 | padding: 0; 173 | 174 | &:after { 175 | background-image: none; 176 | background-color: _palette(accent3); 177 | } 178 | } 179 | } 180 | } 181 | 182 | @include breakpoint('<=small') { 183 | display: none; 184 | } 185 | } -------------------------------------------------------------------------------- /public/assets/sass/layout/_wrapper.scss: -------------------------------------------------------------------------------- 1 | /// 2 | /// Hyperspace by HTML5 UP 3 | /// html5up.net | @ajlkn 4 | /// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | /// 6 | 7 | /* Wrapper (main) */ 8 | 9 | #wrapper { 10 | #sidebar + & { 11 | margin-left: _size(sidebar-width); 12 | 13 | @include breakpoint('<=large') { 14 | margin-left: 0; 15 | padding-top: _size(sidebar-height); 16 | } 17 | 18 | @include breakpoint('<=small') { 19 | padding-top: 0; 20 | } 21 | } 22 | 23 | #header + & { 24 | > .wrapper { 25 | > .inner { 26 | margin: 0 auto; 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /public/assets/sass/libs/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | // breakpoints.scss v1.0 | @ajlkn | MIT licensed */ 2 | 3 | // Vars. 4 | 5 | /// Breakpoints. 6 | /// @var {list} 7 | $breakpoints: () !global; 8 | 9 | // Mixins. 10 | 11 | /// Sets breakpoints. 12 | /// @param {map} $x Breakpoints. 13 | @mixin breakpoints($x: ()) { 14 | $breakpoints: $x !global; 15 | } 16 | 17 | /// Wraps @content in a @media block targeting a specific orientation. 18 | /// @param {string} $orientation Orientation. 19 | @mixin orientation($orientation) { 20 | @media screen and (orientation: #{$orientation}) { 21 | @content; 22 | } 23 | } 24 | 25 | /// Wraps @content in a @media block using a given query. 26 | /// @param {string} $query Query. 27 | @mixin breakpoint($query: null) { 28 | 29 | $breakpoint: null; 30 | $op: null; 31 | $media: null; 32 | 33 | // Determine operator, breakpoint. 34 | 35 | // Greater than or equal. 36 | @if (str-slice($query, 0, 2) == '>=') { 37 | 38 | $op: 'gte'; 39 | $breakpoint: str-slice($query, 3); 40 | 41 | } 42 | 43 | // Less than or equal. 44 | @elseif (str-slice($query, 0, 2) == '<=') { 45 | 46 | $op: 'lte'; 47 | $breakpoint: str-slice($query, 3); 48 | 49 | } 50 | 51 | // Greater than. 52 | @elseif (str-slice($query, 0, 1) == '>') { 53 | 54 | $op: 'gt'; 55 | $breakpoint: str-slice($query, 2); 56 | 57 | } 58 | 59 | // Less than. 60 | @elseif (str-slice($query, 0, 1) == '<') { 61 | 62 | $op: 'lt'; 63 | $breakpoint: str-slice($query, 2); 64 | 65 | } 66 | 67 | // Not. 68 | @elseif (str-slice($query, 0, 1) == '!') { 69 | 70 | $op: 'not'; 71 | $breakpoint: str-slice($query, 2); 72 | 73 | } 74 | 75 | // Equal. 76 | @else { 77 | 78 | $op: 'eq'; 79 | $breakpoint: $query; 80 | 81 | } 82 | 83 | // Build media. 84 | @if ($breakpoint and map-has-key($breakpoints, $breakpoint)) { 85 | 86 | $a: map-get($breakpoints, $breakpoint); 87 | 88 | // Range. 89 | @if (type-of($a) == 'list') { 90 | 91 | $x: nth($a, 1); 92 | $y: nth($a, 2); 93 | 94 | // Max only. 95 | @if ($x == null) { 96 | 97 | // Greater than or equal (>= 0 / anything) 98 | @if ($op == 'gte') { 99 | $media: 'screen'; 100 | } 101 | 102 | // Less than or equal (<= y) 103 | @elseif ($op == 'lte') { 104 | $media: 'screen and (max-width: ' + $y + ')'; 105 | } 106 | 107 | // Greater than (> y) 108 | @elseif ($op == 'gt') { 109 | $media: 'screen and (min-width: ' + ($y + 1) + ')'; 110 | } 111 | 112 | // Less than (< 0 / invalid) 113 | @elseif ($op == 'lt') { 114 | $media: 'screen and (max-width: -1px)'; 115 | } 116 | 117 | // Not (> y) 118 | @elseif ($op == 'not') { 119 | $media: 'screen and (min-width: ' + ($y + 1) + ')'; 120 | } 121 | 122 | // Equal (<= y) 123 | @else { 124 | $media: 'screen and (max-width: ' + $y + ')'; 125 | } 126 | 127 | } 128 | 129 | // Min only. 130 | @else if ($y == null) { 131 | 132 | // Greater than or equal (>= x) 133 | @if ($op == 'gte') { 134 | $media: 'screen and (min-width: ' + $x + ')'; 135 | } 136 | 137 | // Less than or equal (<= inf / anything) 138 | @elseif ($op == 'lte') { 139 | $media: 'screen'; 140 | } 141 | 142 | // Greater than (> inf / invalid) 143 | @elseif ($op == 'gt') { 144 | $media: 'screen and (max-width: -1px)'; 145 | } 146 | 147 | // Less than (< x) 148 | @elseif ($op == 'lt') { 149 | $media: 'screen and (max-width: ' + ($x - 1) + ')'; 150 | } 151 | 152 | // Not (< x) 153 | @elseif ($op == 'not') { 154 | $media: 'screen and (max-width: ' + ($x - 1) + ')'; 155 | } 156 | 157 | // Equal (>= x) 158 | @else { 159 | $media: 'screen and (min-width: ' + $x + ')'; 160 | } 161 | 162 | } 163 | 164 | // Min and max. 165 | @else { 166 | 167 | // Greater than or equal (>= x) 168 | @if ($op == 'gte') { 169 | $media: 'screen and (min-width: ' + $x + ')'; 170 | } 171 | 172 | // Less than or equal (<= y) 173 | @elseif ($op == 'lte') { 174 | $media: 'screen and (max-width: ' + $y + ')'; 175 | } 176 | 177 | // Greater than (> y) 178 | @elseif ($op == 'gt') { 179 | $media: 'screen and (min-width: ' + ($y + 1) + ')'; 180 | } 181 | 182 | // Less than (< x) 183 | @elseif ($op == 'lt') { 184 | $media: 'screen and (max-width: ' + ($x - 1) + ')'; 185 | } 186 | 187 | // Not (< x and > y) 188 | @elseif ($op == 'not') { 189 | $media: 'screen and (max-width: ' + ($x - 1) + '), screen and (min-width: ' + ($y + 1) + ')'; 190 | } 191 | 192 | // Equal (>= x and <= y) 193 | @else { 194 | $media: 'screen and (min-width: ' + $x + ') and (max-width: ' + $y + ')'; 195 | } 196 | 197 | } 198 | 199 | } 200 | 201 | // String. 202 | @else { 203 | 204 | // Missing a media type? Prefix with "screen". 205 | @if (str-slice($a, 0, 1) == '(') { 206 | $media: 'screen and ' + $a; 207 | } 208 | 209 | // Otherwise, use as-is. 210 | @else { 211 | $media: $a; 212 | } 213 | 214 | } 215 | 216 | } 217 | 218 | // Output. 219 | @media #{$media} { 220 | @content; 221 | } 222 | 223 | } -------------------------------------------------------------------------------- /public/assets/sass/libs/_functions.scss: -------------------------------------------------------------------------------- 1 | /// Removes a specific item from a list. 2 | /// @author Hugo Giraudel 3 | /// @param {list} $list List. 4 | /// @param {integer} $index Index. 5 | /// @return {list} Updated list. 6 | @function remove-nth($list, $index) { 7 | 8 | $result: null; 9 | 10 | @if type-of($index) != number { 11 | @warn "$index: #{quote($index)} is not a number for `remove-nth`."; 12 | } 13 | @else if $index == 0 { 14 | @warn "List index 0 must be a non-zero integer for `remove-nth`."; 15 | } 16 | @else if abs($index) > length($list) { 17 | @warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`."; 18 | } 19 | @else { 20 | 21 | $result: (); 22 | $index: if($index < 0, length($list) + $index + 1, $index); 23 | 24 | @for $i from 1 through length($list) { 25 | 26 | @if $i != $index { 27 | $result: append($result, nth($list, $i)); 28 | } 29 | 30 | } 31 | 32 | } 33 | 34 | @return $result; 35 | 36 | } 37 | 38 | /// Gets a value from a map. 39 | /// @author Hugo Giraudel 40 | /// @param {map} $map Map. 41 | /// @param {string} $keys Key(s). 42 | /// @return {string} Value. 43 | @function val($map, $keys...) { 44 | 45 | @if nth($keys, 1) == null { 46 | $keys: remove-nth($keys, 1); 47 | } 48 | 49 | @each $key in $keys { 50 | $map: map-get($map, $key); 51 | } 52 | 53 | @return $map; 54 | 55 | } 56 | 57 | /// Gets a duration value. 58 | /// @param {string} $keys Key(s). 59 | /// @return {string} Value. 60 | @function _duration($keys...) { 61 | @return val($duration, $keys...); 62 | } 63 | 64 | /// Gets a font value. 65 | /// @param {string} $keys Key(s). 66 | /// @return {string} Value. 67 | @function _font($keys...) { 68 | @return val($font, $keys...); 69 | } 70 | 71 | /// Gets a misc value. 72 | /// @param {string} $keys Key(s). 73 | /// @return {string} Value. 74 | @function _misc($keys...) { 75 | @return val($misc, $keys...); 76 | } 77 | 78 | /// Gets a palette value. 79 | /// @param {string} $keys Key(s). 80 | /// @return {string} Value. 81 | @function _palette($keys...) { 82 | @return val($palette, $keys...); 83 | } 84 | 85 | /// Gets a size value. 86 | /// @param {string} $keys Key(s). 87 | /// @return {string} Value. 88 | @function _size($keys...) { 89 | @return val($size, $keys...); 90 | } -------------------------------------------------------------------------------- /public/assets/sass/libs/_html-grid.scss: -------------------------------------------------------------------------------- 1 | // html-grid.scss v1.0 | @ajlkn | MIT licensed */ 2 | 3 | // Mixins. 4 | 5 | /// Initializes the current element as an HTML grid. 6 | /// @param {mixed} $gutters Gutters (either a single number to set both column/row gutters, or a list to set them individually). 7 | /// @param {mixed} $suffix Column class suffix (optional; either a single suffix or a list). 8 | @mixin html-grid($gutters: 1.5em, $suffix: '') { 9 | 10 | // Initialize. 11 | $cols: 12; 12 | $multipliers: 0, 0.25, 0.5, 1, 1.50, 2.00; 13 | $unit: 100% / $cols; 14 | 15 | // Suffixes. 16 | $suffixes: null; 17 | 18 | @if (type-of($suffix) == 'list') { 19 | $suffixes: $suffix; 20 | } 21 | @else { 22 | $suffixes: ($suffix); 23 | } 24 | 25 | // Gutters. 26 | $guttersCols: null; 27 | $guttersRows: null; 28 | 29 | @if (type-of($gutters) == 'list') { 30 | 31 | $guttersCols: nth($gutters, 1); 32 | $guttersRows: nth($gutters, 2); 33 | 34 | } 35 | @else { 36 | 37 | $guttersCols: $gutters; 38 | $guttersRows: 0; 39 | 40 | } 41 | 42 | // Row. 43 | display: flex; 44 | flex-wrap: wrap; 45 | box-sizing: border-box; 46 | align-items: stretch; 47 | 48 | // Columns. 49 | > * { 50 | box-sizing: border-box; 51 | } 52 | 53 | // Gutters. 54 | &.gtr-uniform { 55 | > * { 56 | > :last-child { 57 | margin-bottom: 0; 58 | } 59 | } 60 | } 61 | 62 | // Alignment. 63 | &.aln-left { 64 | justify-content: flex-start; 65 | } 66 | 67 | &.aln-center { 68 | justify-content: center; 69 | } 70 | 71 | &.aln-right { 72 | justify-content: flex-end; 73 | } 74 | 75 | &.aln-top { 76 | align-items: flex-start; 77 | } 78 | 79 | &.aln-middle { 80 | align-items: center; 81 | } 82 | 83 | &.aln-bottom { 84 | align-items: flex-end; 85 | } 86 | 87 | // Step through suffixes. 88 | @each $suffix in $suffixes { 89 | 90 | // Suffix. 91 | @if ($suffix != '') { 92 | $suffix: '-' + $suffix; 93 | } 94 | @else { 95 | $suffix: ''; 96 | } 97 | 98 | // Row. 99 | 100 | // Important. 101 | > .imp#{$suffix} { 102 | order: -1; 103 | } 104 | 105 | // Columns, offsets. 106 | @for $i from 1 through $cols { 107 | > .col-#{$i}#{$suffix} { 108 | width: $unit * $i; 109 | } 110 | 111 | > .off-#{$i}#{$suffix} { 112 | margin-left: $unit * $i; 113 | } 114 | } 115 | 116 | // Step through multipliers. 117 | @each $multiplier in $multipliers { 118 | 119 | // Gutters. 120 | $class: null; 121 | 122 | @if ($multiplier != 1) { 123 | $class: '.gtr-' + ($multiplier * 100); 124 | } 125 | 126 | &#{$class} { 127 | margin-top: ($guttersRows * $multiplier * -1); 128 | margin-left: ($guttersCols * $multiplier * -1); 129 | 130 | > * { 131 | padding: ($guttersRows * $multiplier) 0 0 ($guttersCols * $multiplier); 132 | } 133 | 134 | // Uniform. 135 | &.gtr-uniform { 136 | margin-top: $guttersCols * $multiplier * -1; 137 | 138 | > * { 139 | padding-top: $guttersCols * $multiplier; 140 | } 141 | } 142 | 143 | } 144 | 145 | } 146 | 147 | } 148 | 149 | } -------------------------------------------------------------------------------- /public/assets/sass/libs/_mixins.scss: -------------------------------------------------------------------------------- 1 | /// Makes an element's :before pseudoelement a FontAwesome icon. 2 | /// @param {string} $content Optional content value to use. 3 | /// @param {string} $where Optional pseudoelement to target (before or after). 4 | @mixin icon($content: false, $where: before) { 5 | 6 | text-decoration: none; 7 | 8 | &:#{$where} { 9 | 10 | @if $content { 11 | content: $content; 12 | } 13 | 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-font-smoothing: antialiased; 16 | font-family: FontAwesome; 17 | font-style: normal; 18 | font-weight: normal; 19 | text-transform: none !important; 20 | 21 | } 22 | 23 | } 24 | 25 | /// Applies padding to an element, taking the current element-margin value into account. 26 | /// @param {mixed} $tb Top/bottom padding. 27 | /// @param {mixed} $lr Left/right padding. 28 | /// @param {list} $pad Optional extra padding (in the following order top, right, bottom, left) 29 | /// @param {bool} $important If true, adds !important. 30 | @mixin padding($tb, $lr, $pad: (0,0,0,0), $important: null) { 31 | 32 | @if $important { 33 | $important: '!important'; 34 | } 35 | 36 | $x: 0.1em; 37 | 38 | @if unit(_size(element-margin)) == 'rem' { 39 | $x: 0.1rem; 40 | } 41 | 42 | padding: ($tb + nth($pad,1)) ($lr + nth($pad,2)) max($x, $tb - _size(element-margin) + nth($pad,3)) ($lr + nth($pad,4)) #{$important}; 43 | 44 | } 45 | 46 | /// Encodes a SVG data URL so IE doesn't choke (via codepen.io/jakob-e/pen/YXXBrp). 47 | /// @param {string} $svg SVG data URL. 48 | /// @return {string} Encoded SVG data URL. 49 | @function svg-url($svg) { 50 | 51 | $svg: str-replace($svg, '"', '\''); 52 | $svg: str-replace($svg, '%', '%25'); 53 | $svg: str-replace($svg, '<', '%3C'); 54 | $svg: str-replace($svg, '>', '%3E'); 55 | $svg: str-replace($svg, '&', '%26'); 56 | $svg: str-replace($svg, '#', '%23'); 57 | $svg: str-replace($svg, '{', '%7B'); 58 | $svg: str-replace($svg, '}', '%7D'); 59 | $svg: str-replace($svg, ';', '%3B'); 60 | 61 | @return url("data:image/svg+xml;charset=utf8,#{$svg}"); 62 | 63 | } -------------------------------------------------------------------------------- /public/assets/sass/libs/_vars.scss: -------------------------------------------------------------------------------- 1 | // Misc. 2 | $misc: ( 3 | z-index-base: 10000, 4 | max-features: 20, 5 | max-sidebar-links: 20 6 | ); 7 | 8 | // Duration. 9 | $duration: ( 10 | transition: 0.2s, 11 | activation: 1s 12 | ); 13 | 14 | // Size. 15 | $size: ( 16 | border-radius: 0.25em, 17 | border-width: 1px, 18 | element-height: 2.75em, 19 | element-margin: 2em, 20 | sidebar-width: 18em, 21 | sidebar-height: 3.5em, // when <=large is active 22 | inner-width: 75em 23 | ); 24 | 25 | // Font. 26 | $font: ( 27 | family: (Arial, Helvetica, sans-serif), 28 | family-fixed: ('Courier New', monospace), 29 | weight: normal, 30 | weight-bold: bold, 31 | kerning-alt: 0.25em 32 | ); 33 | 34 | // Palette. 35 | $palette: ( 36 | bg: #312450, 37 | bg-alt: darken(#312450, 5), 38 | fg: rgba(255,255,255,0.55), 39 | fg-bold: #ffffff, 40 | fg-light: rgba(255,255,255,0.35), 41 | border: rgba(255,255,255,0.15), 42 | border-bg: rgba(255,255,255,0.05), 43 | accent1: #5e42a6, 44 | accent1-alt: darken(#5e42a6, 10), 45 | accent2: #5052b5, 46 | accent2-alt: darken(#5052b5, 10), 47 | accent3: #b74e91, 48 | accent3-alt: darken(#b74e91, 10) 49 | ); -------------------------------------------------------------------------------- /public/assets/sass/libs/_vendor.scss: -------------------------------------------------------------------------------- 1 | // vendor.scss v1.0 | @ajlkn | MIT licensed */ 2 | 3 | // Vars. 4 | 5 | /// Vendor prefixes. 6 | /// @var {list} 7 | $vendor-prefixes: ( 8 | '-moz-', 9 | '-webkit-', 10 | '-ms-', 11 | '' 12 | ); 13 | 14 | /// Properties that should be vendorized. 15 | /// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org 16 | /// @var {list} 17 | $vendor-properties: ( 18 | 19 | // Animation. 20 | 'animation', 21 | 'animation-delay', 22 | 'animation-direction', 23 | 'animation-duration', 24 | 'animation-fill-mode', 25 | 'animation-iteration-count', 26 | 'animation-name', 27 | 'animation-play-state', 28 | 'animation-timing-function', 29 | 30 | // Appearance. 31 | 'appearance', 32 | 33 | // Backdrop filter. 34 | 'backdrop-filter', 35 | 36 | // Background image options. 37 | 'background-clip', 38 | 'background-origin', 39 | 'background-size', 40 | 41 | // Box sizing. 42 | 'box-sizing', 43 | 44 | // Clip path. 45 | 'clip-path', 46 | 47 | // Filter effects. 48 | 'filter', 49 | 50 | // Flexbox. 51 | 'align-content', 52 | 'align-items', 53 | 'align-self', 54 | 'flex', 55 | 'flex-basis', 56 | 'flex-direction', 57 | 'flex-flow', 58 | 'flex-grow', 59 | 'flex-shrink', 60 | 'flex-wrap', 61 | 'justify-content', 62 | 'order', 63 | 64 | // Font feature. 65 | 'font-feature-settings', 66 | 'font-language-override', 67 | 'font-variant-ligatures', 68 | 69 | // Font kerning. 70 | 'font-kerning', 71 | 72 | // Fragmented borders and backgrounds. 73 | 'box-decoration-break', 74 | 75 | // Grid layout. 76 | 'grid-column', 77 | 'grid-column-align', 78 | 'grid-column-end', 79 | 'grid-column-start', 80 | 'grid-row', 81 | 'grid-row-align', 82 | 'grid-row-end', 83 | 'grid-row-start', 84 | 'grid-template-columns', 85 | 'grid-template-rows', 86 | 87 | // Hyphens. 88 | 'hyphens', 89 | 'word-break', 90 | 91 | // Masks. 92 | 'mask', 93 | 'mask-border', 94 | 'mask-border-outset', 95 | 'mask-border-repeat', 96 | 'mask-border-slice', 97 | 'mask-border-source', 98 | 'mask-border-width', 99 | 'mask-clip', 100 | 'mask-composite', 101 | 'mask-image', 102 | 'mask-origin', 103 | 'mask-position', 104 | 'mask-repeat', 105 | 'mask-size', 106 | 107 | // Multicolumn. 108 | 'break-after', 109 | 'break-before', 110 | 'break-inside', 111 | 'column-count', 112 | 'column-fill', 113 | 'column-gap', 114 | 'column-rule', 115 | 'column-rule-color', 116 | 'column-rule-style', 117 | 'column-rule-width', 118 | 'column-span', 119 | 'column-width', 120 | 'columns', 121 | 122 | // Object fit. 123 | 'object-fit', 124 | 'object-position', 125 | 126 | // Regions. 127 | 'flow-from', 128 | 'flow-into', 129 | 'region-fragment', 130 | 131 | // Scroll snap points. 132 | 'scroll-snap-coordinate', 133 | 'scroll-snap-destination', 134 | 'scroll-snap-points-x', 135 | 'scroll-snap-points-y', 136 | 'scroll-snap-type', 137 | 138 | // Shapes. 139 | 'shape-image-threshold', 140 | 'shape-margin', 141 | 'shape-outside', 142 | 143 | // Tab size. 144 | 'tab-size', 145 | 146 | // Text align last. 147 | 'text-align-last', 148 | 149 | // Text decoration. 150 | 'text-decoration-color', 151 | 'text-decoration-line', 152 | 'text-decoration-skip', 153 | 'text-decoration-style', 154 | 155 | // Text emphasis. 156 | 'text-emphasis', 157 | 'text-emphasis-color', 158 | 'text-emphasis-position', 159 | 'text-emphasis-style', 160 | 161 | // Text size adjust. 162 | 'text-size-adjust', 163 | 164 | // Text spacing. 165 | 'text-spacing', 166 | 167 | // Transform. 168 | 'transform', 169 | 'transform-origin', 170 | 171 | // Transform 3D. 172 | 'backface-visibility', 173 | 'perspective', 174 | 'perspective-origin', 175 | 'transform-style', 176 | 177 | // Transition. 178 | 'transition', 179 | 'transition-delay', 180 | 'transition-duration', 181 | 'transition-property', 182 | 'transition-timing-function', 183 | 184 | // Unicode bidi. 185 | 'unicode-bidi', 186 | 187 | // User select. 188 | 'user-select', 189 | 190 | // Writing mode. 191 | 'writing-mode', 192 | 193 | ); 194 | 195 | /// Values that should be vendorized. 196 | /// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org 197 | /// @var {list} 198 | $vendor-values: ( 199 | 200 | // Cross fade. 201 | 'cross-fade', 202 | 203 | // Element function. 204 | 'element', 205 | 206 | // Filter function. 207 | 'filter', 208 | 209 | // Flexbox. 210 | 'flex', 211 | 'inline-flex', 212 | 213 | // Grab cursors. 214 | 'grab', 215 | 'grabbing', 216 | 217 | // Gradients. 218 | 'linear-gradient', 219 | 'repeating-linear-gradient', 220 | 'radial-gradient', 221 | 'repeating-radial-gradient', 222 | 223 | // Grid layout. 224 | 'grid', 225 | 'inline-grid', 226 | 227 | // Image set. 228 | 'image-set', 229 | 230 | // Intrinsic width. 231 | 'max-content', 232 | 'min-content', 233 | 'fit-content', 234 | 'fill', 235 | 'fill-available', 236 | 'stretch', 237 | 238 | // Sticky position. 239 | 'sticky', 240 | 241 | // Transform. 242 | 'transform', 243 | 244 | // Zoom cursors. 245 | 'zoom-in', 246 | 'zoom-out', 247 | 248 | ); 249 | 250 | // Functions. 251 | 252 | /// Removes a specific item from a list. 253 | /// @author Hugo Giraudel 254 | /// @param {list} $list List. 255 | /// @param {integer} $index Index. 256 | /// @return {list} Updated list. 257 | @function remove-nth($list, $index) { 258 | 259 | $result: null; 260 | 261 | @if type-of($index) != number { 262 | @warn "$index: #{quote($index)} is not a number for `remove-nth`."; 263 | } 264 | @else if $index == 0 { 265 | @warn "List index 0 must be a non-zero integer for `remove-nth`."; 266 | } 267 | @else if abs($index) > length($list) { 268 | @warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`."; 269 | } 270 | @else { 271 | 272 | $result: (); 273 | $index: if($index < 0, length($list) + $index + 1, $index); 274 | 275 | @for $i from 1 through length($list) { 276 | 277 | @if $i != $index { 278 | $result: append($result, nth($list, $i)); 279 | } 280 | 281 | } 282 | 283 | } 284 | 285 | @return $result; 286 | 287 | } 288 | 289 | /// Replaces a substring within another string. 290 | /// @author Hugo Giraudel 291 | /// @param {string} $string String. 292 | /// @param {string} $search Substring. 293 | /// @param {string} $replace Replacement. 294 | /// @return {string} Updated string. 295 | @function str-replace($string, $search, $replace: '') { 296 | 297 | $index: str-index($string, $search); 298 | 299 | @if $index { 300 | @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); 301 | } 302 | 303 | @return $string; 304 | 305 | } 306 | 307 | /// Replaces a substring within each string in a list. 308 | /// @param {list} $strings List of strings. 309 | /// @param {string} $search Substring. 310 | /// @param {string} $replace Replacement. 311 | /// @return {list} Updated list of strings. 312 | @function str-replace-all($strings, $search, $replace: '') { 313 | 314 | @each $string in $strings { 315 | $strings: set-nth($strings, index($strings, $string), str-replace($string, $search, $replace)); 316 | } 317 | 318 | @return $strings; 319 | 320 | } 321 | 322 | // Mixins. 323 | 324 | /// Wraps @content in vendorized keyframe blocks. 325 | /// @param {string} $name Name. 326 | @mixin keyframes($name) { 327 | 328 | @-moz-keyframes #{$name} { @content; } 329 | @-webkit-keyframes #{$name} { @content; } 330 | @-ms-keyframes #{$name} { @content; } 331 | @keyframes #{$name} { @content; } 332 | 333 | } 334 | 335 | /// Vendorizes a declaration's property and/or value(s). 336 | /// @param {string} $property Property. 337 | /// @param {mixed} $value String/list of value(s). 338 | @mixin vendor($property, $value) { 339 | 340 | // Determine if property should expand. 341 | $expandProperty: index($vendor-properties, $property); 342 | 343 | // Determine if value should expand (and if so, add '-prefix-' placeholder). 344 | $expandValue: false; 345 | 346 | @each $x in $value { 347 | @each $y in $vendor-values { 348 | @if $y == str-slice($x, 1, str-length($y)) { 349 | 350 | $value: set-nth($value, index($value, $x), '-prefix-' + $x); 351 | $expandValue: true; 352 | 353 | } 354 | } 355 | } 356 | 357 | // Expand property? 358 | @if $expandProperty { 359 | @each $vendor in $vendor-prefixes { 360 | #{$vendor}#{$property}: #{str-replace-all($value, '-prefix-', $vendor)}; 361 | } 362 | } 363 | 364 | // Expand just the value? 365 | @elseif $expandValue { 366 | @each $vendor in $vendor-prefixes { 367 | #{$property}: #{str-replace-all($value, '-prefix-', $vendor)}; 368 | } 369 | } 370 | 371 | // Neither? Treat them as a normal declaration. 372 | @else { 373 | #{$property}: #{$value}; 374 | } 375 | 376 | } -------------------------------------------------------------------------------- /public/assets/sass/main.scss: -------------------------------------------------------------------------------- 1 | @import 'libs/vars'; 2 | @import 'libs/functions'; 3 | @import 'libs/mixins'; 4 | @import 'libs/vendor'; 5 | @import 'libs/breakpoints'; 6 | @import 'libs/html-grid'; 7 | @import 'font-awesome.min.css'; 8 | 9 | /* 10 | Hyperspace by HTML5 UP 11 | html5up.net | @ajlkn 12 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 13 | */ 14 | 15 | // Breakpoints. 16 | 17 | @include breakpoints(( 18 | xlarge: ( 1281px, 1680px ), 19 | large: ( 981px, 1280px ), 20 | medium: ( 737px, 980px ), 21 | small: ( 481px, 736px ), 22 | xsmall: ( 361px, 480px ), 23 | xxsmall: ( null, 360px ) 24 | )); 25 | 26 | // Base. 27 | 28 | @import 'base/reset'; 29 | @import 'base/page'; 30 | @import 'base/typography'; 31 | 32 | // Component. 33 | 34 | @import 'components/row'; 35 | @import 'components/box'; 36 | @import 'components/button'; 37 | @import 'components/features'; 38 | @import 'components/form'; 39 | @import 'components/icon'; 40 | @import 'components/image'; 41 | @import 'components/list'; 42 | @import 'components/actions'; 43 | @import 'components/contact'; 44 | @import 'components/icons'; 45 | @import 'components/menu'; 46 | @import 'components/section'; 47 | @import 'components/split'; 48 | @import 'components/spotlights'; 49 | @import 'components/table'; 50 | @import 'components/wrapper'; 51 | 52 | // Layout. 53 | 54 | @import 'layout/header'; 55 | @import 'layout/wrapper'; 56 | @import 'layout/footer'; 57 | @import 'layout/sidebar'; 58 | @import 'layout/intro'; -------------------------------------------------------------------------------- /public/assets/sass/noscript.scss: -------------------------------------------------------------------------------- 1 | @import 'libs/vars'; 2 | @import 'libs/functions'; 3 | @import 'libs/mixins'; 4 | @import 'libs/vendor'; 5 | @import 'libs/breakpoints'; 6 | @import 'libs/html-grid'; 7 | 8 | /* 9 | Hyperspace by HTML5 UP 10 | html5up.net | @ajlkn 11 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 12 | */ 13 | 14 | /* Spotlights */ 15 | 16 | .spotlights { 17 | > section { 18 | > .image { 19 | &:before { 20 | opacity: 0 !important; 21 | } 22 | } 23 | 24 | > .content { 25 | > .inner { 26 | @include vendor('transform', 'none !important'); 27 | opacity: 1 !important; 28 | } 29 | } 30 | } 31 | } 32 | 33 | /* Wrapper */ 34 | 35 | .wrapper { 36 | > .inner { 37 | opacity: 1 !important; 38 | @include vendor('transform', 'none !important'); 39 | } 40 | } 41 | 42 | /* Sidebar */ 43 | 44 | #sidebar { 45 | > .inner { 46 | opacity: 1 !important; 47 | } 48 | 49 | nav { 50 | > ul { 51 | > li { 52 | @include vendor('transform', 'none !important'); 53 | opacity: 1 !important; 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /public/images/pic01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepfeiffer/node-express-azure/8a14daa1bcd45d126b4cf6fb30b5650aa0fba10d/public/images/pic01.jpg -------------------------------------------------------------------------------- /public/images/pic02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepfeiffer/node-express-azure/8a14daa1bcd45d126b4cf6fb30b5650aa0fba10d/public/images/pic02.jpg -------------------------------------------------------------------------------- /public/images/pic03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepfeiffer/node-express-azure/8a14daa1bcd45d126b4cf6fb30b5650aa0fba10d/public/images/pic03.jpg -------------------------------------------------------------------------------- /public/images/pic04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepfeiffer/node-express-azure/8a14daa1bcd45d126b4cf6fb30b5650aa0fba10d/public/images/pic04.jpg -------------------------------------------------------------------------------- /public/images/pic05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepfeiffer/node-express-azure/8a14daa1bcd45d126b4cf6fb30b5650aa0fba10d/public/images/pic05.jpg -------------------------------------------------------------------------------- /public/images/pic06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikepfeiffer/node-express-azure/8a14daa1bcd45d126b4cf6fb30b5650aa0fba10d/public/images/pic06.jpg -------------------------------------------------------------------------------- /routes/contact.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | /* GET contact page. */ 5 | router.get('/', (req, res) => { 6 | res.render('contact', { 7 | title: 'Contact Us', 8 | }); 9 | }); 10 | 11 | module.exports = router; -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', (req, res) => { 6 | res.render('index', { 7 | title: 'Welcome to GitHub Copilot Training!', 8 | }); 9 | }); 10 | 11 | module.exports = router; -------------------------------------------------------------------------------- /routes/who.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | /* GET "who we are" page. */ 5 | router.get('/', (req, res) => { 6 | res.render('who', { 7 | title: 'Who We Are', 8 | }); 9 | }); 10 | 11 | module.exports = router; -------------------------------------------------------------------------------- /sqltest.js: -------------------------------------------------------------------------------- 1 | const { Connection, Request } = require("tedious"); 2 | 3 | //Use Azure App Service Managed Identity to connect to the SQL database 4 | const config = { 5 | server: "pssqlserver1.database.windows.net", 6 | authentication: { 7 | type: 'azure-active-directory-msi-app-service', 8 | }, 9 | options: { 10 | database: "adventureworks", 11 | encrypt: true, 12 | port: 1433 13 | } 14 | }; 15 | 16 | const connection = new Connection(config); 17 | 18 | // Attempt to connect and execute queries if connection goes through 19 | connection.on("connect", err => { 20 | if (err) { 21 | console.error(err.message); 22 | } else { 23 | queryDatabase(); 24 | } 25 | }); 26 | 27 | connection.connect(); 28 | 29 | function queryDatabase() { 30 | console.log("Reading rows from the Table..."); 31 | 32 | // Read all rows from table 33 | const request = new Request( 34 | `SELECT TOP 20 pc.Name as CategoryName, 35 | p.name as ProductName 36 | FROM [SalesLT].[ProductCategory] pc 37 | JOIN [SalesLT].[Product] p ON pc.productcategoryid = p.productcategoryid`, 38 | (err, rowCount) => { 39 | if (err) { 40 | console.error(err.message); 41 | } else { 42 | console.log(`${rowCount} row(s) returned`); 43 | } 44 | } 45 | ); 46 | 47 | request.on("row", columns => { 48 | columns.forEach(column => { 49 | console.log("%s\t%s", column.metadata.colName, column.value); 50 | }); 51 | }); 52 | 53 | connection.execSql(request); 54 | } -------------------------------------------------------------------------------- /test/contact_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const config = require('../config'); 3 | const chai = require('chai'); 4 | const chaiHttp = require('chai-http'); 5 | const should = chai.should(); 6 | const server = require('../app'); 7 | 8 | chai.use(chaiHttp); 9 | 10 | describe('/GET', () => { 11 | it('returns the contact page', (done) => { 12 | chai.request(`http://localhost:${config.port}`) 13 | .get('/contact') 14 | .end((err, res) => { 15 | res.should.have.status(200); 16 | res.text.should.contain('Contact Us'); 17 | done(); 18 | }); 19 | }); 20 | }); -------------------------------------------------------------------------------- /test/index_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const config = require('../config'); 3 | const chai = require('chai'); 4 | const chaiHttp = require('chai-http'); 5 | const should = chai.should(); 6 | const server = require('../app'); 7 | 8 | chai.use(chaiHttp); 9 | 10 | describe('/GET', () => { 11 | it('returns the homepage', (done) => { 12 | chai.request(`http://localhost:${config.port}`) 13 | .get('/') 14 | .end((err, res) => { 15 | res.should.have.status(200); 16 | res.text.should.contain('Welcome to GitHub Copilot Training!'); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/who_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const config = require('../config'); 3 | const chai = require('chai'); 4 | const chaiHttp = require('chai-http'); 5 | const should = chai.should(); 6 | const server = require('../app'); 7 | 8 | chai.use(chaiHttp); 9 | 10 | describe('/GET', () => { 11 | it('returns the who page', (done) => { 12 | chai.request(`http://localhost:${config.port}`) 13 | .get('/who') 14 | .end((err, res) => { 15 | res.should.have.status(200); 16 | res.text.should.contain('Who We Are'); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /views/contact.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{title}}

4 |

You didn't expect this form to actually work did you? This is just a demo app :)

5 |
6 |
7 |
8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 25 |
26 |
27 |
28 |
    29 |
  • 30 |

    Address

    31 | 12345 Somewhere Road #654
    32 | Nashville, TN 00000-0000
    33 | USA
    34 |
  • 35 |
  • 36 |

    Email

    37 | user@untitled.tld 38 |
  • 39 |
  • 40 |

    Phone

    41 | (000) 000-0000 42 |
  • 43 |
  • 44 |

    Social

    45 | 52 |
  • 53 |
54 |
55 |
56 |
57 |
-------------------------------------------------------------------------------- /views/index.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{title}}

4 |

This is a simple Node.js website courtesy of Mike Pfeiffer from CloudSkills.io. Thanks, Mike!

5 |

This is version 1.0.0 of this web application.

6 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /views/layouts/main.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 |
27 | 28 | {{{body}}} 29 | 30 |
31 | 32 | 33 |
34 |
35 | 38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /views/who.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{title}}

4 |

A February 26, 2010 Microsoft whitepaper examined "the potential outcomes of a pilot implementation of Google Apps from the vantage point of a hypothetical company" called Contoso Ltd.

5 |

We're actuaully not a real company.

6 | 9 |
10 |
--------------------------------------------------------------------------------