diff --git a/frontend/linktree-frontend/src/app/components/ExpandableGroup.module.css b/frontend/linktree-frontend/src/app/components/ExpandableGroup.module.css
new file mode 100644
index 0000000..55f9b1f
--- /dev/null
+++ b/frontend/linktree-frontend/src/app/components/ExpandableGroup.module.css
@@ -0,0 +1,163 @@
+.expandToggle {
+ margin-top: 1rem;
+ position: relative;
+}
+
+.expandButton {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 50px;
+ font-weight: 500;
+ transition: all 0.2s ease;
+ border-radius: 10px;
+
+ &:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ }
+}
+
+.expandOverlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: 10px;
+ pointer-events: none;
+}
+
+.linkItem {
+ position: relative;
+ margin-bottom: 0.75rem;
+}
+
+.linkContent {
+ display: block;
+ text-decoration: none;
+ color: inherit;
+ transition: all 0.2s ease;
+ position: relative;
+ overflow: hidden;
+ border-radius: 10px;
+
+ &:hover {
+ text-decoration: none;
+ color: inherit;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ }
+}
+
+.linkInner {
+ display: flex;
+ align-items: center;
+ padding: 1rem;
+ background: white;
+ border-radius: 10px;
+ border: 1px solid #e9ecef;
+ position: relative;
+ z-index: 1;
+}
+
+.linkIcon {
+ width: 40px;
+ height: 40px;
+ object-fit: cover;
+ border-radius: 8px;
+ margin-right: 1rem;
+ flex-shrink: 0;
+}
+
+.linkInfo {
+ flex: 1;
+ min-width: 0;
+}
+
+.linkTitle {
+ margin: 0 0 0.25rem 0;
+ font-size: 1rem;
+ font-weight: 600;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.linkDescription {
+ margin: 0;
+ font-size: 0.875rem;
+ color: #6c757d;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.linkOverlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ pointer-events: none;
+ border-radius: 10px;
+ z-index: 2;
+}
+
+/* Layout-specific styles */
+.linkItem :global(.grid-link) {
+ .linkInner {
+ flex-direction: column;
+ text-align: center;
+ padding: 1.5rem;
+ }
+
+ .linkIcon {
+ width: 60px;
+ height: 60px;
+ margin-right: 0;
+ margin-bottom: 1rem;
+ }
+
+ .linkTitle, .linkDescription {
+ white-space: normal;
+ text-align: center;
+ }
+}
+
+.linkItem :global(.cards-link) {
+ .linkInner {
+ padding: 1.25rem;
+ }
+
+ .linkIcon {
+ width: 50px;
+ height: 50px;
+ }
+}
+
+.linkItem :global(.timeline-link) {
+ .linkInner {
+ padding: 1rem 1.25rem;
+ border-left: 4px solid var(--theme-color, #007bff);
+ }
+}
+
+.linkItem :global(.magazine-link) {
+ .linkInner {
+ flex-direction: column;
+ padding: 1.5rem;
+ }
+
+ .linkIcon {
+ width: 80px;
+ height: 80px;
+ margin-right: 0;
+ margin-bottom: 1rem;
+ }
+
+ .linkTitle, .linkDescription {
+ white-space: normal;
+ text-align: center;
+ }
+}
\ No newline at end of file
diff --git a/frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx b/frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx
new file mode 100644
index 0000000..c7ff9e3
--- /dev/null
+++ b/frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx
@@ -0,0 +1,145 @@
+'use client'
+
+import React, { useState } from 'react'
+import styles from './ExpandableGroup.module.css'
+
+interface Link {
+ id: number
+ title: string
+ url: string
+ description?: string
+ image?: string
+}
+
+interface ExpandableGroupProps {
+ links: Link[]
+ layout: 'grid' | 'cards' | 'timeline' | 'magazine'
+ initialShowCount?: number
+ className?: string
+ linkClassName?: string
+ overlayColor?: string
+ overlayOpacity?: number
+}
+
+export function ExpandableGroup({
+ links,
+ layout,
+ initialShowCount = 5,
+ className = '',
+ linkClassName = '',
+ overlayColor,
+ overlayOpacity
+}: ExpandableGroupProps) {
+ const [isExpanded, setIsExpanded] = useState(false)
+
+ const overlayStyles = overlayColor && overlayOpacity ? {
+ backgroundColor: overlayColor,
+ opacity: overlayOpacity
+ } : undefined
+
+ if (links.length <= initialShowCount) {
+ return (
+
+ {links.map((link) => (
+
+ ))}
+
+ )
+ }
+
+ const visibleLinks = isExpanded ? links : links.slice(0, initialShowCount)
+ const hiddenCount = links.length - initialShowCount
+
+ return (
+
+ {visibleLinks.map((link) => (
+
+ ))}
+
+ {hiddenCount > 0 && (
+
+
+ {overlayStyles && (
+
+ )}
+
+ )}
+
+ )
+}
+
+interface LinkItemProps {
+ link: Link
+ layout: string
+ className?: string
+ overlayColor?: string
+ overlayOpacity?: number
+}
+
+function LinkItem({ link, layout, className = '', overlayColor, overlayOpacity }: LinkItemProps) {
+ const overlayStyles = overlayColor && overlayOpacity ? {
+ backgroundColor: overlayColor,
+ opacity: overlayOpacity
+ } : undefined
+
+ return (
+
+ )
+}
+
+export default ExpandableGroup
\ No newline at end of file
diff --git a/frontend/linktree-frontend/src/app/components/TemplatesSelector.module.css b/frontend/linktree-frontend/src/app/components/TemplatesSelector.module.css
new file mode 100644
index 0000000..13d8d0e
--- /dev/null
+++ b/frontend/linktree-frontend/src/app/components/TemplatesSelector.module.css
@@ -0,0 +1,69 @@
+.template-selector {
+ .template-card {
+ cursor: pointer;
+ transition: all 0.2s ease;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ }
+
+ &.selected {
+ border-color: var(--bs-primary) !important;
+ box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
+ }
+ }
+
+ .template-preview {
+ height: 120px;
+ border-radius: 0.375rem 0.375rem 0 0;
+ position: relative;
+ overflow: hidden;
+ }
+
+ .preview-content {
+ padding: 1rem;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ }
+
+ .preview-header {
+ height: 20px;
+ border-radius: 4px;
+ opacity: 0.8;
+ width: 70%;
+ margin-bottom: 0.5rem;
+ }
+
+ .preview-subtitle {
+ height: 12px;
+ border-radius: 2px;
+ opacity: 0.6;
+ width: 50%;
+ margin-bottom: 0.5rem;
+ }
+
+ .preview-button {
+ height: 24px;
+ border-radius: 6px;
+ width: 80%;
+ margin-bottom: 0.25rem;
+ }
+
+ .preview-text {
+ height: 8px;
+ border-radius: 2px;
+ opacity: 0.5;
+ width: 60%;
+ }
+
+ .overlay-demo {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+}
\ No newline at end of file
diff --git a/frontend/linktree-frontend/src/app/components/TemplatesSelector.tsx b/frontend/linktree-frontend/src/app/components/TemplatesSelector.tsx
new file mode 100644
index 0000000..3440475
--- /dev/null
+++ b/frontend/linktree-frontend/src/app/components/TemplatesSelector.tsx
@@ -0,0 +1,122 @@
+'use client'
+
+import React from 'react'
+import { designTemplates, DesignTemplate } from '../constants/designTemplates'
+import styles from './TemplatesSelector.module.css'
+
+interface TemplatesSelectorProps {
+ onTemplateSelect: (template: DesignTemplate) => void
+ currentTemplate?: string
+}
+
+export function TemplatesSelector({ onTemplateSelect, currentTemplate }: TemplatesSelectorProps) {
+ return (
+
+
+
+ Готовые шаблоны
+
+
+ {designTemplates.map((template) => (
+
+
onTemplateSelect(template)}
+ >
+
+ {/* Мини-превью дизайна */}
+
+
+ {/* Overlay для демонстрации */}
+ {template.settings.group_overlay_enabled && (
+
+ )}
+
+
+
+
+ {template.name}
+
+
+ {template.description}
+
+
+ {currentTemplate === template.id && (
+
+
+
+ Выбран
+
+
+ )}
+
+
+
+ ))}
+
+
+
+
+
+
+ Совет: После выбора шаблона вы можете дополнительно настроить цвета, шрифты и другие параметры во вкладках выше.
+
+
+
+
+ )
+}
+
+export default TemplatesSelector
\ No newline at end of file
diff --git a/frontend/linktree-frontend/src/app/constants/designTemplates.ts b/frontend/linktree-frontend/src/app/constants/designTemplates.ts
new file mode 100644
index 0000000..015b3a5
--- /dev/null
+++ b/frontend/linktree-frontend/src/app/constants/designTemplates.ts
@@ -0,0 +1,362 @@
+// Предустановленные дизайн-шаблоны
+export interface DesignTemplate {
+ id: string
+ name: string
+ description: string
+ preview: string
+ settings: {
+ theme_color: string
+ background_color: string
+ font_family: string
+ heading_font_family?: string
+ body_font_family?: string
+ header_text_color: string
+ group_text_color: string
+ link_text_color: string
+ group_description_text_color: string
+ dashboard_layout: 'sidebar' | 'grid' | 'list' | 'cards' | 'compact' | 'masonry' | 'timeline' | 'magazine'
+ group_overlay_enabled?: boolean
+ group_overlay_color?: string
+ group_overlay_opacity?: number
+ show_groups_title?: boolean
+ custom_css?: string
+ }
+}
+
+export const designTemplates: DesignTemplate[] = [
+ {
+ id: 'minimalist',
+ name: 'Минимализм',
+ description: 'Чистый современный дизайн с акцентом на контент',
+ preview: '/templates/minimalist.jpg',
+ settings: {
+ theme_color: '#2563eb',
+ background_color: '#ffffff',
+ font_family: "'PT Sans', sans-serif",
+ heading_font_family: "'PT Sans', sans-serif",
+ body_font_family: "'PT Sans', sans-serif",
+ header_text_color: '#1f2937',
+ group_text_color: '#374151',
+ link_text_color: '#6b7280',
+ group_description_text_color: '#9ca3af',
+ dashboard_layout: 'list',
+ group_overlay_enabled: false,
+ show_groups_title: true,
+ custom_css: `
+ .card {
+ border: 1px solid #e5e7eb;
+ border-radius: 12px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ transition: all 0.2s ease;
+ }
+ .card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ }
+ .btn {
+ border-radius: 8px;
+ font-weight: 500;
+ }
+ `
+ }
+ },
+ {
+ id: 'dark',
+ name: 'Темная тема',
+ description: 'Элегантный темный дизайн для современного вида',
+ preview: '/templates/dark.jpg',
+ settings: {
+ theme_color: '#06d6a0',
+ background_color: '#1a1a1a',
+ font_family: "'Inter', sans-serif",
+ heading_font_family: "'Inter', sans-serif",
+ body_font_family: "'Inter', sans-serif",
+ header_text_color: '#ffffff',
+ group_text_color: '#e5e7eb',
+ link_text_color: '#d1d5db',
+ group_description_text_color: '#9ca3af',
+ dashboard_layout: 'cards',
+ group_overlay_enabled: true,
+ group_overlay_color: '#000000',
+ group_overlay_opacity: 0.4,
+ show_groups_title: true,
+ custom_css: `
+ body {
+ background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
+ }
+ .card {
+ background: rgba(255, 255, 255, 0.05);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 16px;
+ backdrop-filter: blur(10px);
+ }
+ .btn {
+ background: linear-gradient(135deg, #06d6a0 0%, #118ab2 100%);
+ border: none;
+ color: white;
+ }
+ `
+ }
+ },
+ {
+ id: 'corporate',
+ name: 'Корпоративный',
+ description: 'Деловой профессиональный стиль',
+ preview: '/templates/corporate.jpg',
+ settings: {
+ theme_color: '#1e40af',
+ background_color: '#f8fafc',
+ font_family: "'Roboto', sans-serif",
+ heading_font_family: "'Roboto', sans-serif",
+ body_font_family: "'Roboto', sans-serif",
+ header_text_color: '#1e293b',
+ group_text_color: '#334155',
+ link_text_color: '#475569',
+ group_description_text_color: '#64748b',
+ dashboard_layout: 'grid',
+ group_overlay_enabled: false,
+ show_groups_title: true,
+ custom_css: `
+ .card {
+ border: 1px solid #cbd5e1;
+ border-radius: 8px;
+ background: #ffffff;
+ }
+ .card-header {
+ background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
+ color: white;
+ border-radius: 8px 8px 0 0;
+ }
+ .btn {
+ border-radius: 6px;
+ font-weight: 500;
+ text-transform: uppercase;
+ font-size: 0.875rem;
+ }
+ `
+ }
+ },
+ {
+ id: 'creative',
+ name: 'Творческий',
+ description: 'Яркий креативный дизайн с градиентами',
+ preview: '/templates/creative.jpg',
+ settings: {
+ theme_color: '#f59e0b',
+ background_color: '#fef3c7',
+ font_family: "'Comfortaa', cursive",
+ heading_font_family: "'Comfortaa', cursive",
+ body_font_family: "'Open Sans', sans-serif",
+ header_text_color: '#7c2d12',
+ group_text_color: '#ea580c',
+ link_text_color: '#c2410c',
+ group_description_text_color: '#f97316',
+ dashboard_layout: 'masonry',
+ group_overlay_enabled: true,
+ group_overlay_color: '#f59e0b',
+ group_overlay_opacity: 0.2,
+ show_groups_title: true,
+ custom_css: `
+ body {
+ background: linear-gradient(135deg, #fef3c7 0%, #fed7aa 50%, #fecaca 100%);
+ }
+ .card {
+ border: none;
+ border-radius: 20px;
+ background: linear-gradient(135deg, #ffffff 0%, #fef7ed 100%);
+ box-shadow: 0 8px 32px rgba(251, 146, 60, 0.3);
+ }
+ .btn {
+ border-radius: 25px;
+ background: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%);
+ border: none;
+ color: white;
+ font-weight: 600;
+ }
+ `
+ }
+ },
+ {
+ id: 'nature',
+ name: 'Природа',
+ description: 'Органический дизайн с натуральными цветами',
+ preview: '/templates/nature.jpg',
+ settings: {
+ theme_color: '#059669',
+ background_color: '#ecfdf5',
+ font_family: "'Source Serif Pro', serif",
+ heading_font_family: "'Source Serif Pro', serif",
+ body_font_family: "'PT Sans', sans-serif",
+ header_text_color: '#064e3b',
+ group_text_color: '#065f46',
+ link_text_color: '#047857',
+ group_description_text_color: '#10b981',
+ dashboard_layout: 'timeline',
+ group_overlay_enabled: true,
+ group_overlay_color: '#059669',
+ group_overlay_opacity: 0.15,
+ show_groups_title: true,
+ custom_css: `
+ body {
+ background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
+ }
+ .card {
+ border: 2px solid #a7f3d0;
+ border-radius: 16px;
+ background: rgba(255, 255, 255, 0.9);
+ backdrop-filter: blur(5px);
+ }
+ .timeline-content {
+ position: relative;
+ }
+ .timeline-content::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -20px;
+ width: 4px;
+ height: 100%;
+ background: linear-gradient(180deg, #059669 0%, #10b981 100%);
+ border-radius: 2px;
+ }
+ `
+ }
+ },
+ {
+ id: 'retro',
+ name: 'Ретро',
+ description: 'Винтажный стиль с теплыми цветами',
+ preview: '/templates/retro.jpg',
+ settings: {
+ theme_color: '#dc2626',
+ background_color: '#fef2f2',
+ font_family: "'Merriweather', serif",
+ heading_font_family: "'Merriweather', serif",
+ body_font_family: "'Source Sans Pro', sans-serif",
+ header_text_color: '#7f1d1d',
+ group_text_color: '#991b1b',
+ link_text_color: '#b91c1c',
+ group_description_text_color: '#dc2626',
+ dashboard_layout: 'magazine',
+ group_overlay_enabled: true,
+ group_overlay_color: '#7c2d12',
+ group_overlay_opacity: 0.25,
+ show_groups_title: true,
+ custom_css: `
+ body {
+ background: radial-gradient(circle at center, #fef2f2 0%, #fee2e2 100%);
+ }
+ .card {
+ border: 3px solid #fca5a5;
+ border-radius: 12px;
+ background: #fffbeb;
+ box-shadow: 4px 4px 0px #f87171;
+ }
+ .card-header {
+ background: linear-gradient(135deg, #dc2626 0%, #f59e0b 100%);
+ color: white;
+ border-radius: 8px 8px 0 0;
+ font-weight: bold;
+ }
+ .btn {
+ border: 2px solid #dc2626;
+ border-radius: 8px;
+ font-weight: bold;
+ text-transform: uppercase;
+ }
+ `
+ }
+ },
+ {
+ id: 'neon',
+ name: 'Неон',
+ description: 'Футуристический стиль с неоновыми акцентами',
+ preview: '/templates/neon.jpg',
+ settings: {
+ theme_color: '#8b5cf6',
+ background_color: '#0f0f23',
+ font_family: "'Russo One', sans-serif",
+ heading_font_family: "'Russo One', sans-serif",
+ body_font_family: "'Fira Sans', sans-serif",
+ header_text_color: '#a855f7',
+ group_text_color: '#c084fc',
+ link_text_color: '#ddd6fe',
+ group_description_text_color: '#e9d5ff',
+ dashboard_layout: 'grid',
+ group_overlay_enabled: true,
+ group_overlay_color: '#8b5cf6',
+ group_overlay_opacity: 0.3,
+ show_groups_title: true,
+ custom_css: `
+ body {
+ background: radial-gradient(circle at 20% 50%, #1a1a2e 0%, #16213e 25%, #0f0f23 100%);
+ }
+ .card {
+ border: 1px solid #8b5cf6;
+ border-radius: 12px;
+ background: rgba(139, 92, 246, 0.1);
+ backdrop-filter: blur(10px);
+ box-shadow: 0 0 20px rgba(139, 92, 246, 0.3);
+ }
+ .card:hover {
+ box-shadow: 0 0 30px rgba(139, 92, 246, 0.5);
+ border-color: #a855f7;
+ }
+ .btn {
+ background: linear-gradient(135deg, #8b5cf6 0%, #06d6a0 100%);
+ border: none;
+ border-radius: 8px;
+ box-shadow: 0 0 15px rgba(139, 92, 246, 0.4);
+ text-shadow: 0 0 10px rgba(255, 255, 255, 0.8);
+ }
+ `
+ }
+ },
+ {
+ id: 'soft',
+ name: 'Мягкий',
+ description: 'Пастельный дизайн с деликатными оттенками',
+ preview: '/templates/soft.jpg',
+ settings: {
+ theme_color: '#ec4899',
+ background_color: '#fdf2f8',
+ font_family: "'Ubuntu', sans-serif",
+ heading_font_family: "'Ubuntu', sans-serif",
+ body_font_family: "'Ubuntu', sans-serif",
+ header_text_color: '#831843',
+ group_text_color: '#be185d',
+ link_text_color: '#db2777',
+ group_description_text_color: '#f472b6',
+ dashboard_layout: 'cards',
+ group_overlay_enabled: true,
+ group_overlay_color: '#ec4899',
+ group_overlay_opacity: 0.1,
+ show_groups_title: true,
+ custom_css: `
+ body {
+ background: linear-gradient(135deg, #fdf2f8 0%, #fce7f3 50%, #fbcfe8 100%);
+ }
+ .card {
+ border: none;
+ border-radius: 24px;
+ background: rgba(255, 255, 255, 0.7);
+ backdrop-filter: blur(10px);
+ box-shadow: 0 8px 32px rgba(236, 72, 153, 0.1);
+ }
+ .btn {
+ border-radius: 20px;
+ background: linear-gradient(135deg, #ec4899 0%, #8b5cf6 100%);
+ border: none;
+ color: white;
+ font-weight: 500;
+ box-shadow: 0 4px 15px rgba(236, 72, 153, 0.3);
+ }
+ .card-header {
+ background: linear-gradient(135deg, #fce7f3 0%, #f3e8ff 100%);
+ border-radius: 24px 24px 0 0;
+ border-bottom: 1px solid rgba(236, 72, 153, 0.2);
+ }
+ `
+ }
+ }
+]
\ No newline at end of file