umpordez 1.2.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/README.md +28 -1
  3. package/cli.mjs +4 -2
  4. package/create.mjs +86 -7
  5. package/package.json +1 -1
  6. package/template/CLAUDE.md +1 -1
  7. package/template/gitignore +13 -0
  8. package/template/misc/build-local.sh +82 -0
  9. package/template/mobile/{{PROJECT_SLUG}}/App.tsx +5 -2
  10. package/template/mobile/{{PROJECT_SLUG}}/CHANGELOG.md +28 -0
  11. package/template/mobile/{{PROJECT_SLUG}}/CLAUDE.md +10 -1
  12. package/template/mobile/{{PROJECT_SLUG}}/docs/releases.md +78 -0
  13. package/template/mobile/{{PROJECT_SLUG}}/docs/screenshots.md +84 -0
  14. package/template/mobile/{{PROJECT_SLUG}}/docs/store-copy.md +122 -0
  15. package/template/mobile/{{PROJECT_SLUG}}/docs/store-listing.md +107 -0
  16. package/template/mobile/{{PROJECT_SLUG}}/gitignore +29 -0
  17. package/template/mobile/{{PROJECT_SLUG}}/src/config.ts +6 -0
  18. package/template/scripts/README.md +18 -1
  19. package/template/scripts/appstore-resize.sh +70 -0
  20. package/template/scripts/frame-shots.mjs +246 -0
  21. package/template/scripts/gen-mockups.mjs +120 -0
  22. package/template/scripts/screenshots-capture.sh +50 -0
  23. package/template/scripts/screenshots.flow.yaml +54 -0
  24. package/template/ui/site/_variants/app/en.ts +131 -0
  25. package/template/ui/site/_variants/app/footer.ejs +46 -0
  26. package/template/ui/site/_variants/app/header.ejs +102 -0
  27. package/template/ui/site/_variants/app/home.ejs +237 -0
  28. package/template/ui/site/_variants/app/not-found.ejs +22 -0
  29. package/template/ui/site/_variants/app/pt-BR.ts +203 -0
  30. package/template/ui/site/tailwind.config.js +9 -1
  31. package/template-variables.mjs +34 -0
  32. package/update.mjs +37 -2
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * gen-mockups — render headless store-screen mockups straight to PNGs,
4
+ * no simulator required. Runs anywhere in ~1s and feeds frame-shots.mjs.
5
+ *
6
+ * node scripts/gen-mockups.mjs # → screenshots/raw + raw-tablet
7
+ * node scripts/frame-shots.mjs # → screenshots/store/**
8
+ *
9
+ * This ships as a STARTING POINT — the screens below are generic
10
+ * placeholders in your brand color. Edit SCREENS (which screens, what
11
+ * they show) and the copy in LABELS to recreate your real app. Both
12
+ * stores allow marketing recreations of the UI.
13
+ *
14
+ * Requires @resvg/resvg-js in the mobile app (add it as a devDependency
15
+ * if missing: `cd mobile/{{PROJECT_SLUG}} && npm i -D @resvg/resvg-js`).
16
+ */
17
+
18
+ import { writeFileSync, mkdirSync } from 'node:fs';
19
+ import { dirname, join } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { createRequire } from 'node:module';
22
+
23
+ const REPO = join(dirname(fileURLToPath(import.meta.url)), '..');
24
+ const APP = join(REPO, 'mobile', '{{PROJECT_SLUG}}');
25
+ const require = createRequire(join(APP, 'package.json'));
26
+ const { Resvg } = require('@resvg/resvg-js');
27
+
28
+ const SHOTS = join(APP, 'screenshots');
29
+
30
+ // Brand + neutral surface palette.
31
+ const PRIMARY = '{{PRIMARY_COLOR}}';
32
+ const INK = '#0B0F12';
33
+ const CARD = '#151A1E';
34
+ const LINE = '#242B31';
35
+ const MUTED = '#3A434B';
36
+ const TEXT = '#E7EBEE';
37
+
38
+ // Per-locale copy. Add locales freely; each becomes a raw/<locale>/ dir.
39
+ const LABELS = {
40
+ 'pt-BR': {
41
+ greeting: 'Olá 👋',
42
+ tabs: ['Início', 'Explorar', 'Biblioteca', 'Perfil'],
43
+ titles: ['Sua tela inicial', 'Explore', 'Biblioteca', 'Perfil'],
44
+ },
45
+ en: {
46
+ greeting: 'Hi 👋',
47
+ tabs: ['Home', 'Explore', 'Library', 'Profile'],
48
+ titles: ['Your home', 'Explore', 'Library', 'Profile'],
49
+ },
50
+ };
51
+
52
+ // One entry per screenshot; `active` picks the highlighted tab.
53
+ const SCREENS = [
54
+ { active: 0 },
55
+ { active: 1 },
56
+ { active: 2 },
57
+ { active: 3 },
58
+ ];
59
+
60
+ const DEVICES = {
61
+ raw: { w: 1080, h: 2340 }, // phone
62
+ 'raw-tablet': { w: 1600, h: 2560 }, // tablet (portrait)
63
+ };
64
+
65
+ function screenSvg(w, h, labels, screen) {
66
+ const pad = Math.round(w * 0.06);
67
+ const tabW = w / labels.tabs.length;
68
+ const tabY = h - Math.round(h * 0.09);
69
+ const tabs = labels.tabs.map((label, i) => {
70
+ const cx = tabW * i + tabW / 2;
71
+ const on = i === screen.active;
72
+ return `
73
+ <rect x="${cx - 26}" y="${tabY}" width="52" height="52" rx="14" fill="${on ? PRIMARY : MUTED}"/>
74
+ <text x="${cx}" y="${tabY + 96}" text-anchor="middle" font-family="sans-serif" font-size="30" fill="${on ? TEXT : MUTED}">${label}</text>`;
75
+ }).join('');
76
+
77
+ const rows = [0, 1, 2, 3].map(i => {
78
+ const y = Math.round(h * 0.46) + i * Math.round(h * 0.1);
79
+ return `
80
+ <rect x="${pad}" y="${y}" width="${w - pad * 2}" height="${Math.round(h * 0.085)}" rx="26" fill="${CARD}" stroke="${LINE}" stroke-width="2"/>
81
+ <rect x="${pad + 30}" y="${y + 28}" width="70" height="70" rx="18" fill="${PRIMARY}" opacity="0.22"/>
82
+ <rect x="${pad + 130}" y="${y + 34}" width="${Math.round(w * 0.4)}" height="20" rx="10" fill="${MUTED}"/>
83
+ <rect x="${pad + 130}" y="${y + 70}" width="${Math.round(w * 0.26)}" height="16" rx="8" fill="${LINE}"/>`;
84
+ }).join('');
85
+
86
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
87
+ <rect width="${w}" height="${h}" fill="${INK}"/>
88
+ <text x="${pad}" y="${Math.round(h * 0.11)}" font-family="sans-serif" font-size="34" fill="${MUTED}">${labels.greeting}</text>
89
+ <text x="${pad}" y="${Math.round(h * 0.155)}" font-family="sans-serif" font-weight="800" font-size="72" fill="${TEXT}">${labels.titles[screen.active]}</text>
90
+ <rect x="${pad}" y="${Math.round(h * 0.2)}" width="${w - pad * 2}" height="${Math.round(h * 0.2)}" rx="34" fill="${PRIMARY}"/>
91
+ <rect x="${pad + 44}" y="${Math.round(h * 0.24)}" width="${Math.round(w * 0.3)}" height="24" rx="12" fill="#000000" opacity="0.35"/>
92
+ <rect x="${pad + 44}" y="${Math.round(h * 0.29)}" width="${Math.round(w * 0.5)}" height="44" rx="16" fill="#000000" opacity="0.5"/>
93
+ ${rows}
94
+ <rect x="0" y="${tabY - 24}" width="${w}" height="2" fill="${LINE}"/>
95
+ ${tabs}
96
+ </svg>`;
97
+ }
98
+
99
+ function render(svg, w) {
100
+ return new Resvg(svg, {
101
+ fitTo: { mode: 'width', value: w },
102
+ font: { loadSystemFonts: true, defaultFontFamily: 'sans-serif' },
103
+ }).render().asPng();
104
+ }
105
+
106
+ let count = 0;
107
+ for (const [dir, dev] of Object.entries(DEVICES)) {
108
+ for (const [locale, labels] of Object.entries(LABELS)) {
109
+ const outDir = join(SHOTS, dir, locale);
110
+ mkdirSync(outDir, { recursive: true });
111
+ SCREENS.forEach((screen, i) => {
112
+ const png = render(screenSvg(dev.w, dev.h, labels, screen), dev.w);
113
+ const name = String(i).padStart(2, '0');
114
+ writeFileSync(join(outDir, `${name}-screen.png`), png);
115
+ count++;
116
+ });
117
+ console.log(` ✓ ${dir}/${locale}: ${SCREENS.length} screens`);
118
+ }
119
+ }
120
+ console.log(`Done — ${count} raw mockups under mobile/{{PROJECT_SLUG}}/screenshots/`);
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # screenshots-capture — drive the Maestro flow for BOTH locales on the
4
+ # currently booted device, writing raw store screenshots.
5
+ #
6
+ # The app must already be built + installed on a booted simulator /
7
+ # emulator. Run:
8
+ # ./scripts/run-ios.sh # or: cd mobile/{{PROJECT_SLUG}} && npm run android
9
+ # ./scripts/screenshots-capture.sh phone
10
+ #
11
+ # Run once on a phone sim and once on an iPad / Android tablet:
12
+ # ./scripts/screenshots-capture.sh phone # → screenshots/raw
13
+ # ./scripts/screenshots-capture.sh tablet # → screenshots/raw-tablet
14
+ #
15
+ # Raw PNGs are the correct device resolution and can be uploaded to the
16
+ # stores as-is; frame them for polish with frame-shots.mjs (see
17
+ # docs/screenshots.md). Tune the labels below + the flow to your nav.
18
+ set -euo pipefail
19
+
20
+ GROUP="${1:-phone}"
21
+ case "$GROUP" in
22
+ phone) RAW=raw ;;
23
+ tablet) RAW=raw-tablet ;;
24
+ *) echo "Usage: $0 [phone|tablet]"; exit 1 ;;
25
+ esac
26
+
27
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
28
+ cd "$SCRIPT_DIR/../mobile/{{PROJECT_SLUG}}"
29
+ FLOW="../../scripts/screenshots.flow.yaml"
30
+
31
+ echo "==> Capturing pt-BR ($GROUP)…"
32
+ maestro test \
33
+ -e OUT="$RAW/pt-BR" -e LANG_LABEL="Português (Brasil)" \
34
+ -e IDIOMA="Idioma" -e HOME="Início" -e EXPLORE="Explorar" \
35
+ -e LIBRARY="Biblioteca" -e PROFILE="Perfil" \
36
+ -e DARK="Tema escuro" \
37
+ "$FLOW"
38
+
39
+ echo "==> Capturing en ($GROUP)…"
40
+ maestro test \
41
+ -e OUT="$RAW/en" -e LANG_LABEL="English" \
42
+ -e IDIOMA="Language" -e HOME="Home" -e EXPLORE="Explore" \
43
+ -e LIBRARY="Library" -e PROFILE="Profile" \
44
+ -e DARK="Dark theme" \
45
+ "$FLOW"
46
+
47
+ echo ""
48
+ echo "Done. Raw screenshots:"
49
+ echo " mobile/{{PROJECT_SLUG}}/screenshots/$RAW/pt-BR/"
50
+ echo " mobile/{{PROJECT_SLUG}}/screenshots/$RAW/en/"
@@ -0,0 +1,54 @@
1
+ # Maestro flow — capture store screenshots deterministically.
2
+ #
3
+ # Runs on any booted iOS simulator OR Android emulator. It resets the
4
+ # app, gets past onboarding (labels matched in BOTH languages so it works
5
+ # whatever the device language is), switches the in-app language to the
6
+ # target, then walks the main tabs and saves raw PNGs.
7
+ #
8
+ # This is a STARTING POINT — tune the tab labels + steps to your actual
9
+ # navigation. If you seed demo content via a dev-only action, add a tapOn
10
+ # for it after onboarding.
11
+ #
12
+ # Do not run this directly for both locales — use
13
+ # scripts/screenshots-capture.sh, which sets OUT + labels per locale.
14
+ #
15
+ # Raw output lands in screenshots/<OUT>/NN-name.png. iOS bundle is
16
+ # {{IOS_BUNDLE_ID}}; for Android pass --app-id {{ANDROID_PACKAGE}} on the CLI.
17
+ appId: {{IOS_BUNDLE_ID}}
18
+ ---
19
+ - launchApp:
20
+ clearState: true
21
+ stopApp: true
22
+
23
+ # ── Get past onboarding (tolerant to device language) ───────────────
24
+ - tapOn:
25
+ text: "Começar|Get started|Vamos|Continue"
26
+ retryTapIfNoChange: true
27
+ optional: true
28
+ - tapOn:
29
+ text: "Pular|Skip|Começar a usar|Start using"
30
+ optional: true
31
+
32
+ # ── Force the target language via Profile → language picker ─────────
33
+ - tapOn: "${PROFILE}|Perfil|Profile"
34
+ - tapOn:
35
+ text: "${IDIOMA}|Idioma|Language"
36
+ optional: true
37
+ - tapOn:
38
+ text: "${LANG_LABEL}"
39
+ optional: true
40
+
41
+ # ── 03 Profile ──────────────────────────────────────────────────────
42
+ - takeScreenshot: "screenshots/${OUT}/03-profile"
43
+
44
+ # ── 00 Home ─────────────────────────────────────────────────────────
45
+ - tapOn: "${HOME}"
46
+ - takeScreenshot: "screenshots/${OUT}/00-home"
47
+
48
+ # ── 01 Explore ──────────────────────────────────────────────────────
49
+ - tapOn: "${EXPLORE}"
50
+ - takeScreenshot: "screenshots/${OUT}/01-explore"
51
+
52
+ # ── 02 Library ──────────────────────────────────────────────────────
53
+ - tapOn: "${LIBRARY}"
54
+ - takeScreenshot: "screenshots/${OUT}/02-library"
@@ -0,0 +1,131 @@
1
+ import { TranslationShape } from './pt-BR.js';
2
+
3
+ const en: TranslationShape = {
4
+ meta: {
5
+ siteTitle: '{{PROJECT_NAME}} — for iOS and Android',
6
+ siteDescription: '{{PROJECT_DESCRIPTION}}'
7
+ },
8
+ nav: {
9
+ features: 'Features',
10
+ howItWorks: 'How it works',
11
+ pricing: 'Pricing',
12
+ download: 'Download',
13
+ languageToggle: 'PT'
14
+ },
15
+ hero: {
16
+ eyebrow: 'Now on iOS and Android',
17
+ title1: '{{PROJECT_NAME}},',
18
+ title2: 'in your pocket.',
19
+ subtitle: '{{PROJECT_DESCRIPTION}}',
20
+ ctaIos: 'Download on the App Store',
21
+ ctaAndroid: 'Get it on Google Play',
22
+ noSub: 'Free to start. No account needed.'
23
+ },
24
+ notIs: {
25
+ title: 'What it is not',
26
+ items: [
27
+ 'No ads',
28
+ 'No trackers',
29
+ 'No forced sign-up',
30
+ 'No hidden subscription'
31
+ ],
32
+ isTitle: 'Just the app you wanted.',
33
+ isBody: 'Fast, to the point, and built for daily use. Open it '
34
+ + 'and start; setup can wait until you want it.'
35
+ },
36
+ flow: {
37
+ eyebrow: 'How it works',
38
+ title: 'Three steps, done',
39
+ sub: 'No long tutorial. You get it in seconds.',
40
+ steps: [
41
+ {
42
+ title: 'Download',
43
+ body: 'Install from the App Store or Google Play. Takes '
44
+ + 'under a minute.'
45
+ },
46
+ {
47
+ title: 'Open and use',
48
+ body: 'No sign-up screen in the way. Start on the very '
49
+ + 'first screen.'
50
+ },
51
+ {
52
+ title: 'Take it with you',
53
+ body: 'Works when you need it, wherever you are, at your '
54
+ + 'own pace.'
55
+ }
56
+ ]
57
+ },
58
+ features: {
59
+ eyebrow: 'Features',
60
+ title: 'Built for every day',
61
+ sub: 'The essentials, done well. No bloat, no fuss.',
62
+ items: [
63
+ {
64
+ title: 'Genuinely fast',
65
+ body: 'Opens instantly and responds to every tap. No '
66
+ + 'loading spinners.'
67
+ },
68
+ {
69
+ title: 'Works offline',
70
+ body: 'Your data lives on the device. Use it on the '
71
+ + 'subway, on a plane, with no signal.'
72
+ },
73
+ {
74
+ title: 'Your data is yours',
75
+ body: 'No trackers, no data selling. What is yours stays '
76
+ + 'yours.'
77
+ },
78
+ {
79
+ title: 'Easy to learn',
80
+ body: 'A clean, direct interface. You find everything '
81
+ + 'without a manual.'
82
+ },
83
+ {
84
+ title: 'Backup anytime',
85
+ body: 'Export and restore in one tap. Switch phones '
86
+ + 'without losing a thing.'
87
+ },
88
+ {
89
+ title: 'Light and dark',
90
+ body: 'Follows the system theme or pin your favorite. '
91
+ + 'Comfortable any time of day.'
92
+ }
93
+ ]
94
+ },
95
+ pricing: {
96
+ eyebrow: 'Pricing',
97
+ title: 'One price, everything included',
98
+ sub: 'No catch, no hidden premium tier.',
99
+ price: 'Free',
100
+ priceNote: 'Download and start right now.',
101
+ included: [
102
+ 'Every feature, no locks',
103
+ 'Works offline',
104
+ 'Backup and restore',
105
+ 'Updates included'
106
+ ],
107
+ cta: 'Download now',
108
+ fine: 'No credit card. No sign-up.'
109
+ },
110
+ cta: {
111
+ title: 'Ready to start?',
112
+ body: 'Download {{PROJECT_NAME}} and see for yourself in under '
113
+ + 'a minute.'
114
+ },
115
+ footer: {
116
+ tagline: '{{PROJECT_DESCRIPTION}}',
117
+ product: 'Product',
118
+ support: 'Support',
119
+ contact: 'Get in touch',
120
+ rights: 'All rights reserved.',
121
+ terms: 'Terms of Use',
122
+ privacy: 'Privacy'
123
+ },
124
+ notFound: {
125
+ title: 'Page not found',
126
+ body: 'The content you are looking for is not here.',
127
+ cta: 'Back to home'
128
+ }
129
+ };
130
+
131
+ export default en;
@@ -0,0 +1,46 @@
1
+ <footer class="border-t border-zinc-800 bg-zinc-950">
2
+ <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-14">
3
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-10">
4
+ <div class="col-span-2">
5
+ <a href="<%= urlForLocale(locale) %>" class="flex items-center gap-2.5 mb-4">
6
+ <span class="w-8 h-8 rounded-xl bg-primary flex items-center justify-center font-black text-zinc-950 text-lg leading-none">
7
+ {{PROJECT_NAME_INITIAL}}
8
+ </span>
9
+ <span class="text-lg font-bold tracking-tight text-zinc-100">{{PROJECT_NAME}}</span>
10
+ </a>
11
+ <p class="text-sm text-zinc-500 max-w-xs leading-relaxed">
12
+ <%= t('footer.tagline') %>
13
+ </p>
14
+ </div>
15
+
16
+ <div>
17
+ <h4 class="text-xs font-semibold uppercase tracking-wider text-zinc-500 mb-4"><%= t('footer.product') %></h4>
18
+ <ul class="space-y-3 text-sm text-zinc-400">
19
+ <li><a href="<%= urlForLocale(locale) %>#features" class="hover:text-zinc-100 transition-colors"><%= t('nav.features') %></a></li>
20
+ <li><a href="<%= urlForLocale(locale) %>#how-it-works" class="hover:text-zinc-100 transition-colors"><%= t('nav.howItWorks') %></a></li>
21
+ <li><a href="<%= urlForLocale(locale) %>#pricing" class="hover:text-zinc-100 transition-colors"><%= t('nav.pricing') %></a></li>
22
+ </ul>
23
+ </div>
24
+
25
+ <div>
26
+ <h4 class="text-xs font-semibold uppercase tracking-wider text-zinc-500 mb-4"><%= t('footer.support') %></h4>
27
+ <ul class="space-y-3 text-sm text-zinc-400">
28
+ <li><a href="mailto:contato@{{DOMAIN}}" class="hover:text-zinc-100 transition-colors"><%= t('footer.contact') %></a></li>
29
+ <li><a href="<%= urlForLocale(locale) %>#download" class="hover:text-zinc-100 transition-colors"><%= t('nav.download') %></a></li>
30
+ </ul>
31
+ </div>
32
+ </div>
33
+
34
+ <div class="border-t border-zinc-800 mt-12 pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
35
+ <p class="text-sm text-zinc-500">
36
+ &copy; <%= currentYear %> {{PROJECT_NAME}}. <%= t('footer.rights') %>
37
+ </p>
38
+ <div class="flex gap-6 text-sm text-zinc-500">
39
+ <a href="<%= urlForLocale(locale) %>/termos" class="hover:text-zinc-300 transition-colors"><%= t('footer.terms') %></a>
40
+ <a href="<%= urlForLocale(locale) %>/privacidade" class="hover:text-zinc-300 transition-colors"><%= t('footer.privacy') %></a>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </footer>
45
+ </body>
46
+ </html>
@@ -0,0 +1,102 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= htmlLang %>">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title><%= typeof title !== 'undefined' ? title : t('meta.siteTitle') %></title>
7
+ <meta name="description" content="<%= t('meta.siteDescription') %>">
8
+ <meta name="robots" content="index, follow">
9
+ <meta name="theme-color" content="{{PRIMARY_COLOR}}">
10
+
11
+ <%# SEO: hreflang alternates — tells Google about every translated %>
12
+ <%# version of this page. x-default points at the primary locale %>
13
+ <%# so users in unmapped regions see the right language. %>
14
+ <% hreflangAlternates.forEach(function(alt) { %>
15
+ <link rel="alternate" hreflang="<%= alt.hreflang %>" href="<%= alt.url %>">
16
+ <% }); %>
17
+ <link rel="alternate" hreflang="x-default" href="<%= urlForLocale('pt-BR') %>">
18
+
19
+ <%# Canonical URL — avoids duplicate-content issues if the same %>
20
+ <%# content is reachable via multiple URLs. %>
21
+ <link rel="canonical" href="<%= canonicalUrl %>">
22
+
23
+ <%# Open Graph — locale + alternates for social shares. %>
24
+ <meta property="og:type" content="website">
25
+ <meta property="og:title" content="<%= typeof title !== 'undefined' ? title : t('meta.siteTitle') %>">
26
+ <meta property="og:description" content="<%= t('meta.siteDescription') %>">
27
+ <meta property="og:locale" content="<%= ogLocale %>">
28
+ <% ogLocaleAlternates.forEach(function(alt) { %>
29
+ <meta property="og:locale:alternate" content="<%= alt %>">
30
+ <% }); %>
31
+ <meta property="og:url" content="<%= canonicalUrl %>">
32
+
33
+ <link rel="stylesheet" href="/css/output.css">
34
+ </head>
35
+ <body class="min-h-screen bg-zinc-950 text-zinc-100 antialiased selection:bg-primary/30">
36
+ <header class="sticky top-0 z-50 border-b border-zinc-800/80 bg-zinc-950/80 backdrop-blur-md">
37
+ <nav class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
38
+ <div class="flex items-center justify-between h-16">
39
+ <a href="<%= urlForLocale(locale) %>" class="flex items-center gap-2.5 group">
40
+ <span class="w-8 h-8 rounded-xl bg-primary flex items-center justify-center font-black text-zinc-950 text-lg leading-none">
41
+ {{PROJECT_NAME_INITIAL}}
42
+ </span>
43
+ <span class="text-lg font-bold tracking-tight text-zinc-100">{{PROJECT_NAME}}</span>
44
+ </a>
45
+
46
+ <div class="hidden md:flex items-center gap-1">
47
+ <a href="<%= urlForLocale(locale) %>#features" class="px-4 py-2 text-sm font-medium text-zinc-400 hover:text-zinc-100 transition-colors">
48
+ <%= t('nav.features') %>
49
+ </a>
50
+ <a href="<%= urlForLocale(locale) %>#how-it-works" class="px-4 py-2 text-sm font-medium text-zinc-400 hover:text-zinc-100 transition-colors">
51
+ <%= t('nav.howItWorks') %>
52
+ </a>
53
+ <a href="<%= urlForLocale(locale) %>#pricing" class="px-4 py-2 text-sm font-medium text-zinc-400 hover:text-zinc-100 transition-colors">
54
+ <%= t('nav.pricing') %>
55
+ </a>
56
+ </div>
57
+
58
+ <div class="flex items-center gap-2">
59
+ <a href="<%= urlForLocale(locale === 'pt-BR' ? 'en' : 'pt-BR') %>"
60
+ class="hidden sm:inline-flex items-center px-3 py-2 text-xs font-bold tracking-wide text-zinc-400 hover:text-zinc-100 transition-colors"
61
+ hreflang="<%= locale === 'pt-BR' ? 'en' : 'pt-BR' %>">
62
+ <%= t('nav.languageToggle') %>
63
+ </a>
64
+ <a href="<%= urlForLocale(locale) %>#download"
65
+ class="inline-flex items-center gap-2 bg-zinc-100 text-zinc-900 px-4 py-2 rounded-xl hover:bg-white transition-colors text-sm font-semibold">
66
+ <%= t('nav.download') %>
67
+ </a>
68
+
69
+ <button id="mobile-menu-btn" class="md:hidden p-2 text-zinc-400 hover:text-zinc-100 transition-colors" aria-label="Menu">
70
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
71
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
72
+ </svg>
73
+ </button>
74
+ </div>
75
+ </div>
76
+
77
+ <div id="mobile-menu" class="hidden md:hidden pb-4">
78
+ <div class="flex flex-col gap-1 pt-3 border-t border-zinc-800">
79
+ <a href="<%= urlForLocale(locale) %>#features" class="px-4 py-3 text-sm font-medium text-zinc-400 hover:text-zinc-100 transition-colors">
80
+ <%= t('nav.features') %>
81
+ </a>
82
+ <a href="<%= urlForLocale(locale) %>#how-it-works" class="px-4 py-3 text-sm font-medium text-zinc-400 hover:text-zinc-100 transition-colors">
83
+ <%= t('nav.howItWorks') %>
84
+ </a>
85
+ <a href="<%= urlForLocale(locale) %>#pricing" class="px-4 py-3 text-sm font-medium text-zinc-400 hover:text-zinc-100 transition-colors">
86
+ <%= t('nav.pricing') %>
87
+ </a>
88
+ <a href="<%= urlForLocale(locale === 'pt-BR' ? 'en' : 'pt-BR') %>"
89
+ class="px-4 py-3 text-sm font-medium text-zinc-400 hover:text-zinc-100 transition-colors"
90
+ hreflang="<%= locale === 'pt-BR' ? 'en' : 'pt-BR' %>">
91
+ <%= t('nav.languageToggle') %>
92
+ </a>
93
+ </div>
94
+ </div>
95
+ </nav>
96
+ </header>
97
+
98
+ <script>
99
+ document.getElementById('mobile-menu-btn').addEventListener('click', function () {
100
+ document.getElementById('mobile-menu').classList.toggle('hidden');
101
+ });
102
+ </script>