Compare commits

..

5 Commits

Author SHA1 Message Date
04b302625f Fixed mobile layout
All checks were successful
Build and publish / Build Container (push) Successful in 7s
Build and publish / Deploy Container (push) Successful in 3s
2026-06-14 21:50:44 -04:00
3098886938 Connect momentum
All checks were successful
Build and publish / Build Container (push) Successful in 10s
Build and publish / Deploy Container (push) Successful in 12s
2026-06-12 02:42:58 -04:00
7dda34ec19 Updated build.yaml
All checks were successful
Build and publish / Build Container (push) Successful in 8s
Build and publish / Deploy Container (push) Successful in 3s
2026-06-12 02:10:00 -04:00
f68213955e Updated logo
Some checks failed
Build and publish / Build Container (push) Failing after 4s
Build and publish / Deploy Container (push) Has been skipped
2026-06-12 02:05:20 -04:00
4a97d4bbd3 Updated website
Some checks failed
Build and publish / Build Container (push) Failing after 21s
Build and publish / Deploy Container (push) Has been skipped
2026-06-12 02:03:03 -04:00
46 changed files with 1112 additions and 21295 deletions

View File

@@ -1,16 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR

17
.github/issue_template/ai-refinement.md vendored Normal file
View File

@@ -0,0 +1,17 @@
---
name: "AI refinement"
about: "Use AI to refine ticket"
ref: "develop"
labels:
- Review/AI
---
Describe your request:
What are you trying to do and what's happening?
How can it be fixed or improved?
Any other useful information? Logs, screenshots, steps to reproduce?

30
.github/issue_template/bug.md vendored Normal file
View File

@@ -0,0 +1,30 @@
---
name: "Bug report"
about: "Encountered an issue"
ref: "develop"
labels:
- Kind/Bug
---
# Bug Report
<!-- Description of problem -->
I tried to ...
But instead ...
## Steps to Reproduce
<!-- How can developers replicate & test the problem? -->
1. ...
2. ...
3. ...
## Logs
<!-- Any logs that accompanied the error -->
```
```
## Screenshots
<!-- Screenshots (annotated if possible) of the problem -->

28
.github/issue_template/enhancement.md vendored Normal file
View File

@@ -0,0 +1,28 @@
---
name: "Enhancement"
about: "Request an enhancement"
ref: "develop"
labels:
- Kind/Enhancement
---
# Feature Request
<!-- Detailed description -->
As a...
I want to...
## Requirements
<!-- List of any requirements -->
- [ ] ...
- [ ] ...
- [ ] ...
## Notes
<!-- Any additional design considerations -->
...
## Mockups
<!-- Any visual aids -->

26
.github/issue_template/refactor.md vendored Normal file
View File

@@ -0,0 +1,26 @@
---
name: "Refactor"
about: "Refactor existing system"
ref: "develop"
labels:
- Kind/Refactor
---
# Refactor
<!-- Detailed description -->
The current...
Should be changed/updated to...
## Reasoning
- ...
## Scope
<!-- What systems will this impact -->
- ...
## Notes
<!-- Any additional design considerations -->
...

View File

@@ -1,5 +1,5 @@
## Description ## Description
<!-- Adition information & context --> <!-- Addition information & context -->
... ...
## Issues ## Issues
@@ -8,8 +8,6 @@
- owner/repo#___ - owner/repo#___
## Checklist ## Checklist
<!-- Compelte after creating PR --> <!-- Complete after creating PR -->
- [ ] Linked issues - [ ] Reviewed changes (or use `Review/AI` label)
- [ ] Reviewed changes
- [ ] Updated comments/documentation - [ ] Updated comments/documentation

45
.github/workflows/build.yaml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Build and publish
run-name: Build and publish
on:
push:
jobs:
container:
name: Build Container
runs-on: ubuntu-latest
container: docker
steps:
- name: Build Container
run: |
git clone -b "${{github.ref_name}}" "$(echo ${{github.server_url}}/${{github.repository}}.git | sed s%://%://${{github.token}}@% )" .
DOCKER_HUB=$([ -n "${{secrets.DOCKER_HUB_USER}}" ] && [ -n "${{secrets.DOCKER_HUB_TOKEN}}" ] && [ -n "${{secrets.DOCKER_HUB_IMAGE}}" ] && echo "true" || echo "false")
REGISTRY="$(echo "${{github.server_url}}" | sed -E 's|https?://||')"
docker login -u "${{github.repository_owner}}" -p "${{secrets.DEPLOY_TOKEN}}" "$REGISTRY"
if [ "$DOCKER_HUB" = "true" ]; then docker login -u "${{secrets.DOCKER_HUB_USER}}" -p "${{secrets.DOCKER_HUB_TOKEN}}" docker.io; fi
docker build -t "$REGISTRY/${{github.repository}}:${{github.ref_name}}" .
docker push "$REGISTRY/${{github.repository}}:${{github.ref_name}}"
if [ "$DOCKER_HUB" = "true" ]; then
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:${{github.ref_name}}"
docker push "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:${{github.ref_name}}"
fi
if [ "${{github.ref_name}}" = "master" ]; then
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "$REGISTRY/${{github.repository}}:latest"
docker push "$REGISTRY/${{github.repository}}:latest"
if [ "$DOCKER_HUB" = "true" ]; then
docker tag "$REGISTRY/${{github.repository}}:${{github.ref_name}}" "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:latest"
docker push "docker.io/${{secrets.DOCKER_HUB_IMAGE}}:latest"
fi
fi
deploy:
name: Deploy Container
runs-on: ubuntu-latest
container: node
needs:
- container
steps:
- name: Deploy
run: curl -X POST https://manage.zakscode.com/api/stacks/webhooks/d0f72ae1-b623-44ef-ac13-e87da1270a40

