diff --git a/src/components/Newsletter/SubscribeForm.tsx b/src/components/Newsletter/SubscribeForm.tsx index 687062e..eacf5f1 100644 --- a/src/components/Newsletter/SubscribeForm.tsx +++ b/src/components/Newsletter/SubscribeForm.tsx @@ -16,6 +16,7 @@ export default function SubscribeForm(): React.JSX.Element { const {t, locale} = useI18n(); const [email, setEmail] = useState(''); const [status, setStatus] = useState('idle'); + const [consentVisible, setConsentVisible] = useState(false); const handleSubmit = useCallback( (event: React.FormEvent) => { @@ -44,18 +45,21 @@ export default function SubscribeForm(): React.JSX.Element { // list — the form must not answer "is this person subscribed?". if (status === 'sent') { return ( -
-

{t('newsletter.checkInbox')}

-

{t('newsletter.confirmHint')}

+
+
+

{t('newsletter.checkInbox')}

+

{t('newsletter.confirmHint')}

+
); } return ( -
-

{t('newsletter.eyebrow')}

-

{t('newsletter.title')}

-

{t('newsletter.description')}

+ +
+

{t('newsletter.title')}

+

{t('newsletter.description')}

+
@@ -92,7 +102,18 @@ export default function SubscribeForm(): React.JSX.Element {

) : null} -

{t('newsletter.consent')}

+ {/* + The strip stays one line until the visitor engages with it, but the + consent text must be readable before they can submit — so it unfolds on + the first focus of the field, and screen readers reach it either way + through aria-describedby. + */} +
); } diff --git a/src/components/Newsletter/styles.module.css b/src/components/Newsletter/styles.module.css index 93dd17c..d7b0cf3 100644 --- a/src/components/Newsletter/styles.module.css +++ b/src/components/Newsletter/styles.module.css @@ -1,48 +1,62 @@ -.panel { +/* + * A single slim strip, not a card: the home page already stacks two full-width + * panels (docs, support) and a third heavy block between them read as an + * interruption. Same border, radius family and surface as those panels so it + * sits in the rhythm instead of announcing itself. + */ +.bar { display: flex; - flex-direction: column; - gap: 0.4rem; - padding: 1.25rem 1.5rem; - border: 1px solid var(--ifm-color-emphasis-200); + flex-wrap: wrap; + align-items: center; + gap: 0.6rem 1.5rem; + padding: 0.85rem 1.25rem; + border: 1px solid var(--card-border); border-radius: 14px; - background: var(--ifm-background-surface-color); + background: rgba(255, 255, 255, 0.02); } -.eyebrow { - margin: 0; - font-size: 0.72rem; - letter-spacing: 0.08em; - text-transform: uppercase; - opacity: 0.6; +.text { + flex: 1 1 17rem; + min-width: 0; } .title { margin: 0; - font-size: 1.15rem; + font-size: 0.95rem; + font-weight: 600; + line-height: 1.3; } .note { - margin: 0; - font-size: 0.9rem; - opacity: 0.8; + margin: 0.15rem 0 0; + font-size: 0.78rem; + line-height: 1.4; + opacity: 0.65; } .row { display: flex; - gap: 0.6rem; - flex-wrap: wrap; - margin-top: 0.6rem; + align-items: center; + gap: 0.5rem; + margin-left: auto; } .input { - flex: 1 1 16rem; + flex: 1 1 13rem; min-width: 0; - padding: 0.55rem 0.8rem; - border: 1px solid var(--ifm-color-emphasis-300); - border-radius: 9px; - background: var(--ifm-background-color); + padding: 0.4rem 0.7rem; + border: 1px solid var(--card-border); + border-radius: 8px; + background: transparent; color: inherit; - font-size: 0.95rem; + /* iOS zooms the page in when a focused field is under 16px. */ + font-size: 1rem; +} + +@media (min-width: 601px) { + .input { + font-size: 0.875rem; + } } .input:focus { @@ -54,22 +68,26 @@ border-color: var(--ifm-color-danger); } -.error { - margin: 0.4rem 0 0; - font-size: 0.85rem; - color: var(--ifm-color-danger); +.submit { + white-space: nowrap; } -.successTitle { +/* Full-width lines below the strip, so they never widen the row itself. */ +.error, +.consent { + flex: 1 0 100%; margin: 0; - font-weight: 600; + line-height: 1.45; } -.consent { - margin: 0.7rem 0 0; +.error { font-size: 0.78rem; - opacity: 0.65; - line-height: 1.5; + color: var(--ifm-color-danger); +} + +.consent { + font-size: 0.7rem; + opacity: 0.55; } .srOnly { @@ -83,3 +101,19 @@ white-space: nowrap; border: 0; } + +@media (max-width: 700px) { + .row { + width: 100%; + margin-left: 0; + } +} + +:global(html[data-theme='light']) .bar { + background: var(--light-surface); + box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06); +} + +:global(html[data-theme='light']) .input { + background: var(--light-surface-strong, #fff); +} diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index 93c6dd8..14c59f6 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -84,7 +84,6 @@ export const de: TranslationDict = { error: 'Gespeicherte Berechnungen konnten nicht geladen werden. Bitte erneut versuchen.', }, newsletter: { - eyebrow: 'Auf dem Laufenden bleiben', title: 'Neue Tools, direkt per E-Mail', description: 'Eine gelegentliche Nachricht, wenn ein neues Ingenieur-Tool erscheint oder ein bestehendes erweitert wird. Höchstens einmal im Monat.', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 16e1a5c..92df722 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -82,7 +82,6 @@ export const en = { error: 'Unable to load your saved calculations. Please try again.', }, newsletter: { - eyebrow: 'Stay updated', title: 'New tools, in your inbox', description: 'An occasional note when a new engineering utility ships or an existing one gains a feature. No more than once a month.', diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index 3f6196c..99b1aa0 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -84,7 +84,6 @@ export const es: TranslationDict = { error: 'No se pudieron cargar tus cálculos guardados. Inténtalo de nuevo.', }, newsletter: { - eyebrow: 'Mantente al día', title: 'Nuevas herramientas, en tu correo', description: 'Un aviso ocasional cuando se publica una nueva utilidad de ingeniería o una existente gana una función. No más de una vez al mes.', diff --git a/src/i18n/locales/et.ts b/src/i18n/locales/et.ts index e9c7d1f..c21a048 100644 --- a/src/i18n/locales/et.ts +++ b/src/i18n/locales/et.ts @@ -84,7 +84,6 @@ export const et: TranslationDict = { error: 'Salvestatud arvutusi ei õnnestunud laadida. Palun proovi uuesti.', }, newsletter: { - eyebrow: 'Ole kursis', title: 'Uued tööriistad sinu postkasti', description: 'Harv kiri, kui ilmub uus inseneritööriist või olemasolev saab uue võimaluse. Mitte sagedamini kui kord kuus.', diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index 8c0bc99..a47dbd5 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -84,7 +84,6 @@ export const ru: TranslationDict = { error: 'Не удалось загрузить сохранённые расчёты. Попробуйте снова.', }, newsletter: { - eyebrow: 'Будьте в курсе', title: 'Новые инструменты — на вашу почту', description: 'Редкое письмо, когда выходит новая инженерная утилита или у существующей появляется функция. Не чаще раза в месяц.', diff --git a/src/i18n/locales/ua.ts b/src/i18n/locales/ua.ts index 8100d15..a5b9eec 100644 --- a/src/i18n/locales/ua.ts +++ b/src/i18n/locales/ua.ts @@ -84,7 +84,6 @@ export const ua: TranslationDict = { error: 'Не вдалося завантажити збережені розрахунки. Спробуйте знову.', }, newsletter: { - eyebrow: 'Будьте в курсі', title: 'Нові інструменти — на вашу пошту', description: 'Рідкий лист, коли виходить нова інженерна утиліта або наявна отримує функцію. Не частіше разу на місяць.', diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 5597892..b1fa013 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -383,9 +383,7 @@ export default function Home(): ReactNode { -
- -
+