Harbingers Of Death

'Are you going to die?' sammelt historische Aberglauben über den Tod. Dieses Projekt satirisiert heutige Verschwörungstheorien, indem es veraltete Aberglauben als real darstellt.


Harbingers Of Death

Typ

LAMP Website

Jahr

2018

Angewandte Fähigkeiten

  • Back-End
  • Front-End
  • Java

Team

  • Eigenes Projekt
<h2>Die Aufgabe</h2>
<p>Ziel dieses Projekts war es, Back-end-Entwicklung mit dem LAMP-Stack zu üben. Nutzer können sich auf der Seite registrieren, mit Inhalten interagieren und werden von einer personalisierten Startseite begrüßt. Die Seite bietet datenbankgesteuerte Inhalte, sichere Authentifizierung sowie AJAX-Suche und -Filterung.</p>
Harbingers of Death – Startseite
Startseite
Harbingers of Death – Omen-Inhaltsseite
Omen-Inhaltseinheit

Das Ergebnis

Harbingers of Death präsentiert veraltete Omen aus einem Artikel von 1889, als wären sie real – als Satire moderner Verschwörungstheorien. Die Content Units sind einzelne Omen. Sie enthalten Informationen darüber, wen sie betreffen, was sie auslöst und in welcher Situation man ihnen begegnet. Nutzer können sie über verschiedene Taxonomien und per Suche erkunden.

Besucher können Omen filtern nach Schuldigen, Betroffenen und Lebensaspekt. Außerdem können Omen über ein Textfeld durchsucht werden.

Mitglieder melden sich an, um zu verfolgen, welche Omen sie erlebt haben und wen sie als nächstes sterben lassen.

  <figure onclick="viewImage(this)">
    <img src="/assets/img/harbingersofdeath/lqip/animated-form.jpg" lqip-gif alt="Animiertes Formular mit schwebenden Labels (ARIA-freundlich)">
    <figcaption class="center">Formular-Styling (ARIA-freundlich)</figcaption>
  </figure>
  <figure onclick="viewImage(this)">
    <img src="/assets/img/harbingersofdeath/lqip/screen-member-home.jpg" alt="Harbingers of Death – Mitglieder-Startseite">
    <figcaption class="center">Mitglieder-Startseite</figcaption>
  </figure>
</div>

Der Code

Ein entscheidendes Element für den Erfolg dieses Projekts war die Trennung von Views und Logik. So konnten wir sehr sauberen Code schreiben. Als ich die einzelnen Omen-Inhaltsseiten implementierte, trennte ich Code für die Darstellung von Code für das Abrufen und Organisieren der Daten. Zur Laufzeit wird beim Verarbeiten der Omen-Route in index.php eine Omen-Instanz über OmenCollection.php erzeugt und an die Seitenansicht omen.php übergeben.

index.php
<?php// CODE SNIPPET, NOT THE FULL FILE ///************************************************* **  INDIVIDUAL OMEN ROUTE *************************************************/// Add the individual omen routeRoute::get('/omen/([a-z0-9]+(?:-[a-z0-9]+)*)', function($slug) {    $omen = OmenCollection::getOmenBySlug($slug);    Page::build('omen', ["omen" => $omen]);});?>

View gist on GitHub

OmenCollection.php
<?php// CODE SNIPPET, NOT THE FULL FILE ///** * @return Omen */public static function getOmenBySlug(string $slug) : Omen{    //Select all from the table that is created by performing inner joins. Tables joined: omen, aspect, death & fault.    $query = "SELECT * FROM";    //Join omen with aspect    $query .= " (((".self::T_OMEN." INNER JOIN ".self::T_ASPECT." ON ".self::T_OMEN.".".self::C_ASPECT." = ".self::T_ASPECT.".".self::C_TERM.")";    //Join result with death    $query .= " INNER JOIN ".self::T_DEATH." ON ".self::T_OMEN.".".self::C_DEATH." = ".self::T_DEATH.".".self::C_TERM.")";    //Join result with fault    $query .= " INNER JOIN ".self::T_FAULT." ON ".self::T_OMEN.".".self::C_FAULT." = ".self::T_FAULT.".".self::C_TERM.")";    //Filter result by omen slug    $query .= " WHERE ".self::T_OMEN.".".self::C_SLUG." = '".$slug."';";    return (new self)->processQuery($query)->getOmens()[0];}?>

View gist on GitHub

