mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
Merge PR #474: миграция тулинга с ESLint + Prettier на Biome
Замена ESLint + Prettier (9 dev-зависимостей) на Biome (одна): линт ~10×, формат-чек ~49× быстрее. Паритет форматирования (single quotes/semi/width 100/trailing/arrowParens/jsxDouble) и Telegram WebView-гвардов (перенесены GritQL-плагином). Имена npm-скриптов lint/format:check сохранены — CI не тронут. Ревью-фиксы поверх PR (восстановление точного паритета): - grit-исключение сужено до якорного src/platform/ (было bare-substring "platform", щадившее любой файл с этим словом в имени) - вернул *.min.js/*.min.css в biome ignore (были в .prettierignore) - вернул *.css в lint-staged pre-commit - поправил устаревшую метку CI-шага
This commit is contained in:
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run ESLint
|
||||
- name: Run linter
|
||||
run: npm run lint
|
||||
|
||||
- name: Check formatting
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
dist
|
||||
node_modules
|
||||
public
|
||||
*.min.js
|
||||
*.min.css
|
||||
package-lock.json
|
||||
13
.prettierrc
13
.prettierrc
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "auto",
|
||||
"jsxSingleQuote": false,
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
35
biome-plugins/telegram-webview-guards.grit
Normal file
35
biome-plugins/telegram-webview-guards.grit
Normal file
@@ -0,0 +1,35 @@
|
||||
language js
|
||||
|
||||
// Cross-platform guard (was eslint no-restricted-properties): these browser
|
||||
// APIs misbehave inside the Telegram WebView. Route them through the platform
|
||||
// abstraction instead. The platform adapters and the clipboard util (the
|
||||
// canonical implementations) are exempted by the filename conditions below.
|
||||
|
||||
file(name=$name, body=$body) where {
|
||||
// Exempt the canonical implementations only, matching the old eslint override
|
||||
// scope (`src/platform/**`, `src/utils/clipboard.ts`) — an anchored path
|
||||
// fragment, not a bare "platform" substring that would also spare any
|
||||
// unrelated file with "platform" in its name.
|
||||
$name <: not includes "src/platform/",
|
||||
$name <: not includes or {
|
||||
"utils/clipboard.ts",
|
||||
"utils/clipboard.js"
|
||||
},
|
||||
$body <: contains bubble or {
|
||||
`window.confirm` as $use where {
|
||||
register_diagnostic(span=$use, message="Use useNativeDialog().confirm — window.confirm is silently ignored in the Telegram WebView.", severity="error")
|
||||
},
|
||||
`window.alert` as $use where {
|
||||
register_diagnostic(span=$use, message="Use useNativeDialog().alert — window.alert is silently ignored in the Telegram WebView.", severity="error")
|
||||
},
|
||||
`window.open` as $use where {
|
||||
register_diagnostic(span=$use, message="Use usePlatform().openLink / openTelegramLink — window.open is intercepted by the Telegram WebView.", severity="error")
|
||||
},
|
||||
`window.prompt` as $use where {
|
||||
register_diagnostic(span=$use, message="Use usePrompt() (PromptDialogHost) — window.prompt is not supported in the Telegram WebView.", severity="error")
|
||||
},
|
||||
`navigator.clipboard` as $use where {
|
||||
register_diagnostic(span=$use, message="Use copyToClipboard from @/utils/clipboard — it falls back to execCommand when the Clipboard API is unavailable (Telegram WebView).", severity="error")
|
||||
}
|
||||
}
|
||||
}
|
||||
94
biome.json
Normal file
94
biome.json
Normal file
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.5.3/schema.json",
|
||||
"files": {
|
||||
"includes": [
|
||||
"**",
|
||||
"!dist",
|
||||
"!node_modules",
|
||||
"!public",
|
||||
"!package-lock.json",
|
||||
"!**/*.min.js",
|
||||
"!**/*.min.css"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineWidth": 100,
|
||||
"lineEnding": "auto"
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "single",
|
||||
"jsxQuoteStyle": "double",
|
||||
"semicolons": "always",
|
||||
"trailingCommas": "all",
|
||||
"arrowParentheses": "always",
|
||||
"bracketSpacing": true
|
||||
}
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"a11y": {
|
||||
"recommended": false
|
||||
},
|
||||
"correctness": {
|
||||
"noUnusedVariables": "warn",
|
||||
"useExhaustiveDependencies": "warn",
|
||||
"noInnerDeclarations": "warn",
|
||||
"noUnsafeOptionalChaining": "warn"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "warn",
|
||||
"noArrayIndexKey": "off",
|
||||
"noImplicitAnyLet": "warn",
|
||||
"useIterableCallbackReturn": "warn"
|
||||
},
|
||||
"security": {
|
||||
"noGlobalEval": "error",
|
||||
"noDangerouslySetInnerHtml": "warn"
|
||||
}
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"enabled": false
|
||||
},
|
||||
"plugins": ["./biome-plugins/telegram-webview-guards.grit"],
|
||||
"css": {
|
||||
"parser": {
|
||||
"tailwindDirectives": true
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"includes": ["**/*.css"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"suspicious": {
|
||||
"noDuplicateProperties": "off",
|
||||
"noUnknownAtRules": {
|
||||
"options": {
|
||||
"ignore": ["tailwind"]
|
||||
},
|
||||
"level": "error"
|
||||
}
|
||||
},
|
||||
"correctness": {
|
||||
"noUnknownFunction": {
|
||||
"options": {
|
||||
"ignore": ["theme", "screen"]
|
||||
},
|
||||
"level": "error"
|
||||
}
|
||||
},
|
||||
"complexity": {
|
||||
"noImportantStyles": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import js from '@eslint/js';
|
||||
import globals from 'globals';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: ['dist', 'node_modules', 'public'],
|
||||
},
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-hooks/set-state-in-effect': 'off',
|
||||
'react-hooks/static-components': 'off',
|
||||
'react-hooks/immutability': 'off',
|
||||
'react-hooks/refs': 'off',
|
||||
'react-hooks/purity': 'off',
|
||||
'react-hooks/variables': 'off',
|
||||
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_' },
|
||||
],
|
||||
'no-empty': ['warn', { allowEmptyCatch: true }],
|
||||
'no-eval': 'error',
|
||||
'no-implied-eval': 'error',
|
||||
'no-new-func': 'error',
|
||||
'no-script-url': 'error',
|
||||
// Cross-platform guard: these browser APIs misbehave inside the Telegram WebView.
|
||||
// Route them through the platform abstraction instead. The platform adapters and
|
||||
// the clipboard util (the canonical implementations) are exempted below.
|
||||
'no-restricted-properties': [
|
||||
'error',
|
||||
{
|
||||
object: 'window',
|
||||
property: 'confirm',
|
||||
message:
|
||||
'Use useNativeDialog().confirm — window.confirm is silently ignored in the Telegram WebView.',
|
||||
},
|
||||
{
|
||||
object: 'window',
|
||||
property: 'alert',
|
||||
message:
|
||||
'Use useNativeDialog().alert — window.alert is silently ignored in the Telegram WebView.',
|
||||
},
|
||||
{
|
||||
object: 'window',
|
||||
property: 'open',
|
||||
message:
|
||||
'Use usePlatform().openLink / openTelegramLink — window.open is intercepted by the Telegram WebView.',
|
||||
},
|
||||
{
|
||||
object: 'window',
|
||||
property: 'prompt',
|
||||
message:
|
||||
'Use usePrompt() (PromptDialogHost) — window.prompt is not supported in the Telegram WebView.',
|
||||
},
|
||||
{
|
||||
object: 'navigator',
|
||||
property: 'clipboard',
|
||||
message:
|
||||
'Use copyToClipboard from @/utils/clipboard — it falls back to execCommand when the Clipboard API is unavailable (Telegram WebView).',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
// Canonical implementations that intentionally call the restricted browser APIs.
|
||||
files: ['src/platform/**/*.{ts,tsx}', 'src/utils/clipboard.ts'],
|
||||
rules: {
|
||||
'no-restricted-properties': 'off',
|
||||
},
|
||||
},
|
||||
eslintConfigPrettier,
|
||||
);
|
||||
1644
package-lock.json
generated
1644
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -8,10 +8,11 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write \"src/**/*.{ts,tsx,css,json}\"",
|
||||
"format:check": "prettier --check \"src/**/*.{ts,tsx,css,json}\"",
|
||||
"lint": "biome lint .",
|
||||
"lint:fix": "biome lint --write .",
|
||||
"format": "biome format --write .",
|
||||
"format:check": "biome format .",
|
||||
"check": "biome check .",
|
||||
"type-check": "tsc --noEmit",
|
||||
"preview": "vite preview",
|
||||
"prepare": "husky"
|
||||
@@ -74,35 +75,26 @@
|
||||
"zustand": "^5.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@biomejs/biome": "2.5.3",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/node": "^25.2.1",
|
||||
"@types/react": "^19.2.13",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react-twemoji": "^0.4.3",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.0",
|
||||
"globals": "^17.1.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.2.7",
|
||||
"postcss": "^8.4.31",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript-eslint": "^8.54.0",
|
||||
"vite": "^7.3.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
"*.{ts,tsx,json}": [
|
||||
"biome check --write --no-errors-on-unmatched"
|
||||
],
|
||||
"*.{css,json}": [
|
||||
"prettier --write"
|
||||
"*.css": [
|
||||
"biome format --write --no-errors-on-unmatched"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,11 +284,7 @@ export const banSystemApi = {
|
||||
|
||||
// Users
|
||||
getUsers: async (
|
||||
params: {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
status?: string;
|
||||
} = {},
|
||||
params: { offset?: number; limit?: number; status?: string } = {},
|
||||
): Promise<BanUsersListResponse> => {
|
||||
const response = await apiClient.get('/cabinet/admin/ban-system/users', { params });
|
||||
return response.data;
|
||||
@@ -349,11 +345,7 @@ export const banSystemApi = {
|
||||
|
||||
// Agents
|
||||
getAgents: async (
|
||||
params: {
|
||||
search?: string;
|
||||
health?: string;
|
||||
status?: string;
|
||||
} = {},
|
||||
params: { search?: string; health?: string; status?: string } = {},
|
||||
): Promise<BanAgentsListResponse> => {
|
||||
const response = await apiClient.get('/cabinet/admin/ban-system/agents', { params });
|
||||
return response.data;
|
||||
|
||||
@@ -52,7 +52,8 @@ const cardVariants = cva(
|
||||
);
|
||||
|
||||
export interface CardProps
|
||||
extends Omit<HTMLMotionProps<'div'>, 'children'>, VariantProps<typeof cardVariants> {
|
||||
extends Omit<HTMLMotionProps<'div'>, 'children'>,
|
||||
VariantProps<typeof cardVariants> {
|
||||
children: ReactNode;
|
||||
asChild?: boolean;
|
||||
haptic?: boolean;
|
||||
|
||||
@@ -7,7 +7,8 @@ import { buttonTap, buttonHover, springTransition } from '../../motion/transitio
|
||||
import { buttonVariants, type ButtonVariants } from './Button.variants';
|
||||
|
||||
export interface ButtonProps
|
||||
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'disabled'>, ButtonVariants {
|
||||
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'disabled'>,
|
||||
ButtonVariants {
|
||||
children: ReactNode;
|
||||
asChild?: boolean;
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -60,9 +60,8 @@ export const DialogOverlay = forwardRef<HTMLDivElement, DialogOverlayProps>(
|
||||
DialogOverlay.displayName = 'DialogOverlay';
|
||||
|
||||
// Content
|
||||
export interface DialogContentProps extends ComponentPropsWithoutRef<
|
||||
typeof DialogPrimitive.Content
|
||||
> {
|
||||
export interface DialogContentProps
|
||||
extends ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
|
||||
showCloseButton?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,8 @@ export {
|
||||
} from '@radix-ui/react-dropdown-menu';
|
||||
|
||||
// SubTrigger
|
||||
export interface DropdownMenuSubTriggerProps extends ComponentPropsWithoutRef<
|
||||
typeof DropdownMenuPrimitive.SubTrigger
|
||||
> {
|
||||
export interface DropdownMenuSubTriggerProps
|
||||
extends ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> {
|
||||
inset?: boolean;
|
||||
}
|
||||
|
||||
@@ -115,9 +114,8 @@ export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuConten
|
||||
DropdownMenuContent.displayName = 'DropdownMenuContent';
|
||||
|
||||
// Item
|
||||
export interface DropdownMenuItemProps extends ComponentPropsWithoutRef<
|
||||
typeof DropdownMenuPrimitive.Item
|
||||
> {
|
||||
export interface DropdownMenuItemProps
|
||||
extends ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> {
|
||||
inset?: boolean;
|
||||
destructive?: boolean;
|
||||
}
|
||||
@@ -210,9 +208,8 @@ export const DropdownMenuRadioItem = forwardRef<HTMLDivElement, DropdownMenuRadi
|
||||
DropdownMenuRadioItem.displayName = 'DropdownMenuRadioItem';
|
||||
|
||||
// Label
|
||||
export interface DropdownMenuLabelProps extends ComponentPropsWithoutRef<
|
||||
typeof DropdownMenuPrimitive.Label
|
||||
> {
|
||||
export interface DropdownMenuLabelProps
|
||||
extends ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> {
|
||||
inset?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,8 @@ export {
|
||||
} from '@radix-ui/react-popover';
|
||||
|
||||
// Content
|
||||
export interface PopoverContentProps extends ComponentPropsWithoutRef<
|
||||
typeof PopoverPrimitive.Content
|
||||
> {
|
||||
export interface PopoverContentProps
|
||||
extends ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> {
|
||||
showCloseButton?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,8 @@ import { dropdown, dropdownTransition } from '../../motion/transitions';
|
||||
export { Root as Select, Group as SelectGroup } from '@radix-ui/react-select';
|
||||
|
||||
// Trigger
|
||||
export interface SelectTriggerProps extends ComponentPropsWithoutRef<
|
||||
typeof SelectPrimitive.Trigger
|
||||
> {
|
||||
export interface SelectTriggerProps
|
||||
extends ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { motion, AnimatePresence, useDragControls, PanInfo } from 'framer-motion';
|
||||
import { motion, AnimatePresence, useDragControls, type PanInfo } from 'framer-motion';
|
||||
import {
|
||||
forwardRef,
|
||||
type ComponentPropsWithoutRef,
|
||||
@@ -97,9 +97,8 @@ export const SheetOverlay = forwardRef<HTMLDivElement, SheetOverlayProps>(
|
||||
SheetOverlay.displayName = 'SheetOverlay';
|
||||
|
||||
// Content
|
||||
export interface SheetContentProps extends ComponentPropsWithoutRef<
|
||||
typeof DialogPrimitive.Content
|
||||
> {
|
||||
export interface SheetContentProps
|
||||
extends ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
|
||||
showDragHandle?: boolean;
|
||||
showCloseButton?: boolean;
|
||||
enableDragToClose?: boolean;
|
||||
|
||||
@@ -3,10 +3,8 @@ import { forwardRef, type ComponentPropsWithoutRef } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { usePlatform } from '@/platform';
|
||||
|
||||
export interface SwitchProps extends Omit<
|
||||
ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>,
|
||||
'onChange'
|
||||
> {
|
||||
export interface SwitchProps
|
||||
extends Omit<ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>, 'onChange'> {
|
||||
label?: string;
|
||||
description?: string;
|
||||
onChange?: (checked: boolean) => void;
|
||||
|
||||
@@ -357,7 +357,7 @@ export default function Wheel() {
|
||||
// Web-only: synchronously pre-open a tab during the user gesture to dodge the
|
||||
// popup blocker before the async invoice URL resolves. Not reached in Telegram
|
||||
// (hasInvoice is true there, so the native invoice flow is used instead).
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
// biome-ignore lint: canonical popup-blocker workaround, see comment above
|
||||
preOpenedWindowRef.current = window.open('about:blank', '_blank') || null;
|
||||
}
|
||||
starsInvoiceMutation.mutate();
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
of the font stacks fixes every flag everywhere without touching any markup
|
||||
and without affecting any other text. Root fix, not a per-component wrap. */
|
||||
@font-face {
|
||||
font-family: 'Twemoji Country Flags';
|
||||
font-family: "Twemoji Country Flags";
|
||||
unicode-range: U+1F1E6-1F1FF, U+1F3F4, U+E0062-E007F;
|
||||
src: url('/fonts/TwemojiCountryFlags.woff2') format('woff2');
|
||||
src: url("/fonts/TwemojiCountryFlags.woff2") format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Animated gradient border — @property enables CSS-only angle animation */
|
||||
@property --border-angle {
|
||||
syntax: '<angle>';
|
||||
syntax: "<angle>";
|
||||
initial-value: 0deg;
|
||||
inherits: false;
|
||||
}
|
||||
@@ -295,7 +295,7 @@
|
||||
|
||||
/* Global Noise Texture — no mix-blend-mode to avoid fullscreen GPU compositing */
|
||||
body::before {
|
||||
content: '';
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
@@ -450,7 +450,7 @@ img.twemoji {
|
||||
|
||||
/* CSS Spotlight Effect via pseudo-element */
|
||||
.bento-card-hover::after {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at top, rgba(255, 255, 255, 0.06), transparent 60%);
|
||||
@@ -625,7 +625,7 @@ img.twemoji {
|
||||
/* Remove tap highlight and focus outline on mobile */
|
||||
button,
|
||||
a,
|
||||
[role='button'] {
|
||||
[role="button"] {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
@@ -633,13 +633,13 @@ img.twemoji {
|
||||
/* Only show focus ring for keyboard navigation */
|
||||
button:focus-visible,
|
||||
a:focus-visible,
|
||||
[role='button']:focus-visible {
|
||||
[role="button"]:focus-visible {
|
||||
@apply ring-2 ring-accent-500/50 ring-offset-2 ring-offset-dark-900;
|
||||
}
|
||||
|
||||
.light button:focus-visible,
|
||||
.light a:focus-visible,
|
||||
.light [role='button']:focus-visible {
|
||||
.light [role="button"]:focus-visible {
|
||||
@apply ring-offset-champagne-100;
|
||||
}
|
||||
|
||||
@@ -1181,11 +1181,11 @@ img.twemoji {
|
||||
|
||||
/* Glow effects */
|
||||
.glow-accent {
|
||||
box-shadow: 0 0 20px theme('colors.accent.500 / 30%');
|
||||
box-shadow: 0 0 20px theme("colors.accent.500 / 30%");
|
||||
}
|
||||
|
||||
.glow-success {
|
||||
box-shadow: 0 0 20px theme('colors.success.500 / 30%');
|
||||
box-shadow: 0 0 20px theme("colors.success.500 / 30%");
|
||||
}
|
||||
|
||||
/* Blur backdrop */
|
||||
@@ -1229,9 +1229,9 @@ img.twemoji {
|
||||
.shimmer {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
theme('colors.dark.800') 0%,
|
||||
theme('colors.dark.700') 50%,
|
||||
theme('colors.dark.800') 100%
|
||||
theme("colors.dark.800") 0%,
|
||||
theme("colors.dark.700") 50%,
|
||||
theme("colors.dark.800") 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
@@ -1240,9 +1240,9 @@ img.twemoji {
|
||||
.light .shimmer {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
theme('colors.champagne.200') 0%,
|
||||
theme('colors.champagne.300') 50%,
|
||||
theme('colors.champagne.200') 100%
|
||||
theme("colors.champagne.200") 0%,
|
||||
theme("colors.champagne.300") 50%,
|
||||
theme("colors.champagne.200") 100%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1256,7 +1256,7 @@ img.twemoji {
|
||||
}
|
||||
|
||||
/* Color picker range input styling */
|
||||
input[type='range'] {
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 12px;
|
||||
@@ -1267,13 +1267,13 @@ input[type='range'] {
|
||||
|
||||
/* Desktop: smaller track */
|
||||
@media (min-width: 640px) {
|
||||
input[type='range'] {
|
||||
input[type="range"] {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-thumb {
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 24px;
|
||||
@@ -1290,26 +1290,26 @@ input[type='range']::-webkit-slider-thumb {
|
||||
box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-thumb:hover {
|
||||
input[type="range"]::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow:
|
||||
0 3px 8px rgba(0, 0, 0, 0.4),
|
||||
0 0 0 2px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-thumb:active {
|
||||
input[type="range"]::-webkit-slider-thumb:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* Desktop: smaller thumb */
|
||||
@media (min-width: 640px) {
|
||||
input[type='range']::-webkit-slider-thumb {
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='range']::-moz-range-thumb {
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
@@ -1324,7 +1324,7 @@ input[type='range']::-moz-range-thumb {
|
||||
box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
input[type='range']::-moz-range-thumb:hover {
|
||||
input[type="range"]::-moz-range-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow:
|
||||
0 3px 8px rgba(0, 0, 0, 0.4),
|
||||
@@ -1333,25 +1333,25 @@ input[type='range']::-moz-range-thumb:hover {
|
||||
|
||||
/* Desktop: smaller thumb */
|
||||
@media (min-width: 640px) {
|
||||
input[type='range']::-moz-range-thumb {
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide number input spinners for cleaner look */
|
||||
input[type='number']::-webkit-inner-spin-button,
|
||||
input[type='number']::-webkit-outer-spin-button {
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type='number'] {
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
/* Custom checkbox styling - more visible */
|
||||
input[type='checkbox'] {
|
||||
input[type="checkbox"] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 1.25rem;
|
||||
@@ -1367,13 +1367,13 @@ input[type='checkbox'] {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
input[type='checkbox']:checked {
|
||||
input[type="checkbox"]:checked {
|
||||
background-color: rgb(var(--color-accent-500));
|
||||
border-color: rgb(var(--color-accent-500));
|
||||
}
|
||||
|
||||
input[type='checkbox']:checked::after {
|
||||
content: '';
|
||||
input[type="checkbox"]:checked::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 45%;
|
||||
@@ -1384,12 +1384,12 @@ input[type='checkbox']:checked::after {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
|
||||
input[type='checkbox']:focus {
|
||||
input[type="checkbox"]:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(var(--color-accent-500), 0.3);
|
||||
}
|
||||
|
||||
input[type='checkbox']:hover:not(:checked) {
|
||||
input[type="checkbox"]:hover:not(:checked) {
|
||||
border-color: rgb(var(--color-dark-500));
|
||||
background-color: rgb(var(--color-dark-600));
|
||||
}
|
||||
@@ -1477,17 +1477,17 @@ input[type='checkbox']:hover:not(:checked) {
|
||||
background-color: rgb(var(--color-dark-700));
|
||||
}
|
||||
|
||||
.light input[type='checkbox'] {
|
||||
.light input[type="checkbox"] {
|
||||
border-color: rgb(var(--color-champagne-400));
|
||||
background-color: rgb(var(--color-champagne-100));
|
||||
}
|
||||
|
||||
.light input[type='checkbox']:checked {
|
||||
.light input[type="checkbox"]:checked {
|
||||
background-color: rgb(var(--color-accent-500));
|
||||
border-color: rgb(var(--color-accent-500));
|
||||
}
|
||||
|
||||
.light input[type='checkbox']:hover:not(:checked) {
|
||||
.light input[type="checkbox"]:hover:not(:checked) {
|
||||
border-color: rgb(var(--color-champagne-500));
|
||||
background-color: rgb(var(--color-champagne-200));
|
||||
}
|
||||
@@ -1523,7 +1523,7 @@ input[type='checkbox']:hover:not(:checked) {
|
||||
}
|
||||
|
||||
.onboarding-tooltip::before {
|
||||
content: '';
|
||||
content: "";
|
||||
@apply absolute h-3 w-3 border-l border-t border-accent-500/30 bg-dark-900;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user