- Better banner
Some checks failed
Build Website / Build Container (push) Failing after 1m39s
Build Website / Tag Version (push) Has been cancelled
Build Website / Build NPM Project (push) Has been cancelled

- Removed old communication methods
- Connected email form
This commit is contained in:
2026-06-04 18:59:10 -04:00
parent ddfa97b5d0
commit 38e018d034
13 changed files with 287 additions and 89 deletions

14
package-lock.json generated
View File

@@ -18,7 +18,7 @@
"@angular/platform-browser": "^14.2.0",
"@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0",
"@ztimson/momentum": "^1.1.11",
"@ztimson/momentum": "^1.2.1",
"bootstrap": "^5.2.1",
"jquery": "^3.6.1",
"ngx-google-analytics": "^14.0.1",
@@ -3342,9 +3342,9 @@
"dev": true
},
"node_modules/@ztimson/momentum": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@ztimson/momentum/-/momentum-1.1.11.tgz",
"integrity": "sha512-ArFIOJj0mCbvR/P6XBlOjvICytf3ggPClbMmDDsZNtLMGXIZ+HqNjt58LDuEpLr3a8pe0mXEZ3jGkIAv2no8mA==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@ztimson/momentum/-/momentum-1.2.1.tgz",
"integrity": "sha512-O3Z06SfMin6zXlW0jZjgS16qwcyPtKfJ8hRzV6SuFlN9Il8y7HepUylnV8Hd5RTdWXeztBmo+pNTD/RjPJ37wQ==",
"dependencies": {
"@ztimson/utils": "0.29.1"
},
@@ -14098,9 +14098,9 @@
"dev": true
},
"@ztimson/momentum": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@ztimson/momentum/-/momentum-1.1.11.tgz",
"integrity": "sha512-ArFIOJj0mCbvR/P6XBlOjvICytf3ggPClbMmDDsZNtLMGXIZ+HqNjt58LDuEpLr3a8pe0mXEZ3jGkIAv2no8mA==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@ztimson/momentum/-/momentum-1.2.1.tgz",
"integrity": "sha512-O3Z06SfMin6zXlW0jZjgS16qwcyPtKfJ8hRzV6SuFlN9Il8y7HepUylnV8Hd5RTdWXeztBmo+pNTD/RjPJ37wQ==",
"requires": {
"@ztimson/utils": "0.29.1"
}

View File