omen.php
<?phpuse Taxonomy\Taxonomy\AspectTaxonomy;use Taxonomy\Death\DeathTaxonomy;use Taxonomy\Fault\FaultTaxonomy;use Entity\Omen;use Entity\Omen\OmenCollection;use View\Partial;// TODO: 404 error handling//Provide similar omens (same fault)$filter = array("fault" => $omen->getFault()->getSlug());$omensCollection = OmenCollection::findOmensByFilter($filter);// if the user is logged inif (isset($_SESSION['user'])){  //Get user omens so that we can set the title to statement on only those omens  $user = $_SESSION['user'];  // update the omen's that the user has selected  $omensCollection->setStatements($user->getUserOmens());}?><?= Partial::build('layout/header'); ?><!--THE REGISTRATION MODAL WINDOW--><?= Partial::build('modal/register'); ?><!--THE LOGIN MODAL WINDOW--><?= Partial::build('modal/login'); ?><!--THE ACCOUNT MODAL WINDOW--><?= Partial::build('modal/account'); ?><!--THE HOMEPAGE WINDOW--><?= Partial::build('nav', ["breadcrumb" => $omen->getTitle()]) ?><img src="<?php echo $omen->getImage() ?>" class="omenImg" alt="<?php echo $omen->getImageAuthor() ?>" title="<?php echo $omen->getImageAuthor() ?>"><?= Partial::build("omenHero", ["omen" => $omen ]); ?><article itemscope itemtype="http://schema.org/CreativeWork" class="poem g-margin4of9 g-span4of9">  <p class="poem__body">    <?= str_replace('/',"<br>", $omen->getPoem()); ?>  </p>  <footer class="poem__author">    &mdash;<cite itemprop="author"><?php echo $omen->getPoemAuthor() ?></cite>  </footer></article><?= Partial::build('taxonomyTile'); ?>      <section>      <div class="layout layout--distant g-flex">          <div class="tile__panel tile__panel--primary g-span3of9">              <h2>Other ways <?php echo strtolower($omen->getFault()->getTitle()) ?> can kill people</h2>              <span class="callToAction"><a href="/omen/?fault=<?php echo $omen->getFault()->getSlug() ?>" class="input__seeMore" >See more</a></span>                        </div>          <?= Partial::build('omenGrid', ["omenCollection" => $omensCollection]); ?>      </div>  </section>  <?= Partial::build('footer'); ?></div>

View gist on GitHub

Eine interessante Herausforderung war die Implementierung schwebender Labels für Texteingaben. Die Labels erscheinen wie Platzhaltertext, bis der Nutzer Text eingibt; dann werden sie verkleinert und oberhalb des Eingabefelds positioniert – wie ein klassisches Label. Die Herausforderung bestand darin, Funktion und Form nicht zu beeinträchtigen. Labels und Eingabefelder sind daher separate DOM-Objekte, und das Formular ist ARIA-freundlich.

FormLabels.js
// CODE SNIPPET, NOT THE FULL FILE ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////__________________________________________________________ FLOATING LABEL TEXT INPUTS  _________________________________________________________/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////For floating labels: User enters text into text input and label floats up.const labelMod = "input__label--selected";const labelModFloating = "input__label--floating"//Each instance contains a label & a text input. The input listens for user inputs and styles the label.//________________________________________________________________ Pair Classvar Pair = function(label, input) {	this.label = label;	this.input = input;	//add floating modifier	if (!this.label.classList.contains(labelModFloating)) this.label.classList.add(labelModFloating);  if (this.input.value !== "") if (!this.label.classList.contains(labelMod)) this.label.classList.add(labelMod);  this.input.addEventListener('input', (event) => {    this.label.classList.add(labelMod);    //console.log("Focus on: " + this.input.id + ", with label: " + this.label.innerHTML + ", text:" + event.target.value);    //Change label style on input    if (event.target.value === ""){      if (this.label.classList.contains(labelMod)) this.label.classList.remove(labelMod);    } else {      if (!this.label.classList.contains(labelMod)) this.label.classList.add(labelMod);    }  });};//Find all cellsvar cells = [...document.querySelectorAll(".form__cell")];//Create pair classes for each cell that contains exactly one label & one inputfor (let i = 0; i < cells.length; i++) {		let cell = cells[i];	let labels = [...cell.querySelectorAll(".input__label")];	let inputs = [...cell.querySelectorAll(".input__text")];	if (labels.length === 1 && inputs.length === 1){		new Pair(labels[0], inputs[0]);	}}

View gist on GitHub

Für dieses Projekt war ich außerdem für die Einrichtung der Datenbank und aller Abfragen verantwortlich. Ich entwarf die Datenbank mit folgenden Tabellen: user (Nutzerinformationen), user_omen (Zuordnungstabelle für erlebte Omen), omen (Informationen zu jedem Omen) sowie drei Taxonomie-Tabellen: aspect, death und fault (zum Taggen von Omen). Über die Website änderbar sind nur user und user_omen; die Omen- und Taxonomie-Tabellen sind statisch.

ER-Diagramm der Harbingers-of-Death-Datenbank
ER-Diagramm unserer Datenbank.
Diese Portfolio-Website wurde entwickelt mit Astro. Urheberrecht © 2026 Robert Michels. Alle Rechte vorbehalten.
Lebenslauf Datenschutz