fix(ui): remove sub-AA opacity dimming from informational text

The muted-foreground token is AA-tuned in both themes; the /70 and /80
opacity modifiers silently undid that (2.9-4.2:1 measured). Every
informational text node drops the modifier for the full token: sleep
hypnogram captions, learning-gate caveat, integration-card setup hints,
module lock notes, glucose panel and token-footer meta, invite-token
hint, wizard hint, notification channel labels and disabled notes (the
disabled state reads through the note and the disabled switches, not
through dimming), plus the resting state of the timestamp, suggestion
chip and sidebar-collapse controls. User-authored mood notes move to
foreground text-sm — user data is never muted. Decorative non-text
strokes (sparkline grid, calendar dot, titration connector) keep their
opacity; icons keep the /60 graphics floor.
This commit is contained in:
Marc-André Bombeck committed 2026-07-05 15:03:01 +02:00
1 parent 5cc7fc6607
commit 4846dcb478
20 files changed
+34 -38

No files matched your search

+7 -12
View File
@@ -205,17 +205,12 @@ export default function NotificationsPage() {
key={ch.id}
className="px-4 py-3 text-center text-xs font-medium tracking-wider uppercase"
>
<span
className={
ch.enabled
? "text-muted-foreground"
: "text-muted-foreground/70"
}
>
{ch.label}
</span>
{/* Disabled state reads through the note + the disabled
switches below — never through sub-AA opacity dimming
on the label or the note itself. */}
<span className="text-muted-foreground">{ch.label}</span>
{!ch.enabled && (
<span className="text-muted-foreground/70 block text-[10px] normal-case">
<span className="text-muted-foreground block text-xs normal-case">
{t("notifications.channelDisabled")}
</span>
)}
@@ -296,11 +291,11 @@ export default function NotificationsPage() {
>
<Label
htmlFor={switchId}
className={`text-sm ${disabled ? "text-muted-foreground/70" : ""}`}
className={`text-sm ${disabled ? "text-muted-foreground" : ""}`}
>
{ch.label}
{disabled && (
<span className="text-muted-foreground/70 ml-1 text-xs">
<span className="text-muted-foreground ml-1 text-xs">
({t("notifications.channelDisabled")})
</span>
)}
@@ -325,7 +325,7 @@ export function InviteTokensSection() {
<p className="text-muted-foreground mt-1 pl-7 text-xs">
{t("admin.invites.description")}
</p>
<p className="text-muted-foreground/80 mt-1 flex items-center gap-1.5 pl-7 text-xs">
<p className="text-muted-foreground mt-1 flex items-center gap-1.5 pl-7 text-xs">
<ShieldCheck className="size-3.5 shrink-0" aria-hidden="true" />
{t("admin.invites.adminOnlyHint")}
</p>
+1 -3
View File
@@ -289,9 +289,7 @@ function EpisodeGroup({
aria-hidden="true"
/>
{title}
<span className="text-muted-foreground/70 normal-case">
({parents.length})
</span>
<span className="normal-case">({parents.length})</span>
</button>
{open ? grid : null}
</section>
@@ -97,7 +97,7 @@ export function GuidedQuestionsCard({
disabled={disabled}
onClick={onDismissAll}
className={cn(
"text-muted-foreground/70 hover:text-foreground inline-flex min-h-8 items-center rounded-full px-2.5 text-xs",
"text-muted-foreground hover:text-foreground inline-flex min-h-8 items-center rounded-full px-2.5 text-xs",
"hover:bg-dracula-purple/10 focus-visible:ring-dracula-purple/40 focus-visible:ring-2 focus-visible:outline-none",
"disabled:opacity-60",
)}
@@ -714,7 +714,7 @@ function BubbleTimestamp({
aria-label={t("insights.coach.messageTimeLabel", { time: label })}
onClick={() => setOpen((o) => !o)}
onBlur={() => setOpen(false)}
className="peer text-muted-foreground/70 hover:text-muted-foreground focus-visible:ring-ring/50 inline-flex size-11 items-center justify-center rounded outline-none focus-visible:ring-2 sm:size-8"
className="peer text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 inline-flex size-11 items-center justify-center rounded outline-none focus-visible:ring-2 sm:size-8"
>
<Clock className="size-3" aria-hidden="true" />
</button>
@@ -29,7 +29,7 @@ export function MessageTokenFooter({ tokens, model }: MessageTokenFooterProps) {
return (
<p
data-slot="coach-token-footer"
className="text-muted-foreground/80 text-[11px] tabular-nums"
className="text-muted-foreground text-[11px] tabular-nums"
>
{label}
</p>
@@ -223,7 +223,7 @@ export function GlucoseClinicalPanel() {
the data. */}
{clinical.isSpotEstimate ? (
<p
className="text-muted-foreground/80 text-xs"
className="text-muted-foreground text-xs"
data-slot="glucose-spot-caveat"
>
{t("insights.bloodGlucose.clinical.spotCaveat")}
+2 -2
View File
@@ -264,7 +264,7 @@ export function SleepHypnogram({ session }: SleepHypnogramProps) {
{session.source ? (
<span
data-slot="sleep-hypnogram-source"
className="text-muted-foreground/70 text-xs"
className="text-muted-foreground text-xs"
>
{t("insights.sleep.hypnogram.source", {
source: session.source,
@@ -278,7 +278,7 @@ export function SleepHypnogram({ session }: SleepHypnogramProps) {
{session.reconstructed === true && hasBreakdown ? (
<span
data-slot="sleep-hypnogram-estimate-note"
className="text-muted-foreground/70 text-xs"
className="text-muted-foreground text-xs"
>
{t("insights.sleep.hypnogram.estimateNote")}
</span>
+1 -1
View File
@@ -406,7 +406,7 @@ export function SidebarNav() {
<button
onClick={toggleCollapsed}
className={cn(
"text-muted-foreground/80 hover:text-foreground hover:bg-accent z-20 rounded-md p-1 transition-colors",
"text-muted-foreground hover:text-foreground hover:bg-accent z-20 rounded-md p-1 transition-colors",
className,
)}
aria-label={t("nav.collapseSidebar")}
@@ -538,7 +538,7 @@ function WizardDialogShell({
knows what the Next tap leads to (or, on the final
slot, that Next saves). */}
<p
className="text-muted-foreground/80 flex items-center gap-0.5 pt-0.5 text-xs"
className="text-muted-foreground flex items-center gap-0.5 pt-0.5 text-xs"
data-slot="wizard-next-hint"
>
<ChevronRight
+5 -2
View File
@@ -1081,9 +1081,12 @@ function MoodNoteText({
ref={paragraphRef}
data-testid="mood-note-text"
className={
// User-authored content renders in foreground — user data is never
// muted (the row's timestamp stays muted); italic keeps the note
// visually distinct from app copy.
expanded
? "text-muted-foreground/80 text-xs break-words whitespace-pre-wrap italic"
: "text-muted-foreground/80 line-clamp-2 text-xs italic"
? "text-foreground text-sm break-words whitespace-pre-wrap italic"
: "text-foreground line-clamp-2 text-sm italic"
}
>
{note}
@@ -328,7 +328,7 @@ export function FitbitCard({
/>
</div>
</div>
<p className="text-muted-foreground/80 text-xs">
<p className="text-muted-foreground text-xs">
{t("settings.integrationCredentialsHint")}
</p>
<div className="flex justify-end">
@@ -359,7 +359,7 @@ export function GoogleHealthCard({
/>
</div>
</div>
<p className="text-muted-foreground/80 text-xs">
<p className="text-muted-foreground text-xs">
{t("settings.integrationCredentialsHint")}
</p>
<div className="flex justify-end">
@@ -282,7 +282,7 @@ export function NightscoutCard({ enabled = true }: { enabled?: boolean }) {
autoCapitalize="none"
enterKeyHint="done"
/>
<p className="text-muted-foreground/80 text-xs">
<p className="text-muted-foreground text-xs">
{t("settings.nightscoutTokenHelp")}
</p>
</div>
@@ -292,7 +292,7 @@ export function NightscoutCard({ enabled = true }: { enabled?: boolean }) {
<Label htmlFor="nightscout-private" className="text-sm">
{t("settings.nightscoutPrivateHost")}
</Label>
<p className="text-muted-foreground/80 text-xs">
<p className="text-muted-foreground text-xs">
{t("settings.nightscoutPrivateHostHelp")}
</p>
</div>
@@ -322,7 +322,7 @@ export function OAuthProviderCard({
/>
</div>
</div>
<p className="text-muted-foreground/80 text-xs">
<p className="text-muted-foreground text-xs">
{t("settings.integrationCredentialsHint")}
</p>
<div className="flex justify-end">
@@ -357,7 +357,7 @@ export function OAuthProviderCard({
{serverUnavailable && (
<p
className="text-muted-foreground/80 text-xs"
className="text-muted-foreground text-xs"
data-testid={`${provider}-unavailable`}
>
{t(`${i18nPrefix}Unavailable`)}
@@ -54,7 +54,7 @@ export function IntegrationCardDescription({
return (
<>
<p className="text-foreground">{t(`${i18nPrefix}Description`)}</p>
<p className="text-muted-foreground/80">
<p className="text-muted-foreground">
{t(`${i18nPrefix}DescriptionSecondary`)}{" "}
<a
href={integrationDocsHref(provider)}
@@ -309,7 +309,7 @@ export function WhoopCard({
/>
</div>
</div>
<p className="text-muted-foreground/80 text-xs">
<p className="text-muted-foreground text-xs">
{t("settings.integrationCredentialsHint")}
</p>
<div className="flex justify-end">
@@ -353,7 +353,7 @@ export function WithingsCard({
/>
</div>
</div>
<p className="text-muted-foreground/80 text-xs">
<p className="text-muted-foreground text-xs">
{t("settings.integrationCredentialsHint")}
</p>
<div className="flex justify-end">
@@ -106,13 +106,13 @@ export function ModuleToggleRow({
)}
<p className="text-muted-foreground text-xs">{description}</p>
{locked && lockedNote ? (
<p className="text-muted-foreground/80 text-xs">{lockedNote}</p>
<p className="text-muted-foreground text-xs">{lockedNote}</p>
) : null}
</div>
</div>
{operatorDisabled ? (
// Operator kill-switch: read-only note, no toggle.
<span className="text-muted-foreground/80 mt-0.5 shrink-0 text-xs">
<span className="text-muted-foreground mt-0.5 shrink-0 text-xs">
{operatorNote}
</span>
) : managed ? (
+1 -1
View File
@@ -83,7 +83,7 @@ export function LearningGate({
{caveat ? (
<p
data-slot="learning-gate-caveat"
className="text-muted-foreground/80 text-xs leading-snug"
className="text-muted-foreground text-xs leading-snug"
>
{caveat}
</p>