feat: update PWA assets and improve input field styles

This commit is contained in:
2026-08-07 16:35:23 +07:00
parent 96cb8d728b
commit 6f613139f7
20 changed files with 3713 additions and 55 deletions
+3 -1
View File
@@ -3,7 +3,9 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#0b0d12" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Ккал" />
<title>Ккал — дневник питания</title>
</head>
<body>
+6 -3
View File
@@ -7,6 +7,7 @@
"scripts": {
"dev": "vite",
"build:engine": "pnpm --dir ../vue-sync-engine install --frozen-lockfile && pnpm --dir ../vue-sync-engine build:lib",
"generate-pwa-assets": "pwa-assets-generator",
"build": "pnpm run build:engine && vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
@@ -22,7 +23,7 @@
"@robonen/stdlib": "^0.0.12",
"@robonen/vue": "^0.2.0",
"date-fns": "^4.4.0",
"lucide": "^1.28.0",
"lucide": "^1.29.0",
"vue": "3.6.0-rc.2",
"vue-sync-engine": "link:../vue-sync-engine/lib"
},
@@ -30,13 +31,15 @@
"@robonen/eslint": "^0.0.1",
"@robonen/tsconfig": "^0.1.0",
"@tailwindcss/vite": "^4.3.3",
"@types/node": "^25.9.1",
"@types/node": "^26.1.2",
"@vite-pwa/assets-generator": "^1.0.2",
"eslint": "^10.8.0",
"jiti": "^2.7.0",
"tailwindcss": "^4.3.3",
"typescript": "~6.0.3",
"vite": "^8.2.1",
"vitest": "^4.1.8",
"vite-plugin-pwa": "^1.3.0",
"vitest": "^4.1.10",
"vue-jsx-vapor": "^3.2.19"
}
}
+3624 -43
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -1,7 +1,9 @@
allowBuilds:
sharp: true
unrs-resolver: true
minimumReleaseAgeExclude:
- vite@8.2.1
- lucide@1.29.0
peerDependencyRules:
allowAny:
- vue
Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

