From 4fe020e3ab55fd94837d400c385e3e669bfc74cc Mon Sep 17 00:00:00 2001 From: iliya Date: Mon, 19 Oct 2026 19:53:12 +0300 Subject: [PATCH 1/4] feat(landing): redesign restore-environment block as a visual transfer flow Show the restore journey as three visual beats (this machine -> uap.toml/uap.lock -> next machine) instead of stacked prose, move the release note into the availability tooltip, and drop the intro and directory copy from all nine locales. Keeps the availability label, guide link, RTL layout and command snippets intact. --- .../components/registry/RegistryRestore.vue | 329 ++++++++++++++---- landing/locales/ar.json | 2 - landing/locales/en.json | 2 - landing/locales/es.json | 2 - landing/locales/fr.json | 2 - landing/locales/hi.json | 2 - landing/locales/pt.json | 2 - landing/locales/ru.json | 2 - landing/locales/uk.json | 2 - landing/locales/zh.json | 2 - .../tests/browser/i18n-integration.spec.ts | 7 +- 11 files changed, 268 insertions(+), 86 deletions(-) diff --git a/landing/components/registry/RegistryRestore.vue b/landing/components/registry/RegistryRestore.vue index 2066b17e0..873102c3d 100644 --- a/landing/components/registry/RegistryRestore.vue +++ b/landing/components/registry/RegistryRestore.vue @@ -10,37 +10,96 @@ const { t } = useI18n(); dir="auto" >
-
-

{{ t('shell.restore.eyebrow') }}

-

{{ t('shell.restore.title') }}

-

{{ t('shell.restore.intro') }}

-

- {{ t('shell.restore.availability') }} - {{ t('shell.restore.releaseNote') }} -

- {{ t('shell.restore.guide') }} -
-
- - - - - - -

{{ t('shell.restore.directory') }}

+
+
+

{{ t('shell.restore.eyebrow') }}

+

{{ t('shell.restore.title') }}

+
+
+ + + {{ t('shell.restore.releaseNote') }} + + {{ t('shell.restore.guide') }} +
+
+ +
+
+

+ + + {{ t('shell.restore.from') }} +

+ +
+ +
+
+ + + uap.toml + + + + uap.lock + +
+ + + + + +
+ +
+

+ + + {{ t('shell.restore.to') }} +

+ +
+

{{ t('shell.restore.requirements') }} {{ t('shell.restore.excludes') }}