29
.github/workflows/code-review.yaml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Code review
on:
pull_request:
types: [opened, synchronize, reopened, labeled]
jobs:
review:
runs-on: ubuntu-latest
container: node:22
steps:
- name: Fetch code
run: |
git clone "$(echo ${{github.server_url}}/${{github.repository}}.git | sed s%://%://${{github.token}}@% )" .
git checkout ${{ github.event.pull_request.head.sha }}
git fetch origin ${{ github.event.pull_request.base.ref }}
- name: Create review
run: npx -y -p @ztimson/ai-agents@latest review $GITHUB_WORKSPACE
env:
AI_HOST: anthropic
AI_MODEL: claude-sonnet-4-5
AI_TOKEN: ${{ secrets.ANTHROPIC_TOKEN }}
GIT_HOST: ${{ github.server_url }}
GIT_OWNER: ${{ github.repository_owner }}
GIT_REPO: ${{ github.event.repository.name }}
GIT_TOKEN: ${{ secrets.ASSISTANT_TOKEN }}
GIT_BRANCH: origin/${{ github.event.pull_request.base.ref }}
PULL_REQUEST: ${{ github.event.pull_request.number }}

27
.github/workflows/release-creator.yaml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Release Bot
on:
milestone:
types: [closed]
jobs:
release:
runs-on: ubuntu-latest
container: node:22
steps:
- name: Checkout
run: |
git clone "$(echo ${{github.server_url}}/${{github.repository}}.git | sed s%://%://${{github.token}}@% )" .
git checkout ${{ github.event.repository.default_branch }}
- name: Create release
run: npx -y @ztimson/ai-agents@latest release
env:
AI_HOST: anthropic
AI_MODEL: claude-sonnet-4-5
AI_TOKEN: ${{ secrets.ANTHROPIC_TOKEN }}
GIT_HOST: ${{ github.server_url }}
GIT_OWNER: ${{ github.repository_owner }}
GIT_REPO: ${{ github.event.repository.name }}
GIT_TOKEN: ${{ secrets.ASSISTANT_TOKEN }}
MILESTONE: ${{ github.event.milestone.number }}

View File

@@ -0,0 +1,27 @@
name: Ticket refinement
on:
issues:
types: [labeled]
jobs:
format:
runs-on: ubuntu-latest
container: node:22
steps:
- name: Fetch code
run: |
git clone "$(echo ${{github.server_url}}/${{github.repository}}.git | sed s%://%://${{github.token}}@% )" .
git checkout ${{ github.event.repository.default_branch }}
- name: Refine ticket
run: npx -y -p @ztimson/ai-agents@latest refine
env:
AI_HOST: anthropic
AI_MODEL: claude-sonnet-4-5
AI_TOKEN: ${{ secrets.ANTHROPIC_TOKEN }}
GIT_HOST: ${{ github.server_url }}
GIT_OWNER: ${{ github.repository_owner }}
GIT_REPO: ${{ github.event.repository.name }}
GIT_TOKEN: ${{ secrets.ASSISTANT_TOKEN }}
TICKET: ${{ github.event.issue.number }}

View File

@@ -1,54 +0,0 @@
name: Build Website
run-name: Build Website
on:
push:
jobs:
build:
name: Build NPM Project
runs-on: ubuntu-latest
container: node
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Install Dependencies
run: npm i
- name: Build Project
run: npm run build
- name: Upload Artifacts
if: ${{inputs.artifacts}} != "false"
uses: actions/upload-artifact@v3
with:
name: website
path: dist
retention-days: 7
tag:
name: Tag Version
needs: build
runs-on: ubuntu-latest
container: node
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Get Version Number
run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
- name: Tag Version
uses: ztimson/actions/tag@develop
with:
tag: ${{env.VERSION}}
publish:
name: Build & Push Dockerfile
needs: build
uses: ztimson/actions/.github/workflows/docker.yaml@develop
with:
name: ztimson/291st
repository: ${{github.server_url}}/${{github.repository}}.git
pass: ${{secrets.DEPLOY_TOKEN}}

View File

@@ -1,28 +1,3 @@
FROM node:16 as build FROM git.zakscode.com/momentum/momentum:latest
# Variables COPY public /app/server/public
ARG NODE_ENV=prod
ARG NODE_OPTIONS="--max_old_space_size=4096"
ENV NG_CLI_ANALYTICS=ci \
NODE_ENV=${NODE_ENV} \
NODE_OPTIONS=${NODE_OPTIONS}
# Setup
RUN npm config set unsafe-perm true && \
mkdir /app
WORKDIR /app
COPY . .
# Install
RUN if [ ! -d "dist" ] && [ ! -d "node_modules" ]; then npm install; fi
# Build
RUN BUILD_MODE=$([ "$NODE_ENV" = "prod" ] && echo "prod" || echo "dev") && \
if [ ! -d "dist" ]; then npm run "build:$BUILD_MODE"; fi
# Use Nginx to serve
FROM nginx:1.20-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY docker/robots.txt /usr/share/nginx/html/robots.txt
COPY docker/nginx.conf /etc/nginx/nginx.conf
EXPOSE 80

View File

