Files
AirCardIOS/web/src/logos/logos.css
T
ClaudeandClaude Opus 5.5 a75be2d44c feat(logos): Infozeile mit Melden, Löschen und Admin-Aktionen
Task 9: die Infozeile zum gewählten Logo unter dem Raster (Spec § 3 und § 6).

- logos/logo-info.ts (neu): „<Name> · von <Anzeigename> · ♥ <Zahl>“ und „Melden“ (auch für Gäste; ein ausgeblendetes Logo lässt
  sich nicht melden). Der Hochlader sieht „Löschen“ mit Rückfrage (Titel mit Namen, Folgen im Text), Admins „<n> Meldungen“ mit den
  Gründen als Text (listLogoReports, einmal je Logo und Stand), „Ausblenden“/„Einblenden“ und „Löschen“. Gelöscht: deleteLogo,
  forgetLogo, Kachel weg, onSelect(null); ein-/ausgeblendet: setLogoVisibility, forgetLogo (ein Null-Bild bleibt nicht im Zwischen-
  speicher), die Zeile des Pickers wird fortgeschrieben (Einblenden setzt reportCount auf 0). Gewähltes Logo nicht auffindbar: „Das
  gewählte Logo ist nicht mehr verfügbar.“ (ein Netzfehler von getLogo sagt nichts). Zugleich die Meldezeile (role="alert") für Fehler
  von Herz, Hochladen und diesen Aktionen; ein Wechsel der Auswahl räumt sie ab. Immer nur eine Aktion zugleich; die Rechte werden
  nach der Rückfrage noch einmal geprüft; der Aufbau steht einmal da, update() ändert nur Texte und Sichtbarkeit (Fokus bleibt).
- logos/logo-picker.ts: hängt die Infozeile als Letztes unter das Raster (info.el). Herz- und Upload-Fehler gehen in die Infozeile,
  der Statusbereich des Pickers bleibt den Ladefehlern der Liste vorbehalten (ein Klick auf ein Herz löscht ihren Text nicht mehr).
  Aus dem Review von Task 8: requireLogin, das wirft, zeigt den Grund statt einer unbehandelten Ablehnung; select() nach dispose()
  ist wirkungslos. Löschen entfernt Kachel, Zeile, Herz und Eintrag der Liste, setzt den Cursor von „Mehr laden“ auf das Logo davor
  (Appwrite lehnt einen Cursor auf eine gelöschte Zeile ab), hebt die Auswahl über onSelect(null) auf und holt den Fokus nach
  „Kein Logo“, wenn er auf „Löschen“ lag. LogoPickerDeps erweitert um reportLogo, deleteLogo, setLogoVisibility, listLogoReports,
  confirm und reportDialog.
- community/report-dialog.ts: subject 'card' (Standard, unverändert) oder 'logo': Überschrift „Logo melden“, Einleitung „dieses
  Logo“, vier Gründe ohne „Echte Kartendaten“, Duplikat-Dank „Danke, du hast dieses Logo schon gemeldet.“. Der ganze Dialog geht
  durch t()/msg(); Fehler des Dienstes über apiErrorText.
- logos/logos.css: das Herz sitzt in der Textzeile unter der Bildfläche (nicht mehr auf dem Logo, folgt dem Theme), die Kachel füllt
  die Zelle (height: 100%), damit „Kein Logo“ so hoch ist wie sein Nachbar; Stile der Infozeile.
- editor/editor-page.ts: Aus dem Review von Task 8: der Ladefehler der Logos verschwindet, sobald ein Logo danach lädt, aber nur
  der, den der Logo-Zustand selbst ins Fehlerfeld geschrieben hat; jeder andere Text im Feld bleibt.
- i18n/en.ts: englische Texte zu allen neuen Beschriftungen und zum Melden-Dialog.
- Tests: logo-info.test.ts (neu), logo-picker.test.ts (Infozeile, Löschen, Ein-/Ausblenden, Fehler, Cursor, Fokus), report-dialog.test.ts
  (Logo und Englisch), editor-page.test.ts (Fehlerfeld). Vitest 52 Dateien, 1764 Tests grün, tsc und Build sauber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:21:33 +00:00

465 lines
9.8 KiB
CSS

