Rework folder structure in frontend

Pages are now grouped per group they appear in on in the header (where possible)
Utilities used by multiple pages in the project are grouped in folders such as services/pipes/etc
This commit is contained in:
2025-07-04 19:50:21 +02:00
parent c42fc38b2c
commit ebe66c87c0
169 changed files with 123 additions and 113 deletions
@@ -0,0 +1,157 @@
<ng-container>
<app-header [current_page]="'home'" height="100vh"
[background_image]="'/public/img/backgrounds/120spawn-min.png'">
<div class="title" header-content>
<h1 style="display: none;">Altitude</h1>
<img id="header-img" ngSrc="/public/img/logos/logo.png" alt="The Altitude Minecraft Server" height="319"
width="550">
<h2 style="font-size: 2.5em;" id="homeh2">Altitude now on {{ ALTITUDE_VERSION }}!</h2>
<a id="scroll-button" (click)="scrollToSection()">
<span></span>
<p style="display: none;">Scroll Down</p>
</a>
</div>
</app-header>
<main>
<section id="scrollingpoint" style="background: #202020; text-align: center; padding: 80px 0;">
<!-- TODO load player count from old api or backend?-->
<h2 style="color: white;"><span class="player-count">Loading...</span></h2>
<h2 style="color: white;">Server IP: play.alttd.com</h2>
<div style="padding-top: 35px;">
<app-copy-ip></app-copy-ip>
</div>
</section>
<section class="darkmodeSection">
<div class="container">
<div class="paragraph">
<h2>Adventure Begins</h2>
<p>You awake in a strange town, where are you? There are residents running about trading with each other and
stories of distant realms with more towns. It's time to write your story. Welcome to Altitude, the laid-back
community-oriented server that hosts your home for Minecraft.</p>
</div>
<img ngSrc="/public/img/items/bookquill.png" style="width: 150px; align-self: center; margin: 0 auto;"
alt="Alternative Altitude Server Logo"
height="150" width="150">
</div>
</section>
<!--
<section id="section1">
<div class="container" id="video">
<h2 style="display: none;">YouTube Trailer</h2>
<div style="border-radius:5px;overflow:hidden;position:relative;width:100%">
<img style="width: 100%" src="https://img.youtube.com/vi/Nzbj9Dbv5Wk/maxresdefault.jpg" alt="Altitude YouTube Trailer">
<div id="youtube">
<div class="play" onclick="playVideo()"></div>
</div>
</div>
</div>
</section>
-->
<section class="darkmodeSection">
<div class="container" style="padding: 10px 0 80px 0">
<iframe id="discord-widget" src="https://discordapp.com/widget?id=141644560005595136&theme=dark"></iframe>
<div class="paragraph" id="discordp">
<h2>Meet the Community</h2>
<p>Altitude is home to players young and old from all around the globe, and here, everyone is family. Altitude
is your place to get together with friends and relax - and maybe enjoy some survival too. Altitude is
intended for older players, but all are welcome!</p>
<p>Don't have Discord? Keep up with news and announcements at the <a href="alttd.com/blog">blog</a>.</p>
<div style="display: flex; justify-content: center;">
<a target="_blank" rel="noopener" href="https://discordapp.com/invite/TGqpzCJ">
<div style="margin-top: 30px;" class="button-outer">
<span class="button-inner">Join Our Discord</span>
</div>
</a>
</div>
</div>
</div>
</section>
<section id="section2">
<div class="customContainer">
<h2 style="color: white; padding-bottom: 35px; font-size: 2.8em;">Survival Shaped by You</h2>
<p style="color: white; padding-bottom: 35px; font-size: 1.1em; margin: auto;">Altitude is built by the
community, for the community. We've added features requested by our members and several custom plugins to
create our "perfect" survival experience.</p>
<div class="survivalShapedContainerPlugins">
<div class="pluginColumn">
<h2>McMMO & MyPet</h2>
<p>Two of the most requested plugins on Altitude, level up yourself and your pet with these MMO-based
plugins!</p>
<a [routerLink]="['/mypet']">
<div class="button-outer">
<span class="button-inner">MyPet Info</span>
</div>
</a>
</div>
<div class="pluginColumn">
<h2>Dynamic map</h2>
<p>See the world and the players around it in real-time! The map shows the entire survival world with claims
and warps.</p>
<a [routerLink]="['/map']">
<div class="button-outer">
<span class="button-inner">Visit Map</span>
</div>
</a>
</div>
<div class="pluginColumn">
<h2>Player Shops</h2>
<p>Our economy is built on player shops, encouraging player interaction and putting the control in your
hands.</p>
<a [routerLink]="['/economy']">
<div class="button-outer">
<span class="button-inner">Shop Guide</span>
</div>
</a>
</div>
</div>
</div>
</section>
<section class="removemobile darkmodeSection">
<div class="customContainer">
<h2 style="padding-bottom: 35px; font-size: 2.8em;">Community Builds</h2>
<p style="padding-bottom: 35px; font-size: 1.1em; margin: auto;">Thank you to our brilliant players for sharing
their impressive builds. Take a look at some of them here!<br>
If you know of any builds that should be featured, please let us know!</p>
<div class="sliderWrapper">
<div class="sliderContent">
<div class="indexSlider">
<div id="go-left" (click)="previousSlide()" class="circleBehind goLeft"></div>
<div id="go-right" (click)="nextSlide()" class="circleBehind goRight"></div>
<div class="display">
<span class="slide"
[style.background-image]="'url(' + slide + ')'"
[style.opacity]="carouselOpacity"
[style.display]="'block'"></span>
</div>
</div>
<div class="dots">
<ng-container *ngFor="let slide of getSlideIndices();">
<span class="dot" (click)="setSlide(slide)" [ngClass]="getDotClass(slide)"></span>
</ng-container>
</div>
</div>
</div>
</div>
</section>
<section style="background: #202020;">
<div class="customContainer">
<h2 id="quote" style="color: white; font-family: 'minecraft-text',sans-serif; line-height: 1.3em;">"Great
community, great people, great server all round . . . If it can bring back my love for minecraft, it could
work wonders for you."</h2>
<p style="color: white; margin-top:30px;">- /u/seanhanley1993</p>
</div>
</section>
<section class="darkmodeSection">
<div class="customContainer">
<img ngSrc="/public/img/logos/log.png" alt="Alternative Altitude Server Logo" height="129"
width="120">
<h2 style="margin: 20px 0; font-size: 1.8em; padding-bottom: 0 !important;">play.alttd.com</h2>
<app-copy-ip></app-copy-ip>
</div>
</section>
<a (click)="this.scrollService.scrollToTop()" class="scroll-up-button, active">
<span></span>
<p style="display: none;">Scroll Down</p>
</a>
</main>
</ng-container>
@@ -0,0 +1,359 @@
#section1 {
background: -webkit-linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/public/img/backgrounds/town2.jpg");
background: -o-linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/public/img/backgrounds/town2.jpg");
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/public/img/backgrounds/town2.jpg");
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
text-align: center;
}
#section2 {
background: -webkit-linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/public/img/backgrounds/windmill.png");
background: -o-linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/public/img/backgrounds/windmill.png");
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/public/img/backgrounds/windmill.png");
background-attachment: fixed;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
main .container {
padding: 80px 0;
display: flex;
justify-content: center;
flex-wrap: wrap;
}
.customContainer {
width: 80%;
max-width: 1020px;
margin: auto;
padding: 80px 0;
text-align: center;
}
// Discord Widget
#discord-widget {
margin: 0 auto;
width: 40%;
max-width: 380px;
height: 400px;
}
//Scroll button/anchorpoint
#scroll-button {
position: absolute;
bottom: 50px;
left: 50%;
z-index: 2;
display: inline-block;
-webkit-transform: translate(0, -50%);
transform: translate(0, -50%);
color: #fff;
transition: opacity .3s;
padding-top: 40px;
}
#scroll-button:hover {
opacity: .5;
cursor: pointer;
}
#scroll-button span {
position: absolute;
top: 0;
left: 50%;
width: 46px;
height: 46px;
margin-left: -23px;
border: 2px solid #fff;
border-radius: 100%;
box-sizing: border-box;
}
#scroll-button span::after {
position: absolute;
top: 50%;
left: 50%;
content: '';
width: 16px;
height: 16px;
margin: -12px 0 0 -8px;
border-left: 1px solid #fff;
border-bottom: 1px solid #fff;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
box-sizing: border-box;
}
#scroll-button span::before {
position: absolute;
top: 0;
left: 0;
z-index: -1;
content: '';
width: 44px;
height: 44px;
box-shadow: 0 0 0 0 rgba(255, 255, 255, .1);
border-radius: 100%;
opacity: 0;
-webkit-animation: sdb03 3s infinite;
animation: sdb03 3s infinite;
box-sizing: border-box;
}
@-webkit-keyframes sdb03 {
0% {
opacity: 0;
}
30% {
opacity: 1;
}
60% {
box-shadow: 0 0 0 60px rgba(255, 255, 255, .1);
opacity: 0;
}
100% {
opacity: 0;
}
}
@keyframes sdb03 {
0% {
opacity: 0;
}
30% {
opacity: 1;
}
60% {
box-shadow: 0 0 0 60px rgba(255, 255, 255, .1);
opacity: 0;
}
100% {
opacity: 0;
}
}
// Survival Shaped by You section
.survivalShapedContainerPlugins {
display: flex;
justify-content: space-between;
}
.pluginColumn {
width: 250px;
}
.pluginColumn div {
margin-top: 30px;
}
.pluginColumn h2 {
color: var(--white);
font-size: 1.4em;
margin-bottom: 10px;
}
.pluginColumn p {
color: var(--white);
}
// Community builds section, removed if on mobile
.sliderWrapper, .sliderContent {
display: flex;
margin: auto;
}
.sliderContent {
position: relative;
box-shadow: 8px 8px 25px 0 rgba(0, 0, 0, 0.2);
}
.indexSlider {
width: 100%;
height: 100%;
position: relative;
background: rgba(0, 0, 0, 0.9);
font-size: 21px;
text-align: center;
border-radius: 5px;
}
.goLeft, .goRight {
display: block;
text-align: center;
position: absolute;
width: 250px;
}
.goRight:hover, .goLeft:hover {
background: rgba(155, 155, 155, 0.3);
box-shadow: -2px 0 15px rgba(200, 200, 200, 0.5);
cursor: pointer;
}
.goLeft {
height: 100%;
color: white;
left: 0;
}
.goRight {
height: 100%;
color: white;
right: 0;
}
.slide {
border-radius: 5px;
}
.display > span {
width: 700px;
height: 400px;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
.dots {
text-align: center;
position: absolute;
bottom: 3%;
left: 0;
right: 0;
margin: 0 auto;
}
.dot {
position: relative;
cursor: pointer;
height: 1rem;
width: 1rem;
border: 1px solid rgb(255, 255, 255);
opacity: 0.5;
margin: 0 5px;
border-radius: 50%;
display: inline-block;
transition: all 0.3s ease;
}
.dot:hover {
box-shadow: -2px 0 10px 5px rgba(200, 200, 200, 0.3);
background-color: rgba(200, 200, 200, 0.8);
border: 1px solid rgb(255, 255, 255);
transform: scale(1.2);
}
.active-dot {
background-color: var(--link-color);
}
.inactive-dot {
background-color: rgba(0, 0, 0, 0);
}
// Different resolution changes
@media (max-width: 1150px) {
.container {
width: 90%;
}
h1 {
font-size: 3em;
}
.title h2 {
font-size: 1em;
}
#homeh2 {
font-size: 1.6em;
}
.title {
height: calc(100% - 150px);
margin-top: 0;
}
.container, .customContainer {
width: 90%;
}
#header-img {
width: 45%;
}
}
@media (max-width: 900px) {
.removemobile {
display: none;
}
}
@media (max-width: 690px) {
#discordwidget {
margin: 0 auto;
width: 60%;
max-width: 380px;
height: 400px;
}
#discordp {
margin: 50px auto;
}
.title h2 {
font-size: 0.9em;
}
#homeh2 {
font-size: 1.4em;
width: 100%;
}
#header-img {
width: 65%;
}
.title {
height: calc(100% - 110px);
margin-top: 100px;
}
.columnContainer {
width: 100%;
text-align: center;
}
main .container {
text-align: center;
width: 90%;
padding: 30px 0;
}
.paragraph {
width: 80%;
margin: 0 auto 30px auto;
}
.survivalShapedContainerPlugins {
display: block;
}
.pluginColumn {
margin: 30px auto;
}
#quote {
font-size: 1em;
}
}
@@ -0,0 +1,23 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HomeComponent} from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HomeComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,108 @@
import {Component, OnInit} from '@angular/core';
import {Title} from '@angular/platform-browser';
import {ALTITUDE_VERSION} from '@custom-types/constant';
import {ScrollService} from '@services/scroll.service';
import {CommonModule, NgOptimizedImage} from '@angular/common';
import {HeaderComponent} from '@header/header.component';
import {CopyIpComponent} from '@shared-components/copy-ip/copy-ip.component';
import {RouterLink} from '@angular/router';
@Component({
standalone: true,
imports: [
CommonModule,
HeaderComponent,
CopyIpComponent,
RouterLink,
NgOptimizedImage
],
selector: 'app-home',
templateUrl: './home.component.html',
styleUrl: './home.component.scss'
})
export class HomeComponent implements OnInit {
constructor(private titleService: Title, public scrollService: ScrollService) {
}
private slides: string[] = ["/public/img/backgrounds/caruselimage2.png",
"/public/img/backgrounds/caruselimage4.png",
"/public/img/backgrounds/caruselimage1.png",
"/public/img/backgrounds/caruselimage5.png",
"/public/img/backgrounds/caruselimage3.png"
];
private slideIndex = 0;
public carouselOpacity = 1;
public triggerAnimation = false;
ngOnInit(): void {
this.titleService.setTitle('Survival Minecraft Server on ' + ALTITUDE_VERSION + ' | Altitude Community');
this.randomizeSlides()
}
private randomizeSlides(): void {
const array = [...this.slides];
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
this.slides = array;
}
get slide(): string {
return this.slides[this.slideIndex];
}
public previousSlide(): void {
if (this.slideIndex > 0) {
this.setSlide(this.slideIndex - 1);
} else {
this.setSlide(this.slides.length - 1);
}
}
public nextSlide(): void {
this.setSlide((this.slideIndex + 1) % this.slides.length);
}
public getSlideIndices(): number[] {
return Array.from({length: this.slides.length}, (_, i) => i);
}
public setSlide(slide: number): void {
this.slideIndex = slide;
this.triggerAnimation = true
this.carouselOpacity = 0;
const startTime = Date.now();
const duration = 1500;
const animate = () => {
const elapsed = Date.now() - startTime;
this.carouselOpacity = Math.min(elapsed / duration, 1);
if (this.carouselOpacity < 1) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
}
public getDotClass(slide: number): string {
return slide == this.slideIndex ? 'active-dot' : 'inactive-dot';
}
public scrollToSection(): void {
const element = document.getElementById('scrollingpoint');
if (element) {
element.scrollIntoView({behavior: 'smooth', block: 'start'});
} else {
console.error('Element with id "scrollingpoint" not found.');
}
}
protected readonly ALTITUDE_VERSION = ALTITUDE_VERSION;
}