@@ -1,6 +1,6 @@
{
"name": "legio-xxx",
"version": "0.0.0",
"version": "1.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
@@ -21,7 +21,7 @@
"@angular/platform-browser": "^14.2.0",
"@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0",
"@ztimson/momentum": "^1.1.11",
"@ztimson/momentum": "^1.2.1",
"bootstrap": "^5.2.1",
"jquery": "^3.6.1",
"ngx-google-analytics": "^14.0.1",

View File

@@ -12,11 +12,11 @@
<mat-icon *ngIf="manual">play_arrow</mat-icon>
</div>
<div *ngIf="!manual" class="banner-seal d-flex flex-column align-items-center justify-content-center">
<img src="/assets/img/favicon.svg" class="mt-5" alt="SPQR" height="250" width="250" style="filter: brightness(100%) drop-shadow(2px 4px 6px black);">
<img src="/assets/img/favicon.svg" class="mt-5" alt="SPQR" height="250" width="250">
<div>
<a class="text-white" routerLink="" fragment="about">
<i class="fa fa-angle-double-down fa-4x" style="filter: drop-shadow(2px 4px 6px black);"></i>
</a>
<button type="button" class="banner-scroll text-white" (click)="scrollToAbout()" aria-label="Scroll to about section">
<i class="fa fa-angle-double-down fa-4x"></i>
</button>
</div>
</div>
</div>

View File

@@ -3,48 +3,192 @@
overflow: hidden;
width: 100%;
height: 100%;
min-height: 420px;
background: #111;
isolation: isolate;
}
.banner-container::before {
content: '';
position: absolute;
inset: 0;
z-index: 1;
background:
linear-gradient(90deg, rgba(0, 0, 0, 0.32), rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.32)),
linear-gradient(180deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.34));
pointer-events: none;
}
.banner-container::after {
content: '';
position: absolute;
inset: auto 0 0;
z-index: 2;
height: 28%;
background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.38));
pointer-events: none;
}
.banner-background {
width: 100%;
height: 100%;
filter: blur(10px);
-webkit-filter: blur(10px);
object-fit: cover;
transform: scale(1.04);
filter: blur(8px) brightness(90%) saturate(112%);
-webkit-filter: blur(8px) brightness(90%) saturate(112%);
}
.banner-image {
position: absolute;
z-index: 1;
height: 90%;
width: auto;
max-width: 92%;
top: 5%;
left: 50%;
transform: translateX(-50%);
object-fit: contain;
border-radius: 14px;
box-shadow:
0 18px 44px rgba(0, 0, 0, 0.36),
0 0 0 1px rgba(255, 255, 255, 0.14);
filter: contrast(102%) saturate(104%);
}
.banner-next,
.banner-previous,
.banner-pause {
z-index: 4;
display: flex;
align-items: center;
justify-content: center;
width: 52px;
height: 52px;
color: #fff;
cursor: pointer;
border-radius: 999px;
background: rgba(0, 0, 0, 0.34);
border: 1px solid rgba(255, 255, 255, 0.24);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
box-shadow: 0 10px 22px rgba(0, 0, 0, 0.24);
transition:
background 160ms ease,
border-color 160ms ease,
box-shadow 160ms ease,
color 160ms ease;
}
.banner-next:hover,
.banner-previous:hover,
.banner-pause:hover {
background: rgba(75, 15, 15, 0.55);
border-color: rgba(240, 195, 106, 0.52);
color: #f0c36a;
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.34);
}
.banner-next mat-icon,
.banner-previous mat-icon,
.banner-pause mat-icon {
font-size: 30px;
width: 30px;
height: 30px;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.42));
}
.banner-next {
position: absolute;
top: 50%;
right: 26px;
transform: translate(-50%, -50%);
transform: translateY(-50%);
}
.banner-previous {
position: absolute;
top: 50%;
left: 50px;
transform: translate(-50%, -50%);
left: 26px;
transform: translateY(-50%);
}
.banner-seal {
position: absolute;
z-index: 3;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
pointer-events: none;
}
.banner-seal img {
max-width: min(250px, 42vw);
height: auto;
opacity: 0.95;
filter: brightness(108%) drop-shadow(0 10px 16px rgba(0, 0, 0, 0.65)) !important;
}
.banner-seal a,
.banner-scroll {
pointer-events: auto;
display: inline-flex;
margin-top: 18px;
color: #fff;
opacity: 0.9;
transition:
opacity 160ms ease,
color 160ms ease;
}
.banner-scroll {
padding: 0;
border: 0;
background: transparent;
cursor: pointer;
filter: drop-shadow(2px 4px 6px black);
}
.banner-seal a:hover,
.banner-scroll:hover {
color: #f0c36a !important;
opacity: 1;
}
.banner-pause {
position: absolute;
bottom: 25px;
left: 50%;
transform: translate(-50%, -50%);
transform: translateX(-50%);
width: 46px;
height: 46px;
}
@media (max-width: 768px) {
.banner-container {
min-height: 340px;
}
.banner-image {
height: 78%;
max-width: 96%;
border-radius: 12px;
}
.banner-next,
.banner-previous {
width: 44px;
height: 44px;
}
.banner-next {
right: 12px;
}
.banner-previous {
left: 12px;
}
.banner-pause {
bottom: 16px;
}
}

View File

@@ -41,7 +41,7 @@ export class BannerComponent implements AfterViewInit, OnDestroy, OnInit {
this.sub = interval(this.speed)
.subscribe( i => {
if(this.manual) return;
this.selected = i % this.images.length
this.selected = i % this.images.length
});
}
@@ -66,4 +66,11 @@ export class BannerComponent implements AfterViewInit, OnDestroy, OnInit {
this.selected--;
if(this.selected < 0) this.selected = this.images.length - 1;
}
scrollToAbout() {
document.getElementById('about')?.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}

View File