@@ -53,46 +112,73 @@ const { t } = useI18n(); padding-block: 32px 48px; &__panel { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); - gap: 24px 40px; + position: relative; padding: clamp(20px, 4vw, 36px); border: 1px solid var(--line-strong); border-radius: 16px; background: var(--surface); + overflow: hidden; + + &::before { + content: ''; + position: absolute; + inset-inline: 0; + top: 0; + height: 2px; + background: linear-gradient(90deg, var(--cyan), var(--primary-bright)); + opacity: 0.8; + } + } + + &__header { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + justify-content: space-between; + gap: 14px 24px; + margin-block-end: 26px; } h2 { - margin-block: 8px 12px; + margin-block: 8px 0; font-size: clamp(1.5rem, 3vw, 2rem); line-height: 1.2; letter-spacing: -0.035em; text-wrap: balance; } - p { - margin-block: 0 14px; - color: var(--muted); - font-size: 0.9rem; - line-height: 1.65; - } - .eyebrow { color: var(--cyan); } + &__meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px 16px; + } + &__availability { - display: grid; - gap: 6px; + display: inline-flex; + align-items: center; + gap: 7px; + padding: 5px 11px; + border: 1px solid var(--line-strong); + border-radius: 8px; + color: var(--text); + background: var(--surface-raised); + font-size: 0.78rem; + font-weight: 700; + cursor: help; - strong { - width: fit-content; - padding: 3px 9px; - border: 1px solid var(--line-strong); - border-radius: 6px; - color: var(--text); - background: var(--surface-raised); - font-size: 0.8rem; + svg { + width: 13px; + height: 13px; + color: var(--muted); + stroke: currentColor; + stroke-width: 1.9; + stroke-linecap: round; + stroke-linejoin: round; } } @@ -103,17 +189,22 @@ const { t } = useI18n(); text-underline-offset: 3px; } - &__steps { - min-width: 0; - - p { - margin-block: 12px; - font-size: 0.8rem; - } + &__flow { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(150px, 200px) minmax(0, 1fr); + align-items: stretch; + gap: 16px; + } - code { - color: var(--text); - } + &__stage { + display: grid; + align-content: start; + gap: 12px; + min-width: 0; + padding: 16px; + border: 1px solid var(--line); + border-radius: 14px; + background: var(--surface-raised); :deep(pre) { direction: ltr; @@ -122,18 +213,124 @@ const { t } = useI18n(); } } - .registry-restore__boundary { - grid-column: 1 / -1; + &__stage-label { + display: flex; + align-items: center; + gap: 9px; margin: 0; + color: var(--muted); + font-size: 0.68rem; + font-weight: 750; + letter-spacing: 0.09em; + text-transform: uppercase; + } + + &__step { + color: color-mix(in srgb, var(--text) 38%, transparent); + font-size: 1.35rem; + font-weight: 450; + letter-spacing: -0.05em; + line-height: 1; + font-variant-numeric: tabular-nums; + } + + &__stage-icon { + width: 17px; + height: 17px; + flex: 0 0 17px; + color: var(--cyan); + stroke: currentColor; + stroke-width: 1.7; + stroke-linecap: round; + stroke-linejoin: round; + } + + &__handoff { + display: grid; + align-content: center; + justify-items: center; + gap: 12px; + min-width: 0; + } + + &__files { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 8px; + } + + &__file { + display: inline-flex; + align-items: center; + gap: 7px; + padding: 8px 11px; + border: 1px solid var(--line-strong); + border-radius: 10px; + background: color-mix(in srgb, var(--surface-solid) 55%, var(--surface-raised)); + font-size: 0.76rem; + + svg { + width: 13px; + height: 13px; + flex: 0 0 13px; + color: var(--primary-bright); + stroke: currentColor; + stroke-width: 1.8; + stroke-linecap: round; + stroke-linejoin: round; + } + + code { + color: var(--text); + } + } + + &__arrow { + width: min(100%, 140px); + height: 22px; + color: var(--cyan); + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + } + + :dir(rtl) &__arrow { + transform: scaleX(-1); + } + + &__boundary { + margin: 22px 0 0; padding-block-start: 16px; border-block-start: 1px solid var(--line); - font-size: 0.8rem; + color: var(--muted); + font-size: 0.78rem; + line-height: 1.6; } } -@media (max-width: 720px) { - .registry-restore__panel { +@media (max-width: 980px) { + .registry-restore__flow { grid-template-columns: minmax(0, 1fr); } + + .registry-restore__stage :deep(.command-snippet--inline pre), + .registry-restore__stage :deep(.command-snippet--inline code) { + white-space: pre-wrap; + overflow-wrap: anywhere; + } + + .registry-restore__handoff { + gap: 10px; + padding-block: 2px; + } + + .registry-restore__arrow, + :dir(rtl) .registry-restore__arrow { + transform: rotate(90deg); + width: 44px; + height: 30px; + } } diff --git a/landing/locales/ar.json b/landing/locales/ar.json index ee9da2f85..e1bbfe6c9 100644 --- a/landing/locales/ar.json +++ b/landing/locales/ar.json @@ -211,14 +211,12 @@ "restore": { "eyebrow": "بيئات إضافات قابلة لإعادة الإنشاء", "title": "إصدارات الإضافات نفسها على حاسوبك القادم", - "intro": "احفظ مجموعة الإضافات العامة وتبعياتها، ثم استعد الإصدارات المسجلة بدقة على حاسوب آخر.", "availability": "متاح في الإصدار 0.1.86", "releaseNote": "مضمّن في إصدارات npm وHomebrew والملفات التنفيذية الأصلية بدءًا من الإصدار 0.1.86.", "guide": "دليل الاستعادة (بالإنجليزية)", "from": "على هذا الحاسوب", "to": "على حاسوبك القادم", "transfer": "انسخ {manifest} و{lockfile} إلى الحاسوب الآخر بين تنفيذ هذين الأمرين.", - "directory": "يستخدم الأمران المجلد الحالي عند عدم تحديد مجلد.", "requirements": "تتطلب الاستعادة بقاء المصادر المسجلة متاحة وعملاء تدعمهم إدارة التبعيات.", "excludes": "لا تُنقل عمليات تسجيل الدخول أو الأسرار أو الإعدادات أو بيانات الإضافات." }, diff --git a/landing/locales/en.json b/landing/locales/en.json index 34fb6e2ac..973a2fa18 100644 --- a/landing/locales/en.json +++ b/landing/locales/en.json @@ -365,14 +365,12 @@ "restore": { "eyebrow": "Reproducible plugin environments", "title": "Same plugin versions on your next machine", - "intro": "Save your global plugin set and its dependencies, then restore the exact recorded versions on another computer.", "availability": "Available in 0.1.86", "releaseNote": "Included in npm, Homebrew and native releases from version 0.1.86.", "guide": "Restore guide (English)", "from": "On this computer", "to": "On your next computer", "transfer": "Copy {manifest} and {lockfile} to the other computer between these commands.", - "directory": "Both commands use the current directory when no directory is specified.", "requirements": "Restoration requires the recorded sources to remain available and clients supported by dependency management.", "excludes": "Sign-ins, secrets, settings and plugin data are not transferred." }, diff --git a/landing/locales/es.json b/landing/locales/es.json index 073b3a836..22c8e1b56 100644 --- a/landing/locales/es.json +++ b/landing/locales/es.json @@ -348,14 +348,12 @@ "restore": { "eyebrow": "Entornos de plugins reproducibles", "title": "Las mismas versiones de plugins en tu próximo equipo", - "intro": "Guarda tu conjunto global de plugins y sus dependencias, y restaura las versiones exactas registradas en otro equipo.", "availability": "Disponible en 0.1.86", "releaseNote": "Incluido en las versiones de npm, Homebrew y los binarios nativos a partir de la versión 0.1.86.", "guide": "Guía de restauración (en inglés)", "from": "En este equipo", "to": "En tu próximo equipo", "transfer": "Copia {manifest} y {lockfile} al otro equipo entre estos comandos.", - "directory": "Si no se especifica un directorio, ambos comandos usan el directorio actual.", "requirements": "La restauración requiere que las fuentes registradas sigan disponibles y que los clientes sean compatibles con la gestión de dependencias.", "excludes": "No se transfieren sesiones, secretos, ajustes ni datos de plugins." }, diff --git a/landing/locales/fr.json b/landing/locales/fr.json index e15e6f599..a4a3a41ab 100644 --- a/landing/locales/fr.json +++ b/landing/locales/fr.json @@ -348,14 +348,12 @@ "restore": { "eyebrow": "Environnements de plugins reproductibles", "title": "Les mêmes versions de plugins sur votre prochain ordinateur", - "intro": "Enregistrez votre ensemble global de plugins et leurs dépendances, puis restaurez les versions exactes enregistrées sur un autre ordinateur.", "availability": "Disponible dans la version 0.1.86", "releaseNote": "Inclus dans les versions npm, Homebrew et les binaires natifs à partir de la version 0.1.86.", "guide": "Guide de restauration (en anglais)", "from": "Sur cet ordinateur", "to": "Sur votre prochain ordinateur", "transfer": "Copiez {manifest} et {lockfile} sur l’autre ordinateur entre ces commandes.", - "directory": "Sans répertoire indiqué, les deux commandes utilisent le répertoire courant.", "requirements": "La restauration nécessite des sources enregistrées toujours disponibles et des clients compatibles avec la gestion des dépendances.", "excludes": "Les connexions aux comptes, secrets, réglages et données des plugins ne sont pas transférés." }, diff --git a/landing/locales/hi.json b/landing/locales/hi.json index 632614ced..8d00375c8 100644 --- a/landing/locales/hi.json +++ b/landing/locales/hi.json @@ -348,14 +348,12 @@ "restore": { "eyebrow": "दोबारा बनाए जा सकने वाले प्लगइन परिवेश", "title": "अगले कंप्यूटर पर भी प्लगइन के वही संस्करण", - "intro": "अपने वैश्विक प्लगइन समूह और उसकी निर्भरताओं को सहेजें, फिर दूसरे कंप्यूटर पर दर्ज किए गए उन्हीं संस्करणों को बहाल करें।", "availability": "0.1.86 में उपलब्ध", "releaseNote": "संस्करण 0.1.86 से npm, Homebrew और नेटिव रिलीज़ में शामिल है।", "guide": "बहाली की गाइड (अंग्रेज़ी में)", "from": "इस कंप्यूटर पर", "to": "अगले कंप्यूटर पर", "transfer": "इन दोनों कमांड के बीच {manifest} और {lockfile} को दूसरे कंप्यूटर पर कॉपी करें।", - "directory": "डायरेक्टरी न देने पर दोनों कमांड वर्तमान डायरेक्टरी का उपयोग करती हैं।", "requirements": "बहाली के लिए दर्ज स्रोतों का उपलब्ध रहना और निर्भरता प्रबंधन द्वारा समर्थित क्लाइंट आवश्यक हैं।", "excludes": "साइन-इन, सीक्रेट, सेटिंग और प्लगइन डेटा स्थानांतरित नहीं होते।" }, diff --git a/landing/locales/pt.json b/landing/locales/pt.json index 0d62decb8..78f47336f 100644 --- a/landing/locales/pt.json +++ b/landing/locales/pt.json @@ -211,14 +211,12 @@ "restore": { "eyebrow": "Ambientes de plugins reproduzíveis", "title": "As mesmas versões de plugins no seu próximo computador", - "intro": "Salve seu conjunto global de plugins e suas dependências e restaure as versões exatas registradas em outro computador.", "availability": "Disponível na versão 0.1.86", "releaseNote": "Incluído nas versões npm, Homebrew e nos binários nativos a partir da versão 0.1.86.", "guide": "Guia de restauração (em inglês)", "from": "Neste computador", "to": "No próximo computador", "transfer": "Copie {manifest} e {lockfile} para o outro computador entre estes comandos.", - "directory": "Sem um diretório especificado, ambos os comandos usam o diretório atual.", "requirements": "A restauração exige que as fontes registradas continuem disponíveis e que os clientes sejam compatíveis com o gerenciamento de dependências.", "excludes": "Logins, segredos, configurações e dados dos plugins não são transferidos." }, diff --git a/landing/locales/ru.json b/landing/locales/ru.json index e35a790a2..0c2991d8a 100644 --- a/landing/locales/ru.json +++ b/landing/locales/ru.json @@ -348,14 +348,12 @@ "restore": { "eyebrow": "Воспроизводимое окружение плагинов", "title": "Те же версии плагинов на другом компьютере", - "intro": "Сохраните глобальный набор плагинов и их зависимостей, затем восстановите точно зафиксированные версии на другом компьютере.", "availability": "Доступно в 0.1.86", "releaseNote": "Входит в выпуски npm, Homebrew и нативные сборки начиная с версии 0.1.86.", "guide": "Инструкция по восстановлению (на английском)", "from": "На этом компьютере", "to": "На другом компьютере", "transfer": "Между этими командами скопируйте {manifest} и {lockfile} на другой компьютер.", - "directory": "Если каталог не указан, обе команды используют текущий каталог.", "requirements": "Для восстановления нужны доступные зафиксированные источники и клиенты, поддерживаемые системой управления зависимостями.", "excludes": "Входы в аккаунты, секреты, настройки и данные плагинов не переносятся." }, diff --git a/landing/locales/uk.json b/landing/locales/uk.json index 3bad1a286..26dc8570a 100644 --- a/landing/locales/uk.json +++ b/landing/locales/uk.json @@ -348,14 +348,12 @@ "restore": { "eyebrow": "Відтворюване середовище плагінів", "title": "Ті самі версії плагінів на іншому компʼютері", - "intro": "Збережіть глобальний набір плагінів та їхніх залежностей, а потім відновіть точно зафіксовані версії на іншому компʼютері.", "availability": "Доступно у 0.1.86", "releaseNote": "Входить до випусків npm, Homebrew і нативних збірок починаючи з версії 0.1.86.", "guide": "Інструкція з відновлення (англійською)", "from": "На цьому компʼютері", "to": "На іншому компʼютері", "transfer": "Між цими командами скопіюйте {manifest} і {lockfile} на інший компʼютер.", - "directory": "Якщо каталог не вказано, обидві команди використовують поточний каталог.", "requirements": "Для відновлення потрібні доступні зафіксовані джерела та клієнти, підтримувані системою керування залежностями.", "excludes": "Входи в облікові записи, секрети, налаштування та дані плагінів не переносяться." }, diff --git a/landing/locales/zh.json b/landing/locales/zh.json index 5688376e2..e930c3db2 100644 --- a/landing/locales/zh.json +++ b/landing/locales/zh.json @@ -348,14 +348,12 @@ "restore": { "eyebrow": "可复现的插件环境", "title": "在下一台电脑上使用相同的插件版本", - "intro": "保存全局插件集及其依赖,然后在另一台电脑上恢复所记录的确切版本。", "availability": "已在 0.1.86 中发布", "releaseNote": "自 0.1.86 版本起,已包含在 npm、Homebrew 和原生发行版中。", "guide": "恢复指南(英文)", "from": "在当前电脑上", "to": "在下一台电脑上", "transfer": "在运行这两条命令之间,将 {manifest} 和 {lockfile} 复制到另一台电脑。", - "directory": "未指定目录时,两条命令均使用当前目录。", "requirements": "恢复需要记录的源仍可访问,并且客户端支持依赖管理。", "excludes": "不会传输登录状态、机密信息、设置或插件数据。" }, diff --git a/landing/tests/browser/i18n-integration.spec.ts b/landing/tests/browser/i18n-integration.spec.ts index e7b52c10a..6b6dec378 100644 --- a/landing/tests/browser/i18n-integration.spec.ts +++ b/landing/tests/browser/i18n-integration.spec.ts @@ -36,8 +36,11 @@ for (const locale of publishedLocales) { ).shell.restore as Record; const restore = page.locator('#restore-environment'); await expect(restore.getByRole('heading', { level: 2 })).toHaveText(copy.title!); - await expect(restore.locator('.registry-restore__availability strong')).toHaveText(copy.availability!); - await expect(restore.locator('.registry-restore__availability span')).toHaveText(copy.releaseNote!); + const availability = restore.locator('.registry-restore__availability'); + await expect(availability).toHaveText(copy.availability!); + await availability.hover(); + await expect(page.locator('.app-tooltip')).toContainText(copy.releaseNote!); + await page.mouse.move(0, 0); await expect(restore.locator('.command-snippet pre code')).toHaveText([ 'agentplugins deps export', 'agentplugins deps import --install', From f18066db6f96d19c457f90dd9d697418216fc9d3 Mon Sep 17 00:00:00 2001 From: iliya Date: Mon, 19 Oct 2026 22:12:59 +0300 Subject: [PATCH 2/4] refactor(landing): simplify restore block to one heading and redraw transfer art Drop the eyebrow and the 0.1.86 availability badge (owner request), make the guide link a single word in all locales, and give the machines and transfer arrow distinctive artwork: terminal-prompt monitor, laptop with check, and a paper dart on a dashed flight path. --- .../components/registry/RegistryRestore.vue | 100 +++++------------- landing/locales/ar.json | 5 +- landing/locales/en.json | 5 +- landing/locales/es.json | 5 +- landing/locales/fr.json | 5 +- landing/locales/hi.json | 5 +- landing/locales/pt.json | 5 +- landing/locales/ru.json | 5 +- landing/locales/uk.json | 5 +- landing/locales/zh.json | 5 +- landing/scripts/check-i18n.mjs | 1 + .../tests/browser/i18n-integration.spec.ts | 6 +- 12 files changed, 37 insertions(+), 115 deletions(-) diff --git a/landing/components/registry/RegistryRestore.vue b/landing/components/registry/RegistryRestore.vue index 873102c3d..182e44d5d 100644 --- a/landing/components/registry/RegistryRestore.vue +++ b/landing/components/registry/RegistryRestore.vue @@ -11,31 +11,14 @@ const { t } = useI18n(); >
-
-

{{ t('shell.restore.eyebrow') }}

-

{{ t('shell.restore.title') }}

-
-
- - - {{ t('shell.restore.releaseNote') }} - - {{ t('shell.restore.guide') }} -
+

{{ t('shell.restore.title') }}

+ {{ t('shell.restore.guide') }}
@@ -48,8 +31,10 @@ const { t } = useI18n(); fill="none" aria-hidden="true" > - + + + {{ t('shell.restore.from') }}

@@ -73,8 +58,9 @@ const { t } = useI18n(); uap.lock
- uap.toml @@ -91,8 +77,9 @@ const { t } = useI18n(); fill="none" aria-hidden="true" > - - + + + {{ t('shell.restore.to') }}

@@ -133,55 +120,20 @@ const { t } = useI18n(); &__header { display: flex; flex-wrap: wrap; - align-items: flex-end; + align-items: baseline; justify-content: space-between; gap: 14px 24px; margin-block-end: 26px; } h2 { - margin-block: 8px 0; + margin: 0; font-size: clamp(1.5rem, 3vw, 2rem); line-height: 1.2; letter-spacing: -0.035em; text-wrap: balance; } - .eyebrow { - color: var(--cyan); - } - - &__meta { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 10px 16px; - } - - &__availability { - display: inline-flex; - align-items: center; - gap: 7px; - padding: 5px 11px; - border: 1px solid var(--line-strong); - border-radius: 8px; - color: var(--text); - background: var(--surface-raised); - font-size: 0.78rem; - font-weight: 700; - cursor: help; - - svg { - width: 13px; - height: 13px; - color: var(--muted); - stroke: currentColor; - stroke-width: 1.9; - stroke-linecap: round; - stroke-linejoin: round; - } - } - &__guide { color: var(--primary-bright); font-size: 0.9rem; @@ -235,9 +187,9 @@ const { t } = useI18n(); } &__stage-icon { - width: 17px; - height: 17px; - flex: 0 0 17px; + width: 18px; + height: 18px; + flex: 0 0 18px; color: var(--cyan); stroke: currentColor; stroke-width: 1.7; @@ -287,8 +239,8 @@ const { t } = useI18n(); } &__arrow { - width: min(100%, 140px); - height: 22px; + width: min(100%, 150px); + height: 30px; color: var(--cyan); stroke: currentColor; stroke-width: 2; @@ -329,8 +281,8 @@ const { t } = useI18n(); .registry-restore__arrow, :dir(rtl) .registry-restore__arrow { transform: rotate(90deg); - width: 44px; - height: 30px; + width: 56px; + height: 34px; } } diff --git a/landing/locales/ar.json b/landing/locales/ar.json index e1bbfe6c9..1ac278f39 100644 --- a/landing/locales/ar.json +++ b/landing/locales/ar.json @@ -209,11 +209,8 @@ }, "shell": { "restore": { - "eyebrow": "بيئات إضافات قابلة لإعادة الإنشاء", "title": "إصدارات الإضافات نفسها على حاسوبك القادم", - "availability": "متاح في الإصدار 0.1.86", - "releaseNote": "مضمّن في إصدارات npm وHomebrew والملفات التنفيذية الأصلية بدءًا من الإصدار 0.1.86.", - "guide": "دليل الاستعادة (بالإنجليزية)", + "guide": "الدليل", "from": "على هذا الحاسوب", "to": "على حاسوبك القادم", "transfer": "انسخ {manifest} و{lockfile} إلى الحاسوب الآخر بين تنفيذ هذين الأمرين.", diff --git a/landing/locales/en.json b/landing/locales/en.json index 973a2fa18..4be6ab008 100644 --- a/landing/locales/en.json +++ b/landing/locales/en.json @@ -363,11 +363,8 @@ }, "shell": { "restore": { - "eyebrow": "Reproducible plugin environments", "title": "Same plugin versions on your next machine", - "availability": "Available in 0.1.86", - "releaseNote": "Included in npm, Homebrew and native releases from version 0.1.86.", - "guide": "Restore guide (English)", + "guide": "Guide", "from": "On this computer", "to": "On your next computer", "transfer": "Copy {manifest} and {lockfile} to the other computer between these commands.", diff --git a/landing/locales/es.json b/landing/locales/es.json index 22c8e1b56..f280868e0 100644 --- a/landing/locales/es.json +++ b/landing/locales/es.json @@ -346,11 +346,8 @@ }, "shell": { "restore": { - "eyebrow": "Entornos de plugins reproducibles", "title": "Las mismas versiones de plugins en tu próximo equipo", - "availability": "Disponible en 0.1.86", - "releaseNote": "Incluido en las versiones de npm, Homebrew y los binarios nativos a partir de la versión 0.1.86.", - "guide": "Guía de restauración (en inglés)", + "guide": "Guía", "from": "En este equipo", "to": "En tu próximo equipo", "transfer": "Copia {manifest} y {lockfile} al otro equipo entre estos comandos.", diff --git a/landing/locales/fr.json b/landing/locales/fr.json index a4a3a41ab..46ab372ed 100644 --- a/landing/locales/fr.json +++ b/landing/locales/fr.json @@ -346,11 +346,8 @@ }, "shell": { "restore": { - "eyebrow": "Environnements de plugins reproductibles", "title": "Les mêmes versions de plugins sur votre prochain ordinateur", - "availability": "Disponible dans la version 0.1.86", - "releaseNote": "Inclus dans les versions npm, Homebrew et les binaires natifs à partir de la version 0.1.86.", - "guide": "Guide de restauration (en anglais)", + "guide": "Guide", "from": "Sur cet ordinateur", "to": "Sur votre prochain ordinateur", "transfer": "Copiez {manifest} et {lockfile} sur l’autre ordinateur entre ces commandes.", diff --git a/landing/locales/hi.json b/landing/locales/hi.json index 8d00375c8..192e320a1 100644 --- a/landing/locales/hi.json +++ b/landing/locales/hi.json @@ -346,11 +346,8 @@ }, "shell": { "restore": { - "eyebrow": "दोबारा बनाए जा सकने वाले प्लगइन परिवेश", "title": "अगले कंप्यूटर पर भी प्लगइन के वही संस्करण", - "availability": "0.1.86 में उपलब्ध", - "releaseNote": "संस्करण 0.1.86 से npm, Homebrew और नेटिव रिलीज़ में शामिल है।", - "guide": "बहाली की गाइड (अंग्रेज़ी में)", + "guide": "गाइड", "from": "इस कंप्यूटर पर", "to": "अगले कंप्यूटर पर", "transfer": "इन दोनों कमांड के बीच {manifest} और {lockfile} को दूसरे कंप्यूटर पर कॉपी करें।", diff --git a/landing/locales/pt.json b/landing/locales/pt.json index 78f47336f..51b8d2799 100644 --- a/landing/locales/pt.json +++ b/landing/locales/pt.json @@ -209,11 +209,8 @@ }, "shell": { "restore": { - "eyebrow": "Ambientes de plugins reproduzíveis", "title": "As mesmas versões de plugins no seu próximo computador", - "availability": "Disponível na versão 0.1.86", - "releaseNote": "Incluído nas versões npm, Homebrew e nos binários nativos a partir da versão 0.1.86.", - "guide": "Guia de restauração (em inglês)", + "guide": "Guia", "from": "Neste computador", "to": "No próximo computador", "transfer": "Copie {manifest} e {lockfile} para o outro computador entre estes comandos.", diff --git a/landing/locales/ru.json b/landing/locales/ru.json index 0c2991d8a..9161f7318 100644 --- a/landing/locales/ru.json +++ b/landing/locales/ru.json @@ -346,11 +346,8 @@ }, "shell": { "restore": { - "eyebrow": "Воспроизводимое окружение плагинов", "title": "Те же версии плагинов на другом компьютере", - "availability": "Доступно в 0.1.86", - "releaseNote": "Входит в выпуски npm, Homebrew и нативные сборки начиная с версии 0.1.86.", - "guide": "Инструкция по восстановлению (на английском)", + "guide": "Инструкция", "from": "На этом компьютере", "to": "На другом компьютере", "transfer": "Между этими командами скопируйте {manifest} и {lockfile} на другой компьютер.", diff --git a/landing/locales/uk.json b/landing/locales/uk.json index 26dc8570a..67b1d7004 100644 --- a/landing/locales/uk.json +++ b/landing/locales/uk.json @@ -346,11 +346,8 @@ }, "shell": { "restore": { - "eyebrow": "Відтворюване середовище плагінів", "title": "Ті самі версії плагінів на іншому компʼютері", - "availability": "Доступно у 0.1.86", - "releaseNote": "Входить до випусків npm, Homebrew і нативних збірок починаючи з версії 0.1.86.", - "guide": "Інструкція з відновлення (англійською)", + "guide": "Інструкція", "from": "На цьому компʼютері", "to": "На іншому компʼютері", "transfer": "Між цими командами скопіюйте {manifest} і {lockfile} на інший компʼютер.", diff --git a/landing/locales/zh.json b/landing/locales/zh.json index e930c3db2..9aef9e280 100644 --- a/landing/locales/zh.json +++ b/landing/locales/zh.json @@ -346,11 +346,8 @@ }, "shell": { "restore": { - "eyebrow": "可复现的插件环境", "title": "在下一台电脑上使用相同的插件版本", - "availability": "已在 0.1.86 中发布", - "releaseNote": "自 0.1.86 版本起,已包含在 npm、Homebrew 和原生发行版中。", - "guide": "恢复指南(英文)", + "guide": "指南", "from": "在当前电脑上", "to": "在下一台电脑上", "transfer": "在运行这两条命令之间,将 {manifest} 和 {lockfile} 复制到另一台电脑。", diff --git a/landing/scripts/check-i18n.mjs b/landing/scripts/check-i18n.mjs index b76c104dc..ab40a8f1a 100644 --- a/landing/scripts/check-i18n.mjs +++ b/landing/scripts/check-i18n.mjs @@ -111,6 +111,7 @@ const reviewedCaptions = { 'shell.header.faq': 'FAQ', 'shell.navigation.plugins': 'Plugins', 'shell.navigation.faq': 'FAQ', + 'shell.restore.guide': 'Guide', 'registryUi.catalog.source': 'Source', 'registryUi.catalog.agent': 'Agent', 'registryUi.card.installable': 'installable', diff --git a/landing/tests/browser/i18n-integration.spec.ts b/landing/tests/browser/i18n-integration.spec.ts index 6b6dec378..2229b2273 100644 --- a/landing/tests/browser/i18n-integration.spec.ts +++ b/landing/tests/browser/i18n-integration.spec.ts @@ -36,11 +36,7 @@ for (const locale of publishedLocales) { ).shell.restore as Record; const restore = page.locator('#restore-environment'); await expect(restore.getByRole('heading', { level: 2 })).toHaveText(copy.title!); - const availability = restore.locator('.registry-restore__availability'); - await expect(availability).toHaveText(copy.availability!); - await availability.hover(); - await expect(page.locator('.app-tooltip')).toContainText(copy.releaseNote!); - await page.mouse.move(0, 0); + await expect(restore.locator('h1, h2, h3, h4, h5, h6')).toHaveCount(1); await expect(restore.locator('.command-snippet pre code')).toHaveText([ 'agentplugins deps export', 'agentplugins deps import --install', From ac18bd748d3714b9b6fe509c9b5867629fd4e60d Mon Sep 17 00:00:00 2001 From: iliya Date: Tue, 20 Oct 2026 00:00:55 +0300 Subject: [PATCH 3/4] feat(landing): restage restore block as monitor screens with flat commands The two stages now render as monitor objects with stands and the transfer arrow spans directly between them. Commands sit flat on the screen surface without nested card chrome, and the screen keeps the dark display palette in every theme so the flat text stays readable. --- .../components/registry/RegistryRestore.vue | 211 ++++++++++++------ 1 file changed, 142 insertions(+), 69 deletions(-) diff --git a/landing/components/registry/RegistryRestore.vue b/landing/components/registry/RegistryRestore.vue index 182e44d5d..e6f71c36a 100644 --- a/landing/components/registry/RegistryRestore.vue +++ b/landing/components/registry/RegistryRestore.vue @@ -22,23 +22,17 @@ const { t } = useI18n();
-
-

- - - {{ t('shell.restore.from') }} -

- +
+
+

+ + {{ t('shell.restore.from') }} +

+
+ +
+
+
@@ -58,9 +52,9 @@ const { t } = useI18n(); uap.lock
- uap.toml @@ -68,22 +62,17 @@ const { t } = useI18n();
-
-

- - - {{ t('shell.restore.to') }} -

- +
+
+

+ + {{ t('shell.restore.to') }} +

+
+ +
+
+
@@ -143,33 +132,75 @@ const { t } = useI18n(); &__flow { display: grid; - grid-template-columns: minmax(0, 1fr) minmax(150px, 200px) minmax(0, 1fr); + grid-template-columns: minmax(0, 1fr) minmax(220px, 250px) minmax(0, 1fr); align-items: stretch; gap: 16px; + padding-block-end: 30px; } - &__stage { - display: grid; - align-content: start; - gap: 12px; + &__machine { + position: relative; min-width: 0; - padding: 16px; - border: 1px solid var(--line); - border-radius: 14px; + padding: 12px 12px 26px; + border: 1px solid var(--line-strong); + border-radius: 18px; background: var(--surface-raised); + box-shadow: 0 12px 28px color-mix(in srgb, var(--text) 9%, transparent); - :deep(pre) { - direction: ltr; - text-align: left; - overflow-wrap: anywhere; + &::before { + content: ''; + position: absolute; + top: 5px; + left: 50%; + translate: -50% 0; + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--line-strong); + } + + &::after { + content: ''; + position: absolute; + bottom: 9px; + left: 50%; + translate: -50% 0; + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--cyan); + box-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 75%, transparent); } } + &__screen { + // A monitor screen keeps the dark display palette in every theme. + --surface-solid: #101522; + --surface-raised: #172033; + --text: #e0e6ff; + --muted: #9aa6c5; + --subtle: #7f8dac; + --line: rgba(203, 211, 255, 0.12); + --cyan: #00f0ff; + + overflow: hidden; + border-radius: 10px; + background: + linear-gradient(155deg, color-mix(in srgb, var(--cyan) 7%, transparent), transparent 48%), + var(--surface-solid); + box-shadow: + inset 0 0 0 1px var(--line), + inset 0 0 30px color-mix(in srgb, var(--cyan) 6%, transparent); + } + &__stage-label { display: flex; align-items: center; gap: 9px; margin: 0; + padding: 9px 13px; + border-block-end: 1px solid var(--line); + background: color-mix(in srgb, var(--surface-raised) 55%, transparent); color: var(--muted); font-size: 0.68rem; font-weight: 750; @@ -186,30 +217,67 @@ const { t } = useI18n(); font-variant-numeric: tabular-nums; } - &__stage-icon { - width: 18px; + &__screen-body { + padding: 15px 13px 17px; + + // Flat on the screen: no card chrome, just the command row. + :deep(.command-snippet.command-snippet--inline) { + border: 0; + border-radius: 0; + background: transparent; + } + + :deep(.command-snippet__header) { + min-height: 0; + padding: 0; + border: 0; + background: transparent; + } + + :deep(.command-snippet button) { + padding: 2px 4px; + color: var(--muted); + } + + :deep(pre) { + direction: ltr; + text-align: left; + white-space: pre-wrap; + overflow-wrap: break-word; + padding: 0; + } + } + + &__stand { + position: absolute; + top: 100%; + left: 50%; + translate: -50% 0; + width: 92px; height: 18px; - flex: 0 0 18px; - color: var(--cyan); - stroke: currentColor; - stroke-width: 1.7; - stroke-linecap: round; - stroke-linejoin: round; + background: var(--line-strong); + clip-path: polygon(38% 0, 62% 0, 66% 58%, 94% 58%, 100% 100%, 0 100%, 6% 58%, 34% 58%); } &__handoff { + position: relative; display: grid; - align-content: center; + grid-template-rows: 1fr auto 1fr; justify-items: center; - gap: 12px; min-width: 0; } &__files { + position: absolute; + left: 50%; + bottom: calc(50% + 27px); + translate: -50% 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; + width: max-content; + max-width: 100%; } &__file { @@ -239,8 +307,10 @@ const { t } = useI18n(); } &__arrow { - width: min(100%, 150px); - height: 30px; + grid-row: 2; + width: calc(100% + 32px); + margin-inline: -16px; + height: 36px; color: var(--cyan); stroke: currentColor; stroke-width: 2; @@ -265,24 +335,27 @@ const { t } = useI18n(); @media (max-width: 980px) { .registry-restore__flow { grid-template-columns: minmax(0, 1fr); + padding-block-end: 26px; } - .registry-restore__stage :deep(.command-snippet--inline pre), - .registry-restore__stage :deep(.command-snippet--inline code) { - white-space: pre-wrap; - overflow-wrap: anywhere; + .registry-restore__handoff { + grid-template-rows: auto auto; + gap: 12px; + padding-block: 4px; } - .registry-restore__handoff { - gap: 10px; - padding-block: 2px; + .registry-restore__files { + position: static; + translate: none; + width: auto; } .registry-restore__arrow, :dir(rtl) .registry-restore__arrow { transform: rotate(90deg); - width: 56px; - height: 34px; + width: 92px; + height: 36px; + margin-inline: 0; } } From 93d311c4813267ab8d928834f50e2fd4267f3394 Mon Sep 17 00:00:00 2001 From: iliya Date: Tue, 20 Oct 2026 14:05:27 +0300 Subject: [PATCH 4/4] fix(landing): reserve rotated restore arrow height on narrow screens A rotate(90deg) transform does not change the layout box, so the 92px-tall visual arrow only reserved 36px and could overlap the file chips and the machine below at stacked widths. Margins now reserve the full visual height. --- landing/components/registry/RegistryRestore.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/landing/components/registry/RegistryRestore.vue b/landing/components/registry/RegistryRestore.vue index e6f71c36a..61978cdfb 100644 --- a/landing/components/registry/RegistryRestore.vue +++ b/landing/components/registry/RegistryRestore.vue @@ -355,6 +355,8 @@ const { t } = useI18n(); transform: rotate(90deg); width: 92px; height: 36px; + // Rotation does not affect layout: reserve the full 92px visual height. + margin-block: 28px; margin-inline: 0; } }