From 9815c2fa271082df67ee24e69feee8c87ef7e298 Mon Sep 17 00:00:00 2001 From: clay Date: Mon, 9 Nov 2026 19:30:31 -0500 Subject: [PATCH] Add optional motion addon --- clarth/charcoal outline/README.md | 13 + .../charcoal-outline-motion.css | 655 ++++++++++++++++++ 2 files changed, 668 insertions(+) create mode 100644 clarth/charcoal outline/charcoal-outline-motion.css diff --git a/clarth/charcoal outline/README.md b/clarth/charcoal outline/README.md index ded3f54..1367c58 100644 --- a/clarth/charcoal outline/README.md +++ b/clarth/charcoal outline/README.md @@ -56,6 +56,19 @@ The theme avoids custom sidebar offsets, forced control sizes, clipping workarou Keyboard-focus indicators remain separate from mouse-hover outlines. Existing selected-server outlines and semantic feature colors are preserved. No custom motion effects are added. +## Optional motion addon + +`charcoal-outline-motion.css` is stand alone css that adds subtle animations to Charcoal Outline without changing its colors or layout. + +### Included effects + +- Thin gray hover outlines fade in and out. +- Buttons compress slightly when pressed. +- Server icons lift slightly on hover. +- Menus, tooltips, notifications, and message-action toolbars have quick entrance animations. +- The composer border transitions smoothly when focused. +- Channel and settings rows transition between their existing background colors. + ## Changelog ### 1.0.1 diff --git a/clarth/charcoal outline/charcoal-outline-motion.css b/clarth/charcoal outline/charcoal-outline-motion.css new file mode 100644 index 0000000..64b126c --- /dev/null +++ b/clarth/charcoal outline/charcoal-outline-motion.css @@ -0,0 +1,655 @@ +/* + * @name Charcoal Outline — Motion Addon + * @description Optional subtle feedback for Charcoal Outline 1.0.1. + * @version 1.0.0 + * Beer-Ware (Revision 42): Michael Moon wrote this file. As long as + * you retain this notice you can do whatever you want with this stuff. + * If we meet some day, and you think this stuff is worth it, you can + * buy me a beer in return. + */ + +/* + * @name Charcoal Outline + * @description Solid charcoal, transparent panels, blue feature accents and thin gray hover outlines. No external assets. + * @version 1.0.1 + * Standalone replacement based on the supplied CSS and Fluxer reference. + * Consolidated hover rules; preserves the supplied feature colors and native layout. + * CSS syntax checked; not live-tested in Fluxer. + */ +:root, body { + --co-background: #1e1e1e; + --co-text: #e8ebf2; + --co-muted: #98a1b3; + --co-accent: #4fa3ff; + --co-danger: #ef6565; + --co-success: #7bdca2; + --co-warning: #f2ce75; + --co-line: rgba(128,128,128,.35); + --co-code: rgba(128,128,128,.2); + --co-hover: rgba(128,128,128,.08); + --co-selected: rgba(79,163,255,.14); + --co-font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + --co-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + --background-primary: var(--co-background); + --background-secondary: var(--co-background); + --background-secondary-lighter: var(--co-background); + --background-secondary-alt: var(--co-background); + --background-tertiary: var(--co-background); + --guild-badge-surface: var(--co-background); + --composer-surface-color: var(--co-background); + --text-on-brand-primary: var(--co-background); + --text-on-brand-primary-light: var(--co-background); + --brand-primary-foreground: var(--co-background); + --settings-panel-background: var(--co-background); + --tooltip-surface: var(--co-background); + --background-channel-header: transparent; + --background-header-primary: transparent; + --background-header-secondary: transparent; + --background-textarea: transparent; + --guilds-layout-item-bg: transparent; + --form-surface-background: transparent; + --panel-control-bg: transparent; + --button-primary-fill: transparent; + --button-secondary-fill: transparent; + --button-danger-fill: transparent; + --control-button-normal-bg: transparent; + --message-preview-card-background: transparent; + --bg-table-row-odd: transparent; + --scrollbar-track-bg: transparent; + --background-header-primary-hover: var(--co-hover); + --panel-control-highlight: var(--co-hover); + --background-modifier-hover: var(--co-hover); + --background-modifier-accent: var(--co-hover); + --surface-interactive-hover-bg: var(--co-hover); + --button-secondary-active-fill: var(--co-hover); + --control-button-hover-bg: var(--co-hover); + --message-reply-bg: var(--co-hover); + --bg-blockquote: var(--co-hover); + --bg-table-row-even: var(--co-hover); + --background-modifier-selected: var(--co-selected); + --background-modifier-accent-focus: var(--co-selected); + --surface-interactive-selected-bg: var(--co-selected); + --brand-tint: var(--co-selected); + --button-primary-active-fill: var(--co-selected); + --control-button-active-bg: var(--co-selected); + --markup-mention-fill: var(--co-selected); + --panel-control-border: var(--co-line); + --panel-control-divider: var(--co-line); + --floating-surface-ring-color: var(--co-line); + --user-area-divider-color: var(--co-line); + --skeleton-chrome-border-color: var(--co-line); + --border-color: var(--co-line); + --button-outline-border: var(--co-line); + --message-preview-card-border: var(--co-line); + --message-preview-card-divider: var(--co-line); + --code-block-border: var(--co-line); + --scrollbar-thumb-bg: var(--co-line); + --composer-box-ring-color: var(--co-line); + --settings-border-color: var(--co-line); + --tooltip-border-color: var(--co-line); + --surface-interactive-selected-color: var(--co-accent); + --floating-surface-ring-color-strong: var(--co-accent); + --border-color-hover: var(--co-accent); + --border-color-focus: var(--co-accent); + --focus-primary: var(--co-accent); + --text-link: var(--co-accent); + --text-on-brand-tint: var(--co-accent); + --brand-primary: var(--co-accent); + --brand-primary-fill: var(--co-accent); + --brand-secondary: var(--co-accent); + --accent-primary: var(--co-accent); + --accent-info: var(--co-accent); + --button-primary-text: var(--co-accent); + --button-ghost-text: var(--co-accent); + --button-outline-text: var(--co-accent); + --control-button-active-text: var(--co-accent); + --message-mention-color: var(--co-accent); + --markup-mention-text: var(--co-accent); + --settings-hover-border-color: var(--co-accent); + --settings-selected-border-color: var(--co-accent); + --text-primary: var(--co-text); + --text-chat: var(--co-text); + --text-on-dark: var(--co-text); + --interactive-active: var(--co-text); + --button-secondary-text: var(--co-text); + --button-secondary-active-text: var(--co-text); + --control-button-hover-text: var(--co-text); + --markup-interactive-hover-text: var(--co-text); + --code-text: var(--co-text); + --guild-list-foreground: var(--co-muted); + --text-secondary: var(--co-muted); + --text-primary-muted: var(--co-muted); + --text-chat-muted: var(--co-muted); + --text-tertiary: var(--co-muted); + --text-tertiary-muted: var(--co-muted); + --text-tertiary-secondary: var(--co-muted); + --interactive-muted: var(--co-muted); + --message-timestamp-color: var(--co-muted); + --status-offline: var(--co-muted); + --control-button-normal-text: var(--co-muted); + --message-reply-color: var(--co-muted); + --scrollbar-thumb-bg-hover: var(--co-muted); + --text-selection: rgba(79,163,255,.28); + --brand-primary-light: #78b8ff; + --brand-primary-light-fill: #78b8ff; + --brand-primary-pressed: #378bdd; + --brand-primary-shade: #378bdd; + --brand-tint-hover: rgba(79,163,255,.22); + --markup-interactive-hover-fill: rgba(79,163,255,.22); + --brand-tint-border: rgba(79,163,255,.45); + --accent-success: var(--co-success); + --status-online: var(--co-success); + --accent-warning: var(--co-warning); + --status-idle: var(--co-warning); + --status-warning: var(--co-warning); + --markup-everyone-text: var(--co-warning); + --markup-here-text: var(--co-warning); + --accent-danger: var(--co-danger); + --status-dnd: var(--co-danger); + --status-danger: var(--co-danger); + --button-danger-text: var(--co-danger); + --accent-purple: #ba9cff; + --button-danger-active-fill: rgba(239,101,101,.14); + --message-mention-bg: rgba(79,163,255,.10); + --message-mention-bg-hover: rgba(79,163,255,.16); + --markup-mention-border: rgba(79,163,255,.35); + --markup-everyone-fill: rgba(242,206,117,.12); + --markup-here-fill: rgba(242,206,117,.12); + --markup-everyone-border: rgba(242,206,117,.35); + --markup-here-border: rgba(242,206,117,.35); + --code-block-bg: var(--co-code); + --code-inline-bg: var(--co-code); + --bg-table-header: var(--co-code); + --shadow-sm: none; + --shadow-md: none; + --shadow-lg: none; + --shadow-xl: none; + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 10px; + --radius-xl: 14px; + --radius-2xl: 14px; + --font-sans-primary: var(--co-font); +} + +/* Preserve native dimensions, scroll containers and control geometry. */ +html, body { + background: var(--co-background) !important; + color: var(--co-text); + font-family: var(--co-font); +} + +::selection { + background: rgba(79,163,255,.28); +} + +:focus-visible { + outline: 2px solid var(--co-accent); + outline-offset: 2px; +} + +[class*='App.module__appContainer___'] { + background: var(--co-background) !important; + font-family: var(--co-font); +} + +:is( + [class*='GuildsLayout.module__guildsLayoutContainer___'], + [class*='GuildsLayout.module__guildListScrollerWrapper___'], + [class*='GuildsLayout.module__guildListScrollContainer___'], + [class*='GuildsLayout.module__contentContainer___'], + [class*='GuildsLayout.module__contentInner___'], + [class*='GuildLayout.module__guildLayoutContainer___'], + [class*='GuildLayout.module__guildMainContent___'], + [class*='GuildNavbar.module__guildNavbarContainer___'], + [class*='ChannelListContent.module__channelListScroller___'], + [class*='ChannelLayout.module__channelLayoutContainer___'], + [class*='ChannelChatLayout.module__container___'], + [class*='MemberListContainer.module__memberListContainer___'], + [class*='MemberListContainer.module__memberListScroller___'] +) { + background: transparent !important; +} + +[class*='GuildNavbar.module__guildNavbarContainer___'] { + border-right: 1px solid var(--co-line); +} + +[class*='MemberListContainer.module__memberListContainer___'] { + border-left: 1px solid var(--co-line); +} + +:is( + [class*='GuildHeader.module__headerContainer___'], + [class*='ChannelHeader.module__headerContainer___'] +) { + background: transparent !important; + border-bottom: 1px solid var(--co-line); + box-shadow: none !important; +} + +[class*='ChannelHeader.module__channelName'] { + color: var(--co-text); +} + +[class*='GuildsLayout.module__guildDivider___'] { + background: var(--co-line); +} + +[class*='GuildsLayout.module__guildIndicatorBar___'] { + background: var(--co-accent); +} + +[class*='GuildsLayout.module__guildIconSelected___'] { + outline: 2px solid var(--co-accent); + outline-offset: 2px; +} + +:is( + [class*='SettingsSection.module__sectionTitle___'], + [class*='ChannelItem.module__categoryName___'] +) { + color: var(--co-muted); + font-size: .8rem; + font-weight: 600; + letter-spacing: .06em; + text-transform: uppercase; +} + +[class*='SettingsModalHeader.module__title___'] { + color: var(--co-text); +} + +/* Full-sized buttons: preserve feature colors; hover borders are gray. */ +:is( + [class*='Button.module__secondary___'], + [class*='Button.module__primary___'], + [class*='Button.module__danger'] +) { + background: transparent !important; + background-image: none !important; + border: 1px solid var(--co-line) !important; + border-radius: 10px; + box-shadow: none !important; +} + +[class*='Button.module__secondary___'] { + color: var(--co-text) !important; +} + +[class*='Button.module__primary___'] { + color: var(--co-accent) !important; + border-color: var(--co-accent) !important; +} + +[class*='Button.module__danger'] { + color: var(--co-danger) !important; +} + +:is( + [class*='Button.module__secondary___'], + [class*='Button.module__primary___'] +):not(:disabled):not([aria-disabled='true']):not([data-disabled='true']):not([data-disabled='']):hover { + background: var(--co-hover) !important; + border-color: var(--co-line) !important; +} + +[class*='Button.module__danger']:not(:disabled):not([aria-disabled='true']):not([data-disabled='true']):not([data-disabled='']):hover { + background: rgba(239,101,101,.10) !important; + border-color: var(--co-line) !important; +} + +:is( + [class*='ChannelItemSurface.module__channelItemSurface___'], + [class*='SettingsModalLayout.module__sidebarItem___'], + [class*='SettingsModalLayout.module__sidebarSubItem___'] +) { + border-radius: 8px; +} + +:is( + [class*='ChannelItemSurface.module__channelItemSurface___'], + [class*='SettingsModalLayout.module__sidebarItem___'], + [class*='SettingsModalLayout.module__sidebarSubItem___'] +):not(:disabled):not([aria-disabled='true']):not([data-disabled='true']):not([data-disabled='']):hover { + background-color: var(--co-hover) !important; +} + +:is( + [class*='ChannelItemSurface.module__channelItemSurfaceSelected___'], + [class*='SettingsModalLayout.module__sidebarItemSelected___'], + [class*='SettingsModalLayout.module__sidebarSubItemActive___'] +) { + background-color: var(--co-selected) !important; + color: var(--co-accent); +} + +:is( + [class*='ChannelHeader.module__iconButton___'], + [class*='TextareaButton.module__button___'], + [class*='UserArea.module__controlButton___'], + [class*='MessageActionBar.module__button___'] +) { + border-radius: 8px; + box-shadow: none; +} + +:is( + [class*='ChannelHeader.module__iconButton___'], + [class*='TextareaButton.module__button___'], + [class*='UserArea.module__controlButton___'], + [class*='MessageActionBar.module__button___'] +):not(:disabled):not([aria-disabled='true']):not([data-disabled='true']):not([data-disabled='']):hover { + background-color: var(--co-hover); +} + +:is( + [class*='ChannelHeader.module__iconButtonSelected___'], + [class*='TextareaButton.module__selected___'] +) { + background-color: var(--co-selected) !important; + color: var(--co-accent); +} + +[class*='UserArea.module__userAreaInnerWrapper___'] { + background: var(--co-background) !important; + border-top: 1px solid var(--co-line); + box-shadow: none; +} + +:is( + [class*='TextareaInput.module__textareaOuterRow___'], + [class*='MessageSearchBar.module__inputContainer___'] +) { + background: transparent !important; + border: 1px solid var(--co-line); + border-radius: 10px; + box-shadow: none !important; +} + +:is( + [class*='TextareaInput.module__textareaOuterRow___'], + [class*='MessageSearchBar.module__inputContainer___'] +):focus-within { + border-color: var(--co-accent); +} + +[class*='MessageSearchBar.module__scopeBadge___'] { + background: var(--co-hover); + color: var(--co-muted); + border-radius: 4px; +} + +/* Opaque floating surfaces preserve readability without clipping overlays. */ +:is( + [class*='ContextMenu.module__contextMenu___'], + [class*='PopoverPopout.module__popout___'], + [class*='Tooltip.module__tooltip___'], + [class*='Toast.module__toast___'], + [class*='MessageActionBar.module__actionBar___'] +) { + background: var(--co-background) !important; + background-image: none !important; + border: 1px solid var(--co-line); + border-radius: 10px; + box-shadow: none !important; +} + +[class*='ContextMenu.module__item___'] { + border-radius: 6px; +} + +[class*='ContextMenu.module__item___']:not(:disabled):not([aria-disabled='true']):not([data-disabled='true']):not([data-disabled='']):is(:hover, :focus-visible, [data-highlighted]:not([data-highlighted='false'])) { + background-color: var(--co-hover) !important; +} + +[class*='ContextMenu.module__danger___'] { + color: var(--co-danger) !important; +} + +[class*='ContextMenu.module__separator___'] { + background-color: var(--co-line); +} + +[class*='SettingsModalLayout.module__container___'] { + background: var(--co-background) !important; + border: 1px solid var(--co-line); + border-radius: 14px; + box-shadow: none !important; +} + +:is( + [class*='SettingsModalLayout.module__desktopSidebar___'], + [class*='SettingsModalLayout.module__desktopContent___'], + [class*='SettingsModalLayout.module__desktopHeader___'] +) { + background: transparent !important; +} + +[class*='SettingsModalLayout.module__desktopSidebar___'] { + border-right: 1px solid var(--co-line); +} + +[class*='SettingsModalLayout.module__desktopHeader___'] { + border-bottom: 1px solid var(--co-line); +} + +[class*='Message.module__message___'] :is(code, kbd, samp) { + background: var(--co-code); + border-radius: 4px; + font-family: var(--co-mono); +} + +[class*='Message.module__message___'] pre { + background: var(--co-code); + border: 1px solid var(--co-line); + border-radius: 8px; + font-family: var(--co-mono); +} + +[class*='Message.module__message___'] pre code { + background: transparent; +} + +[class*='Message.module__message___'] a[href] { + color: var(--co-accent); +} + +:is( + [class*='GuildsLayout.module__guildListScrollContainer___'], + [class*='ChannelListContent.module__channelListScroller___'], + [class*='MemberListContainer.module__memberListScroller___'] +) { + scrollbar-width: thin; + scrollbar-color: var(--co-line) transparent; +} + +/* Consolidated hover outline. + All state filters belong to the SAME element (no descendant whitespace). + Keyboard-focus and selected-guild outlines are intentionally preserved. + Existing bordered buttons use their border, avoiding a double line. */ +:is( + [class*='ChannelHeader.module__iconButton___'], + [class*='TextareaButton.module__button___'], + [class*='UserArea.module__controlButton___'], + [class*='MessageActionBar.module__button___'], + [class*='ChannelItemSurface.module__channelItemSurface___'], + [class*='SettingsModalLayout.module__sidebarItem___'], + [class*='SettingsModalLayout.module__sidebarSubItem___'], + [class*='ContextMenu.module__item___'], + [class*='GuildsLayout.module__guildListItem___'], + [class*='TextareaInput.module__textareaOuterRow___'], + [class*='MessageSearchBar.module__inputContainer___'] +):not(:disabled):not([aria-disabled='true']):not([data-disabled='true']):not([data-disabled='']):hover:not(:focus-visible):not(:focus-within) { + outline: 1px solid var(--co-line) !important; + outline-offset: -1px; +} + +/* Do not replace an already-visible selected server outline on hover. */ +[class*='GuildsLayout.module__guildListItem___']:has([class*='GuildsLayout.module__guildIconSelected___']):not(:disabled):not([aria-disabled='true']):not([data-disabled='true']):not([data-disabled='']):hover:not(:focus-visible):not(:focus-within) { + outline: none !important; +} + + +/*Motion stuff is below + + + +@media (prefers-reduced-motion: no-preference) { + /* Neutral starting outline; the base theme supplies the hover color. + Exclude keyboard focus and rows containing a selected guild icon. */ + :is( + [class*='ChannelHeader.module__iconButton___'], + [class*='TextareaButton.module__button___'], + [class*='UserArea.module__controlButton___'], + [class*='MessageActionBar.module__button___'], + [class*='ContextMenu.module__item___'], + [class*='GuildsLayout.module__guildListItem___'], + [class*='MessageSearchBar.module__inputContainer___'], + [class*='ChannelItemSurface.module__channelItemSurface___'], + [class*='SettingsModalLayout.module__sidebarItem___'], + [class*='SettingsModalLayout.module__sidebarSubItem___'], + [class*='TextareaInput.module__textareaOuterRow___'] + ):not(:focus-visible):not(:focus-within):not(:has([class*='GuildsLayout.module__guildIconSelected___'])) { + outline: 1px solid transparent; + outline-offset: -1px; + } + + /* One transition list per component group avoids competing patches. */ + :is( + [class*='UserArea.module__controlButton___'], + [class*='MessageActionBar.module__button___'], + [class*='ContextMenu.module__item___'], + [class*='GuildsLayout.module__guildListItem___'], + [class*='MessageSearchBar.module__inputContainer___'] + ) { + transition: outline-color 140ms ease; + } + + :is( + [class*='ChannelHeader.module__iconButton___'], + [class*='TextareaButton.module__button___'] + ) { + transition: scale 90ms ease, outline-color 140ms ease; + } + + :is( + [class*='ChannelItemSurface.module__channelItemSurface___'], + [class*='SettingsModalLayout.module__sidebarItem___'], + [class*='SettingsModalLayout.module__sidebarSubItem___'] + ) { + transition: background-color 120ms ease, outline-color 140ms ease; + } + + [class*='TextareaInput.module__textareaOuterRow___'] { + transition: border-color 120ms ease, outline-color 140ms ease; + } + + /* Focus outlines appear immediately; other feedback stays enabled. */ + :is( + [class*='UserArea.module__controlButton___'], + [class*='MessageActionBar.module__button___'], + [class*='ContextMenu.module__item___'], + [class*='GuildsLayout.module__guildListItem___'], + [class*='MessageSearchBar.module__inputContainer___'] + ):is(:focus-visible, :focus-within) { + transition: none; + } + + :is( + [class*='ChannelHeader.module__iconButton___'], + [class*='TextareaButton.module__button___'] + ):is(:focus-visible, :focus-within) { + transition: scale 90ms ease; + } + + :is( + [class*='ChannelItemSurface.module__channelItemSurface___'], + [class*='SettingsModalLayout.module__sidebarItem___'], + [class*='SettingsModalLayout.module__sidebarSubItem___'] + ):is(:focus-visible, :focus-within) { + transition: background-color 120ms ease; + } + + [class*='TextareaInput.module__textareaOuterRow___']:is(:focus-visible, :focus-within) { + transition: border-color 120ms ease; + } + + :is( + [class*='Button.module__secondary___'], + [class*='Button.module__primary___'], + [class*='Button.module__danger'] + ) { + transition: scale 100ms ease, border-color 140ms ease; + } + + :is( + [class*='Button.module__secondary___'], + [class*='Button.module__primary___'], + [class*='Button.module__danger'] + ):not(:disabled):not([aria-disabled='true']):not([data-disabled='true']):not([data-disabled='']):active { + scale: .98; + } + + :is( + [class*='ChannelHeader.module__iconButton___'], + [class*='TextareaButton.module__button___'] + ):not(:disabled):not([aria-disabled='true']):not([data-disabled='true']):not([data-disabled='']):active { + scale: .95; + } + + /* Entrances run when elements are inserted/rendered, not necessarily + whenever a persistently mounted element becomes visible. + No fill-mode: native styles resume after each entrance. + These declarations replace native animation lists on these targets. */ + :is( + [class*='ContextMenu.module__contextMenu___'], + [class*='PopoverPopout.module__popout___'] + ) { + animation: co-motion-enter 140ms ease-out; + } + + [class*='Toast.module__toast___'] { + animation: co-motion-enter 150ms ease-out; + } + + [class*='Tooltip.module__tooltip___'] { + animation: co-motion-fade 110ms ease-out; + } + + [class*='MessageActionBar.module__actionBar___'] { + animation: co-motion-fade 120ms ease-out; + } + +} + +/* Move only the server icon, and only for fine hover-capable pointers. */ +@media (hover: hover) and (pointer: fine) and + (prefers-reduced-motion: no-preference) { + [class*='GuildsLayout.module__guildIcon___'] { + transition: translate 140ms ease; + } + + [class*='GuildsLayout.module__guildListItem___']:not(:disabled):not([aria-disabled='true']):not([data-disabled='true']):not([data-disabled='']):hover + [class*='GuildsLayout.module__guildIcon___'] { + translate: 0 -2px; + } + + [class*='GuildsLayout.module__guildListItem___']:not(:disabled):not([aria-disabled='true']):not([data-disabled='true']):not([data-disabled='']):active + [class*='GuildsLayout.module__guildIcon___'] { + translate: 0 0; + } + +} + +/* Reduced-motion mode receives no motion rules from this addon. + Native client animations are deliberately left alone. */ +@keyframes co-motion-enter { + from { opacity: 0; translate: 0 3px; } + to { opacity: 1; translate: 0 0; } +} + +@keyframes co-motion-fade { + from { opacity: 0; } + to { opacity: 1; } +}