@@ -3,9 +3,8 @@
<br /> <br />
<!-- Logo --> <!-- Logo -->
<a href="/src/assets/img/header.png"> <img src="./public/assets/img/logo.png" alt="Logo" width="200" height="200">
<img src="./src/assets/img/logo.png" alt="Logo" width="200" height="200">
</a>
<!-- Title --> <!-- Title -->
### 291st Joint Task Force ### 291st Joint Task Force
@@ -44,19 +43,12 @@
This website was created for the _291st Joint Task Force_ to act as a simple landing page for their domain. This website was created for the _291st Joint Task Force_ to act as a simple landing page for their domain.
It includes a list of managed servers & the _Discord_ server list.
The technology stack consists of a front-end built with _Angular_ & is deployed using _Docker_.
### Demo ### Demo
Website: https://291st.ca Website: https://291st.ca
### Built With ### Built With
[![Angular](https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular)](https://angular.io/)
[![Bootstrap](https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white)](https://getbootstrap.com)
[![Docker](https://img.shields.io/badge/Docker-384d54?style=for-the-badge&logo=docker)](https://docker.com/) [![Docker](https://img.shields.io/badge/Docker-384d54?style=for-the-badge&logo=docker)](https://docker.com/)
[![Node](https://img.shields.io/badge/Node.js-000000?style=for-the-badge&logo=nodedotjs)](https://nodejs.org/) [![Momentum](https://img.shields.io/badge/Momentum-4c7bbc?style=for-the-badge)](https://momentum.zakscode.com)
[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://typescriptlang.org/)
## Setup ## Setup

View File

@@ -1,91 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"291st": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [ ]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "291st:build:production"
},
"development": {
"browserTarget": "291st:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "291st:build"
}
}
}
}
},
"cli": {
"analytics": false
}
}

View File

@@ -1,31 +0,0 @@
worker_processes auto;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
gzip on;
gzip_proxied any;
gzip_types text/plain text/css application/xml application/xhtml+xml application/rss+xml application/javascript application/x-javascript application/json application/x-font-woff;
gzip_vary on;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
sendfile off;
keepalive_timeout 65;
server {
listen 80;
index index.html;
root /usr/share/nginx/html;
autoindex off;
location / {
try_files $uri $uri/ /index.html;
}
}
}

View File

@@ -1,2 +0,0 @@
User-Agent: *
Allow: /

20566
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,42 +0,0 @@
{
"name": "291st",
"version": "2.0.0",
"scripts": {
"start": "npx ng serve",
"build": "npm run build:dev",
"build:dev": "npx ng build --configuration development",
"build:prod": "npx ng build --configuration production"
},
"private": true,
"dependencies": {
"@angular/animations": "^14.2.0",
"@angular/cdk": "^14.2.2",
"@angular/common": "^14.2.0",
"@angular/compiler": "^14.2.0",
"@angular/core": "^14.2.0",
"@angular/forms": "^14.2.0",
"@angular/material": "^14.2.2",
"@angular/platform-browser": "^14.2.0",
"@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0",
"bootstrap": "^5.2.1",
"jquery": "^3.6.1",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"webstorage-decorators": "^4.2.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.2.2",
"@angular/cli": "~14.2.2",
"@angular/compiler-cli": "^14.2.0",
"@types/jasmine": "~4.0.0",
"jasmine-core": "~4.3.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~4.7.2"
}
}

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 MiB

After

Width:  |  Height:  |  Size: 7.0 MiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

564
public/index.html Normal file
View File