@@ -1,9 +1,18 @@
<div class="d-flex flex-column-reverse flex-md-row justify-content-center cap-width">
<div style="flex: 2 0 0;">
<div *ngIf="error" class="alert alert-danger py-2">
Coming Soon: This feature is under development
{{error}}
</div>
<form [formGroup]="form">
<div *ngIf="success" class="alert alert-success py-2">
Your message was sent successfully.
</div>
<form [formGroup]="form" (ngSubmit)="submitEmail()">
<div>
<mat-form-field appearance="fill" class="w-100">
<mat-label>Name</mat-label>
<input matInput formControlName="name">
</mat-form-field>
</div>
<div>
<mat-form-field appearance="fill" class="w-100">
<mat-label>Email</mat-label>
@@ -25,13 +34,10 @@
<textarea matInput rows="10" formControlName="body"></textarea>
</mat-form-field>
</div>
<div class="d-flex justify-content-between align-items-center">
<div class="d-flex justify-content-end align-items-center">
<div>
<mat-checkbox color="primary">Send me a copy</mat-checkbox>
</div>
<div>
<button mat-stroked-button class="me-3" (click)="reset()">Reset</button>
<button mat-raised-button color="primary" (click)="error = true">Send</button>
<button mat-stroked-button type="button" class="me-3" (click)="reset()">Reset</button>
<button mat-raised-button type="submit" color="primary">Send</button>
</div>
</div>
</form>
@@ -41,17 +47,24 @@
<div class="d-none d-md-block mx-4 border-end border-dark" style="height: 100%; width: 1px;"></div>
</div>
<div class="d-flex flex-column text-center text-md-start align-self-center" style="width: min(100%, 250px)">
<div *ngIf="form.controls['subject'].value != 'Castra'">
<h3>Robert Sacco</h3>
<h4 class="mb-0">Legio XXX President</h4>
<h5>Portrays: Aquilifer Primus Marius Maximus</h5>
<a href="mailto:primuspiluslxxx@gmail.com" target="_blank">primuspiluslxxx@gmail.com</a>
</div>
<div *ngIf="form.controls['subject'].value == 'Castra'">
<h3>Tom Ross</h3>
<h4 class="mb-0">Legio XXX <em>Patronus</em> (Patron)</h4>
<h5>Portrays: Titus Quartinius Saturnalus</h5>
<a href="mailto:tomlongwoods@gmail.com" target="_blank">tomlongwoods@gmail.com</a>
<div class="d-flex flex-column text-center text-md-start align-self-center" style="width: min(100%, 250px)">
<div
class="mb-3"
[ngClass]="form.controls['subject'].value != 'Castra' ? '' : 'opacity-50'"
>
<h3>Robert Sacco</h3>
<h4 class="mb-0">Legio XXX President</h4>
<h5>Portrays: Aquilifer Primus Marius Maximus</h5>
<a href="mailto:primuspiluslxxx@gmail.com" target="_blank">primuspiluslxxx@gmail.com</a>
</div>
<div
[ngClass]="form.controls['subject'].value == 'Castra' ? '' : 'opacity-50'"
>
<h3>Tom Ross</h3>
<h4 class="mb-0">Legio XXX <em>Patronus</em> (Patron)</h4>
<h5>Portrays: Titus Quartinius Saturnalus</h5>
<a href="mailto:tomlongwoods@gmail.com" target="_blank">tomlongwoods@gmail.com</a>
</div>
</div>
</div>
</div>

View File

