diff --git a/.streamlit/config.toml b/.streamlit/config.toml
index c66a55f..4900afa 100644
--- a/.streamlit/config.toml
+++ b/.streamlit/config.toml
@@ -1,15 +1,15 @@
# Dashboard-only Streamlit settings (doctor dashboard + demo console).
[theme]
base = "light"
-primaryColor = "#2A78D6"
-backgroundColor = "#F6F7F9"
+primaryColor = "#285E4E"
+backgroundColor = "#F6F7F3"
secondaryBackgroundColor = "#FFFFFF"
-textColor = "#1B1F24"
-borderColor = "#E3E6EA"
+textColor = "#213B38"
+borderColor = "#E1E7E3"
baseRadius = "medium"
# Falls back to the system sans-serif when offline (ADR-004).
-font = "Inter:https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap, sans-serif"
-headingFontWeights = 700
+font = "sans-serif"
+headingFontWeights = 600
[client]
toolbarMode = "minimal"
diff --git a/dashboard/components/patient_card.py b/dashboard/components/patient_card.py
index 91b7764..7b08d72 100644
--- a/dashboard/components/patient_card.py
+++ b/dashboard/components/patient_card.py
@@ -42,7 +42,10 @@ def dose_strip_html(grid: h.DoseGrid, start: date | None) -> str:
f'ожидается · {c["pending"]}'
""
)
- return f'
Покажите QR пациенту или продиктуйте код — бот спросит '
@@ -108,7 +108,7 @@ def main() -> None:
st.button("Заполнить демо-данными (Андрей)", icon=":material/auto_fix_high:", on_click=_fill_demo, args=(today,))
left, right = st.columns(2, gap="large")
- with left, st.container(border=True):
+ with left, st.container(border=True, key="panel-connect-person"):
st.html('
Пациент
')
st.text_input("Имя и инициал фамилии", key="cp_name", placeholder="Андрей С.", help="Без полного ФИО")
a, b = st.columns(2)
@@ -128,7 +128,7 @@ def main() -> None:
key="cp_modules",
format_func=lambda m: h.MODULE_LABELS[m],
)
- with right, st.container(border=True):
+ with right, st.container(border=True, key="panel-connect-prescription"):
st.html('
Назначение
')
d1, d2 = st.columns([1.4, 1])
with d1:
diff --git a/dashboard/pages/2_Демо-консоль.py b/dashboard/pages/2_Демо-консоль.py
index 903cb5e..427330c 100644
--- a/dashboard/pages/2_Демо-консоль.py
+++ b/dashboard/pages/2_Демо-консоль.py
@@ -14,21 +14,31 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from console import widgets as w # noqa: E402
from console.playback_ui import render_playback_panel # noqa: E402
+from dashboard import theme # noqa: E402
+from dashboard.components import ui # noqa: E402
+
st.set_page_config(page_title="Демо-консоль", page_icon="🎬", layout="wide")
+st.html(theme.CSS)
+st.html(
+ ui.page_header(
+ "Демо-консоль",
+ "Чат пациента и сценарий сопровождения. Прототип на синтетических данных.",
+ kicker="Демонстрация",
+ )
+)
+
persona = w.select_persona()
w.render_connect_hero()
w.show_flash()
st.sidebar.caption(f"API: {w.get_api().base_url}")
-st.title("Демо-консоль")
-st.caption("Прототип на синтетических данных. Время — виртуальные демо-часы (ADR-005).")
-w.render_clock_bar(persona, key="console-clock")
-st.divider()
+with st.container(border=True, key="panel-console-clock"):
+ w.render_clock_bar(persona, key="console-clock")
chat_col, play_col = st.columns([1.05, 1], gap="large")
-with chat_col:
- st.subheader(f"💬 Чат пациента — {persona.get('display_name') or persona['token']}")
+with chat_col, st.container(border=True, key="panel-console-chat"):
+ st.subheader(f"Чат пациента — {persona.get('display_name') or persona['token']}")
w.render_chat(persona["token"], height=560, key="console-chat")
-with play_col:
+with play_col, st.container(border=True, key="panel-console-scenario"):
render_playback_panel(persona)
diff --git a/dashboard/pages/3_Сплит-экран.py b/dashboard/pages/3_Сплит-экран.py
index 2403d3f..8d4b26f 100644
--- a/dashboard/pages/3_Сплит-экран.py
+++ b/dashboard/pages/3_Сплит-экран.py
@@ -15,12 +15,12 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from console import widgets as w # noqa: E402
+from dashboard import theme # noqa: E402
+from dashboard.components import ui # noqa: E402
+
st.set_page_config(page_title="Сплит-экран", page_icon="🖥️", layout="wide", initial_sidebar_state="collapsed")
-st.markdown(
- "",
- unsafe_allow_html=True,
-)
+st.html(theme.CSS)
+st.html(ui.page_header("Сплит-экран", "Пациент и врач: сопровождение в реальном времени.", kicker="Демонстрация"))
persona = w.select_persona()
w.show_flash()
@@ -44,15 +44,15 @@ if clicked and nxt is not None:
st.rerun()
left, right = st.columns([1.1, 1], gap="large")
-with left:
+with left, st.container(border=True, key="panel-split-patient"):
url = w.patient_web_url()
if url:
- st.markdown(f"#### 📱 Пациент — {persona.get('display_name') or token}")
+ st.markdown(f"#### Пациент — {persona.get('display_name') or token}")
st.iframe(url, height=760)
st.caption(f"Веб-приложение пациента: {url}")
else:
- st.markdown(f"#### 💬 Пациент — {persona.get('display_name') or token}")
+ st.markdown(f"#### Пациент — {persona.get('display_name') or token}")
w.render_chat(token, height=600, key="split-chat")
-with right:
- st.markdown("#### 🩺 Кабинет врача — д-р Смирнова Е. А.")
+with right, st.container(border=True, key="panel-split-doctor"):
+ st.markdown("#### Кабинет врача — д-р Смирнова Е. А.")
w.polled_doctor_panel(token, key="split-doc")
diff --git a/dashboard/theme.py b/dashboard/theme.py
index efc4aa9..76ee726 100644
--- a/dashboard/theme.py
+++ b/dashboard/theme.py
@@ -138,8 +138,8 @@ div[class*="st-key-prow-"] {{ border-top:1px solid var(--line); padding:18px 0 4
.mt-empty p {{ font-size:.85rem; margin:8px 0 0; }}
/* ---------- chips, badges, bars ---------- */
-.mt-chip {{ display:inline-block; font-size:.72rem; padding: 1px 8px; border-radius: 999px; margin-right: 4px;
- background: var(--blue-tint); color: var(--blue-ink); font-weight: 500; }}
+.mt-chip {{ display:inline-block; font-size:.72rem; padding: 1px 8px; border-radius: 5px; margin-right: 4px;
+ background: #edf3ee; color: #38634b; font-weight: 500; }}
.mt-badge {{ display:inline-flex; align-items:center; gap:6px; font-size:.7rem; font-weight:550; padding: 5px 8px;
border-radius: 5px; white-space: normal; }}
.mt-badge.t-red {{ background: var(--red-tint); color: var(--red-ink); }}
@@ -166,7 +166,7 @@ div[class*="st-key-prow-"] {{ border-top:1px solid var(--line); padding:18px 0 4
.mt-banner b {{ color: inherit; }}
.mt-pulse {{ width: 12px; height: 12px; border-radius: 50%; background: var(--red); margin-top: 5px; flex: none;
box-shadow: 0 0 0 4px #faeeee; }}
-.mt-panel-title {{ font-size: .8rem; text-transform: uppercase; letter-spacing: .07em; font-weight: 650;
+.mt-panel-title {{ font-size: .82rem; text-transform: uppercase; letter-spacing: .03em; font-weight: 550;
color: var(--ink-3); margin: 2px 0 8px; }}
.mt-alert {{ border-left: 4px solid var(--grey); padding: 4px 0 4px 12px; }}
.mt-alert.t-red {{ border-left-color: var(--red); }}
@@ -175,9 +175,9 @@ div[class*="st-key-prow-"] {{ border-top:1px solid var(--line); padding:18px 0 4
.mt-alert .when {{ color: var(--ink-3); font-size: .78rem; }}
.mt-alert .why {{ color: var(--ink); font-weight: 600; font-size: .92rem; margin: 2px 0; }}
.mt-alert.acked .why {{ color: var(--ink-3); font-weight: 500; }}
-.mt-summary {{ background: var(--blue-tint); border-radius: 12px; padding: 14px 16px; color: var(--ink);
- line-height: 1.5; font-size: .93rem; }}
-.mt-summary .disc {{ color: var(--blue-ink); font-size: .76rem; margin-top: 8px; }}
+.mt-summary {{ background: #f4f6f2; border:1px solid var(--line); border-radius: 12px; padding: 14px 16px;
+ color: var(--ink); line-height: 1.5; font-size: .93rem; }}
+.mt-summary .disc {{ color: var(--ink-2); font-size: .76rem; margin-top: 8px; }}
.mt-plan ol {{ margin: 0; padding-left: 1.2rem; }}
.mt-plan li {{ margin: 3px 0; color: var(--ink); font-size: .9rem; }}
@@ -195,6 +195,21 @@ div[class*="st-key-prow-"] {{ border-top:1px solid var(--line); padding:18px 0 4
.mt-legend i {{ display:inline-block; width: 12px; height: 12px; border-radius: 3px; margin-right: 5px;
vertical-align: -1px; }}
+/* ---------- internal pages ---------- */
+div[class*="st-key-panel-"] {{ background:var(--surface); border:1px solid var(--line); border-radius:12px;
+ padding:20px; }}
+[data-testid="stExpander"] {{ background:var(--surface); border-color:var(--line); border-radius:12px; }}
+[data-testid="stChatMessage"] {{ background:#f4f6f2; border:1px solid var(--line); border-radius:12px; }}
+[data-testid="stChatInput"] {{ background:var(--surface); border:1px solid var(--line); border-radius:8px; }}
+[data-testid="stNumberInputContainer"], [data-testid="stDateInput"] input {{
+ background:var(--surface); color:var(--ink); border-color:var(--line); border-radius:8px; }}
+[data-testid="stApp"] [data-variant="pills"][data-selected] {{ background:#edf3ee; color:#285e4e;
+ border-color:#b4c6ba; }}
+.mt-console-alert {{ font-size:1.1rem; font-weight:600; }}
+.mt-console-alert .mt-sub {{ color:var(--red-ink); font-weight:400; }}
+.mt-dose-scroll {{ overflow-x:auto; padding-bottom:4px; }}
+.mt-doses {{ min-width:max-content; }}
+
/* ---------- connect result ---------- */
.mt-code {{ font-size: 2.4rem; font-weight: 700; letter-spacing: .12em; color: var(--ink);
font-variant-numeric: tabular-nums; }}
@@ -222,6 +237,8 @@ div[class*="st-key-prow-"] {{ border-top:1px solid var(--line); padding:18px 0 4
.mt-mobile-label {{ display:block; color:var(--ink-3); font-size:.68rem; margin-bottom:5px; }}
.mt-row-details {{ grid-template-columns:1fr; gap:12px; }}
.mt-list-footer {{ flex-wrap:wrap; }}
+ .mt-head .mt-side {{ text-align:left; }}
+ div[class*="st-key-panel-"] {{ padding:16px; }}
.mt-tip:hover::after, .mt-tip:focus-visible::after {{ display:none; }}
}}
@media (prefers-reduced-motion: reduce) {{
diff --git a/web/public/favicon.svg b/web/public/favicon.svg
index 7830f3e..0001347 100644
--- a/web/public/favicon.svg
+++ b/web/public/favicon.svg
@@ -1 +1 @@
-
+
diff --git a/web/src/components/AppShell.tsx b/web/src/components/AppShell.tsx
index 1fde105..a93786e 100644
--- a/web/src/components/AppShell.tsx
+++ b/web/src/components/AppShell.tsx
@@ -3,13 +3,14 @@ import { Link, NavLink } from "react-router-dom";
import { ru } from "../content/ru";
import { useDialog } from "../dialog/DialogContext";
+import { useSession } from "../session/SessionContext";
import { HotlineCard, useHelp } from "./HelpSheet";
import { Icon, LogoMark, type IconName } from "./Icon";
/*
- * Layout from docs/mock (series 2).
+ * Shared patient workspace, following the approved doctor dashboard.
* phone: content → «Экстренная помощь» block → bottom tab bar (signed in)
- * desktop: navy sidebar | content | right rail (optional `aside` + emergency panel)
+ * desktop: white sidebar | workspace header | content + emergency panel
* bare: one centred column on every width (questionnaires)
*/
@@ -87,6 +88,10 @@ function Sidebar({ signedIn }: { signedIn: boolean }) {
)}
+
+ {ru.product.supportTitle}
+ {ru.product.supportText}
+
);
}
@@ -122,6 +127,24 @@ function Rail({ aside }: { aside?: ReactNode }) {
);
}
+function WorkspaceHeader({ signedIn, bare = false }: { signedIn: boolean; bare?: boolean }) {
+ const { session } = useSession();
+ return (
+
+ );
+}
+
export function AppShell({
children,
signedIn = true,
@@ -144,6 +167,7 @@ export function AppShell({
return (
+
{children}
{sos && }
@@ -155,6 +179,7 @@ export function AppShell({
+
{children}
{sos && }
diff --git a/web/src/components/Icon.tsx b/web/src/components/Icon.tsx
index cf90800..05510a8 100644
--- a/web/src/components/Icon.tsx
+++ b/web/src/components/Icon.tsx
@@ -163,8 +163,8 @@ export function Icon({ name, size = 22, label }: { name: IconName; size?: number
export function LogoMark({ size = 32 }: { size?: number }) {
return (
);
}
diff --git a/web/src/content/ru.ts b/web/src/content/ru.ts
index 44e0c74..37e1b28 100644
--- a/web/src/content/ru.ts
+++ b/web/src/content/ru.ts
@@ -6,6 +6,9 @@ export const ru = {
product: {
name: "Сопровождение пациента",
tagline: "между визитами к врачу",
+ workspace: "Личный кабинет",
+ supportTitle: "Рядом между визитами",
+ supportText: "Наблюдение за самочувствием и поддержка назначенного лечения.",
},
nav: {
today: "Сегодня",
diff --git a/web/src/main.tsx b/web/src/main.tsx
index 0b98700..4b51591 100644
--- a/web/src/main.tsx
+++ b/web/src/main.tsx
@@ -1,7 +1,3 @@
-import "@fontsource/golos-text/400.css";
-import "@fontsource/golos-text/500.css";
-import "@fontsource/golos-text/600.css";
-import "@fontsource/golos-text/700.css";
import "./theme/tokens.css";
import "./theme/global.css";
diff --git a/web/src/theme/global.css b/web/src/theme/global.css
index 5d351c2..672710b 100644
--- a/web/src/theme/global.css
+++ b/web/src/theme/global.css
@@ -1,4 +1,4 @@
-/* Phone-first (docs/mock series 2). ≥1024px: navy sidebar | content | emergency rail. Tokens only. */
+/* Phone-first patient workspace, matching the doctor dashboard. */
*,
*::before,
@@ -122,15 +122,15 @@ button:disabled {
}
.tabbar__item.active {
- color: var(--c-navy);
- font-weight: 700;
+ color: var(--c-primary);
+ font-weight: 600;
}
/* ---------------------------------------------------------------- type */
.h1 {
font-size: var(--fs-28);
- font-weight: 700;
+ font-weight: 550;
line-height: 1.15;
letter-spacing: -0.02em;
color: var(--c-text);
@@ -139,13 +139,13 @@ button:disabled {
.h2 {
font-size: var(--fs-20);
- font-weight: 700;
+ font-weight: 550;
line-height: 1.2;
}
.h3 {
font-size: var(--fs-18);
- font-weight: 700;
+ font-weight: 600;
}
.lead {
@@ -196,7 +196,7 @@ button:disabled {
}
.tel {
- font-weight: 700;
+ font-weight: 600;
white-space: nowrap;
color: var(--c-red-strong);
}
@@ -239,7 +239,7 @@ button:disabled {
.topbar__title {
font-size: var(--fs-20);
- font-weight: 700;
+ font-weight: 600;
}
.divider {
@@ -284,30 +284,30 @@ button:disabled {
}
.btn--primary {
- background: var(--c-navy);
+ background: var(--c-primary);
color: #fff;
}
.btn--primary:hover:not(:disabled) {
- background: var(--c-navy-hover);
+ background: var(--c-primary-hover);
}
.btn--outline {
background: var(--c-surface);
- border-color: var(--c-navy);
- color: var(--c-navy);
+ border-color: var(--c-primary);
+ color: var(--c-primary);
}
.btn--ghost {
background: var(--c-tile);
border-color: var(--c-border);
- color: var(--c-navy);
+ color: var(--c-primary);
}
.btn--soft {
background: var(--c-blue-soft);
- color: var(--c-navy);
- border-color: #cfe0f5;
+ color: var(--c-primary);
+ border-color: var(--c-border);
}
.btn--danger {
@@ -317,7 +317,7 @@ button:disabled {
.btn--primary:disabled,
.btn--danger:disabled {
- background: #c5cfdc;
+ background: var(--c-border-strong);
color: #fff;
}
@@ -330,7 +330,7 @@ button:disabled {
place-items: center;
margin-left: -10px;
border: 0;
- border-radius: 12px;
+ border-radius: var(--r-tile);
background: transparent;
color: var(--c-text);
}
@@ -366,7 +366,7 @@ button:disabled {
}
.chip--teal {
- background: #cdeee8;
+ background: var(--c-teal-soft);
color: var(--c-teal-strong);
}
@@ -381,9 +381,9 @@ button:disabled {
height: 48px;
display: grid;
place-items: center;
- border-radius: 14px;
+ border-radius: var(--r-btn);
background: var(--c-tile);
- color: var(--c-navy);
+ color: var(--c-primary);
}
.tile-icon--teal {
@@ -435,7 +435,7 @@ button:disabled {
border-radius: 50%;
background: var(--c-blue-soft);
color: var(--c-blue);
- font-weight: 700;
+ font-weight: 600;
}
.avatar--lg {
@@ -514,6 +514,7 @@ button:disabled {
/* ---------------------------------------------------------------- cards */
.panel {
+ border: 1px solid var(--c-border);
display: grid;
gap: var(--s-3);
padding: var(--s-4);
@@ -541,7 +542,7 @@ button:disabled {
display: flex;
align-items: center;
gap: var(--s-2);
- font-weight: 700;
+ font-weight: 600;
}
.iconlist {
@@ -665,7 +666,7 @@ button.row-card:hover:not(:disabled) {
.signin__name {
display: block;
font-size: var(--fs-18);
- font-weight: 700;
+ font-weight: 600;
color: var(--c-text);
}
@@ -679,9 +680,9 @@ button.row-card:hover:not(:disabled) {
display: grid;
gap: var(--s-5);
padding: var(--s-8) var(--s-6);
- border-radius: 24px;
+ border-radius: var(--r-tile);
background: var(--c-surface);
- box-shadow: 0 12px 40px rgba(15, 39, 71, 0.08);
+ border: 1px solid var(--c-border);
}
.signin__card .entry__title {
@@ -708,6 +709,8 @@ button.row-card:hover:not(:disabled) {
}
.signin__foot {
+ border: 1px solid var(--c-red-line);
+ border-left: 3px solid var(--c-red);
display: flex;
align-items: center;
justify-content: space-between;
@@ -718,16 +721,21 @@ button.row-card:hover:not(:disabled) {
}
.signin__sos {
+ border: 0;
+ background: transparent;
+ padding: 8px 0;
+ min-height: 44px;
+ text-align: left;
display: inline-flex;
align-items: center;
gap: var(--s-2);
- font-weight: 700;
+ font-weight: 600;
color: var(--c-red-strong);
}
.signin__tel {
font-size: var(--fs-20);
- font-weight: 700;
+ font-weight: 600;
color: var(--c-red-strong);
text-decoration: none;
}
@@ -742,7 +750,7 @@ button.row-card:hover:not(:disabled) {
gap: var(--s-3);
padding: var(--s-5);
border: 1.5px solid var(--c-border);
- border-radius: 20px;
+ border-radius: var(--r-tile);
background: var(--c-surface);
text-align: left;
color: var(--c-text);
@@ -777,7 +785,7 @@ button.row-card:hover:not(:disabled) {
align-items: center;
justify-content: space-between;
font-size: var(--fs-20);
- font-weight: 700;
+ font-weight: 600;
}
.entry__title svg {
@@ -829,7 +837,7 @@ button.row-card:hover:not(:disabled) {
min-height: 60px;
padding: var(--s-3) var(--s-4);
border: 1.5px solid var(--c-border);
- border-radius: 14px;
+ border-radius: var(--r-btn);
background: var(--c-surface);
text-align: left;
font-size: var(--fs-16);
@@ -856,7 +864,7 @@ button.row-card:hover:not(:disabled) {
.option.is-on {
background: var(--c-teal-soft);
- border-color: var(--c-teal-soft);
+ border-color: var(--c-teal);
}
.option.is-on .option__mark {
@@ -873,9 +881,9 @@ button.row-card:hover:not(:disabled) {
.numscale__value {
justify-self: center;
font-size: var(--fs-48);
- font-weight: 700;
+ font-weight: 600;
line-height: 1;
- color: var(--c-navy);
+ color: var(--c-primary);
}
.numscale__row {
@@ -947,7 +955,7 @@ button.row-card:hover:not(:disabled) {
gap: var(--s-3);
justify-items: center;
padding: var(--s-6) var(--s-4);
- border-radius: 20px;
+ border-radius: var(--r-tile);
background: var(--c-teal-soft);
}
@@ -970,8 +978,8 @@ button.row-card:hover:not(:disabled) {
border-radius: var(--r-tile);
background: var(--c-surface);
font-size: var(--fs-28);
- font-weight: 700;
- color: var(--c-navy);
+ font-weight: 600;
+ color: var(--c-primary);
}
.reminder__time svg {
@@ -994,9 +1002,9 @@ button.row-card:hover:not(:disabled) {
min-height: 60px;
padding: 0 var(--s-4);
border: 0;
- border-radius: 14px;
+ border-radius: var(--r-btn);
background: var(--c-blue-soft);
- color: var(--c-navy);
+ color: var(--c-primary);
font-size: var(--fs-18);
font-weight: 600;
}
@@ -1057,7 +1065,7 @@ button.row-card:hover:not(:disabled) {
}
.breath__title {
- font-weight: 700;
+ font-weight: 600;
}
.breath__viz {
@@ -1074,7 +1082,7 @@ button.row-card:hover:not(:disabled) {
border-radius: 50%;
border: 6px solid var(--c-teal-soft);
border-top-color: var(--c-teal);
- border-right-color: #7fd1c6;
+ border-right-color: var(--c-border-strong);
animation: breathe 10s var(--ease) infinite;
}
@@ -1206,7 +1214,7 @@ button.row-card:hover:not(:disabled) {
}
.task__title {
- font-weight: 700;
+ font-weight: 600;
}
.task__sub {
@@ -1214,13 +1222,14 @@ button.row-card:hover:not(:disabled) {
color: var(--c-text-2);
overflow: hidden;
text-overflow: ellipsis;
- white-space: nowrap;
+ white-space: normal;
}
.task--hero {
- background: var(--c-navy);
- border-color: var(--c-navy);
- color: #fff;
+ background: var(--c-teal-soft);
+ border-color: var(--c-border-strong);
+ border-left: 3px solid var(--c-primary);
+ color: var(--c-text);
}
.task--hero .task__icon {
@@ -1230,7 +1239,7 @@ button.row-card:hover:not(:disabled) {
.task--hero .task__sub,
.task--hero > svg:last-child {
- color: #c9d6e8;
+ color: var(--c-text-2);
}
.done-row {
@@ -1257,7 +1266,7 @@ button.row-card:hover:not(:disabled) {
display: grid;
gap: var(--s-3);
padding: var(--s-6) var(--s-5);
- border-radius: 20px;
+ border-radius: var(--r-tile);
background: var(--c-teal-soft);
}
@@ -1277,13 +1286,13 @@ button.row-card:hover:not(:disabled) {
border-radius: var(--r-tile);
background: var(--c-surface);
text-align: left;
- color: var(--c-navy);
+ color: var(--c-primary);
}
.quick__title {
display: block;
font-size: var(--fs-14);
- font-weight: 700;
+ font-weight: 600;
}
.quick__sub {
@@ -1368,7 +1377,7 @@ button.row-card:hover:not(:disabled) {
.rx__drug {
display: block;
font-size: var(--fs-18);
- font-weight: 700;
+ font-weight: 600;
}
.rx__by {
@@ -1457,7 +1466,7 @@ button.row-card:hover:not(:disabled) {
.bubble p {
padding: var(--s-3) var(--s-4);
- border-radius: 18px;
+ border-radius: var(--r-tile);
white-space: pre-wrap;
overflow-wrap: anywhere;
}
@@ -1504,7 +1513,7 @@ button.row-card:hover:not(:disabled) {
border-radius: var(--r-btn);
background: var(--c-surface);
font-weight: 600;
- color: var(--c-navy);
+ color: var(--c-primary);
}
.qr:first-child {
@@ -1560,10 +1569,12 @@ button.row-card:hover:not(:disabled) {
/* ---------------------------------------------------------------- emergency */
.sosblock {
+ border: 1px solid var(--c-red-line);
+ border-left: 3px solid var(--c-red);
display: grid;
gap: var(--s-2);
padding: var(--s-2);
- border-radius: 18px;
+ border-radius: var(--r-tile);
background: var(--c-red-soft);
}
@@ -1576,7 +1587,7 @@ button.row-card:hover:not(:disabled) {
border: 0;
background: none;
color: var(--c-red-strong);
- font-weight: 700;
+ font-weight: 600;
font-size: var(--fs-16);
text-align: left;
}
@@ -1629,7 +1640,7 @@ button.row-card:hover:not(:disabled) {
.hotline--sm {
padding: var(--s-2) var(--s-3);
background: rgb(255 255 255 / 0.65);
- border-radius: 12px;
+ border-radius: var(--r-tile);
}
.hotline__icon {
@@ -1658,7 +1669,7 @@ button.row-card:hover:not(:disabled) {
.hotline__phone {
font-size: var(--fs-28);
- font-weight: 700;
+ font-weight: 600;
line-height: 1.1;
color: var(--c-red-strong);
}
@@ -1704,14 +1715,14 @@ button.row-card:hover:not(:disabled) {
margin: auto auto 0;
padding: 0;
border: 0;
- border-radius: 24px 24px 0 0;
+ border-radius: 16px 16px 0 0;
background: var(--c-surface);
color: var(--c-text);
}
.sheet::backdrop,
.modal::backdrop {
- background: rgb(15 39 71 / 0.5);
+ background: rgb(33 59 56 / 0.35);
}
.sheet[open],
@@ -1739,7 +1750,7 @@ button.row-card:hover:not(:disabled) {
.sheet__title {
font-size: var(--fs-20);
- font-weight: 700;
+ font-weight: 600;
}
.sos {
@@ -1755,7 +1766,7 @@ button.row-card:hover:not(:disabled) {
display: grid;
place-items: center;
border-radius: 50%;
- background: radial-gradient(circle, var(--c-red-soft) 55%, #fff6f6 56% 100%);
+ background: var(--c-red-soft);
}
.sos__btn {
@@ -1790,12 +1801,14 @@ button.row-card:hover:not(:disabled) {
.app {
display: grid;
- grid-template-columns: var(--sidebar-w) 1fr;
+ grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
}
.tabbar,
.only-phone,
.screen > .sosblock {
+ border: 1px solid var(--c-red-line);
+ border-left: 3px solid var(--c-red);
display: none;
}
@@ -1811,21 +1824,22 @@ button.row-card:hover:not(:disabled) {
align-content: start;
gap: var(--s-8);
padding: var(--s-6) var(--s-4);
- background: var(--c-navy);
- color: #fff;
+ background: var(--c-surface);
+ border-right: 1px solid var(--c-border);
+ color: var(--c-text);
}
.sidebar__brand {
display: flex;
gap: var(--s-3);
align-items: center;
- color: #fff;
+ color: var(--c-text);
text-decoration: none;
}
.sidebar__name {
display: block;
- font-weight: 700;
+ font-weight: 600;
font-size: var(--fs-14);
line-height: 1.2;
}
@@ -1833,7 +1847,7 @@ button.row-card:hover:not(:disabled) {
.sidebar__tag {
display: block;
font-size: var(--fs-12);
- color: #a9b9cf;
+ color: var(--c-text-3);
}
.sidebar__list {
@@ -1852,7 +1866,7 @@ button.row-card:hover:not(:disabled) {
border: 0;
border-radius: var(--r-btn);
background: none;
- color: #c9d6e8;
+ color: var(--c-text-2);
font-size: var(--fs-16);
font-weight: 500;
text-decoration: none;
@@ -1860,25 +1874,26 @@ button.row-card:hover:not(:disabled) {
}
.sidebar__link:hover {
- background: rgb(255 255 255 / 0.06);
- color: #fff;
+ background: var(--c-tile);
+ color: var(--c-primary);
}
.sidebar__link.active {
- background: rgb(255 255 255 / 0.12);
- color: #fff;
- font-weight: 700;
+ background: var(--c-primary-soft);
+ color: var(--c-primary);
+ font-weight: 600;
}
.sidebar__link .dot {
position: static;
margin-left: auto;
- border-color: var(--c-navy);
+ border-color: var(--c-surface);
}
.app__main {
display: grid;
- grid-template-columns: minmax(0, 1fr) var(--rail-w);
+ grid-template-columns: minmax(0, 1fr);
+ align-content: start;
gap: var(--s-6);
max-width: 1320px;
padding: var(--s-8) var(--s-8) var(--s-8) var(--s-10);
@@ -1895,18 +1910,18 @@ button.row-card:hover:not(:disabled) {
/* Tab screens and sign-in: the page content sits on one wide white card. */
.screen > .stack {
padding: var(--s-8) var(--s-10);
- border-radius: 24px;
+ border-radius: var(--r-tile);
background: var(--c-surface);
- box-shadow: 0 1px 2px rgb(15 39 71 / 0.06);
+ border: 1px solid var(--c-border);
}
/* Focus screens: question on the left, answers on the right. */
.focus,
.done {
padding: var(--s-8) var(--s-10) var(--s-10);
- border-radius: 24px;
+ border-radius: var(--r-tile);
background: var(--c-surface);
- box-shadow: 0 1px 2px rgb(15 39 71 / 0.06);
+ border: 1px solid var(--c-border);
}
.done {
@@ -1920,7 +1935,7 @@ button.row-card:hover:not(:disabled) {
}
.split {
- grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
+ grid-template-columns: minmax(0, 1fr);
gap: var(--s-12);
align-items: start;
}
@@ -1932,7 +1947,7 @@ button.row-card:hover:not(:disabled) {
.split__act {
padding: var(--s-6);
- border-radius: 20px;
+ border-radius: var(--r-tile);
background: var(--c-tile);
}
@@ -1991,7 +2006,7 @@ button.row-card:hover:not(:disabled) {
/* Today: dashboard. */
.dash {
- grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
+ grid-template-columns: minmax(0, 1fr);
gap: var(--s-6);
align-items: start;
}
@@ -2003,19 +2018,18 @@ button.row-card:hover:not(:disabled) {
.dash__main {
padding: var(--s-6);
- border-radius: 24px;
+ border-radius: var(--r-tile);
background: var(--c-surface);
- box-shadow: 0 1px 2px rgb(15 39 71 / 0.06);
+ border: 1px solid var(--c-border);
}
.dash__side .card {
- border-color: transparent;
- box-shadow: 0 1px 2px rgb(15 39 71 / 0.06);
+ border: 1px solid var(--c-border);
}
.grid2,
.cols {
- grid-template-columns: 1fr 1fr;
+ grid-template-columns: minmax(0, 1fr);
align-items: start;
}
@@ -2028,7 +2042,7 @@ button.row-card:hover:not(:disabled) {
}
.rail {
- position: sticky;
+ position: static;
top: var(--s-8);
display: grid;
gap: var(--s-4);
@@ -2036,10 +2050,12 @@ button.row-card:hover:not(:disabled) {
}
.railsos {
+ border: 1px solid var(--c-red-line);
+ border-left: 3px solid var(--c-red);
display: grid;
gap: var(--s-2);
padding: var(--s-3);
- border-radius: 20px;
+ border-radius: var(--r-tile);
background: var(--c-red-soft);
}
@@ -2052,7 +2068,7 @@ button.row-card:hover:not(:disabled) {
border: 0;
background: none;
color: var(--c-red-strong);
- font-weight: 700;
+ font-weight: 600;
text-align: left;
}
@@ -2081,7 +2097,7 @@ button.row-card:hover:not(:disabled) {
.modal {
max-width: 460px;
margin: auto;
- border-radius: 24px;
+ border-radius: var(--r-tile);
}
.composer {
@@ -2118,13 +2134,13 @@ button.row-card:hover:not(:disabled) {
.chip--trauma {
background: var(--c-amber-soft);
- color: #a86a00;
+ color: var(--c-amber);
}
.chip--alcohol,
.chip--about {
- background: var(--c-navy-soft);
- color: var(--c-navy);
+ background: var(--c-primary-soft);
+ color: var(--c-primary);
}
.survey {
@@ -2243,7 +2259,7 @@ button.row-card:hover:not(:disabled) {
.drinks tbody th {
white-space: nowrap;
- color: var(--c-navy);
+ color: var(--c-primary);
}
/* diary: hours of sleep */
@@ -2280,8 +2296,8 @@ button.row-card:hover:not(:disabled) {
outline: none;
background: none;
font-size: var(--fs-28);
- font-weight: 700;
- color: var(--c-navy);
+ font-weight: 600;
+ color: var(--c-primary);
}
.hours__field span {
@@ -2309,7 +2325,7 @@ button.row-card:hover:not(:disabled) {
gap: var(--s-4);
width: 100%;
padding: var(--s-3) var(--s-4);
- border: 1px solid #cfe0f5;
+ border: 1px solid var(--c-border);
border-radius: var(--r-tile);
background: var(--c-blue-soft);
text-align: left;
@@ -2327,7 +2343,7 @@ button.row-card:hover:not(:disabled) {
place-items: center;
width: 56px;
height: 56px;
- border-radius: 14px;
+ border-radius: var(--r-btn);
background: var(--c-surface);
color: var(--c-blue);
line-height: 1;
@@ -2335,7 +2351,7 @@ button.row-card:hover:not(:disabled) {
.effect__num {
font-size: var(--fs-24);
- font-weight: 700;
+ font-weight: 600;
}
.effect__unit {
@@ -2404,7 +2420,7 @@ button.row-card:hover:not(:disabled) {
gap: var(--s-3);
margin: 0;
font-size: var(--fs-18);
- font-weight: 700;
+ font-weight: 600;
}
.drug__sub {
@@ -2443,7 +2459,7 @@ button.row-card:hover:not(:disabled) {
border: 3px solid var(--c-surface);
border-radius: 50%;
background: var(--c-teal);
- box-shadow: 0 1px 3px rgb(15 39 71 / 25%);
+ box-shadow: 0 1px 3px rgb(33 59 56 / 20%);
transform: translate(-50%, -50%);
}
@@ -2463,7 +2479,7 @@ button.row-card:hover:not(:disabled) {
@media (min-width: 1024px) {
.drug__grid {
- grid-template-columns: 1fr 1fr;
+ grid-template-columns: minmax(0, 1fr);
gap: var(--s-5);
}
@@ -2498,10 +2514,12 @@ button.row-card:hover:not(:disabled) {
min-height: 100dvh;
margin: 0 auto;
padding: var(--s-10) var(--s-4);
- align-content: center;
+ align-content: start;
}
.app--bare .screen > .sosblock {
+ border: 1px solid var(--c-red-line);
+ border-left: 3px solid var(--c-red);
display: grid;
}
@@ -2527,7 +2545,7 @@ button.row-card:hover:not(:disabled) {
.result__title {
font-size: var(--fs-20);
- font-weight: 700;
+ font-weight: 600;
}
.result__chips {
@@ -2621,9 +2639,9 @@ button.row-card:hover:not(:disabled) {
gap: var(--s-3);
padding: var(--s-3) var(--s-3) var(--s-3) var(--s-4);
border-radius: var(--r-tile);
- background: var(--c-navy);
+ background: var(--c-primary);
color: #fff;
- box-shadow: 0 12px 32px rgb(15 39 71 / 0.25);
+ box-shadow: 0 12px 32px rgb(33 59 56 / 0.15);
pointer-events: auto;
animation: toast-in 200ms var(--ease);
}
@@ -2656,3 +2674,266 @@ button.row-card:hover:not(:disabled) {
transform: translateY(-8px);
}
}
+
+/* ---------------------------------------------------------------- workspace frame */
+
+.app__main,
+.screen,
+.screen > *,
+.split > *,
+.cols > *,
+.dash > * {
+ min-width: 0;
+}
+
+.workspace {
+ grid-column: 1 / -1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: var(--s-3);
+ margin: 0 max(var(--s-5), var(--safe-left));
+ padding: calc(var(--s-4) + var(--safe-top)) 0 var(--s-4);
+ border-bottom: 1px solid var(--c-border);
+ color: var(--c-text-3);
+ font-size: var(--fs-13);
+}
+
+.workspace__brand,
+.workspace__profile {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--s-2);
+}
+
+.workspace__brand svg {
+ flex: none;
+}
+
+.workspace__profile {
+ text-decoration: none;
+ color: var(--c-text);
+ font-weight: 500;
+}
+
+.workspace__profile .avatar {
+ width: 34px;
+ height: 34px;
+ background: var(--c-teal-soft);
+ color: var(--c-primary);
+}
+
+.workspace__tag {
+ font-size: var(--fs-12);
+}
+
+.screen {
+ padding-top: var(--s-6);
+}
+
+.head {
+ padding-bottom: var(--s-4);
+ border-bottom: 1px solid var(--c-border);
+}
+
+.focus .head {
+ border-bottom: 0;
+ padding-bottom: 0;
+}
+
+.tabbar__item.active {
+ background: var(--c-primary-soft);
+ box-shadow: inset 0 2px var(--c-primary);
+}
+
+.btn--outline {
+ border-color: var(--c-border-strong);
+}
+
+.btn--outline:hover:not(:disabled),
+.btn--ghost:hover:not(:disabled),
+.btn--soft:hover:not(:disabled),
+.task:hover:not(:disabled),
+.quick__btn:hover:not(:disabled) {
+ background: var(--c-primary-soft);
+ border-color: var(--c-border-strong);
+}
+
+.composer:focus-within {
+ outline: 3px solid var(--c-focus);
+ outline-offset: 2px;
+}
+
+.composer textarea {
+ min-width: 0;
+}
+
+.log__row {
+ border: 0;
+ background: transparent;
+}
+
+.log__row:hover:not(:disabled) {
+ background: var(--c-tile);
+}
+
+.result {
+ border: 1px solid var(--c-border-strong);
+ border-left: 3px solid var(--c-teal);
+}
+
+.notice--info {
+ border: 1px solid var(--c-border);
+}
+
+.notice--warn,
+.notice--danger {
+ border: 1px solid var(--c-red-line);
+ border-left: 3px solid var(--c-red);
+}
+
+@media (max-width: 1023px) {
+ .screen > .stack,
+ .focus,
+ .done,
+ .dash__main {
+ padding: var(--s-5);
+ background: var(--c-surface);
+ border: 1px solid var(--c-border);
+ border-radius: var(--r-tile);
+ }
+
+ .workspace {
+ max-width: 600px;
+ margin-inline: auto;
+ width: calc(100% - 40px);
+ }
+
+ .workspace__tag {
+ display: none;
+ }
+}
+
+@media (max-width: 480px) {
+ .screen > .stack,
+ .focus,
+ .done,
+ .dash__main {
+ padding: var(--s-4);
+ }
+
+ .btn-row,
+ .quick,
+ .sosblock__lines {
+ grid-template-columns: minmax(0, 1fr);
+ }
+
+ .signin__card {
+ padding: var(--s-6) var(--s-5);
+ }
+
+ .task,
+ .effect {
+ flex-wrap: wrap;
+ }
+
+ .task__text {
+ flex-basis: 55%;
+ }
+
+ .signin__foot {
+ flex-wrap: wrap;
+ }
+
+ .numscale__row {
+ gap: 3px;
+ }
+}
+
+@media (min-width: 1024px) {
+ .workspace {
+ margin: 0;
+ min-height: 62px;
+ padding: 0 0 var(--s-5);
+ }
+
+ .workspace:not(.workspace--bare) .workspace__brand svg {
+ display: none;
+ }
+
+ .screen {
+ padding-top: 0;
+ }
+
+ .sidebar {
+ display: flex;
+ flex-direction: column;
+ padding: var(--s-8) var(--s-5);
+ overflow-y: auto;
+ }
+
+ .sidebar__brand > svg {
+ flex: none;
+ }
+
+ .sidebar__note {
+ display: grid;
+ gap: var(--s-2);
+ margin-top: auto;
+ padding-top: var(--s-5);
+ border-top: 1px solid var(--c-border);
+ font-size: var(--fs-12);
+ line-height: 1.6;
+ color: var(--c-text-3);
+ }
+
+ .sidebar__note strong {
+ color: var(--c-text);
+ font-weight: 500;
+ }
+
+ .rail {
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ }
+
+ .rail > * {
+ align-self: start;
+ }
+
+ .screen > .stack,
+ .focus,
+ .done {
+ padding: var(--s-6);
+ }
+}
+
+@media (min-width: 1200px) {
+ .dash {
+ grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
+ }
+
+ .grid2,
+ .cols,
+ .drug__grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (min-width: 1440px) {
+ .app__main {
+ grid-template-columns: minmax(0, 1fr) var(--rail-w);
+ }
+
+ .rail {
+ position: sticky;
+ grid-template-columns: minmax(0, 1fr);
+ }
+}
+
+/* Respect reduced motion for toasts defined after the earlier component rules. */
+@media (prefers-reduced-motion: reduce) {
+ .toast {
+ animation: none;
+ }
+}
diff --git a/web/src/theme/tokens.css b/web/src/theme/tokens.css
index cc72e7b..ebb5c01 100644
--- a/web/src/theme/tokens.css
+++ b/web/src/theme/tokens.css
@@ -1,45 +1,45 @@
/*
- * Design tokens — single source of colours, type and spacing (docs/mock, series 2).
+ * Shared visual language with dashboard/theme.py and the approved doctor workspace.
* Capacitor wraps this same bundle; a React Native port maps these 1:1 to a theme object.
*/
:root {
/* Ink & surfaces */
- --c-navy: #0f2747; /* primary buttons, headings, desktop sidebar */
- --c-navy-hover: #0a1d37;
- --c-navy-soft: #e8eef6;
- --c-text: #13294b;
- --c-text-2: #4d5d73;
- --c-text-3: #7a889b;
- --c-page: #ffffff;
- --c-page-desk: #f4f7fb; /* desktop canvas behind white panels */
+ --c-primary: #285e4e; /* primary buttons and active navigation */
+ --c-primary-hover: #204b3e;
+ --c-primary-soft: #edf3ee;
+ --c-text: #213b38;
+ --c-text-2: #526660;
+ --c-text-3: #63736f;
+ --c-page: #f6f7f3;
+ --c-page-desk: #f6f7f3; /* desktop canvas behind white panels */
--c-surface: #ffffff;
- --c-tile: #f3f7fb; /* soft card fill */
- --c-border: #e2e9f1;
- --c-border-strong: #c9d4e1;
+ --c-tile: #f4f6f2; /* soft card fill */
+ --c-border: #e1e7e3;
+ --c-border-strong: #b4c6ba;
- /* Selection & positive: teal */
- --c-teal: #17a08f;
- --c-teal-strong: #0e7e71;
- --c-teal-soft: #e3f5f2;
+ /* Selection & positive */
+ --c-teal: #527f61;
+ --c-teal-strong: #38634b;
+ --c-teal-soft: #eef4ee;
- /* Info: blue */
- --c-blue: #2f7fdb;
- --c-blue-soft: #eaf3fd;
+ /* Informational surfaces */
+ --c-blue: #38634b;
+ --c-blue-soft: #edf3ee;
/* Help & crisis: red */
- --c-red: #e2383b;
- --c-red-strong: #c62b2e;
- --c-red-soft: #fdeded;
- --c-red-line: #f7d3d3;
+ --c-red: #a83236;
+ --c-red-strong: #a83236;
+ --c-red-soft: #faeeee;
+ --c-red-line: #e7cbcb;
/* Warning */
- --c-amber: #e69a12;
- --c-amber-soft: #fff4dd;
+ --c-amber: #805714;
+ --c-amber-soft: #fbf3e4;
- --c-focus: #2f7fdb;
+ --c-focus: #598d7a;
- /* Type: Golos Text (OFL), bundled locally — works offline. */
- --font: "Golos Text", system-ui, -apple-system, "Segoe UI", sans-serif;
+ /* Same offline system stack as the doctor workspace. */
+ --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
--fs-12: 0.75rem;
--fs-13: 0.8125rem;
--fs-14: 0.875rem;
@@ -63,14 +63,14 @@
--s-12: 48px;
/* Radii: tiles > buttons > chips */
- --r-tile: 16px;
- --r-btn: 12px;
- --r-chip: 999px;
+ --r-tile: 12px;
+ --r-btn: 8px;
+ --r-chip: 5px;
--tap: 52px;
--nav-h: 64px;
- --sidebar-w: 232px;
- --rail-w: 300px;
+ --sidebar-w: 228px;
+ --rail-w: 264px;
--safe-top: env(safe-area-inset-top, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);