Новый стиль

This commit is contained in:
Халимов Рустам
2026-03-31 21:11:05 +03:00
parent 4faa7561a0
commit aa69c44a64
39 changed files with 3812 additions and 735 deletions

View File

@@ -5,7 +5,8 @@
<title>Knot Messenger</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet" />
</head>
<body>
<div id="root"></div>

View File

@@ -15,10 +15,10 @@ export default function App() {
if (isLoading) {
return (
<div className="h-full flex items-center justify-center bg-surface">
<div className="flex flex-col items-center gap-4">
<div className="h-full flex items-center justify-center bg-surface-container-lowest">
<div className="flex flex-col items-center gap-6">
<AppLoader />
<p className="text-zinc-500 text-sm">Загрузка...</p>
<p className="text-on-surface-variant/40 text-[10px] font-black uppercase tracking-[0.3em] animate-pulse">Knot Loading</p>
</div>
</div>
);
@@ -45,14 +45,14 @@ export default function App() {
function AppLoader() {
return (
<div className="relative w-12 h-12">
<div className="absolute inset-0 rounded-full border-2 border-transparent border-t-accent animate-spin" />
<div className="relative w-16 h-16">
<div className="absolute inset-0 rounded-3xl border-2 border-transparent border-t-primary animate-spin shadow-[0_0_15px_rgba(var(--primary-rgb),0.3)]" />
<div
className="absolute inset-1 rounded-full border-2 border-transparent border-t-accent/70 animate-spin"
className="absolute inset-2 rounded-3xl border-2 border-transparent border-t-primary/60 animate-spin"
style={{ animationDuration: '0.8s', animationDirection: 'reverse' }}
/>
<div
className="absolute inset-2 rounded-full border-2 border-transparent border-t-accent/40 animate-spin"
className="absolute inset-4 rounded-3xl border-2 border-transparent border-t-primary/30 animate-spin"
style={{ animationDuration: '0.6s' }}
/>
</div>

View File

@@ -14,6 +14,10 @@ const translations = {
// Profile
name: 'Имя',
username: 'Имя пользователя',
chats: 'Чаты',
calls: 'Звонки',
contacts: 'Контакты',
archive: 'Архив',
aboutMe: 'О себе',
birthday: 'Дата рождения',
enterName: 'Введите имя',
@@ -300,6 +304,10 @@ const translations = {
tellAboutYourself: 'Tell about yourself',
removePhoto: 'Remove photo',
language: 'Language',
chats: 'Chats',
calls: 'Calls',
contacts: 'Contacts',
archive: 'Archive',
interfaceLang: 'Interface language',
theme: 'Theme',
russian: 'Русский',

View File

@@ -31,7 +31,7 @@ function AvatarInner({ src, name, size = 'md', className = '', online }: AvatarP
const gradientClass = generateAvatarColor(name || '');
return (
<div className={`relative shrink-0 ${className}`}>
<div className={`relative shrink-0 ${className} rounded-full border-2 border-outline-variant/10 overflow-hidden`}>
{src ? (
<img
src={src}
@@ -40,15 +40,15 @@ function AvatarInner({ src, name, size = 'md', className = '', online }: AvatarP
/>
) : (
<div
className={`${sizeClass} rounded-full bg-gradient-to-br ${gradientClass} flex items-center justify-center text-white font-medium`}
className={`${sizeClass} rounded-full bg-gradient-to-br from-primary/80 to-primary flex items-center justify-center text-on-primary font-bold tracking-tighter`}
>
{initials}
</div>
)}
{online !== undefined && (
<div
className={`absolute bottom-0 right-0 ${onlineDotSize[size]} rounded-full border-surface ${
online ? 'bg-emerald-500' : 'bg-zinc-500'
className={`absolute bottom-0 right-0 ${onlineDotSize[size]} rounded-full border-surface-container ${
online ? 'bg-success shadow-[0_0_8px_rgba(76,175,80,0.5)]' : 'bg-on-surface-variant/20'
}`}
/>
)}

View File

@@ -0,0 +1,59 @@
import { useAuthStore } from '../../../modules/auth/application/authStore';
import { useLang } from '../../infrastructure/i18n';
interface GlobalNavBarProps {
activeTab: string;
onTabChange: (tab: string) => void;
}
export default function GlobalNavBar({ activeTab, onTabChange }: GlobalNavBarProps) {
const { user } = useAuthStore();
const { t } = useLang();
const menuItems = [
{ id: 'chats', icon: 'chat', label: t('chats') },
{ id: 'calls', icon: 'call', label: t('calls') },
{ id: 'contacts', icon: 'contacts', label: t('contacts') },
{ id: 'archive', icon: 'archive', label: t('archive') },
{ id: 'settings', icon: 'settings', label: t('settings') },
];
return (
<nav className="fixed left-0 top-0 h-full z-40 bg-surface-container w-20 flex flex-col items-center py-6 slide-on-ice border-none shadow-xl">
<div className="mb-10 flex flex-col items-center">
<span className="text-2xl font-black text-primary tracking-tighter italic knot-logo-spin">Knot</span>
</div>
<div className="flex flex-col gap-6 w-full px-2">
{menuItems.map((item) => (
<button
key={item.id}
onClick={() => onTabChange(item.id)}
className={`flex flex-col items-center justify-center p-3 rounded-xl transition-all duration-300 scale-95 active:scale-90 ${
activeTab === item.id
? 'bg-primary/10 text-primary'
: 'text-on-surface-variant hover:bg-surface-container-high hover:text-primary'
}`}
>
<span
className={`material-symbols-outlined mb-1 ${activeTab === item.id ? 'fill-1' : ''}`}
style={{ fontVariationSettings: `'FILL' ${activeTab === item.id ? 1 : 0}` }}
>
{item.icon}
</span>
<span className="text-[10px] font-medium">{item.label}</span>
</button>
))}
</div>
<div className="mt-auto group cursor-pointer relative">
<div className="absolute -inset-1 bg-primary/20 rounded-full opacity-0 group-hover:opacity-100 blur transition-opacity" />
<img
alt="User Profile"
className="relative w-10 h-10 rounded-full border-2 border-outline-variant/10 hover:border-primary/50 transition-colors object-cover"
src={user?.avatar || `https://ui-avatars.com/api/?name=${user?.username || 'user'}&background=3096e5&color=fff`}
/>
</div>
</nav>
);
}

View File

@@ -98,59 +98,29 @@ export default function Sidebar() {
return (
<>
<div className="w-full h-full flex flex-col bg-surface-secondary sm:rounded-[2rem] overflow-hidden border-x sm:border border-border/50 shadow-2xl relative z-10">
{/* Шапка */}
<div className="h-14 px-3 flex items-center justify-between border-b border-border/40 bg-surface-secondary flex-shrink-0">
<div className="flex items-center gap-2">
<button
onClick={() => setShowSideMenu(true)}
className="p-1.5 rounded-lg hover:bg-surface-hover transition-colors text-zinc-400 hover:text-white"
title={t('menu')}
>
<Menu size={20} />
</button>
<div className="flex items-center gap-2.5 min-w-0">
<div className="flex items-center justify-center w-8 h-8 rounded-xl bg-accent text-white flex-shrink-0">
<MessageSquare size={16} fill="currentColor" strokeWidth={0} />
</div>
<h1 className="text-[16px] font-semibold text-white truncate tracking-tight">Knot Messenger</h1>
</div>
</div>
<button
onClick={() => setShowNewChat(true)}
className="p-1.5 rounded-lg hover:bg-surface-hover transition-colors text-zinc-400 hover:text-white"
title={t('newChat')}
>
<Plus size={22} />
</button>
<div className="w-full h-full flex flex-col bg-surface-container-low overflow-hidden border-none relative z-10 slide-on-ice">
{/* Title Section (Minimal) */}
<div className="px-6 py-8 pb-4">
<h1 className="text-2xl font-bold font-headline text-white tracking-tight leading-none">{t('chats')}</h1>
</div>
{/* Поиск */}
<div className="px-3 py-2 bg-surface-secondary">
{/* Search */}
<div className="px-6 mb-4">
<div className="relative group">
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500 transition-colors" />
<span className="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 group-focus-within:text-primary transition-colors text-[20px]">search</span>
<input
type="text"
placeholder={t('searchChats')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-9 pr-8 py-1.5 rounded-[10px] bg-surface-tertiary text-[14px] text-white placeholder-zinc-500 border border-transparent focus:border-accent/50 hover:bg-surface-hover transition-all outline-none"
className="w-full pl-12 pr-4 py-3 rounded-xl bg-surface-container-highest text-sm text-on-surface placeholder-on-surface-variant/30 border-none focus:ring-2 focus:ring-primary/20 hover:bg-surface-bright transition-all outline-none"
/>
{searchQuery && (
<button
onClick={() => setSearchQuery('')}
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded-full text-zinc-400 hover:text-white transition-colors"
title={t('clear')}
>
<X size={14} />
</button>
)}
</div>
</div>
{/* Story circles */}
{(storyGroups.length > 0 || true) && (
<div className="flex items-center gap-2 px-3 py-2 overflow-x-auto scrollbar-hide border-b border-border/20 flex-shrink-0">
<div className="flex items-center gap-4 px-6 py-2 pb-6 overflow-x-auto scrollbar-hide flex-shrink-0">
{/* Add story circle */}
<button
onClick={() => setShowCreateStory(true)}
@@ -197,10 +167,10 @@ export default function Sidebar() {
)}
{/* Список чатов */}
<div className="flex-1 overflow-y-auto">
<div className="flex-1 overflow-y-auto px-2 custom-scrollbar">
{filteredChats.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-zinc-500 gap-3 px-6">
<MessageSquare size={40} className="opacity-30" />
<div className="flex flex-col items-center justify-center h-full text-on-surface-variant/40 gap-3 px-6">
<span className="material-symbols-outlined text-5xl opacity-20">chat_bubble</span>
<p className="text-sm text-center">
{searchQuery ? t('nothingFound') : t('noChats')}
</p>
@@ -211,6 +181,15 @@ export default function Sidebar() {
))
)}
</div>
{/* Float Action Button equivalent for Web */}
<button
onClick={() => setShowNewChat(true)}
className="absolute bottom-6 right-6 w-14 h-14 rounded-2xl bg-primary text-on-primary-container shadow-lg shadow-primary/20 flex items-center justify-center hover:scale-105 active:scale-95 transition-transform slide-on-ice"
title={t('newChat')}
>
<span className="material-symbols-outlined text-2xl">add</span>
</button>
</div>
{/* Модалки */}

View File

@@ -1,340 +1,234 @@
@import "tailwindcss";
:root {
color-scheme: dark;
--primary: #9acbff;
--primary-container: #3096e5;
--on-primary: #003355;
--on-primary-container: #e0f2ff;
--secondary: #c7c6ca;
--secondary-container: #48494c;
--on-secondary: #2f3033;
--on-secondary-container: #e3e2e6;
--tertiary: #53e16f;
--tertiary-container: #006e25;
--on-tertiary: #00390a;
--on-tertiary-container: #affca0;
--error: #ffb4ab;
--error-container: #93000a;
--on-error: #690005;
--on-error-container: #ffdad6;
--surface: #131313;
--surface-container-lowest: #0b0b0b;
--surface-container-low: #1b1b1b;
--surface-container: #201f1f;
--surface-container-high: #2a2a2a;
--surface-container-highest: #353535;
--on-surface: #f5f5fa;
--on-surface-variant: #b4b4c3;
--outline: #3c3c4b;
--outline-variant: #2d2d37;
--ease-ice: cubic-bezier(0.4, 0, 0.2, 1);
--shadow-ambient: 0 40px 80px -20px rgba(0, 0, 0, 0.5);
}
@theme {
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-headline: 'Inter', sans-serif;
--font-body: 'Inter', sans-serif;
--color-knot-50: #eef2ff;
--color-knot-100: #e0e7ff;
--color-knot-200: #c7d2fe;
--color-knot-300: #a5b4fc;
--color-knot-400: #818cf8;
--color-knot-500: #6366f1;
--color-knot-600: #4f46e5;
--color-knot-700: #4338ca;
--color-knot-800: #3730a3;
--color-knot-900: #312e81;
--color-knot-950: #1e1b4b;
--color-primary: var(--primary);
--color-primary-container: var(--primary-container);
--color-on-primary: var(--on-primary);
--color-on-primary-container: var(--on-primary-container);
--color-surface: #09090b;
--color-surface-secondary: #111113;
--color-surface-tertiary: #1a1a1e;
--color-surface-hover: #222226;
--color-border: rgba(255, 255, 255, 0.08);
--color-border-light: rgba(255, 255, 255, 0.12);
--color-secondary: var(--secondary);
--color-secondary-container: var(--secondary-container);
--color-on-secondary: var(--on-secondary);
--color-on-secondary-container: var(--on-secondary-container);
--color-accent: #6366f1;
--color-accent-hover: #818cf8;
--color-accent-light: rgba(99, 102, 241, 0.15);
--color-tertiary: var(--tertiary);
--color-tertiary-container: var(--tertiary-container);
--color-on-tertiary: var(--on-tertiary);
--color-on-tertiary-container: var(--on-tertiary-container);
--color-error: var(--error);
--color-error-container: var(--error-container);
--color-on-error: var(--on-error);
--color-on-error-container: var(--on-error-container);
--color-surface: var(--surface);
--color-surface-container-lowest: var(--surface-container-lowest);
--color-surface-container-low: var(--surface-container-low);
--color-surface-container: var(--surface-container);
--color-surface-container-high: var(--surface-container-high);
--color-surface-container-highest: var(--surface-container-highest);
--color-on-surface: var(--on-surface);
--color-on-surface-variant: var(--on-surface-variant);
--color-outline: var(--outline);
--color-outline-variant: var(--outline-variant);
--animate-kinetic-in: slideInUp 0.6s var(--ease-ice) forwards;
}
* {
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
}
*::-webkit-scrollbar {
width: 6px;
}
*::-webkit-scrollbar-track {
background: transparent;
}
*::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
*::-webkit-scrollbar-thumb:hover {
background-color: rgba(255, 255, 255, 0.2);
}
html, body, #root {
/* Global resets and base styles */
html,
body,
#root {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
body {
font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', Arial, sans-serif);
background-color: var(--color-surface, #17212b);
color: #fafafa;
background-color: #131313 !important;
/* Force to Obsidian surface */
color: #f5f5fa !important;
/* Force to high-contrast white */
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
/* Base style for all elements if they inherit color incorrectly */
* {
box-sizing: border-box;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
/* Kinetic Core Components */
.knot-card {
background-color: var(--surface-container-low) !important;
border-radius: 2rem;
/* 32px */
transition: all 0.4s var(--ease-ice);
overflow: hidden;
}
@keyframes pulse-soft {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
.knot-card-active {
background-color: var(--surface-container) !important;
}
.animate-slide-in {
animation: slideIn 0.2s ease-out;
.knot-input-group {
background-color: rgba(53, 53, 53, 0.4) !important;
border-radius: 1.75rem;
padding: 1.25rem 1.6rem;
border: 1px solid rgba(154, 203, 255, 0.1);
transition: all 0.4s var(--ease-ice);
display: flex;
flex-direction: column;
backdrop-filter: blur(10px);
}
.animate-fade-in {
animation: fadeIn 0.15s ease-out;
.knot-input-group:focus-within {
background-color: rgba(53, 53, 53, 0.6) !important;
border-color: #9acbff !important;
box-shadow: 0 0 0 1px #9acbff, 0 0 40px rgba(154, 203, 255, 0.1);
}
.glass {
background: rgba(255, 255, 255, 0.03);
.knot-input-group input {
background: transparent !important;
border: none !important;
outline: none !important;
width: 100%;
color: #f5f5fa !important;
}
.knot-button-primary {
background: linear-gradient(135deg, #9acbff, #3096e5) !important;
color: #e0f2ff !important;
border-radius: 1.5rem;
font-weight: 900;
letter-spacing: 0.05em;
transition: all 0.4s var(--ease-ice);
}
.knot-button-primary:hover {
transform: translateY(-2px);
filter: brightness(1.1);
box-shadow: 0 24px 32px -12px rgba(48, 150, 229, 0.4);
}
/* Utilities */
.glass-effect {
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.06);
background: rgba(19, 19, 19, 0.6);
}
.glass-strong {
background: rgba(17, 17, 19, 0.85);
backdrop-filter: blur(40px);
-webkit-backdrop-filter: blur(40px);
border: 1px solid rgba(255, 255, 255, 0.1);
.slide-on-ice {
transition: all 0.4s var(--ease-ice);
}
.gradient-text {
background: linear-gradient(135deg, #6366f1, #8b5cf6, #a855f7);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
.animate-kinetic {
animation: var(--animate-kinetic-in);
}
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: var(--outline-variant);
border-radius: 10px;
}
/* Overrides for legacy code */
.bg-surface {
background-color: var(--surface) !important;
}
.bg-surface-container-low {
background-color: var(--surface-container-low) !important;
}
.bg-surface-container-lowest {
background-color: var(--surface-container-lowest) !important;
}
.bg-surface-container-high {
background-color: var(--surface-container-high) !important;
}
.text-on-surface {
color: var(--on-surface) !important;
}
.text-on-surface-variant {
color: var(--on-surface-variant) !important;
}
.text-primary {
color: var(--primary) !important;
}
.text-accent {
color: #9acbff !important;
}
/* Bubble overrides */
.bubble-sent {
background: #3b82f6;
background: linear-gradient(135deg, #9acbff, #3096e5) !important;
color: #e0f2ff !important;
border-radius: 1.5rem;
border-bottom-right-radius: 0.25rem;
}
.bubble-received {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.08);
background: var(--surface-container-highest) !important;
color: var(--on-surface) !important;
border-radius: 1.5rem;
border-bottom-left-radius: 0.25rem;
}
.bubble-sent ::selection {
background: rgba(255, 255, 255, 0.35);
}
/* ==================== */
/* Animation Keyframes */
/* ==================== */
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
@keyframes call-wave {
0% { transform: scale(1); opacity: 0.6; }
100% { transform: scale(2.5); opacity: 0; }
}
.animate-float {
animation: float 6s ease-in-out infinite;
}
.animate-float-delayed {
animation: float 6s ease-in-out 3s infinite;
}
.animate-call-wave {
animation: call-wave 2s ease-out infinite;
}
.animate-call-wave-delayed {
animation: call-wave 2s ease-out 1s infinite;
}
/* ==================== */
/* Chat Themes */
/* ==================== */
.chat-theme-midnight {
background-color: #0f0f13;
background-image: url("data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3z' fill='%23ffffff' fill-opacity='0.02' fill-rule='evenodd'/%3E%3C/svg%3E");
}
.chat-theme-ocean {
background-color: #0b172a;
background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%233b82f6' fill-opacity='0.05' fill-rule='evenodd'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/svg%3E");
}
.chat-theme-forest {
background-color: #0f1c15;
background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%2310b981' fill-opacity='0.04' fill-rule='evenodd'%3E%3Ccircle cx='3' cy='3' r='3'/%3E%3Ccircle cx='13' cy='13' r='3'/%3E%3C/g%3E%3C/svg%3E");
}
.chat-theme-sunset {
background: linear-gradient(#1f111a, #150a0f);
position: relative;
}
.chat-theme-sunset::after {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
pointer-events: none;
background-image: radial-gradient(circle at 50% 150%, rgba(236, 72, 153, 0.1), transparent 60%);
}
.chat-theme-classic {
background-color: #121215;
}
.chat-theme-neon {
background-color: #0b0f19;
position: relative;
}
.chat-theme-neon::before {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
pointer-events: none;
background: radial-gradient(circle 400px at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(139, 92, 246, 0.15), transparent 80%);
transition: background 0.15s ease-out;
}
.chat-theme-aurora {
background: linear-gradient(135deg, #022c22, #064e3b);
position: relative;
}
.chat-theme-aurora::before {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
pointer-events: none;
background: radial-gradient(circle 600px at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(16, 185, 129, 0.2), transparent 70%);
transition: background 0.15s ease-out;
}
.chat-theme-cyber {
background-color: #000;
background-image:
linear-gradient(rgba(245, 158, 11, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(245, 158, 11, 0.03) 1px, transparent 1px);
background-size: 20px 20px;
position: relative;
}
.chat-theme-cyber::before {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
pointer-events: none;
background: radial-gradient(circle 300px at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(245, 158, 11, 0.15), transparent);
transition: background 0.15s ease-out;
}
.chat-theme-glass {
background-color: #0d1117;
position: relative;
overflow: hidden;
}
.chat-theme-glass::before {
content: '';
position: absolute;
width: 50vw;
height: 50vh;
left: var(--mouse-x, 50%);
top: var(--mouse-y, 50%);
transform: translate(-50%, -50%);
pointer-events: none;
background: radial-gradient(circle, rgba(59, 130, 246, 0.2), transparent 60%);
filter: blur(80px);
transition: left 0.5s cubic-bezier(0.2, 0.8, 0.2, 1), top 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.chat-theme-void {
background-color: #000;
position: relative;
}
.chat-theme-void::before {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
pointer-events: none;
background: radial-gradient(circle 120px at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(255, 255, 255, 0.05), transparent);
transition: background 0.1s;
}
.chat-bg {
background-color: #09090b;
background-image:
radial-gradient(ellipse at 20% 50%, rgba(99, 102, 241, 0.06) 0%, transparent 50%),
radial-gradient(ellipse at 80% 20%, rgba(139, 92, 246, 0.05) 0%, transparent 50%),
radial-gradient(ellipse at 60% 80%, rgba(168, 85, 247, 0.04) 0%, transparent 50%),
url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%236366f1' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
}
::selection {
background: rgba(99, 102, 241, 0.3);
text-shadow: 0 0 8px rgba(99, 102, 241, 0.4);
}
input:focus, textarea:focus {
outline: none;
}
/* Focus-visible ring for keyboard navigation accessibility */
:focus-visible {
outline: 2px solid rgba(99, 102, 241, 0.6);
outline-offset: 2px;
}
button:focus:not(:focus-visible),
a:focus:not(:focus-visible) {
outline: none;
}
/* emoji-mart dark theme overrides */
em-emoji-picker {
--em-rgb-background: 23, 33, 43; /* Telegram surface color */
--em-rgb-input: 30, 44, 58;
--em-rgb-color: 240, 240, 240;
--border-radius: 0 0 16px 16px;
border: none !important;
/* Overrides for category navigation to remove blue line and add soft bg */
--category-icon-active-border-color: transparent !important;
--category-icon-active-color: #5288c1 !important;
}
em-emoji-picker::part(category-icons) {
padding-top: 4px;
}
em-emoji-picker::part(category-icon) {
border-radius: 6px;
transition: background 0.2s ease;
}
em-emoji-picker::part(category-icon):hover {
background-color: rgba(255, 255, 255, 0.05);
}
em-emoji-picker::part(category-icon-active) {
background-color: rgba(82, 136, 193, 0.15); /* Accent light bg */
}
/* Highlight for quoted messages */
@keyframes highlight-glow {
0% { box-shadow: 0 0 0 3px rgba(82, 136, 193, 0.8), 0 0 20px rgba(82, 136, 193, 0.6); }
20% { box-shadow: 0 0 0 4px rgba(82, 136, 193, 1), 0 0 30px rgba(82, 136, 193, 0.8); }
100% { box-shadow: 0 0 0 0 rgba(82, 136, 193, 0), 0 0 0 rgba(82, 136, 193, 0); }
}
.highlight-message {
animation: highlight-glow 3s ease-out forwards;
}
/* Autofill fix - prevent browser blue background on dark theme */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
-webkit-box-shadow: 0 0 0 1000px #0b0b0b inset !important;
-webkit-text-fill-color: #f5f5fa !important;
transition: background-color 5000s ease-in-out 0s;
}

View File

@@ -800,7 +800,7 @@ export default function AdminPage() {
}
};
const fetchUserDetails = async (id: string) => {
const fetchUserStats = async (id: string) => {
try {
const res = await httpClient.request<AppUser>(`/admin/users/${id}`);
setSelectedUser(res);
@@ -838,73 +838,112 @@ export default function AdminPage() {
if (!authenticated) {
return (
<div className="min-h-screen flex items-center justify-center bg-black/90 text-white font-sans">
<form onSubmit={handleLogin} className="bg-surface border border-accent/20 p-8 rounded-2xl w-full max-w-sm">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold text-accent">{t.loginTitle}</h1>
<div className="flex gap-2">
<button type="button" onClick={() => setLang('en')} className={`text-sm ${lang === 'en' ? 'text-accent font-bold' : 'text-gray-500'}`}>EN</button>
<button type="button" onClick={() => setLang('ru')} className={`text-sm ${lang === 'ru' ? 'text-accent font-bold' : 'text-gray-500'}`}>RU</button>
<div className="min-h-screen flex items-center justify-center bg-surface-container-lowest text-on-surface font-sans">
<form onSubmit={handleLogin} className="knot-card p-12 w-full max-w-sm flex flex-col items-center gap-8 border border-outline-variant/10">
<div className="w-16 h-16 rounded-3xl bg-primary/10 border border-primary/20 flex items-center justify-center text-primary">
<span className="material-symbols-outlined text-4xl">hub</span>
</div>
<div className="text-center">
<h1 className="text-3xl font-black font-headline tracking-tighter uppercase mb-2">{t.loginTitle}</h1>
<p className="text-[10px] font-black text-on-surface-variant/40 uppercase tracking-[0.3em]">Administrator</p>
</div>
<div className="w-full flex flex-col gap-5">
<div className="knot-input-group">
<label className="block text-[11px] font-black uppercase tracking-[0.2em] text-on-surface-variant/40 mb-3 ml-1">
{lang === 'ru' ? 'ИМЯ ПОЛЬЗОВАТЕЛЯ' : 'USERNAME'}
</label>
<div className="relative">
<span className="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 text-[20px]">person</span>
<input
type="text"
placeholder="username"
className="w-full pl-12 pr-4 py-4 rounded-2xl bg-surface-container-highest/60 text-on-surface placeholder-on-surface-variant/20 border-none focus:ring-2 focus:ring-primary/20 transition-all outline-none text-[15px]"
value={creds.user}
onChange={(e) => setCreds({ ...creds, user: e.target.value })}
autoComplete="off"
/>
</div>
</div>
<div className="knot-input-group">
<label className="block text-[11px] font-black uppercase tracking-[0.2em] text-on-surface-variant/40 mb-3 ml-1">
{lang === 'ru' ? 'ПАРОЛЬ' : 'PASSWORD'}
</label>
<div className="relative">
<span className="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 text-[20px]">lock</span>
<input
type="password"
placeholder="••••••••"
className="w-full pl-12 pr-4 py-4 rounded-2xl bg-surface-container-highest/60 text-on-surface placeholder-on-surface-variant/20 border-none focus:ring-2 focus:ring-primary/20 transition-all outline-none text-[15px]"
value={creds.pass}
onChange={(e) => setCreds({ ...creds, pass: e.target.value })}
autoComplete="current-password"
/>
</div>
</div>
</div>
<input
className="w-full bg-black/50 border border-white/10 rounded-xl px-4 py-3 mb-4 outline-none focus:border-accent"
placeholder={t.username}
value={creds.user}
onChange={(e) => setCreds({ ...creds, user: e.target.value })}
/>
<input
className="w-full bg-black/50 border border-white/10 rounded-xl px-4 py-3 mb-6 outline-none focus:border-accent"
type="password"
placeholder={t.password}
value={creds.pass}
onChange={(e) => setCreds({ ...creds, pass: e.target.value })}
/>
<button className="w-full bg-accent hover:bg-accentLight text-black font-semibold py-3 rounded-xl transition-colors">
{t.loginBtn}
<button className="knot-button-primary w-full py-4.5 flex items-center justify-center gap-3 slide-on-ice mt-4">
<span className="font-black uppercase tracking-widest text-[14px]">{t.loginBtn}</span>
<span className="material-symbols-outlined text-[20px]">arrow_forward</span>
</button>
<div className="flex gap-4 p-1.5 bg-surface-container-highest/20 rounded-2xl w-full">
<button type="button" onClick={() => setLang('ru')} className={`flex-1 py-3 text-[11px] font-black rounded-xl transition-all ${lang === 'ru' ? 'bg-primary text-on-primary' : 'text-on-surface-variant/40 hover:text-on-surface'}`}>RU</button>
<button type="button" onClick={() => setLang('en')} className={`flex-1 py-3 text-[11px] font-black rounded-xl transition-all ${lang === 'en' ? 'bg-primary text-on-primary' : 'text-on-surface-variant/40 hover:text-on-surface'}`}>EN</button>
</div>
</form>
</div>
);
}
return (
<div className="h-[100dvh] w-full bg-black text-white font-sans flex overflow-hidden">
<div className="h-full flex overflow-hidden bg-surface-container-lowest text-on-surface font-sans selection:bg-primary/30">
{/* Sidebar */}
<aside className="w-72 border-r border-white/10 bg-surface/50 p-6 flex flex-col gap-2 shrink-0">
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-bold text-accent flex items-center gap-2">
<Shield className="w-6 h-6" /> Knot Control
</h2>
<aside className="w-[280px] flex-shrink-0 bg-surface-container-low border-r border-outline-variant/10 flex flex-col slide-on-ice z-20">
<div className="p-8 pb-4 flex items-center gap-4">
<div className="w-10 h-10 rounded-2xl bg-primary/10 border border-primary/20 flex items-center justify-center">
<span className="material-symbols-outlined text-primary text-[24px]">hub</span>
</div>
<h2 className="text-xl font-black font-headline tracking-tighter">Knot</h2>
</div>
<NavButton icon={<Activity />} label={t.dashboard} active={activeTab === 'dashboard'} onClick={() => setActiveTab('dashboard')} />
<NavButton icon={<Users />} label={t.userAnalytics} active={activeTab === 'users'} onClick={() => { setActiveTab('users'); setSelectedUser(null); }} />
<nav className="flex-1 overflow-y-auto px-4 py-6 space-y-1 custom-scrollbar">
<div className="px-4 mb-4 text-[10px] font-black uppercase tracking-[0.2em] text-on-surface-variant/30">
{t.dashboard}
</div>
<NavButton icon="dashboard" label={t.dashboard} active={activeTab === 'dashboard'} onClick={() => setActiveTab('dashboard')} />
<NavButton icon="group" label={t.userAnalytics} active={activeTab === 'users'} onClick={() => { setActiveTab('users'); setSelectedUser(null); }} />
<div className="mt-6 mb-2 text-[10px] uppercase tracking-widest text-gray-500 font-bold px-4">{t.settingsGroup}</div>
<NavButton icon={<Settings />} label={t.system} active={activeTab === 'settings' && activeSettingsTab === 'system'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('system'); }} />
<NavButton icon={<Activity />} label={t.stories} active={activeTab === 'settings' && activeSettingsTab === 'stories'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('stories'); }} />
<NavButton icon={<Users />} label={t.chats} active={activeTab === 'settings' && activeSettingsTab === 'chats'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('chats'); }} />
<NavButton icon={<Activity />} label={t.messages} active={activeTab === 'settings' && activeSettingsTab === 'messages'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('messages'); }} />
<NavButton icon={<Globe />} label={t.webRtc} active={activeTab === 'settings' && activeSettingsTab === 'webRtc'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('webRtc'); }} />
<NavButton icon={<Database />} label={t.klipy} active={activeTab === 'settings' && activeSettingsTab === 'klipy'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('klipy'); }} />
<NavButton icon={<Download />} label={t.import} active={activeTab === 'settings' && activeSettingsTab === 'import'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('import'); }} />
<NavButton icon={<Globe />} label={t.federation} active={activeTab === 'settings' && activeSettingsTab === 'federation'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('federation'); }} />
<div className="px-4 mt-8 mb-4 text-[10px] font-black uppercase tracking-[0.2em] text-on-surface-variant/30">
{t.settingsGroup}
</div>
<NavButton icon="settings" label={t.system} active={activeTab === 'settings' && activeSettingsTab === 'system'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('system'); }} />
<NavButton icon="auto_stories" label={t.stories} active={activeTab === 'settings' && activeSettingsTab === 'stories'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('stories'); }} />
<NavButton icon="chat_bubble" label={t.chats} active={activeTab === 'settings' && activeSettingsTab === 'chats'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('chats'); }} />
<NavButton icon="message" label={t.messages} active={activeTab === 'settings' && activeSettingsTab === 'messages'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('messages'); }} />
<NavButton icon="call" label={t.webRtc} active={activeTab === 'settings' && activeSettingsTab === 'webRtc'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('webRtc'); }} />
<NavButton icon="gif_box" label={t.klipy} active={activeTab === 'settings' && activeSettingsTab === 'klipy'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('klipy'); }} />
<NavButton icon="download" label={t.importTitle} active={activeTab === 'settings' && activeSettingsTab === 'import'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('import'); }} />
<NavButton icon="language" label={t.federation} active={activeTab === 'settings' && activeSettingsTab === 'federation'} onClick={() => { setActiveTab('settings'); setActiveSettingsTab('federation'); }} />
</nav>
<div className="mt-auto pt-6 border-t border-white/5">
<div className="p-4 border-t border-outline-variant/10">
<button
onClick={() => setLang(lang === 'ru' ? 'en' : 'ru')}
className="w-full flex items-center justify-between p-3 rounded-xl bg-white/5 hover:bg-white/10 transition-all border border-white/5 group"
className="w-full flex items-center justify-between p-3 rounded-2xl bg-surface-container-highest/10 hover:bg-surface-container-highest/20 transition-all border border-outline-variant/5 group slide-on-ice"
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center text-xs font-bold text-accent">
<div className="w-8 h-8 rounded-xl bg-primary/10 flex items-center justify-center text-[10px] font-black text-primary">
{lang.toUpperCase()}
</div>
<span className="text-sm font-medium text-gray-300 group-hover:text-white transition-colors">
<span className="text-[13px] font-bold text-on-surface-variant group-hover:text-on-surface transition-colors">
{lang === 'ru' ? 'Русский' : 'English'}
</span>
</div>
<Globe className="w-4 h-4 text-gray-600 group-hover:text-accent transition-colors" />
<span className="material-symbols-outlined text-on-surface-variant/20 group-hover:text-primary transition-colors text-[18px]">language</span>
</button>
</div>
</aside>
@@ -915,16 +954,16 @@ export default function AdminPage() {
{activeTab === 'dashboard' && stats && (
<motion.div key="dashboard" initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} className="flex flex-col gap-6 max-w-5xl">
<h1 className="text-3xl font-bold">{t.dashboard}</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="bg-surface border border-white/10 p-8 rounded-3xl lg:col-span-2 relative overflow-hidden group">
<div className="flex justify-between items-end mb-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<div className="knot-card p-10 lg:col-span-2 relative overflow-hidden group border border-outline-variant/10">
<div className="flex justify-between items-end mb-8">
<div>
<h3 className="text-gray-400 text-[10px] mb-1 flex items-center gap-2 uppercase tracking-[0.2em] font-black">
<Database className="w-4 h-4 text-accent" /> {t.storageUsed}
<h3 className="text-on-surface-variant/40 text-[10px] mb-2 flex items-center gap-3 uppercase tracking-[0.3em] font-black">
<span className="material-symbols-outlined text-primary text-sm">database</span> {t.storageUsed}
</h3>
<div className="text-4xl font-black flex items-baseline gap-2">
<div className="text-5xl font-black font-headline tracking-tighter flex items-baseline gap-3">
{formatBytes(stats.storageUsedBytes)}
<span className="text-sm font-medium text-gray-500">/ {formatBytes(stats.storageLimitBytes)}</span>
<span className="text-sm font-medium text-on-surface-variant/20 tracking-normal">/ {formatBytes(stats.storageLimitBytes)}</span>
</div>
</div>
<div className="text-right">
@@ -989,31 +1028,32 @@ export default function AdminPage() {
{String(t[activeSettingsTab as keyof typeof t] || activeSettingsTab)}
</h1>
<div className="ml-auto flex gap-2">
<button onClick={fetchSettings} className="p-2.5 hover:bg-white/5 rounded-xl text-gray-400 transition-all active:scale-95">
<Activity className="w-5 h-5" />
<button onClick={fetchSettings} className="p-3 hover:bg-surface-container-highest/10 rounded-2xl text-on-surface-variant/60 hover:text-on-surface transition-all active:scale-95 slide-on-ice">
<span className="material-symbols-outlined text-[20px] font-black">sync</span>
</button>
<button onClick={saveSettings} className="bg-accent hover:bg-accentLight text-black font-bold px-8 py-2.5 rounded-xl transition-all shadow-lg shadow-accent/20 active:scale-95">
{t.saveChanges}
<button onClick={saveSettings} className="knot-button-primary px-10 py-3 rounded-2xl flex items-center gap-3 slide-on-ice">
<span className="text-[11px] font-black uppercase tracking-widest">{t.saveChanges}</span>
<span className="material-symbols-outlined text-[18px]">save</span>
</button>
</div>
</div>
{/* System Settings */}
{activeSettingsTab === 'system' && (
<div className="bg-surface border border-white/10 rounded-2xl p-8 flex flex-col gap-8 shadow-xl shadow-black/40">
<div className="knot-card p-10 flex flex-col gap-8 border border-outline-variant/10">
<h3 className="text-xl font-semibold opacity-30 uppercase tracking-widest text-sm">{t.system}</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-12 gap-y-8">
<label className="flex flex-col gap-1.5 group">
<span className="text-xs font-bold text-gray-500 uppercase ml-1 group-focus-within:text-accent transition-colors">{t.domainUrl}</span>
<input className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent/50 focus:bg-black/60 transition-all font-mono text-sm" value={config.system.domainUrl} onChange={e => setConfig({ ...config, system: { ...config.system, domainUrl: e.target.value } })} />
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1 group-focus-within:text-accent transition-colors">{t.domainUrl}</span>
<input className="knot-input-group" value={config.system.domainUrl} onChange={e => setConfig({ ...config, system: { ...config.system, domainUrl: e.target.value } })} />
<Hint>{t.hints.domainUrl}</Hint>
</label>
<label className="flex flex-col gap-1.5 group">
<span className="text-xs font-bold text-gray-500 uppercase ml-1 group-focus-within:text-accent transition-colors">{t.adminPublicPath}</span>
<input className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent/50 focus:bg-black/60 transition-all font-mono text-sm" value={config.system.adminRoute} onChange={e => setConfig({ ...config, system: { ...config.system, adminRoute: e.target.value } })} placeholder="/admin" />
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1 group-focus-within:text-accent transition-colors">{t.adminPublicPath}</span>
<input className="knot-input-group" value={config.system.adminRoute} onChange={e => setConfig({ ...config, system: { ...config.system, adminRoute: e.target.value } })} placeholder="/admin" />
<Hint>{t.hints.adminRoute}</Hint>
</label>
<div className="flex flex-col gap-2 bg-white/[0.02] p-4 rounded-2xl border border-white/5">
<div className="bg-surface-container-highest/20 p-6 rounded-3xl border border-outline-variant/10 flex flex-col gap-4">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-200 font-semibold">{t.enableReg}</span>
<Toggle checked={config.system.enableRegistration} onChange={v => setConfig({ ...config, system: { ...config.system, enableRegistration: v } })} />
@@ -1021,10 +1061,10 @@ export default function AdminPage() {
<Hint>{t.hints.enableRegistration}</Hint>
</div>
<div className="flex flex-col gap-1.5 group relative">
<span className="text-xs font-bold text-gray-500 uppercase ml-1 group-focus-within:text-accent transition-colors">{t.timezone}</span>
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1 group-focus-within:text-primary transition-colors">{t.timezone}</span>
<div className="relative">
<input
className="w-full bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent/50 focus:bg-black/60 transition-all font-mono text-xs pr-10"
className="w-full knot-input-group transition-all font-mono text-xs pr-10"
value={config.system.serverTimezone}
onFocus={() => setShowTzDropdown(true)}
onChange={e => {
@@ -1033,7 +1073,7 @@ export default function AdminPage() {
}}
placeholder="UTC"
/>
<Globe className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
<span className="material-symbols-outlined absolute right-4 top-1/2 -translate-y-1/2 text-on-surface-variant/30">public</span>
<AnimatePresence>
{showTzDropdown && (
@@ -1041,7 +1081,7 @@ export default function AdminPage() {
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="absolute left-0 right-0 top-full mt-2 bg-surface border border-white/10 rounded-xl shadow-2xl z-[100] max-h-60 overflow-y-auto overflow-x-hidden p-1"
className="absolute left-0 right-0 top-full mt-2 bg-surface-container-high border border-outline-variant/10 rounded-3xl shadow-[0_16px_48px_rgba(0,0,0,0.6)] z-[100] max-h-64 overflow-y-auto custom-scrollbar p-2"
>
{timezones
.filter(tz => tz.displayName.toLowerCase().includes(tzSearch.toLowerCase()) || tz.id.toLowerCase().includes(tzSearch.toLowerCase()))
@@ -1053,10 +1093,10 @@ export default function AdminPage() {
setTzSearch('');
setShowTzDropdown(false);
}}
className="w-full text-left p-3 hover:bg-white/5 rounded-lg transition-colors flex flex-col group/tz"
className="w-full text-left p-4 hover:bg-primary/10 rounded-2xl transition-all flex flex-col gap-1 group"
>
<div className="flex justify-between items-center w-full">
<span className="text-xs font-bold text-white group-hover/tz:text-accent truncate">{tz.displayName}</span>
<span className="text-[13px] font-bold text-on-surface group-hover:text-primary transition-colors">{tz.displayName}</span>
<span className="text-[10px] font-mono text-gray-500 group-hover/tz:text-accent/50 ml-2 shrink-0">{tz.offsetString}</span>
</div>
<span className="text-[10px] text-gray-600 truncate">{tz.id}</span>
@@ -1076,7 +1116,7 @@ export default function AdminPage() {
{/* Stories Settings */}
{activeSettingsTab === 'stories' && (
<div className="bg-surface border border-white/10 rounded-2xl p-8 flex flex-col gap-8">
<div className="knot-card p-10 flex flex-col gap-8 border border-outline-variant/10">
<div className="flex items-center justify-between">
<h3 className="text-xl font-semibold opacity-30 uppercase tracking-widest text-sm">{t.stories}</h3>
<Toggle checked={config.stories.enabled} onChange={v => setConfig({ ...config, stories: { ...config.stories, enabled: v } })} />
@@ -1084,16 +1124,16 @@ export default function AdminPage() {
{config.stories.enabled && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-12 gap-y-10 pt-4 border-t border-white/10">
<label className="flex flex-col gap-1.5 group">
<span className="text-xs font-bold text-gray-500 uppercase ml-1">{t.maxStories}</span>
<input type="number" className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent/50 transition-all" value={config.stories.maxStoriesPerPeriod} onChange={e => setConfig({ ...config, stories: { ...config.stories, maxStoriesPerPeriod: parseInt(e.target.value) || 0 } })} />
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1">{t.maxStories}</span>
<input type="number" className="knot-input-group" value={config.stories.maxStoriesPerPeriod} onChange={e => setConfig({ ...config, stories: { ...config.stories, maxStoriesPerPeriod: parseInt(e.target.value) || 0 } })} />
<Hint>{t.hints.maxStories}</Hint>
</label>
<label className="flex flex-col gap-1.5 group">
<span className="text-xs font-bold text-gray-500 uppercase ml-1">{t.storyLifetime}</span>
<input type="number" className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent/50 transition-all" value={config.stories.storyLifetimeHours} onChange={e => setConfig({ ...config, stories: { ...config.stories, storyLifetimeHours: parseInt(e.target.value) || 0 } })} />
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1">{t.storyLifetime}</span>
<input type="number" className="knot-input-group" value={config.stories.storyLifetimeHours} onChange={e => setConfig({ ...config, stories: { ...config.stories, storyLifetimeHours: parseInt(e.target.value) || 0 } })} />
<Hint>{t.hints.storyLifetime}</Hint>
</label>
<div className="flex flex-col gap-2 bg-white/[0.02] p-4 rounded-2xl border border-white/5">
<div className="bg-surface-container-highest/20 p-6 rounded-3xl border border-outline-variant/10 flex flex-col gap-4">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-200 font-semibold">{t.textStories}</span>
<Toggle checked={config.stories.textStoriesEnabled} onChange={v => setConfig({ ...config, stories: { ...config.stories, textStoriesEnabled: v } })} />
@@ -1101,13 +1141,13 @@ export default function AdminPage() {
<Hint>{t.hints.textStoryDuration}</Hint>
</div>
<label className="flex flex-col gap-1.5 group">
<span className="text-xs font-bold text-gray-500 uppercase ml-1">{t.mediaStoryDuration}</span>
<input type="number" className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent/50 transition-all" value={config.stories.mediaStoryMaxDurationSeconds} onChange={e => setConfig({ ...config, stories: { ...config.stories, mediaStoryMaxDurationSeconds: parseInt(e.target.value) || 0 } })} />
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1">{t.mediaStoryDuration}</span>
<input type="number" className="knot-input-group" value={config.stories.mediaStoryMaxDurationSeconds} onChange={e => setConfig({ ...config, stories: { ...config.stories, mediaStoryMaxDurationSeconds: parseInt(e.target.value) || 0 } })} />
<Hint>{t.hints.mediaStoryDuration}</Hint>
</label>
<label className="flex flex-col gap-1.5 group">
<span className="text-xs font-bold text-gray-500 uppercase ml-1">{t.maxMediaSize}</span>
<input type="number" className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent/50 transition-all" value={bytesToMb(config.stories.maxMediaSizeBytes)} onChange={e => setConfig({ ...config, stories: { ...config.stories, maxMediaSizeBytes: mbToBytes(parseInt(e.target.value) || 0) } })} />
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1">{t.maxMediaSize}</span>
<input type="number" className="knot-input-group" value={bytesToMb(config.stories.maxMediaSizeBytes)} onChange={e => setConfig({ ...config, stories: { ...config.stories, maxMediaSizeBytes: mbToBytes(parseInt(e.target.value) || 0) } })} />
<Hint>{t.hints.maxMediaSize}</Hint>
</label>
</div>
@@ -1117,11 +1157,11 @@ export default function AdminPage() {
{/* Chats Settings */}
{activeSettingsTab === 'chats' && (
<div className="bg-surface border border-white/10 rounded-2xl p-8 flex flex-col gap-8">
<div className="knot-card p-10 flex flex-col gap-8 border border-outline-variant/10">
<h3 className="text-xl font-semibold opacity-30 uppercase tracking-widest text-sm">{t.chats}</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
<div className="flex flex-col gap-8">
<div className="flex flex-col gap-2 bg-white/[0.02] p-4 rounded-2xl border border-white/5">
<div className="bg-surface-container-highest/20 p-6 rounded-3xl border border-outline-variant/10 flex flex-col gap-4">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-200 font-semibold">{t.supportGroups}</span>
<Toggle checked={config.chats.supportGroups} onChange={v => setConfig({ ...config, chats: { ...config.chats, supportGroups: v } })} />
@@ -1129,27 +1169,27 @@ export default function AdminPage() {
<Hint>{t.hints.supportGroups}</Hint>
</div>
<label className="flex flex-col gap-1.5 group">
<span className="text-xs font-bold text-gray-500 uppercase ml-1">{t.maxGroupMembers}</span>
<input type="number" className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent/50 transition-all" value={config.chats.maxGroupParticipants} onChange={e => setConfig({ ...config, chats: { ...config.chats, maxGroupParticipants: parseInt(e.target.value) || 0 } })} />
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1">{t.maxGroupMembers}</span>
<input type="number" className="knot-input-group" value={config.chats.maxGroupParticipants} onChange={e => setConfig({ ...config, chats: { ...config.chats, maxGroupParticipants: parseInt(e.target.value) || 0 } })} />
<Hint>{t.hints.maxGroupMembers}</Hint>
</label>
</div>
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-2 bg-white/[0.02] p-4 rounded-2xl border border-white/5">
<div className="bg-surface-container-highest/20 p-6 rounded-3xl border border-outline-variant/10 flex flex-col gap-4">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-200 font-semibold">{t.autoClean}</span>
<Toggle checked={config.chats.enableAutoClean} onChange={v => setConfig({ ...config, chats: { ...config.chats, enableAutoClean: v } })} />
</div>
<Hint>{t.hints.autoClean}</Hint>
</div>
<div className="flex flex-col gap-2 bg-white/[0.02] p-4 rounded-2xl border border-white/5">
<div className="bg-surface-container-highest/20 p-6 rounded-3xl border border-outline-variant/10 flex flex-col gap-4">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-200 font-semibold">{t.chatToGroup}</span>
<Toggle checked={config.chats.allowChatToGroupConversion} onChange={v => setConfig({ ...config, chats: { ...config.chats, allowChatToGroupConversion: v } })} />
</div>
<Hint>{t.hints.chatToGroup}</Hint>
</div>
<div className="flex flex-col gap-2 bg-white/[0.02] p-4 rounded-2xl border border-white/5">
<div className="bg-surface-container-highest/20 p-6 rounded-3xl border border-outline-variant/10 flex flex-col gap-4">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-200 font-semibold">{t.enableFolders}</span>
<Toggle checked={config.chats.enableFolders} onChange={v => setConfig({ ...config, chats: { ...config.chats, enableFolders: v } })} />
@@ -1163,23 +1203,23 @@ export default function AdminPage() {
{/* Messages Settings */}
{activeSettingsTab === 'messages' && (
<div className="bg-surface border border-white/10 rounded-2xl p-8 flex flex-col gap-8">
<div className="knot-card p-10 flex flex-col gap-8 border border-outline-variant/10">
<h3 className="text-xl font-semibold opacity-30 uppercase tracking-widest text-sm">{t.messages}</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-16 gap-y-10">
<div className="flex flex-col gap-8">
<label className="flex flex-col gap-1.5 group">
<span className="text-xs font-bold text-gray-500 uppercase ml-1">{t.dailyLimit}</span>
<input type="number" className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent/50 transition-all font-mono" value={config.messages.dailyMessageLimitPerUser} onChange={e => setConfig({ ...config, messages: { ...config.messages, dailyMessageLimitPerUser: parseInt(e.target.value) || 0 } })} />
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1">{t.dailyLimit}</span>
<input type="number" className="knot-input-group font-mono" value={config.messages.dailyMessageLimitPerUser} onChange={e => setConfig({ ...config, messages: { ...config.messages, dailyMessageLimitPerUser: parseInt(e.target.value) || 0 } })} />
<Hint>{t.hints.dailyLimit}</Hint>
</label>
<label className="flex flex-col gap-1.5 group">
<span className="text-xs font-bold text-gray-500 uppercase ml-1">{t.chatLimit}</span>
<input type="number" className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent/50 transition-all font-mono" value={config.messages.chatMessageLimit} onChange={e => setConfig({ ...config, messages: { ...config.messages, chatMessageLimit: parseInt(e.target.value) || 0 } })} />
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1">{t.chatLimit}</span>
<input type="number" className="knot-input-group font-mono" value={config.messages.chatMessageLimit} onChange={e => setConfig({ ...config, messages: { ...config.messages, chatMessageLimit: parseInt(e.target.value) || 0 } })} />
<Hint>{t.hints.chatLimit}</Hint>
</label>
<label className="flex flex-col gap-1.5 group">
<span className="text-xs font-bold text-gray-500 uppercase ml-1">{t.maxFileSize}</span>
<input type="number" className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent/50 transition-all font-mono" value={bytesToMb(config.messages.maxFileSize)} onChange={e => setConfig({ ...config, messages: { ...config.messages, maxFileSize: mbToBytes(parseInt(e.target.value) || 0) } })} />
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1">{t.maxFileSize}</span>
<input type="number" className="knot-input-group font-mono" value={bytesToMb(config.messages.maxFileSize)} onChange={e => setConfig({ ...config, messages: { ...config.messages, maxFileSize: mbToBytes(parseInt(e.target.value) || 0) } })} />
<Hint>{t.hints.maxFileSize}</Hint>
</label>
</div>
@@ -1213,7 +1253,7 @@ export default function AdminPage() {
{/* WebRTC Settings */}
{activeSettingsTab === 'webRtc' && (
<div className="bg-surface border border-white/10 rounded-2xl p-8 flex flex-col gap-8 shadow-xl shadow-accent/5">
<div className="knot-card p-10 flex flex-col gap-8 border border-outline-variant/10 shadow-xl shadow-accent/5">
<div className="flex items-center justify-between">
<h3 className="text-xl font-bold text-accent uppercase tracking-tighter flex items-center gap-2">
<Globe className="w-5 h-5" /> {t.webRtc}
@@ -1250,22 +1290,22 @@ export default function AdminPage() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<label className="flex flex-col gap-1.5 group">
<span className="text-[10px] font-black uppercase text-gray-500 ml-1 group-focus-within:text-accent transition-colors">{t.turnHost}</span>
<input className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent font-mono text-xs" value={config.webRtc.turnHost} onChange={e => setConfig({ ...config, webRtc: { ...config.webRtc, turnHost: e.target.value } })} placeholder="turn.example.com" />
<input className="knot-input-group" value={config.webRtc.turnHost} onChange={e => setConfig({ ...config, webRtc: { ...config.webRtc, turnHost: e.target.value } })} placeholder="turn.example.com" />
<Hint>{t.hints.turnHost}</Hint>
</label>
<label className="flex flex-col gap-1.5 group">
<span className="text-[10px] font-black uppercase text-gray-500 ml-1 group-focus-within:text-accent transition-colors">{t.turnPort}</span>
<input type="number" className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent font-mono text-xs" value={config.webRtc.turnPort} onChange={e => setConfig({ ...config, webRtc: { ...config.webRtc, turnPort: parseInt(e.target.value) || 0 } })} />
<input type="number" className="knot-input-group" value={config.webRtc.turnPort} onChange={e => setConfig({ ...config, webRtc: { ...config.webRtc, turnPort: parseInt(e.target.value) || 0 } })} />
<Hint>{t.hints.turnPort}</Hint>
</label>
<label className="flex flex-col gap-1.5 group">
<span className="text-[10px] font-black uppercase text-gray-500 ml-1 group-focus-within:text-accent transition-colors">{t.turnUser}</span>
<input className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent font-mono text-xs" value={config.webRtc.turnUser} onChange={e => setConfig({ ...config, webRtc: { ...config.webRtc, turnUser: e.target.value } })} />
<input className="knot-input-group" value={config.webRtc.turnUser} onChange={e => setConfig({ ...config, webRtc: { ...config.webRtc, turnUser: e.target.value } })} />
<Hint>{t.hints.turnUser}</Hint>
</label>
<label className="flex flex-col gap-1.5 group">
<span className="text-[10px] font-black uppercase text-gray-500 ml-1 group-focus-within:text-accent transition-colors">{t.turnSecret}</span>
<input type="password" name="turnSecret" className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-accent font-mono text-xs" value={config.webRtc.turnSecret} onChange={e => setConfig({ ...config, webRtc: { ...config.webRtc, turnSecret: e.target.value } })} />
<input type="password" name="turnSecret" className="knot-input-group" value={config.webRtc.turnSecret} onChange={e => setConfig({ ...config, webRtc: { ...config.webRtc, turnSecret: e.target.value } })} />
<Hint>{t.hints.turnSecret}</Hint>
</label>
</div>
@@ -1280,7 +1320,7 @@ export default function AdminPage() {
{/* Klipy Settings */}
{activeSettingsTab === 'klipy' && (
<div className="bg-surface border border-white/10 rounded-2xl p-8 flex flex-col gap-8 shadow-xl shadow-purple-500/5">
<div className="knot-card p-10 flex flex-col gap-8 border border-outline-variant/10 shadow-xl shadow-purple-500/5">
<div className="flex items-center justify-between">
<h3 className="text-xl font-bold flex items-center gap-3">
<div className="w-6 h-6 bg-purple-500 rounded-md" /> {t.klipy}
@@ -1292,18 +1332,18 @@ export default function AdminPage() {
<div className="flex flex-col gap-8 pt-4 border-t border-white/10">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<label className="flex flex-col gap-1.5 group">
<span className="text-xs font-bold text-gray-500 uppercase ml-1 group-focus-within:text-purple-400 transition-colors">{t.appName}</span>
<input className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-purple-500/50 transition-all font-mono text-sm" value={config.klipy.appName} onChange={e => setConfig({ ...config, klipy: { ...config.klipy, appName: e.target.value } })} />
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1 group-focus-within:text-purple-400 transition-colors">{t.appName}</span>
<input className="knot-input-group" value={config.klipy.appName} onChange={e => setConfig({ ...config, klipy: { ...config.klipy, appName: e.target.value } })} />
<Hint>{t.hints.appName}</Hint>
</label>
<label className="flex flex-col gap-1.5 group">
<span className="text-xs font-bold text-gray-500 uppercase ml-1 group-focus-within:text-purple-400 transition-colors">{t.apiKey}</span>
<input className="bg-black/40 border border-white/10 rounded-xl px-4 py-3.5 outline-none text-white focus:border-purple-500/50 transition-all font-mono text-sm" value={config.klipy.apiKey} onChange={e => setConfig({ ...config, klipy: { ...config.klipy, apiKey: e.target.value } })} type="password" />
<span className="text-[11px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1 group-focus-within:text-purple-400 transition-colors">{t.apiKey}</span>
<input className="knot-input-group" value={config.klipy.apiKey} onChange={e => setConfig({ ...config, klipy: { ...config.klipy, apiKey: e.target.value } })} type="password" />
<Hint>{t.hints.apiKey}</Hint>
</label>
</div>
<button onClick={handleTestKlipy} disabled={isTestingKlipy} className="bg-purple-500/10 hover:bg-purple-500/20 text-purple-400 font-bold px-8 py-3.5 rounded-xl flex items-center justify-center gap-3 transition-all w-full md:w-max">
{isTestingKlipy ? <Loader2 className="w-5 h-5 animate-spin" /> : <Activity className="w-5 h-5" />}
<button onClick={handleTestKlipy} disabled={isTestingKlipy} className="bg-primary/10 hover:bg-primary/20 text-primary font-black px-8 py-3.5 rounded-2xl flex items-center justify-center gap-3 transition-all w-full md:w-max uppercase text-[10px] tracking-widest">
{isTestingKlipy ? <span className="material-symbols-outlined animate-spin">refresh</span> : <span className="material-symbols-outlined">network_check</span>}
{t.testConnection}
</button>
</div>
@@ -1313,21 +1353,21 @@ export default function AdminPage() {
{/* Import Settings */}
{activeSettingsTab === 'import' && (
<div className="bg-surface border border-white/10 rounded-2xl p-8 flex flex-col gap-8 shadow-xl shadow-blue-500/5">
<div className="knot-card p-10 flex flex-col gap-8 border border-outline-variant/10 shadow-xl shadow-blue-500/5">
<div className="flex items-center justify-between">
<h3 className="text-xl font-bold flex items-center gap-3">
<Download className="w-6 h-6 text-blue-400" /> {t.importTitle}
<span className="material-symbols-outlined text-primary text-2xl">download</span> {t.importTitle}
</h3>
</div>
<Hint>{t.hints.import}</Hint>
<div className="flex flex-col gap-6 pt-4 border-t border-white/10">
<div className="bg-white/[0.03] p-5 rounded-2xl border border-white/5 flex flex-col gap-4">
<div className="bg-surface-container-highest/10 p-6 rounded-3xl border border-outline-variant/10 flex flex-col gap-4">
<div className="flex items-center justify-between">
<span className="text-sm font-semibold text-gray-200">{t.telegramImport}</span>
<span className="text-sm font-black uppercase tracking-widest text-on-surface-variant/70">{t.telegramImport}</span>
<Toggle checked={config.import.enableTelegramImport} onChange={v => setConfig({ ...config, import: { ...config.import, enableTelegramImport: v } })} />
</div>
<Hint>{t.hints.import}</Hint>
<p className="text-xs text-gray-500 italic mt-2">
<p className="text-[11px] text-on-surface-variant/30 leading-relaxed italic mt-2">
{t.importDescription}
</p>
</div>
@@ -1337,7 +1377,7 @@ export default function AdminPage() {
{/* Federation Settings */}
{activeSettingsTab === 'federation' && (
<div className="bg-surface border border-white/10 rounded-2xl p-8 flex flex-col gap-8">
<div className="knot-card p-10 flex flex-col gap-8 border border-outline-variant/10">
<div className="flex items-center justify-between">
<h3 className="text-xl font-semibold opacity-30 uppercase tracking-widest text-sm">{t.federation}</h3>
<Toggle checked={config.federation.enabled} onChange={v => setConfig({ ...config, federation: { ...config.federation, enabled: v } })} />
@@ -1346,28 +1386,30 @@ export default function AdminPage() {
{config.federation.enabled && (
<div className="flex flex-col gap-6 pt-4 border-t border-white/10">
<div className="flex justify-between items-center mb-2">
<span className="text-sm text-gray-400 font-medium">{t.allowedNodes}</span>
<span className="text-[10px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1">{t.allowedNodes}</span>
<div className="flex gap-2">
<input type="file" id="jsonIn" className="hidden" accept=".json" onChange={importDomains} />
<label htmlFor="jsonIn" className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-white/5 hover:bg-white/10 transition-colors text-[10px] uppercase font-bold cursor-pointer border border-white/5">
<Upload className="w-3.5 h-3.5" /> {t.importJson}
<label htmlFor="jsonIn" className="flex items-center gap-2 px-4 py-2 rounded-xl bg-surface-container-highest/10 hover:bg-surface-container-highest/20 transition-all text-[10px] uppercase font-black tracking-widest cursor-pointer border border-outline-variant/10">
<span className="material-symbols-outlined text-sm">upload</span> {t.importJson}
</label>
<button onClick={exportDomains} className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-white/5 hover:bg-white/10 transition-colors text-[10px] uppercase font-bold border border-white/5">
<Download className="w-3.5 h-3.5" /> {t.exportJson}
<button onClick={exportDomains} className="flex items-center gap-2 px-4 py-2 rounded-xl bg-surface-container-highest/10 hover:bg-surface-container-highest/20 transition-all text-[10px] uppercase font-black tracking-widest border border-outline-variant/10">
<span className="material-symbols-outlined text-sm">download</span> {t.exportJson}
</button>
</div>
</div>
<div className="flex gap-2">
<input className="flex-1 bg-black/50 border border-white/10 rounded-xl px-4 py-3 outline-none text-white focus:border-accent text-sm font-mono" value={domainInput} onChange={e => setDomainInput(e.target.value)} placeholder="domain.tld" />
<button onClick={addDomain} className="bg-accent/10 hover:bg-accent/20 text-accent px-6 rounded-xl transition-colors font-bold text-sm">{t.addNode}</button>
<div className="knot-input-group flex-1">
<input value={domainInput} onChange={e => setDomainInput(e.target.value)} placeholder="domain.tld" />
</div>
<button onClick={addDomain} className="bg-primary/10 hover:bg-primary/20 text-primary px-8 rounded-2xl font-black text-[11px] uppercase tracking-widest transition-all">{t.addNode}</button>
</div>
<div className="flex flex-col gap-2 max-h-60 overflow-y-auto pr-1">
{config.federation.allowedDomains.map(d => (
<div key={d.domain} className="flex items-center justify-between p-4 rounded-xl bg-black/30 border border-white/5 group hover:border-white/10 transition-all">
<span className="text-sm font-mono text-gray-300">{d.domain}</span>
<button onClick={() => removeDomain(d.domain)} className="text-gray-600 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all p-1"><Trash2 className="w-4 h-4" /></button>
<div key={d.domain} className="flex items-center justify-between p-5 rounded-2xl bg-surface-container-highest/5 border border-outline-variant/5 group hover:border-outline-variant/20 transition-all">
<span className="text-sm font-mono text-on-surface-variant/70">{d.domain}</span>
<button onClick={() => removeDomain(d.domain)} className="text-on-surface-variant/20 hover:text-error opacity-0 group-hover:opacity-100 transition-all p-2"><span className="material-symbols-outlined text-[18px]">delete</span></button>
</div>
))}
{config.federation.allowedDomains.length === 0 && (
@@ -1398,14 +1440,22 @@ export default function AdminPage() {
<div className="flex-1 overflow-y-auto p-2 flex flex-col gap-1">
{users.length === 0 && !isSearching && <div className="text-center py-10 text-gray-500">{t.noUsersFound}</div>}
{users.map(u => (
<button key={u.id} onClick={() => fetchUserDetails(u.id)} className={`flex items-center gap-3 p-3 rounded-xl transition-all text-left group ${selectedUser?.id === u.id ? 'bg-accent/10' : 'hover:bg-white/5'}`}>
<button
key={u.id}
onClick={() => fetchUserStats(u.id)}
className={`flex items-center gap-3 p-4 rounded-2xl transition-all text-left group border shadow-sm ${
selectedUser?.id === u.id
? 'bg-primary/10 border-primary/30 scale-[1.01] shadow-primary/5'
: 'hover:bg-surface-container-highest/10 border-transparent'
}`}
>
<div className="relative">
<div className="w-10 h-10 rounded-full bg-accent/20 flex items-center justify-center text-accent font-bold shrink-0">
<div className="w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center text-primary font-black shrink-0 slide-on-ice border border-primary/10">
{u.avatar ? <img src={u.avatar} className="w-full h-full rounded-full" /> : u.username.charAt(0).toUpperCase()}
</div>
{u.isBanned && (
<div className="absolute -bottom-0.5 -left-0.5 w-4 h-4 bg-surface rounded-full flex items-center justify-center" title={t.blockedTooltip}>
<XCircle className="w-3.5 h-3.5 text-red-500" />
<div className="absolute -bottom-1 -right-1 w-5 h-5 bg-surface-container-lowest rounded-full flex items-center justify-center" title={t.blockedTooltip}>
<span className="material-symbols-outlined text-error text-[14px]">block</span>
</div>
)}
</div>
@@ -1440,12 +1490,12 @@ export default function AdminPage() {
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} className="w-80 bg-surface border border-white/10 rounded-2xl p-6 flex flex-col gap-6 shrink-0 sticky top-0 overflow-y-auto max-h-full">
<div className="flex flex-col items-center gap-4 text-center pb-6 border-b border-white/10">
<div className="relative">
<div className="w-20 h-20 rounded-full bg-accent/20 flex items-center justify-center text-2xl font-bold text-accent">
<div className="w-24 h-24 rounded-full bg-primary/20 flex items-center justify-center text-3xl font-black text-primary slide-on-ice border border-primary/10">
{selectedUser.avatar ? <img src={selectedUser.avatar} className="w-full h-full rounded-full" /> : selectedUser.username.charAt(0).toUpperCase()}
</div>
{selectedUser.isBanned && (
<div className="absolute -bottom-1 -left-1 w-6 h-6 bg-surface rounded-full flex items-center justify-center" title={t.blockedTooltip}>
<XCircle className="w-5 h-5 text-red-500" />
<div className="absolute -bottom-1 -right-1 w-8 h-8 bg-surface-container-lowest rounded-full flex items-center justify-center border border-outline-variant/10" title={t.blockedTooltip}>
<span className="material-symbols-outlined text-error text-[20px]">block</span>
</div>
)}
</div>
@@ -1466,26 +1516,26 @@ export default function AdminPage() {
</div>
</div>
<div className="mt-auto flex flex-col gap-2">
<button onClick={() => handleResetPassword(selectedUser.id)} className="w-full py-2.5 rounded-xl bg-white/5 hover:bg-white/10 text-white font-semibold transition-all border border-white/5 text-sm">
<div className="mt-auto flex flex-col gap-4">
<button onClick={() => handleResetPassword(selectedUser.id)} className="w-full py-4 rounded-2xl bg-surface-container-highest/10 hover:bg-surface-container-highest/20 text-on-surface-variant font-black uppercase text-[10px] tracking-widest transition-all slide-on-ice">
{t.resetPassword}
</button>
{generatedPass && (
<div className="p-3 bg-accent/10 border border-accent/20 rounded-xl flex items-center justify-between">
<span className="font-mono text-accent text-sm">{generatedPass}</span>
<button onClick={() => { navigator.clipboard.writeText(generatedPass); showToast(t.copied); }} className="text-accent underline text-[10px]">{t.copy}</button>
<div className="p-4 bg-primary/10 border border-primary/20 rounded-2xl flex items-center justify-between">
<span className="font-mono text-primary text-sm font-black">{generatedPass}</span>
<button onClick={() => { navigator.clipboard.writeText(generatedPass); showToast(t.copied); }} className="text-primary underline text-[10px] font-black uppercase tracking-widest">{t.copy}</button>
</div>
)}
{selectedUser.isBanned ? (
<button onClick={() => handleUnbanUser(selectedUser.id)} className="w-full py-2.5 rounded-xl bg-green-500/10 hover:bg-green-500/20 text-green-500 font-bold transition-all border border-green-500/20 text-sm mt-2">
<button onClick={() => handleUnbanUser(selectedUser.id)} className="w-full py-4 rounded-2xl bg-primary/10 hover:bg-primary/20 text-primary font-black uppercase text-[10px] tracking-widest transition-all slide-on-ice">
{t.unblockUser}
</button>
) : (
<button onClick={() => handleBanUser(selectedUser.id)} className="w-full py-2.5 rounded-xl bg-red-500/10 hover:bg-red-500/20 text-red-500 font-bold transition-all border border-red-500/20 text-sm mt-2">
<button onClick={() => handleBanUser(selectedUser.id)} className="w-full py-4 rounded-2xl bg-error/10 hover:bg-error/20 text-error font-black uppercase text-[10px] tracking-widest transition-all slide-on-ice">
{t.blockUser}
</button>
)}
<button onClick={() => handleDeleteUser(selectedUser.id)} className="w-full py-2.5 rounded-xl bg-red-500/10 hover:bg-red-500/20 text-red-500 font-bold transition-all border border-red-500/20 text-sm mt-4">
<button onClick={() => handleDeleteUser(selectedUser.id)} className="w-full py-4 rounded-2xl bg-error/5 hover:bg-error/10 text-error/60 hover:text-error font-black uppercase text-[10px] tracking-widest transition-all slide-on-ice">
{t.deleteUser}
</button>
</div>
@@ -1502,27 +1552,33 @@ export default function AdminPage() {
{/* MODALS */}
{showAddUserModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm" onClick={() => setShowAddUserModal(false)}>
<motion.div initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} onClick={e => e.stopPropagation()} className="w-full max-w-sm bg-surface border border-white/10 rounded-2xl p-6 shadow-2xl relative">
<button onClick={() => setShowAddUserModal(false)} className="absolute top-4 right-4 text-gray-500 hover:text-white"><XCircle className="w-5 h-5" /></button>
<h2 className="text-xl font-bold text-white mb-4">{t.addUser}</h2>
<div className="flex flex-col gap-4">
<label className="flex flex-col gap-1 text-xs text-gray-400">
{t.username}
<input className="bg-black/50 border border-white/10 rounded-xl px-4 py-2.5 outline-none text-white focus:border-accent" value={newUser.username} onChange={e => setNewUser({ ...newUser, username: e.target.value.toLowerCase() })} placeholder={t.usernamePlaceholder} />
</label>
<label className="flex flex-col gap-1 text-xs text-gray-400">
{t.displayName}
<input className="bg-black/50 border border-white/10 rounded-xl px-4 py-2.5 outline-none text-white focus:border-accent" value={newUser.displayName} onChange={e => setNewUser({ ...newUser, displayName: e.target.value })} placeholder={t.displayNamePlaceholder} />
</label>
<label className="flex flex-col gap-1 text-xs text-gray-400">
{t.password}
<div className="flex gap-2">
<input className="bg-black/50 border border-white/10 rounded-xl px-4 py-2.5 outline-none text-white focus:border-accent flex-1 font-mono" value={newUser.password} onChange={e => setNewUser({ ...newUser, password: e.target.value })} placeholder={t.passwordPlaceholder} />
<button onClick={() => setNewUser({ ...newUser, password: generatePassword() })} className="bg-white/5 hover:bg-white/10 px-3 rounded-xl text-[10px] transition-colors">{t.generate}</button>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-surface-container-lowest/90 backdrop-blur-xl" onClick={() => setShowAddUserModal(false)}>
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} onClick={e => e.stopPropagation()} className="w-full max-w-sm knot-card p-10 border border-outline-variant/20 relative shadow-2xl">
<button onClick={() => setShowAddUserModal(false)} className="absolute top-6 right-6 text-on-surface-variant/40 hover:text-on-surface"><span className="material-symbols-outlined">close</span></button>
<h2 className="text-2xl font-black font-headline tracking-tighter mb-8">{t.addUser}</h2>
<div className="flex flex-col gap-6">
<label className="flex flex-col gap-2">
<span className="text-[10px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1">{t.username}</span>
<div className="knot-input-group">
<input value={newUser.username} onChange={e => setNewUser({ ...newUser, username: e.target.value.toLowerCase() })} placeholder={t.usernamePlaceholder} />
</div>
</label>
<button onClick={handleAddUser} disabled={!newUser.username || !newUser.password} className="bg-accent text-black font-bold py-2.5 rounded-xl hover:bg-accentLight transition-all mt-2 disabled:opacity-50">
<label className="flex flex-col gap-2">
<span className="text-[10px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1">{t.displayName}</span>
<div className="knot-input-group">
<input value={newUser.displayName} onChange={e => setNewUser({ ...newUser, displayName: e.target.value })} placeholder={t.displayNamePlaceholder} />
</div>
</label>
<label className="flex flex-col gap-2">
<span className="text-[10px] font-black uppercase tracking-widest text-on-surface-variant/40 ml-1">{t.password}</span>
<div className="flex gap-2">
<div className="knot-input-group flex-1">
<input className="font-mono text-sm" value={newUser.password} onChange={e => setNewUser({ ...newUser, password: e.target.value })} placeholder={t.passwordPlaceholder} />
</div>
<button onClick={() => setNewUser({ ...newUser, password: generatePassword() })} className="bg-primary/10 hover:bg-primary/20 text-primary px-4 rounded-2xl font-black text-[10px] uppercase tracking-widest transition-all">{t.generate}</button>
</div>
</label>
<button onClick={handleAddUser} disabled={!newUser.username || !newUser.password} className="knot-button-primary w-full py-4 mt-4 disabled:opacity-30">
{t.createUser}
</button>
</div>
@@ -1532,28 +1588,67 @@ export default function AdminPage() {
{/* Global Toast */}
{toast && (
<div className="fixed top-4 right-4 z-[9999] px-5 py-3 rounded-2xl bg-surface border border-white/10 text-white shadow-2xl flex items-center gap-2">
{toast.type === 'success' ? <CheckCircle className="text-green-400 w-5 h-5" /> : <XCircle className="text-red-400 w-5 h-5" />}
{toast.message}
</div>
<motion.div
initial={{ opacity: 0, y: 20, x: '-50%' }}
animate={{ opacity: 1, y: 0, x: '-50%' }}
exit={{ opacity: 0, y: 20, x: '-50%' }}
className="fixed bottom-10 left-1/2 z-[9999] px-6 py-4 rounded-3xl bg-surface-container-high border border-outline-variant/20 text-on-surface shadow-[0_24px_64px_rgba(0,0,0,0.8)] flex items-center gap-4 min-w-[280px] backdrop-blur-3xl"
>
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${toast.type === 'success' ? 'bg-primary/10' : 'bg-error/10'}`}>
<span className={`material-symbols-outlined text-[22px] ${toast.type === 'success' ? 'text-primary' : 'text-error'}`}>
{toast.type === 'success' ? 'check_circle' : 'error'}
</span>
</div>
<div className="flex flex-col">
<span className="text-[10px] font-black uppercase tracking-widest opacity-40">{toast.type === 'success' ? (lang === 'ru' ? 'Успешно' : 'Success') : (lang === 'ru' ? 'Ошибка' : 'Error')}</span>
<span className="text-[13px] font-bold">{toast.message}</span>
</div>
</motion.div>
)}
</div>
);
}
function NavButton({ icon, label, active, onClick }: { icon: any, label: string, active: boolean, onClick: () => void }) {
function NavButton({ icon, label, active, onClick }: { icon: string, label: string, active: boolean, onClick: () => void }) {
return (
<button onClick={onClick} className={`flex items-center gap-3 px-5 py-3.5 rounded-xl transition-all font-medium text-sm group ${active ? 'bg-accent/20 text-accent shadow-inner shadow-white/5' : 'text-gray-500 hover:text-white hover:bg-white/[0.03]'}`}>
<span className={`transition-transform duration-300 ${active ? 'scale-110' : 'group-hover:scale-105'}`}>{icon}</span>
{label}
<button
onClick={onClick}
className={`w-full flex items-center gap-4 px-4 py-3 rounded-2xl transition-all duration-400 ease-ice group relative ${
active
? 'bg-primary/10 text-primary'
: 'text-on-surface-variant/40 hover:text-on-surface hover:bg-surface-container-highest/10'
}`}
>
{active && (
<motion.div
layoutId="nav-active"
className="absolute left-0 w-1 h-6 bg-primary rounded-full"
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
/>
)}
<span className="material-symbols-outlined text-[22px] transition-transform group-hover:scale-110">
{icon}
</span>
<span className="text-[13px] font-black uppercase tracking-widest">{label}</span>
</button>
);
}
function Toggle({ checked, onChange }: { checked: boolean, onChange: (v: boolean) => void }) {
return (
<button onClick={() => onChange(!checked)} className={`w-12 h-6 rounded-full relative transition-colors border ${checked ? 'bg-accent border-accent' : 'bg-black border-white/20'}`}>
<motion.div animate={{ x: checked ? 26 : 2 }} transition={{ type: 'spring', stiffness: 500, damping: 30 }} className={`absolute top-1 w-4 h-4 rounded-full ${checked ? 'bg-black' : 'bg-white/40'}`} />
<button
onClick={() => onChange(!checked)}
className={`w-11 h-6 rounded-full relative transition-all duration-300 ${
checked ? 'bg-primary' : 'bg-surface-container-highest'
}`}
>
<motion.div
animate={{ x: checked ? 22 : 3 }}
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
className={`absolute top-1 w-4 h-4 rounded-full ${
checked ? 'bg-surface-container-lowest' : 'bg-on-surface-variant/40'
}`}
/>
</button>
);
}

View File

@@ -27,37 +27,36 @@ export default function AuthPage() {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="h-full flex flex-col items-center justify-center relative overflow-hidden bg-[#0a0a0c]"
className="h-full flex flex-col items-center justify-center relative overflow-hidden bg-surface-container-lowest"
>
{/* Переключатель языка сверху по центру */}
<div className="absolute top-8 left-1/2 -translate-x-1/2 flex gap-4 text-sm font-semibold text-zinc-500 z-50">
<button onClick={() => setLang('en')} className={lang === 'en' ? 'text-[#9b66ff]' : 'hover:text-white transition-colors'}>EN</button>
<div className="w-px h-4 bg-white/10 self-center" />
<button onClick={() => setLang('ru')} className={lang === 'ru' ? 'text-[#9b66ff]' : 'hover:text-white transition-colors'}>RU</button>
{/* Language Switcher */}
<div className="absolute top-12 left-1/2 -translate-x-1/2 flex items-center gap-1 p-1 bg-surface-container-low rounded-full border border-outline-variant/10 shadow-lg z-50 slide-on-ice">
<button onClick={() => setLang('en')} className={`px-4 py-1.5 rounded-full text-xs font-black tracking-widest transition-all ${lang === 'en' ? 'bg-primary text-on-primary' : 'text-on-surface-variant/40 hover:text-on-surface'}`}>EN</button>
<button onClick={() => setLang('ru')} className={`px-4 py-1.5 rounded-full text-xs font-black tracking-widest transition-all ${lang === 'ru' ? 'bg-primary text-on-primary' : 'text-on-surface-variant/40 hover:text-on-surface'}`}>RU</button>
</div>
{/* Карточка авторизации */}
<motion.div
initial={{ scale: 0.95, y: 20 }}
animate={{ scale: 1, y: 0 }}
transition={{ duration: 0.4, ease: 'easeOut' }}
className="relative z-10 w-full max-w-[420px] mx-4"
initial={{ scale: 0.98, opacity: 0, y: 10 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
className="relative z-10 w-full max-w-[440px] mx-4"
>
<div className="bg-[#111113] rounded-[32px] p-10 shadow-2xl border border-white/5">
<div className="knot-card p-10 shadow-none border-none">
{/* Заголовок */}
<div className="flex flex-col items-center mb-10">
{/* Header */}
<div className="flex flex-col items-center mb-12">
<motion.div
initial={{ rotate: -180, scale: 0 }}
initial={{ rotate: -10, scale: 0.9 }}
animate={{ rotate: 0, scale: 1 }}
transition={{ duration: 0.6, type: 'spring', bounce: 0.4 }}
className="w-[84px] h-[84px] rounded-[28px] bg-[#1a1625] flex items-center justify-center mb-6 shadow-inner border border-white/5"
transition={{ duration: 0.8, type: 'spring' }}
className="w-20 h-20 rounded-3xl bg-primary/10 flex items-center justify-center mb-6 shadow-none border-2 border-primary/20 slide-on-ice group"
>
<MessageSquare className="w-9 h-9 text-[#8b5cf6]" />
<span className="material-symbols-outlined text-4xl text-primary group-hover:scale-110 transition-transform">hub</span>
</motion.div>
<h1 className="text-[28px] font-bold bg-gradient-to-r from-[#9b66ff] to-[#bd99ff] text-transparent bg-clip-text tracking-tight">Knot Messenger</h1>
<p className="text-zinc-500 text-[11px] mt-2.5 tracking-widest uppercase font-semibold">
{isLogin ? (lang === 'ru' ? 'вход' : 'login') : (lang === 'ru' ? 'регистрация' : 'registration')}
<h1 className="text-3xl font-black font-headline text-on-surface tracking-tighter mb-1">Knot</h1>
<p className="text-on-surface-variant/40 text-[11px] tracking-[0.2em] uppercase font-black">
{isLogin ? (lang === 'ru' ? 'authorization' : 'authorization') : (lang === 'ru' ? 'registration' : 'registration')}
</p>
</div>

View File

@@ -37,10 +37,10 @@ export default function LoginForm({ onRegisterClick, enableRegistration }: Props
<AnimatePresence>
{error && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="mb-6 p-3.5 rounded-xl bg-red-500/10 border border-red-500/20 text-red-400 text-sm font-medium text-center"
initial={{ opacity: 0, scale: 0.98 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.98 }}
className="mb-6 p-4 rounded-2xl bg-error/10 border-none text-error text-[13px] font-bold text-center uppercase tracking-widest"
role="alert"
>
{error}
@@ -49,76 +49,79 @@ export default function LoginForm({ onRegisterClick, enableRegistration }: Props
</AnimatePresence>
<form onSubmit={handleSubmit} className="space-y-4" autoComplete="off">
<div>
<label className="block text-sm font-semibold text-zinc-300 mb-2">
Username
<div className="knot-input-group">
<label className="block text-[11px] font-black uppercase tracking-[0.2em] text-on-surface-variant/40 mb-3 ml-1">
{lang === 'ru' ? 'Имя пользователя' : 'Username'}
</label>
<input
type="text"
value={credentials.username}
onChange={(e) => setCredentials({ ...credentials, username: e.target.value.replace(/[^a-zA-Z0-9_]/g, '') })}
placeholder="username"
className="w-full px-4 py-3.5 rounded-xl bg-[#18181b] border border-white/5 text-white placeholder-zinc-600 focus:border-[#9b66ff]/50 focus:bg-[#1f1f22] transition-colors outline-none text-[15px]"
required
autoFocus
autoComplete="off"
/>
<div className="relative">
<span className="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 text-[20px]">person</span>
<input
type="text"
value={credentials.username}
onChange={(e) => setCredentials({ ...credentials, username: e.target.value.replace(/[^a-zA-Z0-9_]/g, '') })}
placeholder="username"
className="w-full pl-12 pr-4 py-4 rounded-2xl bg-surface-container-highest/60 text-on-surface placeholder-on-surface-variant/20 border-none focus:ring-2 focus:ring-primary/20 transition-all outline-none text-[15px]"
required
autoFocus
autoComplete="off"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-zinc-300 mb-2">
<div className="knot-input-group">
<label className="block text-[11px] font-black uppercase tracking-[0.2em] text-on-surface-variant/40 mb-3 ml-1">
{lang === 'ru' ? 'Пароль' : 'Password'}
</label>
<div className="relative">
<span className="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 text-[20px]">lock</span>
<input
type={showPassword ? 'text' : 'password'}
value={credentials.password}
onChange={(e) => setCredentials({ ...credentials, password: e.target.value })}
placeholder={lang === 'ru' ? 'Введите пароль' : 'Enter password'}
className="w-full px-4 py-3.5 rounded-xl bg-[#18181b] border border-white/5 text-white placeholder-zinc-600 focus:border-[#9b66ff]/50 focus:bg-[#1f1f22] transition-colors pr-12 outline-none text-[15px]"
placeholder={lang === 'ru' ? '••••••••' : '••••••••'}
className="w-full pl-12 pr-12 py-4 rounded-2xl bg-surface-container-highest/60 text-on-surface placeholder-on-surface-variant/20 border-none focus:ring-2 focus:ring-primary/20 transition-all outline-none text-[15px]"
required
autoComplete="current-password"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-300 transition-colors"
className="absolute right-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 hover:text-primary transition-colors slide-on-ice"
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
<span className="material-symbols-outlined text-[20px]">{showPassword ? 'visibility_off' : 'visibility'}</span>
</button>
</div>
</div>
<motion.button
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
<button
disabled={isSubmitting}
type="submit"
className="w-full py-3.5 px-4 rounded-xl bg-[#8b5cf6] hover:bg-[#7c3aed] text-white font-semibold flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed mt-8 transition-colors text-[16px]"
style={{ marginTop: '32px' }}
className="knot-button-primary w-full py-4 mt-8 flex items-center justify-center gap-3 disabled:opacity-50 slide-on-ice"
>
{isSubmitting ? (
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
<div className="w-5 h-5 border-2 border-on-primary/30 border-t-on-primary rounded-full animate-spin" />
) : (
<>
{lang === 'ru' ? 'Войти' : 'Login'}
<ArrowRight size={18} />
<span className="font-black uppercase tracking-widest text-[14px]">
{lang === 'ru' ? 'Войти' : 'Continue'}
</span>
<span className="material-symbols-outlined text-[20px]">arrow_forward</span>
</>
)}
</motion.button>
</button>
</form>
{enableRegistration && onRegisterClick && (
<div className="mt-8 pt-6 border-t border-white/5 flex justify-center items-center gap-2">
<p className="text-zinc-500 text-[13px] font-medium">
{lang === 'ru' ? 'Нет аккаунта?' : "Don't have an account?"}
<div className="mt-10 pt-8 border-t border-on-surface-variant/5 flex flex-col items-center gap-4">
<p className="text-on-surface-variant/40 text-[11px] font-black uppercase tracking-widest">
{lang === 'ru' ? 'Нет аккаунта?' : "New to Knot?"}
</p>
<button
onClick={onRegisterClick}
className="text-[#7c3aed] hover:text-[#8b5cf6] text-[13px] font-semibold transition-colors"
className="text-primary hover:text-primary/80 text-[13px] font-black uppercase tracking-widest transition-all slide-on-ice"
type="button"
>
{lang === 'ru' ? 'Зарегистрироваться' : 'Register'}
{lang === 'ru' ? 'Создать аккаунт' : 'Create Account'}
</button>
</div>
)}

View File

@@ -39,10 +39,10 @@ export default function RegisterForm({ onLoginClick, enableRegistration }: Props
<AnimatePresence>
{error && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="mb-6 p-3.5 rounded-xl bg-red-500/10 border border-red-500/20 text-red-400 text-sm font-medium text-center"
initial={{ opacity: 0, scale: 0.98 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.98 }}
className="mb-8 p-4 rounded-2xl bg-error/10 border-none text-error text-[13px] font-bold text-center uppercase tracking-widest"
role="alert"
>
{error}
@@ -51,46 +51,53 @@ export default function RegisterForm({ onLoginClick, enableRegistration }: Props
</AnimatePresence>
<form onSubmit={handleSubmit} className="space-y-4" autoComplete="off">
<div>
<label className="block text-sm font-semibold text-zinc-300 mb-2">
Username <span className="text-zinc-600 font-normal ml-1">({lang === 'ru' ? 'латиница, нельзя изменить' : 'latin, cannot change'})</span>
<div className="knot-input-group">
<label className="block text-[11px] font-black uppercase tracking-[0.2em] text-on-surface-variant/40 mb-3 ml-1">
{lang === 'ru' ? 'Имя пользователя (латиница)' : 'Username (latin only)'}
</label>
<input
type="text"
value={credentials.username}
onChange={(e) => setCredentials({ ...credentials, username: e.target.value.replace(/[^a-zA-Z0-9_]/g, '') })}
placeholder="username"
className="w-full px-4 py-3.5 rounded-xl bg-[#18181b] border border-white/5 text-white placeholder-zinc-600 focus:border-[#9b66ff]/50 focus:bg-[#1f1f22] transition-colors outline-none text-[15px]"
required
autoFocus
autoComplete="off"
/>
<div className="relative">
<span className="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 text-[20px]">alternate_email</span>
<input
type="text"
value={credentials.username}
onChange={(e) => setCredentials({ ...credentials, username: e.target.value.replace(/[^a-zA-Z0-9_]/g, '') })}
placeholder="username"
className="w-full pl-12 pr-4 py-4 rounded-2xl bg-surface-container-highest/60 text-on-surface placeholder-on-surface-variant/20 border-none focus:ring-2 focus:ring-primary/20 transition-all outline-none text-[15px]"
required
autoFocus
autoComplete="off"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-zinc-300 mb-2">
<div className="knot-input-group">
<label className="block text-[11px] font-black uppercase tracking-[0.2em] text-on-surface-variant/40 mb-3 ml-1">
{lang === 'ru' ? 'Отображаемое имя' : 'Display Name'}
</label>
<input
type="text"
value={credentials.displayName}
onChange={(e) => setCredentials({ ...credentials, displayName: e.target.value })}
placeholder={lang === 'ru' ? 'Ваше имя (любой язык)' : 'Your name'}
className="w-full px-4 py-3.5 rounded-xl bg-[#18181b] border border-white/5 text-white placeholder-zinc-600 focus:border-[#9b66ff]/50 focus:bg-[#1f1f22] transition-colors outline-none text-[15px]"
/>
<div className="relative">
<span className="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 text-[20px]">face</span>
<input
type="text"
value={credentials.displayName}
onChange={(e) => setCredentials({ ...credentials, displayName: e.target.value })}
placeholder={lang === 'ru' ? 'Ваше имя' : 'Your name'}
className="w-full pl-12 pr-4 py-4 rounded-2xl bg-surface-container-highest/60 text-on-surface placeholder-on-surface-variant/20 border-none focus:ring-2 focus:ring-primary/20 transition-all outline-none text-[15px]"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-zinc-300 mb-2">
<div className="knot-input-group">
<label className="block text-[11px] font-black uppercase tracking-[0.2em] text-on-surface-variant/40 mb-3 ml-1">
{lang === 'ru' ? 'Пароль' : 'Password'}
</label>
<div className="relative">
<span className="material-symbols-outlined absolute left-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 text-[20px]">lock_open</span>
<input
type={showPassword ? 'text' : 'password'}
value={credentials.password}
onChange={(e) => setCredentials({ ...credentials, password: e.target.value })}
placeholder={lang === 'ru' ? 'Введите пароль' : 'Enter password'}
className="w-full px-4 py-3.5 rounded-xl bg-[#18181b] border border-white/5 text-white placeholder-zinc-600 focus:border-[#9b66ff]/50 focus:bg-[#1f1f22] transition-colors pr-12 outline-none text-[15px]"
placeholder="••••••••"
className="w-full pl-12 pr-12 py-4 rounded-2xl bg-surface-container-highest/60 text-on-surface placeholder-on-surface-variant/20 border-none focus:ring-2 focus:ring-primary/20 transition-all outline-none text-[15px]"
required
autoComplete="new-password"
minLength={8}
@@ -98,14 +105,14 @@ export default function RegisterForm({ onLoginClick, enableRegistration }: Props
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-300 transition-colors"
className="absolute right-4 top-1/2 -translate-y-1/2 text-on-surface-variant/40 hover:text-primary transition-colors slide-on-ice"
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
<span className="material-symbols-outlined text-[20px]">{showPassword ? 'visibility_off' : 'visibility'}</span>
</button>
</div>
<p className="mt-2 text-[12px] text-zinc-500 flex items-center gap-1.5 font-medium">
<span className="w-1 h-1 rounded-full bg-[#9b66ff]" />
{lang === 'ru' ? 'Минимум 8 символов, буквы и цифры' : 'Minimum 8 characters, letters and numbers'}
<p className="mt-3 text-[10px] text-on-surface-variant/40 flex items-center gap-1.5 font-black uppercase tracking-widest ml-1">
<span className="w-2.5 h-2.5 rounded-full border-2 border-primary" />
{lang === 'ru' ? '8+ символов, любые знаки' : '8+ characters required'}
</p>
</div>
@@ -122,36 +129,35 @@ export default function RegisterForm({ onLoginClick, enableRegistration }: Props
/>
</div>
<motion.button
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
<button
disabled={isSubmitting}
type="submit"
className="w-full py-3.5 px-4 rounded-xl bg-[#8b5cf6] hover:bg-[#7c3aed] text-white font-semibold flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed mt-8 transition-colors text-[16px]"
style={{ marginTop: '32px' }}
className="knot-button-primary w-full py-4 mt-8 flex items-center justify-center gap-3 disabled:opacity-50 slide-on-ice"
>
{isSubmitting ? (
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
<div className="w-5 h-5 border-2 border-on-primary/30 border-t-on-primary rounded-full animate-spin" />
) : (
<>
{lang === 'ru' ? 'Создать аккаунт' : 'Create account'}
<ArrowRight size={18} />
<span className="font-black uppercase tracking-widest text-[14px]">
{lang === 'ru' ? 'Создать аккаунт' : 'Create Account'}
</span>
<span className="material-symbols-outlined text-[20px]">check_circle</span>
</>
)}
</motion.button>
</button>
</form>
{enableRegistration && (
<div className="mt-8 pt-6 border-t border-white/5 flex justify-center items-center gap-2">
<p className="text-zinc-500 text-[13px] font-medium">
<div className="mt-10 pt-8 border-t border-on-surface-variant/5 flex flex-col items-center gap-4">
<p className="text-on-surface-variant/40 text-[11px] font-black uppercase tracking-widest">
{lang === 'ru' ? 'Уже есть аккаунт?' : 'Already have an account?'}
</p>
<button
onClick={onLoginClick}
className="text-[#7c3aed] hover:text-[#8b5cf6] text-[13px] font-semibold transition-colors"
className="text-primary hover:text-primary/80 text-[13px] font-black uppercase tracking-widest transition-all slide-on-ice"
type="button"
>
{lang === 'ru' ? 'Войти' : 'Login'}
{lang === 'ru' ? 'Войти в систему' : 'Sign In instead'}
</button>
</div>
)}

View File

@@ -9,6 +9,7 @@ import { useLang } from '../../../core/infrastructure/i18n';
import type { Message, UserBasic, CallInfo, ChatMember } from '../../../core/domain/types';
import { Send, Check, Phone, PhoneOff } from 'lucide-react';
import Sidebar from '../../../core/presentation/layouts/Sidebar';
import GlobalNavBar from '../../../core/presentation/layouts/GlobalNavBar';
import ChatView from './components/ChatView';
import CallModal from '../../calls/presentation/components/CallModal';
import GroupCallModal from '../../calls/presentation/components/GroupCallModal';
@@ -362,14 +363,25 @@ export default function ChatPage() {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="h-full flex bg-surface sm:p-3 sm:gap-3 overflow-hidden"
className="h-screen w-screen flex bg-surface-dim overflow-hidden antialiased font-body selection:bg-primary/30"
>
<div className={`${activeChat ? 'hidden sm:block' : 'block'} w-full sm:w-[340px] flex-shrink-0`}>
<Sidebar />
</div>
<div className={`${activeChat ? 'block' : 'hidden sm:block'} flex-1 h-full min-w-0`}>
<ChatView onStartCall={handleStartCall} onStartGroupCall={handleStartGroupCall} />
</div>
<GlobalNavBar activeTab="chats" onTabChange={() => {}} />
<main className="ml-20 flex-1 flex flex-row relative h-full">
{/* Chat List (Sidebar) */}
<div
className={`${activeChat ? 'hidden lg:block' : 'block'} w-full lg:w-[360px] flex-shrink-0 bg-surface-container-low tonal-transition-no-border h-full`}
>
<Sidebar />
</div>
{/* Selected Chat View (Main Area) */}
<div
className={`${activeChat ? 'block' : 'hidden lg:block'} flex-1 h-full min-w-0 bg-surface-container-lowest m-4 rounded-3xl overflow-hidden relative group slide-on-ice`}
>
<ChatView onStartCall={handleStartCall} onStartGroupCall={handleStartGroupCall} />
</div>
</main>
<CallModal
key={callSessionId}
isOpen={callOpen}

View File

@@ -141,18 +141,20 @@ function ChatListItem({ chat, isActive }: ChatListItemProps) {
<button
onClick={handleClick}
onContextMenu={handleContextMenu}
className={`w-full flex items-center gap-3 px-3 py-3 transition-colors text-left ${
isActive ? 'bg-accent/15 border-r-2 border-accent' : 'hover:bg-surface-hover'
className={`w-full flex items-center gap-4 px-4 py-3.5 transition-all duration-300 slide-on-ice text-left rounded-2xl mx-1 my-0.5 w-[calc(100%-8px)] ${
isActive ? 'bg-primary/10' : 'hover:bg-surface-container-highest/20'
}`}
>
{/* Аватар */}
<div className="relative flex-shrink-0">
{isFavorites ? (
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center shadow-lg">
<Bookmark size={22} className="text-white" />
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-primary to-primary-container flex items-center justify-center shadow-lg shadow-primary/10 border-2 border-outline-variant/10">
<span className="material-symbols-outlined text-on-primary-container text-[24px]">bookmark</span>
</div>
) : (
<Avatar src={chatAvatar || undefined} name={chatName || '??'} size="lg" online={isOnline ? true : false} />
<div className="avatar-knot-container group-hover:scale-105 transition-transform">
<Avatar src={chatAvatar || undefined} name={chatName || '??'} size="lg" online={isOnline ? true : false} />
</div>
)}
</div>
@@ -160,28 +162,26 @@ function ChatListItem({ chat, isActive }: ChatListItemProps) {
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5 min-w-0">
{isPinned && <Pin size={12} className="text-knot-400 flex-shrink-0 rotate-45" />}
<span className="text-sm font-medium text-white truncate">{chatName}</span>
{isPinned && <span className="material-symbols-outlined text-[14px] text-primary rotate-45">push_pin</span>}
<span className={`text-sm font-bold tracking-tight truncate ${isActive ? 'text-primary' : 'text-on-surface'}`}>{chatName}</span>
</div>
{timeStr && <span className="text-xs text-zinc-500 flex-shrink-0 ml-2">{timeStr}</span>}
{timeStr && <span className="text-[10px] uppercase font-bold tracking-wider text-on-surface-variant/50 flex-shrink-0 ml-2">{timeStr}</span>}
</div>
<div className="flex items-center justify-between mt-0.5">
<div className="flex items-center gap-1 min-w-0 flex-1">
{isMine && lastMessage && !lastMessage.isDeleted && (
<span className="flex-shrink-0">
{isRead ? (
<CheckCheck size={14} className="text-knot-400" />
) : (
<Check size={14} className="text-zinc-500" />
)}
<span className={`material-symbols-outlined text-[14px] ${isRead ? 'text-primary fill-1' : 'text-on-surface-variant/40'}`} style={{ fontVariationSettings: `'FILL' ${isRead ? 1 : 0}` }}>
{isRead ? 'done_all' : 'done'}
</span>
</span>
)}
<p className={`text-xs truncate ${isTyping ? 'text-knot-400 font-medium' : draft ? 'text-red-400' : 'text-zinc-400'}`}>
{isTyping ? t('typing') : draft ? <><span className="font-medium">{t('draft')} </span>{stripMarkdown(draft)}</> : previewText}
<p className={`text-[13px] truncate leading-tight ${isTyping ? 'text-tertiary font-bold' : draft ? 'text-error font-medium' : 'text-on-surface-variant/60'}`}>
{isTyping ? t('typing') : draft ? <><span className="font-bold">{t('draft')} </span>{stripMarkdown(draft)}</> : previewText}
</p>
</div>
{chat.unreadCount > 0 && !isActive && (
<span className="ml-2 flex-shrink-0 min-w-[20px] h-5 px-1.5 rounded-full bg-accent flex items-center justify-center text-[11px] text-white font-medium">
<span className="ml-2 flex-shrink-0 min-w-[20px] h-5 px-1.5 rounded-full bg-primary text-on-primary-container flex items-center justify-center text-[10px] font-black shadow-lg shadow-primary/20 animate-pulse">
{chat.unreadCount}
</span>
)}

View File

@@ -501,15 +501,16 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
<div
ref={chatViewRef}
onMouseMove={handleMouseMove}
className={`flex-1 flex flex-col h-full sm:rounded-[2rem] overflow-hidden shadow-[0_0_120px_-20px_rgba(0,0,0,0.5)] border-x sm:border border-border/50 relative z-0 chat-theme-${chatTheme} transition-colors duration-500`}
className={`flex-1 flex flex-col h-full overflow-hidden bg-surface-container-lowest relative z-0 chat-theme-${chatTheme} transition-colors duration-500`}
>
<div className="absolute inset-0 pointer-events-none bg-gradient-to-b from-primary/5 to-transparent h-32 opacity-30" />
{selectionMode ? (
<div className="h-[76px] flex items-center justify-between px-4 sm:px-6 border-b border-border/40 bg-surface-secondary/80 backdrop-blur-xl z-20 flex-shrink-0 animate-in slide-in-from-top-2">
<div className="flex items-center gap-2 sm:gap-4 text-white">
<button onClick={() => { setSelectionMode(false); setSelectedMessages(new Set()); }} className="p-2 -ml-2 rounded-full hover:bg-white/10 transition">
<X size={20} className="text-zinc-300" />
<div className="h-[76px] flex items-center justify-between px-6 bg-surface-container-highest/80 backdrop-blur-2xl z-20 flex-shrink-0 animate-in slide-in-from-top-2 border-none">
<div className="flex items-center gap-4 text-on-surface">
<button onClick={() => { setSelectionMode(false); setSelectedMessages(new Set()); }} className="p-2 -ml-2 rounded-xl hover:bg-on-surface/10 transition slide-on-ice">
<span className="material-symbols-outlined">close</span>
</button>
<span className="font-medium text-[15px]">{selectedMessages.size} {t('selected') || 'выбрано'}</span>
<span className="text-lg font-bold font-headline">{selectedMessages.size} {t('selected')}</span>
</div>
<div className="flex items-center gap-3">
<div className="relative" ref={deleteMenuRef}>
@@ -560,16 +561,16 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
</div>
</div>
) : (
<div className="h-[76px] flex items-center justify-between px-3 sm:px-6 border-b border-border/40 bg-surface-secondary/80 backdrop-blur-xl z-20 flex-shrink-0 gap-2">
<div className="h-[76px] flex items-center justify-between px-6 bg-surface-container-lowest/40 backdrop-blur-xl z-20 flex-shrink-0 gap-2 border-none">
<div className="flex items-center gap-2 min-w-0 flex-1">
<button
onClick={() => setActiveChat(null)}
className="sm:hidden p-2 -ml-1 rounded-xl hover:bg-surface-hover text-zinc-400 transition-colors"
className="lg:hidden p-2 -ml-1 rounded-xl hover:bg-surface-container-highest/30 text-on-surface-variant transition-colors"
>
<ArrowLeft size={22} />
<span className="material-symbols-outlined">arrow_back</span>
</button>
<button
className="flex items-center gap-3 min-w-0 flex-1 group transition-all"
<div
className="flex items-center gap-4 min-w-0 flex-1 group cursor-pointer"
onClick={() => {
if (chat.type === 'personal' && otherMember) {
setProfileUserId(otherMember.user.id);
@@ -578,10 +579,10 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
}
}}
>
<div className="relative flex-shrink-0 transform transition-transform duration-300 group-hover:scale-105">
<div className="relative flex-shrink-0 transform transition-all duration-500 group-hover:scale-105 active:scale-95">
{isFavorites ? (
<div className="w-11 h-11 rounded-full bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center shadow-lg ring-2 ring-transparent group-hover:ring-accent/30 transition-all duration-300">
<Bookmark size={20} className="text-white" />
<div className="w-11 h-11 rounded-full bg-gradient-to-br from-primary to-primary-container flex items-center justify-center shadow-lg shadow-primary/10 border-2 border-outline-variant/10">
<span className="material-symbols-outlined text-on-primary-container text-[20px]">bookmark</span>
</div>
) : (
<Avatar
@@ -589,27 +590,27 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
name={chatName}
size="md"
online={isOnline ? true : undefined}
className="ring-2 ring-transparent group-hover:ring-accent/30 transition-all duration-300 rounded-full"
className="avatar-knot-container group-hover:border-primary/30"
/>
)}
</div>
<div className="min-w-0 text-left">
<h3 className="text-base font-semibold text-white truncate drop-shadow-sm group-hover:text-accent/90 transition-colors">{chatName}</h3>
<p className="text-xs text-zinc-400 truncate">
<h3 className="text-lg font-bold font-headline text-on-surface truncate group-hover:text-primary transition-colors leading-none mb-1">{chatName}</h3>
<p className="text-[11px] font-bold uppercase tracking-widest text-on-surface-variant/50">
{isFavorites
? t('favoritesDescription')
: typingInChat.length > 0
? <span className="text-accent font-medium">{t('typing')}</span>
? <span className="text-primary font-black animate-pulse">{t('typing')}</span>
: isOnline
? <span className="text-emerald-400">{t('online')}</span>
? <span className="text-success">{t('online')}</span>
: chat.type === 'personal' && otherMember?.user.lastSeen
? `${t('lastSeenAt')} ${formatLastSeen(otherMember.user.lastSeen, lang)}`
? `${formatLastSeen(otherMember.user.lastSeen, lang)}`
: chat.type === 'group'
? `${chat.members.length} ${t('members')}`
: ''}
</p>
</div>
</button>
</div>
</div>
<div className="flex items-center gap-1.5 ml-4">
@@ -627,7 +628,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
placeholder={t('searchMessages')}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
className="w-full px-3 py-1.5 rounded-lg bg-surface-tertiary text-sm text-white placeholder-zinc-500 border border-border focus:border-accent"
className="w-full px-4 py-2 rounded-xl bg-surface-container text-sm text-on-surface placeholder-on-surface-variant/30 border-none focus:ring-2 focus:ring-primary/20"
/>
</motion.div>
)}
@@ -643,9 +644,9 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
openSearch();
}
}}
className="p-2 rounded-lg hover:bg-surface-hover transition-colors text-zinc-400 hover:text-white"
className="p-2 rounded-xl hover:bg-surface-container-highest/40 transition-all duration-300 text-on-surface-variant hover:text-primary slide-on-ice"
>
{showSearch ? <X size={18} /> : <Search size={18} />}
{showSearch ? <span className="material-symbols-outlined">close</span> : <span className="material-symbols-outlined">search</span>}
</button>
{!isFavorites && config?.enableCalls && (
@@ -658,8 +659,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
onStartGroupCall?.(chat.id, chat.name || 'Group', 'voice');
}
}}
className="p-2 rounded-lg hover:bg-surface-hover transition-colors text-zinc-400 hover:text-white" title={t('call')}>
<Phone size={18} />
className="p-2 rounded-xl hover:bg-surface-container-highest/40 transition-all duration-300 text-on-surface-variant hover:text-primary slide-on-ice" title={t('call')}>
<span className="material-symbols-outlined">call</span>
</button>
<button
onClick={() => {
@@ -669,8 +670,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
onStartGroupCall?.(chat.id, chat.name || 'Group', 'video');
}
}}
className="p-2 rounded-lg hover:bg-surface-hover transition-colors text-zinc-400 hover:text-white" title={t('videoCall')}>
<Video size={18} />
className="p-2 rounded-xl hover:bg-surface-container-highest/40 transition-all duration-300 text-on-surface-variant hover:text-primary slide-on-ice" title={t('videoCall')}>
<span className="material-symbols-outlined">videocam</span>
</button>
</>
)}
@@ -678,9 +679,9 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
<div className="relative" ref={topMenuRef}>
<button
onClick={() => setShowTopMenu(!showTopMenu)}
className="p-2 rounded-lg hover:bg-surface-hover transition-colors text-zinc-400 hover:text-white"
className="p-2 rounded-xl hover:bg-surface-container-highest/40 transition-all duration-300 text-on-surface-variant hover:text-primary slide-on-ice"
>
<MoreVertical size={18} />
<span className="material-symbols-outlined">more_vert</span>
</button>
<AnimatePresence>
{showTopMenu && (

View File

@@ -426,12 +426,12 @@ function MessageBubble({
onContextMenu={handleContextMenu}
onDoubleClick={handleReply}
title={t('reply') ? `${t('reply')} (Double Click)` : 'Double click to reply'}
className={`cursor-pointer max-w-full min-w-[40px] rounded-[1.25rem] transition-all duration-300 overflow-hidden ${
className={`cursor-pointer max-w-full min-w-[60px] transition-all duration-500 overflow-hidden ${
!needsFrame
? 'p-0 shadow-none border-none bg-transparent'
: isMine
? 'bubble-sent text-white shadow-sm px-[14px] py-[8px] hover:shadow-md rounded-br-sm'
: 'bubble-received text-zinc-100 shadow-sm px-[14px] py-[8px] hover:shadow-md rounded-bl-[4px]'
? 'bubble-sent px-4 py-2.5 hover:bg-primary/[0.12] rounded-2xl rounded-br-[4px]'
: 'bubble-received px-4 py-2.5 hover:bg-surface-container-highest/80 rounded-2xl rounded-bl-[4px]'
}`}
>
@@ -451,7 +451,7 @@ function MessageBubble({
}
}}
>
<p className={`text-[13.5px] font-semibold mb-0.5 truncate ${isMine ? 'text-white' : 'text-knot-500'}`}>
<p className={`text-[13px] font-bold mb-0.5 truncate ${isMine ? 'text-primary' : 'text-primary'}`}>
{message.replyTo.sender?.displayName || message.replyTo.sender?.userName || message.replyTo.sender?.username || ''}
</p>
<div className="flex items-center gap-1.5">
@@ -481,7 +481,7 @@ function MessageBubble({
</div>
);
})()}
<p className={`text-[13px] line-clamp-2 break-words whitespace-pre-wrap ${isMine ? 'text-white/80' : 'text-zinc-600 dark:text-zinc-300'}`}>
<p className={`text-[13px] line-clamp-2 break-words whitespace-pre-wrap ${isMine ? 'text-on-surface-variant' : 'text-on-surface-variant'}`}>
{message.quote || message.replyTo.content || (message.replyTo.media && message.replyTo.media.length > 0 ? (() => {
const m = message.replyTo.media[0];
if (m.type === 'image' && m.url?.toLowerCase().endsWith('.mp4')) return 'GIF';
@@ -619,14 +619,12 @@ function MessageBubble({
{!message.content && (
<div className="absolute bottom-1.5 right-1.5 z-10 pointer-events-none flex justify-end">
<span className="text-[10px] text-white/80 bg-black/40 shadow-sm px-2 py-0.5 rounded-full flex items-center gap-1 backdrop-blur-md pointer-events-auto">
<span className="text-[10px] font-bold text-on-surface-variant/40 bg-surface-container-highest/40 px-2 py-0.5 rounded-full flex items-center gap-1 backdrop-blur-md pointer-events-auto">
{timeStr}
{isMine && !message.scheduledAt && (
isRead ? (
<CheckCheck size={13} className="text-sky-300" />
) : (
<Check size={13} />
)
<span className={`material-symbols-outlined text-[14px] ${isRead ? 'text-primary fill-1' : 'text-on-surface-variant/40'}`} style={{ fontVariationSettings: `'FILL' ${isRead ? 1 : 0}` }}>
{isRead ? 'done_all' : 'done'}
</span>
)}
</span>
</div>
@@ -677,8 +675,8 @@ function MessageBubble({
<div
key={i}
className={`flex-1 rounded-full transition-colors duration-150 ${isActive
? isMine ? 'bg-white/80' : 'bg-knot-400'
: isMine ? 'bg-white/20' : 'bg-white/10'
? isMine ? 'bg-primary/40' : 'bg-primary'
: isMine ? 'bg-primary/10' : 'bg-on-surface-variant/10'
}`}
style={{ height: `${barHeight}%` }}
/>
@@ -797,17 +795,14 @@ function MessageBubble({
</div>
)}
</div>
<span className={`text-[10.5px] flex-shrink-0 flex items-center gap-0.5 self-end float-right leading-none ${isOnlyEmojis ? '-mb-1' : 'mb-0.5'} ${isMine ? 'text-white/60' : 'text-zinc-500'
}`}>
<span className={`text-[10px] font-bold flex-shrink-0 flex items-center gap-0.5 self-end float-right leading-none ${isOnlyEmojis ? '-mb-1' : 'mb-0.5'} ${isMine ? 'text-primary/60' : 'text-on-surface-variant/40'}`}>
{message.isEdited && <span className="mr-0.5">{t('edited')}</span>}
{message.scheduledAt && <Clock size={11} className="text-amber-400 mr-0.5" />}
{message.scheduledAt && <span className="material-symbols-outlined text-[12px] text-amber-400 mr-0.5">schedule</span>}
{timeStr}
{isMine && !message.scheduledAt && (
isRead ? (
<CheckCheck size={14} className="text-sky-300 ml-0.5" />
) : (
<Check size={14} className="ml-0.5" />
)
<span className={`material-symbols-outlined text-[14px] ${isRead ? 'text-primary fill-1' : 'text-on-surface-variant/40'}`} style={{ fontVariationSettings: `'FILL' ${isRead ? 1 : 0}` }}>
{isRead ? 'done_all' : 'done'}
</span>
)}
</span>
</div>

View File

@@ -589,11 +589,11 @@ export default function MessageInput({ chatId }: MessageInputProps) {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute inset-0 z-50 rounded-[2rem] mx-6 mb-6 mt-2 bg-knot-500/10 border-2 border-dashed border-knot-400 backdrop-blur-sm flex items-center justify-center pointer-events-none"
className="absolute inset-0 z-50 rounded-3xl mx-6 mb-6 mt-2 bg-primary/10 border-2 border-dashed border-primary/40 backdrop-blur-sm flex items-center justify-center pointer-events-none"
>
<div className="flex flex-col items-center gap-2 text-knot-300">
<FileText size={32} className="animate-bounce" />
<p className="font-semibold">{t('dropFileHere')}</p>
<div className="flex flex-col items-center gap-4 text-primary">
<span className="material-symbols-outlined text-5xl animate-bounce">upload_file</span>
<p className="font-bold text-lg font-headline uppercase tracking-widest">{t('dropFileHere')}</p>
</div>
</motion.div>
)}
@@ -603,27 +603,25 @@ export default function MessageInput({ chatId }: MessageInputProps) {
<AnimatePresence>
{(replyTo || editingMessage) && (
<motion.div
initial={{ height: 0, opacity: 0, y: 10, scale: 0.95 }}
animate={{ height: 'auto', opacity: 1, y: 0, scale: 1 }}
exit={{ height: 0, opacity: 0, y: 10, scale: 0.95 }}
className="mb-2 max-w-3xl mx-auto overflow-hidden px-1.5"
initial={{ height: 0, opacity: 0, scale: 0.98 }}
animate={{ height: 'auto', opacity: 1, scale: 1 }}
exit={{ height: 0, opacity: 0, scale: 0.98 }}
className="mb-2 max-w-4xl mx-auto overflow-hidden"
>
<div className="flex items-center gap-3 px-4 py-2.5 bg-white/[0.04] backdrop-blur-2xl border border-white/10 rounded-2xl relative shadow-xl">
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-3/5 bg-gradient-to-b from-knot-400 to-purple-500 rounded-r-md" />
<div className="w-6 h-6 rounded-full bg-white/5 flex items-center justify-center flex-shrink-0">
{editingMessage ? (
<Pencil size={12} className="text-knot-400" />
) : (
<Reply size={12} className="text-knot-400" />
)}
<div className="flex items-center gap-4 px-5 py-3 bg-surface-container-high/80 backdrop-blur-2xl border-none rounded-2xl relative shadow-xl slide-on-ice">
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1.5 h-3/5 bg-primary rounded-r-full" />
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0">
<span className="material-symbols-outlined text-primary text-[20px]">
{editingMessage ? 'edit' : 'reply'}
</span>
</div>
<div className="flex-1 min-w-0 flex flex-col justify-center">
<p className="text-xs font-semibold text-knot-400 mb-0.5">
<p className="text-[11px] font-black tracking-widest uppercase text-primary mb-0.5">
{editingMessage
? t('editing')
: `${t('replyTo')} ${replyTo?.sender?.displayName || replyTo?.sender?.userName || replyTo?.sender?.username || ''}`}
</p>
<div className="text-xs text-zinc-300 truncate opacity-80 border-l border-white/20 pl-2 ml-1">
<div className="text-[13px] text-on-surface-variant truncate opacity-80 pl-1 border-l border-on-surface-variant/10 ml-0.5">
{replyTo?.quote ? `«${replyTo.quote}»` : (editingMessage || replyTo)?.content || t('media') || 'Медиа'}
</div>
</div>
@@ -634,9 +632,9 @@ export default function MessageInput({ chatId }: MessageInputProps) {
setAttachments([]);
setText('');
}}
className="w-7 h-7 rounded-full flex items-center justify-center text-white/40 hover:text-white hover:bg-white/10 transition-colors"
className="w-8 h-8 rounded-full flex items-center justify-center text-on-surface-variant/40 hover:text-primary hover:bg-primary/10 transition-all slide-on-ice"
>
<X size={14} />
<span className="material-symbols-outlined text-[20px]">close</span>
</button>
</div>
</motion.div>
@@ -711,21 +709,23 @@ export default function MessageInput({ chatId }: MessageInputProps) {
{/* Recording UI */}
{isRecording ? (
<div className="flex items-center gap-3">
<div className="flex items-center gap-4 bg-surface-container-high rounded-[2rem] px-6 py-4 w-full max-w-4xl mx-auto shadow-2xl animate-in slide-in-from-bottom-4 slide-on-ice">
<button
onClick={cancelRecording}
className="p-2 rounded-lg text-red-400 hover:bg-red-500/10 transition-colors flex-shrink-0"
className="w-10 h-10 rounded-full flex items-center justify-center text-error hover:bg-error/10 transition-all slide-on-ice"
>
<X size={20} />
<span className="material-symbols-outlined">delete</span>
</button>
<div className="flex-1 flex items-center gap-3">
<span className="w-2.5 h-2.5 rounded-full bg-red-500 animate-pulse" />
<span className="text-sm text-zinc-300 font-mono">{formatTime(recordingTime)}</span>
<div className="flex-1 flex items-center gap-0.5 h-6">
<div className="flex-1 flex items-center gap-4">
<div className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-error animate-pulse shadow-[0_0_8px_rgba(244,67,54,0.5)]" />
<span className="text-sm font-bold font-mono text-on-surface tracking-tighter w-12">{formatTime(recordingTime)}</span>
</div>
<div className="flex-1 flex items-center gap-[3px] h-8 items-center">
{liveBars.map((h, i) => (
<div
key={i}
className="flex-1 bg-knot-400/60 rounded-full transition-all duration-100"
className="flex-1 bg-primary/40 rounded-full transition-all duration-100"
style={{ height: `${h}%` }}
/>
))}
@@ -733,20 +733,20 @@ export default function MessageInput({ chatId }: MessageInputProps) {
</div>
<button
onClick={stopRecording}
className="p-3 rounded-full bg-accent hover:bg-accent-hover transition-colors text-white flex-shrink-0"
className="w-12 h-12 rounded-2xl bg-primary text-on-primary flex items-center justify-center hover:scale-105 active:scale-95 transition-all shadow-lg shadow-primary/20 slide-on-ice"
>
<Send size={18} />
<span className="material-symbols-outlined">send</span>
</button>
</div>
) : (
<div className="flex items-end gap-1.5 bg-surface-secondary rounded-2xl border border-border/40 px-2 py-1.5 w-full max-w-4xl mx-auto transition-all duration-200 focus-within:border-border/80">
<div className="flex items-end gap-2 bg-surface-container-high rounded-[2rem] px-4 py-2 w-full max-w-4xl mx-auto shadow-sm transition-all duration-500 focus-within:shadow-xl focus-within:bg-surface-container-highest slide-on-ice">
{/* Attach */}
<div className="relative mb-0 flex-shrink-0 self-center">
<button
onClick={() => setShowAttachMenu(!showAttachMenu)}
className="p-2 rounded-full text-zinc-400 hover:text-accent hover:bg-surface-hover transition-colors"
className="w-10 h-10 rounded-full text-on-surface-variant hover:text-primary hover:bg-primary/10 transition-all slide-on-ice flex items-center justify-center"
>
<Paperclip size={22} strokeWidth={1.5} />
<span className="material-symbols-outlined text-[24px]">add</span>
</button>
<AnimatePresence>
{showAttachMenu && (
@@ -887,14 +887,13 @@ export default function MessageInput({ chatId }: MessageInputProps) {
}}
onKeyDown={handleKeyDown}
onContextMenu={handleInputContextMenu}
className="w-full bg-transparent border-none focus:ring-0 text-on-surface placeholder-on-surface-variant/30 text-[15px] leading-relaxed resize-none max-h-[150px] custom-scrollbar scroll-smooth outline-none py-1"
placeholder={attachments.length > 0 ? t('addCaption') : t('message')}
rows={1}
className="w-full resize-none bg-transparent text-[15px] text-white placeholder-zinc-500 leading-[22px] py-[10px] px-2 border-none focus:ring-0 max-h-[150px] outline-none"
/>
</div>
{/* Emoji */}
<div className="relative mb-0 flex-shrink-0 self-center">
{/* Action Buttons */}
<div className="flex items-center gap-1 self-center pb-2 flex-shrink-0">
<button
onClick={() => setShowEmoji(!showEmoji)}
className="p-2 rounded-full text-zinc-400 hover:text-accent hover:bg-surface-hover transition-colors"

217
stitch/_1/code.html Normal file
View File

@@ -0,0 +1,217 @@
<!DOCTYPE html>
<html class="dark" lang="ru"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"inverse-on-surface": "#313030",
"surface-container": "#201f1f",
"on-secondary": "#2f3033",
"tertiary-fixed-dim": "#53e16f",
"error": "#ffb4ab",
"primary-container": "#3096e5",
"secondary-container": "#48494c",
"surface-dim": "#131313",
"error-container": "#93000a",
"inverse-primary": "#00629e",
"inverse-surface": "#e5e2e1",
"on-tertiary-fixed": "#002107",
"secondary-fixed": "#e3e2e6",
"on-tertiary": "#003911",
"on-secondary-fixed": "#1a1b1e",
"surface-container-low": "#1c1b1b",
"on-primary-fixed-variant": "#004a79",
"on-surface": "#e5e2e1",
"on-secondary-container": "#b9b8bc",
"tertiary-fixed": "#72fe88",
"secondary-fixed-dim": "#c7c6ca",
"primary-fixed-dim": "#9acbff",
"on-error": "#690005",
"surface-container-high": "#2a2a2a",
"on-primary-fixed": "#001d34",
"on-primary-container": "#002c4a",
"on-primary": "#003355",
"surface-container-lowest": "#0e0e0e",
"on-surface-variant": "#c1c6d7",
"surface-variant": "#353534",
"secondary": "#c7c6ca",
"background": "#131313",
"surface-container-highest": "#353534",
"on-tertiary-fixed-variant": "#00531c",
"surface": "#131313",
"tertiary": "#53e16f",
"on-error-container": "#ffdad6",
"on-tertiary-container": "#00320e",
"primary-fixed": "#cfe5ff",
"outline": "#8b90a0",
"surface-bright": "#393939",
"on-background": "#e5e2e1",
"primary": "#9acbff",
"outline-variant": "#414755",
"surface-tint": "#9acbff",
"tertiary-container": "#00a741",
"on-secondary-fixed-variant": "#46474a"
},
fontFamily: {
"headline": ["Inter"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.custom-scrollbar::-webkit-scrollbar { width: 4px; height: 4px; }
.custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
.custom-scrollbar::-webkit-scrollbar-thumb { background: #414755; border-radius: 10px; }
.glass-effect { backdrop-filter: blur(20px); background: rgba(19, 19, 19, 0.6); }
.slide-on-ice { transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); }
.knot-shadow { shadow-[0_40px_40px_rgba(255,255,255,0.05)]; }
</style>
</head>
<body class="bg-background text-on-surface font-body selection:bg-primary/30 overflow-hidden">
<!-- Top Navigation Anchor (Suppressing SideNav for focused task) -->
<header class="fixed top-0 right-0 left-0 z-50 h-16 flex items-center justify-between px-8 glass-effect tonal-transition-no-border">
<div class="flex items-center gap-4">
<span class="text-2xl font-black text-primary tracking-tighter">Knot</span>
<div class="h-6 w-px bg-outline-variant/30"></div>
<div class="flex items-center gap-2 bg-primary/10 px-3 py-1 rounded-full border border-primary/20">
<span class="material-symbols-outlined text-primary text-sm" style="font-variation-settings: 'FILL' 1;">screen_share</span>
<span class="text-xs font-bold text-primary tracking-wide">Демонстрация экрана</span>
</div>
</div>
<div class="flex items-center gap-4">
<div class="flex -space-x-2">
<img class="w-8 h-8 rounded-full border-2 border-surface shadow-lg" data-alt="close-up portrait of a woman with curly hair in a professional studio setting with soft lighting" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCQOYXDTe873WFSTuBViod2N2vyMKu0a8LywarkXzYhwBYLksc3-vOMZ0Qu3tenOZLlFVmee9sZ5zxMBB2LAgPcFBy7VKrJaAC1MlClaICn_6udnRhV4igPWZrY2r2ajd9cViqSE5xakaD1VWdRpzvcG6SNrDdS7ol6GZDVsurtHzG3_dfUHSCZfrony_bb79Hlr_mKZVvw2hOYznufZVYtCJeWPYEo4EssjffjNI2eU9_G_JNkQ6yefEYV6UVK5OKHBl6uQq6qpmI"/>
<img class="w-8 h-8 rounded-full border-2 border-surface shadow-lg" data-alt="headshot of a man with a beard smiling against a neutral gray background in high resolution" src="https://lh3.googleusercontent.com/aida-public/AB6AXuD3nyMH20a9qYWETGg6HM0rCl2oXq157Bq770FFfIhYvJV7bK7lCOA6FS19bKuxey7Sij6zUndXAvb059VwJ8fPSJoNwTAZmBXS_TcNSQ59bWHyYA9U-UZ6Y7Yp2GE787b2_GpOSslXChM9I7RNzKB646rp4YD6Qs7TgYX-yAsljMvEODUGLXGhLbC_cq0CUCQPX0-QcxPn52wzcw3Gsk4vZPBL-VCWesLxp0SWO7UHwHlh9HQrwk95luPEFXFez__19BObhgGJp9c"/>
<div class="w-8 h-8 rounded-full bg-surface-container-highest border-2 border-surface flex items-center justify-center text-[10px] font-bold">+12</div>
</div>
<button class="material-symbols-outlined text-on-surface-variant hover:text-white slide-on-ice">more_vert</button>
</div>
</header>
<main class="relative h-screen w-screen flex flex-row pt-16 pb-24">
<!-- Main Area: Shared Screen -->
<section class="flex-grow relative overflow-hidden bg-surface-container-lowest m-4 rounded-3xl group">
<img class="w-full h-full object-cover" data-alt="ultra high definition screenshot of a complex data analytics dashboard with colorful charts and graphs on a dark background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuD3IK-bAccF3ye36q_HNXWwuM5iEPEQnqZAP63xVytYVdgxEda-KpaHqKlznDK1Rsk3Uv-ORG89Wi9kSaI9quqMHR7Vvpiuoi-3AclxiKhBlPAGmeIuk6NMcIVWHVW2xz25DxZufv2qhANmwTITjs6nDcpMWqpUd5rdo5UMJGuYiL-WzKLKGDOAmLbxUv1J87mTniG3RVqz-5w_9BK5p531xdWXlM6kyC0j2NnKK04dV1u2LbyfGRqQZ7O0NQLAjfwikvJhet0BnUc"/>
<!-- Speaker Overlay (PIP) -->
<div class="absolute top-6 right-6 w-48 h-64 rounded-2xl overflow-hidden shadow-2xl border border-white/10 glass-effect">
<img class="w-full h-full object-cover" data-alt="close-up of a young man with glasses working on a computer in a modern home office with warm ambient lighting" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAnnjs23HXR1h9kfp47pIQSkRHqBdoEmcHi5SAZhrEvxGz-BNQCP23QW3xklSw_OTGSTesEQmd_N2ag7-NlebVieB0p1q5mS9SKcSBDjLqvDW9Trc7WBT9zlyOekMm5wN2jbqRzwmXMqgshr1JylRtVWq8lstaCW5Z__92Z87OYXh-YBaIQrxHr1-0ik5oLjRKYWK7LlX7E6cnAKR8cpWymURSFhakJKbFt0c6bx8aYWAz9rEAXxG74XJNLbj84VzhSpmsKoE6IBSU"/>
<div class="absolute bottom-2 left-2 bg-black/40 px-2 py-0.5 rounded-lg text-[10px] backdrop-blur-md">Алексей (Вы)</div>
</div>
<!-- Bottom Participant Carousel -->
<div class="absolute bottom-6 left-1/2 -translate-x-1/2 w-[90%] flex gap-4 overflow-x-auto custom-scrollbar py-2">
<div class="flex-shrink-0 w-40 h-28 rounded-xl overflow-hidden border-2 border-transparent hover:border-primary/50 slide-on-ice relative">
<img class="w-full h-full object-cover" data-alt="portrait of a woman laughing during a video call in a bright airy living room" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCJeI0aiu0U6y55kNUPb0tZckdLqwz_QHL2_7KgjW_PJioO3i4aYq7_seK5drya6JXL3-Z-51Cc-Cy7TCberW1u03lcdj4cnycJ-nxQBcdMpUOV7fFS6yoMJyC6G40blkW7SbzwbufoB0LYTUPtRH7PB1u_Sa58D3_XfLcfxJ0QgYBeeh8sGbhdzB5fI65TyIhDaYPkq0kQVAvnSdOH-oi46mjBc8gqBVqftO46r455P1WRp3jHUBJXwFwnsRfq3hcIH2dO9nv7z28"/>
<div class="absolute bottom-2 left-2 text-[10px] font-medium bg-black/40 px-2 py-0.5 rounded backdrop-blur-md text-white">Мария</div>
</div>
<div class="flex-shrink-0 w-40 h-28 rounded-xl overflow-hidden border-2 border-primary slide-on-ice relative">
<img class="w-full h-full object-cover" data-alt="professional man in a dark suit speaking during a conference call in a corporate boardroom" src="https://lh3.googleusercontent.com/aida-public/AB6AXuA2WOavNmTknvL8z4wCh9WOSUKm6wgstUrPksi9C0gu5HuEJya4YOHbyYNGl9_1zkZICCLUs6xrTsxEAtAyODT_ubsV1g9gqSFZwHMxEaUlFErsyihLozMPD2rR9ukuKSN8n3XNAZMtICi-VKwosine_V-hkiNftKIgAABDnCtzVCJ62BbuCS40oxNAWQJ4yDLMTnWgIMPqL3IfDWOOzRj45pFzIfAFMVzN8tXS5xUNizJELBJV8-QeZAid8cU1LO98o3d6ESnr9NY"/>
<div class="absolute top-2 right-2 flex gap-1">
<span class="material-symbols-outlined text-primary text-xs" style="font-variation-settings: 'FILL' 1;">mic</span>
</div>
<div class="absolute bottom-2 left-2 text-[10px] font-medium bg-black/40 px-2 py-0.5 rounded backdrop-blur-md text-white">Дмитрий • Говорит</div>
</div>
<div class="flex-shrink-0 w-40 h-28 rounded-xl overflow-hidden border-2 border-transparent relative">
<div class="w-full h-full bg-surface-container-high flex items-center justify-center">
<span class="material-symbols-outlined text-on-surface-variant text-4xl">person</span>
</div>
<div class="absolute bottom-2 left-2 text-[10px] font-medium bg-black/40 px-2 py-0.5 rounded backdrop-blur-md text-white">Елена (камера выкл.)</div>
</div>
<div class="flex-shrink-0 w-40 h-28 rounded-xl overflow-hidden border-2 border-transparent relative">
<img class="w-full h-full object-cover" data-alt="middle aged man with glasses listening intently during a remote meeting" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAzchHleDnFE6W2f1wDeb-0VonPovw_7pPqj_0xcps0qvuH6AsCaGTcCVBs8tmM2-VCk-rOJvlvSprmX4cHXo-pxNTbwIYrumDCZgKiA9TQVrpZ3MRUWRhQkNwcOnzB7JdFwAiUwMvYAg23XT2AMPhcVphsFp5hshZMqurB3ibeS5SD4AZdGBQrY2iSwuap3fwdIt4Yijlv3S521W-CQrw-CvWhDfdK68l33ezycotFOsnUihuMvq2VRs6QgGWHUT4vPBZgD2oeeeM"/>
<div class="absolute bottom-2 left-2 text-[10px] font-medium bg-black/40 px-2 py-0.5 rounded backdrop-blur-md text-white">Виктор</div>
</div>
<div class="flex-shrink-0 w-40 h-28 rounded-xl overflow-hidden border-2 border-transparent relative">
<img class="w-full h-full object-cover" data-alt="young businesswoman smiling during a video presentation" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCtseBhAbV_1DAS-i2THnsV9lK8TT23CFeIt2E8pdFvNWPgd2RbGERr7LOYcHL_Wk97lW9Xn-ROM1VPXZ_rkvpig8zEJ1QQUFlzWlhSZ1rIA2tq0f2-foxB55ZbkcjvMzYVrhqJS6TtL6oUrW7DkLjLFdJoNz5km6caP9h613A9BbS7187zOX9kC8SY9YMcw2NqAsIYbyd-TlbeaJIJ4xRNaekW56CRYhgcoYOoqPjWYiM4gQCjYgmz7Qatoek_AMy50ki80Yk4Ing"/>
<div class="absolute bottom-2 left-2 text-[10px] font-medium bg-black/40 px-2 py-0.5 rounded backdrop-blur-md text-white">Ольга</div>
</div>
</div>
</section>
<!-- Side Panel: Chat -->
<aside class="w-96 flex flex-col bg-surface-container-low mr-4 my-4 rounded-3xl overflow-hidden border-none">
<div class="p-6 flex items-center justify-between border-b border-outline-variant/10">
<h2 class="headline-sm font-bold text-white tracking-tight">Чат встречи</h2>
<button class="material-symbols-outlined text-on-surface-variant hover:text-white slide-on-ice">close</button>
</div>
<div class="flex-grow overflow-y-auto p-6 space-y-6 custom-scrollbar">
<!-- Chat Message Incoming -->
<div class="flex flex-col gap-1 items-start max-w-[85%]">
<div class="flex items-center gap-2 mb-1">
<span class="text-[10px] font-bold text-primary">Дмитрий</span>
<span class="text-[9px] text-on-surface-variant">14:20</span>
</div>
<div class="bg-secondary-container text-on-secondary-container p-3 rounded-2xl rounded-tl-sm text-sm leading-relaxed">
Коллеги, я вывел текущие показатели на экран. Видно всем?
</div>
</div>
<!-- Chat Message Outgoing (Me) -->
<div class="flex flex-col gap-1 items-end ml-auto max-w-[85%]">
<div class="flex items-center gap-2 mb-1">
<span class="text-[9px] text-on-surface-variant">14:21</span>
<span class="text-[10px] font-bold text-tertiary">Вы</span>
</div>
<div class="bg-primary-container text-on-primary-container p-3 rounded-2xl rounded-tr-sm text-sm leading-relaxed">
Да, все отлично видно. Цифры по второму кварталу впечатляют.
</div>
</div>
<!-- Chat Message Incoming -->
<div class="flex flex-col gap-1 items-start max-w-[85%]">
<div class="flex items-center gap-2 mb-1">
<span class="text-[10px] font-bold text-primary">Ольга</span>
<span class="text-[9px] text-on-surface-variant">14:22</span>
</div>
<div class="bg-secondary-container text-on-secondary-container p-3 rounded-2xl rounded-tl-sm text-sm leading-relaxed">
Можем ли мы подробнее разобрать сегмент B2B?
</div>
</div>
</div>
<!-- Chat Input -->
<div class="p-6 pt-0">
<div class="relative bg-surface-container-highest rounded-full px-4 py-3 flex items-center gap-2 group transition-all duration-300 focus-within:bg-surface-bright">
<input class="bg-transparent border-none focus:ring-0 text-sm flex-grow placeholder:text-on-surface-variant/50" placeholder="Напишите сообщение..." type="text"/>
<button class="material-symbols-outlined text-primary slide-on-ice hover:scale-110">send</button>
</div>
</div>
</aside>
</main>
<!-- Control Bar (Floating Bottom) -->
<div class="fixed bottom-8 left-1/2 -translate-x-1/2 z-50 flex items-center gap-4 bg-surface-container-high/80 backdrop-blur-2xl px-6 py-4 rounded-full shadow-2xl border border-white/5 slide-on-ice">
<!-- Mic -->
<button class="w-12 h-12 rounded-full flex items-center justify-center bg-surface-container-highest text-white hover:bg-surface-bright slide-on-ice group">
<span class="material-symbols-outlined group-active:scale-90 transition-transform">mic</span>
</button>
<!-- Camera -->
<button class="w-12 h-12 rounded-full flex items-center justify-center bg-surface-container-highest text-white hover:bg-surface-bright slide-on-ice group">
<span class="material-symbols-outlined group-active:scale-90 transition-transform">videocam</span>
</button>
<!-- Screen Share (Active State) -->
<button class="w-12 h-12 rounded-full flex items-center justify-center bg-primary text-on-primary-container shadow-lg shadow-primary/20 slide-on-ice group">
<span class="material-symbols-outlined group-active:scale-90 transition-transform" style="font-variation-settings: 'FILL' 1;">screen_share</span>
</button>
<!-- Divider -->
<div class="w-px h-8 bg-outline-variant/20 mx-2"></div>
<!-- Minimize -->
<button class="w-12 h-12 rounded-full flex items-center justify-center bg-surface-container-highest text-white hover:bg-surface-bright slide-on-ice group">
<span class="material-symbols-outlined group-active:scale-90 transition-transform">picture_in_picture_alt</span>
</button>
<!-- End Call (Red) -->
<button class="px-6 h-12 rounded-full flex items-center justify-center bg-error text-on-error hover:opacity-90 slide-on-ice group gap-2">
<span class="material-symbols-outlined group-active:scale-90 transition-transform" style="font-variation-settings: 'FILL' 1;">call_end</span>
<span class="text-sm font-bold uppercase tracking-widest">Завершить</span>
</button>
</div>
</body></html>

BIN
stitch/_1/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 KiB

305
stitch/_10/code.html Normal file
View File

@@ -0,0 +1,305 @@
<!DOCTYPE html>
<html class="dark" lang="ru"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;300;400;500;600;700;800;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"inverse-on-surface": "#313030",
"surface-container": "#201f1f",
"on-secondary": "#2f3033",
"tertiary-fixed-dim": "#53e16f",
"error": "#ffb4ab",
"primary-container": "#3096e5",
"secondary-container": "#48494c",
"surface-dim": "#131313",
"error-container": "#93000a",
"inverse-primary": "#00629e",
"inverse-surface": "#e5e2e1",
"on-tertiary-fixed": "#002107",
"secondary-fixed": "#e3e2e6",
"on-tertiary": "#003911",
"on-secondary-fixed": "#1a1b1e",
"surface-container-low": "#1c1b1b",
"on-primary-fixed-variant": "#004a79",
"on-surface": "#e5e2e1",
"on-secondary-container": "#b9b8bc",
"tertiary-fixed": "#72fe88",
"secondary-fixed-dim": "#c7c6ca",
"primary-fixed-dim": "#9acbff",
"on-error": "#690005",
"surface-container-high": "#2a2a2a",
"on-primary-fixed": "#001d34",
"on-primary-container": "#002c4a",
"on-primary": "#003355",
"surface-container-lowest": "#0e0e0e",
"on-surface-variant": "#c1c6d7",
"surface-variant": "#353534",
"secondary": "#c7c6ca",
"background": "#131313",
"surface-container-highest": "#353534",
"on-tertiary-fixed-variant": "#00531c",
"surface": "#131313",
"tertiary": "#53e16f",
"on-error-container": "#ffdad6",
"on-tertiary-container": "#00320e",
"primary-fixed": "#cfe5ff",
"outline": "#8b90a0",
"surface-bright": "#393939",
"on-background": "#e5e2e1",
"primary": "#9acbff",
"outline-variant": "#414755",
"surface-tint": "#9acbff",
"tertiary-container": "#00a741",
"on-secondary-fixed-variant": "#46474a"
},
fontFamily: {
"headline": ["Inter"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.contrast-bg-shift-no-border {
border: none !important;
}
.tonal-transition-no-border {
border: none !important;
}
.slide-on-ice {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
</style>
</head>
<body class="bg-background text-on-background font-body antialiased selection:bg-primary-container/30">
<!-- SideNavBar Shell -->
<aside class="fixed left-0 top-0 h-full z-40 flex flex-col border-none bg-[#201F1F] dark:bg-[#201F1F] h-screen w-20 items-center py-6 font-['Inter'] text-sm leading-[1.6] antialiased">
<div class="mb-10 text-2xl font-black text-[#9ACBFF] tracking-tighter">Knot</div>
<nav class="flex flex-col gap-6 w-full px-2">
<button class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1)">
<span class="material-symbols-outlined mb-1">chat</span>
<span class="text-[10px]">Чаты</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1)">
<span class="material-symbols-outlined mb-1">call</span>
<span class="text-[10px]">Звонки</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1)">
<span class="material-symbols-outlined mb-1">person</span>
<span class="text-[10px]">Контакты</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1)">
<span class="material-symbols-outlined mb-1">archive</span>
<span class="text-[10px]">Архив</span>
</button>
<button class="flex flex-col items-center justify-center bg-[#3096E5]/20 text-[#9ACBFF] rounded-xl p-3 transition-all duration-300 scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1)">
<span class="material-symbols-outlined mb-1">settings</span>
<span class="text-[10px]">Настройки</span>
</button>
</nav>
<div class="mt-auto flex flex-col items-center gap-6">
<button class="text-on-surface-variant hover:text-primary transition-colors">
<span class="material-symbols-outlined">dark_mode</span>
</button>
<img alt="User profile knot anchor" class="w-10 h-10 rounded-full outline outline-2 outline-variant/10" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAlJaSKuQXbwDLkELRY0G419NbhzCCWvoWX0rli-EoWYgxdrmYV4xcIC0pGKvhvFg_QNd2xJBMI_scy59vAAOpGF7RZ6ZP2cwOmGSs-zbL6x4YoT5Fq2ml1iuI0Td83fTJSZp2p0AYSjGwbdDE0OaxAIGhbXbQcFJkYsq8P3sdNaEBdysjA39BRGEaMS7kKzfL-1K9q21BbahowAXmRQcbvwNAbkV2QKy4HAYJa5U1rUBpjzS8SJWge6EfXA9ENkAqSeEkJwQQxiig"/>
</div>
</aside>
<!-- Main Content Canvas -->
<main class="ml-20 min-h-screen bg-surface-container-lowest">
<!-- Header -->
<header class="fixed top-0 right-0 left-20 z-30 flex items-center justify-between px-8 bg-[#131313]/60 backdrop-blur-xl docked full-width h-16 tonal-transition-no-border shadow-[0_40px_40px_rgba(255,255,255,0.05)]">
<div class="flex items-center gap-4">
<h1 class="headline-sm font-bold text-[#FFFFFF] tracking-tight">Настройки профиля</h1>
</div>
<div class="flex items-center gap-4">
<button class="p-2 text-[#939090] hover:opacity-80 transition-opacity">
<span class="material-symbols-outlined">search</span>
</button>
<button class="p-2 text-[#939090] hover:opacity-80 transition-opacity">
<span class="material-symbols-outlined">more_vert</span>
</button>
</div>
</header>
<div class="pt-24 pb-12 px-8 max-w-6xl mx-auto space-y-8">
<!-- Hero Profile Section: Bento Style -->
<section class="grid grid-cols-1 lg:grid-cols-12 gap-6">
<div class="lg:col-span-8 bg-surface-container rounded-[2rem] p-8 flex flex-col md:flex-row items-center md:items-start gap-8 relative overflow-hidden">
<!-- Background Accent -->
<div class="absolute -top-24 -right-24 w-64 h-64 bg-primary/10 blur-[80px] rounded-full"></div>
<div class="relative group">
<img class="w-32 h-32 md:w-40 md:h-40 rounded-full object-cover border-4 border-surface-container-high shadow-xl transition-transform duration-500 group-hover:scale-105" data-alt="Modern cinematic portrait of a creative professional with short dark hair and stylish glasses, soft studio lighting on dark background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDGZ8-0i3dYZfuHmJsgDu1hQKDtBu_SH6Q7FKmWop9nAc1iplh4qQCUadWuFGPIv1xGEUjEtWt8Tt0bs-2JwFU4ptCuAgGPZibOmomHjXlBWX-35d31KwHeEOrcffLHqtPkXapcFWT_ILpQNVRnkm0IuanRk9ja0J7uRATmfOp6iColHAw-MRc7j9CnHGzWx0Gp-5Xshsf9jREBPERoXaJrZWuG_BWv5fqD3CSucUypP5-UlV3WbtK01adCo90g52xgLOG5sNrtFLQ"/>
<button class="absolute bottom-1 right-1 bg-primary text-on-primary p-2 rounded-full shadow-lg hover:scale-110 transition-transform">
<span class="material-symbols-outlined text-sm">edit</span>
</button>
</div>
<div class="flex-1 text-center md:text-left space-y-2">
<div class="flex flex-col md:flex-row md:items-center gap-3">
<h2 class="text-3xl font-extrabold tracking-tight">Александр Волков</h2>
<span class="px-3 py-1 bg-tertiary-container/20 text-tertiary text-xs font-bold rounded-full w-fit mx-auto md:mx-0">В СЕТИ</span>
</div>
<p class="text-on-surface-variant text-lg">Product Designer • Кофе и интерфейсы</p>
<div class="pt-4 flex flex-wrap justify-center md:justify-start gap-3">
<button class="px-6 py-2.5 bg-gradient-to-br from-primary to-primary-container text-on-primary font-bold rounded-xl shadow-lg hover:brightness-110 active:scale-95 transition-all">
Редактировать профиль
</button>
<button class="px-6 py-2.5 bg-surface-container-high text-on-surface font-semibold rounded-xl hover:bg-surface-bright transition-colors">
Поделиться
</button>
</div>
</div>
</div>
<!-- Info Card -->
<div class="lg:col-span-4 bg-surface-container rounded-[2rem] p-8 space-y-6">
<h3 class="text-on-surface-variant font-bold text-xs uppercase tracking-widest">Информация</h3>
<div class="space-y-4">
<div class="flex items-center gap-4 group">
<div class="w-10 h-10 rounded-xl bg-surface-container-high flex items-center justify-center text-primary group-hover:bg-primary group-hover:text-on-primary transition-colors">
<span class="material-symbols-outlined">call</span>
</div>
<div>
<p class="text-sm font-bold text-on-surface">+7 900 123-45-67</p>
<p class="text-[10px] text-on-surface-variant">Мобильный</p>
</div>
</div>
<div class="flex items-center gap-4 group">
<div class="w-10 h-10 rounded-xl bg-surface-container-high flex items-center justify-center text-primary group-hover:bg-primary group-hover:text-on-primary transition-colors">
<span class="material-symbols-outlined">alternate_email</span>
</div>
<div>
<p class="text-sm font-bold text-on-surface">@volkov_design</p>
<p class="text-[10px] text-on-surface-variant">Имя пользователя</p>
</div>
</div>
<div class="flex items-center gap-4 group">
<div class="w-10 h-10 rounded-xl bg-surface-container-high flex items-center justify-center text-primary group-hover:bg-primary group-hover:text-on-primary transition-colors">
<span class="material-symbols-outlined">info</span>
</div>
<div>
<p class="text-sm font-bold text-on-surface">Создаю будущее в Knot</p>
<p class="text-[10px] text-on-surface-variant">О себе</p>
</div>
</div>
</div>
</div>
</section>
<!-- Grid Layout for Settings and Content -->
<section class="grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- Settings Column -->
<div class="lg:col-span-4 space-y-6">
<div class="bg-surface-container rounded-[2rem] p-6 space-y-2">
<h3 class="px-2 pb-4 text-on-surface-variant font-bold text-xs uppercase tracking-widest">Настройки</h3>
<button class="w-full flex items-center justify-between p-3 rounded-2xl hover:bg-surface-container-high transition-colors group">
<div class="flex items-center gap-4">
<span class="material-symbols-outlined text-on-surface-variant group-hover:text-primary transition-colors">lock</span>
<span class="font-medium">Конфиденциальность</span>
</div>
<span class="material-symbols-outlined text-on-surface-variant">chevron_right</span>
</button>
<button class="w-full flex items-center justify-between p-3 rounded-2xl hover:bg-surface-container-high transition-colors group">
<div class="flex items-center gap-4">
<span class="material-symbols-outlined text-on-surface-variant group-hover:text-primary transition-colors">notifications</span>
<span class="font-medium">Уведомления</span>
</div>
<span class="material-symbols-outlined text-on-surface-variant">chevron_right</span>
</button>
<button class="w-full flex items-center justify-between p-3 rounded-2xl hover:bg-surface-container-high transition-colors group">
<div class="flex items-center gap-4">
<span class="material-symbols-outlined text-on-surface-variant group-hover:text-primary transition-colors">database</span>
<span class="font-medium">Данные и память</span>
</div>
<span class="material-symbols-outlined text-on-surface-variant">chevron_right</span>
</button>
<button class="w-full flex items-center justify-between p-3 rounded-2xl hover:bg-surface-container-high transition-colors group">
<div class="flex items-center gap-4">
<span class="material-symbols-outlined text-on-surface-variant group-hover:text-primary transition-colors">language</span>
<span class="font-medium">Язык</span>
</div>
<div class="flex items-center gap-2">
<span class="text-xs text-on-surface-variant">Русский</span>
<span class="material-symbols-outlined text-on-surface-variant">chevron_right</span>
</div>
</button>
</div>
<!-- Shared Groups -->
<div class="bg-surface-container rounded-[2rem] p-6 space-y-6">
<h3 class="text-on-surface-variant font-bold text-xs uppercase tracking-widest">Общие группы</h3>
<div class="space-y-4">
<div class="flex items-center gap-4 p-2 rounded-2xl hover:bg-surface-container-high transition-colors cursor-pointer group">
<img class="w-10 h-10 rounded-full" data-alt="vibrant abstract startup team logo with neon highlights and geometric shapes" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBRX7TJtpA_07qcPYrm4PEEQBxcaZ2-S3XH68m9xHAo55Npm3TfJMLN_cOVawrj032r85PgFovGE5oijDukO10xYWP3e_Unuvv1f4pdnSpgpckzcY3I2KVNOK2ImhxkaNoP89863eOfELlazHMWfHQPIVD6BvK2ANmWNpN4Iin5mX9zcVwSDH1nkuskjl6W4UvkUnCZ-Bs7kIW2J1FqB-DNf9QvFBDfky560X69mPYi2BDkJHpGhLvemE2n6w-GZupGxrViwzuhYok"/>
<div class="flex-1">
<p class="text-sm font-bold">Knot Design Team</p>
<p class="text-[10px] text-on-surface-variant">42 участника</p>
</div>
</div>
<div class="flex items-center gap-4 p-2 rounded-2xl hover:bg-surface-container-high transition-colors cursor-pointer group">
<img class="w-10 h-10 rounded-full" data-alt="minimalist aesthetic coffee shop branding with earthy tones and elegant typography" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDovZ3Y5-57xWYNlYPsBsklIlfHdzKsRxlJq7DpcrhlVaqSoB4XILLNKpE7lo4OHMilxSbNR_rw5Wq3_6Mc_Nq2Jv8sfIkEDzI023N2iN4NxJjA4S3NMRqvxdHU5tAIBzIjG3a7QUyVR7WNe2BxqyikaPTQg-av0Wc6fREkoj-CQ4_x7j_BzcxI04eA1htXB2ySKP7y4xtC-V1cLzG-burfNDCQo0IqBTEAwMaRbKvGCZT-Odf-kFWHr_hVvNWp0hJLoDN_pco0q7A"/>
<div class="flex-1">
<p class="text-sm font-bold">Coffee &amp; Pixels</p>
<p class="text-[10px] text-on-surface-variant">128 участников</p>
</div>
</div>
</div>
<button class="w-full py-3 text-primary text-sm font-bold hover:underline">Показать все (8)</button>
</div>
</div>
<!-- Media/Files Tabs Column -->
<div class="lg:col-span-8 space-y-6">
<div class="bg-surface-container rounded-[2rem] p-8 min-h-[500px] flex flex-col">
<nav class="flex gap-8 border-b border-outline-variant/10 mb-8 overflow-x-auto no-scrollbar">
<button class="pb-4 border-b-2 border-primary text-primary font-bold text-sm whitespace-nowrap">Медиа</button>
<button class="pb-4 text-on-surface-variant hover:text-on-surface font-medium text-sm whitespace-nowrap transition-colors">Файлы</button>
<button class="pb-4 text-on-surface-variant hover:text-on-surface font-medium text-sm whitespace-nowrap transition-colors">Ссылки</button>
<button class="pb-4 text-on-surface-variant hover:text-on-surface font-medium text-sm whitespace-nowrap transition-colors">Голосовые</button>
</nav>
<!-- Media Grid (Asymmetric) -->
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4 flex-1">
<div class="aspect-square bg-surface-container-high rounded-2xl overflow-hidden group cursor-pointer">
<img class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110" data-alt="high contrast architectural photo of modern skyscraper geometry against deep blue sky" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDdOxqzyl8aW7sWpufyMn78TvwBDolP6M2ehGCkj5qdHJERP4NHccOLqMVWMOVeDDHVaCiPzpjPa4PHRsRg11223anpvAnXs3Aj4n_AdsYBqAlFJk1v-B5oCEOWfjkl_Zws0evvCmLKp2lQj7CSnxiAH8Q8O0sRccSdSf7Fhr3zAeKry3ZdmL-6fi3dtE4ZZ-tnTao8rGCYKhmHNaDFyRkluyhwrU0G7aMyqml9ZN2bPBUqfKjNVv-l7UQGivZPgUU61pjUZzToxpA"/>
</div>
<div class="aspect-square bg-surface-container-high rounded-2xl overflow-hidden group cursor-pointer">
<img class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110" data-alt="soft focus close up of pink cherry blossom petals with morning dew in gentle natural light" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDrgHA0CO99sFYILC8um0PPygbkY81us1wSIJDtSbVlgHu6WPFXJxyDPHEDWFvNomMlwXUAmwjfOFKp5RhKpn1GAhch_D0ytQD3NzLSCXYAdsFeboQ1EuQu9lKUqGMePvMF5JwTKN8qui5GGps1SWQ_bT08fj-nfb2ef9BFxBpjxtzt4tOR8NG1gLXbLE6cw3yzxkwbcaSpZMVwTl02Dn_R6HzxXQ7Rgitzr34Nef7LDmTrjDn6UguJv4yZ6hGAj338BKZFvHPKkYc"/>
</div>
<div class="aspect-square bg-surface-container-high rounded-2xl overflow-hidden group cursor-pointer relative">
<img class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110" data-alt="futuristic dark interior with glowing blue neon lines and polished concrete floors" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBfWWylzZ8tD-3t9HMIe12SdJi0bZUHUGwFLJRGTNBbUcPRw8iR44PqAjtAJUzvC39-NTeqoje4-v5tsVjv6ZjOSvYBYsOpkO463D6MNnNdvsUNukSVjV3MJkxtpw3DsPREOAGaxJFqBzqrYQFhR_f37xVlvzmsFwXVO24rOJPMx2PKQm1BM-hJwEEfDPGxLDBSWCCPXHSdsoOFpSzTQX1AKQNLxBn6EYQxgIatxm5WZ0oZzD92b0wSi4TbIR_Pui9vPqy0uTba0ZE"/>
<div class="absolute inset-0 bg-black/40 flex items-center justify-center">
<span class="material-symbols-outlined text-white text-3xl">play_circle</span>
</div>
</div>
<div class="aspect-square bg-surface-container-high rounded-2xl overflow-hidden group cursor-pointer">
<img class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110" data-alt="abstract flowing liquid metal texture with holographic iridescent colors and deep shadows" src="https://lh3.googleusercontent.com/aida-public/AB6AXuARjdQ2uhAK5itrlQLsf_tmx-QzJ-VjJNGhzNMWKeqxFpENULfntf2Dg_H3zQ7QXUQ95Y8IJGO0IlkzT1GMK6393He26dxkYwS-s_zse4zp3TpDgsc20g7qrUWCefsebWRZlsZLkRGu7g8IEA0WHq1Jh4Z7fl5hXO80P1ggU0ruGeYPs5CcWIDmehjZ-TIcwEeSvpMnJSV_M-iVXsDJmSyK40gcKc1P3epdHnnQR73oHv9IX2x-ZMWK3xdcQ2jscfelBy7VMxjBBo8"/>
</div>
<div class="aspect-square bg-surface-container-high rounded-2xl overflow-hidden group cursor-pointer">
<img class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110" data-alt="moody urban street photography at night with rain reflections and yellow city lights" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAi68xAqDFsknkJZ3ajeDyA9mN_ElfhpXWwIn1NZkXBcFBq66W83BPaGvacHQO280JtF-p4BkAlsv-OZjreVYE7qEQgqomgHhcQ9xoeKJk0dyWYwh2_Pc1EW6EF7sCJIIgc1PPPdH_Z-omtBZkF7qsnDBnJaRuaGoe-is-meZX7N21nWvAuMeXDLrw_agQ0f7WFRf6crayzYl3cqkcWLV4bf0QhtSXoSTPT8J3hSX9S1XmJuDayOEGN7ljrH0WGqPRmgAh7GA-T0ls"/>
</div>
<div class="aspect-square bg-surface-container-high rounded-2xl flex items-center justify-center group cursor-pointer hover:bg-surface-bright transition-colors">
<span class="material-symbols-outlined text-primary text-4xl">add</span>
</div>
</div>
<!-- Empty State Example (Hidden by default but ready) -->
<div class="hidden flex-col items-center justify-center py-20 text-center space-y-4">
<span class="material-symbols-outlined text-6xl text-on-surface-variant/20">photo_library</span>
<h4 class="text-xl font-bold text-on-surface-variant">Нет медиафайлов</h4>
<p class="text-on-surface-variant max-w-xs">Здесь появятся фотографии и видео, которыми вы обменивались.</p>
</div>
</div>
</div>
</section>
</div>
</main>
<!-- Contextual FAB (Suppressed on settings/profile as per instructions, but placeholder logic if needed elsewhere) -->
</body></html>

BIN
stitch/_10/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 KiB

369
stitch/_2/code.html Normal file
View File

@@ -0,0 +1,369 @@
<!DOCTYPE html>
<html class="dark" lang="ru"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Knot - Групповой чат</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"inverse-on-surface": "#313030",
"surface-container": "#201f1f",
"on-secondary": "#2f3033",
"tertiary-fixed-dim": "#53e16f",
"error": "#ffb4ab",
"primary-container": "#3096e5",
"secondary-container": "#48494c",
"surface-dim": "#131313",
"error-container": "#93000a",
"inverse-primary": "#00629e",
"inverse-surface": "#e5e2e1",
"on-tertiary-fixed": "#002107",
"secondary-fixed": "#e3e2e6",
"on-tertiary": "#003911",
"on-secondary-fixed": "#1a1b1e",
"surface-container-low": "#1c1b1b",
"on-primary-fixed-variant": "#004a79",
"on-surface": "#e5e2e1",
"on-secondary-container": "#b9b8bc",
"tertiary-fixed": "#72fe88",
"secondary-fixed-dim": "#c7c6ca",
"primary-fixed-dim": "#9acbff",
"on-error": "#690005",
"surface-container-high": "#2a2a2a",
"on-primary-fixed": "#001d34",
"on-primary-container": "#002c4a",
"on-primary": "#003355",
"surface-container-lowest": "#0e0e0e",
"on-surface-variant": "#c1c6d7",
"surface-variant": "#353534",
"secondary": "#c7c6ca",
"background": "#131313",
"surface-container-highest": "#353534",
"on-tertiary-fixed-variant": "#00531c",
"surface": "#131313",
"tertiary": "#53e16f",
"on-error-container": "#ffdad6",
"on-tertiary-container": "#00320e",
"primary-fixed": "#cfe5ff",
"outline": "#8b90a0",
"surface-bright": "#393939",
"on-background": "#e5e2e1",
"primary": "#9acbff",
"outline-variant": "#414755",
"surface-tint": "#9acbff",
"tertiary-container": "#00a741",
"on-secondary-fixed-variant": "#46474a"
},
fontFamily: {
"headline": ["Inter"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.contrast-bg-shift-no-border {
background-color: #201F1F;
}
.tonal-transition-no-border {
background-color: rgba(19, 19, 19, 0.6);
backdrop-filter: blur(20px);
}
.slide-on-ice {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
body {
background-color: #131313;
color: #e5e2e1;
font-family: 'Inter', sans-serif;
}
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #353534;
border-radius: 10px;
}
</style>
</head>
<body class="flex h-screen overflow-hidden">
<!-- SideNavBar (Authority: JSON & Design System) -->
<nav class="fixed left-0 top-0 h-full z-40 flex flex-col border-none bg-[#201F1F] dark:bg-[#201F1F] h-screen w-20 flex flex-col items-center py-6 font-['Inter'] text-sm leading-[1.6] antialiased">
<div class="mb-10">
<span class="text-2xl font-black text-[#9ACBFF] tracking-tighter">Knot</span>
</div>
<div class="flex flex-col gap-6 w-full px-2">
<!-- Чаты (Active) -->
<a class="flex flex-col items-center justify-center bg-[#3096E5]/20 text-[#9ACBFF] rounded-xl p-3 transition-all duration-300 scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1)" href="#">
<span class="material-symbols-outlined mb-1" data-icon="chat">chat</span>
<span class="text-[10px]">Чаты</span>
</a>
<!-- Звонки -->
<a class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1)" href="#">
<span class="material-symbols-outlined mb-1" data-icon="call">call</span>
<span class="text-[10px]">Звонки</span>
</a>
<!-- Контакты -->
<a class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1)" href="#">
<span class="material-symbols-outlined mb-1" data-icon="person">person</span>
<span class="text-[10px]">Контакты</span>
</a>
<!-- Архив -->
<a class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1)" href="#">
<span class="material-symbols-outlined mb-1" data-icon="archive">archive</span>
<span class="text-[10px]">Архив</span>
</a>
<!-- Настройки -->
<a class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1)" href="#">
<span class="material-symbols-outlined mb-1" data-icon="settings">settings</span>
<span class="text-[10px]">Настройки</span>
</a>
</div>
<div class="mt-auto">
<img alt="User profile knot anchor" class="w-10 h-10 rounded-full border-2 border-outline-variant/10" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAMItyxSRMCM8e1vaJvyyqzYsVBaAoyqSkEhSN0kmn3W7A7Rc_oCJSSmHVzT7IAgrsDQmkUwu2AiEyamkLCx7HVLCZImvyM62Xa3INKhRDAkqK8aV8W63ZuTDOLEI_MkQlpjxXq3CeHwUw1ZWQeFsANuie8SIaZnQPxI_E51PRjYziwvB1B6rknQ0gEt2-oK5EU9ewmHXLr00JtA4isMgFN4_lZQxY8akg2FG7h-RbFir-uNN72AYnA68OGoS0XwFdAPLx3iEn4U2A"/>
</div>
</nav>
<!-- Main Content Canvas -->
<main class="ml-20 flex-1 flex flex-col relative bg-surface-dim">
<!-- TopAppBar -->
<header class="fixed top-0 right-0 left-20 z-30 flex items-center justify-between px-8 bg-[#131313]/60 backdrop-blur-xl docked full-width top-0 h-16 tonal-transition-no-border shadow-[0_40px_40px_rgba(255,255,255,0.05)]">
<div class="flex items-center gap-4">
<div class="relative">
<img alt="Group Icon" class="w-10 h-10 rounded-full border-2 border-outline-variant/10" data-alt="vibrant abstract spaceship icon on a dark circular background with professional lighting" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAbWHWaMw9RAPyagQDKKctb1D0Vj0TUftqzZi7x9CBKq1ddTAz8xND0esDaQZt7-shmmJAObzGoETmq_kFN_fomI7_o6OXIE82N3KImQYZaiclcL2U20YOjGLHyIvFya2G2_vxWxcrCxbB_Ue3Tafsz1YWuG9ro7ZwLqrLiolDuzBOI8HUqg9rMtaye_xh6MtSyOHn9u8d-gZX4MXDs2PTdza2Mn-Jl5VaYuDstx4g_WzP5skvFpVfdow5DTamm8qoQnyen5dWJvnQ"/>
<div class="absolute bottom-0 right-0 w-3 h-3 bg-tertiary rounded-full border-2 border-surface"></div>
</div>
<div>
<h1 class="headline-sm font-bold text-[#FFFFFF] tracking-tight">Дизайн-команда Knot</h1>
<p class="text-[10px] text-on-surface-variant uppercase tracking-widest">128 участников • 42 онлайн</p>
</div>
</div>
<div class="flex items-center gap-6">
<button class="text-[#9ACBFF] hover:opacity-80 transition-opacity slide-on-ice">
<span class="material-symbols-outlined" data-icon="videocam">videocam</span>
</button>
<button class="text-[#9ACBFF] hover:opacity-80 transition-opacity slide-on-ice">
<span class="material-symbols-outlined" data-icon="call">call</span>
</button>
<button class="text-[#9ACBFF] hover:opacity-80 transition-opacity slide-on-ice">
<span class="material-symbols-outlined" data-icon="more_vert">more_vert</span>
</button>
</div>
</header>
<!-- Pinned Message Banner -->
<div class="mt-16 bg-surface-container-low px-8 py-2 flex items-center gap-3 border-l-4 border-primary shadow-sm z-20">
<span class="material-symbols-outlined text-primary text-sm" data-icon="push_pin">push_pin</span>
<div class="flex-1 overflow-hidden">
<p class="text-xs text-on-surface-variant font-medium truncate">Закреплённое сообщение: Дедлайн по редизайну — пятница, 18:00. Просьба всех обновить статусы.</p>
</div>
<button class="text-on-surface-variant hover:text-on-surface transition-colors">
<span class="material-symbols-outlined text-sm" data-icon="close">close</span>
</button>
</div>
<!-- Chat Area -->
<div class="flex-1 overflow-y-auto px-8 py-6 flex flex-col gap-6 custom-scrollbar">
<!-- System Message -->
<div class="flex justify-center">
<span class="bg-surface-container-highest/30 px-4 py-1 rounded-full text-[11px] text-on-surface-variant font-medium">Алексей Смирнов присоединился к группе по ссылке</span>
</div>
<!-- Incoming Message (User 1) -->
<div class="flex items-end gap-3 max-w-[80%] group">
<img alt="Avatar" class="w-8 h-8 rounded-full mb-1 border-2 border-outline-variant/10" data-alt="portrait of a creative man with glasses in a soft studio lighting with dark background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDV0m2KbS4UYoia-ARAro07qfE2D2rga-mp752lKxrvxvEzSl_cmmvNJq1iPK0jeTn12fqO33I3COTkpksNXidC5dZT7Bno2JhmVvaZNdrTvLa48FJ-RGLLVRdI2lTx9cbBAxk9Xr_lRRh9gCKeFUB_ul-XNS26mtHizIcDFb1VkJsYTE-kcGoHhDAYC9i4Tx7YTWBfSpkhx1p3Vs1LHA_0wEtKCt1rONuuANJJhYc7tWv730vMTRVoXpKt1JOyd6PcujkjFGFTr-s"/>
<div class="flex flex-col gap-1">
<span class="text-[11px] font-bold text-tertiary-fixed-dim ml-2">Анна Морозова</span>
<div class="bg-secondary-container text-on-secondary-container rounded-xl rounded-bl-sm p-4 relative">
<p class="body-lg leading-[1.6]">Коллеги, я подготовила наброски для нового BottomNavBar. Посмотрите, пожалуйста, в Figma. Основной упор сделала на "Kinetic Connection".</p>
<div class="flex gap-1 absolute -bottom-3 left-2">
<span class="bg-surface-container-high px-1.5 py-0.5 rounded-full text-[10px] flex items-center gap-1 border border-outline-variant/10">🔥 4</span>
<span class="bg-surface-container-high px-1.5 py-0.5 rounded-full text-[10px] flex items-center gap-1 border border-outline-variant/10">🚀 2</span>
</div>
<span class="text-[9px] text-on-surface-variant/60 absolute bottom-1 right-2">14:20</span>
</div>
</div>
</div>
<!-- Outgoing Message -->
<div class="flex flex-col items-end gap-1 self-end max-w-[80%]">
<div class="bg-gradient-to-br from-primary to-primary-container text-on-primary-container rounded-xl rounded-br-sm p-4 shadow-lg relative">
<p class="body-lg leading-[1.6]">Отлично, Анна! Сейчас гляну. Особенно интересует реализация "No-Line" правила в переходах между секциями.</p>
<div class="flex items-center gap-1 absolute bottom-1 right-2">
<span class="text-[9px] text-on-primary-container/70">14:22</span>
<span class="material-symbols-outlined text-[12px]" data-icon="done_all" style="font-variation-settings: 'FILL' 1;">done_all</span>
</div>
</div>
</div>
<!-- System Message -->
<div class="flex justify-center my-2">
<span class="text-[11px] text-on-surface-variant/50 font-bold tracking-widest uppercase">Сегодня</span>
</div>
<!-- Incoming Message (User 2) -->
<div class="flex items-end gap-3 max-w-[80%] group">
<img alt="Avatar" class="w-8 h-8 rounded-full mb-1 border-2 border-outline-variant/10" data-alt="portrait of a focused developer with headphones in a moody blue neon lit environment" src="https://lh3.googleusercontent.com/aida-public/AB6AXuABOZJ2A8AZo9boOGjR8wt68Q8DVm1KmbapEBTl8H3pOnP3UNeXYuoCCNUr3KsmwfUdTIK0jXlJvCXcCvoa7p9xqXzzAG3UCz9y9Q4p9tpysaKbMb5i5wIto05D7lmdRSVcPGLxTO0iMXUbgAbFysYrI1e0-8BMGaQIPCT_gFM9SnBlxKm3qn6lOITtDw7486y1kNbDntQZNIvmbxpG-lN1blepP29TWOuFJKj2lN1nvsGNyFYrP7-BNV8kpTn7ck0vF38PFD0B6fY"/>
<div class="flex flex-col gap-1">
<span class="text-[11px] font-bold text-[#FF9ACD] ml-2">Дмитрий Петров</span>
<div class="bg-secondary-container text-on-secondary-container rounded-xl rounded-bl-sm p-4 relative">
<p class="body-lg leading-[1.6]">Я уже начал настраивать Tailwind-конфиг для этих переходов. Использовал Cubic Bezier (0.4, 0, 0.2, 1) как в спецификации.</p>
<div class="flex gap-1 absolute -bottom-3 left-2">
<span class="bg-tertiary-container/20 text-tertiary-fixed px-1.5 py-0.5 rounded-full text-[10px] flex items-center gap-1 border border-tertiary-container/30">💻 1</span>
</div>
<span class="text-[9px] text-on-surface-variant/60 absolute bottom-1 right-2">14:45</span>
</div>
</div>
</div>
<!-- Message with Image -->
<div class="flex items-end gap-3 max-w-[80%] group">
<img alt="Avatar" class="w-8 h-8 rounded-full mb-1 border-2 border-outline-variant/10" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAG28WgEZDmjaq4dWwnC2ZUmKXFtMI7572hmFSQaj_gKZBXDL6H-drJ5sNt-Q3xNakd7Pp0oMdD7xvjR2x3ANWbSsEjeTgKlfSw2KFmLrjP13khaSZi-RWOA_o8Vgomzgf_VUCgGCokXIPV-VdXRBV75-EtzgYwljetlQuB34RODjCt5s0lZUBjidnqkzatZOr7f_L-ZlovdLEh3amwRlmdBm_kkxsRJlf557sndXxAB6qLTHxBWvmUR687VhCLh4v5qrvNnmKaX_I"/>
<div class="flex flex-col gap-1">
<span class="text-[11px] font-bold text-tertiary-fixed-dim ml-2">Анна Морозова</span>
<div class="bg-secondary-container rounded-xl rounded-bl-sm p-2 overflow-hidden max-w-sm">
<img alt="UI Draft" class="rounded-lg w-full h-48 object-cover mb-2" data-alt="abstract modern ui dashboard layout with glassmorphism elements and glowing blue accents on a dark background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCgrXkZcMizGeuhDREidveGalc_mGeYdIHxnH-MeuP3jj3t-4cl_rgI5OnKWwAe82fij4lRuHOfZpkxHy7M97l8Z21Nn9j1CtAwxHeHSfEbkeE4IefBvAjmSo5jnWnPMRofccty5GPJPop7qYflZ9s2KRn56FN9oBhP8pxLjWRdzjUtG4wivvSnYCzxVqvrmlWEB7PhGGeJln0dGk6GnvwQH-SuSLyLTyV9HOatcfnb3uTJr2UJ7x8JP6fP59lYICXl2yUzAvbZ-hU"/>
<p class="body-lg px-2 pb-2">Вот превью главного экрана. Что думаете?</p>
<div class="flex justify-between items-center px-2 pb-1">
<div class="flex gap-1">
<span class="bg-surface-container-high px-1.5 py-0.5 rounded-full text-[10px]">❤️ 8</span>
</div>
<span class="text-[9px] text-on-surface-variant/60">15:02</span>
</div>
</div>
</div>
</div>
</div>
<!-- Message Input -->
<footer class="p-6 bg-surface-dim">
<div class="flex items-center gap-4 bg-surface-container-highest px-4 py-2 rounded-full focus-within:bg-surface-bright transition-all duration-300">
<button class="text-on-surface-variant hover:text-primary transition-colors">
<span class="material-symbols-outlined" data-icon="add_circle">add_circle</span>
</button>
<input class="flex-1 bg-transparent border-none focus:ring-0 text-on-surface body-lg placeholder:text-on-surface-variant/50" placeholder="Написать сообщение..." type="text"/>
<div class="flex items-center gap-3">
<button class="text-on-surface-variant hover:text-primary transition-colors">
<span class="material-symbols-outlined" data-icon="sentiment_satisfied">sentiment_satisfied</span>
</button>
<button class="text-on-surface-variant hover:text-primary transition-colors">
<span class="material-symbols-outlined" data-icon="mic">mic</span>
</button>
<button class="bg-primary text-on-primary-container p-2 rounded-full flex items-center justify-center hover:scale-105 active:scale-95 transition-transform">
<span class="material-symbols-outlined text-sm" data-icon="send" style="font-variation-settings: 'FILL' 1;">send</span>
</button>
</div>
</div>
</footer>
</main>
<!-- Right Side Panel (Group Info) -->
<aside class="w-80 bg-surface-container border-l border-outline-variant/10 h-full flex flex-col hidden lg:flex">
<div class="p-6 flex flex-col items-center text-center">
<div class="relative mb-4 group cursor-pointer">
<img alt="Group Icon Big" class="w-24 h-24 rounded-full border-4 border-surface shadow-xl" data-alt="vibrant abstract spaceship icon on a dark circular background with professional lighting" src="https://lh3.googleusercontent.com/aida-public/AB6AXuC94nMayqkCc9lYgOS-eh0R2oG0gLZ-ENGSXbZ5dUqt3Y5Qj0VZfn5ZNqIa4_yXfy6RocbBrp-aIgfFKD2CsBI9rmZ2BafaJSMKW0eTlq0OXds_xkCQ_zLgKg3Krt2kT8_4HHkpjJdf9qECNJVk7eEeV2Mnj1Iusm8AqwY5Nl2TomITIGwXWptUbACidBfqDgoaJ4DkL2DgwqeJjdS17pgSeCrJyNMjkIYR8_TxuQmLLp6TFuuf_95Qc_uimow5TgOjnrh0TqWXIZk"/>
<div class="absolute inset-0 bg-black/40 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<span class="material-symbols-outlined text-white" data-icon="edit">edit</span>
</div>
</div>
<h2 class="headline-sm font-bold text-on-surface mb-1">Дизайн-команда Knot</h2>
<p class="text-sm text-on-surface-variant">Группа для обсуждения интерфейсов и визуальной стратегии.</p>
</div>
<div class="flex-1 overflow-y-auto custom-scrollbar">
<!-- Media/Docs Bento Preview -->
<div class="px-6 py-4">
<div class="flex justify-between items-center mb-4">
<h3 class="label-sm font-bold uppercase tracking-widest text-on-surface-variant">Медиа и файлы</h3>
<button class="text-xs text-primary font-bold">Все</button>
</div>
<div class="grid grid-cols-3 gap-2">
<div class="aspect-square rounded-lg bg-surface-variant overflow-hidden">
<img alt="media preview" class="w-full h-full object-cover" data-alt="vibrant neon geometric patterns with dark contrast and glowing lines" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCNKzWSpIaMejvpImO6GiUfro-l9gIxoeYAOG_r0j5ETdfguZWTBIoNm6wiruo4PDb6SJK24054IDNZwcUoNBwo-gbH_d59xpcgB7C-L6aMAtXkhZ9UkpbjJoHut3lnQpMdoBDH8RbvPzunas4Z2gbVixgNS2OCSN1D8pIgz7cwGYQq6f-aYHRhRNC1tSySurmjeI1WlSe6az3wqXvT7UI5geY_ad1DUggZ0kRGU_YKqaw31npC5lCc-w-ZshLZkJbsdvauiRwhUZ0"/>
</div>
<div class="aspect-square rounded-lg bg-surface-variant overflow-hidden">
<img alt="media preview" class="w-full h-full object-cover" data-alt="minimalist architectural photo of a sleek modern building facade at night" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDQrihfyBqfTnUFRDCHjr40uHG9xkAEyWLOoIV4g-H8EpMkrbMaA5owaCqA494ejg6eq-02h-NP4X4Sa1vcowtUa5Bd-GIMi_DU6WPCdsUQh266L3VmwbAXikhIurS-ljHjz4csi26-4lbNJwXU6nc_05cE87dc1JwgLspmHkopsU-Eaftnj5XJ-A2tmRNmPBkT9wajuM04aW1wzKxNxlSAP0TpY7NCcYtpu-9bb0DYt8S5RA0h-N8rOjt__RpDKJCDd4Nqku41oFs"/>
</div>
<div class="aspect-square rounded-lg bg-surface-variant flex items-center justify-center">
<span class="text-xs font-bold text-on-surface-variant">+24</span>
</div>
</div>
</div>
<!-- Members List -->
<div class="px-6 py-4">
<div class="flex justify-between items-center mb-4">
<h3 class="label-sm font-bold uppercase tracking-widest text-on-surface-variant">Участники (128)</h3>
<button class="text-on-surface-variant">
<span class="material-symbols-outlined text-sm" data-icon="search">search</span>
</button>
</div>
<div class="flex flex-col gap-4">
<!-- Member 1 (Admin) -->
<div class="flex items-center justify-between group">
<div class="flex items-center gap-3">
<div class="relative">
<img alt="Member" class="w-9 h-9 rounded-full" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCfEkb5y79iisA9Yy7QO-ZD8UpLjPqu7FxNRORuk5fYtwdv7SSRfXWeSpx6CqtXHr02uLQUnGr7EhxPmOExctgMTA5GDYByRoT4TTo69mBpu5_PUO-EBuCgE-eVXe0MGbHFHj3nCkThraB4lHKT7d9Bw4ENjk_NZPqrYx9emGKI_0kTpA9iYxH48f_QNkBfF24kKGflQG0rJZHhiCwMMsovVVdGIKC2Yruu8e2AjQbs9-cgIyMwhJeE3A6hjDP5fpzBrMe0zh5tQ1I"/>
<div class="absolute bottom-0 right-0 w-2.5 h-2.5 bg-tertiary rounded-full border-2 border-surface-container"></div>
</div>
<div>
<p class="text-sm font-medium text-on-surface">Анна Морозова</p>
<p class="text-[10px] text-tertiary font-bold uppercase">Владелец</p>
</div>
</div>
</div>
<!-- Member 2 (Admin) -->
<div class="flex items-center justify-between group">
<div class="flex items-center gap-3">
<div class="relative">
<img alt="Member" class="w-9 h-9 rounded-full" src="https://lh3.googleusercontent.com/aida-public/AB6AXuD30XfOGEh-5_D-hQYR0OH9QIl1NQ0QHreZB25v7llJbWv3m4lIEdfZzUOhMH7PNDrvX6edccKp9E6dThgx-Ynw-WEpgbyCf2DETKXIVMJMOuLAOIIlf2IUDP_Gi1BnCJ4Y8kjIvxd5h5sJn-6oPZRIhYpdSkJpgVFd4YX-X6ajFe1pQRxVMvkyc-QaDURNhzwTcEB8PtSETqFYIy2F195DX8B-0y2lyYNtTqTyuT3w0hJjgHYjaqo2zwUkB0C_LtgZHhgtRyC-AsM"/>
<div class="absolute bottom-0 right-0 w-2.5 h-2.5 bg-tertiary rounded-full border-2 border-surface-container"></div>
</div>
<div>
<p class="text-sm font-medium text-on-surface">Дмитрий Петров</p>
<p class="text-[10px] text-primary font-bold uppercase">Админ</p>
</div>
</div>
</div>
<!-- Member 3 (Regular) -->
<div class="flex items-center justify-between group">
<div class="flex items-center gap-3">
<div class="relative">
<img alt="Member" class="w-9 h-9 rounded-full" src="https://lh3.googleusercontent.com/aida-public/AB6AXuD179Zxdu7W03DzO0ubizZw7AByl-tHn9ZzMFuhDXrgJ8wZGHosJCmV-871Ht6jXX7OsJk5-5VZG5Y0TVbxxSI_O1ADrU53ZWODU_U4poG7TUe4aVAc7UTri4OjEIW0gS1008cijqB4SMwjrIQpvz5RsV04epENeDeoKtD1q5jtK2TZKX21_8aA-Ng5stBfum8Hn9QiFDWzjNQfFbVhk8NgSjYRFhTzH3qmRqy_d0-SSz20kz5e-kUo_qdshC9pFwHlz1nDAOETAz4"/>
<div class="absolute bottom-0 right-0 w-2.5 h-2.5 bg-on-surface-variant/30 rounded-full border-2 border-surface-container"></div>
</div>
<div>
<p class="text-sm font-medium text-on-surface">Игорь Коваль</p>
<p class="text-[10px] text-on-surface-variant">был(а) 10 мин. назад</p>
</div>
</div>
</div>
<!-- Member 4 (Regular) -->
<div class="flex items-center justify-between group">
<div class="flex items-center gap-3">
<div class="relative">
<img alt="Member" class="w-9 h-9 rounded-full" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCMfEDl8p2mo_5YENauKmT3qKEPGRKcnBY5Jssz3--DLWDSN3Av_6oxNfhLL1j4RGfaUqWZZy__a4A7m3INHMQfZ26lHuS9bHuPDuLW_RzeUCNJM5jlIBlKXbaMtdTcIPG2aSdVxMaEeTHGsuxFFBmQnzrqo68MjeCHYcGBL7lUJnZ6Gpo2rzbj04Fj354Of-DEdxG597GARHuDfg2I08ecHQUhFb0odDZ6uZN3hehFNcJEdBQcWTZV0VawoTbCRYPUO-svuQUmsKY"/>
<div class="absolute bottom-0 right-0 w-2.5 h-2.5 bg-tertiary rounded-full border-2 border-surface-container"></div>
</div>
<div>
<p class="text-sm font-medium text-on-surface">Ольга Лебедева</p>
<p class="text-[10px] text-on-surface-variant">онлайн</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="p-6 mt-auto">
<button class="w-full flex items-center justify-center gap-2 py-3 rounded-xl bg-error/10 text-error hover:bg-error/20 transition-colors font-bold text-sm">
<span class="material-symbols-outlined text-sm" data-icon="logout">logout</span>
Покинуть группу
</button>
</div>
</aside>
</body></html>

BIN
stitch/_2/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

185
stitch/_3/code.html Normal file
View File

@@ -0,0 +1,185 @@
<!DOCTYPE html>
<html class="dark" lang="ru"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Регистрация в Knot</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"surface-variant": "#353534",
"primary-container": "#3096e5",
"on-surface-variant": "#c1c6d7",
"error-container": "#93000a",
"surface-container-low": "#1c1b1b",
"tertiary": "#53e16f",
"tertiary-fixed-dim": "#53e16f",
"on-tertiary-fixed": "#002107",
"secondary-fixed-dim": "#c7c6ca",
"outline-variant": "#414755",
"on-tertiary-fixed-variant": "#00531c",
"surface-container": "#201f1f",
"on-primary": "#003355",
"on-error-container": "#ffdad6",
"background": "#131313",
"error": "#ffb4ab",
"outline": "#8b90a0",
"surface-container-lowest": "#0e0e0e",
"surface-container-highest": "#353534",
"on-secondary-fixed-variant": "#46474a",
"secondary-container": "#48494c",
"on-surface": "#e5e2e1",
"surface-dim": "#131313",
"on-background": "#e5e2e1",
"surface": "#131313",
"secondary": "#c7c6ca",
"on-primary-fixed": "#001d34",
"surface-tint": "#9acbff",
"surface-bright": "#393939",
"secondary-fixed": "#e3e2e6",
"primary-fixed-dim": "#9acbff",
"on-secondary-container": "#b9b8bc",
"on-primary-container": "#002c4a",
"primary": "#9acbff",
"on-secondary": "#2f3033",
"tertiary-fixed": "#72fe88",
"inverse-primary": "#00629e",
"tertiary-container": "#00a741",
"on-primary-fixed-variant": "#004a79",
"on-error": "#690005",
"inverse-surface": "#e5e2e1",
"on-tertiary-container": "#00320e",
"on-tertiary": "#003911",
"surface-container-high": "#2a2a2a",
"primary-fixed": "#cfe5ff",
"on-secondary-fixed": "#1a1b1e",
"inverse-on-surface": "#313030"
},
fontFamily: {
"headline": ["Inter"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
body {
background-color: #131313;
color: #e5e2e1;
font-family: 'Inter', sans-serif;
-webkit-font-smoothing: antialiased;
}
.knot-gradient {
background: linear-gradient(135deg, #9acbff 0%, #3096e5 100%);
}
.glass-header {
background: rgba(57, 57, 57, 0.6);
backdrop-filter: blur(20px);
}
input:focus ~ .input-glow {
opacity: 1;
}
</style>
</head>
<body class="min-h-screen flex flex-col items-center selection:bg-primary/30">
<!-- TopAppBar Fragmented Logic -->
<header class="fixed top-0 w-full flex justify-between items-center px-6 py-4 z-50 bg-transparent">
<div class="text-2xl font-black tracking-tighter text-[#9ACBFF] dark:text-[#9ACBFF] italic">Knot</div>
<div class="flex items-center gap-4">
<button class="text-gray-400 hover:opacity-80 transition-opacity">
<span class="material-symbols-outlined" data-icon="more_vert">more_vert</span>
</button>
</div>
</header>
<main class="flex-1 w-full max-w-[420px] px-6 flex flex-col justify-center py-20 relative">
<!-- Abstract Decoration (The Thread) -->
<div class="absolute -top-20 -right-20 w-64 h-64 rounded-full border-[1px] border-primary/10 pointer-events-none"></div>
<div class="absolute top-40 -left-40 w-80 h-80 rounded-full border-[1px] border-primary/5 pointer-events-none"></div>
<!-- Identity Section -->
<div class="mb-12">
<h1 class="text-[3.5rem] font-black leading-[1.1] tracking-tighter mb-4 text-on-background">
Регистрация<br/><span class="text-primary italic">в Knot</span>
</h1>
<p class="text-on-surface-variant body-lg max-w-[300px] leading-relaxed">
Начните общение в новом формате. Простая регистрация без лишних данных.
</p>
</div>
<!-- Registration Form -->
<form class="space-y-6">
<div class="space-y-5">
<!-- Username Field -->
<div class="group relative">
<label class="block text-xs font-semibold tracking-widest text-on-surface-variant uppercase mb-2 ml-1" for="username">
Имя пользователя
</label>
<div class="relative">
<input class="w-full bg-surface-container-highest border-none rounded-xl px-5 py-4 text-on-surface placeholder:text-outline/50 focus:ring-2 focus:ring-primary/20 transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)]" id="username" placeholder="@username" type="text"/>
<div class="input-glow absolute inset-0 rounded-xl bg-primary/5 opacity-0 pointer-events-none transition-opacity duration-300"></div>
</div>
</div>
<!-- Password Field -->
<div class="group relative">
<label class="block text-xs font-semibold tracking-widest text-on-surface-variant uppercase mb-2 ml-1" for="password">
Пароль
</label>
<div class="relative">
<input class="w-full bg-surface-container-highest border-none rounded-xl px-5 py-4 text-on-surface placeholder:text-outline/50 focus:ring-2 focus:ring-primary/20 transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)]" id="password" placeholder="••••••••" type="password"/>
<div class="input-glow absolute inset-0 rounded-xl bg-primary/5 opacity-0 pointer-events-none transition-opacity duration-300"></div>
</div>
</div>
<!-- Repeat Password Field -->
<div class="group relative">
<label class="block text-xs font-semibold tracking-widest text-on-surface-variant uppercase mb-2 ml-1" for="repeat-password">
Повторите пароль
</label>
<div class="relative">
<input class="w-full bg-surface-container-highest border-none rounded-xl px-5 py-4 text-on-surface placeholder:text-outline/50 focus:ring-2 focus:ring-primary/20 transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)]" id="repeat-password" placeholder="••••••••" type="password"/>
<div class="input-glow absolute inset-0 rounded-xl bg-primary/5 opacity-0 pointer-events-none transition-opacity duration-300"></div>
</div>
</div>
</div>
<!-- Primary Action -->
<button class="w-full knot-gradient text-on-primary-container font-bold text-lg py-4 rounded-xl shadow-[0_8px_30px_rgb(48,150,229,0.3)] hover:scale-[0.98] active:scale-95 transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] mt-4" type="submit">
Создать аккаунт
</button>
</form>
<!-- Footer Link -->
<div class="mt-8 text-center">
<a class="text-on-surface-variant hover:text-primary transition-colors text-sm font-medium" href="#">
Уже есть аккаунт? <span class="text-primary underline decoration-primary/30 underline-offset-4">Войти</span>
</a>
</div>
</main>
<!-- BottomNavBar Suppression Check: Only render if not transactional.
But wait, the prompt asks for specific components and a minimalist screen.
Following the "Semantic Shell" mandate: Registration is a transactional flow.
However, for the sake of following the provided JSON as an anchor for the platform,
we include the BottomNavBar as a visual reference of the "Registration" active state. -->
<nav class="fixed bottom-0 left-0 w-full z-50 flex justify-around items-center px-4 pb-8 pt-4 bg-[#131313]/60 backdrop-blur-xl bg-gradient-to-t from-[#201F1F] to-transparent shadow-[0_-4px_40px_rgba(0,0,0,0.5)] rounded-t-[24px]">
<a class="flex flex-col items-center justify-center text-gray-500 px-6 py-2 hover:text-white transition-colors" href="#">
<span class="material-symbols-outlined mb-1" data-icon="login">login</span>
<span class="font-['Inter'] text-[11px] font-medium tracking-wide">Вход</span>
</a>
<a class="flex flex-col items-center justify-center bg-[#3096E5]/20 text-[#9ACBFF] rounded-xl px-6 py-2 scale-98 transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)]" href="#">
<span class="material-symbols-outlined mb-1" data-icon="person_add">person_add</span>
<span class="font-['Inter'] text-[11px] font-medium tracking-wide">Регистрация</span>
</a>
</nav>
<!-- Decorative Canvas Elements -->
<div class="fixed bottom-32 right-8 pointer-events-none opacity-20">
<div class="w-24 h-24 rounded-full border-[10px] border-tertiary-container blur-2xl"></div>
</div>
</body></html>

BIN
stitch/_3/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

300
stitch/_4/code.html Normal file
View File

@@ -0,0 +1,300 @@
<!DOCTYPE html>
<html class="dark" lang="ru"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"inverse-on-surface": "#313030",
"surface-container": "#201f1f",
"on-secondary": "#2f3033",
"tertiary-fixed-dim": "#53e16f",
"error": "#ffb4ab",
"primary-container": "#3096e5",
"secondary-container": "#48494c",
"surface-dim": "#131313",
"error-container": "#93000a",
"inverse-primary": "#00629e",
"inverse-surface": "#e5e2e1",
"on-tertiary-fixed": "#002107",
"secondary-fixed": "#e3e2e6",
"on-tertiary": "#003911",
"on-secondary-fixed": "#1a1b1e",
"surface-container-low": "#1c1b1b",
"on-primary-fixed-variant": "#004a79",
"on-surface": "#e5e2e1",
"on-secondary-container": "#b9b8bc",
"tertiary-fixed": "#72fe88",
"secondary-fixed-dim": "#c7c6ca",
"primary-fixed-dim": "#9acbff",
"on-error": "#690005",
"surface-container-high": "#2a2a2a",
"on-primary-fixed": "#001d34",
"on-primary-container": "#002c4a",
"on-primary": "#003355",
"surface-container-lowest": "#0e0e0e",
"on-surface-variant": "#c1c6d7",
"surface-variant": "#353534",
"secondary": "#c7c6ca",
"background": "#131313",
"surface-container-highest": "#353534",
"on-tertiary-fixed-variant": "#00531c",
"surface": "#131313",
"tertiary": "#53e16f",
"on-error-container": "#ffdad6",
"on-tertiary-container": "#00320e",
"primary-fixed": "#cfe5ff",
"outline": "#8b90a0",
"surface-bright": "#393939",
"on-background": "#e5e2e1",
"primary": "#9acbff",
"outline-variant": "#414755",
"surface-tint": "#9acbff",
"tertiary-container": "#00a741",
"on-secondary-fixed-variant": "#46474a"
},
fontFamily: {
"headline": ["Inter"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.custom-scrollbar::-webkit-scrollbar { width: 4px; }
.custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
.custom-scrollbar::-webkit-scrollbar-thumb { background: #353534; border-radius: 10px; }
.glass-header { backdrop-filter: blur(20px); background: rgba(19, 19, 19, 0.6); }
.knot-shadow { box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.5); }
.message-gradient-out { background: linear-gradient(135deg, #9ACBFF 0%, #3096E5 100%); }
</style>
</head>
<body class="bg-background text-on-surface font-body overflow-hidden">
<!-- Sidebar Navigation (Rail) -->
<nav class="fixed left-0 top-0 h-full z-40 flex flex-col border-none bg-[#201F1F] dark:bg-[#201F1F] w-20 items-center py-6 font-['Inter'] text-sm leading-[1.6] antialiased">
<div class="mb-10">
<span class="text-2xl font-black text-[#9ACBFF] tracking-tighter">Knot</span>
</div>
<div class="flex flex-col gap-6 w-full items-center">
<!-- Active: Чаты -->
<button class="flex flex-col items-center justify-center bg-[#3096E5]/20 text-[#9ACBFF] rounded-xl p-3 transition-all duration-300 scale-95 active:scale-90 cubic-bezier(0.4,0,0.2,1)">
<span class="material-symbols-outlined mb-1" data-icon="chat">chat</span>
<span class="text-[10px] font-medium">Чаты</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 cubic-bezier(0.4,0,0.2,1)">
<span class="material-symbols-outlined mb-1" data-icon="call">call</span>
<span class="text-[10px] font-medium">Звонки</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 cubic-bezier(0.4,0,0.2,1)">
<span class="material-symbols-outlined mb-1" data-icon="person">person</span>
<span class="text-[10px] font-medium">Контакты</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 cubic-bezier(0.4,0,0.2,1)">
<span class="material-symbols-outlined mb-1" data-icon="archive">archive</span>
<span class="text-[10px] font-medium">Архив</span>
</button>
</div>
<div class="mt-auto">
<button class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 cubic-bezier(0.4,0,0.2,1)">
<span class="material-symbols-outlined mb-1" data-icon="settings">settings</span>
<span class="text-[10px] font-medium">Настройки</span>
</button>
</div>
</nav>
<!-- Main Content Container -->
<main class="ml-20 h-screen flex overflow-hidden">
<!-- Left: Miniature Chat List (Modern Bento approach) -->
<aside class="w-80 bg-surface-container-low flex flex-col border-none">
<div class="p-6">
<h1 class="headline-sm font-bold text-white tracking-tight mb-6">Чаты</h1>
<div class="relative group">
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-on-surface-variant text-sm" data-icon="search">search</span>
<input class="w-full bg-surface-container-highest border-none rounded-xl py-2.5 pl-10 pr-4 text-sm focus:ring-1 focus:ring-primary outline-none transition-all placeholder:text-on-surface-variant/50" placeholder="Поиск..." type="text"/>
</div>
</div>
<div class="flex-1 overflow-y-auto custom-scrollbar px-3 space-y-1">
<!-- Chat Item 1: Active -->
<div class="p-3 bg-surface-container rounded-2xl flex items-center gap-4 cursor-pointer transition-all duration-300">
<div class="relative">
<img class="w-12 h-12 rounded-full ring-2 ring-primary/20" data-alt="close-up portrait of a woman with short dark hair in a professional studio setting with soft cyan ambient light" src="https://lh3.googleusercontent.com/aida-public/AB6AXuD06H-S0vEdSKVHBzneSmjFzNkpekSWz9FvqBfav1Rv0J7Xl9ZhmCddfbY5wtoLHu3aL84akTOYgBvqyEFsLlsdBugXDEolqv2dRJ2zyenpjdyOluOKwM-uNCp4KrIXLvmZQp5MSvYjIooTyfUqrKus5ktUT8fjfe_4yCDZULrtF5SelBIr7ZbAHPmeNo0NW2kiK1WNMc0z35KCFWc3TMjGDm21n7GQgRmaa6q9rBLvTRMfLQBJPI4LFquMMJeVTTjPhJULdy0W92s"/>
<div class="absolute bottom-0 right-0 w-3 h-3 bg-tertiary rounded-full border-2 border-surface-container-low"></div>
</div>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-baseline">
<h3 class="font-bold text-sm truncate">Анна Волкова</h3>
<span class="label-sm text-on-surface-variant">14:20</span>
</div>
<p class="text-xs text-primary truncate">Печатает...</p>
</div>
</div>
<!-- Chat Item 2 -->
<div class="p-3 hover:bg-surface-container/50 rounded-2xl flex items-center gap-4 cursor-pointer transition-all duration-300">
<img class="w-12 h-12 rounded-full" data-alt="profile photo of a young man with glasses and a stylish haircut against a minimalist grey background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDyHGAUNzg1HfR-AEi0jlLPBFJNqowGoHrFWnCo2GWn1HaHPzj2gT-zEnd8S9tdr405Q0n1Mq5JgdWVoDlPAsLliRXcTISfHllscmZ_dNXcNNHMGNwqB-Dn2h86Z1Gfy-zefNuwWXWwYjIxnStyt4ozxnWtV4Lc-YGXPnj1IyZosFTKuzLkkWhMqZLkkrADsOrYfGHyiVmRlPiuwtZQoU_I8zR9hScmfJhfHqAC_V91MvOdZNORI2XHabpndvu0pqmLDBuPjsxA-0Y"/>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-baseline">
<h3 class="font-bold text-sm truncate">Дмитрий Соколов</h3>
<span class="label-sm text-on-surface-variant">12:05</span>
</div>
<p class="text-xs text-on-surface-variant truncate">Ок, договорились!</p>
</div>
</div>
<!-- Chat Item 3 -->
<div class="p-3 hover:bg-surface-container/50 rounded-2xl flex items-center gap-4 cursor-pointer transition-all duration-300">
<div class="w-12 h-12 rounded-full bg-tertiary-container/20 flex items-center justify-center text-tertiary-fixed">
<span class="material-symbols-outlined" data-icon="groups">groups</span>
</div>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-baseline">
<h3 class="font-bold text-sm truncate">Дизайн Ревью</h3>
<span class="label-sm text-on-surface-variant">Пн</span>
</div>
<p class="text-xs text-on-surface-variant truncate"><span class="text-white">Макс:</span> Посмотрите новый вариант логотипа...</p>
</div>
</div>
</div>
</aside>
<!-- Right: Chat Area -->
<section class="flex-1 flex flex-col bg-surface-container-lowest relative">
<!-- TopAppBar -->
<header class="glass-header h-16 flex items-center justify-between px-8 z-30 tonal-transition-no-border shadow-[0_40px_40px_rgba(255,255,255,0.05)]">
<div class="flex items-center gap-4">
<div class="relative">
<img class="w-10 h-10 rounded-full" data-alt="close-up portrait of a woman with short dark hair in a professional studio setting with soft cyan ambient light" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDPTKGW-8CwkyPdFY3AQ-MaYvPSpOjFFRi9zSLG6E6bB3B5QWPfnVdY4vVBBJpIbcC4z57YPSm4serdfOj0WMhGhmlqchdSusWB8zmg9DmFCwjLwh4FMunBsB7WWUYtAAQ_TVRg6xmtS40ZUI8pFfNopODIbpMwbiPoPhcYsLbjL3a7Q-sNpQIbn4xzPeWfVekD3YXkrhFuwIkCffHTbOfq-bb8tiPqDbjMMWpyqtc-LSrfCtg9YVqfdB5aFa5r98e2kFKsY3JuAt8"/>
<div class="absolute bottom-0 right-0 w-2.5 h-2.5 bg-tertiary rounded-full border-2 border-[#131313]"></div>
</div>
<div>
<h2 class="headline-sm font-bold text-[#FFFFFF] tracking-tight leading-tight">Анна Волкова</h2>
<p class="text-[10px] text-tertiary font-medium">в сети</p>
</div>
</div>
<div class="flex items-center gap-2">
<button class="w-10 h-10 flex items-center justify-center rounded-xl hover:bg-surface-bright transition-all text-[#9ACBFF]">
<span class="material-symbols-outlined" data-icon="videocam">videocam</span>
</button>
<button class="w-10 h-10 flex items-center justify-center rounded-xl hover:bg-surface-bright transition-all text-[#9ACBFF]">
<span class="material-symbols-outlined" data-icon="call">call</span>
</button>
<button class="w-10 h-10 flex items-center justify-center rounded-xl hover:bg-surface-bright transition-all text-[#9ACBFF]">
<span class="material-symbols-outlined" data-icon="more_vert">more_vert</span>
</button>
</div>
</header>
<!-- Message Thread -->
<div class="flex-1 overflow-y-auto custom-scrollbar p-8 space-y-6">
<!-- Date Divider -->
<div class="flex justify-center">
<span class="bg-surface-container px-4 py-1 rounded-full text-[10px] font-bold text-on-surface-variant tracking-widest uppercase">Сегодня</span>
</div>
<!-- Incoming Message with Reply -->
<div class="flex flex-col gap-1 items-start max-w-[70%]">
<div class="bg-surface-container text-on-surface p-4 rounded-xl rounded-bl-sm knot-shadow">
<div class="bg-surface-variant/30 border-l-2 border-primary p-2 mb-2 rounded-lg text-xs">
<p class="font-bold text-primary mb-0.5">Вы</p>
<p class="truncate opacity-70 italic">Привет! Скинь пожалуйста макеты для Knot.</p>
</div>
<p class="body-lg leading-relaxed">Лови! Это пока черновик, но основная идея с "узлами" уже видна в навигации. Давай обсудим через 10 минут?</p>
<div class="flex justify-end mt-1">
<span class="label-sm text-on-surface-variant/60">14:15</span>
</div>
</div>
<!-- Reactions -->
<div class="flex gap-1 -mt-2 ml-2">
<div class="bg-surface-container-high border border-outline-variant/20 rounded-full px-2 py-0.5 flex items-center gap-1 cursor-pointer hover:bg-surface-bright transition-colors">
<span class="text-xs">🔥</span>
<span class="text-[10px] font-bold text-on-surface-variant">3</span>
</div>
</div>
</div>
<!-- Outgoing Message -->
<div class="flex flex-col gap-1 items-end ml-auto max-w-[70%]">
<div class="message-gradient-out text-on-primary-container p-4 rounded-xl rounded-br-sm knot-shadow">
<p class="body-lg leading-relaxed font-medium">Круто! Особенно нравятся закругления у бабблов. Да, я сейчас как раз освобожусь.</p>
<div class="flex justify-end items-center gap-1 mt-1">
<span class="label-sm text-on-primary-container/70">14:18</span>
<span class="material-symbols-outlined text-xs" data-icon="done_all">done_all</span>
</div>
</div>
</div>
<!-- GIF Message (Incoming) -->
<div class="flex flex-col gap-1 items-start max-w-[70%]">
<div class="bg-surface-container p-1 rounded-xl rounded-bl-sm overflow-hidden knot-shadow">
<img class="w-full h-48 object-cover rounded-lg" data-alt="expressive cinematic motion gif of abstract flowing liquid metal and digital knots in blue and white neon colors" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAje_de0kl_Z3LNBrQIe6PLgPxUlCCOOx9jJGSW3G9EsoERz4U-BsaM4HFWvh2VJSO1Z8UnRumq6AkDp3sxBxzCW15FvDhTTA_PLx9FRWWviVEvyzxXCM44cMpXO9zvhx3JBbqIjmG3azciT97gO4_eRBIwjJatLUrR_wmf_k8AZfO2L_KNVTByqevhnOghfndER-fkIiSBGDjPMQFrjk1hSWhxPqi7yIMgk-zehqAMITsmMW_jvTKsNvNRJGYwssERyJyHqWhnyHQ"/>
<div class="px-3 py-1.5 flex justify-between items-center">
<span class="text-[10px] font-bold text-on-surface-variant/60">GIPHY</span>
<span class="label-sm text-on-surface-variant/60">14:20</span>
</div>
</div>
</div>
<!-- Audio Message (Outgoing) -->
<div class="flex flex-col gap-1 items-end ml-auto max-w-[70%]">
<div class="message-gradient-out text-on-primary-container p-4 rounded-xl rounded-br-sm knot-shadow min-w-[280px]">
<div class="flex items-center gap-4">
<button class="w-10 h-10 flex items-center justify-center bg-white/20 rounded-full hover:bg-white/30 transition-all">
<span class="material-symbols-outlined" data-icon="play_arrow" style="font-variation-settings: 'FILL' 1;">play_arrow</span>
</button>
<div class="flex-1 h-8 flex items-center gap-[2px]">
<!-- Waveform simulation -->
<div class="h-2 w-[3px] bg-on-primary-container/30 rounded-full"></div>
<div class="h-4 w-[3px] bg-on-primary-container/30 rounded-full"></div>
<div class="h-6 w-[3px] bg-white rounded-full"></div>
<div class="h-3 w-[3px] bg-white rounded-full"></div>
<div class="h-5 w-[3px] bg-white rounded-full"></div>
<div class="h-7 w-[3px] bg-white rounded-full"></div>
<div class="h-4 w-[3px] bg-white rounded-full"></div>
<div class="h-6 w-[3px] bg-white rounded-full"></div>
<div class="h-2 w-[3px] bg-on-primary-container/30 rounded-full"></div>
<div class="h-5 w-[3px] bg-on-primary-container/30 rounded-full"></div>
<div class="h-3 w-[3px] bg-on-primary-container/30 rounded-full"></div>
<div class="h-6 w-[3px] bg-on-primary-container/30 rounded-full"></div>
<div class="h-4 w-[3px] bg-on-primary-container/30 rounded-full"></div>
<div class="h-2 w-[3px] bg-on-primary-container/30 rounded-full"></div>
</div>
<span class="text-xs font-bold whitespace-nowrap">0:42</span>
</div>
<div class="flex justify-end items-center gap-1 mt-2">
<span class="label-sm text-on-primary-container/70">14:22</span>
<span class="material-symbols-outlined text-xs" data-icon="done_all">done_all</span>
</div>
</div>
</div>
</div>
<!-- Input Area -->
<footer class="p-6 bg-surface-container-lowest relative z-10">
<div class="flex items-end gap-3 max-w-5xl mx-auto">
<!-- Attachment -->
<button class="w-12 h-12 flex items-center justify-center text-on-surface-variant hover:text-primary transition-colors mb-0.5">
<span class="material-symbols-outlined" data-icon="attach_file">attach_file</span>
</button>
<!-- Main Input Pill -->
<div class="flex-1 relative bg-surface-container-highest rounded-2xl flex items-center px-4 group transition-all duration-300">
<button class="text-on-surface-variant hover:text-primary transition-colors">
<span class="material-symbols-outlined" data-icon="sentiment_satisfied">sentiment_satisfied</span>
</button>
<textarea class="w-full bg-transparent border-none focus:ring-0 py-4 px-4 text-on-surface placeholder:text-on-surface-variant/40 resize-none max-h-32 custom-scrollbar" placeholder="Сообщение..." rows="1"></textarea>
</div>
<!-- Action Button: Mic (Voice) -->
<button class="w-12 h-12 flex items-center justify-center bg-primary-container rounded-2xl text-on-primary-container hover:opacity-90 transition-all active:scale-90 cubic-bezier(0.4, 0, 0.2, 1) mb-0.5 shadow-lg shadow-primary-container/20">
<span class="material-symbols-outlined" data-icon="mic" style="font-variation-settings: 'FILL' 1;">mic</span>
</button>
</div>
</footer>
<!-- Floating Decoration Element (Subtle brand touch) -->
<div class="absolute top-24 right-12 w-64 h-64 bg-primary/5 rounded-full blur-[100px] pointer-events-none"></div>
<div class="absolute bottom-24 left-12 w-48 h-48 bg-tertiary/5 rounded-full blur-[80px] pointer-events-none"></div>
</section>
</main>
</body></html>

BIN
stitch/_4/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

466
stitch/_5/code.html Normal file
View File

@@ -0,0 +1,466 @@
<!DOCTYPE html>
<html class="dark" lang="ru"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Knot Admin Panel - Статистика</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"on-primary": "#003355",
"surface-variant": "#353534",
"surface-tint": "#9acbff",
"secondary-fixed-dim": "#c7c6ca",
"on-secondary": "#2f3033",
"surface-bright": "#393939",
"background": "#131313",
"secondary-container": "#48494c",
"primary": "#9acbff",
"on-secondary-fixed-variant": "#46474a",
"secondary-fixed": "#e3e2e6",
"on-surface-variant": "#c1c6d7",
"on-primary-container": "#002c4a",
"on-primary-fixed": "#001d34",
"inverse-surface": "#e5e2e1",
"surface-dim": "#131313",
"on-background": "#e5e2e1",
"inverse-on-surface": "#313030",
"outline-variant": "#414755",
"surface-container-highest": "#353534",
"on-tertiary": "#003911",
"tertiary": "#53e16f",
"secondary": "#c7c6ca",
"primary-fixed": "#cfe5ff",
"primary-fixed-dim": "#9acbff",
"on-primary-fixed-variant": "#004a79",
"surface-container-low": "#1c1b1b",
"on-surface": "#e5e2e1",
"on-secondary-container": "#b9b8bc",
"primary-container": "#3096e5",
"surface-container": "#201f1f",
"tertiary-container": "#00a741",
"tertiary-fixed-dim": "#53e16f",
"on-secondary-fixed": "#1a1b1e",
"error-container": "#93000a",
"surface": "#131313",
"on-tertiary-container": "#00320e",
"on-error-container": "#ffdad6",
"on-error": "#690005",
"on-tertiary-fixed": "#002107",
"inverse-primary": "#00629e",
"tertiary-fixed": "#72fe88",
"on-tertiary-fixed-variant": "#00531c",
"error": "#ffb4ab",
"outline": "#8b90a0",
"surface-container-lowest": "#0e0e0e",
"surface-container-high": "#2a2a2a"
},
fontFamily: {
"headline": ["Inter"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
body { font-family: 'Inter', sans-serif; background-color: #131313; color: #e5e2e1; }
.material-symbols-outlined { font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; }
.glass-card { background: rgba(32, 31, 31, 0.6); backdrop-filter: blur(12px); }
.no-scrollbar::-webkit-scrollbar { display: none; }
</style>
</head>
<body class="antialiased selection:bg-primary/30">
<!-- SideNavBar (Shared Component) -->
<aside class="h-screen w-64 fixed left-0 top-0 flex flex-col bg-[#131313] dark:bg-[#131313] shadow-2xl shadow-black/50 z-50">
<div class="flex flex-col h-full py-6 px-4">
<!-- Header Section -->
<div class="flex items-center gap-3 mb-8 px-2">
<div class="w-10 h-10 rounded-full bg-primary-container flex items-center justify-center overflow-hidden">
<img alt="Администратор" data-alt="Close up of a professional futuristic digital avatar profile picture with neon blue highlights and dark background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBlXqRIuWbYVUsvDBny3t4xpE51L2IA3ab6RUk2So4URi5UwU3gS_AgXSfEtoaGTnBnJZPC5psA4b44OkTfQckuorvbjnlUGxqJBJwSKkhAk0tX85nS9wL4iL7DhZTzpKHPvieKpFmJv89ZE2d-aL8OcGG7xBxZBX19Sy0rsYWTWDco6hoeegGQkQr7_i0jubpGqMpJlE4gfBwpzHmp1gDnfcc1ZuktZH5EM5BiExGri7Qc5AFu1EGBYIP-iGM_qJoUl3ysXgaPYx8"/>
</div>
<div>
<h1 class="text-lg font-bold tracking-tighter text-[#9ACBFF]">Knot Admin</h1>
<p class="text-[10px] uppercase tracking-widest text-white/40">Управление узлом</p>
</div>
</div>
<!-- Navigation Tabs -->
<nav class="flex-1 space-y-1">
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-white/60 hover:text-white hover:bg-white/5 transition-colors transform active:scale-95 transition-transform" href="#">
<span class="material-symbols-outlined text-xl" data-icon="group">group</span>
<span class="font-medium text-sm">Пользователи</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-white/60 hover:text-white hover:bg-white/5 transition-colors transform active:scale-95 transition-transform" href="#">
<span class="material-symbols-outlined text-xl" data-icon="chat_bubble">chat_bubble</span>
<span class="font-medium text-sm">Группы</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-white/60 hover:text-white hover:bg-white/5 transition-colors transform active:scale-95 transition-transform" href="#">
<span class="material-symbols-outlined text-xl" data-icon="hub">hub</span>
<span class="font-medium text-sm">Федерация</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-white/60 hover:text-white hover:bg-white/5 transition-colors transform active:scale-95 transition-transform" href="#">
<span class="material-symbols-outlined text-xl" data-icon="dns">dns</span>
<span class="font-medium text-sm">Серверы</span>
</a>
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-white/60 hover:text-white hover:bg-white/5 transition-colors transform active:scale-95 transition-transform" href="#">
<span class="material-symbols-outlined text-xl" data-icon="settings">settings</span>
<span class="font-medium text-sm">Настройки</span>
</a>
<!-- Active Tab: Статистика -->
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-[#9ACBFF] font-bold border-r-2 border-[#9ACBFF] bg-gradient-to-r from-[#3096E5]/10 to-transparent transform active:scale-95 transition-transform" href="#">
<span class="material-symbols-outlined text-xl" data-icon="analytics" style="font-variation-settings: 'FILL' 1;">analytics</span>
<span class="font-medium text-sm">Статистика</span>
</a>
</nav>
<!-- Footer Section -->
<div class="mt-auto pt-6 border-t border-white/5">
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-white/60 hover:text-error transition-colors" href="#">
<span class="material-symbols-outlined text-xl" data-icon="logout">logout</span>
<span class="font-medium text-sm">Выход</span>
</a>
</div>
</div>
</aside>
<!-- Main Canvas -->
<main class="ml-64 min-h-screen bg-surface flex flex-col">
<!-- TopAppBar (Shared Component) -->
<header class="sticky top-0 w-full z-40 bg-[#131313]/60 backdrop-blur-xl flex justify-between items-center h-16 px-8">
<div class="flex items-center gap-4">
<h2 class="text-[#9ACBFF] font-['Inter'] text-lg font-semibold">Панель администратора</h2>
</div>
<div class="flex items-center gap-6">
<!-- Search -->
<div class="relative group hidden md:block">
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-white/40 group-focus-within:text-primary transition-colors" data-icon="search">search</span>
<input class="bg-surface-container border-none rounded-full py-1.5 pl-10 pr-4 text-sm w-64 focus:ring-1 focus:ring-primary/50 transition-all outline-none" placeholder="Поиск по системе..." type="text"/>
</div>
<!-- Actions -->
<div class="flex items-center gap-2">
<button class="hover:bg-white/10 rounded-full p-2 transition-opacity hover:opacity-80">
<span class="material-symbols-outlined text-white/70" data-icon="notifications">notifications</span>
</button>
<button class="hover:bg-white/10 rounded-full p-2 transition-opacity hover:opacity-80">
<span class="material-symbols-outlined text-white/70" data-icon="dark_mode">dark_mode</span>
</button>
<div class="w-8 h-8 rounded-full bg-surface-container-highest overflow-hidden border border-white/10 ml-2">
<img alt="Профиль админа" data-alt="Portrait of a modern software engineer in a dark studio, minimalist aesthetic, low key lighting" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCQFskpv3vWWtWgQ0DuyTtExLXj-Jt_ihnMDS_vBTb0yQP4MFdktX9T4tCQBhADSrhfKJ1leSKVTmNyoa5CbC94Q-YZRECE9rr6SzSM-gnTLOJX8m1UW5XovVaCze9-t_LvuqgCbPddKIcvqfAuskzD5Hydc4RQpQz-P7ZB9sGDvoTgkRfzpsZRGH3djJKEFpoFfhQDZm4hwgJjX9jtC8QC0OSCLxzQnvELD4Ii8nafdHgeslMnROcTFyK5EJXZP1jn77RyxN531XE"/>
</div>
</div>
</div>
</header>
<!-- Content Area -->
<div class="p-8 space-y-8 max-w-7xl mx-auto w-full">
<!-- Metric Cards: Bento Grid Style -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<!-- Users Metric -->
<div class="glass-card p-6 rounded-xl border border-white/5 hover:border-primary/20 transition-all group">
<div class="flex justify-between items-start mb-4">
<div class="p-2 bg-primary/10 rounded-lg text-primary">
<span class="material-symbols-outlined" data-icon="group">group</span>
</div>
<span class="text-tertiary text-xs font-bold flex items-center gap-1">
<span class="material-symbols-outlined text-[14px]" data-icon="trending_up">trending_up</span> +12%
</span>
</div>
<div class="space-y-1">
<p class="text-white/40 text-xs font-medium uppercase tracking-wider">Пользователи</p>
<h3 class="text-3xl font-bold text-white tracking-tight">12,482</h3>
<p class="text-primary text-sm font-medium">842 онлайн сейчас</p>
</div>
</div>
<!-- Groups Metric -->
<div class="glass-card p-6 rounded-xl border border-white/5 hover:border-primary/20 transition-all group">
<div class="flex justify-between items-start mb-4">
<div class="p-2 bg-secondary-container/30 rounded-lg text-secondary">
<span class="material-symbols-outlined" data-icon="chat_bubble">chat_bubble</span>
</div>
</div>
<div class="space-y-1">
<p class="text-white/40 text-xs font-medium uppercase tracking-wider">Группы</p>
<h3 class="text-3xl font-bold text-white tracking-tight">3,105</h3>
<p class="text-white/60 text-sm font-medium">156 создано сегодня</p>
</div>
</div>
<!-- Federation Metric -->
<div class="glass-card p-6 rounded-xl border border-white/5 hover:border-primary/20 transition-all group">
<div class="flex justify-between items-start mb-4">
<div class="p-2 bg-tertiary/10 rounded-lg text-tertiary">
<span class="material-symbols-outlined" data-icon="hub">hub</span>
</div>
<span class="bg-tertiary/20 text-tertiary px-2 py-0.5 rounded-full text-[10px] font-bold">ACTIVE</span>
</div>
<div class="space-y-1">
<p class="text-white/40 text-xs font-medium uppercase tracking-wider">Фед. узлы</p>
<h3 class="text-3xl font-bold text-white tracking-tight">84</h3>
<p class="text-white/60 text-sm font-medium">Синхронизировано 100%</p>
</div>
</div>
<!-- Server Load Metric -->
<div class="glass-card p-6 rounded-xl border border-white/5 hover:border-primary/20 transition-all group">
<div class="flex justify-between items-start mb-4">
<div class="p-2 bg-error/10 rounded-lg text-error">
<span class="material-symbols-outlined" data-icon="memory">memory</span>
</div>
</div>
<div class="space-y-4">
<div class="space-y-1">
<div class="flex justify-between text-[10px] font-bold text-white/40 uppercase">
<span>CPU Load</span>
<span class="text-white">42%</span>
</div>
<div class="h-1.5 w-full bg-surface-container rounded-full overflow-hidden">
<div class="h-full bg-primary rounded-full w-[42%]"></div>
</div>
</div>
<div class="space-y-1">
<div class="flex justify-between text-[10px] font-bold text-white/40 uppercase">
<span>RAM Usage</span>
<span class="text-white">68%</span>
</div>
<div class="h-1.5 w-full bg-surface-container rounded-full overflow-hidden">
<div class="h-full bg-tertiary rounded-full w-[68%]"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Activity Chart (Placeholder) & Recent Activity -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Main Activity Chart -->
<div class="lg:col-span-2 glass-card rounded-2xl p-8 border border-white/5">
<div class="flex items-center justify-between mb-8">
<div>
<h4 class="text-xl font-bold text-white">Активность системы</h4>
<p class="text-white/40 text-sm">Трафик и сообщения за последние 24 часа</p>
</div>
<div class="flex bg-surface-container p-1 rounded-lg">
<button class="px-3 py-1 text-xs font-bold bg-surface-container-highest rounded text-white shadow-sm">24ч</button>
<button class="px-3 py-1 text-xs font-bold text-white/40 hover:text-white transition-colors"></button>
<button class="px-3 py-1 text-xs font-bold text-white/40 hover:text-white transition-colors">30д</button>
</div>
</div>
<!-- SVG Chart Visualization -->
<div class="relative h-64 w-full flex items-end justify-between gap-1 overflow-hidden">
<div class="absolute inset-0 flex flex-col justify-between pointer-events-none">
<div class="border-b border-white/5 w-full h-0"></div>
<div class="border-b border-white/5 w-full h-0"></div>
<div class="border-b border-white/5 w-full h-0"></div>
<div class="border-b border-white/5 w-full h-0"></div>
<div class="border-b border-white/5 w-full h-0"></div>
</div>
<!-- Mock bars -->
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 40%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 55%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 35%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 60%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 80%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 95%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 70%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 45%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 50%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 65%"></div>
<div class="bg-primary-container hover:bg-primary transition-colors w-full rounded-t-sm" style="height: 85%"></div>
<div class="bg-primary-container hover:bg-primary transition-colors w-full rounded-t-sm" style="height: 90%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 60%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 40%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 30%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 50%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 75%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 60%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 45%"></div>
<div class="bg-primary/20 hover:bg-primary/40 transition-colors w-full rounded-t-sm" style="height: 40%"></div>
</div>
<div class="mt-4 flex justify-between text-[10px] text-white/30 font-bold uppercase tracking-widest">
<span>00:00</span>
<span>06:00</span>
<span>12:00</span>
<span>18:00</span>
<span>Текущее</span>
</div>
</div>
<!-- Activity Feed -->
<div class="glass-card rounded-2xl border border-white/5 flex flex-col">
<div class="p-6 border-b border-white/5">
<h4 class="text-lg font-bold text-white">Узлы федерации</h4>
<p class="text-white/40 text-xs">Статус подключенных инстансов</p>
</div>
<div class="flex-1 overflow-y-auto no-scrollbar p-2 space-y-1">
<div class="flex items-center gap-3 p-3 rounded-xl hover:bg-white/5 transition-colors group">
<div class="w-10 h-10 rounded-lg bg-surface-container flex items-center justify-center text-primary">
<span class="material-symbols-outlined text-lg" data-icon="dns">dns</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-semibold text-white truncate">Main-Frankfurt-01</p>
<p class="text-xs text-white/40">latency: 24ms</p>
</div>
<div class="w-2 h-2 rounded-full bg-tertiary"></div>
</div>
<div class="flex items-center gap-3 p-3 rounded-xl hover:bg-white/5 transition-colors group">
<div class="w-10 h-10 rounded-lg bg-surface-container flex items-center justify-center text-primary">
<span class="material-symbols-outlined text-lg" data-icon="dns">dns</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-semibold text-white truncate">Relay-Moscow-Node</p>
<p class="text-xs text-white/40">latency: 12ms</p>
</div>
<div class="w-2 h-2 rounded-full bg-tertiary"></div>
</div>
<div class="flex items-center gap-3 p-3 rounded-xl hover:bg-white/5 transition-colors group">
<div class="w-10 h-10 rounded-lg bg-surface-container flex items-center justify-center text-primary">
<span class="material-symbols-outlined text-lg" data-icon="dns">dns</span>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-semibold text-white truncate">Tokyo-Edge-04</p>
<p class="text-xs text-white/40">latency: 180ms</p>
</div>
<div class="w-2 h-2 rounded-full bg-error"></div>
</div>
</div>
<div class="p-4 bg-white/5 rounded-b-2xl">
<button class="w-full py-2 text-xs font-bold text-primary hover:bg-primary/10 transition-colors rounded-lg">Посмотреть все узлы</button>
</div>
</div>
</div>
<!-- Users Table -->
<div class="glass-card rounded-2xl border border-white/5 overflow-hidden">
<div class="p-6 flex items-center justify-between">
<div>
<h4 class="text-xl font-bold text-white">Последние регистрации</h4>
<p class="text-white/40 text-sm">Новые пользователи в сети Knot за последние 24 часа</p>
</div>
<button class="bg-surface-container-highest hover:bg-surface-bright text-white px-4 py-2 rounded-lg text-sm font-semibold transition-colors flex items-center gap-2">
<span class="material-symbols-outlined text-sm" data-icon="filter_list">filter_list</span> Фильтр
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left">
<thead class="bg-white/5 text-[10px] font-bold text-white/40 uppercase tracking-widest">
<tr>
<th class="px-8 py-4">Пользователь</th>
<th class="px-8 py-4">Статус</th>
<th class="px-8 py-4">ID</th>
<th class="px-8 py-4">Последняя сессия</th>
<th class="px-8 py-4 text-right">Действия</th>
</tr>
</thead>
<tbody class="divide-y divide-white/5">
<tr class="hover:bg-white/5 transition-colors group">
<td class="px-8 py-4">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full border-2 border-primary/20 p-0.5">
<img alt="Алексей Медведев" class="w-full h-full rounded-full" data-alt="Cyberpunk style digital portrait of a young man with glowing neon accessories and dark background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBTbaM2vWj1NXg6mZ0oW48sCt35e7qb3hiKqIyQ8p0MP0e4ROwoKsL_6o_aS6Ui4IAV5az8qJtB-Tw-8LIRimY5HT3hjfMvxRqPCY5G5a8N_l9hJ-4MU2Baof2p6ioOI8qduV1shYVbaawQ12F4BIpf-WbKklcxd6gMtLyGD4nigLy6d3iwjVerYosxDO4_VcXJ74e8iz_bSz9UQggIq-dNzDmpMlFxHQpn5JnuygGpwd4MdwoH3ufUFjuyP6pEv1carCW61jEgNNE"/>
</div>
<div>
<p class="text-sm font-bold text-white">Алексей Медведев</p>
<p class="text-xs text-white/40">@medved_alex</p>
</div>
</div>
</td>
<td class="px-8 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[10px] font-bold bg-tertiary/10 text-tertiary uppercase">
<span class="w-1.5 h-1.5 rounded-full bg-tertiary"></span> Онлайн
</span>
</td>
<td class="px-8 py-4">
<code class="text-xs text-white/40 bg-white/5 px-2 py-1 rounded">#kt-8422-92</code>
</td>
<td class="px-8 py-4">
<p class="text-sm text-white/60">Сейчас</p>
</td>
<td class="px-8 py-4 text-right">
<button class="p-2 text-white/40 hover:text-primary transition-colors">
<span class="material-symbols-outlined" data-icon="more_vert">more_vert</span>
</button>
</td>
</tr>
<tr class="hover:bg-white/5 transition-colors group">
<td class="px-8 py-4">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full border-2 border-white/5 p-0.5">
<img alt="Елена Котова" class="w-full h-full rounded-full" data-alt="High quality photography of a woman with short hair in futuristic clothing, soft blue lighting" src="https://lh3.googleusercontent.com/aida-public/AB6AXuA0flGissKLQJb0Bp4pe7hVXX816-5eQjW_AipNJLR_gEF82kzmEXP1iQD0FubVB5VeKxW_2PPQiG9Mrvn4YjfCbd_IF0Pd7iF3c1Dot1-MwlqmxZNSKlZtyRrcyaKQsI8rhqVAT31Wp8EtyVEMpTWTlPujaPhBQIUdmtn0I3tNzB0hiFfPxZRNvJG3Uid2jLXLq5hmUR6LoqE9RfDOuoebyOpDBonkwASVf3Pd_Jc2WqnHYYscLW1Z6F9n1gV90weExNei3wtG_dA"/>
</div>
<div>
<p class="text-sm font-bold text-white">Елена Котова</p>
<p class="text-xs text-white/40">@kot_elena</p>
</div>
</div>
</td>
<td class="px-8 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[10px] font-bold bg-white/5 text-white/40 uppercase">
<span class="w-1.5 h-1.5 rounded-full bg-white/20"></span> Оффлайн
</span>
</td>
<td class="px-8 py-4">
<code class="text-xs text-white/40 bg-white/5 px-2 py-1 rounded">#kt-3310-15</code>
</td>
<td class="px-8 py-4">
<p class="text-sm text-white/60">2 часа назад</p>
</td>
<td class="px-8 py-4 text-right">
<button class="p-2 text-white/40 hover:text-primary transition-colors">
<span class="material-symbols-outlined" data-icon="more_vert">more_vert</span>
</button>
</td>
</tr>
<tr class="hover:bg-white/5 transition-colors group">
<td class="px-8 py-4">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full border-2 border-white/5 p-0.5">
<img alt="Иван Иванов" class="w-full h-full rounded-full" data-alt="Stylized modern character portrait, dark studio lighting with blue accents, sharp focus" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDHzO0GFYeh0U2rBi_kkSwVSFZbjIx0PBubwsWnZfD4VAByJ4IIEUMUEHK1SsIkUfshc7dSYxm9xk2ID5C8wMYJow-D_5EcjhHko7Pe6y7rwUZkDRi-NjmpzDfAtRiSg-DgbDV9VXT4Rxr0L1ZnijCSpk8QvNzbVDDv9V7QOJpJiqyzlv_1hTZnQJqNY3nuPBZerxcrKJv1DHTfYZ3xOlEljklJhGT3LFA9I0k6C_mSW4OEWWdieKTfagX_09WHl1CEwRDOnWTcuPo"/>
</div>
<div>
<p class="text-sm font-bold text-white">Иван Иванов</p>
<p class="text-xs text-white/40">@ivanov_dev</p>
</div>
</div>
</td>
<td class="px-8 py-4">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[10px] font-bold bg-tertiary/10 text-tertiary uppercase">
<span class="w-1.5 h-1.5 rounded-full bg-tertiary"></span> Онлайн
</span>
</td>
<td class="px-8 py-4">
<code class="text-xs text-white/40 bg-white/5 px-2 py-1 rounded">#kt-1029-44</code>
</td>
<td class="px-8 py-4">
<p class="text-sm text-white/60">Сейчас</p>
</td>
<td class="px-8 py-4 text-right">
<button class="p-2 text-white/40 hover:text-primary transition-colors">
<span class="material-symbols-outlined" data-icon="more_vert">more_vert</span>
</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="p-6 border-t border-white/5 bg-white/2 space-y-4">
<div class="flex items-center justify-between">
<p class="text-xs text-white/40 font-medium">Показано 3 из 1,240 новых пользователей</p>
<div class="flex gap-2">
<button class="p-2 bg-surface-container rounded-lg text-white/40 hover:text-white transition-colors">
<span class="material-symbols-outlined text-sm" data-icon="chevron_left">chevron_left</span>
</button>
<button class="p-2 bg-surface-container rounded-lg text-white hover:bg-primary/20 transition-colors">
<span class="material-symbols-outlined text-sm" data-icon="chevron_right">chevron_right</span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Footer spacing -->
<footer class="mt-auto py-8 text-center border-t border-white/5">
<p class="text-[10px] text-white/20 font-bold uppercase tracking-[0.2em]">Knot Messenger Admin Engine v2.4.0-stable</p>
</footer>
</main>
</body></html>

BIN
stitch/_5/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

309
stitch/_6/code.html Normal file
View File

@@ -0,0 +1,309 @@
<!DOCTYPE html>
<html class="dark" lang="ru"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"inverse-on-surface": "#313030",
"surface-container": "#201f1f",
"on-secondary": "#2f3033",
"tertiary-fixed-dim": "#53e16f",
"error": "#ffb4ab",
"primary-container": "#3096e5",
"secondary-container": "#48494c",
"surface-dim": "#131313",
"error-container": "#93000a",
"inverse-primary": "#00629e",
"inverse-surface": "#e5e2e1",
"on-tertiary-fixed": "#002107",
"secondary-fixed": "#e3e2e6",
"on-tertiary": "#003911",
"on-secondary-fixed": "#1a1b1e",
"surface-container-low": "#1c1b1b",
"on-primary-fixed-variant": "#004a79",
"on-surface": "#e5e2e1",
"on-secondary-container": "#b9b8bc",
"tertiary-fixed": "#72fe88",
"secondary-fixed-dim": "#c7c6ca",
"primary-fixed-dim": "#9acbff",
"on-error": "#690005",
"surface-container-high": "#2a2a2a",
"on-primary-fixed": "#001d34",
"on-primary-container": "#002c4a",
"on-primary": "#003355",
"surface-container-lowest": "#0e0e0e",
"on-surface-variant": "#c1c6d7",
"surface-variant": "#353534",
"secondary": "#c7c6ca",
"background": "#131313",
"surface-container-highest": "#353534",
"on-tertiary-fixed-variant": "#00531c",
"surface": "#131313",
"tertiary": "#53e16f",
"on-error-container": "#ffdad6",
"on-tertiary-container": "#00320e",
"primary-fixed": "#cfe5ff",
"outline": "#8b90a0",
"surface-bright": "#393939",
"on-background": "#e5e2e1",
"primary": "#9acbff",
"outline-variant": "#414755",
"surface-tint": "#9acbff",
"tertiary-container": "#00a741",
"on-secondary-fixed-variant": "#46474a"
},
fontFamily: {
"headline": ["Inter"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
}
.contrast-bg-shift-no-border {
border: none !important;
}
.tonal-transition-no-border {
border: none !important;
}
.slide-on-ice {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>
</head>
<body class="bg-background text-on-background font-body selection:bg-primary-container selection:text-on-primary-container">
<!-- SideNavBar Shell -->
<aside class="fixed left-0 top-0 h-full z-40 flex flex-col border-none bg-[#201F1F] dark:bg-[#201F1F] h-screen w-20 items-center py-6 shadow-none font-['Inter'] text-sm leading-[1.6] antialiased">
<div class="mb-10">
<span class="text-2xl font-black text-[#9ACBFF] tracking-tighter">Knot</span>
</div>
<nav class="flex flex-col gap-6 w-full items-center">
<!-- Active State: Чаты -->
<button class="flex flex-col items-center justify-center bg-[#3096E5]/20 text-[#9ACBFF] rounded-xl p-3 transition-all duration-300 scale-95 active:scale-90 cubic-bezier(0.4,0,0.2,1) hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors">
<span class="material-symbols-outlined" data-icon="chat" style="font-variation-settings: 'FILL' 1;">chat</span>
<span class="text-[10px] mt-1 font-medium">Чаты</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1) hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors">
<span class="material-symbols-outlined" data-icon="call">call</span>
<span class="text-[10px] mt-1 font-medium">Звонки</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1) hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors">
<span class="material-symbols-outlined" data-icon="person">person</span>
<span class="text-[10px] mt-1 font-medium">Контакты</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1) hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors">
<span class="material-symbols-outlined" data-icon="archive">archive</span>
<span class="text-[10px] mt-1 font-medium">Архив</span>
</button>
<div class="mt-auto">
<button class="flex flex-col items-center justify-center text-[#939090] p-3 scale-95 active:scale-90 transition-transform cubic-bezier(0.4,0,0.2,1) hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors">
<span class="material-symbols-outlined" data-icon="settings">settings</span>
<span class="text-[10px] mt-1 font-medium">Настройки</span>
</button>
</div>
</nav>
</aside>
<!-- TopAppBar Shell -->
<header class="fixed top-0 right-0 left-20 z-30 flex items-center justify-between px-8 bg-[#131313]/60 backdrop-blur-xl docked full-width h-16 tonal-transition-no-border shadow-[0_40px_40px_rgba(255,255,255,0.05)] slide-on-ice">
<div class="flex items-center gap-4">
<h1 class="headline-sm font-bold text-[#FFFFFF] tracking-tight">Чаты</h1>
</div>
<div class="flex-1 max-w-xl mx-8">
<div class="relative group">
<div class="absolute inset-y-0 left-4 flex items-center pointer-events-none">
<span class="material-symbols-outlined text-on-surface-variant text-sm">search</span>
</div>
<input class="w-full bg-surface-container-highest border-none rounded-full py-2 pl-12 pr-4 text-sm text-on-surface focus:ring-1 focus:ring-primary focus:bg-surface-bright transition-all placeholder:text-on-surface-variant/50" placeholder="Поиск сообщений..." type="text"/>
</div>
</div>
<div class="flex items-center gap-4">
<button class="p-2 text-on-surface-variant hover:opacity-80 transition-opacity">
<span class="material-symbols-outlined" data-icon="videocam">videocam</span>
</button>
<button class="p-2 text-on-surface-variant hover:opacity-80 transition-opacity">
<span class="material-symbols-outlined" data-icon="call">call</span>
</button>
<button class="p-2 text-on-surface-variant hover:opacity-80 transition-opacity">
<span class="material-symbols-outlined" data-icon="more_vert">more_vert</span>
</button>
<div class="w-10 h-10 rounded-full overflow-hidden border-2 border-outline-variant/10 ml-2">
<img class="w-full h-full object-cover" data-alt="Close-up portrait of a professional man with a friendly smile, soft studio lighting on dark background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuD16pFrjyeqVKVL8ZmH1Dv1reVH6ExPbMkbZEOoaPKL7EdR_KO9X0kANtxqyfVLd89UGfIM9wIKfmVLxwCQEsrYxqGvHNwqsf0ZEvwwxAAD9zikG6ujV3BwvXGHeIF62mwM10Ec3hgLNMAJekVVgi0i429oIRIfhlAgwF9ztkgqu9UWuAp-RguSMVXwYnR2tJtAr53vR548lhn2xB1FJF0ubLQKSC6J9RJOsnsEHYc1qHnbAvxyiz25HrJqmFtRQv7t4YVmqqaXxn4"/>
</div>
</div>
</header>
<!-- Main Workspace -->
<main class="ml-20 pt-16 h-screen flex overflow-hidden">
<!-- Folders & Chat List Column -->
<section class="w-[400px] flex flex-col bg-surface-container-low border-none h-full overflow-hidden">
<!-- Story Bubbles Bar -->
<div class="px-6 py-4 flex gap-4 overflow-x-auto no-scrollbar items-center bg-surface-container-low">
<div class="flex flex-col items-center gap-1 shrink-0">
<div class="relative w-14 h-14 rounded-full p-[2px] bg-surface-container-highest flex items-center justify-center cursor-pointer group">
<span class="material-symbols-outlined text-primary group-hover:scale-110 transition-transform">add</span>
</div>
<span class="text-[10px] text-on-surface-variant font-medium">История</span>
</div>
<!-- Active Story -->
<div class="flex flex-col items-center gap-1 shrink-0">
<div class="w-14 h-14 rounded-full p-[2px] bg-gradient-to-tr from-primary to-tertiary">
<div class="w-full h-full rounded-full border-2 border-surface-container-low overflow-hidden">
<img class="w-full h-full object-cover" data-alt="Young smiling woman with vibrant expression in urban setting, warm sunset glow lighting" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCBMKQbynd1VSBypxXb8l-cz8WXvVlb9WhAWcLv9No-p6P1xmisAwo1pQrECECI5leXMr12HOm7q-idnZB7kFJ7wmvipMfene1gt-8SnwGEWs3A9iLcGvtcz-ZPzSyNIJqQPftqPHT8FzA-GEZhhKuCIY8RePcFe2L3mGbhiXi9EhcFm7_F1AbRTQQyZTeDwjhTvwasDY_hHO8zoflggT1Zqq6gUQ5wbxWe0O_GbkLfTTEMxVqcxSP_UWdDbkVbVbixfTAapqxO05M"/>
</div>
</div>
<span class="text-[10px] text-on-surface font-medium">Анна</span>
</div>
<div class="flex flex-col items-center gap-1 shrink-0">
<div class="w-14 h-14 rounded-full p-[2px] bg-gradient-to-tr from-primary to-tertiary">
<div class="w-full h-full rounded-full border-2 border-surface-container-low overflow-hidden">
<img class="w-full h-full object-cover" data-alt="Young man with short hair looking confidently at camera, natural daylight, blurred park background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAB7fbCSbkHnjwyobZ3YTHSXjtCSUHnKO0eSDKzEg8vaSvnfTdeSiEtapM6ZPFSKRpBHMrLJTgsKCC1Mh-O8H0JQI7tUmI1S6S45_Lkg1PmKqOz43lnbDcxacKOuIFdoe1VNGbzCN442z_cjkBtQ_qKkAYIT_QrVA72-tRExhsjgwayAYaC9dEM2Mh0GwoyJVsk4r-PJ-eoOejrKXcjLD2UplnrYMzX-UZgkXBg4eYk_CuQiJG3etFhvezHfhPdftqSjLn9vAHVU2E"/>
</div>
</div>
<span class="text-[10px] text-on-surface font-medium">Максим</span>
</div>
<div class="flex flex-col items-center gap-1 shrink-0">
<div class="w-14 h-14 rounded-full p-[2px] bg-gradient-to-tr from-primary/30 to-tertiary/30">
<div class="w-full h-full rounded-full border-2 border-surface-container-low overflow-hidden">
<img class="w-full h-full object-cover" data-alt="Calm woman with soft lighting, minimalist aesthetic, neutral grey background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCFhPu14QrLlnTtPvgvNGUfeNYu2DbJD-HuHGaBJjiFRB2B74h9BqVeQ1RdiW4fH_IezMfgBB-VxZves9MkKz0gYuCLbQW0VeTPYo9Fed1HduDYxHFW9v1abQbXmI9Rx3-xj37Sv_ReyEolAFSR1F76FrJ0wyj1yMr5Rjn8lEYpA2nMhBK79l8t0noN1wVf3-sED59gtQFXinmxZTlqgBb4gcNCGfW7XfdhGcuQeQiJqnW6sLDlohv1CKV8r9eGXNKeQRiYM62k7Tw"/>
</div>
</div>
<span class="text-[10px] text-on-surface-variant font-medium">Лена</span>
</div>
</div>
<!-- Folder Tabs -->
<div class="px-6 py-2 flex gap-4 overflow-x-auto no-scrollbar">
<button class="shrink-0 text-sm font-bold text-primary border-b-2 border-primary pb-2 px-1">Все чаты</button>
<button class="shrink-0 text-sm font-medium text-on-surface-variant hover:text-on-surface transition-colors pb-2 px-1">Группы</button>
<button class="shrink-0 text-sm font-medium text-on-surface-variant hover:text-on-surface transition-colors pb-2 px-1">Каналы</button>
<button class="shrink-0 text-sm font-medium text-on-surface-variant hover:text-on-surface transition-colors pb-2 px-1">Личные</button>
</div>
<!-- Chat List Content -->
<div class="flex-1 overflow-y-auto no-scrollbar py-2">
<!-- Chat Item (Active) -->
<div class="px-4 py-3 mx-2 bg-surface-container rounded-2xl flex items-center gap-4 cursor-pointer hover:bg-surface-container transition-all group">
<div class="relative">
<div class="w-12 h-12 rounded-full overflow-hidden border-2 border-outline-variant/10">
<img class="w-full h-full object-cover" data-alt="Modern woman with stylish glasses, intense focused look, soft shadows, warm interior lighting" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDYAgAjlE5jr9VwCJZfJ_2dYDw1EFvBJPaTOzDUZwVA1VdSAd9DOFkJOXg1h_bqMcW56qw2S_1qmgP8IM_TA3KfvhFJ8lvEw4r8Pcl3IBmBxcgCSCJi9kS1QrEFFpUzyYqmk2KuCiiiYb4C9CEp738aTk4-FpOLiauVRX5Ucdc8_QJbbG_X7l1uXzikb95cHaQ_J9Kj9-_zYoAj-vjW1Q3QC-szb65B7aCaF_UWxSZcWelvxUT0opgg5Kizm48TFyzwL6SuhxhPVwQ"/>
</div>
<div class="absolute bottom-0 right-0 w-3.5 h-3.5 bg-tertiary border-2 border-surface-container rounded-full"></div>
</div>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-baseline mb-0.5">
<h3 class="text-sm font-bold text-on-surface truncate">Ольга Смирнова</h3>
<span class="label-sm text-on-surface-variant">14:20</span>
</div>
<p class="text-xs text-on-surface-variant truncate">Слушай, а когда мы сможем обсудить проект? Кажется...</p>
</div>
<div class="flex flex-col items-end gap-1">
<div class="bg-primary-container text-on-primary-container text-[10px] font-bold px-1.5 py-0.5 rounded-full min-w-[1.25rem] text-center">3</div>
</div>
</div>
<!-- Chat Item (Read) -->
<div class="px-4 py-3 mx-2 flex items-center gap-4 cursor-pointer hover:bg-surface-container-high rounded-2xl transition-all group">
<div class="relative">
<div class="w-12 h-12 rounded-full overflow-hidden border-2 border-outline-variant/10">
<img class="w-full h-full object-cover" data-alt="Young man with beard, outdoor lifestyle shot, golden hour natural light, forest background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBm_2HxjQmg39Xp4TPylrZd_FOKFASpJ0AT0_6OqXv88eru15cuLrI4jiSZjlEcYK0n6BqmIpeuyXyC5kGPCWJhFX2qmV-CzmD84SBXr_bigNYaKDo82g8sZfzcOo4iMaQ69OkAq93bxXPFaIKzJTB-Y9ZUTaJaM1d5m5EjVvLr9jBi6LX_ODkDbeOsJzYI4Xx-SGOaAmpg0Kri9iWdcpuaPR1faxXI6j4kZnYEuXb8vn8MPsa5UK4FZUmjXSQlT2aiuwSKv0lDDl0"/>
</div>
<div class="absolute bottom-0 right-0 w-3.5 h-3.5 bg-secondary-container border-2 border-surface-container rounded-full"></div>
</div>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-baseline mb-0.5">
<h3 class="text-sm font-bold text-on-surface truncate">Дизайн Отдел</h3>
<span class="label-sm text-on-surface-variant">Вчера</span>
</div>
<div class="flex items-center gap-1">
<span class="material-symbols-outlined text-[14px] text-primary" style="font-variation-settings: 'FILL' 1;">done_all</span>
<p class="text-xs text-on-surface-variant truncate">Файлы приняты в работу, спасибо!</p>
</div>
</div>
</div>
<!-- Chat Item (Channel) -->
<div class="px-4 py-3 mx-2 flex items-center gap-4 cursor-pointer hover:bg-surface-container-high rounded-2xl transition-all group">
<div class="relative">
<div class="w-12 h-12 rounded-full bg-tertiary-container flex items-center justify-center border-2 border-outline-variant/10">
<span class="material-symbols-outlined text-white" data-icon="rocket_launch">rocket_launch</span>
</div>
</div>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-baseline mb-0.5">
<h3 class="text-sm font-bold text-on-surface truncate">Knot Updates</h3>
<span class="label-sm text-on-surface-variant">11:05</span>
</div>
<p class="text-xs text-on-surface-variant truncate">🎉 Встречайте новую версию мессенджера!</p>
</div>
<div class="flex flex-col items-end gap-1">
<div class="bg-surface-container-highest text-on-surface-variant text-[10px] font-bold px-1.5 py-0.5 rounded-full">PIN</div>
</div>
</div>
<!-- More Chat items for scrolling demonstration -->
<div class="px-4 py-3 mx-2 flex items-center gap-4 cursor-pointer hover:bg-surface-container-high rounded-2xl transition-all group">
<div class="relative">
<div class="w-12 h-12 rounded-full overflow-hidden border-2 border-outline-variant/10">
<img class="w-full h-full object-cover" data-alt="Elegant woman in city clothes, overcast daylight, cinematic urban aesthetic" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBx8tTw6oOh7f5DwKce67jJ7fHDtXxSA5AW7yCQDqmJtxFJ7PaADpwnUPwd2r6eNboHvipqiWpiXm9kdXHXi_CEIZiaXC0t6nZcV7VIfqyUaJWqmXa7A4MoTEdAWQk6ewGHvymc-Hm-VJkdjYR3-BhCp3FzFkH1I19lPaZnyqIoTF3G0Yb-76MfgfsvVZm_Tm1SPab5l0mlslzJD29-6Ln994l20P8JCnUytks2WA8woZnfMqq-JnPeLLObMF994Ac_KqWgnfqrhe8"/>
</div>
</div>
<div class="flex-1 min-w-0">
<div class="flex justify-between items-baseline mb-0.5">
<h3 class="text-sm font-bold text-on-surface truncate">Мария (HR)</h3>
<span class="label-sm text-on-surface-variant">09:12</span>
</div>
<p class="text-xs text-on-surface-variant truncate">Ждем вас на собеседование завтра.</p>
</div>
</div>
</div>
</section>
<!-- Content Canvas (Conversation Placeholder) -->
<section class="flex-1 flex flex-col bg-surface-container-lowest relative">
<!-- Background Knot Texture -->
<div class="absolute inset-0 opacity-[0.03] pointer-events-none flex items-center justify-center">
<span class="material-symbols-outlined text-[40rem]" data-icon="all_inclusive">all_inclusive</span>
</div>
<!-- Chat Empty State View -->
<div class="flex-1 flex flex-col items-center justify-center z-10 p-12 text-center">
<div class="w-24 h-24 mb-6 rounded-full bg-surface-container flex items-center justify-center">
<span class="material-symbols-outlined text-primary text-5xl" data-icon="forum">forum</span>
</div>
<h2 class="display-lg font-black text-on-surface tracking-tighter mb-4">Выберите чат</h2>
<p class="body-lg text-on-surface-variant max-w-sm">
Начните общение прямо сейчас. Все ваши сообщения защищены сквозным шифрованием.
</p>
<button class="mt-8 px-8 py-3 bg-gradient-to-tr from-primary to-primary-container text-on-primary-container font-bold rounded-full shadow-lg hover:opacity-90 transition-opacity flex items-center gap-2">
<span class="material-symbols-outlined" data-icon="edit">edit</span>
Новое сообщение
</button>
</div>
<!-- Floating Action Button for mobile/context -->
<button class="absolute bottom-8 right-8 w-14 h-14 bg-primary text-on-primary rounded-full flex items-center justify-center shadow-2xl hover:scale-105 active:scale-95 transition-transform z-20">
<span class="material-symbols-outlined text-3xl" data-icon="add">add</span>
</button>
</section>
</main>
</body></html>

BIN
stitch/_6/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

162
stitch/_7/code.html Normal file
View File

@@ -0,0 +1,162 @@
<!DOCTYPE html>
<html class="dark" lang="ru"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Knot Panel - Вход</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"on-primary": "#003355",
"surface-variant": "#353534",
"surface-tint": "#9acbff",
"secondary-fixed-dim": "#c7c6ca",
"on-secondary": "#2f3033",
"surface-bright": "#393939",
"background": "#131313",
"secondary-container": "#48494c",
"primary": "#9acbff",
"on-secondary-fixed-variant": "#46474a",
"secondary-fixed": "#e3e2e6",
"on-surface-variant": "#c1c6d7",
"on-primary-container": "#002c4a",
"on-primary-fixed": "#001d34",
"inverse-surface": "#e5e2e1",
"surface-dim": "#131313",
"on-background": "#e5e2e1",
"inverse-on-surface": "#313030",
"outline-variant": "#414755",
"surface-container-highest": "#353534",
"on-tertiary": "#003911",
"tertiary": "#53e16f",
"secondary": "#c7c6ca",
"primary-fixed": "#cfe5ff",
"primary-fixed-dim": "#9acbff",
"on-primary-fixed-variant": "#004a79",
"surface-container-low": "#1c1b1b",
"on-surface": "#e5e2e1",
"on-secondary-container": "#b9b8bc",
"primary-container": "#3096e5",
"surface-container": "#201f1f",
"tertiary-container": "#00a741",
"tertiary-fixed-dim": "#53e16f",
"on-secondary-fixed": "#1a1b1e",
"error-container": "#93000a",
"surface": "#131313",
"on-tertiary-container": "#00320e",
"on-error-container": "#ffdad6",
"on-error": "#690005",
"on-tertiary-fixed": "#002107",
"inverse-primary": "#00629e",
"tertiary-fixed": "#72fe88",
"on-tertiary-fixed-variant": "#00531c",
"error": "#ffb4ab",
"outline": "#8b90a0",
"surface-container-lowest": "#0e0e0e",
"surface-container-high": "#2a2a2a"
},
fontFamily: {
"headline": ["Inter"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: { "DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px" },
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.knot-gradient-bg {
background: radial-gradient(circle at 20% 30%, rgba(48, 150, 229, 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(154, 203, 255, 0.1) 0%, transparent 50%);
}
.glass-card {
background: rgba(32, 31, 31, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
</style>
</head>
<body class="bg-background text-on-surface font-body antialiased min-h-screen flex items-center justify-center knot-gradient-bg selection:bg-primary/30">
<!-- Theme Switcher (Corner) -->
<div class="fixed top-8 right-8 z-50">
<button class="w-12 h-12 flex items-center justify-center rounded-full bg-surface-container hover:bg-surface-bright transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] active:scale-95 group">
<span class="material-symbols-outlined text-on-surface-variant group-hover:text-primary">dark_mode</span>
</button>
</div>
<!-- Main Auth Container -->
<main class="relative w-full max-w-[440px] px-6 py-12">
<!-- Logo Section -->
<div class="flex flex-col items-center mb-12">
<div class="relative mb-6">
<!-- Abstract "Knot" visual element -->
<div class="absolute -inset-4 bg-primary/20 blur-2xl rounded-full opacity-50"></div>
<div class="relative w-16 h-16 flex items-center justify-center bg-gradient-to-br from-primary to-primary-container rounded-xl rotate-12 shadow-xl shadow-primary/20">
<span class="material-symbols-outlined text-on-primary-container text-4xl" style="font-variation-settings: 'FILL' 1;">hub</span>
</div>
</div>
<h1 class="text-4xl font-extrabold tracking-tighter text-on-surface mb-2">Knot Admin</h1>
<p class="text-on-surface-variant text-sm font-medium opacity-70">Управление вашей инфраструктурой</p>
</div>
<!-- Auth Card -->
<div class="glass-card rounded-3xl p-8 md:p-10 shadow-2xl border border-outline-variant/10">
<form class="space-y-6">
<!-- Login/Email Field -->
<div class="space-y-2">
<label class="block text-xs font-bold uppercase tracking-widest text-on-surface-variant ml-1">Логин или Email</label>
<div class="relative group">
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<span class="material-symbols-outlined text-on-surface-variant group-focus-within:text-primary transition-colors">person</span>
</div>
<input class="w-full bg-surface-container-highest/50 border-none rounded-2xl py-4 pl-12 pr-4 text-on-surface placeholder:text-on-surface-variant/40 focus:ring-2 focus:ring-primary/30 focus:bg-surface-bright transition-all duration-300" placeholder="Введите ваш логин" type="text"/>
</div>
</div>
<!-- Password Field -->
<div class="space-y-2">
<div class="flex justify-between items-end px-1">
<label class="block text-xs font-bold uppercase tracking-widest text-on-surface-variant">Пароль</label>
<a class="text-xs font-semibold text-primary hover:text-primary-fixed transition-colors" href="#">Забыли пароль?</a>
</div>
<div class="relative group">
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<span class="material-symbols-outlined text-on-surface-variant group-focus-within:text-primary transition-colors">lock</span>
</div>
<input class="w-full bg-surface-container-highest/50 border-none rounded-2xl py-4 pl-12 pr-12 text-on-surface placeholder:text-on-surface-variant/40 focus:ring-2 focus:ring-primary/30 focus:bg-surface-bright transition-all duration-300" placeholder="••••••••" type="password"/>
<button class="absolute inset-y-0 right-0 pr-4 flex items-center text-on-surface-variant hover:text-on-surface transition-colors" type="button">
<span class="material-symbols-outlined">visibility</span>
</button>
</div>
</div>
<!-- Action Button -->
<button class="w-full bg-gradient-to-br from-primary to-primary-container text-on-primary-container font-bold py-4 rounded-2xl shadow-lg shadow-primary/10 hover:shadow-primary/20 hover:opacity-90 active:scale-[0.98] transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] mt-4" type="submit">
Войти в систему
</button>
</form>
<!-- Footer Links -->
<div class="mt-10 pt-8 border-t border-outline-variant/10 text-center">
<p class="text-on-surface-variant text-sm font-medium">
Нет аккаунта?
<a class="text-primary font-bold ml-1 hover:underline underline-offset-4 decoration-2" href="#">Зарегистрироваться</a>
</p>
</div>
</div>
<!-- Bottom Metadata -->
<div class="mt-8 flex justify-center gap-6 opacity-40">
<span class="text-[10px] font-bold uppercase tracking-widest cursor-default">v4.2.0 Stable</span>
<span class="text-[10px] font-bold uppercase tracking-widest cursor-default">© 2024 Knot Ecosystem</span>
</div>
</main>
<!-- Decorative Elements (Blobs/Knots) -->
<div class="fixed top-[-10%] left-[-10%] w-[40%] h-[40%] bg-primary/5 rounded-full blur-[120px] pointer-events-none"></div>
<div class="fixed bottom-[-10%] right-[-10%] w-[40%] h-[40%] bg-tertiary/5 rounded-full blur-[120px] pointer-events-none"></div>
</body></html>

BIN
stitch/_7/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 KiB

259
stitch/_8/code.html Normal file
View File

@@ -0,0 +1,259 @@
<!DOCTYPE html>
<html class="dark" lang="ru"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Knot - Истории</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"inverse-on-surface": "#313030",
"surface-container": "#201f1f",
"on-secondary": "#2f3033",
"tertiary-fixed-dim": "#53e16f",
"error": "#ffb4ab",
"primary-container": "#3096e5",
"secondary-container": "#48494c",
"surface-dim": "#131313",
"error-container": "#93000a",
"inverse-primary": "#00629e",
"inverse-surface": "#e5e2e1",
"on-tertiary-fixed": "#002107",
"secondary-fixed": "#e3e2e6",
"on-tertiary": "#003911",
"on-secondary-fixed": "#1a1b1e",
"surface-container-low": "#1c1b1b",
"on-primary-fixed-variant": "#004a79",
"on-surface": "#e5e2e1",
"on-secondary-container": "#b9b8bc",
"tertiary-fixed": "#72fe88",
"secondary-fixed-dim": "#c7c6ca",
"primary-fixed-dim": "#9acbff",
"on-error": "#690005",
"surface-container-high": "#2a2a2a",
"on-primary-fixed": "#001d34",
"on-primary-container": "#002c4a",
"on-primary": "#003355",
"surface-container-lowest": "#0e0e0e",
"on-surface-variant": "#c1c6d7",
"surface-variant": "#353534",
"secondary": "#c7c6ca",
"background": "#131313",
"surface-container-highest": "#353534",
"on-tertiary-fixed-variant": "#00531c",
"surface": "#131313",
"tertiary": "#53e16f",
"on-error-container": "#ffdad6",
"on-tertiary-container": "#00320e",
"primary-fixed": "#cfe5ff",
"outline": "#8b90a0",
"surface-bright": "#393939",
"on-background": "#e5e2e1",
"primary": "#9acbff",
"outline-variant": "#414755",
"surface-tint": "#9acbff",
"tertiary-container": "#00a741",
"on-secondary-fixed-variant": "#46474a"
},
fontFamily: {
"headline": ["Inter"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
}
.cubic-bezier-standard { transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); }
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
.knot-gradient { background: linear-gradient(135deg, #9ACBFF 0%, #3096E5 100%); }
.glass-header { background: rgba(19, 19, 19, 0.6); backdrop-filter: blur(20px); }
.mask-gradient { mask-image: linear-gradient(to top, transparent, black 20%); }
</style>
</head>
<body class="bg-background text-on-background font-body selection:bg-primary/30 antialiased overflow-hidden h-screen">
<!-- Sidebar Shell (Shared Component Mapping) -->
<aside class="fixed left-0 top-0 h-full z-40 flex flex-col border-none bg-[#201F1F] dark:bg-[#201F1F] w-20 items-center py-6 shadow-none font-['Inter'] text-sm leading-[1.6] antialiased">
<div class="mb-10">
<span class="text-2xl font-black text-[#9ACBFF] tracking-tighter">K</span>
</div>
<nav class="flex flex-col gap-8 flex-1">
<a class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 cubic-bezier-standard" href="#">
<span class="material-symbols-outlined mb-1" data-icon="chat">chat</span>
<span class="text-[10px]">Чаты</span>
</a>
<a class="flex flex-col items-center justify-center bg-[#3096E5]/20 text-[#9ACBFF] rounded-xl p-3 transition-all duration-300 scale-95 active:scale-90 cubic-bezier-standard" href="#">
<span class="material-symbols-outlined mb-1" data-icon="call">call</span>
<span class="text-[10px]">Звонки</span>
</a>
<a class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 cubic-bezier-standard" href="#">
<span class="material-symbols-outlined mb-1" data-icon="person">person</span>
<span class="text-[10px]">Контакты</span>
</a>
<a class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 cubic-bezier-standard" href="#">
<span class="material-symbols-outlined mb-1" data-icon="archive">archive</span>
<span class="text-[10px]">Архив</span>
</a>
</nav>
<div class="mt-auto">
<a class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90 cubic-bezier-standard" href="#">
<span class="material-symbols-outlined" data-icon="settings">settings</span>
<span class="text-[10px] mt-1">Настройки</span>
</a>
</div>
</aside>
<!-- Main Content Canvas -->
<main class="ml-20 h-screen grid grid-cols-1 md:grid-cols-2 gap-4 p-4 overflow-hidden">
<!-- View 1: Create Story (Camera/Upload Interface) -->
<section class="relative h-full w-full rounded-3xl overflow-hidden bg-surface-container-lowest group">
<!-- Camera Feed Mockup -->
<div class="absolute inset-0 z-0">
<img alt="Camera preview" class="w-full h-full object-cover" data-alt="First-person view of a high-end camera lens capturing a blurred neon city street at night with colorful bokeh lights" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAvxl9nvoduy-T4Wxx3dcZLk-nmIQoYSyWU_iNZeXoe-TBswOEHGny0Ugilhxu3e6Cv2TWrQYdkpF2ZyJ54MWIXN-HFyFe-8f0m7g5smLOPWhNblNfqmxejVQszcoRDNvAK7jz3hlZ2HJS9NQAtaYLFOVo_HPUCAxTPhlW08KUUwY0SRxO-BfeFyjt9BNvuP7UsgdDmrDVHHp2VR932nhmlSJ30DrIzQE72Z8jUesAhlFyrl5hR3OdX7Ekp1ZzPh9koSAdrPsmOL2o"/>
</div>
<!-- Header Tools -->
<div class="absolute top-0 inset-x-0 p-6 flex justify-between items-start z-10 glass-header">
<button class="p-2 bg-white/10 rounded-full backdrop-blur-md text-white hover:bg-white/20 transition-all active:scale-90">
<span class="material-symbols-outlined" data-icon="close">close</span>
</button>
<div class="flex gap-4">
<button class="p-3 bg-white/10 rounded-full backdrop-blur-md text-white hover:bg-white/20 transition-all active:scale-90 flex items-center justify-center">
<span class="material-symbols-outlined" data-icon="text_fields">text_fields</span>
</button>
<button class="p-3 bg-white/10 rounded-full backdrop-blur-md text-white hover:bg-white/20 transition-all active:scale-90 flex items-center justify-center">
<span class="material-symbols-outlined" data-icon="draw">draw</span>
</button>
<button class="p-3 bg-white/10 rounded-full backdrop-blur-md text-white hover:bg-white/20 transition-all active:scale-90 flex items-center justify-center">
<span class="material-symbols-outlined" data-icon="sticky_note_2">sticky_note_2</span>
</button>
</div>
</div>
<!-- Stickers/Assets Overlay (Floating Asymmetry) -->
<div class="absolute top-1/4 left-10 z-20 transform -rotate-12 bg-white/20 backdrop-blur-lg px-4 py-2 rounded-xl shadow-2xl border border-white/30">
<span class="text-xl font-bold text-white tracking-tight">В ПУТИ 🚀</span>
</div>
<!-- Bottom Actions -->
<div class="absolute bottom-0 inset-x-0 p-8 z-10 bg-gradient-to-t from-black/80 to-transparent flex items-center justify-between">
<div class="flex gap-4">
<div class="w-12 h-12 rounded-xl bg-white/10 backdrop-blur-md border border-white/20 flex items-center justify-center cursor-pointer hover:bg-white/20 transition-colors">
<span class="material-symbols-outlined text-white" data-icon="photo_library">photo_library</span>
</div>
</div>
<!-- Main Shutter/Capture button -->
<button class="w-20 h-20 rounded-full border-4 border-white flex items-center justify-center group active:scale-95 transition-transform">
<div class="w-16 h-16 rounded-full bg-white scale-90 group-hover:scale-100 transition-transform"></div>
</button>
<button class="knot-gradient text-on-primary-container px-6 py-3 rounded-full font-bold shadow-lg flex items-center gap-2 active:scale-95 transition-all">
<span>Отправить</span>
<span class="material-symbols-outlined text-sm" data-icon="send">send</span>
</button>
</div>
</section>
<!-- View 2: View Story (Fullscreen Viewer) -->
<section class="relative h-full w-full rounded-3xl overflow-hidden bg-surface-container-lowest">
<!-- Story Content -->
<div class="absolute inset-0 z-0">
<img alt="Story content" class="w-full h-full object-cover" data-alt="Cinematic wide shot of a futuristic minimalist mountain cabin under a starry night sky with warm interior lighting glowing through glass" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAR_CvEyvZtfEW3ZjtA2gVlfr83fVVTecwlZAcdFI_PwTDeZp-35BK4QKLQZzGfudLfKfszY1sP5sqPjwvrKs-gcQdO3wE0Fu1NSn9vTbcTIrCmWaD3aYNbTARvUdCzz10n-jTWsEyA6s5Tn9ghHmQke7pNGsAFLHiTUlJk0WVbLTdSaboyNCo7Iwfgirf-4RXGCAp7ioVq1Zd849Hx6kmUXgexo3DeM3jXlKWWHp4-pXcXYaqnLyf9kjbCTPTgylu0ivuF4a1eYG0"/>
</div>
<!-- Top Overlay: Progress and Info -->
<div class="absolute top-0 inset-x-0 p-4 z-20 space-y-4 glass-header">
<!-- Progress Bars -->
<div class="flex gap-1.5 px-2">
<div class="h-1 flex-1 bg-white/30 rounded-full overflow-hidden">
<div class="h-full bg-white w-full"></div>
</div>
<div class="h-1 flex-1 bg-white/30 rounded-full overflow-hidden">
<div class="h-full bg-white w-2/3"></div>
</div>
<div class="h-1 flex-1 bg-white/30 rounded-full overflow-hidden"></div>
</div>
<!-- Author Info -->
<div class="flex items-center justify-between px-2">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full border-2 border-primary p-0.5">
<img alt="User" class="w-full h-full rounded-full object-cover" data-alt="Professional studio portrait of a stylish man with short beard and friendly expression, neutral gray background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuD2gBufEksmGDe5L1msU-XyHaJreehRLPF4tZinwOPG423ORKFwgKgSuSm3VNf0iL74RA8m24qWMNED1Kw27CXYIJwhtnsBBavyNKYTbrhnF9jO3xvD-bkmHAWrNJUrQwTteyrIGmkbZoPsTEm2nfGZ_9JCQWXUB-GgeM-gqVyZRMUxaMxFy5xgCOF-XYdnYEIYWYsvqx5kVuhlWrkydTLvCaGzTs2fszWiwVUWx2CY-pTRBgYbH6fL0Xb4tRtB2cq5Ec3dJrk5V5E"/>
</div>
<div>
<h3 class="text-sm font-bold text-white leading-tight">Артем Волков</h3>
<p class="text-[10px] text-white/70 uppercase tracking-widest">2 часа назад</p>
</div>
</div>
<div class="flex gap-2">
<button class="text-white opacity-70 hover:opacity-100 transition-opacity">
<span class="material-symbols-outlined" data-icon="more_horiz">more_horiz</span>
</button>
<button class="text-white opacity-70 hover:opacity-100 transition-opacity">
<span class="material-symbols-outlined" data-icon="close">close</span>
</button>
</div>
</div>
</div>
<!-- Center Gestures Hint (Left/Right) -->
<div class="absolute inset-0 z-10 flex">
<div class="w-1/3 h-full cursor-pointer"></div>
<div class="w-1/3 h-full cursor-pointer"></div>
<div class="w-1/3 h-full cursor-pointer"></div>
</div>
<!-- Bottom Content: Reply and Reactions -->
<div class="absolute bottom-0 inset-x-0 p-8 z-20 bg-gradient-to-t from-black/90 via-black/40 to-transparent space-y-6">
<!-- Reaction Floating Emojis -->
<div class="flex justify-center gap-4 mb-4">
<button class="w-10 h-10 rounded-full bg-white/10 backdrop-blur-xl border border-white/20 flex items-center justify-center text-xl hover:scale-125 transition-transform active:scale-90">🔥</button>
<button class="w-10 h-10 rounded-full bg-white/10 backdrop-blur-xl border border-white/20 flex items-center justify-center text-xl hover:scale-125 transition-transform active:scale-90">😍</button>
<button class="w-10 h-10 rounded-full bg-white/10 backdrop-blur-xl border border-white/20 flex items-center justify-center text-xl hover:scale-125 transition-transform active:scale-90">😂</button>
<button class="w-10 h-10 rounded-full bg-white/10 backdrop-blur-xl border border-white/20 flex items-center justify-center text-xl hover:scale-125 transition-transform active:scale-90">👏</button>
<button class="w-10 h-10 rounded-full bg-white/10 backdrop-blur-xl border border-white/20 flex items-center justify-center text-xl hover:scale-125 transition-transform active:scale-90">💯</button>
</div>
<!-- Reply Field -->
<div class="flex items-center gap-4">
<div class="flex-1 bg-white/10 backdrop-blur-2xl rounded-full border border-white/20 px-6 py-3.5 group focus-within:bg-white/20 transition-all">
<input class="bg-transparent border-none w-full text-white placeholder-white/50 focus:ring-0 text-sm p-0" placeholder="Ответить на историю..." type="text"/>
</div>
<button class="w-12 h-12 flex items-center justify-center text-white opacity-70 hover:opacity-100 hover:scale-110 transition-all">
<span class="material-symbols-outlined" data-icon="favorite" style="font-variation-settings: 'FILL' 0;">favorite</span>
</button>
<button class="w-12 h-12 flex items-center justify-center text-white opacity-70 hover:opacity-100 hover:scale-110 transition-all">
<span class="material-symbols-outlined" data-icon="share">share</span>
</button>
</div>
</div>
</section>
</main>
<!-- Contextual FAB Suppression (as per manual mandate - not needed for this transactional/stories layout) -->
<style>
/* Precision typography for Cyrillic */
h1, h2, h3, .headline-sm {
letter-spacing: -0.025em;
line-height: 1.2;
}
/* Smooth backdrop transitions */
.glass-header {
transition: background 0.3s ease, backdrop-filter 0.3s ease;
}
/* Blob corners for Knot aesthetic */
.chat-bubble-custom {
border-radius: 1.5rem 1.5rem 1.5rem 0.25rem;
}
</style>
</body></html>

BIN
stitch/_8/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

159
stitch/_9/code.html Normal file
View File

@@ -0,0 +1,159 @@
<!DOCTYPE html>
<html class="dark" lang="ru"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Knot Messenger - Вход</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"surface-variant": "#353534",
"primary-container": "#3096e5",
"on-surface-variant": "#c1c6d7",
"error-container": "#93000a",
"surface-container-low": "#1c1b1b",
"tertiary": "#53e16f",
"tertiary-fixed-dim": "#53e16f",
"on-tertiary-fixed": "#002107",
"secondary-fixed-dim": "#c7c6ca",
"outline-variant": "#414755",
"on-tertiary-fixed-variant": "#00531c",
"surface-container": "#201f1f",
"on-primary": "#003355",
"on-error-container": "#ffdad6",
"background": "#131313",
"error": "#ffb4ab",
"outline": "#8b90a0",
"surface-container-lowest": "#0e0e0e",
"surface-container-highest": "#353534",
"on-secondary-fixed-variant": "#46474a",
"secondary-container": "#48494c",
"on-surface": "#e5e2e1",
"surface-dim": "#131313",
"on-background": "#e5e2e1",
"surface": "#131313",
"secondary": "#c7c6ca",
"on-primary-fixed": "#001d34",
"surface-tint": "#9acbff",
"surface-bright": "#393939",
"secondary-fixed": "#e3e2e6",
"primary-fixed-dim": "#9acbff",
"on-secondary-container": "#b9b8bc",
"on-primary-container": "#002c4a",
"primary": "#9acbff",
"on-secondary": "#2f3033",
"tertiary-fixed": "#72fe88",
"inverse-primary": "#00629e",
"tertiary-container": "#00a741",
"on-primary-fixed-variant": "#004a79",
"on-error": "#690005",
"inverse-surface": "#e5e2e1",
"on-tertiary-container": "#00320e",
"on-tertiary": "#003911",
"surface-container-high": "#2a2a2a",
"primary-fixed": "#cfe5ff",
"on-secondary-fixed": "#1a1b1e",
"inverse-on-surface": "#313030"
},
fontFamily: {
"headline": ["Inter"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
vertical-align: middle;
}
.knot-gradient-bg {
background: radial-gradient(circle at 0% 0%, rgba(48, 150, 229, 0.08) 0%, transparent 50%),
radial-gradient(circle at 100% 100%, rgba(154, 203, 255, 0.05) 0%, transparent 50%);
}
.btn-knot {
background: linear-gradient(135deg, #9ACBFF 0%, #3096E5 100%);
}
</style>
</head>
<body class="bg-background text-on-background font-body knot-gradient-bg min-h-screen flex flex-col">
<!-- Top Navigation (Shell suppressed for transactional screen as per protocol) -->
<header class="fixed top-0 w-full flex justify-between items-center px-6 py-8 z-50">
<div class="text-2xl font-black tracking-tighter text-primary italic">Knot</div>
<div>
<button class="text-on-surface-variant hover:text-primary transition-colors">
<span class="material-symbols-outlined" data-icon="language">language</span>
</button>
</div>
</header>
<main class="flex-grow flex items-center justify-center px-4 relative overflow-hidden">
<!-- Abstract Decoration (The Kinetic Thread) -->
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] opacity-20 pointer-events-none">
<svg class="w-full h-full" viewbox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<path d="M40,100 C40,20 160,180 160,100 C160,20 40,180 40,100 Z" fill="none" stroke="#9ACBFF" stroke-width="0.5"></path>
</svg>
</div>
<div class="w-full max-w-md z-10">
<div class="mb-12 text-center md:text-left">
<h1 class="text-5xl font-extrabold tracking-tight mb-4 text-on-surface leading-tight">Вход в Knot</h1>
<p class="text-on-surface-variant text-lg">Добро пожаловать в единственную нить общения.</p>
</div>
<form class="space-y-6">
<!-- Username Field -->
<div class="space-y-2">
<label class="block text-sm font-medium text-on-surface-variant ml-4">Имя пользователя</label>
<div class="relative group">
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-on-surface-variant group-focus-within:text-primary transition-colors">
<span class="material-symbols-outlined" data-icon="person">person</span>
</div>
<input class="w-full bg-surface-container-highest/30 border-none rounded-xl py-4 pl-12 pr-4 text-on-surface placeholder:text-outline/50 focus:ring-2 focus:ring-primary/20 focus:bg-surface-bright/40 transition-all outline-none" placeholder="username" type="text"/>
</div>
</div>
<!-- Password Field -->
<div class="space-y-2">
<div class="flex justify-between items-center px-4">
<label class="block text-sm font-medium text-on-surface-variant">Пароль</label>
<a class="text-xs font-semibold text-primary hover:opacity-80 transition-opacity" href="#">Забыли пароль?</a>
</div>
<div class="relative group">
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-on-surface-variant group-focus-within:text-primary transition-colors">
<span class="material-symbols-outlined" data-icon="lock">lock</span>
</div>
<input class="w-full bg-surface-container-highest/30 border-none rounded-xl py-4 pl-12 pr-12 text-on-surface placeholder:text-outline/50 focus:ring-2 focus:ring-primary/20 focus:bg-surface-bright/40 transition-all outline-none" placeholder="••••••••" type="password"/>
<button class="absolute inset-y-0 right-0 pr-4 flex items-center text-on-surface-variant hover:text-on-surface transition-colors" type="button">
<span class="material-symbols-outlined" data-icon="visibility">visibility</span>
</button>
</div>
</div>
<!-- Login Button -->
<div class="pt-4">
<button class="btn-knot w-full py-4 rounded-xl text-on-primary-container font-bold text-lg shadow-[0_10px_20px_-5px_rgba(48,150,229,0.3)] hover:scale-[0.98] active:scale-[0.96] transition-all duration-300 ease-[cubic-bezier(0.4,0,0.2,1)]" type="submit">
Войти
</button>
</div>
</form>
<div class="mt-12 text-center">
<p class="text-on-surface-variant">
Нет аккаунта?
<a class="text-primary font-bold ml-1 hover:underline decoration-primary/30 underline-offset-4" href="#">Зарегистрироваться</a>
</p>
</div>
</div>
</main>
<footer class="p-8 flex justify-center items-center space-x-6">
<div class="h-[1px] w-12 bg-outline-variant/20"></div>
<div class="text-[10px] uppercase tracking-[0.2em] text-outline/40 font-bold">Encrypted &amp; Secure</div>
<div class="h-[1px] w-12 bg-outline-variant/20"></div>
</footer>
<!-- Bottom Nav (Suppressed for Login Page as per Contextual Rules) -->
</body></html>

BIN
stitch/_9/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

View File

@@ -0,0 +1,83 @@
```markdown
# Design System Strategy: The Interconnected Thread
## 1. Overview & Creative North Star: "Kinetic Connection"
This design system moves away from the static, boxy nature of traditional messengers. Our North Star is **Kinetic Connection**—a philosophy where the UI is not a collection of separate screens, but a single, fluid thread that loops and weaves as the user navigates.
Inspired by the concept of a "Knot," the interface favors **organic flow over rigid containment**. We break the "template" look by using intentional asymmetry in message grouping, generous breathing room (white space) that suggests a premium editorial layout, and high-contrast typography scales that make the Cyrillic alphabet look authoritative and modern.
---
## 2. Colors & The "No-Line" Rule
The palette is rooted in deep obsidian tones and technical blues, designed to recede and let the conversation take center stage.
### The "No-Line" Rule
**Explicit Instruction:** You are prohibited from using 1px solid borders (`#outline`) to define sections. Traditional dividers are a sign of lazy design. Boundaries must be defined through:
1. **Background Shifts:** Place a `surface-container-low` list item against a `surface` background.
2. **Tonal Transitions:** Use `surface-container-lowest` for the main chat thread and `surface-container` for the side navigation to create natural separation.
### Surface Hierarchy & Nesting
Treat the UI as a series of stacked sheets of fine, semi-translucent paper.
* **Base Layer:** `surface` (#131313) - The absolute floor.
* **Active Interaction Layer:** `surface-container` (#201F1F) - The main workspace.
* **Floating Elements:** Use `surface-bright` with a 60% opacity and a 20px backdrop-blur to create a "Glassmorphism" effect for headers and context menus.
### Signature Textures
Main Action Buttons (CTAs) should never be a flat hex code. Apply a subtle linear gradient from `primary` (#9ACBFF) to `primary_container` (#3096E5) at a 135-degree angle. This adds "soul" and a tactile, liquid quality to the "Knot" identity.
---
## 3. Typography: The Editorial Voice
We use **Inter** as our foundational typeface. For the Russian language interface, Inters tall x-height and neutral terminals provide the clarity needed for long-form messaging.
* **Display (The Brand Moment):** `display-lg` (3.5rem). Use this for empty states or welcome screens. It should feel like a magazine cover.
* **Headlines (Navigation):** `headline-sm` (1.5rem). Used for "Чаты" (Chats) or "Настройки" (Settings). Bold, confident, and high-contrast.
* **Body (The Conversation):** `body-lg` (1.0rem). The primary reading size. We use a generous line-height (1.6) to prevent Cyrillic characters from feeling cluttered.
* **Labels (The Metadata):** `label-sm` (0.68rem). Used for timestamps and "Seen" indicators. These should be rendered in `on-surface-variant` to recede visually.
---
## 4. Elevation & Depth: Tonal Layering
We abandon traditional drop shadows in favor of **Ambient Depth**.
* **The Layering Principle:** To lift a chat bubble, do not add a shadow. Instead, place a `primary_container` bubble on a `surface_container_low` background. The color contrast provides the elevation.
* **Ghost Borders:** If an element (like a search bar) disappears into the background, use a "Ghost Border": the `outline-variant` token at **15% opacity**. Never 100%.
* **Ambient Shadows:** For floating modals (e.g., User Profile), use a massive blur (40px) with the color `on_primary_fixed` at 5% opacity. It should feel like a soft glow rather than a shadow.
---
## 5. Components
### Chat Bubbles (The Core Component)
* **Style:** No sharp corners. Use `xl` (1.5rem) for the outer corners and `sm` (0.25rem) for the "tail" corner to create a "blob" or "knot" feel.
* **Outgoing:** `primary_container` text with `on_primary_container`.
* **Incoming:** `secondary_container` text with `on_secondary_container`.
* **Spacing:** Use `spacing-2` between messages in a cluster; `spacing-4` between different users.
### Input Fields
* **Structure:** A "borderless" pill shape using `surface_container_highest`.
* **Interaction:** Upon focus, the background shifts to `surface_bright` and the `primary` accent color glows subtly at the cursor point.
### Lists & Navigation
* **No Dividers:** Separate chat threads using `spacing-3.5` vertical padding. The user's eye will follow the alignment of the Avatars (the "Knot" anchors) rather than a line.
* **Avatars:** Always circular (`full` roundedness) with a 2px `outline-variant` at 10% opacity to ensure they pop against dark backgrounds.
### Action Chips
* **Style:** For reactions or filters, use `tertiary_container` for a vibrant, interactive "pop" that breaks the monochromatic gray scale.
---
## 6. Do's and Don'ts
### Do:
* **Embrace Asymmetry:** Align timestamps to the far right and message text to the left to create a dynamic, zig-zag visual path.
* **Use Micro-interactions:** When a user scrolls, the "Knot" logo should subtly rotate or tighten, reinforcing the brand identity.
* **Prioritize Russian Typography:** Ensure "ё" and "й" have enough leading (vertical space) so diacritics don't touch the line above.
### Don't:
* **Don't use pure black (#000):** It kills the depth. Always use the `surface` tokens.
* **Don't use 1px dividers:** If you feel the need for a line, use a background color change instead.
* **Don't use standard easing:** Use "Cubic Bezier (0.4, 0, 0.2, 1)" for all transitions. It should feel like the UI is "sliding on ice," not snapping into place.
### Accessibility Note:
While we favor minimalism, ensure that text in `on-surface-variant` still meets a 4.5:1 contrast ratio against `surface-container` for readability. Minimalist does not mean invisible.```

212
stitch/pip/code.html Normal file
View File

@@ -0,0 +1,212 @@
<!DOCTYPE html>
<html class="dark" lang="ru"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&amp;family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"inverse-on-surface": "#313030",
"surface-container": "#201f1f",
"on-secondary": "#2f3033",
"tertiary-fixed-dim": "#53e16f",
"error": "#ffb4ab",
"primary-container": "#3096e5",
"secondary-container": "#48494c",
"surface-dim": "#131313",
"error-container": "#93000a",
"inverse-primary": "#00629e",
"inverse-surface": "#e5e2e1",
"on-tertiary-fixed": "#002107",
"secondary-fixed": "#e3e2e6",
"on-tertiary": "#003911",
"on-secondary-fixed": "#1a1b1e",
"surface-container-low": "#1c1b1b",
"on-primary-fixed-variant": "#004a79",
"on-surface": "#e5e2e1",
"on-secondary-container": "#b9b8bc",
"tertiary-fixed": "#72fe88",
"secondary-fixed-dim": "#c7c6ca",
"primary-fixed-dim": "#9acbff",
"on-error": "#690005",
"surface-container-high": "#2a2a2a",
"on-primary-fixed": "#001d34",
"on-primary-container": "#002c4a",
"on-primary": "#003355",
"surface-container-lowest": "#0e0e0e",
"on-surface-variant": "#c1c6d7",
"surface-variant": "#353534",
"secondary": "#c7c6ca",
"background": "#131313",
"surface-container-highest": "#353534",
"on-tertiary-fixed-variant": "#00531c",
"surface": "#131313",
"tertiary": "#53e16f",
"on-error-container": "#ffdad6",
"on-tertiary-container": "#00320e",
"primary-fixed": "#cfe5ff",
"outline": "#8b90a0",
"surface-bright": "#393939",
"on-background": "#e5e2e1",
"primary": "#9acbff",
"outline-variant": "#414755",
"surface-tint": "#9acbff",
"tertiary-container": "#00a741",
"on-secondary-fixed-variant": "#46474a"
},
fontFamily: {
"headline": ["Inter"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
.slide-on-ice { transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); }
.tonal-transition-no-border { background: linear-gradient(to bottom, rgba(19, 19, 19, 0.8), rgba(19, 19, 19, 0)); }
.contrast-bg-shift-no-border { position: relative; }
.pip-glow { box-shadow: 0 0 40px rgba(154, 203, 255, 0.1); }
</style>
</head>
<body class="bg-background text-on-surface font-body overflow-hidden">
<!-- SideNavBar Anchor -->
<nav class="fixed left-0 top-0 h-full z-40 flex flex-col border-none bg-[#201F1F] dark:bg-[#201F1F] w-20 items-center py-6 font-['Inter'] text-sm leading-[1.6] antialiased">
<div class="mb-10">
<span class="text-2xl font-black text-[#9ACBFF] tracking-tighter">Knot</span>
</div>
<div class="flex flex-col gap-6 w-full px-2">
<!-- Active: Чаты -->
<button class="flex flex-col items-center justify-center bg-[#3096E5]/20 text-[#9ACBFF] rounded-xl p-3 transition-all duration-300 scale-95 active:scale-90 cubic-bezier(0.4,0,0.2,1)">
<span class="material-symbols-outlined mb-1" data-icon="chat" style="font-variation-settings: 'FILL' 1;">chat</span>
<span class="text-[10px]">Чаты</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90">
<span class="material-symbols-outlined mb-1" data-icon="call">call</span>
<span class="text-[10px]">Звонки</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90">
<span class="material-symbols-outlined mb-1" data-icon="person">person</span>
<span class="text-[10px]">Контакты</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90">
<span class="material-symbols-outlined mb-1" data-icon="archive">archive</span>
<span class="text-[10px]">Архив</span>
</button>
<button class="flex flex-col items-center justify-center text-[#939090] p-3 mt-auto hover:bg-[#201F1F] hover:text-[#9ACBFF] transition-colors scale-95 active:scale-90">
<span class="material-symbols-outlined mb-1" data-icon="settings">settings</span>
<span class="text-[10px]">Настройки</span>
</button>
</div>
</nav>
<!-- TopAppBar Anchor -->
<header class="fixed top-0 right-0 left-20 z-30 flex items-center justify-between px-8 bg-[#131313]/60 backdrop-blur-xl h-16 shadow-[0_40px_40px_rgba(255,255,255,0.05)]">
<h1 class="headline-sm font-bold text-[#FFFFFF] tracking-tight">Чаты</h1>
<div class="flex items-center gap-6">
<div class="relative flex items-center bg-surface-container-highest/40 px-4 py-2 rounded-full border border-outline-variant/15">
<span class="material-symbols-outlined text-on-surface-variant text-xl mr-2" data-icon="search">search</span>
<input class="bg-transparent border-none focus:ring-0 p-0 text-sm w-48 text-on-surface" placeholder="Поиск" type="text"/>
</div>
<div class="flex items-center gap-4 text-[#939090]">
<button class="hover:opacity-80 transition-opacity"><span class="material-symbols-outlined" data-icon="videocam">videocam</span></button>
<button class="hover:opacity-80 transition-opacity"><span class="material-symbols-outlined" data-icon="call">call</span></button>
<button class="hover:opacity-80 transition-opacity"><span class="material-symbols-outlined" data-icon="more_vert">more_vert</span></button>
</div>
<div class="w-10 h-10 rounded-full overflow-hidden border border-outline-variant/20">
<img alt="User profile" class="w-full h-full object-cover" data-alt="Close up portrait of a professional man in a dark turtleneck against a minimalist grey background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBkI_gl3mtmp5RGKD8tn3gHGGXNKFOCU-lLShbMjykwUuZFTaNXrLII7SwxG8EOZ-DCXCT41D4UiafLOr9nsQi-_0uVYKuM6pxO3myXo3PE7nb_QzepTeSTFKMWpXLCyErYNMNnCNxk4BGuZ2DiKrv_6tJVWUPZ03vFmdgdySNqfWfHGuThts63m8SFLNh8n6kSNG9z3t_pVCw2rEF47w0uykhBORi2Gww6AT3rFxWC57bJbrabQXiYzRE92scR6Q99eCv59uo5nCE"/>
</div>
</div>
</header>
<!-- Main Content (Blurred Canvas) -->
<main class="ml-20 pt-20 p-8 h-screen overflow-hidden filter blur-md opacity-40">
<div class="max-w-4xl mx-auto space-y-6">
<!-- Simulated Chat List Items -->
<div class="flex items-center gap-5 p-4 bg-surface-container rounded-3xl">
<div class="w-14 h-14 rounded-full bg-surface-bright flex-shrink-0"></div>
<div class="flex-1 space-y-2">
<div class="h-4 w-1/4 bg-surface-variant rounded"></div>
<div class="h-3 w-3/4 bg-surface-variant/50 rounded"></div>
</div>
</div>
<div class="flex items-center gap-5 p-4 bg-surface-container rounded-3xl">
<div class="w-14 h-14 rounded-full bg-surface-bright flex-shrink-0"></div>
<div class="flex-1 space-y-2">
<div class="h-4 w-1/3 bg-surface-variant rounded"></div>
<div class="h-3 w-2/3 bg-surface-variant/50 rounded"></div>
</div>
</div>
<div class="flex items-center gap-5 p-4 bg-surface-container-low rounded-3xl">
<div class="w-14 h-14 rounded-full bg-surface-bright flex-shrink-0"></div>
<div class="flex-1 space-y-2">
<div class="h-4 w-1/5 bg-surface-variant rounded"></div>
<div class="h-3 w-1/2 bg-surface-variant/50 rounded"></div>
</div>
</div>
<!-- Editorial Moment -->
<div class="py-12">
<h2 class="text-6xl font-black text-surface-variant tracking-tighter opacity-20">KINETIC</h2>
</div>
</div>
</main>
<!-- PIP CALL VIEW (The Request) -->
<div class="fixed top-24 right-8 z-50 w-72 h-44 group">
<!-- Floating Window Container -->
<div class="relative w-full h-full rounded-[2rem] bg-surface-container/80 backdrop-blur-2xl border border-white/5 overflow-hidden pip-glow flex flex-col p-1 slide-on-ice cursor-grab active:cursor-grabbing">
<!-- Video/Avatar Feed -->
<div class="relative flex-1 rounded-[1.75rem] overflow-hidden bg-surface-container-lowest">
<img alt="Current speaker" class="w-full h-full object-cover" data-alt="A focused young woman in a high-tech studio environment with soft blue ambient lighting and cinematic bokeh" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDjAjGAHxf2xZFdY2W4WG4l3g2R-UjETfxF8m5r17NLLA8iz7VPKzy2A89zntJkgQIkb7rMQHD0PiXedl6W3-pAYR3P6J0cC5p0rY4IQzRdJ_3190kV2Ohsqi6zW3rJLo62NIgV91d2pUUo7UsIeqp_1HZfynj2dur3Ekhk4JrT-Yesm-7UBo8lAtfMUEJ2df4OY0pOZox69mIdUGAAvKmoyQN7e-EVmGPIssDDcm1BXUEEj14uV3a42CaNQ3zn4dfH7CJrKK74EtQ"/>
<!-- Participant Overlay -->
<div class="absolute top-3 left-3 bg-black/40 backdrop-blur-md px-2 py-1 rounded-full flex items-center gap-1.5 border border-white/10">
<div class="w-1.5 h-1.5 rounded-full bg-tertiary animate-pulse"></div>
<span class="text-[10px] font-bold tracking-wider text-white">4 УЧАСТНИКА</span>
</div>
<!-- Speaker Identity -->
<div class="absolute bottom-3 left-3 flex flex-col">
<span class="text-[11px] font-bold text-white drop-shadow-md">Александра В.</span>
<span class="text-[9px] text-white/70">Говорит сейчас...</span>
</div>
<!-- Drag Handle Hint (Visual Only) -->
<div class="absolute top-2 right-1/2 translate-x-1/2 w-8 h-1 bg-white/20 rounded-full opacity-0 group-hover:opacity-100 transition-opacity"></div>
</div>
<!-- Controls (Revealed on hover) -->
<div class="flex items-center justify-center gap-4 py-2 opacity-0 group-hover:opacity-100 slide-on-ice h-0 group-hover:h-12 overflow-hidden">
<button class="w-10 h-10 rounded-full bg-surface-container-highest/60 flex items-center justify-center hover:bg-surface-bright transition-colors">
<span class="material-symbols-outlined text-on-surface text-xl" data-icon="mic">mic</span>
</button>
<button class="w-12 h-10 rounded-full bg-error flex items-center justify-center hover:opacity-90 transition-opacity">
<span class="material-symbols-outlined text-on-error text-xl" data-icon="call_end" style="font-variation-settings: 'FILL' 1;">call_end</span>
</button>
<button class="w-10 h-10 rounded-full bg-surface-container-highest/60 flex items-center justify-center hover:bg-surface-bright transition-colors">
<span class="material-symbols-outlined text-on-surface text-xl" data-icon="open_in_full">open_in_full</span>
</button>
</div>
<!-- Collapsed Minimal Label (Visible when not hovering) -->
<div class="absolute bottom-3 right-3 flex items-center gap-2 group-hover:opacity-0 transition-opacity pointer-events-none">
<div class="flex -space-x-2">
<div class="w-5 h-5 rounded-full border border-black/20 bg-primary-fixed-dim overflow-hidden">
<img alt="Participant" data-alt="Portrait of a person" src="https://lh3.googleusercontent.com/aida-public/AB6AXuCuMYyl7CwiG9WwcZCzLhs8bL7AF2buJmiwcJh4jczj5-UWxBM8jOwLBfoFriUDkC3vjnFnIkAflDQ781ZbkZl1jgpXp3PPabkAxqYcXARr_ZtjDsbpZVUPTbuEz5d9RYl9RfIZe0xKZXWtRhqU9iOfOkZkkXShLEGyNNhmTT5BoigDw4DcJDw6HkaKWo6W5ikjrcLIBInHXmYn4GNjvODgIPmpVTU6U5kqfOvQz2zmYVlA2Zf6KLnIwV8SrV0mfo4PIkZuwedHLIU"/>
</div>
<div class="w-5 h-5 rounded-full border border-black/20 bg-secondary-fixed-dim overflow-hidden">
<img alt="Participant" data-alt="Portrait of a person" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDuH-FI6o13qn5xMnHfbxpPd5YrBEm2fjOA0PcHZ4vMZCb2LJgtubIHaQUpiYBJkCnRZjlyIVI3a_VBlF3heyqVmROSO6d4VhJvpYIsYaLgvY5BH12pS2-Ed_Jd-XX2lrPZ58LvS2-TMxGXs0VzDm9minzmamM8uOHNFsEmsxsHq5nISf5lmEgAVqpo5pVl3LGGsYx9IliwWTmfy_7ciJ4WImG6tB1GUveS5esw1HNuXQNWGyCbb9OiQEIf-Z5RKSnot_hTEua64_E"/>
</div>
</div>
</div>
</div>
</div>
<!-- Background Decoration -->
<div class="fixed top-0 left-0 w-full h-full pointer-events-none -z-10 opacity-10">
<div class="absolute top-[10%] left-[30%] w-[40rem] h-[40rem] bg-primary/20 blur-[120px] rounded-full"></div>
<div class="absolute bottom-[20%] right-[10%] w-[30rem] h-[30rem] bg-tertiary-container/10 blur-[100px] rounded-full"></div>
</div>
</body></html>

BIN
stitch/pip/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB