From 7c9fa4a8c2ccbd50b3f81fd652d326089ff5e4ec Mon Sep 17 00:00:00 2001 From: Fedorov Vladimir Date: Sat, 29 Aug 2026 01:54:04 +0700 Subject: [PATCH] update design --- index.html | 23 +- package-lock.json | 4 +- src/App.vue | 11 +- src/assets/base.css | 247 ++++++-- src/assets/main.css | 35 +- src/components/SchedulePage.vue | 844 +++++++++++++++++++++++--- src/components/client.ts | 4 +- src/components/icons/IconClock.vue | 17 + src/components/icons/IconMoon.vue | 16 + src/components/icons/IconMusic.vue | 18 + src/components/icons/IconPin.vue | 17 + src/components/icons/IconRefresh.vue | 17 + src/components/icons/IconSparkles.vue | 16 + src/components/icons/IconSun.vue | 24 + src/composables/useTheme.ts | 58 ++ 15 files changed, 1165 insertions(+), 186 deletions(-) create mode 100644 src/components/icons/IconClock.vue create mode 100644 src/components/icons/IconMoon.vue create mode 100644 src/components/icons/IconMusic.vue create mode 100644 src/components/icons/IconPin.vue create mode 100644 src/components/icons/IconRefresh.vue create mode 100644 src/components/icons/IconSparkles.vue create mode 100644 src/components/icons/IconSun.vue create mode 100644 src/composables/useTheme.ts diff --git a/index.html b/index.html index 459615b..bc4631f 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,29 @@ - + - Родники + + + + Родники — расписание
diff --git a/package-lock.json b/package-lock.json index 024e8b3..b7de51d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "vo1", + "name": "${REPO_NAME_SNAKE}", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "vo1", + "name": "${REPO_NAME_SNAKE}", "version": "0.0.0", "dependencies": { "pinia": "^3.0.1", diff --git a/src/App.vue b/src/App.vue index 67faf0b..17ba5f3 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,16 +1,7 @@ - - diff --git a/src/assets/base.css b/src/assets/base.css index f3eb3d8..b8fca44 100644 --- a/src/assets/base.css +++ b/src/assets/base.css @@ -1,88 +1,209 @@ -/* color palette from */ +/* ============================================================ + Дизайн-токены «Родники» + Палитра: белый + голубой + синий. + Тёмная тема включается атрибутом data-theme="dark" на + (см. composables/useTheme.ts). + ============================================================ */ :root { - --vt-c-white: #ffffff; - --vt-c-white-soft: #f8f8f8; - --vt-c-white-mute: #f2f2f2; + color-scheme: light; - --vt-c-black: #181818; - --vt-c-black-soft: #222222; - --vt-c-black-mute: #282828; + /* Фон и поверхности — белый, с едва заметным голубым отливом */ + --c-canvas-1: #ffffff; + --c-canvas-2: #eef5fd; + --c-surface: #ffffff; + --c-surface-soft: #f6faff; - --vt-c-indigo: #2c3e50; + /* Текст */ + --c-ink: #1b2c47; + --c-ink-soft: #51688a; + --c-ink-faint: #5a7194; - --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); - --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); - --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); - --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + /* Акцент — синий */ + --c-accent: #2563eb; + --c-accent-deep: #1e4fd6; + --c-accent-soft: #dbeafe; + --c-accent-text: #1e40af; - --vt-c-text-light-1: var(--vt-c-indigo); - --vt-c-text-light-2: rgba(60, 60, 60, 0.66); - --vt-c-text-dark-1: var(--vt-c-white); - --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); + /* Вторичный акцент — голубой */ + --c-sky: #4f9cf7; + --c-sky-soft: #e6f2ff; + --c-sky-text: #2563eb; + + /* Дополнительные краски */ + --c-violet: #7c3aed; + --c-violet-soft: #ece4fe; + --c-violet-text: #6d28d9; + --c-pink-soft: #fde3ef; + --c-pink-text: #be185d; + --c-amber: #b45309; + --c-amber-soft: #fdf0d7; + --c-amber-text: #92400e; + + /* Бейдж «сегодня» */ + --c-today-text: #ffffff; + --c-today-glow: rgba(37, 99, 235, 0.45); + + /* Линии */ + --c-line: rgba(27, 44, 71, 0.12); + --c-line-strong: rgba(27, 44, 71, 0.2); + + /* Форма и тени */ + --r-lg: 22px; + --r-md: 14px; + --r-sm: 10px; + --r-pill: 999px; + --shadow-card: 0 1px 2px rgba(30, 64, 175, 0.06), + 0 18px 40px -22px rgba(30, 64, 175, 0.35); + + /* Свечение фона */ + --c-glow: rgba(37, 99, 235, 0.06); + --c-glow-2: rgba(124, 58, 237, 0.07); + + /* Типографика */ + --font-display: 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, + 'Times New Roman', serif; + --font-body: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; } -/* semantic color variables for this project */ -:root { - --color-background: var(--vt-c-white); - --color-background-soft: var(--vt-c-white-soft); - --color-background-mute: var(--vt-c-white-mute); +:root[data-theme='dark'] { + color-scheme: dark; - --color-border: var(--vt-c-divider-light-2); - --color-border-hover: var(--vt-c-divider-light-1); + --c-canvas-1: #101a2c; + --c-canvas-2: #0b1220; + --c-surface: #16233c; + --c-surface-soft: #1d2d4b; - --color-heading: var(--vt-c-text-light-1); - --color-text: var(--vt-c-text-light-1); + --c-ink: #f1f6fc; + --c-ink-soft: #b9c9de; + --c-ink-faint: #7f93b4; - --section-gap: 160px; -} - -@media (prefers-color-scheme: dark) { - :root { - color-scheme: only dark; - - --color-background: var(--vt-c-black); - --color-background-soft: var(--vt-c-black-soft); - --color-background-mute: var(--vt-c-black-mute); - - --color-border: var(--vt-c-divider-dark-2); - --color-border-hover: var(--vt-c-divider-dark-1); - - --color-heading: var(--vt-c-text-dark-1); - --color-text: var(--vt-c-text-dark-2); - } + --c-accent: #6ea2ff; + --c-accent-deep: #93b8ff; + --c-accent-soft: rgba(110, 162, 255, 0.16); + --c-accent-text: #a7c4ff; + + --c-sky: #8cc3ff; + --c-sky-soft: rgba(140, 195, 255, 0.14); + --c-sky-text: #b7d6ff; + + --c-violet: #a78bfa; + --c-violet-soft: rgba(167, 139, 250, 0.16); + --c-violet-text: #c4b5fd; + --c-pink-soft: rgba(244, 114, 182, 0.16); + --c-pink-text: #f9a8d4; + --c-amber: #fbbf24; + --c-amber-soft: rgba(251, 191, 36, 0.16); + --c-amber-text: #fcd34d; + + --c-today-text: #0b1220; + --c-today-glow: rgba(110, 162, 255, 0.35); + + --c-line: rgba(241, 246, 252, 0.1); + --c-line-strong: rgba(241, 246, 252, 0.2); + + --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3), + 0 18px 40px -22px rgba(0, 0, 0, 0.6); + + --c-glow: rgba(110, 162, 255, 0.08); + --c-glow-2: rgba(167, 139, 250, 0.07); } +/* ---------- Базовый сброс ---------- */ *, *::before, *::after { box-sizing: border-box; margin: 0; - font-weight: normal; +} + +html { + -webkit-text-size-adjust: 100%; } body { min-height: 100vh; - color: var(--color-text); - background: #ccc; - transition: - color 0.5s, - background-color 0.5s; - line-height: 1.6; - font-family: - Inter, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - Roboto, - Oxygen, - Ubuntu, - Cantarell, - 'Fira Sans', - 'Droid Sans', - 'Helvetica Neue', - sans-serif; - font-size: 15px; + font-family: var(--font-body); + font-size: 16px; + line-height: 1.55; + color: var(--c-ink); + background: linear-gradient(180deg, var(--c-canvas-1), var(--c-canvas-2)); + background: radial-gradient( + 900px 420px at 50% -8%, + var(--c-glow), + transparent 65% + ), + radial-gradient( + 620px 320px at 100% -18%, + var(--c-glow-2), + transparent 60% + ), + linear-gradient(180deg, var(--c-canvas-1), var(--c-canvas-2)); + background-attachment: fixed; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + transition: color 0.3s ease, background-color 0.3s ease; +} + +h1, +h2, +h3, +h4, +h5, +h6, +p { + margin: 0; + overflow-wrap: break-word; +} + +ul, +ol { + margin: 0; + padding: 0; + list-style: none; +} + +img, +svg { + display: block; + max-width: 100%; +} + +button { + font: inherit; +} + +a { + color: inherit; +} + +::selection { + background: var(--c-accent-soft); + color: var(--c-accent-text); +} + +/* ---------- Утилиты ---------- */ +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + border: 0; + clip: rect(0 0 0 0); + clip-path: inset(50%); + overflow: hidden; + white-space: nowrap; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } } diff --git a/src/assets/main.css b/src/assets/main.css index d5b802c..e18d979 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -1,35 +1,10 @@ @import './base.css'; #app { - max-width: 1280px; + max-width: 720px; + min-height: 100vh; margin: 0 auto; - padding: 1rem; - font-weight: normal; -} - -a, -.green { - text-decoration: none; - color: hsla(160, 100%, 37%, 1); - transition: 0.4s; - padding: 3px; -} - -@media (hover: hover) { - a:hover { - background-color: hsla(160, 100%, 37%, 0.2); - } -} - -@media (min-width: 1024px) { - body { - display: flex; - place-items: center; - } - - #app { - display: grid; - grid-template-columns: 1fr 1fr; - padding: 0 2rem; - } + padding: clamp(20px, 5vw, 44px) clamp(14px, 4vw, 24px) 64px; + display: flex; + flex-direction: column; } diff --git a/src/components/SchedulePage.vue b/src/components/SchedulePage.vue index c98e19e..0429410 100644 --- a/src/components/SchedulePage.vue +++ b/src/components/SchedulePage.vue @@ -1,98 +1,788 @@ diff --git a/src/components/client.ts b/src/components/client.ts index 84f5946..d1cc196 100644 --- a/src/components/client.ts +++ b/src/components/client.ts @@ -14,8 +14,8 @@ export const apiGetDays = async (): Promise => { } export function getApiUrl(path: string) { - const url = 'https://' + window.location.host.split(':')[0] + path - // const url = 'http://localhost:8210' + path + // const url = 'https://' + window.location.host.split(':')[0] + path + const url = 'http://localhost:8210' + path return url } diff --git a/src/components/icons/IconClock.vue b/src/components/icons/IconClock.vue new file mode 100644 index 0000000..d210f91 --- /dev/null +++ b/src/components/icons/IconClock.vue @@ -0,0 +1,17 @@ + diff --git a/src/components/icons/IconMoon.vue b/src/components/icons/IconMoon.vue new file mode 100644 index 0000000..ea4fe28 --- /dev/null +++ b/src/components/icons/IconMoon.vue @@ -0,0 +1,16 @@ + diff --git a/src/components/icons/IconMusic.vue b/src/components/icons/IconMusic.vue new file mode 100644 index 0000000..de00060 --- /dev/null +++ b/src/components/icons/IconMusic.vue @@ -0,0 +1,18 @@ + diff --git a/src/components/icons/IconPin.vue b/src/components/icons/IconPin.vue new file mode 100644 index 0000000..b9f0304 --- /dev/null +++ b/src/components/icons/IconPin.vue @@ -0,0 +1,17 @@ + diff --git a/src/components/icons/IconRefresh.vue b/src/components/icons/IconRefresh.vue new file mode 100644 index 0000000..85d55d1 --- /dev/null +++ b/src/components/icons/IconRefresh.vue @@ -0,0 +1,17 @@ + diff --git a/src/components/icons/IconSparkles.vue b/src/components/icons/IconSparkles.vue new file mode 100644 index 0000000..5f8c188 --- /dev/null +++ b/src/components/icons/IconSparkles.vue @@ -0,0 +1,16 @@ + diff --git a/src/components/icons/IconSun.vue b/src/components/icons/IconSun.vue new file mode 100644 index 0000000..2e6e343 --- /dev/null +++ b/src/components/icons/IconSun.vue @@ -0,0 +1,24 @@ + diff --git a/src/composables/useTheme.ts b/src/composables/useTheme.ts new file mode 100644 index 0000000..b38d0af --- /dev/null +++ b/src/composables/useTheme.ts @@ -0,0 +1,58 @@ +import { ref } from 'vue' + +export type Theme = 'light' | 'dark' + +const THEME_KEY = 'rodniki-theme' +const DARK_CANVAS = '#101a2c' +const LIGHT_CANVAS = '#ffffff' + +const theme = ref('light') + +function readStoredTheme(): Theme | null { + try { + const stored = localStorage.getItem(THEME_KEY) + if (stored === 'light' || stored === 'dark') return stored + } catch { + // localStorage может быть недоступен (приватный режим и т.п.) + } + return null +} + +function systemTheme(): Theme { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' +} + +function applyTheme(value: Theme): void { + document.documentElement.setAttribute('data-theme', value) + document + .querySelector('meta[name="theme-color"]') + ?.setAttribute('content', value === 'dark' ? DARK_CANVAS : LIGHT_CANVAS) +} + +if (typeof window !== 'undefined') { + const initial = readStoredTheme() ?? systemTheme() + theme.value = initial + applyTheme(initial) + + // Пока пользователь не выбрал тему вручную — следуем за системной + if (readStoredTheme() === null) { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => { + theme.value = event.matches ? 'dark' : 'light' + applyTheme(theme.value) + }) + } +} + +export function useTheme() { + function toggleTheme(): void { + theme.value = theme.value === 'dark' ? 'light' : 'dark' + try { + localStorage.setItem(THEME_KEY, theme.value) + } catch { + // ignore + } + applyTheme(theme.value) + } + + return { theme, toggleTheme } +}