@@ -1,5 +1,6 @@
import {Component} from '@angular/core';
import {FormBuilder, FormGroup} from '@angular/forms';
import {MomentumService} from '../../services/momentum.service';
@Component({
selector: 'app-contact',
@@ -7,18 +8,33 @@ import {FormBuilder, FormGroup} from '@angular/forms';
})
export class ContactComponent {
public error = false;
public success = false;
public form!: FormGroup;
constructor(private fb: FormBuilder) {
constructor(private fb: FormBuilder, private momentum: MomentumService) {
this.form = fb.group({
name: '',
email: '',
subject: '',
body: '',
});
}
public async submitEmail(): Promise<void> {
this.error = false;
this.success = false;
try {
await this.momentum.api.data.create(MomentumService.SCHEMA['contact'], this.form.value);
this.success = true;
} catch (error: any) {
this.error = error.message;
}
}
reset() {
this.error = false;
this.success = false;
this.form.reset();
}
}

View File

@@ -1,24 +1,24 @@
<footer>
<div class="social text-center py-3" style="background: #990000">
<h2 class="mb-4">Follow us on social media</h2>
<div class="d-flex justify-content-around mx-auto transparent-link" style="max-width: 300px">
<a href="https://discord.gg/wW458KYR79" target="_blank">
<i class="fa-brands fa-discord fa-2xl"></i>
</a>
<a href="https://facebook.com" target="_blank" aria-label="Facebook">
<i class="fa-brands fa-facebook fa-2xl"></i>
</a>
<a href="https://instagram.com" target="_blank" aria-label="Instagram">
<i class="fa-brands fa-instagram fa-2xl"></i>
</a>
<a href="https://tiktok.com" target="_blank" aria-label="TikTok">
<i class="fa-brands fa-tiktok fa-2xl"></i>
</a>
<a href="https://youtube.com" target="_blank" aria-label="Youtube">
<i class="fa-brands fa-youtube fa-2xl"></i>
</a>
</div>
<h3 class="mt-4 mb-0">so we can invade your feed ⚔️</h3>
<!-- <h2 class="mb-4">Follow us on social media</h2>-->
<!-- <div class="d-flex justify-content-around mx-auto transparent-link" style="max-width: 300px">-->
<!-- <a href="https://discord.gg/wW458KYR79" target="_blank">-->
<!-- <i class="fa-brands fa-discord fa-2xl"></i>-->
<!-- </a>-->
<!-- <a href="https://facebook.com" target="_blank" aria-label="Facebook">-->
<!-- <i class="fa-brands fa-facebook fa-2xl"></i>-->
<!-- </a>-->
<!-- <a href="https://instagram.com" target="_blank" aria-label="Instagram">-->
<!-- <i class="fa-brands fa-instagram fa-2xl"></i>-->
<!-- </a>-->
<!-- <a href="https://tiktok.com" target="_blank" aria-label="TikTok">-->
<!-- <i class="fa-brands fa-tiktok fa-2xl"></i>-->
<!-- </a>-->
<!-- <a href="https://youtube.com" target="_blank" aria-label="Youtube">-->
<!-- <i class="fa-brands fa-youtube fa-2xl"></i>-->
<!-- </a>-->
<!-- </div>-->
<!-- <h3 class="mt-4 mb-0">so we can invade your feed ⚔️</h3>-->
</div>
<div class="bg-dark text-center text-sm-start">
<div class="d-flex flex-column flex-sm-row flex-wrap justify-content-center container p-3 pb-0">
@@ -45,7 +45,7 @@
<div class="py-3 text-center">
<p class="copyright m-0">
Copyright &copy; Legio XXX 2024 | All Rights Reserved<br>
Created by <a href="https://zakscode.com" target="_blank">Zak Timson</a>
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>
</div>
</footer>

View File

@@ -2,6 +2,7 @@ import { Component } from '@angular/core';
import {NavigationEnd, Router} from '@angular/router';
import {combineLatest, filter, Subscription} from 'rxjs';
import {BreakpointService} from '../../services/breakpoint.service';
import {MomentumService} from '../../services/momentum.service';
@Component({
selector: 'app-root',
@@ -14,7 +15,7 @@ export class AppComponent {
mobile = false;
open = false;
constructor(private breakpoint: BreakpointService, private router: Router) {
constructor(private breakpoint: BreakpointService, private momentum: MomentumService, private router: Router) {
this.sub = combineLatest([
router.events.pipe(filter(event => event instanceof NavigationEnd)),
breakpoint.isMobile$
@@ -24,6 +25,10 @@ export class AppComponent {
})
}
ngOnInit(): void {
this.momentum.api.client.inject();
}
ngOnDestroy() {
if(this.sub) this.sub.unsubscribe();
}

View File

@@ -1,5 +1,5 @@
import {Injectable} from '@angular/core';
import {Momentum, type User} from '@ztimson/momentum';
import {Momentum} from '@ztimson/momentum';
import {BehaviorSubject} from 'rxjs';
import {from, map} from 'rxjs';
import {filter} from 'rxjs/operators';
@@ -18,7 +18,7 @@ declare global {
@Injectable({providedIn: 'root'})
export class MomentumService {
static SCHEMA: {[key: string]: string} = {
// TODO: Add paths
contact: 'Contact',
}
api!: Momentum;
@@ -29,11 +29,11 @@ export class MomentumService {
// @ts-ignore
user = new BehaviorSubject<User | null | undefined>(undefined); // Undefined at init, null when logged out, object when logged in.
admin = from(this.user).pipe(filter((u: any) => u !== undefined), map((u: User | null) => u?.groups.includes('admin')));
admin = from(this.user).pipe(filter(u => u !== undefined), map(u => u?.groups.includes('admin')));
isLoggedIn = from(this.user).pipe(filter(u => u !== undefined), map(Boolean));
constructor() {
this.api = new Momentum("https://legio-30.org", {
this.api = window['momentum'] = new Momentum("https://legio-30.org", {
app: "Website",
analytics: "prompt",
logLevel: "ERROR",

View File

@@ -20,29 +20,47 @@
that recreate the lives of soldiers found in Trajan's legions during the 1st - 2nd Century AD
</p>
</div>
<a routerLink="/about">More</a>
<a routerLink="/about" class="d-block mt-4" style="font-size: 2em;">More >></a>
</div>
<div class="flex-md-grow-1" style="flex-basis: 0">
<img class="mt-5" src="/assets/img/standard.png" alt="Legio XXX Standard" height="250px" width="auto">
</div>
</section>
<!-- Discord -->
<section class="d-flex" style="background-color: #990000">
<div class="d-flex flex-grow-1">
<div class="h-100 w-100" style="background: #7289d9"></div>
<img class="d-block d-md-none" src="/assets/img/discord.png" style="height: 100px; transform: translateX(-1px);">
<img class="d-none d-md-block" src="/assets/img/discord.png" style="height: 200px; transform: translateX(-1px);">
</div>
<div class="d-flex justify-content-start align-items-center flex-grow-1 p-3">
<!--<section class="d-flex" style="background-color: #990000">-->
<!-- <div class="d-flex flex-grow-1">-->
<!-- <div class="h-100 w-100" style="background: #7289d9"></div>-->
<!-- <img class="d-block d-md-none" src="/assets/img/discord.png" style="height: 100px; transform: translateX(-1px);">-->
<!-- <img class="d-none d-md-block" src="/assets/img/discord.png" style="height: 200px; transform: translateX(-1px);">-->
<!-- </div>-->
<!-- <div class="d-flex justify-content-start align-items-center flex-grow-1 p-3">-->
<!-- <div class="d-block text-center">-->
<!-- <h1 class="d-block m-0 mb-md-3 transparent-link">-->
<!-- <a href="https://discord.gg/wW458KYR79" target="_blank">Join us on Discord</a>-->
<!-- </h1>-->
<!-- <div class="d-none d-md-inline">-->
<!-- <p>-->
<!-- Ask us questions, get involved-->
<!-- <br><br>-->
<!-- and celebrate the glory of Rome with us!-->
<!-- </p>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!--</section>-->
<!-- Meetings -->
<section class="d-flex justify-content-center py-4" style="background-color: #990000">
<div class="d-flex justify-content-start align-items-center">
<div class="d-block text-center">
<h1 class="d-block m-0 mb-md-3 transparent-link">
<a href="https://discord.gg/wW458KYR79" target="_blank">Join us on Discord</a>
<a routerLink="/" fragment="contact">Join Our Monthly Meetings</a>
</h1>
<div class="d-none d-md-inline">
<p>
Ask us questions, get involved
Meet the legion, ask questions, and learn how to get involved.
<br><br>
and celebrate the glory of Rome with us!
Contact us to be added to the event list!
</p>
</div>
</div>

View File

@@ -3,27 +3,19 @@
<html lang="en">
<head>
<base href="/">
<title>LEGIO · XXX</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="author" content="Zak Timson">
<meta property="og:title" content="LEGIO · XXX">
<meta property="og:site_name" content="LEGIO · XXX">
<meta property="og:type" content="article"/>
<meta property="og:url" content="https://legioxxx.zakscode.com">
<meta property="og:image" content="https://legioxxx.zakscode.com/assets/img/standard.png">
<meta name="twitter:card" content="https://legioxxx.zakscode.com/assets/img/standard.png">
<meta name="twitter:image:alt" content="Alt text for image">
<meta name="description" content="Legio XXX is a North American Roman reenactment group established in 2004. It's members represent a cross between living history enthusiasts and 'edutainers' that recreate the lives of soldiers found in Trajan's leagions during the 1st - 2nd Century AD">
<meta property="og:description" content="Legio XXX is a North American Roman reenactment group established in 2004. It's members represent a cross between living history enthusiasts and 'edutainers' that recreate the lives of soldiers found in Trajan's leagions during the 1st - 2nd Century AD">
<title>LEGIO · XXX</title>
<!-- Momentum:meta -->
<link href="assets/img/favicon.svg" rel="icon" type="image/svg">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://use.fontawesome.com/releases/v6.1.1/css/all.css" rel="stylesheet">
<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">
<!-- Momentum:theme -->
</head>
<body class="mat-typography" style="background: #000">

View File

@@ -4,6 +4,8 @@
"compilerOptions": {
"baseUrl": "./",
"outDir": "./out-tsc/app",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
@@ -13,6 +15,7 @@
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"skipLibCheck": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,