feat: update PWA assets and improve input field styles
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
allowBuilds:
|
||||
sharp: true
|
||||
unrs-resolver: true
|
||||
minimumReleaseAgeExclude:
|
||||
- vite@8.2.1
|
||||
- lucide@1.29.0
|
||||
peerDependencyRules:
|
||||
allowAny:
|
||||
- vue
|
||||
|
||||
|
After Width: | Height: | Size: 945 B |
|
After Width: | Height: | Size: 588 B |
@@ -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 |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 557 B |
@@ -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'],
|
||||
});
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,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"]
|
||||
|
||||
@@ -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 в бандл
|
||||
|
||||