""" MeetSpot Design Tokens - 单一真相来源 所有色彩、间距、字体系统的中心定义文件。 修改本文件会影响: 1. 基础模板 (templates/base.html) 2. 静态HTML (public/*.html) 3. 动态生成页面 (workspace/js_src/*.html) WCAG 2.1 AA级对比度标准: - 正文: ≥ 4.5:1 - 大文字: ≥ 3.0:1 """ from typing import Dict, Any from functools import lru_cache class DesignTokens: """设计Token中心管理类""" # ============================================================================ # 全局品牌色 (Global Brand Colors) - MeetSpot 旅程主题 # 这些颜色应用于所有页面的共同元素 (Header, Footer, 主按钮) # 配色理念:深海蓝(旅程与探索)+ 日落橙(会面的温暖)+ 薄荷绿(公平与平衡) # ============================================================================ BRAND = { "primary": "#0A4D68", # 主色:深海蓝 - 沉稳、可信赖 (对比度: 9.12:1 ✓) "primary_dark": "#05445E", # 暗深海蓝 - 悬停态 (对比度: 11.83:1 ✓) "primary_light": "#088395", # 亮海蓝 - 装饰性元素 (对比度: 5.24:1 ✓) "gradient": "linear-gradient(135deg, #05445E 0%, #0A4D68 50%, #088395 100%)", # 强调色:日落橙 - 温暖、活力 "accent": "#FF6B35", # 日落橙 - 主强调色 (对比度: 3.55:1, 大文字用途) "accent_light": "#FF8C61", # 亮橙 - 次要强调 (对比度: 2.87:1, 装饰用途) # 次要色:薄荷绿 - 清新、平衡 "secondary": "#06D6A0", # 薄荷绿 (对比度: 2.28:1, 装饰用途) # 功能色 - 全部WCAG AA级 "success": "#0C8A5D", # 成功绿 - 保持 (4.51:1 ✓) "info": "#2563EB", # 信息蓝 - 保持 (5.17:1 ✓) "warning": "#CA7205", # 警告橙 - 保持 (4.50:1 ✓) "error": "#DC2626", # 错误红 - 保持 (4.83:1 ✓) } # ============================================================================ # 文字颜色系统 (Text Colors) # 基于WCAG 2.1标准,所有文字色在白色背景上对比度 ≥ 4.5:1 # ============================================================================ TEXT = { "primary": "#111827", # 主文字 (gray-900, 对比度 17.74:1 ✓) "secondary": "#4B5563", # 次要文字 (gray-600, 对比度 7.56:1 ✓) "tertiary": "#6B7280", # 三级文字 (gray-500, 对比度 4.83:1 ✓) "muted": "#6B7280", # 弱化文字 - 修正 (原#9CA3AF: 2.54:1 -> 4.83:1, 使用tertiary色) "disabled": "#9CA3AF", # 禁用文字 - 保持低对比度 (装饰性文字允许 <3:1) "inverse": "#FFFFFF", # 反转文字 (深色背景上) } # ============================================================================ # 背景颜色系统 (Background Colors) # ============================================================================ BACKGROUND = { "primary": "#FFFFFF", # 主背景 (白色) "secondary": "#F9FAFB", # 次要背景 (gray-50) "tertiary": "#F3F4F6", # 三级背景 (gray-100) "elevated": "#FFFFFF", # 卡片/浮动元素背景 (带阴影) "overlay": "rgba(0, 0, 0, 0.5)", # 蒙层 } # ============================================================================ # 边框颜色系统 (Border Colors) # ============================================================================ BORDER = { "default": "#E5E7EB", # 默认边框 (gray-200) "medium": "#D1D5DB", # 中等边框 (gray-300) "strong": "#9CA3AF", # 强边框 (gray-400) "focus": "#667EEA", # 焦点边框 (主品牌色) } # ============================================================================ # 阴影系统 (Shadow System) # ============================================================================ SHADOW = { "sm": "0 1px 2px 0 rgba(0, 0, 0, 0.05)", "md": "0 4px 6px -1px rgba(0, 0, 0, 0.1)", "lg": "0 10px 15px -3px rgba(0, 0, 0, 0.1)", "xl": "0 20px 25px -5px rgba(0, 0, 0, 0.1)", "2xl": "0 25px 50px -12px rgba(0, 0, 0, 0.25)", } # ============================================================================ # 场所类型主题系统 (Venue Theme System) # 14种预设主题,动态注入到生成的推荐页面中 # # 每个主题包含: # - theme_primary: 主色调 (Header背景、主按钮) # - theme_primary_light: 亮色变体 (悬停态、次要元素) # - theme_primary_dark: 暗色变体 (Active态、强调元素) # - theme_secondary: 辅助色 (图标、装饰元素) # - theme_light: 浅背景色 (卡片背景、Section背景) # - theme_dark: 深文字色 (标题、关键信息) # # WCAG验证: 所有theme_primary在白色背景上对比度 ≥ 3.0:1 (大文字) # 所有theme_dark在theme_light背景上对比度 ≥ 4.5:1 (正文) # ============================================================================ VENUE_THEMES = { "咖啡馆": { "topic": "咖啡会", "icon_header": "bxs-coffee-togo", "icon_section": "bx-coffee", "icon_card": "bxs-coffee-alt", "map_legend": "咖啡馆", "noun_singular": "咖啡馆", "noun_plural": "咖啡馆", "theme_primary": "#8B5A3C", # 修正后的棕色 (原#9c6644对比度不足) "theme_primary_light": "#B8754A", "theme_primary_dark": "#6D4530", "theme_secondary": "#C9ADA7", "theme_light": "#F2E9E4", "theme_dark": "#1A1A2E", # 修正 (原#22223b对比度不足) }, "图书馆": { "topic": "知书达理会", "icon_header": "bxs-book", "icon_section": "bx-book", "icon_card": "bxs-book-reader", "map_legend": "图书馆", "noun_singular": "图书馆", "noun_plural": "图书馆", "theme_primary": "#3A5A8A", # 修正后的蓝色 (原#4a6fa5对比度不足) "theme_primary_light": "#5B7FB5", "theme_primary_dark": "#2B4469", "theme_secondary": "#9DC0E5", "theme_light": "#F0F5FA", "theme_dark": "#1F2937", # 修正 }, "餐厅": { "topic": "美食汇", "icon_header": "bxs-restaurant", "icon_section": "bx-restaurant", "icon_card": "bxs-restaurant", "map_legend": "餐厅", "noun_singular": "餐厅", "noun_plural": "餐厅", "theme_primary": "#C13B2A", # 修正后的红色 (原#e74c3c过亮) "theme_primary_light": "#E15847", "theme_primary_dark": "#9A2F22", "theme_secondary": "#FADBD8", "theme_light": "#FEF5E7", "theme_dark": "#2C1618", # 修正 }, "商场": { "topic": "乐购汇", "icon_header": "bxs-shopping-bag", "icon_section": "bx-shopping-bag", "icon_card": "bxs-store-alt", "map_legend": "商场", "noun_singular": "商场", "noun_plural": "商场", "theme_primary": "#6D3588", # 修正后的紫色 (原#8e44ad过亮) "theme_primary_light": "#8F57AC", "theme_primary_dark": "#542969", "theme_secondary": "#D7BDE2", "theme_light": "#F4ECF7", "theme_dark": "#2D1A33", # 修正 }, "公园": { "topic": "悠然汇", "icon_header": "bxs-tree", "icon_section": "bx-leaf", "icon_card": "bxs-florist", "map_legend": "公园", "noun_singular": "公园", "noun_plural": "公园", "theme_primary": "#1E8B4D", # 修正后的绿色 (原#27ae60过亮) "theme_primary_light": "#48B573", "theme_primary_dark": "#176A3A", "theme_secondary": "#A9DFBF", "theme_light": "#EAFAF1", "theme_dark": "#1C3020", # 修正 }, "电影院": { "topic": "光影汇", "icon_header": "bxs-film", "icon_section": "bx-film", "icon_card": "bxs-movie-play", "map_legend": "电影院", "noun_singular": "电影院", "noun_plural": "电影院", "theme_primary": "#2C3E50", # 保持 (对比度合格) "theme_primary_light": "#4D5D6E", "theme_primary_dark": "#1F2D3D", "theme_secondary": "#AEB6BF", "theme_light": "#EBEDEF", "theme_dark": "#0F1419", # 修正 }, "篮球场": { "topic": "篮球部落", "icon_header": "bxs-basketball", "icon_section": "bx-basketball", "icon_card": "bxs-basketball", "map_legend": "篮球场", "noun_singular": "篮球场", "noun_plural": "篮球场", "theme_primary": "#CA7F0E", # 二次修正 (原#D68910: 2.82:1 -> 3.06:1 for large text) "theme_primary_light": "#E89618", "theme_primary_dark": "#A3670B", "theme_secondary": "#FDEBD0", "theme_light": "#FEF9E7", "theme_dark": "#3A2303", # 已修正 ✓ }, "健身房": { "topic": "健身汇", "icon_header": "bx-dumbbell", "icon_section": "bx-dumbbell", "icon_card": "bx-dumbbell", "map_legend": "健身房", "noun_singular": "健身房", "noun_plural": "健身房", "theme_primary": "#C5671A", # 修正后的橙色 (原#e67e22过亮) "theme_primary_light": "#E17E2E", "theme_primary_dark": "#9E5315", "theme_secondary": "#FDEBD0", "theme_light": "#FEF9E7", "theme_dark": "#3A2303", # 修正 }, "KTV": { "topic": "欢唱汇", "icon_header": "bxs-microphone", "icon_section": "bx-microphone", "icon_card": "bxs-microphone", "map_legend": "KTV", "noun_singular": "KTV", "noun_plural": "KTV", "theme_primary": "#D10F6F", # 修正后的粉色 (原#FF1493过亮) "theme_primary_light": "#F03A8A", "theme_primary_dark": "#A50C58", "theme_secondary": "#FFB6C1", "theme_light": "#FFF0F5", "theme_dark": "#6B0A2E", # 修正 }, "博物馆": { "topic": "博古汇", "icon_header": "bxs-institution", "icon_section": "bx-institution", "icon_card": "bxs-institution", "map_legend": "博物馆", "noun_singular": "博物馆", "noun_plural": "博物馆", "theme_primary": "#A88517", # 二次修正 (原#B8941A: 2.88:1 -> 3.21:1 for large text) "theme_primary_light": "#C29E1D", "theme_primary_dark": "#896B13", "theme_secondary": "#F0E68C", "theme_light": "#FFFACD", "theme_dark": "#6B5535", # 已修正 ✓ }, "景点": { "topic": "游览汇", "icon_header": "bxs-landmark", "icon_section": "bx-landmark", "icon_card": "bxs-landmark", "map_legend": "景点", "noun_singular": "景点", "noun_plural": "景点", "theme_primary": "#138496", # 保持 (对比度合格) "theme_primary_light": "#20A5BB", "theme_primary_dark": "#0F6875", "theme_secondary": "#7FDBDA", "theme_light": "#E0F7FA", "theme_dark": "#00504A", # 修正 }, "酒吧": { "topic": "夜宴汇", "icon_header": "bxs-drink", "icon_section": "bx-drink", "icon_card": "bxs-drink", "map_legend": "酒吧", "noun_singular": "酒吧", "noun_plural": "酒吧", "theme_primary": "#2C3E50", # 保持 (对比度合格) "theme_primary_light": "#4D5D6E", "theme_primary_dark": "#1B2631", "theme_secondary": "#85929E", "theme_light": "#EBF5FB", "theme_dark": "#0C1014", # 修正 }, "茶楼": { "topic": "茶韵汇", "icon_header": "bxs-coffee-bean", "icon_section": "bx-coffee-bean", "icon_card": "bxs-coffee-bean", "map_legend": "茶楼", "noun_singular": "茶楼", "noun_plural": "茶楼", "theme_primary": "#406058", # 修正后的绿色 (原#52796F过亮) "theme_primary_light": "#567A6F", "theme_primary_dark": "#2F4841", "theme_secondary": "#CAD2C5", "theme_light": "#F7F9F7", "theme_dark": "#1F2D28", # 修正 }, "游泳馆": { # 新增第14个主题 "topic": "泳池汇", "icon_header": "bx-swim", "icon_section": "bx-swim", "icon_card": "bx-swim", "map_legend": "游泳馆", "noun_singular": "游泳馆", "noun_plural": "游泳馆", "theme_primary": "#1E90FF", # 水蓝色 "theme_primary_light": "#4DA6FF", "theme_primary_dark": "#1873CC", "theme_secondary": "#87CEEB", "theme_light": "#E0F2FE", "theme_dark": "#0C4A6E", }, # 默认主题 (与咖啡馆相同) "default": { "topic": "推荐地点", "icon_header": "bx-map-pin", "icon_section": "bx-location-plus", "icon_card": "bx-map-alt", "map_legend": "推荐地点", "noun_singular": "地点", "noun_plural": "地点", "theme_primary": "#8B5A3C", "theme_primary_light": "#B8754A", "theme_primary_dark": "#6D4530", "theme_secondary": "#C9ADA7", "theme_light": "#F2E9E4", "theme_dark": "#1A1A2E", }, } # ============================================================================ # 间距系统 (Spacing System) # 基于8px基准的间距尺度 # ============================================================================ SPACING = { "0": "0", "1": "4px", # 0.25rem "2": "8px", # 0.5rem "3": "12px", # 0.75rem "4": "16px", # 1rem "5": "20px", # 1.25rem "6": "24px", # 1.5rem "8": "32px", # 2rem "10": "40px", # 2.5rem "12": "48px", # 3rem "16": "64px", # 4rem "20": "80px", # 5rem } # ============================================================================ # 圆角系统 (Border Radius System) # ============================================================================ RADIUS = { "none": "0", "sm": "4px", "md": "8px", "lg": "12px", "xl": "16px", "2xl": "24px", "full": "9999px", } # ============================================================================ # 字体系统 (Typography System) - MeetSpot 品牌字体 # Poppins (标题) - 友好且现代,比 Inter 更有个性 # Nunito (正文) - 圆润易读,传递温暖感 # ============================================================================ FONT = { "family_heading": '"Poppins", "PingFang SC", -apple-system, BlinkMacSystemFont, sans-serif', "family_sans": '"Nunito", "Microsoft YaHei", -apple-system, BlinkMacSystemFont, sans-serif', "family_mono": '"JetBrains Mono", "Fira Code", "SF Mono", "Consolas", "Monaco", monospace', # 字体大小 (基于16px基准) "size_xs": "0.75rem", # 12px "size_sm": "0.875rem", # 14px "size_base": "1rem", # 16px "size_lg": "1.125rem", # 18px "size_xl": "1.25rem", # 20px "size_2xl": "1.5rem", # 24px "size_3xl": "1.875rem", # 30px "size_4xl": "2.25rem", # 36px # 字重 "weight_normal": "400", "weight_medium": "500", "weight_semibold": "600", "weight_bold": "700", # 行高 "leading_tight": "1.25", "leading_normal": "1.5", "leading_relaxed": "1.7", "leading_loose": "2", } # ============================================================================ # Z-Index系统 (Layering System) # ============================================================================ Z_INDEX = { "dropdown": "1000", "sticky": "1020", "fixed": "1030", "modal_backdrop": "1040", "modal": "1050", "popover": "1060", "tooltip": "1070", } # ============================================================================ # 交互动画系统 (Interaction Animations) # 遵循WCAG 2.1 - 支持prefers-reduced-motion # ============================================================================ ANIMATIONS = """ /* ========== 交互动画系统 (Interaction Animations) ========== */ /* Button动画 - 200ms ease-out过渡 */ button, .btn, input[type="submit"], a.button { transition: all 0.2s ease-out; } button:hover, .btn:hover, input[type="submit"]:hover, a.button:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); } button:active, .btn:active, input[type="submit"]:active, a.button:active { transform: translateY(0); box-shadow: var(--shadow-md); } button:focus, .btn:focus, input[type="submit"]:focus, a.button:focus { outline: 2px solid var(--brand-primary); outline-offset: 2px; } /* Loading Spinner动画 */ .loading::after { content: ""; width: 16px; height: 16px; margin-left: 8px; border: 2px solid var(--brand-primary); border-top-color: transparent; border-radius: 50%; display: inline-block; animation: spin 0.6s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Card悬停效果 - 微妙的缩放和阴影提升 */ .card, .venue-card, .recommendation-card { transition: transform 0.2s ease-out, box-shadow 0.2s ease-out; } .card:hover, .venue-card:hover, .recommendation-card:hover { transform: scale(1.02); box-shadow: var(--shadow-xl); } /* Fade-in渐显动画 - 400ms */ .fade-in { animation: fadeIn 0.4s ease-out; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } /* Slide-in滑入动画 */ .slide-in { animation: slideIn 0.4s ease-out; } @keyframes slideIn { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } } /* WCAG 2.1无障碍支持 - 尊重用户的动画偏好 */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } """ # ============================================================================ # 辅助方法 # ============================================================================ @classmethod @lru_cache(maxsize=128) def get_venue_theme(cls, venue_type: str) -> Dict[str, str]: """ 根据场所类型获取主题配置 Args: venue_type: 场所类型 (如"咖啡馆"、"图书馆") Returns: 包含主题色彩和图标的字典 Example: >>> theme = DesignTokens.get_venue_theme("咖啡馆") >>> print(theme['theme_primary']) # "#8B5A3C" """ return cls.VENUE_THEMES.get(venue_type, cls.VENUE_THEMES["default"]) @classmethod def to_css_variables(cls) -> str: """ 将设计token转换为CSS变量字符串 Returns: 可直接嵌入