Nach dem Oeffnen der Schublade lag ein blauer Rahmen um "Dashboard", nach dem Schliessen um den Hamburger — bis man woanders hinklickte. Der Ring hat einen praktischen Grund, aber nur fuer die Tastatur: Er zeigt, wo man gerade steht. Nach einem Mausklick weiss man das ohnehin. Ursache war unser eigener Fokus-Sprung, nicht die CSS-Regel. Die benutzt laengst :focus-visible. Setzt aber ein Skript den Fokus — Fokusfalle beim Oeffnen, Rueckgabe an den Ausloeser beim Schliessen —, wertet Chrome das wie Tastaturbedienung und zeigt den Ring trotzdem. Drei Aenderungen: - Beim Oeffnen bekommt der Dialograhmen den Fokus statt des ersten Links. Das ist ohnehin das uebliche Vorgehen fuer einen Dialog; die Sprachausgabe liest den Dialog an, der erste Tabulatorschritt landet auf dem ersten Link. - Elemente mit tabindex="-1" bekommen keinen Ring mehr. Sie sind aus der Tabulatorreihenfolge heraus und werden nur per Skript fokussiert — ein Ring um den ganzen Schubladenrahmen waere dort Laerm. - AppShell schreibt die zuletzt benutzte Eingabeart als data-eingabe an das Wurzelelement. Solange sie "zeiger" ist, entfaellt der Ring an Knoepfen und Links. Eingabefelder sind ausgenommen, dort gehoert er zum Zustand "hier wird gerade geschrieben". Der erste Tastendruck schaltet ihn ueberall zurueck. Dabei ist eine Luecke in der Fokusfalle aufgefallen und behoben: Mit dem Fokus auf dem Rahmen lief Umschalt+Tab aus dem Dialog heraus, weil die Falle nur den ersten Link als Umkehrpunkt kannte. Der Rahmen steht im Dokument vor seinen Kindern und gehoert dazu — sonst waere die Zusage von aria-modal gebrochen. Nachgemessen statt begutachtet, alle Faelle: Maus offen → Rahmen, kein Ring Maus zu → Hamburger, kein Ring Tastatur offen → Rahmen, kein Ring Tastatur erstes → "Dashboard", RING Tastatur rueckwaerts → "Komponenten", RING (Falle haelt) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX
200 lines
6.8 KiB
CSS
200 lines
6.8 KiB
CSS
@import "tailwindcss";
|
||
@import "../ui/theme.css";
|
||
|
||
/*
|
||
* Schrift: Instrument Sans, selbst gehostet — identisch zur Software.
|
||
*
|
||
* Die Dateien stammen aus `apps/web/public/fonts/` von HiveGrid Cloud, damit
|
||
* Schriftschnitt und Metrik exakt übereinstimmen. Variable Schrift (400–700).
|
||
*
|
||
* Warum das nötig war: `ui/theme.css` verlangt für alle Subframe-Typo-Token
|
||
* bereits "Instrument Sans", die Schrift wurde aber nie geladen — und der
|
||
* Fließtext war zusätzlich auf Inter gesetzt. Ergebnis waren zwei verschiedene
|
||
* Schriften auf einer Seite, keine davon die der Software.
|
||
*/
|
||
@font-face {
|
||
font-family: "Instrument Sans";
|
||
font-style: normal;
|
||
font-weight: 400 700;
|
||
font-stretch: 100%;
|
||
font-display: swap;
|
||
src: url("/fonts/instrument-sans-latin.woff2") format("woff2");
|
||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
||
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
|
||
U+2212, U+2215, U+FEFF, U+FFFD;
|
||
}
|
||
@font-face {
|
||
font-family: "Instrument Sans";
|
||
font-style: normal;
|
||
font-weight: 400 700;
|
||
font-stretch: 100%;
|
||
font-display: swap;
|
||
src: url("/fonts/instrument-sans-latin-ext.woff2") format("woff2");
|
||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
|
||
U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
|
||
U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||
}
|
||
|
||
/*
|
||
* Einige Subframe-Komponenten verweisen noch direkt auf Inter. Damit sie nicht
|
||
* auf die Systemschrift zurückfallen, zeigt der Name auf dieselbe Datei —
|
||
* so hält es die Software auch.
|
||
*/
|
||
@font-face {
|
||
font-family: "Inter";
|
||
font-style: normal;
|
||
font-weight: 400 700;
|
||
font-stretch: 100%;
|
||
font-display: swap;
|
||
src: url("/fonts/instrument-sans-latin.woff2") format("woff2");
|
||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
||
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
|
||
U+2212, U+2215, U+FEFF, U+FFFD;
|
||
}
|
||
|
||
@theme {
|
||
--default-font-family: "Instrument Sans", ui-sans-serif, system-ui,
|
||
-apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||
--font-inter: var(--default-font-family);
|
||
--font-sans: var(--default-font-family);
|
||
}
|
||
|
||
/*
|
||
* HiveGrid Analytics Dashboard
|
||
* ---------------------------------------------------------------
|
||
* Chrome (Sidebar, Karten, Buttons, Tabellen) nutzt die Subframe-
|
||
* Design-Tokens aus ui/theme.css.
|
||
* Die Datenvisualisierung folgt IBM Carbon (siehe docs/DESIGN-SYSTEM.md)
|
||
* und definiert eigene Tokens, damit Charts unabhaengig vom Brand-Theme
|
||
* lesbar und barrierearm bleiben.
|
||
*/
|
||
|
||
:root {
|
||
/* --- Carbon Data-Viz Tokens (Light Theme) --- */
|
||
--dv-grid: #e0e0e0; /* cds-layer-accent-01 - Gridlines */
|
||
--dv-axis: #8d8d8d; /* cds-border-strong-01 - Achsenlinie */
|
||
--dv-text: #161616; /* cds-text-primary - Chart-Titel */
|
||
--dv-text-secondary: #525252;/* cds-text-secondary - Achsenlabels */
|
||
--dv-tooltip-bg: #393939; /* cds-background-inverse */
|
||
--dv-tooltip-fg: #ffffff; /* cds-text-inverse */
|
||
|
||
/* Vertikaler Rhythmus fuer Dashboard-Raster (8px-System) */
|
||
--dv-card-padding: 16px;
|
||
--dv-card-gap: 16px;
|
||
}
|
||
|
||
body {
|
||
background: var(--color-neutral-50, #fafafa);
|
||
color: var(--color-text-neutral-primary);
|
||
font-family: var(--default-font-family);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
/*
|
||
* Fokus-Sichtbarkeit.
|
||
* Eine Regel fuer alles Interaktive statt focus:-Klassen an jedem Element.
|
||
* :focus-visible zeigt den Ring nur bei Tastaturbedienung, nicht beim Klick.
|
||
*/
|
||
/*
|
||
* `[tabindex="-1"]` ist ausgenommen: Solche Elemente sind aus der
|
||
* Tabulatorreihenfolge heraus und bekommen den Fokus ausschliesslich per
|
||
* Skript — bei uns der Rahmen der Schublade, damit die Sprachausgabe den
|
||
* Dialog anliest. Ein Ring um den ganzen Rahmen waere dort keine Orientierung,
|
||
* sondern Laerm; die Orientierung gibt der naechste Tabulatorschritt.
|
||
*/
|
||
:where(
|
||
a,
|
||
button,
|
||
[role="button"],
|
||
input,
|
||
select,
|
||
textarea,
|
||
[tabindex]:not([tabindex="-1"])
|
||
):focus-visible {
|
||
outline: none;
|
||
box-shadow: var(--shadow-focus, 0 0 0 3px rgb(7 145 240 / 0.4));
|
||
border-radius: var(--radius-sm, 4px);
|
||
}
|
||
|
||
/*
|
||
* Nachtrag zu :focus-visible — ein Fall, den der Browser falsch einschaetzt.
|
||
*
|
||
* Setzt ein Skript den Fokus, behandelt Chrome das wie Tastaturbedienung. Beim
|
||
* Oeffnen und Schliessen der Schublade tun wir genau das (Fokusfalle rein,
|
||
* Fokus zurueck auf den Hamburger) — nach einem Mausklick stand der Ring
|
||
* danach sichtbar da, bis man woanders hinklickte.
|
||
*
|
||
* `data-eingabe` schreibt AppShell mit: "zeiger" oder "tastatur", je nach
|
||
* letzter Eingabe. Der Ring entfaellt nur waehrend Zeigerbedienung und nur an
|
||
* Knoepfen und Links. Eingabefelder sind ausgenommen — dort gehoert der Ring
|
||
* zum Zustand "hier wird gerade geschrieben", auch nach einem Klick. Der erste
|
||
* Tastendruck schaltet ihn ueberall wieder ein.
|
||
*/
|
||
:root[data-eingabe="zeiger"]
|
||
:where(a, button, [role="button"], [tabindex]:not([tabindex="-1"])):focus-visible {
|
||
box-shadow: none;
|
||
}
|
||
|
||
/*
|
||
* Der Einklapp-Knopf der Seitenleiste ragt bauartbedingt 17 px nach rechts
|
||
* heraus. Faehrt die Leiste unterhalb `md` als Schublade nach links aus dem
|
||
* Bild, bleibt genau dieser Ueberstand bei x = -16 sichtbar: ein angeschnittener
|
||
* Knopf, der anklickbar ist und nichts Sichtbares bewirkt, weil er nur den
|
||
* Zustand der unsichtbaren Schublade aendert.
|
||
*
|
||
* Einklappen ergibt in einer Schublade ohnehin keinen Sinn — dort oeffnet und
|
||
* schliesst der Hamburger in der Kopfzeile. Also unterhalb `md` ausblenden.
|
||
* `group/7ce7d712` ist der von Subframe erzeugte Marker des Knopf-Wrappers in
|
||
* SidebarWithSections; er aendert sich nur bei einem bewussten Resync.
|
||
*/
|
||
@media (max-width: 767px) {
|
||
[data-shell-sidebar] [class*="group/7ce7d712"] {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
/* Radix-Portale (Dropdowns, Tooltips) ueber den Sticky-Header heben. */
|
||
[data-radix-popper-content-wrapper] {
|
||
z-index: 1000 !important;
|
||
}
|
||
|
||
/* Recharts: Fokus-Outline der SVG-Wrapper unterdruecken, Cursor vereinheitlichen. */
|
||
.recharts-wrapper:focus,
|
||
.recharts-surface:focus {
|
||
outline: none;
|
||
}
|
||
.recharts-sector,
|
||
.recharts-rectangle,
|
||
.recharts-dot {
|
||
transition: opacity 140ms ease, filter 140ms ease;
|
||
}
|
||
|
||
/* Waagerecht scrollende Leisten ohne sichtbaren Balken: Der Balken wuerde
|
||
unter einem 40 px hohen Reiterband mehr stoeren als helfen; dass gescrollt
|
||
werden kann, zeigt der angeschnittene letzte Reiter. */
|
||
.dv-scroll-x {
|
||
scrollbar-width: none;
|
||
}
|
||
.dv-scroll-x::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
/* Scrollbars in Karten dezent halten. */
|
||
.dv-scroll::-webkit-scrollbar {
|
||
width: 8px;
|
||
height: 8px;
|
||
}
|
||
.dv-scroll::-webkit-scrollbar-thumb {
|
||
background: #d4d4d4;
|
||
border-radius: 999px;
|
||
}
|
||
.dv-scroll::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
@media print {
|
||
.dv-no-print {
|
||
display: none !important;
|
||
}
|
||
}
|