@@ -0,0 +1,564 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="291st Joint Task Force">
<meta name="author" content="Zak Timson">
<title>291st JTF</title>
<link href="https://fonts.googleapis.com/css2?family=Saira+Stencil+One&family=Roboto:wght@300;400;500&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<script src="login.mjs" type="module" defer></script>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--red: #b10000;
--red-hover: #c00;
--dark: #202225;
--black: #000;
--max-w: 1100px;
--theme-primary: var(--red);
--theme-primary-contrast: white;
--theme-accent: var(--red);
--theme-accent-contrast: white;
}
html, body {
background: var(--black);
color: #fff;
font-family: Roboto, sans-serif;
overflow-x: hidden;
}
.stencil { font-family: 'Saira Stencil One', sans-serif; }
/* ── NAV ── */
nav {
position: sticky;
top: 0;
z-index: 100;
background: var(--dark);
border-bottom: 2px solid var(--red);
}
nav .inner {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25rem;
padding: 0 2rem;
}
nav a {
color: #aaa;
text-decoration: none;
padding: 0.75rem 0.75rem;
font-size: 0.8rem;
letter-spacing: 0.05em;
text-transform: uppercase;
border-bottom: 3px solid transparent;
transition: color 0.2s, border-color 0.2s;
}
nav a:hover, nav a.active {
color: #fff;
border-bottom-color: var(--red);
}
/* ── SHARED LAYOUT ── */
.inner {
max-width: var(--max-w);
margin: 0 auto;
padding: 0;
}
.section-wrap {
padding: 4rem 0;
}
section {
padding: 4rem 2rem;
max-width: var(--max-w);
margin: 0 auto;
}
section h2 { font-size: 2rem; margin-bottom: 0.5rem; }
.section-divider {
width: 60px;
height: 3px;
background: var(--red);
margin-bottom: 2rem;
}
/* ── ABOUT ── */
#about p {
color: #ccc;
line-height: 1.8;
max-width: 720px;
}
.stats {
display: flex;
gap: 2rem;
margin-top: 2rem;
flex-wrap: wrap;
}
.stat {
background: var(--dark);
border-left: 3px solid var(--red);
padding: 1rem 1.5rem;
min-width: 140px;
}
.stat .num {
font-size: 2rem;
font-weight: 700;
color: var(--red);
}
.stat .label {
font-size: 0.8rem;
color: #aaa;
text-transform: uppercase;
letter-spacing: 0.1em;
}
/* ── MISSION SET ── */
#mission-set { background: #0a0a0a; }
.services-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
@media (max-width: 700px) {
.services-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
.services-grid { grid-template-columns: 1fr; }
}
.service-card {
background: var(--dark);
border-top: 3px solid transparent;
padding: 1.25rem;
display: flex;
align-items: flex-start;
gap: 1rem;
transition: border-color 0.2s, transform 0.2s;
}
.service-card:hover {
border-top-color: var(--red);
transform: translateY(-3px);
}
.service-card .icon {
font-size: 1.6rem;
flex-shrink: 0;
}
.service-card .text {
display: flex;
flex-direction: column;
}
.service-card .title { font-size: 1rem; font-weight: 500; margin-bottom: 0.25rem; }
.service-card .desc { font-size: 0.82rem; color: #888; line-height: 1.5; }
/* ── OPERATIONS ── */
.ops-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.op-item {
display: flex;
gap: 1.5rem;
align-items: flex-start;
background: var(--dark);
padding: 1.25rem 1.5rem;
border-left: 4px solid var(--red);
}
.op-item.upcoming { border-left-color: #4caf50; }
.op-item .op-date {
display: flex;
flex-direction: column;
gap: 0.2rem;
font-size: 0.75rem;
color: #888;
white-space: nowrap;
padding-top: 0.2rem;
min-width: 110px;
font-family: 'Share Tech Mono', monospace;
}
.op-item .op-date .op-time {
color: var(--red);
font-size: 0.7rem;
}
.op-item .op-title { font-weight: 500; margin-bottom: 0.25rem; }
.op-item .op-desc { font-size: 0.85rem; color: #aaa; }
.badge {
display: inline-block;
padding: 0.15rem 0.6rem;
font-size: 0.7rem;
border-radius: 2px;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-left: 0.5rem;
}
.badge-upcoming { background: #1a3a1a; color: #4caf50; }
.badge-completed { background: #1a1a2e; color: #888; }
/* ── DISCORD ── */
#discord { background: #050505; }
.discord-inner {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
align-items: start;
}
@media (max-width: 700px) { .discord-inner { grid-template-columns: 1fr; } }
.discord-inner iframe { width: 100%; min-height: 420px; border: none; }
.discord-info p {
color: #aaa;
line-height: 1.7;
margin-bottom: 1.5rem;
font-size: 0.95rem;
}
.btn {
display: inline-block;
padding: 0.75rem 1.75rem;
background: var(--red);
color: #fff;
text-decoration: none;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 0.85rem;
transition: background 0.2s;
}
.btn:hover { background: var(--red-hover); }
/* ── LOCATION ── */
#location { background: #080808; border-top: 1px solid #111; }
.location-inner {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 2rem;
align-items: center;
}
@media (max-width: 700px) { .location-inner { grid-template-columns: 1fr; } }
.location-info p {
color: #aaa;
line-height: 1.7;
font-size: 0.95rem;
margin-bottom: 0.5rem;
}
.location-info .detail {
font-family: 'Share Tech Mono', monospace;
font-size: 0.8rem;
color: #555;
margin-top: 1rem;
line-height: 1.8;
}
#map-wrap {
position: relative;
width: 100%;
aspect-ratio: 16/9;
overflow: hidden;
border: 1px solid #1a1a1a;
}
#map-wrap iframe {
width: 100%;
height: 100%;
border: none;
filter: grayscale(1) invert(1) brightness(0.8) contrast(1.1);
}
.map-pin {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -100%);
z-index: 2;
pointer-events: none;
display: flex;
flex-direction: column;
align-items: center;
}
.map-pin .pin-dot {
width: 14px; height: 14px;
background: var(--red);
border-radius: 50%;
box-shadow: 0 0 0 3px rgba(177,0,0,0.3);
animation: pulse 2s infinite;
}
.map-pin .pin-line { width: 2px; height: 20px; background: var(--red); }
.map-pin .pin-label {
background: var(--red);
color: #fff;
font-size: 0.65rem;
font-family: 'Share Tech Mono', monospace;
letter-spacing: 0.1em;
padding: 0.2rem 0.5rem;
white-space: nowrap;
margin-top: 4px;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 3px rgba(177,0,0,0.3); }
50% { box-shadow: 0 0 0 8px rgba(177,0,0,0.1); }
}
/* ── FOOTER ── */
footer {
background: var(--dark);
border-top: 2px solid #111;
text-align: center;
padding: 1.5rem;
font-size: 0.85rem;
color: #666;
}
footer a, footer a:visited { color: var(--red); text-decoration: none; }
footer a:hover { color: var(--red-hover); }
</style>
</head>
<body>
<jtf-login></jtf-login>
<nav id="main-nav">
<div class="inner">
<a href="#about">About</a>
<a href="#mission-set">Mission Set</a>
<a href="#operations">Operations</a>
<a href="#discord">Discord</a>
</div>
</nav>
<!-- ABOUT -->
<section id="about">
<h2 class="stencil">About</h2>
<div class="section-divider"></div>
<p>
The 291st Joint Task Force is a casual group of gamers focused on tactical & strategical cooperative play.
We operate across multiple titles and timezones We run weekly operations and "book clubs" to enjoy singplayer games together.
If youre looking for friends to enjoy games with on a regular baisis, you came to the right place. We're recruting.
</p>
<div class="stats">
<div class="stat"><div class="num">291st</div><div class="label">Unit</div></div>
<div class="stat"><div class="num">15+</div><div class="label">Members</div></div>
<div class="stat"><div class="num">2015</div><div class="label">Founded</div></div>
<div class="stat"><div class="num">1,000+</div><div class="label">Missions</div></div>
</div>
</section>
<!-- MISSION SET -->
<div id="mission-set" class="section-wrap">
<div class="inner">
<h2 class="stencil">Mission Set</h2>
<div class="section-divider"></div>
<div class="services-grid">
<div class="service-card">
<div class="icon">🎮</div>
<div class="text">
<div class="title">Wide Mission Set</div>
<div class="desc">Solo, Shooters, Strategy and MMO's</div>
</div>
</div>
<div class="service-card">
<div class="icon">📅</div>
<div class="text">
<div class="title">Weekly Operations</div>
<div class="desc">Maintain high readiness at all times.</div>
</div>
</div>
<div class="service-card">
<div class="icon">🖥️</div>
<div class="text">
<div class="title">24/7 Servers</div>
<div class="desc">Requisition servers for any game.</div>
</div>
</div>
<div class="service-card">
<div class="icon">🔒</div>
<div class="text">
<div class="title">VPN Access</div>
<div class="desc">Private VPN access for members.</div>
</div>
</div>
<div class="service-card">
<div class="icon">🎬</div>
<div class="text">
<div class="title">Private Media Server</div>
<div class="desc">REDACTED</div>
</div>
</div>
<div class="service-card">
<div class="icon">🌎</div>
<div class="text">
<div class="title">North American</div>
<div class="desc">EDT timezone — All regions welcome</div>
</div>
</div>
</div>
</div>
</div>
<!-- OPERATIONS -->
<section id="operations">
<h2 class="stencil">Operations</h2>
<div class="section-divider"></div>
<div class="ops-list" id="ops-list"></div>
</section>
<!-- DISCORD -->
<div id="discord" class="section-wrap">
<div class="inner">
<div class="discord-inner">
<div class="discord-info">
<h2 class="stencil">Join Us</h2>
<div class="section-divider"></div>
<p>
Our Discord is the nerve centre of the 291st. Briefings, op schedules, voice comms,
and unit announcements all live here. Active members are expected to stay connected.
</p>
<a class="btn" href="https://discord.gg/your-invite" target="_blank">Join Discord</a>
</div>
<iframe
src="https://discordapp.com/widget?id=399625240927404033&theme=dark"
allowtransparency="true"
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts">
</iframe>
</div>
</div>
</div>
<!-- LOCATION -->
<div id="location" class="section-wrap">
<div class="inner">
<div class="location-inner">
<div class="location-info">
<h2 class="stencil">Location</h2>
<div class="section-divider"></div>
<p>Based out of Ontario, Canada. Operating across North America on Eastern time.</p>
<div class="detail">
REGION: North America<br>
PROVINCE: Ontario, CA<br>
TIMEZONE: EDT / UTC-4<br>
OP TIME: Sun 14:3018:30
</div>
</div>
<div id="map-wrap">
<iframe
src="https://www.openstreetmap.org/export/embed.html?bbox=-130.0%2C25.0%2C-55.0%2C55.0&layer=mapnik&marker=43.6532%2C-79.3832"
style="pointer-events: none;"
loading="lazy">
</iframe>
</div>
</div>
</div>
</div>
<!-- FOOTER -->
<footer>
<p>Copyright &copy; 291st JTF 2025 &nbsp;|&nbsp; All Rights Reserved<br>
Created by <a href="https://zakscode.com" target="_blank">Zak Timson</a> | Built with <a href="https://momentum.zakscode.com" target="_blank">Momentum</a></p>
</footer>
<script type="module">
import {Momentum} from 'https://291st.com/momentum.mjs';
const momentum = window.momentum = new Momentum('https://291st.com', {app: 'Website'});
const session = momentum.auth.readSession();
</script>
<script>
// ── DYNAMIC OPS (newest first) ──
function getSundayOps() {
const now = new Date();
const day = now.getDay();
const lastSunday = new Date(now);
lastSunday.setDate(now.getDate() - day);
lastSunday.setHours(14, 30, 0, 0);
const todayEnd = new Date(now);
todayEnd.setHours(18, 30, 0, 0);
const upcomingDate = (day === 0 && now < todayEnd) ? lastSunday : new Date(lastSunday);
if (!(day === 0 && now < todayEnd)) upcomingDate.setDate(lastSunday.getDate() + 7);
const ops = [
{ date: upcomingDate, upcoming: true },
];
for (let i = 1; i <= 2; i++) {
const d = new Date(upcomingDate);
d.setDate(upcomingDate.getDate() - 7 * i);
ops.push({ date: d, upcoming: false });
}
return ops;
}
function fmtDate(d) {
return d.toLocaleDateString('en-CA', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' });
}
const list = document.getElementById('ops-list');
getSundayOps().forEach(op => {
const item = document.createElement('div');
item.className = 'op-item' + (op.upcoming ? ' upcoming' : '');
item.innerHTML = `
<div class="op-date">
<span>${fmtDate(op.date)}</span>
<span class="op-time">14:30 18:30 EDT</span>
</div>
<div>
<div class="op-title">
Weekly Operation — Sunday Afternoon
${op.upcoming ? `<span class="badge badge-upcoming">Upcoming</span>` : ''}
</div>
<div class="op-desc">${op.upcoming ? 'All squads report in, game TBD' : 'Operation Complete'}</div>
</div>
`;
list.appendChild(item);
});
// ── NAV HIGHLIGHT ──
const navLinks = document.querySelectorAll('nav a');
const observer = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting)
navLinks.forEach(a => a.classList.toggle('active', a.getAttribute('href') === '#' + e.target.id));
});
}, { threshold: 0.4 });
document.querySelectorAll('[id]').forEach(s => observer.observe(s));
</script>
</body>
</html>

