feat: unified glass material system for all floating surfaces

Add .glass-modal CSS class (0.82 opacity) for dialogs and apply
consistent glass tiers across all floating UI: modals, context menus,
tooltips, popovers, and call cards. Replaces ad-hoc bg-surface-elevated
and inline styles with the documented 5-tier glass hierarchy.
This commit is contained in:
Jannis Braun
2026-03-11 22:48:11 +01:00
parent 7572f165de
commit 87e3d34638
12 changed files with 190 additions and 34 deletions
+17
View File
@@ -19,6 +19,23 @@ Backspace has its own visual identity — it is NOT a Discord clone. The design
When making UI changes, consult the prototype for colors, spacing, materials, and hierarchy. The frontend should converge toward this design. When making UI changes, consult the prototype for colors, spacing, materials, and hierarchy. The frontend should converge toward this design.
### Surface Material Tiers
Every surface in Backspace falls into one of these tiers:
| Tier | Class | When to Use |
|------|-------|-------------|
| Structural | `bg-surface-*` | Permanent layout (sidebars, chat area, member list) |
| Strip | `.glass-strip` | Persistent edge chrome (space sidebar) |
| Bubble | `.glass-bubble` | Persistent floating controls (voice bar, input pill, sticky actions) |
| Popover | `.glass` | Small floating surfaces (context menus, popovers, autocomplete, tooltips) |
| Modal | `.glass-modal` | Large center-screen dialogs with backdrop scrim |
| Pill | `.glass-pill` | Tiny inline decorations (reactions, tags) |
**Rule:** If it floats above the content plane, it's glass. Never use `bg-surface-elevated` for floating/overlay elements — that's for static structural panels only.
**Modal backdrops** use `bg-black/50` — light enough for the glass card's blur to show through.
## MISSION ## MISSION
Maintain and extend Backspace as a complete, production-quality application. The core application is fully built and deployed. Every change must uphold the same standard: no stubs, no TODOs, no shortcuts. A user must always be able to `docker compose up` and have a fully working chat platform. Maintain and extend Backspace as a complete, production-quality application. The core application is fully built and deployed. Every change must uphold the same standard: no stubs, no TODOs, no shortcuts. A user must always be able to `docker compose up` and have a fully working chat platform.
@@ -58,7 +58,7 @@ export function MentionPopover({ query, selectedIndex, onSelect, anchorRef }: Me
return createPortal( return createPortal(
<div ref={floatingRef} style={style} className="w-[280px]"> <div ref={floatingRef} style={style} className="w-[280px]">
<div className="bg-surface-elevated rounded-lg shadow-[0_0_0_1px_rgba(0,0,0,0.15),0_8px_16px_rgba(0,0,0,0.24)] overflow-hidden max-h-[320px] overflow-y-auto scrollbar-thin"> <div className="glass rounded-lg overflow-hidden max-h-[320px] overflow-y-auto scrollbar-thin">
<div className="px-2 py-1.5 text-[11px] font-bold text-txt-tertiary uppercase tracking-wider"> <div className="px-2 py-1.5 text-[11px] font-bold text-txt-tertiary uppercase tracking-wider">
Members Members
</div> </div>
@@ -7,6 +7,7 @@ import { useUIStore } from '../../stores/uiStore';
import { useInstanceStore } from '../../stores/instanceStore'; import { useInstanceStore } from '../../stores/instanceStore';
import { useAuthStore } from '../../stores/authStore'; import { useAuthStore } from '../../stores/authStore';
import { Tooltip } from '../ui/Tooltip'; import { Tooltip } from '../ui/Tooltip';
import { ConfirmDialog } from '../ui/ConfirmDialog';
import { getSpaceGradient, HOME_GRADIENT } from '../../utils/gradients'; import { getSpaceGradient, HOME_GRADIENT } from '../../utils/gradients';
@@ -176,12 +177,13 @@ function SpaceContextMenu({ spaceId, x, y, onClose }: { spaceId: string; x: numb
const addToast = useUIStore((s) => s.addToast); const addToast = useUIStore((s) => s.addToast);
const navigate = useNavigate(); const navigate = useNavigate();
const [showTransferModal, setShowTransferModal] = useState(false); const [showTransferModal, setShowTransferModal] = useState(false);
const [showLeaveConfirm, setShowLeaveConfirm] = useState(false);
const isOwner = space?.ownerId === getMyUserIdForOrigin((space as any)?._instanceOrigin ?? ''); const isOwner = space?.ownerId === getMyUserIdForOrigin((space as any)?._instanceOrigin ?? '');
// Close on click-outside and scroll // Close on click-outside and scroll
useEffect(() => { useEffect(() => {
if (showTransferModal) return; // Don't close when transfer modal is open if (showTransferModal || showLeaveConfirm) return; // Don't close when sub-dialog is open
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) { if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
onClose(); onClose();
@@ -200,7 +202,7 @@ function SpaceContextMenu({ spaceId, x, y, onClose }: { spaceId: string; x: numb
document.removeEventListener('scroll', handleScroll, true); document.removeEventListener('scroll', handleScroll, true);
document.removeEventListener('keydown', handleKeyDown); document.removeEventListener('keydown', handleKeyDown);
}; };
}, [onClose, showTransferModal]); }, [onClose, showTransferModal, showLeaveConfirm]);
if (!space) return null; if (!space) return null;
@@ -235,7 +237,7 @@ function SpaceContextMenu({ spaceId, x, y, onClose }: { spaceId: string; x: numb
return ReactDOM.createPortal( return ReactDOM.createPortal(
<div <div
ref={menuRef} ref={menuRef}
className="fixed z-[9999] min-w-[160px] bg-surface-overlay rounded-lg border border-white/[0.07] shadow-lg py-1 animate-in fade-in zoom-in-95 duration-100" className="fixed z-[9999] min-w-[160px] glass rounded-lg py-1 animate-in fade-in zoom-in-95 duration-100"
style={{ left: clampedX, top: clampedY }} style={{ left: clampedX, top: clampedY }}
> >
<button <button
@@ -260,15 +262,7 @@ function SpaceContextMenu({ spaceId, x, y, onClose }: { spaceId: string; x: numb
) : ( ) : (
<button <button
className="w-full flex items-center gap-2 px-3 py-1.5 text-sm text-accent-rose hover:bg-accent-rose/10 transition-colors" className="w-full flex items-center gap-2 px-3 py-1.5 text-sm text-accent-rose hover:bg-accent-rose/10 transition-colors"
onClick={() => { onClick={() => setShowLeaveConfirm(true)}
if (currentSpaceId === spaceId) {
navigate('/channels/@me');
setCurrentSpace(null);
setShowDms(true);
}
leaveSpace(spaceId);
onClose();
}}
> >
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5a2 2 0 00-2 2v4h2V5h14v14H5v-4H3v4a2 2 0 002 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" /> <path d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5a2 2 0 00-2 2v4h2V5h14v14H5v-4H3v4a2 2 0 002 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" />
@@ -276,6 +270,24 @@ function SpaceContextMenu({ spaceId, x, y, onClose }: { spaceId: string; x: numb
Leave Space Leave Space
</button> </button>
)} )}
<ConfirmDialog
isOpen={showLeaveConfirm}
onClose={() => setShowLeaveConfirm(false)}
onConfirm={() => {
if (currentSpaceId === spaceId) {
navigate('/channels/@me');
setCurrentSpace(null);
setShowDms(true);
}
leaveSpace(spaceId);
setShowLeaveConfirm(false);
onClose();
}}
title={`Leave ${space.name}`}
description="Are you sure you want to leave this space? You'll need a new invite to rejoin."
variant="danger"
confirmLabel="Leave"
/>
</div>, </div>,
document.body, document.body,
); );
@@ -351,7 +363,7 @@ function TransferOwnershipModal({ spaceId, onClose }: { spaceId: string; onClose
<div className="fixed inset-0 z-[10000] flex items-center justify-center bg-black/50"> <div className="fixed inset-0 z-[10000] flex items-center justify-center bg-black/50">
<div <div
ref={modalRef} ref={modalRef}
className="w-[380px] max-h-[480px] bg-surface-overlay rounded-xl border border-white/[0.07] shadow-2xl flex flex-col animate-in fade-in zoom-in-95 duration-150" className="w-[380px] max-h-[480px] glass-modal rounded-xl flex flex-col animate-in fade-in zoom-in-95 duration-150"
> >
{/* Header */} {/* Header */}
<div className="px-4 pt-4 pb-3 border-b border-white/[0.06]"> <div className="px-4 pt-4 pb-3 border-b border-white/[0.06]">
@@ -1,5 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Avatar } from '../../ui/Avatar'; import { Avatar } from '../../ui/Avatar';
import { ConfirmDialog } from '../../ui/ConfirmDialog';
import { useSpaceStore, getApiForOrigin } from '../../../stores/spaceStore'; import { useSpaceStore, getApiForOrigin } from '../../../stores/spaceStore';
import { useAuthStore } from '../../../stores/authStore'; import { useAuthStore } from '../../../stores/authStore';
import { parseFederatedUsername } from '../../../utils/identity'; import { parseFederatedUsername } from '../../../utils/identity';
@@ -28,6 +29,7 @@ export function MembersPanel({ spaceId }: MembersPanelProps) {
const [pendingRoleChanges, setPendingRoleChanges] = useState<Map<string, Set<string>>>(new Map()); const [pendingRoleChanges, setPendingRoleChanges] = useState<Map<string, Set<string>>>(new Map());
const [expandedMemberId, setExpandedMemberId] = useState<string | null>(null); const [expandedMemberId, setExpandedMemberId] = useState<string | null>(null);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [pendingAction, setPendingAction] = useState<{ type: 'kick' | 'ban'; userId: string; displayName: string } | null>(null);
// Assignable roles: exclude @everyone (where role.id === spaceId) // Assignable roles: exclude @everyone (where role.id === spaceId)
const assignableRoles = roles.filter((r) => r.id !== spaceId); const assignableRoles = roles.filter((r) => r.id !== spaceId);
@@ -170,7 +172,7 @@ export function MembersPanel({ spaceId }: MembersPanelProps) {
<div className="flex items-center gap-1 flex-shrink-0"> <div className="flex items-center gap-1 flex-shrink-0">
{canBan && member.userId !== currentUser?.id && !isOwner && ( {canBan && member.userId !== currentUser?.id && !isOwner && (
<button <button
onClick={(e) => { e.stopPropagation(); handleBan(member.userId); }} onClick={(e) => { e.stopPropagation(); setPendingAction({ type: 'ban', userId: member.userId, displayName }); }}
className="px-2 py-1 text-xs text-txt-danger hover:bg-accent-rose/10 rounded transition-colors" className="px-2 py-1 text-xs text-txt-danger hover:bg-accent-rose/10 rounded transition-colors"
> >
Ban Ban
@@ -178,7 +180,7 @@ export function MembersPanel({ spaceId }: MembersPanelProps) {
)} )}
{canKick && member.userId !== currentUser?.id && !isOwner && ( {canKick && member.userId !== currentUser?.id && !isOwner && (
<button <button
onClick={(e) => { e.stopPropagation(); handleKick(member.userId); }} onClick={(e) => { e.stopPropagation(); setPendingAction({ type: 'kick', userId: member.userId, displayName }); }}
className="px-2 py-1 text-xs text-txt-danger hover:bg-accent-rose/10 rounded transition-colors" className="px-2 py-1 text-xs text-txt-danger hover:bg-accent-rose/10 rounded transition-colors"
> >
Kick Kick
@@ -241,6 +243,28 @@ export function MembersPanel({ spaceId }: MembersPanelProps) {
</div> </div>
</div> </div>
</div> </div>
<ConfirmDialog
isOpen={pendingAction !== null}
onClose={() => setPendingAction(null)}
onConfirm={async () => {
if (!pendingAction) return;
if (pendingAction.type === 'kick') {
await handleKick(pendingAction.userId);
} else {
await handleBan(pendingAction.userId);
}
setPendingAction(null);
}}
title={pendingAction?.type === 'ban' ? `Ban ${pendingAction.displayName}` : `Kick ${pendingAction?.displayName ?? ''}`}
description={
pendingAction?.type === 'ban'
? 'They will be permanently banned from this space until unbanned.'
: 'They can rejoin with an invite link.'
}
variant={pendingAction?.type === 'ban' ? 'danger' : 'warning'}
confirmLabel={pendingAction?.type === 'ban' ? 'Ban' : 'Kick'}
/>
</div> </div>
); );
} }
@@ -0,0 +1,80 @@
import React, { useEffect, useCallback } from 'react';
import ReactDOM from 'react-dom';
interface ConfirmDialogProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void | Promise<void>;
title: string;
description: string | React.ReactNode;
confirmLabel?: string;
cancelLabel?: string;
variant?: 'danger' | 'warning';
loading?: boolean;
}
export function ConfirmDialog({
isOpen,
onClose,
onConfirm,
title,
description,
confirmLabel = 'Confirm',
cancelLabel = 'Cancel',
variant = 'danger',
loading = false,
}: ConfirmDialogProps) {
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape' && !loading) onClose();
}, [onClose, loading]);
useEffect(() => {
if (isOpen) {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}
}, [isOpen, handleKeyDown]);
if (!isOpen) return null;
const isDanger = variant === 'danger';
const accentBg = isDanger ? 'bg-accent-rose/10' : 'bg-accent-amber/10';
const accentBorder = isDanger ? 'border-accent-rose/20' : 'border-accent-amber/20';
const confirmBg = isDanger
? 'bg-accent-rose hover:bg-accent-rose/80'
: 'bg-accent-amber hover:bg-accent-amber/80';
return ReactDOM.createPortal(
<div className="fixed inset-0 z-[10000] flex items-center justify-center animate-fade-in">
<div
className="absolute inset-0 bg-black/50"
onClick={() => { if (!loading) onClose(); }}
/>
<div className="relative max-w-sm w-full mx-4 glass-modal rounded-lg animate-slide-up">
<div className="p-4">
<h3 className="text-base font-semibold text-txt-primary mb-3">{title}</h3>
<div className={`p-3 rounded-lg ${accentBg} border ${accentBorder} mb-4`}>
<div className="text-sm text-txt-secondary">{description}</div>
</div>
<div className="flex justify-end gap-2">
<button
onClick={onClose}
disabled={loading}
className="px-3 py-1.5 text-sm text-txt-secondary hover:text-txt-primary transition-colors disabled:opacity-50"
>
{cancelLabel}
</button>
<button
onClick={onConfirm}
disabled={loading}
className={`px-3 py-1.5 ${confirmBg} text-white text-sm font-medium rounded transition-colors disabled:opacity-50`}
>
{loading ? 'Please wait...' : confirmLabel}
</button>
</div>
</div>
</div>
</div>,
document.body,
);
}
@@ -64,7 +64,7 @@ export function ContextMenu({ items, children }: ContextMenuProps) {
{isOpen && ( {isOpen && (
<div <div
ref={menuRef} ref={menuRef}
className="fixed z-[200] min-w-[180px] py-1.5 bg-surface-elevated rounded-md shadow-elevation-high animate-fade-in max-h-[calc(100vh-16px)] overflow-y-auto scrollbar-thin" className="fixed z-[200] min-w-[180px] py-1.5 glass rounded-md animate-fade-in max-h-[calc(100vh-16px)] overflow-y-auto scrollbar-thin"
style={{ left: position.x, top: position.y }} style={{ left: position.x, top: position.y }}
> >
{items.map((item, i) => ( {items.map((item, i) => (
+2 -2
View File
@@ -27,10 +27,10 @@ export function Modal({ isOpen, onClose, title, children, maxWidth = 'max-w-md'
return ( return (
<div className="fixed inset-0 z-[200] flex items-center justify-center animate-fade-in"> <div className="fixed inset-0 z-[200] flex items-center justify-center animate-fade-in">
<div <div
className="absolute inset-0 bg-surface-overlay" className="absolute inset-0 bg-black/50"
onClick={onClose} onClick={onClose}
/> />
<div className={`relative ${maxWidth} w-full mx-4 max-h-[calc(100vh-2rem)] flex flex-col bg-surface-elevated rounded-lg shadow-xl animate-slide-up`}> <div className={`relative ${maxWidth} w-full mx-4 max-h-[calc(100vh-2rem)] flex flex-col glass-modal rounded-lg animate-slide-up`}>
{title && ( {title && (
<div className="flex items-center justify-between px-4 pt-4 flex-shrink-0"> <div className="flex items-center justify-between px-4 pt-4 flex-shrink-0">
<h2 className="text-xl font-bold text-txt-primary">{title}</h2> <h2 className="text-xl font-bold text-txt-primary">{title}</h2>
+1 -1
View File
@@ -43,7 +43,7 @@ export function Tooltip({ content, children, position = 'right', delay = 200 }:
<div <div
ref={floatingRef} ref={floatingRef}
style={style} style={style}
className="px-3 py-1.5 text-sm font-medium text-txt-primary bg-surface-elevated rounded-md shadow-elevation-high whitespace-nowrap pointer-events-none" className="px-3 py-1.5 text-sm font-medium text-txt-primary glass rounded-md whitespace-nowrap pointer-events-none"
> >
{content} {content}
</div>, </div>,
@@ -79,16 +79,10 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout
return ( return (
<div <div
className="fixed z-[200] w-[340px] rounded-[12px] overflow-hidden animate-fade-in select-none border border-white/[0.07]" className="fixed z-[200] w-[340px] rounded-[12px] overflow-hidden animate-fade-in select-none glass-modal"
style={{ style={position
...(position
? { top, left } ? { top, left }
: { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }), : { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }}
backdropFilter: 'blur(20px) saturate(1.2)',
WebkitBackdropFilter: 'blur(20px) saturate(1.2)',
backgroundColor: 'rgba(20,20,26,0.85)',
boxShadow: '0 8px 32px rgba(0,0,0,0.45), 0 2px 8px rgba(0,0,0,0.25)',
}}
> >
{/* Banner */} {/* Banner */}
<div <div
@@ -51,10 +51,10 @@ export function IncomingCallModal() {
return ( return (
<div className="fixed inset-0 z-[100] flex items-center justify-center"> <div className="fixed inset-0 z-[100] flex items-center justify-center">
{/* Backdrop */} {/* Backdrop */}
<div className="absolute inset-0 bg-black/60" /> <div className="absolute inset-0 bg-black/50" />
{/* Call card */} {/* Call card */}
<div className="relative bg-surface-channel rounded-lg shadow-2xl w-[340px] overflow-hidden"> <div className="relative glass-modal rounded-lg w-[340px] overflow-hidden">
{/* Ring animation background */} {/* Ring animation background */}
<div className="absolute inset-0 overflow-hidden"> <div className="absolute inset-0 overflow-hidden">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[200px] h-[200px] rounded-full bg-status-online/5 animate-ping" style={{ animationDuration: '2s' }} /> <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[200px] h-[200px] rounded-full bg-status-online/5 animate-ping" style={{ animationDuration: '2s' }} />
@@ -192,7 +192,7 @@ function MoveToSubmenu({ channels, onMove, btnClass, btnStyle }: MoveToSubmenuPr
{open && ReactDOM.createPortal( {open && ReactDOM.createPortal(
<div <div
ref={flyoutRef} ref={flyoutRef}
className="fixed z-[210] bg-surface-elevated rounded-md shadow-elevation-high py-1.5 min-w-[160px] overflow-y-auto scrollbar-thin animate-fade-in" className="fixed z-[210] glass rounded-md py-1.5 min-w-[160px] overflow-y-auto scrollbar-thin animate-fade-in"
style={{ left: -9999, top: -9999 }} style={{ left: -9999, top: -9999 }}
onMouseEnter={cancelCloseTimer} onMouseEnter={cancelCloseTimer}
onMouseLeave={startCloseTimer} onMouseLeave={startCloseTimer}
@@ -291,7 +291,7 @@ export function VoiceUserContextMenu({ targetUserId, channelId, position, onClos
return ReactDOM.createPortal( return ReactDOM.createPortal(
<div <div
ref={menuRef} ref={menuRef}
className="fixed z-[200] bg-surface-elevated rounded-md shadow-elevation-high min-w-[200px] max-h-[calc(100vh-16px)] overflow-y-auto scrollbar-thin animate-fade-in" className="fixed z-[200] glass rounded-md min-w-[200px] max-h-[calc(100vh-16px)] overflow-y-auto scrollbar-thin animate-fade-in"
style={{ left: position.x, top: position.y }} style={{ left: position.x, top: position.y }}
onMouseDown={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
+29
View File
@@ -56,6 +56,7 @@
/* ── Glass Material ── */ /* ── Glass Material ── */
--glass-bg: rgba(20, 20, 26, 0.52); --glass-bg: rgba(20, 20, 26, 0.52);
--glass-modal-bg: rgba(20, 20, 26, 0.82);
--glass-border: rgba(255, 255, 255, 0.07); --glass-border: rgba(255, 255, 255, 0.07);
--glass-highlight: rgba(255, 255, 255, 0.05); --glass-highlight: rgba(255, 255, 255, 0.05);
@@ -117,6 +118,17 @@
} }
@layer components { @layer components {
/* ── Glass Material Tiers ──
* .glass — Popovers, context menus, autocomplete (small, no backdrop)
* .glass-modal — Center-screen dialogs (large, with backdrop, higher opacity for legibility)
* .glass-strip — Space sidebar edge (no border-radius, directional shadow)
* .glass-bubble — Persistent floating controls (voice bar, input pill)
* .glass-pill — Inline decorations (reactions, tags, lighter blur)
*
* RULE: Every floating surface uses a glass tier. Never use bg-surface-elevated for overlays.
* See CLAUDE.md "Surface Material Tiers" for the full decision guide.
*/
/* Standard glass — popovers, floating elements */ /* Standard glass — popovers, floating elements */
.glass { .glass {
backdrop-filter: blur(20px) saturate(120%); backdrop-filter: blur(20px) saturate(120%);
@@ -153,6 +165,18 @@
inset 0 1px 0 var(--glass-highlight); inset 0 1px 0 var(--glass-highlight);
} }
/* Modal glass — center-screen dialogs with backdrop */
.glass-modal {
backdrop-filter: blur(20px) saturate(120%);
-webkit-backdrop-filter: blur(20px) saturate(120%);
background: var(--glass-modal-bg);
border: 1px solid var(--glass-border);
box-shadow:
0 4px 16px rgba(0, 0, 0, 0.30),
0 12px 40px rgba(0, 0, 0, 0.20),
inset 0 1px 0 var(--glass-highlight);
}
/* Reaction pill glass — lighter blur for small inline elements */ /* Reaction pill glass — lighter blur for small inline elements */
.glass-pill { .glass-pill {
backdrop-filter: blur(12px) saturate(110%); backdrop-filter: blur(12px) saturate(110%);
@@ -193,6 +217,11 @@
-webkit-backdrop-filter: none; -webkit-backdrop-filter: none;
background: rgb(var(--bg-elevated)); background: rgb(var(--bg-elevated));
} }
.glass-modal {
backdrop-filter: none;
-webkit-backdrop-filter: none;
background: rgb(var(--bg-elevated));
}
.glass-pill { .glass-pill {
backdrop-filter: none; backdrop-filter: none;
-webkit-backdrop-filter: none; -webkit-backdrop-filter: none;