+6
View File
@@ -0,0 +1,6 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" fill="#12100d"/>
<path d="M256 40c-10 74-44 118-84 152-34 30-76 74-76 144 0 66 72 128 160 128s160-62 160-128c0-96-116-168-160-296z" fill="#cf7728"/>
<path d="M256 208c44 72 84 108 84 158 0 54-38 90-84 90s-84-36-84-90c0-50 40-86 84-158z" fill="#eda05b"/>
<path d="M256 330c20 34 40 50 40 74 0 30-18 48-40 48s-40-18-40-48c0-24 20-40 40-74z" fill="#ece4d6"/>
</svg>

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from '@vite-pwa/assets-generator/config';
// Исходник — public/logo.svg. Фон подложки совпадает с --color-bg, иначе
// maskable-иконка и apple-touch получили бы белые поля на тёмном логотипе.
const background = '#12100d';
export default defineConfig({
headLinkOptions: { preset: '2023' },
preset: {
transparent: { sizes: [64, 192, 512], favicons: [[48, 'favicon.ico']] },
maskable: { sizes: [512], padding: 0.3, resizeOptions: { background } },
apple: { sizes: [180], padding: 0.3, resizeOptions: { background } },
},
images: ['public/logo.svg'],
});
+2 -1
View File
@@ -196,7 +196,8 @@ export default function AddSheet() {
return Number.isFinite(value) && value >= 0 ? value : 0;
};
const fieldClass = 'w-full rounded-xl border hairline bg-raised/70 px-3.5 py-2.5 text-[15px] text-ink outline-none transition focus:border-ember/50 placeholder:text-ink-faint';
// 16px обязательны: при меньшем шрифте iOS Safari зумит страницу на фокусе.
const fieldClass = 'w-full rounded-xl border hairline bg-raised/70 px-3.5 py-2.5 text-[16px] text-ink outline-none transition focus:border-ember/50 placeholder:text-ink-faint';
const chipClass = (active: boolean) =>
`rounded-full border px-3 py-1.5 text-[13px] transition ${active
? 'border-ember/60 bg-ember/15 text-ember-bright'
+2 -1
View File
@@ -73,7 +73,8 @@ export default function EditEntrySheet() {
return Number.isFinite(value) && value >= 0 ? value : 0;
};
const fieldClass = 'w-full rounded-xl border hairline bg-raised/70 px-3.5 py-2.5 text-[15px] text-ink outline-none transition focus:border-ember/50';
// 16px обязательны: при меньшем шрифте iOS Safari зумит страницу на фокусе.
const fieldClass = 'w-full rounded-xl border hairline bg-raised/70 px-3.5 py-2.5 text-[16px] text-ink outline-none transition focus:border-ember/50';
const chipClass = (active: boolean) =>
`rounded-full border px-3 py-1.5 text-[13px] transition ${active
? 'border-ember/60 bg-ember/15 text-ember-bright'
+2 -1
View File
@@ -63,7 +63,8 @@ export default function FoodFormSheet() {
return Number.isFinite(value) && value >= 0 ? value : 0;
};
const fieldClass = 'w-full rounded-xl border hairline bg-raised/70 px-3.5 py-2.5 text-[15px] text-ink outline-none transition focus:border-ember/50 placeholder:text-ink-faint';
// 16px обязательны: при меньшем шрифте iOS Safari зумит страницу на фокусе.
const fieldClass = 'w-full rounded-xl border hairline bg-raised/70 px-3.5 py-2.5 text-[16px] text-ink outline-none transition focus:border-ember/50 placeholder:text-ink-faint';
return (
<div class="fixed inset-0 z-50 flex items-end justify-center">
+1 -1
View File
@@ -46,7 +46,7 @@ export default function FoodsScreen() {
placeholder="Поиск по каталогу…"
value={query.value}
onInput={event => (query.value = event.currentTarget.value)}
class="w-full rounded-xl border hairline bg-raised/70 py-2.5 pr-3.5 pl-10 text-[15px] text-ink outline-none transition focus:border-ember/50 placeholder:text-ink-faint"
class="w-full rounded-xl border hairline bg-raised/70 py-2.5 pr-3.5 pl-10 text-[16px] text-ink outline-none transition focus:border-ember/50 placeholder:text-ink-faint"
/>
</div>
+2 -1
View File
@@ -118,7 +118,8 @@ export default function ProfileScreen(props: { onboarding?: boolean }) {
return Number.isFinite(value) && value >= 0 ? value : 0;
};
const fieldClass = 'w-full rounded-xl border hairline bg-raised/70 px-3.5 py-2.5 text-[15px] text-ink outline-none transition focus:border-ember/50 tnum';
// 16px обязательны: при меньшем шрифте iOS Safari зумит страницу на фокусе.
const fieldClass = 'w-full rounded-xl border hairline bg-raised/70 px-3.5 py-2.5 text-[16px] text-ink outline-none transition focus:border-ember/50 tnum';
const chipClass = (active: boolean) =>
`flex-1 rounded-xl border px-3 py-2 text-[13px] transition ${active
? 'border-ember/60 bg-ember/15 text-ember-bright'
+1 -1
View File
@@ -258,7 +258,7 @@ export default function StatsScreen() {
placeholder="Вес сегодня, кг"
value={weightInput.value || ''}
onInput={event => (weightInput.value = numeric(event.currentTarget.value))}
class="w-full flex-1 rounded-xl border hairline bg-raised/70 px-3.5 py-2.5 text-[15px] text-ink outline-none transition focus:border-ember/50 placeholder:text-ink-faint tnum"
class="w-full flex-1 rounded-xl border hairline bg-raised/70 px-3.5 py-2.5 text-[16px] text-ink outline-none transition focus:border-ember/50 placeholder:text-ink-faint tnum"
/>
<button
type="button"
+5 -1
View File
@@ -5,7 +5,11 @@
"jsxImportSource": "vue-jsx-vapor",
"types": ["node", "vite/client"],
"paths": {
"@/*": ["./src/*"]
"@/*": ["./src/*"],
// vue-sync-engine подключён симлинком и тянет свою vue из devDependencies.
// Аналог resolve.dedupe в vite.config.ts, но для резолвинга типов.
"vue": ["./node_modules/vue"],
"@vue/*": ["./node_modules/@vue/*"]
}
},
"include": ["src", "vite.config.ts", "eslint.config.ts"]
+42 -1
View File
@@ -3,13 +3,54 @@ import { defineConfig } from 'vitest/config';
import vueJsxVapor from 'vue-jsx-vapor/vite';
import tailwindcss from '@tailwindcss/vite';
import { syncEnginePlugin } from 'vue-sync-engine/plugin';
import { VitePWA } from 'vite-plugin-pwa';
// JSX компилируется сразу в Vapor-код (без interop и virtual DOM).
// Tailwind v4 подключён как Vite-плагин, конфиг живёт в src/app.css (@theme).
// syncEnginePlugin собирает дефы в virtual:sync-engine-registry — его требует
// DevTools-ветка движка в dev-режиме.
export default defineConfig({
plugins: [vueJsxVapor(), tailwindcss(), syncEnginePlugin({ definitions: ['/src/data/defs.ts'] })],
plugins: [
vueJsxVapor(),
tailwindcss(),
syncEnginePlugin({ definitions: ['/src/data/defs.ts'] }),
// Установка на домашний экран — единственный способ вывести IndexedDB
// из-под 7-дневной очистки ITP в WebKit (Safari и Chrome на iOS).
VitePWA({
registerType: 'autoUpdate',
injectRegister: 'auto',
// Иконки и <link>-теги берутся из pwa-assets.config.ts.
pwaAssets: { config: true },
manifest: {
name: 'Ккал — дневник питания',
short_name: 'Ккал',
description: 'Локальный дневник питания: калории, БЖУ, вес. Данные хранятся только на устройстве.',
lang: 'ru',
display: 'standalone',
orientation: 'portrait',
start_url: '/',
scope: '/',
theme_color: '#12100d',
background_color: '#12100d',
},
workbox: {
// woff тут дубли woff2 от fontsource — в прекеш идёт только woff2.
globPatterns: ['**/*.{js,css,html,woff2,svg,png,ico}'],
// Штрихкоды из Open Food Facts: сеть свежее, но офлайн отдаём кэш.
runtimeCaching: [
{
urlPattern: /^https:\/\/world\.openfoodfacts\.org\/.*/,
handler: 'NetworkFirst',
options: {
cacheName: 'off-api',
expiration: { maxEntries: 200, maxAgeSeconds: 60 * 60 * 24 * 30 },
cacheableResponse: { statuses: [0, 200] },
},
},
],
},
}),
],
resolve: {
// vue-sync-engine подключён симлинком (link:), поэтому его импорты `vue`
// уходят в собственную vue из devDependencies движка. Без dedupe в бандл