311
public/login.mjs Normal file
View File

@@ -0,0 +1,311 @@
class JTFLogin extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: flex;
position: relative;
height: 100vh;
min-height: 500px;
align-items: center;
justify-content: center;
overflow: hidden;
background: radial-gradient(ellipse at center, #010d1a 0%, #000 100%);
font-family: 'Share Tech Mono', monospace;
}
/* ── BOX ── */
.box {
position: relative;
z-index: 3;
width: min(420px, 90vw);
background: rgba(0, 8, 20, 0.92);
border: 1px solid rgba(0,180,255,0.25);
padding: 2.5rem;
box-shadow:
0 0 60px rgba(0,180,255,0.05),
inset 0 0 40px rgba(0,0,0,0.5);
}
.box::before, .box::after {
content: '';
position: absolute;
left: 0; right: 0;
height: 1px;
}
.box::before { top: 6px; background: linear-gradient(90deg, transparent, rgba(0,180,255,0.3), transparent); }
.box::after { bottom: 6px; background: linear-gradient(90deg, transparent, rgba(0,180,255,0.3), transparent); }
.corner {
position: absolute;
width: 12px; height: 12px;
border-color: rgba(0,180,255,0.6);
border-style: solid;
}
.corner-tl { top: -1px; left: -1px; border-width: 2px 0 0 2px; }
.corner-tr { top: -1px; right: -1px; border-width: 2px 2px 0 0; }
.corner-bl { bottom: -1px; left: -1px; border-width: 0 0 2px 2px; }
.corner-br { bottom: -1px; right: -1px; border-width: 0 2px 2px 0; }
/* ── HEADER ── */
.header { text-align: center; margin-bottom: 2rem; }
.tag {
font-size: 0.65rem;
color: rgba(0,180,255,0.5);
letter-spacing: 0.25em;
text-transform: uppercase;
margin-top: 1rem;
}
.logo-img {
height: 120px;
width: auto;
object-fit: contain;
display: block;
margin: 0 auto 1rem;
filter: drop-shadow(0 0 8px rgba(0,180,255,0.4));
}
.title {
font-family: 'Saira Stencil One', sans-serif;
font-size: 1.8rem;
color: #aaa;
letter-spacing: 0.1em;
margin-bottom: 0.2rem;
text-shadow: 0 0 20px rgba(0,180,255,0.3);
}
.subtitle {
font-size: 0.65rem;
color: rgba(0,180,255,0.4);
letter-spacing: 0.2em;
}
/* ── FIELDS ── */
.field { margin-bottom: 1.25rem; }
.field label {
display: flex;
justify-content: space-between;
font-size: 0.65rem;
color: rgba(0,180,255,0.4);
letter-spacing: 0.2em;
text-transform: uppercase;
margin-bottom: 0.5rem;
}
.field label span { color: rgba(255,255,255,0.15); }
.field-value {
width: 100%;
padding: 0.5rem 0;
border: none;
border-bottom: 1px solid rgba(0,180,255,0.15);
background: transparent;
color: rgba(0,180,255,0.8);
font-family: 'Share Tech Mono', monospace;
font-size: 0.95rem;
letter-spacing: 0.08em;
min-height: 2rem;
user-select: none;
}
.field-value.filled {
border-bottom-color: rgba(0,180,255,0.4);
}
.field-value.locked {
color: rgba(0,180,255,0.35);
border-bottom-color: rgba(0,180,255,0.1);
}
/* ── STATUS ── */
.status {
font-size: 0.68rem;
color: rgba(0,180,255,0.4);
letter-spacing: 0.1em;
margin-bottom: 1.5rem;
min-height: 1rem;
}
.status.active { color: rgba(0,180,255,0.8); }
.status.success { color: #00ff87; }
.status.error { color: #b10000; }
/* ── BUTTON ── */
button {
width: 100%;
padding: 0.85rem;
background: transparent;
border: 1px solid rgba(177,0,0,0.6);
color: rgba(177,0,0,0.8);
font-family: 'Share Tech Mono', monospace;
font-size: 0.85rem;
letter-spacing: 0.25em;
text-transform: uppercase;
cursor: default;
transition: all 0.4s;
position: relative;
overflow: hidden;
}
button::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(177,0,0,0.05), transparent);
transform: translateX(-100%);
}
button.flash { animation: btnFlash 0.35s ease-in-out 4; }
button.proceed {
border-color: rgba(0,255,135,0.5);
color: #00ff87;
cursor: pointer;
box-shadow: 0 0 20px rgba(0,255,135,0.1);
}
button.proceed::before {
animation: shimmer 2s infinite;
}
button.proceed:hover {
background: rgba(0,255,135,0.06);
box-shadow: 0 0 30px rgba(0,255,135,0.15);
}
@keyframes btnFlash {
0%, 100% { opacity: 1; }
50% { opacity: 0.1; }
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
/* ── DIVIDER ── */
.divider {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1.5rem;
color: rgba(0,180,255,0.15);
font-size: 0.6rem;
letter-spacing: 0.2em;
}
.divider::before, .divider::after {
content: '';
flex: 1;
height: 1px;
background: rgba(0,180,255,0.1);
}
</style>
<link href="https://fonts.googleapis.com/css2?family=Saira+Stencil+One&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<div class="box">
<div class="corner corner-tl"></div>
<div class="corner corner-tr"></div>
<div class="corner corner-bl"></div>
<div class="corner corner-br"></div>
<div class="header">
<img class="logo-img" src="assets/img/logo.png" alt="291st JTF">
<div class="title">291st JTF</div>
<div class="subtitle">JOINT TASK FORCE</div>
</div>
<div class="divider">OPERATOR AUTHENTICATION</div>
<div class="field">
<label>Operator ID</label>
<div class="field-value" id="f-user"></div>
</div>
<div class="field">
<label>Access Code</label>
<div class="field-value" id="f-pass"></div>
</div>
<div class="status" id="status"></div>
<button id="btn" disabled>AUTHENTICATE</button>
<div class="tag">Status: SECURE | Version: 1.0.0</div>
</div>
`;
this._runSequence();
}
_type(el, text, delay) {
return new Promise(resolve => {
let i = 0;
const t = setInterval(() => {
el.textContent = text.slice(0, ++i);
if (i >= text.length) { clearInterval(t); resolve(); }
}, delay);
});
}
_wait(ms) { return new Promise(r => setTimeout(r, ms)); }
_setStatus(msg, cls = '') {
const s = this.shadowRoot.getElementById('status');
s.textContent = msg;
s.className = 'status ' + cls;
}
async _runSequence() {
const root = this.shadowRoot;
const fUser = root.getElementById('f-user');
const fPass = root.getElementById('f-pass');
const btn = root.getElementById('btn');
await this._wait(700);
this._setStatus('> INITIALISING ENCRYPTED CHANNEL...', 'active');
await this._wait(1200);
this._setStatus('> BIOMETRIC HANDSHAKE CONFIRMED', 'active');
await this._wait(900);
this._setStatus('> ENTER CREDENTIALS', 'active');
await this._wait(600);
await this._type(fUser, 'OPERATOR_291', 75);
fUser.classList.add('filled');
await this._wait(300);
await this._type(fPass, '● ● ● ● ● ● ● ●', 55);
fPass.classList.add('filled');
await this._wait(400);
this._setStatus('> AUTHENTICATING — STANDBY...', 'active');
await this._wait(300);
btn.disabled = false;
btn.classList.add('flash');
await this._wait(1500);
btn.classList.remove('flash');
this._setStatus('> ACCESS GRANTED — IDENTITY VERIFIED ✓', 'success');
fUser.classList.remove('filled');
fPass.classList.remove('filled');
fUser.classList.add('locked');
fPass.classList.add('locked');
btn.textContent = 'PROCEED ▶';
btn.classList.add('proceed');
btn.addEventListener('click', () => {
document.getElementById('main-nav')?.scrollIntoView({ behavior: 'smooth' });
}, { once: true });
}
}
customElements.define('jtf-login', JTFLogin);

View File

@@ -1,31 +0,0 @@
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppRouting} from './app.routing';
import {DiscordComponent} from './components/discord/discord.component';
import {FooterComponent} from './components/footer/footer.component';
import {ServersComponent} from './components/servers/servers.component';
import {AppComponent} from './containers/app/app.component';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {MaterialModule} from './material.module';
import {HomeComponent} from './views/home/home.component';
export const APP_COMPONENTS = [
AppComponent,
DiscordComponent,
FooterComponent,
HomeComponent,
ServersComponent,
]
@NgModule({
declarations: APP_COMPONENTS,
imports: [
BrowserModule,
AppRouting,
BrowserAnimationsModule,
MaterialModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@@ -1,13 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {HomeComponent} from './views/home/home.component';
const routes: Routes = [
{path: '', pathMatch: 'full', component: HomeComponent}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRouting { }

View File

@@ -1,7 +0,0 @@
import {Component} from '@angular/core';
@Component({
selector: 'app-discord',
template: `<iframe src="https://discordapp.com/widget?id=399625240927404033&theme=dark" width="100%" height="98%" allowtransparency="true" frameborder="0" style="min-height: 400px" sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"></iframe>`
})
export class DiscordComponent { }

