feat(ui): Serverseitig gerenderte Oberflaeche ohne Build-Schritt
Eigenes CSS statt Framework, Vanilla-JavaScript, heller und dunkler Modus. Ohne JavaScript bleibt alles bedienbar; das Liniendiagramm faellt auf eine selbst gezeichnete Balkendarstellung zurueck, wenn das CDN nicht erreichbar ist. Chart.js ist auf eine Version festgelegt und per Subresource Integrity an seinen Hash gebunden. Die Domain-Seite enthaelt eine Schritt-fuer-Schritt-Anleitung fuer DNS, Hosting-Panel und Zertifikat samt Fehlersuche.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
# =============================================================
|
||||
# urlshort-qr-gen – Apache-Konfiguration fuer den Webroot
|
||||
# =============================================================
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
|
||||
# Falls die Anwendung in einem Unterverzeichnis liegt, ermittelt
|
||||
# Apache die Basis in der Regel selbst. Bei Problemen hier setzen:
|
||||
# RewriteBase /
|
||||
|
||||
# Vorhandene Dateien und Verzeichnisse direkt ausliefern
|
||||
# (assets/, favicon.ico, ...).
|
||||
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# Alles Uebrige an den Front Controller.
|
||||
RewriteRule ^ index.php [L]
|
||||
</IfModule>
|
||||
|
||||
<IfModule !mod_rewrite.c>
|
||||
# Ohne mod_rewrite funktionieren nur Aufrufe mit index.php/... –
|
||||
# bitte mod_rewrite beim Hoster aktivieren.
|
||||
ErrorDocument 404 /index.php
|
||||
</IfModule>
|
||||
|
||||
# Authorization-Header durchreichen (manche CGI/FastCGI-Setups)
|
||||
<IfModule mod_setenvif.c>
|
||||
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||||
</IfModule>
|
||||
|
||||
# Verzeichnislisting abschalten
|
||||
Options -Indexes
|
||||
|
||||
# Standard-Zeichensatz
|
||||
AddDefaultCharset UTF-8
|
||||
|
||||
# Sicherheits-Header fuer statische Dateien.
|
||||
# "setifempty" statt "set", weil die Anwendung dieselben Header fuer
|
||||
# PHP-Antworten selbst setzt - sonst wuerden sie doppelt ausgeliefert.
|
||||
<IfModule mod_headers.c>
|
||||
Header setifempty X-Content-Type-Options "nosniff"
|
||||
Header setifempty X-Frame-Options "DENY"
|
||||
Header setifempty Referrer-Policy "strict-origin-when-cross-origin"
|
||||
</IfModule>
|
||||
|
||||
# Statische Assets duerfen gecacht werden
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
ExpiresByType text/css "access plus 7 days"
|
||||
ExpiresByType application/javascript "access plus 7 days"
|
||||
ExpiresByType image/svg+xml "access plus 7 days"
|
||||
</IfModule>
|
||||
|
||||
# Kein Zugriff auf Konfigurations- und Versionsdateien
|
||||
<FilesMatch "^(\.env.*|composer\.(json|lock)|.*\.md|.*\.ya?ml|Dockerfile)$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
@@ -0,0 +1,470 @@
|
||||
/* =============================================================
|
||||
urlshort-qr-gen – Stylesheet (kein Framework, kein Build-Schritt)
|
||||
============================================================= */
|
||||
|
||||
:root {
|
||||
--bg: #f6f7f9;
|
||||
--surface: #ffffff;
|
||||
--border: #e2e5ea;
|
||||
--border-strong: #cbd2dc;
|
||||
--text: #1b2029;
|
||||
--muted: #667085;
|
||||
--primary: #2c5cc5;
|
||||
--primary-dark: #244da6;
|
||||
--primary-soft: #eaf0fd;
|
||||
--danger: #b3261e;
|
||||
--danger-soft: #fdeceb;
|
||||
--warn: #8a5a00;
|
||||
--warn-soft: #fff6e3;
|
||||
--success: #1a6b45;
|
||||
--success-soft: #e8f6ef;
|
||||
--radius: 12px;
|
||||
--radius-sm: 8px;
|
||||
--shadow: 0 1px 2px rgba(16, 24, 40, .05), 0 8px 24px -12px rgba(16, 24, 40, .18);
|
||||
--mono: ui-monospace, "SFMono-Regular", "Cascadia Mono", Consolas, "Liberation Mono", monospace;
|
||||
--sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #14171c;
|
||||
--surface: #1c2027;
|
||||
--border: #2c323b;
|
||||
--border-strong: #3b434f;
|
||||
--text: #e7eaef;
|
||||
--muted: #98a2b3;
|
||||
--primary: #6c96f0;
|
||||
--primary-dark: #82a8f7;
|
||||
--primary-soft: #1e2a3f;
|
||||
--danger: #f28b82;
|
||||
--danger-soft: #3a2320;
|
||||
--warn: #f0c36d;
|
||||
--warn-soft: #38301d;
|
||||
--success: #7ad0a5;
|
||||
--success-soft: #1b2f26;
|
||||
--shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 8px 24px -12px rgba(0, 0, 0, .6);
|
||||
}
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
||||
html { -webkit-text-size-adjust: 100%; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--sans);
|
||||
font-size: 16px;
|
||||
line-height: 1.55;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
h1, h2, h3 { line-height: 1.25; margin: 0 0 .4em; font-weight: 650; letter-spacing: -.01em; }
|
||||
h1 { font-size: 1.6rem; }
|
||||
h2 { font-size: 1.1rem; }
|
||||
p { margin: 0 0 1rem; }
|
||||
p:last-child { margin-bottom: 0; }
|
||||
a { color: var(--primary); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
code, .mono { font-family: var(--mono); font-size: .92em; }
|
||||
code { background: var(--primary-soft); padding: .1em .35em; border-radius: 4px; }
|
||||
|
||||
.container { width: min(1140px, 100% - 2.5rem); margin-inline: auto; }
|
||||
.muted { color: var(--muted); }
|
||||
.small { font-size: .875rem; }
|
||||
.center { text-align: center; }
|
||||
.nowrap { white-space: nowrap; }
|
||||
.sr-only {
|
||||
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
||||
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
||||
}
|
||||
.truncate {
|
||||
display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
|
||||
}
|
||||
|
||||
/* ---------- Kopfzeile ---------- */
|
||||
|
||||
.topbar {
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
position: sticky; top: 0; z-index: 20;
|
||||
}
|
||||
|
||||
.topbar__inner {
|
||||
display: flex; align-items: center; gap: 1.25rem;
|
||||
min-height: 62px; flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.brand { display: inline-flex; align-items: center; gap: .55rem; color: var(--text); font-weight: 650; }
|
||||
.brand:hover { text-decoration: none; }
|
||||
|
||||
.brand__mark {
|
||||
width: 22px; height: 22px; border-radius: 6px; display: inline-block;
|
||||
background:
|
||||
linear-gradient(135deg, var(--primary), color-mix(in srgb, var(--primary) 55%, #14b8a6));
|
||||
}
|
||||
.brand__mark--lg { width: 34px; height: 34px; border-radius: 9px; margin-bottom: .5rem; }
|
||||
|
||||
.mainnav { display: flex; align-items: center; gap: .35rem; margin-right: auto; flex-wrap: wrap; }
|
||||
.mainnav > a {
|
||||
padding: .4rem .7rem; border-radius: var(--radius-sm); color: var(--muted); font-weight: 500;
|
||||
}
|
||||
.mainnav > a:hover { background: var(--bg); color: var(--text); text-decoration: none; }
|
||||
.mainnav > a.is-active { color: var(--text); background: var(--primary-soft); }
|
||||
.mainnav > a.btn { color: #fff; }
|
||||
.mainnav > a.btn:hover { color: #fff; }
|
||||
|
||||
.logout { display: flex; align-items: center; gap: .6rem; margin: 0; }
|
||||
.logout__user { color: var(--muted); font-size: .875rem; }
|
||||
|
||||
.main { padding: 1.75rem 0 3rem; }
|
||||
|
||||
.footer { border-top: 1px solid var(--border); padding: 1.25rem 0; color: var(--muted); font-size: .8125rem; }
|
||||
|
||||
/* ---------- Zentriertes Layout (Login/Setup/Fehler) ---------- */
|
||||
|
||||
.body--centered { display: grid; place-items: center; min-height: 100vh; padding: 1.5rem; }
|
||||
.centered { width: min(460px, 100%); }
|
||||
.card--narrow { padding: 2rem; }
|
||||
.card__head { text-align: center; margin-bottom: 1.5rem; }
|
||||
.card__head h1 { font-size: 1.35rem; }
|
||||
.bignum { font-size: 3rem; font-weight: 700; color: var(--muted); opacity: .35; line-height: 1; }
|
||||
.trace {
|
||||
text-align: left; font-family: var(--mono); font-size: .75rem; background: var(--bg);
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm); padding: .75rem;
|
||||
overflow: auto; max-height: 320px;
|
||||
}
|
||||
|
||||
/* ---------- Karten & Raster ---------- */
|
||||
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 1.25rem 1.35rem;
|
||||
box-shadow: var(--shadow);
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.card--flush { padding: 0; overflow: hidden; }
|
||||
.card--form { max-width: 720px; }
|
||||
.card__head-row {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 1rem;
|
||||
flex-wrap: wrap; margin-bottom: .75rem;
|
||||
}
|
||||
.card__head-row h2 { margin: 0; }
|
||||
|
||||
.grid { display: grid; gap: 1.25rem; }
|
||||
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
|
||||
.grid.tight { gap: .85rem; }
|
||||
|
||||
.page-head {
|
||||
display: flex; align-items: flex-start; justify-content: space-between;
|
||||
gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem;
|
||||
}
|
||||
.page-head h1 { margin-bottom: .15rem; }
|
||||
.page-head p { margin: 0; }
|
||||
.page-head__actions { display: flex; gap: .5rem; flex-wrap: wrap; }
|
||||
|
||||
.empty { text-align: center; padding: 3rem 1.5rem; }
|
||||
.empty .btn { margin-top: .5rem; }
|
||||
|
||||
/* ---------- Kennzahlen ---------- */
|
||||
|
||||
.stats-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: 1rem; margin-bottom: 1.25rem;
|
||||
}
|
||||
.stat {
|
||||
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
|
||||
padding: 1rem 1.15rem; display: flex; flex-direction: column; gap: .25rem;
|
||||
}
|
||||
.stat__label { color: var(--muted); font-size: .8125rem; text-transform: uppercase; letter-spacing: .04em; }
|
||||
.stat__value { font-size: 1.75rem; font-weight: 650; line-height: 1.1; }
|
||||
.stat__value--sm { font-size: 1.1rem; font-weight: 600; }
|
||||
|
||||
/* ---------- Buttons ---------- */
|
||||
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
|
||||
padding: .5rem .9rem; border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-strong); background: var(--surface); color: var(--text);
|
||||
font: inherit; font-size: .9375rem; font-weight: 500; cursor: pointer;
|
||||
text-decoration: none; transition: background .12s ease, border-color .12s ease;
|
||||
}
|
||||
.btn:hover { background: var(--bg); text-decoration: none; }
|
||||
.btn:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible, a:focus-visible {
|
||||
outline: 2px solid var(--primary); outline-offset: 2px;
|
||||
}
|
||||
.btn--primary { background: var(--primary); border-color: var(--primary); color: #fff; }
|
||||
.btn--primary:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
|
||||
.btn--ghost { background: transparent; border-color: transparent; color: var(--muted); }
|
||||
.btn--ghost:hover { background: var(--bg); color: var(--text); }
|
||||
.btn--danger { color: var(--danger); border-color: transparent; background: transparent; }
|
||||
.btn--danger:hover { background: var(--danger-soft); }
|
||||
.btn--sm { padding: .3rem .6rem; font-size: .8125rem; }
|
||||
.btn--block { width: 100%; }
|
||||
.inline { display: inline; margin: 0; }
|
||||
|
||||
/* ---------- Formulare ---------- */
|
||||
|
||||
.form { display: flex; flex-direction: column; gap: 1.1rem; }
|
||||
.form .grid { margin: 0; }
|
||||
.field { display: flex; flex-direction: column; gap: .35rem; margin: 0; }
|
||||
.field label, .label { font-weight: 550; font-size: .9375rem; }
|
||||
.field__hint { color: var(--muted); font-size: .8125rem; margin: 0; }
|
||||
.field__hint.warn { color: var(--warn); }
|
||||
.field__error { color: var(--danger); font-size: .8125rem; margin: 0; }
|
||||
.field--error input, .field--error select { border-color: var(--danger); }
|
||||
.req { color: var(--danger); }
|
||||
|
||||
input[type="text"], input[type="password"], input[type="search"], input[type="url"],
|
||||
input[type="email"], input[type="number"], input[type="file"], select, textarea {
|
||||
width: 100%; padding: .55rem .7rem; font: inherit; font-size: .9375rem;
|
||||
color: var(--text); background: var(--surface);
|
||||
border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
|
||||
}
|
||||
input::placeholder { color: var(--muted); opacity: .8; }
|
||||
input[type="file"] { padding: .4rem; }
|
||||
select { appearance: none; padding-right: 2rem;
|
||||
background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
|
||||
linear-gradient(135deg, var(--muted) 50%, transparent 50%);
|
||||
background-position: right 1rem center, right .7rem center;
|
||||
background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
|
||||
}
|
||||
input[type="range"] { width: 100%; accent-color: var(--primary); }
|
||||
input[type="color"] {
|
||||
width: 46px; height: 38px; padding: 2px; border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius-sm); background: var(--surface); cursor: pointer;
|
||||
}
|
||||
.check { display: flex; align-items: center; gap: .5rem; font-weight: 500; cursor: pointer; }
|
||||
.check input { width: 1.05rem; height: 1.05rem; accent-color: var(--primary); }
|
||||
.form__actions { display: flex; gap: .6rem; flex-wrap: wrap; }
|
||||
|
||||
.input-prefix { display: flex; align-items: stretch; }
|
||||
.input-prefix__label {
|
||||
display: inline-flex; align-items: center; padding: 0 .6rem;
|
||||
background: var(--bg); border: 1px solid var(--border-strong); border-right: 0;
|
||||
border-radius: var(--radius-sm) 0 0 var(--radius-sm);
|
||||
color: var(--muted); font-size: .8125rem; white-space: nowrap;
|
||||
max-width: 55%; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.input-prefix input { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
|
||||
|
||||
.toolbar {
|
||||
display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; margin-bottom: 1.25rem;
|
||||
}
|
||||
.toolbar input[type="search"] { flex: 1 1 260px; }
|
||||
.toolbar select { flex: 0 1 190px; }
|
||||
|
||||
/* ---------- Hinweise ---------- */
|
||||
|
||||
.alert {
|
||||
padding: .7rem .9rem; border-radius: var(--radius-sm); margin-bottom: 1rem;
|
||||
font-size: .9375rem; border: 1px solid transparent;
|
||||
}
|
||||
.alert--success { background: var(--success-soft); color: var(--success); border-color: color-mix(in srgb, var(--success) 25%, transparent); }
|
||||
.alert--error { background: var(--danger-soft); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 25%, transparent); }
|
||||
.alert--warn { background: var(--warn-soft); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
|
||||
|
||||
.badge {
|
||||
display: inline-block; padding: .12rem .5rem; border-radius: 999px;
|
||||
background: var(--primary-soft); color: var(--primary); font-size: .75rem; font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.badge--muted { background: var(--bg); color: var(--muted); border: 1px solid var(--border); }
|
||||
|
||||
/* ---------- Tabellen ---------- */
|
||||
|
||||
.table-wrap { overflow-x: auto; }
|
||||
.table { width: 100%; border-collapse: collapse; font-size: .9375rem; }
|
||||
.table th, .table td { padding: .7rem .9rem; text-align: left; border-bottom: 1px solid var(--border); vertical-align: top; }
|
||||
.table thead th {
|
||||
font-size: .75rem; text-transform: uppercase; letter-spacing: .05em;
|
||||
color: var(--muted); font-weight: 600; background: var(--bg); border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.table tbody tr:last-child td { border-bottom: 0; }
|
||||
.table tbody tr:hover { background: color-mix(in srgb, var(--primary-soft) 45%, transparent); }
|
||||
.table .num { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
.table--compact th, .table--compact td { padding: .45rem .7rem; font-size: .875rem; }
|
||||
.table tr.is-inactive { opacity: .6; }
|
||||
.truncate-cell { max-width: 320px; }
|
||||
.truncate-cell a { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.cell-stack { display: flex; flex-direction: column; gap: .15rem; align-items: flex-start; }
|
||||
.actions { display: flex; gap: .25rem; flex-wrap: wrap; justify-content: flex-end; }
|
||||
|
||||
.linkcopy {
|
||||
border: 0; background: transparent; padding: 0; color: var(--primary); font: inherit;
|
||||
font-family: var(--mono); font-size: .9em; cursor: pointer; text-align: left;
|
||||
}
|
||||
.linkcopy:hover { text-decoration: underline; }
|
||||
.linkcopy.is-copied::after { content: " ✓ kopiert"; color: var(--success); font-family: var(--sans); font-size: .8em; }
|
||||
.linkcopy__domain { color: var(--muted); }
|
||||
|
||||
.badges { display: inline-flex; gap: .3rem; flex-wrap: wrap; }
|
||||
|
||||
.steps { margin: 0 0 1rem; padding-left: 1.25rem; display: flex; flex-direction: column; gap: .5rem; }
|
||||
.steps li { padding-left: .2rem; }
|
||||
|
||||
/* Nummerierte Schrittanleitung mit eigenen Marken */
|
||||
.steps--numbered { counter-reset: step; list-style: none; padding-left: 0; gap: 1.75rem; }
|
||||
.steps--numbered > li { position: relative; padding-left: 2.6rem; }
|
||||
.steps--numbered > li::before {
|
||||
counter-increment: step; content: counter(step);
|
||||
position: absolute; left: 0; top: .05rem;
|
||||
width: 1.85rem; height: 1.85rem; border-radius: 50%;
|
||||
display: grid; place-items: center;
|
||||
background: var(--primary-soft); color: var(--primary);
|
||||
font-weight: 650; font-size: .875rem; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.steps--numbered h3 { font-size: 1rem; margin: .1rem 0 .4rem; }
|
||||
.steps--numbered p { margin-bottom: .5rem; }
|
||||
.steps--numbered .table-wrap { margin-bottom: .5rem; }
|
||||
.steps--numbered .table { border: 1px solid var(--border); border-radius: var(--radius-sm); }
|
||||
|
||||
details {
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||
padding: .5rem .75rem; margin: .5rem 0; background: var(--bg);
|
||||
}
|
||||
details + details { margin-top: .4rem; }
|
||||
details > summary {
|
||||
cursor: pointer; font-weight: 550; font-size: .9375rem; list-style: none;
|
||||
display: flex; align-items: center; gap: .45rem;
|
||||
}
|
||||
details > summary::-webkit-details-marker { display: none; }
|
||||
details > summary::before {
|
||||
content: "▸"; color: var(--muted); font-size: .8em; transition: transform .12s ease;
|
||||
}
|
||||
details[open] > summary::before { transform: rotate(90deg); }
|
||||
details > summary:hover { color: var(--primary); }
|
||||
details > *:not(summary) { margin-top: .6rem; }
|
||||
|
||||
.code-block {
|
||||
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||
padding: .7rem .85rem; overflow-x: auto; font-size: .8125rem; line-height: 1.5; margin: 0 0 .5rem;
|
||||
}
|
||||
.code-block code { background: none; padding: 0; font-size: inherit; white-space: pre; }
|
||||
|
||||
/* ---------- Ergebnis der Domain-Pruefung ---------- */
|
||||
|
||||
.check-result { border-left: 4px solid var(--border-strong); }
|
||||
.check-result--ok { border-left-color: var(--success); }
|
||||
.check-result--warn { border-left-color: var(--warn); }
|
||||
.check-result--error { border-left-color: var(--danger); }
|
||||
|
||||
.check-result__head { display: flex; gap: .85rem; align-items: flex-start; margin-bottom: 1rem; }
|
||||
.check-result__head h2 { margin: 0 0 .15rem; }
|
||||
.check-result__summary { margin: 0; color: var(--muted); }
|
||||
.check-result__icon {
|
||||
flex: 0 0 auto; width: 2rem; height: 2rem; border-radius: 50%;
|
||||
display: grid; place-items: center; font-weight: 700; font-size: 1.05rem; line-height: 1;
|
||||
}
|
||||
.check-result--ok .check-result__icon { background: var(--success-soft); color: var(--success); }
|
||||
.check-result--warn .check-result__icon { background: var(--warn-soft); color: var(--warn); }
|
||||
.check-result--error .check-result__icon { background: var(--danger-soft); color: var(--danger); }
|
||||
|
||||
.check-list { list-style: none; margin: 0 0 1rem; padding: 0; display: flex; flex-direction: column; gap: .55rem; }
|
||||
.check-list__item {
|
||||
display: grid; grid-template-columns: 1.25rem 5.5rem 1fr; gap: .5rem;
|
||||
align-items: baseline; font-size: .9375rem;
|
||||
}
|
||||
.check-list__icon { font-weight: 700; text-align: center; }
|
||||
.check-list__item--ok .check-list__icon { color: var(--success); }
|
||||
.check-list__item--warn .check-list__icon { color: var(--warn); }
|
||||
.check-list__item--error .check-list__icon { color: var(--danger); }
|
||||
.check-list__label { color: var(--muted); font-size: .8125rem; text-transform: uppercase; letter-spacing: .04em; }
|
||||
@media (max-width: 640px) {
|
||||
.check-list__item { grid-template-columns: 1.25rem 1fr; }
|
||||
.check-list__label { grid-column: 2; }
|
||||
.check-list__text { grid-column: 2; }
|
||||
}
|
||||
|
||||
.checklist { list-style: none; padding-left: 0; display: flex; flex-direction: column; gap: .45rem; margin: 0; }
|
||||
.checklist li { padding-left: 1.1rem; position: relative; }
|
||||
.checklist li::before { content: "–"; position: absolute; left: 0; color: var(--muted); }
|
||||
|
||||
.list { list-style: none; margin: 0; padding: 0; }
|
||||
.list__item {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 1rem;
|
||||
padding: .6rem 0; border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.list__item:last-child { border-bottom: 0; }
|
||||
.list__main { display: flex; flex-direction: column; min-width: 0; }
|
||||
|
||||
/* ---------- Diagramme & Balken ---------- */
|
||||
|
||||
.chart { position: relative; min-height: 140px; }
|
||||
.chart canvas { width: 100% !important; }
|
||||
|
||||
.segmented { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); overflow: hidden; }
|
||||
.segmented a {
|
||||
padding: .3rem .7rem; font-size: .8125rem; color: var(--muted);
|
||||
border-right: 1px solid var(--border-strong);
|
||||
}
|
||||
.segmented a:last-child { border-right: 0; }
|
||||
.segmented a:hover { background: var(--bg); text-decoration: none; }
|
||||
.segmented a.is-active { background: var(--primary); color: #fff; }
|
||||
|
||||
.bars { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; }
|
||||
.bars__row { display: grid; grid-template-columns: minmax(80px, 34%) 1fr auto; gap: .6rem; align-items: center; }
|
||||
.bars__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .875rem; }
|
||||
.bars__track { background: var(--bg); border-radius: 999px; height: 9px; overflow: hidden; border: 1px solid var(--border); }
|
||||
.bars__fill { display: block; height: 100%; background: var(--primary); border-radius: 999px; min-width: 2px; }
|
||||
.bars__value { font-variant-numeric: tabular-nums; font-size: .875rem; color: var(--muted); }
|
||||
|
||||
/* ---------- QR-Bereich ---------- */
|
||||
|
||||
.qr-layout { display: grid; grid-template-columns: minmax(300px, 420px) 1fr; gap: 1.25rem; align-items: start; }
|
||||
@media (max-width: 900px) { .qr-layout { grid-template-columns: 1fr; } }
|
||||
|
||||
.qr-preview { position: sticky; top: 82px; }
|
||||
.qr-preview__stage {
|
||||
position: relative; display: grid; place-items: center; padding: 1rem;
|
||||
background:
|
||||
conic-gradient(from 45deg, #00000008 25%, transparent 0 50%, #00000008 0 75%, transparent 0) 0 0/20px 20px,
|
||||
var(--bg);
|
||||
border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 1rem;
|
||||
}
|
||||
.qr-preview__stage img {
|
||||
display: block; width: 100%; max-width: 340px; height: auto; border-radius: 6px;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.qr-preview__spinner {
|
||||
position: absolute; inset: 0; display: grid; place-items: center;
|
||||
background: color-mix(in srgb, var(--surface) 60%, transparent); border-radius: var(--radius);
|
||||
}
|
||||
.qr-preview__spinner::after {
|
||||
content: ""; width: 26px; height: 26px; border-radius: 50%;
|
||||
border: 3px solid var(--border-strong); border-top-color: var(--primary);
|
||||
animation: spin .7s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
@media (prefers-reduced-motion: reduce) { .qr-preview__spinner::after { animation: none; } }
|
||||
|
||||
.qr-download { display: flex; gap: .6rem; flex-wrap: wrap; justify-content: center; margin: .75rem 0 .35rem; }
|
||||
.qr-controls { display: flex; flex-direction: column; }
|
||||
|
||||
.colorpair { display: flex; gap: .5rem; align-items: center; }
|
||||
.colorpair input[type="text"] { flex: 1; }
|
||||
|
||||
.swatches { display: flex; gap: .5rem; flex-wrap: wrap; }
|
||||
.swatch {
|
||||
width: 34px; height: 34px; border-radius: 8px; cursor: pointer; padding: 0;
|
||||
border: 1px solid var(--border-strong);
|
||||
background: linear-gradient(135deg, var(--fg) 0 50%, var(--bg) 50% 100%);
|
||||
}
|
||||
.swatch:hover { transform: scale(1.06); }
|
||||
|
||||
.logo-current { display: flex; gap: 1rem; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; }
|
||||
.logo-current__img {
|
||||
width: 64px; height: 64px; object-fit: contain; background: var(--bg);
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm); padding: .35rem;
|
||||
}
|
||||
.logo-current__meta { display: flex; flex-direction: column; gap: .4rem; align-items: flex-start; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.topbar__inner { padding-block: .6rem; }
|
||||
.logout__user { display: none; }
|
||||
.actions { justify-content: flex-start; }
|
||||
.page-head { flex-direction: column; align-items: stretch; }
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
/* =============================================================
|
||||
urlshort-qr-gen – Vanilla JavaScript, kein Build-Schritt.
|
||||
Alles hier ist optionale Politur: ohne JS bleibt die Anwendung
|
||||
vollstaendig bedienbar (serverseitig gerenderte Formulare).
|
||||
============================================================= */
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/* ---------- Kopieren in die Zwischenablage ---------- */
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
var button = event.target.closest('[data-copy]');
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
var value = button.getAttribute('data-copy');
|
||||
var done = function () {
|
||||
button.classList.add('is-copied');
|
||||
window.setTimeout(function () { button.classList.remove('is-copied'); }, 1600);
|
||||
};
|
||||
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(value).then(done, function () { fallbackCopy(value, done); });
|
||||
} else {
|
||||
fallbackCopy(value, done);
|
||||
}
|
||||
});
|
||||
|
||||
function fallbackCopy(value, done) {
|
||||
var field = document.createElement('textarea');
|
||||
field.value = value;
|
||||
field.setAttribute('readonly', '');
|
||||
field.style.position = 'fixed';
|
||||
field.style.opacity = '0';
|
||||
document.body.appendChild(field);
|
||||
field.select();
|
||||
try { document.execCommand('copy'); done(); } catch (e) { /* still */ }
|
||||
document.body.removeChild(field);
|
||||
}
|
||||
|
||||
/* ---------- Rueckfrage vor destruktiven Aktionen ---------- */
|
||||
|
||||
document.addEventListener('submit', function (event) {
|
||||
var form = event.target.closest('form[data-confirm]');
|
||||
if (form && !window.confirm(form.getAttribute('data-confirm'))) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
/* ---------- Liniendiagramme (Chart.js vom CDN) ---------- */
|
||||
|
||||
var chartContainers = document.querySelectorAll('[data-chart="line"]');
|
||||
if (chartContainers.length > 0) {
|
||||
loadChartJs(function (ok) {
|
||||
chartContainers.forEach(function (container) {
|
||||
if (ok) {
|
||||
renderLineChart(container);
|
||||
} else {
|
||||
renderFallbackChart(container);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadChartJs(callback) {
|
||||
if (window.Chart) {
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var script = document.createElement('script');
|
||||
// Feste Version mit Subresource Integrity: der Browser laedt die
|
||||
// Datei nur, wenn ihr Hash exakt stimmt. Selbst ein manipuliertes
|
||||
// CDN koennte hier also keinen fremden Code einschleusen.
|
||||
// Ist das CDN nicht erreichbar (oder der Hash falsch), greift
|
||||
// renderFallbackChart() mit einer eigenen Darstellung.
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js';
|
||||
script.integrity = 'sha384-9nhczxUqK87bcKHh20fSQcTGD4qq5GhayNYSYWqwBkINBhOfQLg/P5HG5lF1urn4';
|
||||
script.crossOrigin = 'anonymous';
|
||||
script.referrerPolicy = 'no-referrer';
|
||||
script.onload = function () { callback(true); };
|
||||
script.onerror = function () { callback(false); };
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
function seriesOf(container) {
|
||||
try {
|
||||
return JSON.parse(container.getAttribute('data-series') || '[]');
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function cssVar(name, fallback) {
|
||||
var value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||||
return value || fallback;
|
||||
}
|
||||
|
||||
function formatDay(iso) {
|
||||
var parts = String(iso).split('-');
|
||||
return parts.length === 3 ? parts[2] + '.' + parts[1] + '.' : iso;
|
||||
}
|
||||
|
||||
function renderLineChart(container) {
|
||||
var canvas = container.querySelector('canvas');
|
||||
var series = seriesOf(container);
|
||||
if (!canvas || series.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var primary = cssVar('--primary', '#2c5cc5');
|
||||
var muted = cssVar('--muted', '#667085');
|
||||
var border = cssVar('--border', '#e2e5ea');
|
||||
|
||||
if (container._chart) {
|
||||
container._chart.destroy();
|
||||
}
|
||||
|
||||
container._chart = new window.Chart(canvas.getContext('2d'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: series.map(function (point) { return formatDay(point.date); }),
|
||||
datasets: [{
|
||||
label: 'Klicks',
|
||||
data: series.map(function (point) { return point.clicks; }),
|
||||
borderColor: primary,
|
||||
backgroundColor: 'rgba(44, 92, 197, .12)',
|
||||
borderWidth: 2,
|
||||
pointRadius: series.length > 60 ? 0 : 2,
|
||||
pointHoverRadius: 4,
|
||||
tension: 0.3,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (item) { return item.parsed.y + ' Klick(s)'; }
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: muted, maxTicksLimit: 10, autoSkip: true, maxRotation: 0 }
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: border },
|
||||
border: { display: false },
|
||||
ticks: { color: muted, precision: 0, maxTicksLimit: 6 }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Wenn das CDN nicht erreichbar ist: schlichte Balken als Ersatz. */
|
||||
function renderFallbackChart(container) {
|
||||
var series = seriesOf(container);
|
||||
var canvas = container.querySelector('canvas');
|
||||
if (canvas) {
|
||||
canvas.remove();
|
||||
}
|
||||
if (series.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var max = Math.max.apply(null, series.map(function (point) { return point.clicks; })) || 1;
|
||||
var wrapper = document.createElement('div');
|
||||
wrapper.className = 'chart-fallback';
|
||||
wrapper.style.cssText = 'display:flex;align-items:flex-end;gap:2px;height:130px;';
|
||||
|
||||
series.forEach(function (point) {
|
||||
var bar = document.createElement('div');
|
||||
bar.title = point.date + ': ' + point.clicks + ' Klick(s)';
|
||||
bar.style.cssText = 'flex:1;background:var(--primary);border-radius:2px 2px 0 0;min-height:2px;height:'
|
||||
+ Math.round(point.clicks / max * 100) + '%;opacity:.85;';
|
||||
wrapper.appendChild(bar);
|
||||
});
|
||||
|
||||
container.appendChild(wrapper);
|
||||
}
|
||||
|
||||
/* ---------- Kurzlink-Formular: Domain-Prefix ---------- */
|
||||
|
||||
var domainSelect = document.querySelector('[data-domain-select]');
|
||||
var slugPrefix = document.querySelector('[data-slug-prefix]');
|
||||
if (domainSelect && slugPrefix) {
|
||||
domainSelect.addEventListener('change', function () {
|
||||
var option = domainSelect.options[domainSelect.selectedIndex];
|
||||
if (option) {
|
||||
slugPrefix.textContent = (option.getAttribute('data-base-url') || '') + '/';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- QR-Code: Live-Vorschau ---------- */
|
||||
|
||||
var qr = document.querySelector('[data-qr]');
|
||||
if (qr) {
|
||||
initQrPreview(qr);
|
||||
}
|
||||
|
||||
function initQrPreview(root) {
|
||||
var form = document.getElementById('qr-form');
|
||||
var image = document.getElementById('qr-image');
|
||||
var spinner = root.querySelector('[data-qr-spinner]');
|
||||
var warningBox = root.querySelector('[data-qr-warning]');
|
||||
var warningText = root.querySelector('[data-qr-warning-text]');
|
||||
var stage = root.querySelector('.qr-preview__stage');
|
||||
var previewUrl = root.getAttribute('data-preview-url');
|
||||
var downloadUrl = root.getAttribute('data-download-url');
|
||||
var timer = null;
|
||||
|
||||
if (!form || !image) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Hex-Feld und Farbwaehler synchron halten. */
|
||||
root.querySelectorAll('[data-color-sync]').forEach(function (input) {
|
||||
input.addEventListener('input', function () {
|
||||
var partner = document.getElementById(input.getAttribute('data-color-sync'));
|
||||
if (!partner) {
|
||||
return;
|
||||
}
|
||||
var value = input.value.trim();
|
||||
if (input.type === 'text') {
|
||||
if (!/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.test(value)) {
|
||||
return;
|
||||
}
|
||||
partner.value = expandHex(value);
|
||||
} else {
|
||||
partner.value = value;
|
||||
}
|
||||
schedule();
|
||||
});
|
||||
});
|
||||
|
||||
/* Farbvoreinstellungen */
|
||||
root.querySelectorAll('[data-preset]').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
var pair = button.getAttribute('data-preset').split(',');
|
||||
setColor('foreground', pair[0]);
|
||||
setColor('background', pair[1]);
|
||||
schedule();
|
||||
});
|
||||
});
|
||||
|
||||
/* Regler-Anzeigen */
|
||||
root.querySelectorAll('[data-output]').forEach(function (input) {
|
||||
var output = document.getElementById(input.getAttribute('data-output'));
|
||||
if (output) {
|
||||
input.addEventListener('input', function () { output.textContent = input.value; });
|
||||
}
|
||||
});
|
||||
|
||||
root.querySelectorAll('[data-qr-input]').forEach(function (input) {
|
||||
input.addEventListener('input', schedule);
|
||||
input.addEventListener('change', schedule);
|
||||
});
|
||||
|
||||
var resetButton = root.querySelector('[data-qr-reset]');
|
||||
if (resetButton) {
|
||||
resetButton.addEventListener('click', function () {
|
||||
window.setTimeout(function () {
|
||||
root.querySelectorAll('[data-color-sync]').forEach(function (input) {
|
||||
var partner = document.getElementById(input.getAttribute('data-color-sync'));
|
||||
if (partner && input.type === 'color') {
|
||||
partner.value = input.value;
|
||||
}
|
||||
});
|
||||
root.querySelectorAll('[data-output]').forEach(function (input) {
|
||||
var output = document.getElementById(input.getAttribute('data-output'));
|
||||
if (output) { output.textContent = input.value; }
|
||||
});
|
||||
update();
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
function setColor(name, value) {
|
||||
var picker = document.getElementById(name);
|
||||
var text = document.getElementById(name + '_text');
|
||||
if (picker) { picker.value = value; }
|
||||
if (text) { text.value = value; }
|
||||
}
|
||||
|
||||
function expandHex(value) {
|
||||
var hex = value.replace('#', '');
|
||||
if (hex.length === 3) {
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
||||
}
|
||||
return '#' + hex.toLowerCase();
|
||||
}
|
||||
|
||||
function params() {
|
||||
var data = new FormData(form);
|
||||
var query = new URLSearchParams();
|
||||
['foreground', 'background', 'size', 'margin', 'error_correction', 'logo_scale'].forEach(function (key) {
|
||||
var value = data.get(key);
|
||||
if (value !== null) {
|
||||
query.set(key, String(value));
|
||||
}
|
||||
});
|
||||
query.set('logo_punchout', data.get('logo_punchout') ? '1' : '0');
|
||||
return query;
|
||||
}
|
||||
|
||||
function schedule() {
|
||||
window.clearTimeout(timer);
|
||||
timer = window.setTimeout(update, 260);
|
||||
}
|
||||
|
||||
function update() {
|
||||
var query = params();
|
||||
|
||||
if (spinner) { spinner.hidden = false; }
|
||||
|
||||
var next = new Image();
|
||||
next.onload = function () {
|
||||
image.src = next.src;
|
||||
if (spinner) { spinner.hidden = true; }
|
||||
};
|
||||
next.onerror = function () {
|
||||
if (spinner) { spinner.hidden = true; }
|
||||
};
|
||||
next.src = previewUrl + '?' + query.toString();
|
||||
|
||||
root.querySelectorAll('[data-qr-download]').forEach(function (anchor) {
|
||||
var format = anchor.getAttribute('data-qr-download');
|
||||
anchor.href = downloadUrl + '?' + query.toString() + '&format=' + format;
|
||||
});
|
||||
|
||||
if (stage) {
|
||||
stage.style.setProperty('--qr-bg', query.get('background') || '#ffffff');
|
||||
}
|
||||
|
||||
checkContrast(query.get('foreground'), query.get('background'));
|
||||
}
|
||||
|
||||
/* Kontrastpruefung nach WCAG – identische Regeln wie serverseitig. */
|
||||
function luminance(hex) {
|
||||
var value = expandHex(hex || '#000000').substring(1);
|
||||
var channels = [0, 2, 4].map(function (offset) {
|
||||
var channel = parseInt(value.substr(offset, 2), 16) / 255;
|
||||
return channel <= 0.03928 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);
|
||||
});
|
||||
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
|
||||
}
|
||||
|
||||
function checkContrast(foreground, background) {
|
||||
if (!warningBox || !warningText) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lf = luminance(foreground);
|
||||
var lb = luminance(background);
|
||||
var ratio = (Math.max(lf, lb) + 0.05) / (Math.min(lf, lb) + 0.05);
|
||||
var message = null;
|
||||
|
||||
if (ratio < 4) {
|
||||
message = 'Zu geringer Kontrast (' + ratio.toFixed(1)
|
||||
+ ':1, empfohlen mindestens 4.0:1). Der QR-Code ist möglicherweise nicht zuverlässig scannbar.';
|
||||
} else if (lf > lb) {
|
||||
message = 'Heller Vordergrund auf dunklem Hintergrund: viele Scanner erkennen invertierte '
|
||||
+ 'QR-Codes nicht. Besser dunkle Module auf hellem Grund.';
|
||||
}
|
||||
|
||||
warningText.textContent = message || '';
|
||||
warningBox.hidden = message === null;
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Kurzlinks">
|
||||
<rect width="32" height="32" rx="7" fill="#2c5cc5"/>
|
||||
<g fill="#ffffff">
|
||||
<rect x="6" y="6" width="8" height="8" rx="1.5"/>
|
||||
<rect x="18" y="6" width="8" height="8" rx="1.5"/>
|
||||
<rect x="6" y="18" width="8" height="8" rx="1.5"/>
|
||||
<rect x="18" y="18" width="3.5" height="3.5"/>
|
||||
<rect x="22.5" y="22.5" width="3.5" height="3.5"/>
|
||||
</g>
|
||||
<g fill="#2c5cc5">
|
||||
<rect x="8.5" y="8.5" width="3" height="3"/>
|
||||
<rect x="20.5" y="8.5" width="3" height="3"/>
|
||||
<rect x="8.5" y="20.5" width="3" height="3"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 674 B |
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Einziger Einstiegspunkt der Anwendung.
|
||||
*
|
||||
* Verzeichnisstruktur (Standard):
|
||||
* /public <- Webroot (diese Datei)
|
||||
* /src, /templates, /migrations, /data, /vendor <- ausserhalb des Webroots
|
||||
*
|
||||
* Fuer Shared Hosting ohne eigenen Webroot siehe README (Variante B):
|
||||
* dort liegt zusaetzlich eine .htaccess im Stammverzeichnis, die alle
|
||||
* Anfragen an public/index.php weiterreicht.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\App;
|
||||
use App\Config;
|
||||
|
||||
$rootDir = dirname(__DIR__);
|
||||
|
||||
$autoload = $rootDir . '/vendor/autoload.php';
|
||||
if (!is_file($autoload)) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
echo '<!doctype html><meta charset="utf-8"><title>Installation unvollständig</title>'
|
||||
. '<h1>Abhängigkeiten fehlen</h1>'
|
||||
. '<p>Der Ordner <code>vendor/</code> wurde nicht gefunden. '
|
||||
. 'Bitte <code>composer install</code> ausführen oder das fertige Release-Paket hochladen.</p>';
|
||||
exit;
|
||||
}
|
||||
|
||||
require $autoload;
|
||||
|
||||
$config = Config::load($rootDir);
|
||||
|
||||
if ($config->isDevelopment()) {
|
||||
ini_set('display_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
} else {
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL & ~E_DEPRECATED & ~E_NOTICE);
|
||||
}
|
||||
|
||||
(new App($config))->run();
|
||||
Reference in New Issue
Block a user