По фичам Biome v2.3: css.parser.tailwindDirectives снимает исключение
CSS из Biome (globals.css снова под формат-контролем; линт CSS выключен
override-ом — tailwind-функции theme() и пр. правила пока не знают),
lineEnding auto повторяет поведение prettier endOfLine: auto (CRLF в
Windows-чекаутах, LF на CI). Однократный формат-дифф globals.css
45+/45- — расхождения CSS-форматтера Biome с Prettier.
Переменные --rdp-* библиотека объявляет на самом .rdp-root, поэтому
оверрайды на родителе (.rdp-dark) шадовились — отсюда синий акцент,
синяя рамка вместо заливки и тусклые дни. Теперь целимся в
`.rdp-dark .rdp-root` (специфичность выше базовой). Выбранный день —
залит акцентом, числа — яркие, сегодня — акцентная подсветка, все
цвета через токены темы (dark/accent), так что инвертируются для
светлых тем (champagne) и остаются читаемыми везде.
Нативный <input type="date"> выглядел чужеродно (браузерный светлый календарь).
Новый общий компонент DateField: react-day-picker внутри Radix Popover, тёмная
тема через CSS-переменные .rdp-dark, навигация — наши иконки из барреля,
локализация месяцев/дней по языку кабинета (date-fns). Принимает/отдаёт
'YYYY-MM-DD' — drop-in замена нативного инпута. Подключён в селекторе периода
статистики (свой период). Остальные места — следующими шагами.
В recharts столбцы/секторы/точки становятся фокусируемыми, и при клике браузер
рисует свою обводку — артефакт на read-only графиках. Добавил гашение outline
для .recharts-wrapper/.surface/.sector/.rectangle.
Windows ships no glyphs for regional-indicator flag emoji, so flags fell back to country letters everywhere react-twemoji wasn't manually wrapped. Bundle the Twemoji country-flags woff2 locally (no runtime CDN — matters for our audience) and add it as an @font-face scoped with unicode-range to flag codepoints only, then put it first in every Tailwind font stack (sans/display/mono). The browser now uses it solely for flag characters, so every flag renders correctly app-wide with zero markup changes and no effect on any other text.
.prose ul/ol used list-inside, but TipTap wraps each <li>'s content
in <p>. Combining position:inside with a block-level <p> dropped the
content onto the next line under the marker — visible both inside
the editor and on the rendered article page.
Drop list-inside (default to outside) and add `.prose li > p` rules:
first paragraph renders inline with the marker, subsequent paragraphs
stay block to preserve multi-paragraph list items.
Closes Bedolaga bug-topic #597116.
Created with future-API intent during the optimize pass but no .tsx
consumer materialised — the three Subscription progress bars inline
their classes directly. Dead utility classes accumulate, confuse the
next reader ("this looks like our pattern, am I doing it wrong?"),
and add bytes to the CSS. Drop both the dark version and the
.light .progress-bar override.
If a future progress bar needs them back: inline first, extract only
when 3+ call sites share intent (per /impeccable extract guidance).
Audit P2: admin pages prioritise density but cabinet also runs in the
Telegram Mini App on phones. Small button targets (~24-32px) work for
mouse precision but fail mobile thumbs. Apply targeted bumps that keep
desktop density unchanged.
src/styles/globals.css
• .btn — add min-h-[44px] on mobile, sm:min-h-0 on desktop.
Cascades to every .btn-primary / .btn-secondary / .btn-ghost /
.btn-danger consumer (76 + 48 + N usages app-wide). One change,
everywhere.
• .btn-icon — same logic (min-h/min-w 44px mobile). Drops the
duplicate definition that appeared further down the file.
components/admin/userDetail/TicketsTab.tsx — mobile-critical chat
• Back button h-8 w-8 → h-10 w-10 sm:h-8 sm:w-8 (40px / 32px).
Adds aria-label since it had none.
• Status pills px-2.5 py-1 → +min-h-[36px] py-1.5 sm:min-h-0
sm:py-1 (36px / 26px). Mis-tap penalty on mobile is high (it
flips ticket status).
• Reply send button — add min-h/min-w 44px mobile + aria-label.
Why not blanket bump py-1.5 → py-2.5 across all 40+ admin pages:
visual density patterns were iteratively designed and a global sweep
would regress them. Per-page review can target specific buttons that
deserve mobile touch floor without breaking density elsewhere.
icon-only buttons not using .btn-icon (raw <button><svg/></button>)
left as-is — they're typically in compact toolbars where visual
density is the point. Per-page review under /impeccable adapt
target=<page> if mobile flow analysis flags one.
Audit: only 3 selectors respected prefers-reduced-motion (.hover-
border-gradient, .bento-card, .admin-orb). Codebase has ~358 animation
declarations and ~970 transition declarations — without a global rule
the rest ignored the OS-level preference.
Add a universal rule that collapses every animation and transition to
0.01ms when the user has requested reduced motion. State-conveying
motion (toast slide-in, modal fade, accordion expand) still completes
to its final frame — only the journey is skipped. Pure decorative
motion (background gradients, etc.) effectively disappears.
The 3 specific rules above stay as documentation of intent — this
catch-all subsumes them via !important. Affects every animation in
the app without per-call-site touchups. WCAG 2.3.3.
Note: dangerouslySetInnerHTML across all 17 hit sites was already
sanitized via DOMPurify (getSvgHtml uses SVG profile, renderMarkdown
uses strict ALLOWED_TAGS, linkifyText / sanitizeHtml / formatContent
all wire through DOMPurify). The earlier audit P2 flag was a false
positive — no source change needed for that item.
Audit P1: 5 of 7 layout-property animations were transition-[width]
or transition-[left] — these trigger reflow + repaint every frame.
Convert to transform-based equivalents that run on the compositor.
Subscription.tsx — 3 progress bars + 1 toggle:
• Device usage bar (track w-16) — width % → scaleX with a 0.0625
floor (= 4px on 64px track) preserving the minWidth: 4px guard
when connectedDevices > 0.
• Traffic-purchase progress — absolute inset-0 + width % →
transform: scaleX with origin-left, gradient stretches with the
fill same as before.
• Subscription period progress — same conversion.
• Autopay toggle thumb — left: 3↔26px → translateX(0↔23px) with
fixed left: 3px. role='switch' + aria-checked unchanged.
globals.css — .progress-fill utility class:
• transition-property: width, background-color → transform,
background-color. Width set to w-full + origin-left so consumers
just set transform: scaleX(0..1). Currently has no .tsx consumers
(grep clean) but the API stays valid for future use.
Skipped (bounded one-shot, transform alternative has tradeoffs):
• Info.tsx + InfoPageView.tsx accordion height. Animating from 0
to content-driven scrollHeight needs layout one way or another —
grid-template-rows 0fr/1fr and max-height also layout-class
properties; transform: scaleY squishes text contents. Cost is
bounded: one animation per user click, not continuous, not on
critical path. Separate work if priority changes.
Visual parity: scaleX renders identical geometry to width % on the
inner fill. box-shadow on the device bar (0 0 8px 40)
scales proportionally — tiny visual difference at full width, none
visible at partial widths.
The #FFB800 (23 hits) and #FF3B5C (11 hits) raw hex literals across
Subscription / SubscriptionPurchase / TrialOfferCard / useTrafficZone
all represent the same semantic: 'expiring soon' and 'expired'.
Distinct from warning-500 (#f59e0b) and error-500 (#ef4444) — the
subscription timeline needs a sharper, hotter signal than the
system-wide warning/error roles.
Add two semantic tokens:
--color-urgent-400: 255, 184, 0 (#FFB800)
--color-critical-500: 255, 59, 92 (#FF3B5C)
Exposed via Tailwind as urgent.400 / critical.500. Sweep all
single-and double-quoted hex literals to rgb(var(--color-...))
form so style attrs, SVG stroke=, and ternary string values all
resolve through CSS vars (auto-respects future light-theme
variants).
Left as-is: 4 hex appearances inside gradient pair strings —
linear-gradient(135deg, #FFB800, #FF8C00) — where the partner
color (#FF8C00 / #FF6B35) isn't in the token set. Partial-replace
inside gradient strings would just add noise; full extraction of
the orange-ramp companion is out of scope for this pass.
- Add replaces_tab field to admin editor with conflict detection
- Show custom InfoPages as extra tabs on /info page
- Replace built-in tab content when replacement is configured
- Rich DOMPurify sanitizer for TipTap content (images, video, iframe)
- Fix query race: built-in queries wait for tab-replacements to load
- Horizontal scroll for tabs on mobile, 44px touch targets
- Responsive prose: scrollable tables, h-auto on img/video, light theme borders
- Loyalty tier names truncation, progress bar labels stack on mobile
- Rewrite AdminPanel with cosmic floating orbs background using theme colors
- Add stats bar: uptime, bot version, cabinet version, trial/paid counts from API
- Glass morphism cards with 3D tilt effect (rAF-throttled) and entry animations
- Live search with Cmd+K shortcut, highlight matches, permission-aware filtering
- Register Tailwind `light:` variant for proper light theme support
- Mobile optimizations: reduced orbs (3), lower blur (80px), compact 2-col stats
- Telegram MiniApp safe area support via useTelegramSDK
- Full accessibility: aria-hidden on decorative SVGs, aria-labels, role="status"
- React.memo on StatsBar, GlassCard, CosmosBackground for render performance
- Memo-stable section refs via Set<id> pattern to preserve GlassCard memo during search
- i18n keys added to all 4 locales (ru, en, zh, fa)
- Кнопка «Подключить устройство» заблокирована при достижении лимита
- Haptic feedback при нажатии на заблокированную кнопку
- Предупреждение «Отключите устройства для подключения новых»
- Убран дублирующийся текст трафика под прогресс-баром (безлимит)
Add background-size: 200px and background-repeat: repeat so the SVG
noise pattern tiles at its native resolution instead of stretching
across the entire viewport on high-res displays.
Root cause: 225 individually animated DOM elements forced Chrome's
compositor to create separate paint regions per element. Any hover
state change on page content triggered re-compositing of all 225
regions, causing visible flickering on all pages.
Canvas fix: single <canvas> element renders the entire grid effect
via requestAnimationFrame. Canvas content is pixel-based and cannot
be affected by DOM hover state changes on other elements.
Additional fixes:
- Fix z-index collision: portal z-index -1 → -2 (was same as body::before noise)
- Replace all transition-all with specific properties on .btn, .btn-icon,
.input, .nav-item, .bottom-nav-item, checkbox
- Remove backdrop-blur-sm from desktop .bento-card and .card
(forced Chrome to re-sample animated background pixels on every hover)
Remove translateZ(0) from base states of .bento-card, .card, .glass
and their light theme variants. Remove transform-gpu from Card.tsx.
Change bentoFadeIn animation to end with transform:none instead of
translateY(0). GPU promotion now only occurs during hover/active
interactions, preventing Chrome from creating excessive compositing
layers that cause flickering with animated backgrounds.
Previous isolation:isolate fix didn't work because:
- position:fixed elements still see through isolation boundary
- backdrop-filter samples all rendered content regardless of stacking context
- The real issue: animated background at z-index:0 with will-change:transform
forces Chrome to implicitly composite EVERY overlapping element
Fix: render BackgroundRenderer via createPortal on document.body with
z-index:-1, placing it below the root stacking context. This eliminates
implicit compositing entirely.
Also:
- Remove backdrop-blur-xl from desktop header (was resampling bg 60fps)
- Remove will-change:opacity from card ::after pseudo-element
- Replace transition-all with transition-colors on header buttons
Root cause: backdrop-filter on header/nav resamples the animated
background 60fps, causing GPU compositing flicker (Chromium bug).
- Add isolation: isolate wrapper in AppShell to prevent backdrop-filter
from seeing through to the animated background layer
- Add will-change: transform + translateZ(0) to BackgroundRenderer
for stable GPU layer
- Replace contain: layout style with contain: content on main
- Replace transition-all with specific properties in Card component
- Preserve translateZ(0) on all hover/active transforms to prevent
GPU compositing layer teardown/recreation cycle
- Lower body::before noise texture z-index to -1
- Add will-change: opacity to card spotlight pseudo-element
- Include backdrop-blur-linear in mobile performance override
- Replace transition-all with specific properties in BentoCard
- Extract CountdownTimer into React.memo component to isolate 1s
interval re-renders from the full Subscription page
- Add box-shadow transition to .hover-border-gradient (was instant)
- Remove conflicting p-[1.5px] from PurchaseCTAButton (CSS border
already handles 1.5px)
- Replace transition-all with specific properties on bento-card-hover,
bento-card-glow, device dots, toggle switches, progress bars
- Reduce translateY hover from -4px to -2px to prevent cursor
losing hover target during fast vertical mouse movement
Pure CSS approach: @property --border-angle + conic-gradient on border-box.
No extra elements, no overflow issues, no JS animation.
Smooth rotation via CSS Houdini custom property animation.
- Move Google Fonts from CSS @import (render-blocking) to <link> in
index.html with preconnect for earlier discovery
- Remove @import from globals.css — browser no longer waits for CSS
parse -> @import fetch -> font CSS parse before first paint
- Defer initLogoPreload() to requestIdleCallback so logo fetch
doesn't compete with critical resources during page load
- Remove mix-blend-mode: overlay from body::before noise texture
(was forcing fullscreen GPU compositing on every paint)
- Disable noise texture on mobile via display:none media query
- Replace backdrop-filter animation in backdropFadeIn/Out keyframes
with opacity-only animation (backdrop-filter transition is expensive)
- Replace toast progress bar width animation with scaleX (GPU-composited)
- Remove redundant will-change: transform from .glass and .wave-blob
- Fix formatContent to handle mixed content (inline HTML + newlines)
- Only skip paragraph conversion for content with block-level HTML
- Add sanitizer support for <s>, <del>, <ins>, <tg-spoiler>
- Add prose styles for underline, strikethrough, and spoiler tags
- Light theme variants included
CSS wave-blobs now use accent-800 instead of accent-500 for a subdued
background glow. Aurora WebGL shader receives a darkened accent (45%
brightness) so bright accent colors like #e85002 no longer produce
overly intense background blobs while button colors stay unchanged.
Replace hardcoded RGB values with var(--color-champagne-*) references
so the light theme respects admin-configured colors (background, surface,
text). Add !important to override inline styles from applyThemeColors.
Update card/glass/input/button overrides to use champagne-50 (dynamic
surface color) instead of hardcoded white.
Instead of manually overriding each bg-dark-*/border-dark-* opacity
variant with !important rules, swap the entire dark palette CSS
variables to champagne equivalents in .light scope. This automatically
handles all opacity variants, hover states, borders across all pages
without needing individual class overrides for each combination.
- Reduce base .btn padding from px-5 py-2.5 to px-4 py-2
- Change rounded-xl to rounded-lg for buttons
- Remove hover:scale effect for cleaner look
- Fix large buttons in AdminBanSystem, AdminBroadcastDetail
- Compact toggle buttons in AdminPromoOfferSend
- Set --bento-radius to 16px (was 24px) for more modern look
- Unified .card, .bento-card and Card component to use same:
- border-radius: var(--bento-radius)
- border: border-dark-700/40
- background: bg-dark-900/70
- inset shadow for glass effect
- Reduced padding from 24px to 20px
- Re-enable page transition animations in Telegram Mini App
- Change AnimatePresence mode from popLayout to wait (prevents black squares)
- Add initial={false} to skip animation on first render
- Add custom checkbox styling with better visibility (bg-dark-700, border-dark-600)
- Custom checkmark appearance when checked
- Light theme checkbox support
Major changes:
- Redesign cabinet with Linear-style components and top navigation
- Replace detail modals with dedicated pages (users, broadcasts, email templates)
- Add email channel support for broadcasts
- Remove pull-to-refresh, improve drag-and-drop on touch devices
- Fix Telegram Mini App: fullscreen, back button, scroll restoration
- Unify admin pages color scheme to design system
- Mobile-first improvements: horizontal tabs for settings, better touch targets
- Added new BentoCard and BentoSkeleton components for consistent card styling.
- Refactored existing components to utilize BentoCard for improved visual consistency.
- Updated Tailwind configuration to include new spacing and border radius utilities.
- Enhanced global styles with custom scrollbar and noise texture for better aesthetics.
- Introduced color conversion utilities for improved color handling across components.
- Added RGB value updates on color input changes and preset selections.
- Refactored RGB sliders to always be visible, enhancing user experience across devices.
- Improved range input styles for better accessibility and visual feedback.
- Updated number input fields for RGB values to hide spinners and ensure a cleaner look.