import { createVar, keyframes, style, styleVariants } from '@vanilla-extract/css'; import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; import { DefaultReset, color, config, toRem } from 'folds'; export const StickySection = style({ position: 'sticky', top: config.space.S100, }); const SpacingVar = createVar(); const SpacingVariant = styleVariants({ '0': { vars: { [SpacingVar]: config.space.S0, }, }, '100': { vars: { [SpacingVar]: config.space.S100, }, }, '200': { vars: { [SpacingVar]: config.space.S200, }, }, '300': { vars: { [SpacingVar]: config.space.S300, }, }, '400': { vars: { [SpacingVar]: config.space.S400, }, }, '500': { vars: { [SpacingVar]: config.space.S500, }, }, }); const highlightAnime = keyframes({ '0%': { backgroundColor: color.Primary.Container, }, '25%': { backgroundColor: color.Primary.ContainerActive, }, '50%': { backgroundColor: color.Primary.Container, }, '75%': { backgroundColor: color.Primary.ContainerActive, }, '100%': { backgroundColor: color.Primary.Container, }, }); const HighlightVariant = styleVariants({ true: { animation: `${highlightAnime} 2000ms ease-in-out`, animationIterationCount: 'infinite', }, }); const SelectedVariant = styleVariants({ true: { backgroundColor: color.Surface.ContainerActive, }, }); const AutoCollapse = style({ selectors: { [`&+&`]: { marginTop: 0, }, }, }); export const MessageBase = recipe({ base: [ DefaultReset, { marginTop: SpacingVar, padding: `${config.space.S100} ${config.space.S200} ${config.space.S100} ${config.space.S400}`, borderRadius: `0 ${config.radii.R400} ${config.radii.R400} 0`, }, ], variants: { space: SpacingVariant, collapse: { true: { marginTop: 0, }, }, autoCollapse: { true: AutoCollapse, }, highlight: HighlightVariant, selected: SelectedVariant, }, defaultVariants: { space: '400', }, }); export type MessageBaseVariants = RecipeVariants; // ── Mention pulse animation ─────────────────────────────────────────────────── // Animates only `box-shadow` — NOT `transform`. A self-sent @mention message // carries both this class and `MsgAppearClass` (which animates a scale), and two // animations on the same element cannot share the `transform` property: the // later one wins and the other is silently dropped. Pulsing the glow alone keeps // both effects working. (The previous scale(1.003) was imperceptible anyway.) const mentionPulseKeyframes = keyframes({ '0%': { boxShadow: 'none' }, '30%': { boxShadow: `0 0 8px ${color.Warning.Main}` }, '100%': { boxShadow: 'none' }, }); /** * Applied only to new incoming @mention messages. * Respects `prefers-reduced-motion`: no animation when motion is reduced. */ export const MentionHighlightPulse = style({ '@media': { '(prefers-reduced-motion: no-preference)': { animation: `${mentionPulseKeyframes} 0.6s ease-out`, }, }, }); export const CompactHeader = style([ DefaultReset, StickySection, { maxWidth: toRem(170), width: '100%', }, ]); export const AvatarBase = style({ paddingTop: toRem(4), transition: 'transform 200ms cubic-bezier(0, 0.8, 0.67, 0.97)', display: 'flex', alignSelf: 'start', selectors: { '&:hover': { transform: `translateY(${toRem(-2)})`, }, }, }); export const ModernBefore = style({ minWidth: toRem(36), }); export const BubbleBefore = style({ minWidth: toRem(36), }); export const BubbleContent = style({ maxWidth: toRem(800), padding: config.space.S200, backgroundColor: color.SurfaceVariant.Container, color: color.SurfaceVariant.OnContainer, borderRadius: config.radii.R500, position: 'relative', }); export const BubbleContentArrowLeft = style({ borderTopLeftRadius: 0, }); export const BubbleLeftArrow = style({ width: toRem(9), height: toRem(8), position: 'absolute', top: 0, left: toRem(-8), zIndex: 1, }); export const Username = style({ overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', selectors: { 'button&': { cursor: 'pointer', }, 'button&:hover, button&:focus-visible': { textDecoration: 'underline', }, }, }); export const UsernameBold = style({ fontWeight: 550, }); export const MessageTextBody = recipe({ base: { wordBreak: 'break-word', }, variants: { preWrap: { true: { whiteSpace: 'pre-wrap', }, }, jumboEmoji: { true: { fontSize: '1.504em', lineHeight: '1.4962em', }, }, emote: { true: { color: color.Success.Main, fontStyle: 'italic', }, }, }, }); export type MessageTextBodyVariants = RecipeVariants;