diff --git a/bin/evening_detective_server b/bin/evening_detective_server
index d957d0f..5638a27 100755
Binary files a/bin/evening_detective_server and b/bin/evening_detective_server differ
diff --git a/internal/services/users_service/emails.go b/internal/services/users_service/emails.go
index f9ccbc4..1fe23c4 100644
--- a/internal/services/users_service/emails.go
+++ b/internal/services/users_service/emails.go
@@ -42,15 +42,15 @@ func welcomePlain(username, email, password, loginURL string) string {
// welcomeHTML возвращает HTML-версию письма о регистрации.
func welcomeHTML(username, email, password, loginURL string) string {
var content strings.Builder
- content.WriteString("
Привет, ")
+ content.WriteString("Привет, ")
content.WriteString(html.EscapeString(username))
content.WriteString("!
\n")
- content.WriteString("Твоя регистрация в «Вечернем детективе» прошла. Данные для входа:
\n")
+ content.WriteString("Твоя регистрация в «Вечернем детективе» прошла. Данные для входа:
\n")
content.WriteString(credentialsBlockHTML(email, password))
if loginURL != "" {
content.WriteString(buttonHTML(loginURL, "Войти в игру"))
}
- content.WriteString("Если ты не регистрировался — просто проигнорируй это письмо.
")
+ content.WriteString("Если ты не регистрировался — просто проигнорируй это письмо.
")
return wrapHTML(content.String(), loginURL)
}
@@ -80,16 +80,16 @@ func resetPlain(username, email, password, loginURL string) string {
// resetHTML возвращает HTML-версию письма о сбросе пароля.
func resetHTML(username, email, password, loginURL string) string {
var content strings.Builder
- content.WriteString("Привет, ")
+ content.WriteString("Привет, ")
content.WriteString(html.EscapeString(username))
content.WriteString("!
\n")
- content.WriteString("Ты запросил сброс пароля в «Вечернем детективе». Новый пароль для входа:
\n")
+ content.WriteString("Ты запросил сброс пароля в «Вечернем детективе». Новый пароль для входа:
\n")
content.WriteString(credentialsBlockHTML(email, password))
if loginURL != "" {
content.WriteString(buttonHTML(loginURL, "Войти в игру"))
}
- content.WriteString("Никому не сообщай пароль и не пересылай это письмо.
\n")
- content.WriteString("Если это был не ты — ответь на это письмо: мы поможем защитить аккаунт.
")
+ content.WriteString("Никому не сообщай пароль и не пересылай это письмо.
\n")
+ content.WriteString("Если это был не ты — ответь на это письмо: мы поможем защитить аккаунт.
")
return wrapHTML(content.String(), loginURL)
}
@@ -106,11 +106,11 @@ func greeting(username string) string {
// credentialsBlockHTML — блок «Логин / Пароль» в табличной вёрстке,
// совместимой с Gmail.
func credentialsBlockHTML(email, password string) string {
- return `
-| Логин |
-| ` + html.EscapeString(email) + ` |
-| Пароль |
-| ` + html.EscapeString(password) + ` |
+ return `
+| Логин |
+| ` + html.EscapeString(email) + ` |
+| Пароль |
+| ` + html.EscapeString(password) + ` |
`
}
@@ -118,77 +118,58 @@ func credentialsBlockHTML(email, password string) string {
func buttonHTML(link, label string) string {
return ``
}
-// wrapHTML — каркас письма: тёмная шапка с названием сервиса, контент,
-// подвал. Основное оформление — inline; при блокировке текстур и шрифта
-// остаются светлый фон и системный шрифт.
+// wrapHTML — светлая палитра по умолчанию, тёмная через media query.
+// Клиенты без поддержки CSS темы могут перекрашивать обычные цвета сами.
func wrapHTML(content, baseURL string) string {
return `
-
-
+
+
-
-
+
+
-|
-ВЕЧЕРНИЙ ДЕТЕКТИВ
+ |
+ВЕЧЕРНИЙ ДЕТЕКТИВ
|
-|
+ |
` + content + `
|
-|
+ |
Это автоматическое письмо сервиса «Вечерний детектив».
Если ты получил его по ошибке — просто ответь на это письмо.
|
@@ -200,7 +181,7 @@ func wrapHTML(content, baseURL string) string {
`
}
-// Статические копии игровых текстур и шрифта находятся в public/email фронтенда.
+// Шрифт находится в public/email фронтенда.
func emailAssetURL(baseURL, filename string) string {
if baseURL == "" {
return ""
diff --git a/internal/services/users_service/emails_test.go b/internal/services/users_service/emails_test.go
index 8dccedb..4948f96 100644
--- a/internal/services/users_service/emails_test.go
+++ b/internal/services/users_service/emails_test.go
@@ -7,13 +7,23 @@ import (
"golang.org/x/net/html"
)
-func TestEmailPaperText(t *testing.T) {
+func TestEmailThemeAndContent(t *testing.T) {
for name, render := range map[string]func(string, string, string, string) string{
"welcome": welcomeHTML,
"reset": resetHTML,
} {
t.Run(name, func(t *testing.T) {
body := render("", "user@example.com", "<&secret>", "https://example.com")
+ for _, forbidden := range []string{"paper.jpg", "belt.png", "background-image", "text-fill-color", "color:transparent", "background-clip"} {
+ if strings.Contains(body, forbidden) {
+ t.Errorf("theme-blocking style remains: %s", forbidden)
+ }
+ }
+ for _, required := range []string{`content="light dark"`, "prefers-color-scheme:dark"} {
+ if !strings.Contains(body, required) {
+ t.Errorf("missing theme support: %s", required)
+ }
+ }
doc, err := html.Parse(strings.NewReader(body))
if err != nil {
t.Fatal(err)
@@ -22,19 +32,19 @@ func TestEmailPaperText(t *testing.T) {
var walk func(*html.Node, bool, bool)
walk = func(n *html.Node, paper, protected bool) {
for _, a := range n.Attr {
- if a.Key == "class" && a.Val == "paper" {
+ if a.Key == "class" && a.Val == "email-card" {
paper = true
}
- if a.Key == "class" && a.Val == "paper-text" {
+ if a.Key == "class" && a.Val == "email-text" {
protected = true
}
}
if n.Type == html.ElementNode && n.Data == "a" {
- protected = true // The button has its own dark background and light label.
+ protected = true // The button has its own paired background and text colors.
}
if paper && n.Type == html.TextNode && strings.TrimSpace(n.Data) != "" {
if !protected {
- t.Errorf("text without dark-mode protection: %q", n.Data)
+ t.Errorf("text without theme class: %q", n.Data)
}
text.WriteString(n.Data)
}
|