View File

@@ -1,8 +0,0 @@
<footer>
<div class="py-2 text-center">
<p class="copywright m-0">
Copyright &copy; 291st JTF 2023 | All Rights Reserved<br>
Created by <a href="https://zakscode.com" target="_blank">Zak Timson</a>
</p>
</div>
</footer>

View File

@@ -1,4 +0,0 @@
.copywright {
a, a:visited { color: #b10000; }
a:hover, a:visited:hover { color: #cc0000; }
}

View File

@@ -1,8 +0,0 @@
import {Component} from '@angular/core';
@Component({
selector: 'app-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss']
})
export class FooterComponent { }

View File

@@ -1,21 +0,0 @@
<div *ngFor="let s of servers">
<div class="d-flex p-2">
<div class="flex-shrink-1 align-self-center">
<img *ngIf="s.icon" [src]="s.icon">
<mat-icon *ngIf="!s.icon">dns</mat-icon>
</div>
<div class="d-flex flex-column flex-grow-1">
<div mat-line>{{s.name}}</div>
<div mat-line class="text-muted">{{s.url}}</div>
<div *ngIf="s.mods" mat-line><a [href]="s.mods.link">{{s.mods.num}} mods</a></div>
</div>
<div class="d-flex flex-shrink-1 justify-content-end">
<mat-icon [style.visibility]="s.private ? 'visible' : 'hidden'" class="text-muted" matTooltip="Private" style="font-size: 14px">lock</mat-icon>
<div style="width: 12px; height: 12px; border-radius: 6px;"
[style.background-color]="s.online ? '#00FF00' : '#FF0000'"
[matTooltip]="s.online ? 'Online' : 'Offline'">
</div>
</div>
</div>
<mat-divider></mat-divider>
</div>

View File

@@ -1,48 +0,0 @@
import {Component} from '@angular/core';
type Server = {
icon?: string;
name: string;
private: boolean;
online: boolean;
mods?: {num: number, link: string};
url: string;
}
@Component({
selector: 'app-servers',
templateUrl: './servers.component.html'
})
export class ServersComponent {
servers: Server[] = [{
name: 'Arma 3',
private: false,
online: false,
url: '291st.ca:2302'
}, {
name: 'Astroneer',
private: true,
online: false,
url: '291st.ca:7777'
}, {
name: 'Minecraft (Bedrock)',
private: true,
online: true,
url: '291st.ca:19132'
}, {
name: 'Minecraft (Java)',
private: true,
online: true,
url: '291st.ca:25565'
}, {
name: 'Valheim',
private: true,
online: true,
url: '291st.ca:2456'
}, {
name: 'V Rising',
private: true,
online: false,
url: '291st.ca:27015'
}]
}

View File

@@ -1,2 +0,0 @@
<router-outlet></router-outlet>
<app-footer></app-footer>

View File

@@ -1,5 +0,0 @@
.app-container {
overflow-x: hidden;
overflow-y: auto;
scroll-behavior: smooth;
}

View File

@@ -1,23 +0,0 @@
import {BreakpointObserver} from '@angular/cdk/layout';
import { Component } from '@angular/core';
import {ActivatedRoute, NavigationEnd, Router} from '@angular/router';
import {filter} from 'rxjs';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
mobile = false;
open = false;
constructor(private router: Router, route: ActivatedRoute, breakpointObserver: BreakpointObserver) {
router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => this.open = false);
breakpointObserver.observe(['(max-width: 750px)']).subscribe(result => {
this.mobile = result.matches;
this.open = !this.mobile;
})
}
}

