CI / pruefen (push) Canceled after 0s
Auf 375px war das Dashboard 40px breiter als der Bildschirm. Ursache war die Steuerleiste: Suchfeld, Sortierung, Ansichtsumschalter und "Neues Projekt" lagen in einer Flex-Zeile ohne Umbruch, und weil das Sortierfeld ein min-width mitbringt, wurde der Knopf am Ende schlicht hinausgeschoben. Das overflow-x: hidden am body hat ihn dann abgeschnitten statt erreichbar zu lassen. Dasselbe Muster im Projektkopf, wo "Loeschen" ausserhalb lag. Die Zeilen brechen jetzt um, der Ansichtsumschalter steht auf schmalen Anzeigen ueber die volle Breite. Gemessen ueber alle Seiten und beide Testbreiten: scrollWidth == clientWidth. Weil der Umschalter ohnehin umgebaut wurde, kommt die vierte Ansicht gleich mit: eine Zeile je Projekt statt einer Karte. Auf dem Telefon ist das der eigentliche Gewinn — eine Karte braucht rund 260px Hoehe und steht dort zwangslaeufig allein in der Zeile, fuenf Projekte fuellen also zwei Bildschirme. Als Liste passen sie auf einen, und weil alle Zeilen gleich gebaut sind, liegen Name, Fortschritt und Status untereinander. Was nicht mehr in die Breite passt, faellt der Reihe nach weg, statt sie zu sprengen. Dazu die Kleinigkeiten, die auf dem Telefon dranhaengen: - .field bekommt auf Beruehrungsgeraeten 16px. Darunter zoomt iOS Safari beim Hineintippen die Seite heran und kehrt nicht von selbst zurueck. - .btn mindestens 44px hoch, der Schieberegler einen 24px-Griff. - Der Popover klemmt seine Breite ans Fenster. Unter 280px lieferte die Rechnung eine negative linke Kante, das Menue stand halb ausserhalb. - Das Tag-Formular der Auswahlleiste war 16px breiter als sein Popover und wurde von dessen overflow-hidden beschnitten – auf jeder Breite. - Die Heatmap scrollt wieder seitlich, statt sich zu stauchen. - Die Kennzahlen stehen zweispaltig; einspaltig fuellten die fuenf Kacheln den ganzen ersten Bildschirm, bevor das erste Projekt kam. - viewportFit und die safe-area-Abstaende, damit im installierten Betrieb nichts unter Statusleiste und Home-Anzeige verschwindet. - Die beiden Verweise in der Kopfleiste und "Neues Projekt" tragen ein aria-label. Unterhalb von sm sind sie reine Symbole und hiessen fuer einen Screenreader gar nichts; axe-core meldete das als link-name. axe-core laeuft auf 375px ueber alle vier Ansichten, den Dialog und die Projektseite jetzt ohne Beanstandung. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
466 lines
12 KiB
CSS
466 lines
12 KiB
CSS
/* ─────────────────────────────────────────────────────────────
|
||
Nebula – wiederkehrende Bausteine.
|
||
|
||
Bewusst in @layer components: Tailwind sortiert die Layer als
|
||
theme → base → components → utilities. Ohne diese Zuordnung wuerden
|
||
die Kurzschreibweisen hier (padding, border-radius …) die Utilities
|
||
ueberschreiben, weil sie spaeter im Stylesheet stehen – aus
|
||
"field pl-9" wuerde dann wieder das linke Standardpadding und das
|
||
Suchsymbol laege auf dem Platzhaltertext.
|
||
───────────────────────────────────────────────────────────── */
|
||
|
||
@layer components {
|
||
/* ── Glasflaechen ─────────────────────────────────────────── */
|
||
|
||
.glass {
|
||
position: relative;
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
backdrop-filter: blur(18px) saturate(140%);
|
||
-webkit-backdrop-filter: blur(18px) saturate(140%);
|
||
border-radius: var(--radius-card);
|
||
box-shadow:
|
||
0 1px 0 0 var(--glass-highlight) inset,
|
||
0 24px 60px -30px rgba(0, 0, 0, 0.9);
|
||
}
|
||
|
||
.glass-soft {
|
||
background: rgba(255, 255, 255, 0.025);
|
||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||
backdrop-filter: blur(12px);
|
||
-webkit-backdrop-filter: blur(12px);
|
||
border-radius: 0.9rem;
|
||
}
|
||
|
||
/* Karte mit Akzent-Glow. --accent-from/--accent-to setzt die Komponente. */
|
||
.card-glow {
|
||
transition:
|
||
transform 0.35s cubic-bezier(0.22, 1, 0.36, 1),
|
||
box-shadow 0.35s ease,
|
||
border-color 0.35s ease;
|
||
}
|
||
|
||
.card-glow::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: -1px;
|
||
border-radius: inherit;
|
||
padding: 1px;
|
||
background: linear-gradient(135deg, var(--accent-from, #8b5cf6), var(--accent-to, #d946ef));
|
||
-webkit-mask:
|
||
linear-gradient(#000 0 0) content-box,
|
||
linear-gradient(#000 0 0);
|
||
-webkit-mask-composite: xor;
|
||
mask-composite: exclude;
|
||
opacity: 0;
|
||
transition: opacity 0.35s ease;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.card-glow:hover {
|
||
transform: translateY(-4px);
|
||
box-shadow:
|
||
0 1px 0 0 var(--glass-highlight) inset,
|
||
0 30px 70px -30px rgba(0, 0, 0, 0.95),
|
||
0 0 46px -12px var(--accent-from, #8b5cf6);
|
||
}
|
||
|
||
.card-glow:hover::before {
|
||
opacity: 0.85;
|
||
}
|
||
|
||
/**
|
||
* Flaeche fuer alles, was ueber dem Inhalt schwebt: Menues, Dialoge.
|
||
*
|
||
* Bewusst nahezu deckend statt Glas. Ein Overlay muss lesbar sein, egal
|
||
* was darunter liegt - bei 3,5 % Deckkraft schien der Inhalt dahinter
|
||
* durch. backdrop-filter hilft hier nicht: liegt das Element in einem
|
||
* Vorfahren, der selbst backdrop-filter setzt (unsere Karten), bleibt die
|
||
* Weichzeichnung wirkungslos.
|
||
*/
|
||
.popover {
|
||
/* Vollstaendig deckend: bei 97 % schimmerte darunterliegender Text
|
||
(die Schaltflaechen "Bearbeiten" und "Loeschen") noch durch. */
|
||
background: #090b18;
|
||
border: 1px solid rgba(255, 255, 255, 0.11);
|
||
border-radius: 0.9rem;
|
||
box-shadow:
|
||
0 1px 0 0 rgba(255, 255, 255, 0.07) inset,
|
||
0 28px 70px -24px rgba(0, 0, 0, 0.95);
|
||
}
|
||
|
||
/**
|
||
* Markdown-Inhalte. Tailwind setzt in seinem Preflight alle Abstaende und
|
||
* Schriftgroessen zurueck, also muessen Ueberschriften, Listen und
|
||
* Codebloecke hier wieder Form bekommen.
|
||
*/
|
||
.md {
|
||
font-size: 0.875rem;
|
||
line-height: 1.65;
|
||
color: #cbd5e1;
|
||
overflow-wrap: break-word;
|
||
}
|
||
|
||
.md > *:first-child { margin-top: 0; }
|
||
.md > *:last-child { margin-bottom: 0; }
|
||
|
||
.md h1, .md h2, .md h3, .md h4 {
|
||
color: #f1f5f9;
|
||
font-weight: 600;
|
||
line-height: 1.3;
|
||
margin: 1.4em 0 0.5em;
|
||
}
|
||
.md h1 { font-size: 1.3rem; }
|
||
.md h2 { font-size: 1.15rem; }
|
||
.md h3 { font-size: 1rem; }
|
||
.md h4 { font-size: 0.9rem; }
|
||
|
||
.md p { margin: 0.75em 0; }
|
||
|
||
.md a {
|
||
color: #c4b5fd;
|
||
text-decoration: underline;
|
||
text-underline-offset: 2px;
|
||
}
|
||
.md a:hover { color: #ddd6fe; }
|
||
|
||
/* Bilder werden nicht geladen, sondern als Marke gezeigt – siehe die
|
||
Begruendung in Markdown.tsx. Damit sie nicht wie Fliesstext aussieht,
|
||
bekommt sie einen Rahmen und ein Symbol davor. */
|
||
.md .md-image {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.3em;
|
||
border: 1px dashed rgba(255, 255, 255, 0.16);
|
||
border-radius: 0.4rem;
|
||
padding: 0.05em 0.45em;
|
||
font-size: 0.85em;
|
||
color: #8a98b0;
|
||
text-decoration: none;
|
||
vertical-align: baseline;
|
||
}
|
||
.md .md-image::before {
|
||
content: "▣";
|
||
opacity: 0.6;
|
||
}
|
||
.md a.md-image:hover {
|
||
color: #c4b5fd;
|
||
border-color: rgba(196, 181, 253, 0.4);
|
||
}
|
||
|
||
.md strong { color: #f1f5f9; font-weight: 600; }
|
||
|
||
.md ul, .md ol { margin: 0.75em 0; padding-left: 1.35em; }
|
||
.md ul { list-style: disc; }
|
||
.md ol { list-style: decimal; }
|
||
.md li { margin: 0.3em 0; }
|
||
.md li::marker { color: #64748b; }
|
||
|
||
/* Aufgabenlisten aus "- [ ] …" */
|
||
.md li:has(> input[type="checkbox"]) {
|
||
list-style: none;
|
||
margin-left: -1.35em;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 0.5rem;
|
||
}
|
||
.md input[type="checkbox"] {
|
||
margin-top: 0.35em;
|
||
accent-color: #8b5cf6;
|
||
width: 0.9rem;
|
||
height: 0.9rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.md code {
|
||
background: rgba(255, 255, 255, 0.07);
|
||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||
border-radius: 0.35rem;
|
||
padding: 0.1em 0.35em;
|
||
font-family: var(--font-mono);
|
||
font-size: 0.85em;
|
||
color: #e9d5ff;
|
||
}
|
||
|
||
.md pre {
|
||
background: rgba(4, 6, 16, 0.75);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 0.75rem;
|
||
padding: 0.85rem 1rem;
|
||
margin: 0.9em 0;
|
||
overflow-x: auto;
|
||
}
|
||
.md pre code {
|
||
background: none;
|
||
border: none;
|
||
padding: 0;
|
||
color: #cbd5e1;
|
||
font-size: 0.8rem;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.md blockquote {
|
||
border-left: 2px solid rgba(167, 139, 250, 0.5);
|
||
padding-left: 0.9rem;
|
||
margin: 0.9em 0;
|
||
color: #94a3b8;
|
||
font-style: italic;
|
||
}
|
||
|
||
.md hr {
|
||
border: 0;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.09);
|
||
margin: 1.4em 0;
|
||
}
|
||
|
||
.md table {
|
||
width: 100%;
|
||
margin: 0.9em 0;
|
||
border-collapse: collapse;
|
||
font-size: 0.8rem;
|
||
display: block;
|
||
overflow-x: auto;
|
||
}
|
||
.md th, .md td {
|
||
border: 1px solid rgba(255, 255, 255, 0.09);
|
||
padding: 0.4rem 0.6rem;
|
||
text-align: left;
|
||
}
|
||
.md th {
|
||
background: rgba(255, 255, 255, 0.04);
|
||
color: #e2e8f0;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.md img {
|
||
max-width: 100%;
|
||
height: auto;
|
||
border-radius: 0.5rem;
|
||
}
|
||
|
||
/* ── Text & Akzente ───────────────────────────────────────── */
|
||
|
||
.text-gradient {
|
||
background: linear-gradient(120deg, #c4b5fd 0%, #67e8f9 45%, #f0abfc 100%);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
color: transparent;
|
||
}
|
||
|
||
.accent-bar {
|
||
background: linear-gradient(90deg, var(--accent-from, #8b5cf6), var(--accent-to, #d946ef));
|
||
}
|
||
|
||
.accent-dot {
|
||
box-shadow: 0 0 12px 1px currentColor;
|
||
}
|
||
|
||
/* ── Buttons ──────────────────────────────────────────────── */
|
||
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.5rem;
|
||
border-radius: 0.75rem;
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
padding: 0.55rem 1rem;
|
||
transition:
|
||
background-color 0.2s ease,
|
||
border-color 0.2s ease,
|
||
color 0.2s ease,
|
||
box-shadow 0.2s ease,
|
||
filter 0.2s ease;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
border: 1px solid transparent;
|
||
}
|
||
|
||
/**
|
||
* Auf Zeigergeraeten reichen die 36px, die sich aus Schriftgroesse und
|
||
* Innenabstand ergeben. Ein Finger trifft das nicht zuverlaessig – dort
|
||
* gilt die uebliche Untergrenze von 44px.
|
||
*
|
||
* Ueber "pointer: coarse" statt einer Breitenabfrage: entscheidend ist das
|
||
* Eingabegeraet, nicht die Fenstergroesse. Ein Tablet im Querformat ist
|
||
* breit und wird trotzdem mit dem Finger bedient.
|
||
*/
|
||
@media (pointer: coarse) {
|
||
.btn {
|
||
min-height: 2.75rem;
|
||
}
|
||
}
|
||
|
||
.btn:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.btn:focus-visible {
|
||
outline: 2px solid rgba(167, 139, 250, 0.8);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: linear-gradient(120deg, #7c3aed, #c026d3);
|
||
color: #fff;
|
||
box-shadow: 0 10px 30px -12px rgba(168, 85, 247, 0.9);
|
||
}
|
||
|
||
.btn-primary:hover:not(:disabled) {
|
||
filter: brightness(1.12);
|
||
box-shadow: 0 14px 36px -12px rgba(168, 85, 247, 1);
|
||
}
|
||
|
||
.btn-ghost {
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border-color: rgba(255, 255, 255, 0.1);
|
||
color: #cbd5f5;
|
||
}
|
||
|
||
.btn-ghost:hover:not(:disabled) {
|
||
background: rgba(255, 255, 255, 0.09);
|
||
color: #fff;
|
||
}
|
||
|
||
.btn-danger {
|
||
background: rgba(244, 63, 94, 0.12);
|
||
border-color: rgba(244, 63, 94, 0.35);
|
||
color: #fda4af;
|
||
}
|
||
|
||
.btn-danger:hover:not(:disabled) {
|
||
background: rgba(244, 63, 94, 0.22);
|
||
color: #fff;
|
||
}
|
||
|
||
/* ── Formularfelder ───────────────────────────────────────── */
|
||
|
||
.field {
|
||
width: 100%;
|
||
border-radius: 0.75rem;
|
||
background: rgba(8, 10, 24, 0.6);
|
||
border: 1px solid rgba(255, 255, 255, 0.09);
|
||
padding: 0.6rem 0.85rem;
|
||
font-size: 0.875rem;
|
||
color: #e6e8f5;
|
||
transition:
|
||
border-color 0.2s ease,
|
||
box-shadow 0.2s ease;
|
||
}
|
||
|
||
/**
|
||
* Unter 16px zoomt iOS Safari beim Hineintippen die ganze Seite heran und
|
||
* kehrt danach nicht von selbst zurueck – aus einem Formular wird so eine
|
||
* Schiebepartie. Deshalb auf Beruehrungsgeraeten volle 16px, unabhaengig
|
||
* von der Fensterbreite.
|
||
*/
|
||
@media (pointer: coarse) {
|
||
.field {
|
||
font-size: 1rem;
|
||
padding: 0.65rem 0.85rem;
|
||
}
|
||
}
|
||
|
||
.field::placeholder {
|
||
color: rgba(148, 163, 184, 0.6);
|
||
}
|
||
|
||
.field:focus {
|
||
outline: none;
|
||
border-color: rgba(167, 139, 250, 0.6);
|
||
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.18);
|
||
}
|
||
|
||
.field-label {
|
||
display: block;
|
||
font-size: 0.7rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: rgba(148, 163, 184, 0.85);
|
||
margin-bottom: 0.4rem;
|
||
}
|
||
|
||
select.field option {
|
||
background: #0a0c1a;
|
||
color: #e6e8f5;
|
||
}
|
||
|
||
/**
|
||
* Schieberegler. Der native Regler zeichnet die Spur rechts vom Griff in
|
||
* einem hellen Standardton - im dunklen Layout ein grelles Band. Deshalb
|
||
* vollstaendig selbst gezeichnet: der gefuellte Anteil kommt aus der
|
||
* Variablen --fill, die die Komponente aus dem Wert setzt.
|
||
*/
|
||
.range {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 100%;
|
||
height: 6px;
|
||
border-radius: 9999px;
|
||
cursor: pointer;
|
||
background: linear-gradient(
|
||
90deg,
|
||
#8b5cf6 0%,
|
||
#d946ef var(--fill, 0%),
|
||
rgba(255, 255, 255, 0.1) var(--fill, 0%),
|
||
rgba(255, 255, 255, 0.1) 100%
|
||
);
|
||
}
|
||
|
||
.range:focus-visible {
|
||
outline: 2px solid rgba(167, 139, 250, 0.8);
|
||
outline-offset: 4px;
|
||
}
|
||
|
||
.range::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
height: 16px;
|
||
width: 16px;
|
||
border-radius: 9999px;
|
||
background: #f5f3ff;
|
||
border: 2px solid #7c3aed;
|
||
box-shadow: 0 0 14px -2px rgba(168, 85, 247, 0.9);
|
||
cursor: grab;
|
||
}
|
||
|
||
.range::-webkit-slider-thumb:active {
|
||
cursor: grabbing;
|
||
}
|
||
|
||
.range::-moz-range-thumb {
|
||
height: 14px;
|
||
width: 14px;
|
||
border-radius: 9999px;
|
||
background: #f5f3ff;
|
||
border: 2px solid #7c3aed;
|
||
box-shadow: 0 0 14px -2px rgba(168, 85, 247, 0.9);
|
||
cursor: grab;
|
||
}
|
||
|
||
.range::-moz-range-track {
|
||
background: transparent;
|
||
}
|
||
|
||
/* Ein 16px-Griff ist mit dem Finger kaum zu fassen. Die Spur bleibt
|
||
schmal, damit der Regler nicht ploetzlich klobig wirkt. */
|
||
@media (pointer: coarse) {
|
||
.range {
|
||
height: 10px;
|
||
}
|
||
|
||
.range::-webkit-slider-thumb {
|
||
height: 24px;
|
||
width: 24px;
|
||
}
|
||
|
||
.range::-moz-range-thumb {
|
||
height: 22px;
|
||
width: 22px;
|
||
}
|
||
}
|
||
}
|