/* Community-Logos. Dialog „Logo hochladen“: Hülle, Felder, Pflicht-Häkchen, Meldungen und Knöpfe kommen aus community.css
(Melden-/Veröffentlichen-Dialog) und account.css; hier steht nur, was neu ist: das Datei-Feld und die Vorschau.
Weiter unten der Bereich „Logo unten rechts“ im Editor (Raster, Sortierung, Herzen, Infozeile); seine Kacheln beruhen auf `.tile` aus editor.css. */
/* Etwas breiter als der Veröffentlichen-Dialog (480 px): zwei Vorschauen nebeneinander. Mit zwei Klassen, damit die Breite
nicht von der Reihenfolge der Stylesheets abhängt. */
.report-dialog.logo-dialog {
width: min(520px, calc(100vw - 32px));
}
/* Datei-Feld: Rahmen und Fokus wie ein Textfeld (.auth-input), der Knopf darin wie die übrigen Knöpfe */
.auth-input.logo-file {
padding: 6px 8px;
cursor: pointer;
}
.logo-file::file-selector-button {
min-height: 32px;
margin: 0 12px 0 0;
padding: 0 14px;
border: 1px solid var(--line-strong);
border-radius: 8px;
background: transparent;
color: var(--text);
font: inherit;
font-weight: 600;
cursor: pointer;
}
.logo-file:hover:not(:disabled)::file-selector-button {
border-color: var(--sel-line);
background: var(--panel-2);
}
.logo-file:disabled {
opacity: 0.55;
cursor: not-allowed;
}
/* Vorschau: dasselbe Logo einmal auf dunklem und einmal auf hellem Grund (nebeneinander, solange es ein Logo gibt) */
.logo-previews {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.logo-previews:empty {
display: none;
}
.logo-preview {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
margin: 0; /* <figure> hat sonst einen Außenabstand */
}
.logo-preview-surface {
display: flex;
align-items: center;
justify-content: center;
height: 96px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 12px;
}
/* Die Karte hängt nicht vom Theme ab: dunkler Kartengrund wie im Renderer (#1B2738), heller Grund Weiß. */
.logo-preview-dark .logo-preview-surface {
background: #1b2738;
}
.logo-preview-light .logo-preview-surface {
background: #ffffff;
}
.logo-preview-img,
.logo-preview-mask,
.logo-tile-img,
.logo-tile-mask {
display: block;
width: 100%;
height: 100%;
}
.logo-preview-img,
.logo-tile-img {
object-fit: contain;
}
/* Einfarbig: nur die Deckkraft des PNGs zählt (Maske auf dessen Object-URL, `--logo-url` setzt der Dialog bzw. die Kachel), die
Farbe kommt als Hintergrund (Weiß auf dunklem, #111111 auf hellem Grund im Dialog; Weiß in der Kachel, ebenfalls gesetzt). */
.logo-preview-mask,
.logo-tile-mask {
-webkit-mask-image: var(--logo-url);
mask-image: var(--logo-url);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-size: contain;
mask-size: contain;
}
.logo-preview-caption {
color: var(--muted);
font-size: 0.85rem;
text-align: center;
}
/* „Einfarbig“: das Häkchen im Stil des Pflicht-Häkchens; gesperrt (noch keine Datei oder keine transparenten Bereiche) blasser */
.logo-mono .auth-hint {
margin: 6px 0 0 4px;
}
.logo-mono .publish-consent:has(input:disabled) {
opacity: 0.55;
cursor: not-allowed;
}
/* Bereich „Logo unten rechts“ im Editor */
.logo-picker {
display: flex;
flex-direction: column;
gap: 12px;
}
.logo-picker-head {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 10px;
}
/* wie `.panel-subtitle` */
.logo-picker-title {
margin: 0;
padding-left: 4px;
font-size: 1.05rem;
font-weight: 600;
line-height: 1.2;
}
.logo-badge {
padding: 2px 9px;
border: 1px solid var(--line);
border-radius: 999px;
background: var(--panel-2);
color: var(--muted);
font-size: 0.78rem;
font-weight: 700;
}
/* Mit zwei Klassen, damit `margin: 0` aus `.auth-btn` nicht von der Reihenfolge der Stylesheets abhängt (wie beim Dialog). */
.auth-btn.logo-upload {
display: inline-flex;
align-items: center;
gap: 6px;
margin-left: auto;
}
.logo-picker-note {
margin: -4px 0 0;
padding-left: 4px;
color: var(--muted);
font-size: 0.85rem;
}
/* Sortierung: Umschalter wie die gewählte Kachel (Rahmen und Grund der Auswahl) */
.logo-sort {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.logo-sort-btn {
appearance: none;
min-height: 34px;
margin: 0;
padding: 0 14px;
border: 1px solid var(--line);
border-radius: 999px;
background: transparent;
color: var(--text);
font: inherit;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.12s, border-color 0.12s;
}
.logo-sort-btn:hover {
border-color: var(--line-strong);
background: var(--panel);
}
.logo-sort-btn[aria-pressed='true'] {
border-color: var(--sel-line);
background: var(--sel-bg);
}
/* Raster: ab 1024 px ist die Optionsspalte schmal (zwei Spalten). Darunter steht sie in voller Breite, auch auf dem Handy unter
900 px: so viele Spalten, wie mit mindestens 140 px je Kachel passen. */
.logo-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 10px;
margin: 0;
padding: 0;
list-style: none;
}
@media (min-width: 1024px) {
.logo-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.logo-cell {
position: relative;
min-width: 0;
}
/* `height: 100%` füllt die Zelle (das Raster streckt sie auf die Höhe der Reihe): „Kein Logo“ ist so hoch wie sein Nachbar. */
.logo-tile {
display: flex;
flex-direction: column;
gap: 2px;
width: 100%;
height: 100%;
min-width: 0;
padding: 8px;
}
/* Die Bildfläche steht immer auf dem dunklen Kartengrund (#1B2738), in beiden Themes: so sieht das Logo aus wie auf der Karte. */
.logo-tile-art {
display: flex;
align-items: center;
justify-content: center;
height: 72px;
margin-bottom: 6px;
padding: 10px;
border-radius: 8px;
background: #1b2738;
color: #9db0c8;
}
.logo-tile-none .logo-tile-art {
background: var(--panel-2);
color: var(--muted);
}
.logo-tile-art .logo-none-icon {
width: 36px;
height: 36px;
}
/* Bild nicht verfügbar (Ladefehler, nicht lesbar, zu groß): ein neutraler gestrichelter Platzhalter */
.logo-tile-art[data-state='unusable']::after {
content: '';
width: 44%;
height: 56%;
border: 2px dashed currentColor;
border-radius: 6px;
opacity: 0.45;
}
.logo-tile-name,
.logo-tile-by {
display: block;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.logo-tile-name {
padding: 0 2px;
font-size: 0.95rem;
font-weight: 700;
line-height: 1.3;
}
.logo-tile-by {
padding: 0 2px;
color: var(--muted);
font-size: 0.8rem;
line-height: 1.3;
}
/* Name und „von …“ lassen rechts Platz für das Herz (nur Logo-Kacheln: „Kein Logo“ hat keins). */
.logo-tile:not(.logo-tile-none) .logo-tile-name,
.logo-tile:not(.logo-tile-none) .logo-tile-by {
padding-right: 64px;
}
/* Herz: ein eigener Knopf neben dem Auswahlknopf, am Ende der Textzeile unter der Bildfläche (liegt also nicht auf dem Logo, sondern
auf dem Grund der Kachel und folgt dem Theme), mittig neben Name und „von …“. Gedrückt: gefülltes Herz. */
.logo-heart {
appearance: none;
position: absolute;
right: 8px;
bottom: 11px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
min-width: 44px;
min-height: 32px;
margin: 0;
padding: 0 9px;
border: 1px solid var(--line);
border-radius: 999px;
background: transparent;
color: var(--muted);
font: inherit;
font-size: 0.8rem;
font-weight: 700;
font-variant-numeric: tabular-nums;
cursor: pointer;
transition: background-color 0.12s, border-color 0.12s;
}
.logo-heart:hover {
border-color: var(--line-strong);
background: var(--panel-2);
}
.logo-heart[aria-pressed='true'] {
border-color: var(--danger-text);
color: var(--danger-text);
}
.logo-heart[aria-pressed='true'] .icon {
fill: currentColor;
}
.logo-heart .icon {
flex: none;
font-size: 1rem;
}
/* Leer, Fehler, „Nochmal versuchen“ und „Mehr laden“ */
.logo-empty {
margin: 0;
padding-left: 4px;
color: var(--muted);
font-size: 0.9rem;
}
.logo-empty:empty {
display: none;
}
.logo-retry,
.logo-more {
align-self: center;
}
/* Infozeile zum gewählten Logo (unter dem Raster): Karte mit Name, Urheber und Herzen, Knöpfen und, für Admins, den Meldungen.
Ohne Auswahl ist die Karte verborgen; die beiden Meldezeilen (Hinweis, Fehler) sind immer da. */
.logo-info {
display: flex;
flex-direction: column;
gap: 8px;
}
/* `hidden` gilt auch dort, wo ein Stil `display` setzt (und außerhalb des Editors, der `.editor [hidden]` mitbringt). */
.logo-info [hidden] {
display: none !important;
}
.logo-info-card {
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px;
border: 1px solid var(--line);
border-radius: var(--radius);
background: var(--panel);
}
.logo-info-text {
margin: 0;
color: var(--muted);
font-size: 0.9rem;
line-height: 1.35;
overflow-wrap: anywhere;
}
.logo-info-name {
color: var(--text);
font-size: 0.95rem;
font-weight: 700;
}
.logo-info-hearts {
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.logo-info-hidden {
align-self: flex-start;
}
.logo-info-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.logo-info-admin {
display: flex;
flex-direction: column;
gap: 6px;
padding-top: 10px;
border-top: 1px solid var(--line);
}
.logo-info-reports {
margin: 0;
font-size: 0.9rem;
font-weight: 700;
}
.logo-info-reasons {
margin: 0;
padding-left: 20px;
color: var(--muted);
font-size: 0.9rem;
}
.logo-info-reasons:empty,
.logo-info-note:empty {
display: none;
}
.logo-info-note {
margin: 0;
color: var(--muted);
font-size: 0.85rem;
}
.logo-info-gone {
padding-left: 4px;
color: var(--muted);
}
@media (prefers-reduced-motion: reduce) {
.logo-sort-btn,
.logo-heart {
transition: none;
}
}