View File

@@ -1,20 +0,0 @@
import {NgModule} from '@angular/core';
import {MatButtonModule} from '@angular/material/button';
import {MatIconModule} from '@angular/material/icon';
import {MatListModule} from '@angular/material/list';
import {MatTabsModule} from '@angular/material/tabs';
import {MatTooltipModule} from '@angular/material/tooltip';
export const MATERIAL_MODULES = [
MatButtonModule,
MatIconModule,
MatListModule,
MatTabsModule,
MatTooltipModule,
];
@NgModule({
imports: MATERIAL_MODULES,
exports: MATERIAL_MODULES,
})
export class MaterialModule {}

View File

@@ -1,38 +0,0 @@
<div class="h-100 d-none d-sm-flex align-items-center justify-content-center" style="background: #000 url('/assets/img/helicopter.gif'); background-size: cover;">
<img src="/assets/img/logo.png" width="auto" height="60%" style="max-width: 60%">
</div>
<div class="d-flex d-sm-none align-items-center justify-content-center" style="background: #000 url('/assets/img/helicopter.gif'); background-size: cover;">
<img src="/assets/img/logo.png" width="auto" height="60%" style="max-width: 60%">
</div>
<div style="position: absolute; top: 2rem; left: 2rem;">
<h1 class="mb-1 stencil" style="font-size: 3rem">
291st
<span class="d-none d-sm-inline">Joint Task Force</span>
<span class="d-inline d-sm-none">JTF</span>
</h1>
<h2 class="d-none d-sm-block stencil">"Putting the Damned to Rest"</h2>
</div>
<div class="content">
<mat-tab-group mat-stretch-tabs="false" mat-align-tabs="start" class="h-100" style="min-height: 400px">
<mat-tab class="h-100">
<ng-template mat-tab-label>
<mat-icon class="me-2">discord</mat-icon>
Discord
</ng-template>
<app-discord></app-discord>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<mat-icon class="me-2">dns</mat-icon>
Servers
</ng-template>
<div class="d-flex justify-content-center">
<div class="pt-4" style="width: 300px">
<app-servers></app-servers>
</div>
</div>
</mat-tab>
</mat-tab-group>
</div>

View File

@@ -1,23 +0,0 @@
.content {
text-align: center;
background-color: #202225;
position: relative;
display: block;
width: 100%;
}
@media(orientation: landscape) {
.content {
position: fixed;
right: 0;
top: 0;
width: 30vw;
height: calc(100% - 2rem);
margin: 1rem;
box-shadow: 6px 6px 5px black;
}
}
::ng-deep .mat-tab-body-wrapper {
height: 100%;
}

View File

@@ -1,10 +0,0 @@
import {Component} from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent {
}

View File

@@ -1,3 +0,0 @@
export const environment = {
production: true
};

View File

@@ -1,3 +0,0 @@
export const environment = {
production: false
};

View File

@@ -1,22 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="291st Joint Task Force">
<meta name="author" content="Zak Timson">
<title>291st JTF</title>
<link href="assets/img/logo.png" rel="icon" type="image/png">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Saira+Stencil+One" rel="stylesheet">
</head>
<body class="mat-typography" style="background: #000">
<app-root></app-root>
</body>
</html>

View File

@@ -1,12 +0,0 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

View File

@@ -1,53 +0,0 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes recent versions of Safari, Chrome (including
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@@ -1,57 +0,0 @@
@use '@angular/material' as mat;
@include mat.core();
// hue. Available color palettes: https://material.io/design/color/
$JTF-primary: mat.define-palette(mat.$red-palette, 900);
$JTF-accent: mat.define-palette(mat.$indigo-palette, 900);
$JTF-warn: mat.define-palette(mat.$orange-palette, 500);
$JTF-theme: mat.define-dark-theme((
color: (
primary: $JTF-primary,
accent: $JTF-accent,
warn: $JTF-warn,
)
));
@include mat.all-component-themes($JTF-theme);
@import '~bootstrap/dist/css/bootstrap-utilities.min.css';
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0);
}
::-webkit-scrollbar {
width: 10px;
background: rgba(0, 0, 0, 0);
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.6);
&:hover { background: rgba(255, 255, 255, 0.8); }
}
html, body {
height: 100%;
}
body {
background: #000;
color: #fff;
font-family: Roboto, sans-serif;
margin: 0;
}
a, a:visited {
text-decoration: none;
color: #3c0000;
}
a:hover, a:visited:hover { color: #550000; }
.fill {
height: 0;
min-height: calc(100vh - 64px);
}
.stencil {
font-family: Saira Stencil One !important;
}

View File

@@ -1,39 +0,0 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./out-tsc/app",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2020",
"types": [],
"module": "es2020",
"lib": [
"es2020",
"dom"
]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}