Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Lugares que combinam com você. PWA de recomendação de restaurantes por ocasião em Campo Grande (MS).
A pessoa escolhe a ocasião ("A dois, pra conversar", "Pagode e porção"...) e o tipo de comida, recebe os lugares que mais combinam, com o índice de compatibilidade e o "Por que combina com você?", e manda as 3 melhores opções para o grupo votar pelo WhatsApp, arrastando cards.
Precisa de Node 20+ e Docker (o Supabase local roda em containers).
npm install
npx supabase start # sobe Postgres, Auth, Storage e um caixa de e-mail local
cp .env.example .env.local # cole as chaves que o comando acima imprimiu
npm run dev
NEXT_PUBLIC_SUPABASE_URL = API URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY = Publishable key, SUPABASE_SECRET_KEY = Secret key.ADMIN_EMAILS para virar admin.supabase/seed.sql, fotos de teste do Unsplash) para testar. Em produção ele começa vazio e se enche pelo botão de importação do /admin.npx supabase db reset apaga tudo e recria o banco com o seed.| Quem | Onde | O que faz |
|---|---|---|
| Qualquer pessoa, sem conta | /, /o/..., /l/..., /v/... | Escolhe ocasião e comida, vê lugares, vota em link de amigo, avalia em 3 toques |
| Cliente com conta | /perfil | Listas "Quero ir" e "Já fui", selo Explorador (5 avaliações), nome |
| Dono de restaurante | /painel | Pede acesso ao lugar; depois de aprovado vê aparições por ocasião, cliques no WhatsApp e rotas, quem salvou; edita WhatsApp, Instagram, cardápio, tipo de comida e foto de capa |
| Time Kombin (admin) | /admin | Importa restaurantes do Google com um botão, revisa lugares, marca destaques, cadastra lugar à mão, aprova donos, vê cliques |
Login: e-mail e senha (com "Criar conta" e "Esqueci a senha") e, opcionalmente, Google (Supabase Auth). Admin é quem tem o e-mail em ADMIN_EMAILS ou role = 'admin' na tabela profiles.
https://kombin-seven.vercel.app) e, em Redirect URLs, adicione https://kombin-seven.vercel.app/auth/callback. Sem isso, o link de "esqueci a senha" volta para o lugar errado.https://<seu-projeto>.supabase.co/auth/v1/callback.AUTH_GOOGLE=1 e faça um Redeploy. O botão "Continuar com Google" só aparece com ela, para nunca mostrar um botão que dá erro.| Parte | Onde está |
|---|---|
| Descobrir: saudação do momento, ocasiões em carrossel, sua lista, "Escolhas para o seu momento" | src/app/page.tsx, src/components/OccasionCard.tsx, src/components/RestaurantCard.tsx |
| Filtros (momento, com quem, orçamento por pessoa, sem couvert, dá pra conversar, espaço kids, bairro) e contagem ao vivo | src/app/buscar |
Ranking por ocasião ou por comida (/o/tudo?c=pizza) + chips rápidos | src/app/o/[slug]/page.tsx, src/components/QuickPrefs.tsx |
| Tipos de comida, etiquetas de ambiente e mapeamento dos tipos do Google | src/lib/cuisines.ts, src/lib/tags.ts |
| Índice de compatibilidade (regras e pesos, sem IA em tempo real) | src/lib/match.ts, pesos em src/lib/occasions.ts |
| "Decidir com o mozão/galera": link de votação sem login, cards para arrastar, resultado ao vivo | src/app/v/[code], src/components/VoteBoard.tsx |
| Página do lugar: galeria, "Por que combina com você?", informações, sinais da galera, salvar | src/app/l/[id]/page.tsx |
| Botão "Reservar via WhatsApp" com mensagem pronta e clique rastreado | src/app/api/go/[kind]/route.ts |
| Avaliação relâmpago em 3 toques (gasto, couvert, barulho) | src/components/MicroReview.tsx, src/app/api/review/route.ts |
| Login com senha, cadastro, nova senha, perfil, painel do restaurante, admin | src/app/entrar, redefinir-senha, perfil, painel, admin |
Importação do Google pelo /admin (ou terminal), notas por regra e vibe-check opcional por IA de visão | src/app/admin/ImportPanel.tsx, src/app/admin/import-actions.ts, src/lib/ingest/ |
| Animações: transição entre telas e foto que "voa" para a página do lugar (View Transitions), abas, coração, confete | src/app/globals.css, src/components/ui/ |
| Banco | supabase/migrations/ |
Cada ocasião dá pesos aos critérios: vibe (nota por regra, da IA ou do admin para aquela ocasião), barulho, couvert, orçamento, distância, etiquetas que a ocasião pede (ex.: aniversário quer "bom pra grupos" e "aceita reserva") e tamanho do grupo. Cada critério tem um encaixe de 0 a 1 e uma confiança, que cresce com as micro-reviews:
compat = 100 × (Σ peso·confiança·encaixe + 0,5) / (Σ peso·confiança + 1)
Lugar sem dados fica em 50%. Filtros duros (tipo de comida, bairro, etiquetas marcadas, "sem couvert", orçamento muito abaixo) tiram o lugar da lista. O resultado nunca passa de 99% e sempre vem com até 3 motivos e 1 alerta. Para mudar o comportamento, mexa nos pesos em src/lib/occasions.ts. Os testes estão em tests/match.test.ts (npm test).
Três coisas deixam o índice mais esperto, todas de graça (testes em tests/context.test.ts):
src/lib/weather.ts, src/lib/context.ts): a cada 15 minutos o servidor consulta a Open-Meteo (sem chave, sem cadastro) para o centro de Campo Grande. Chovendo ou abaixo de 18 °C, lugar com a etiqueta "Coberto / climatizado" sobe e lugar só com "Área externa" desce, e a lista mostra "Chovendo agora em CG · Priorizando lugares cobertos". Em noite quente (24 °C ou mais, sem chuva) a área externa ganha um empurrão. Lugar sem nenhuma das duas etiquetas não muda. A importação marca "Coberto" quando o Google diz que o lugar não tem mesa ao ar livre; o resto se ajusta no /admin.src/components/MicroReview.tsx, src/app/api/review/route.ts): avaliação vale em dobro se a pessoa fez check-in (o navegador calcula a distância até o lugar com Haversine e manda só "sim"; a localização nunca sai do celular) ou clicou em "Como chegar" entre 30 minutos e 3 dias antes de avaliar. A view place_signals faz as médias ponderadas.src/lib/taste.ts): cada busca fica na tabela search_log (sem localização) e, junto com os lugares que a pessoa escolhe (Como chegar, WhatsApp, salvos, avaliados), vira um gosto: etiquetas, comidas, gasto típico e se prefere lugar calmo. O que foi feito há um mês vale metade. Depois de umas 3 buscas, o gosto entra como mais um critério e a lista avisa "No seu estilo".supabase/migrations/0001_init.sql, 0002_contas_paineis_culinaria.sql, 0003_tags_destaque.sql, 0004_seguranca.sql, 0005_cardapio_dono.sql e 0006_indice_contexto.sql. O editor de consultas da Vercel não serve: ele não roda vários comandos de uma vez.NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY e SUPABASE_SECRET_KEY. Nos dois casos, cadastre ADMIN_EMAILS na Vercel.GOOGLE_MAPS_API_KEY na Vercel (Settings > Environment Variables), depois faça um Redeploy. Antes, crie um alerta de orçamento de US$ 1 e limite a cota diária da API./admin > Importar do Google > Começar importação. Primeiro entram, pelo nome, as casas essenciais de Campo Grande (lista ESSENTIALS em src/lib/ingest/google.ts: Enoteca Decanter, Turista Sushi, Mr. VIP...). Depois vêm 16 buscas (pizzarias, japoneses, bares com música...); entram só lugares abertos, com nota 4,2+ e 150+ avaliações, já aprovados e com notas por ocasião calculadas a partir dos atributos do Google (música ao vivo, bom pra grupos, cardápio infantil...). Dá para rodar de novo: só entram lugares novos. Lugar específico que faltou: campo "Adicionar um lugar específico" na mesma tela.GEMINI_API_KEY (ou ANTHROPIC_API_KEY). Em /admin > Importar aparece "Refinar com IA de visão": a IA olha 3 fotos de cada lugar e ajusta as notas pelo ambiente; ambiente feio volta para "Para revisar"./admin > Aprovados, confira o tipo de comida, marque os destaques (aparecem primeiro no Descobrir) e cadastre o WhatsApp de cada lugar.npm run refresh (renova as coordenadas, regra dos 30 dias do Google).Pelo terminal, a mesma importação: npm run ingest -- --dry-run (não grava, salva scripts/out/), --limit 40, --vision, --min-rating 4.3, --min-reviews 200, --provider anthropic. Precisa das chaves no .env.local (npx vercel env pull .env.local baixa as da Vercel).
| Etapa | Cota grátis | Depois |
|---|---|---|
| Importação: Text Search com atributos (Enterprise + Atmosphere) | 1.000 chamadas | US$ 40 por mil |
| Fotos exibidas no app (Place Photos) | 1.000 fotos | US$ 7 por mil |
| Vibe-check no Gemini Flash (opcional, 3 fotos por lugar) | plano gratuito (com limite por minuto) | ver preços do Google AI |
| Página do lugar: nota e horário ao vivo (Enterprise) | 1.000 | US$ 20 por mil |
Uma importação completa gasta 16 buscas. As fotos passam pelo nosso servidor (/api/photo/...) e ficam no cache da Vercel por 1 dia; a nota e o horário ao vivo, por 30 minutos. Assim cada foto é cobrada no máximo uma vez por dia, e não a cada visita. No plano gratuito do Gemini, o Google pode usar as imagens enviadas para melhorar os modelos.
place_id (permitido para sempre) e as coordenadas por até 30 dias.src/lib/validation.ts, zod) e conferir o limite de requisições (src/lib/rate-limit.ts). A chave pública vai para o navegador só para o login; a migração 0004_seguranca.sql liga RLS em todas as tabelas, libera apenas leitura do que é público ou da própria pessoa e revoga qualquer escrita pela chave pública.GOOGLE_DAILY_LIMIT, padrão 500 chamadas). Mesmo assim, coloque cota diária na Places API e alerta de orçamento no Google Cloud: é o único freio que vale fora do código.ADMIN_EMAILS e ainda não tem conta. Em produção prefira update profiles set role = 'admin' where id = (select id from auth.users where email = '[email protected]'); e deixe ADMIN_EMAILS vazio, ou mantenha a confirmação de e-mail ligada.Suba o repositório no GitHub e importe na Vercel (plano Hobby). Cadastre as mesmas variáveis do .env.local no painel da Vercel. O plano Hobby é para uso não comercial: quando começarem a cobrar restaurantes, migrem para o Pro ou para outro provedor.
Direção "Tela cheia": a foto do lugar ocupa a tela e o resto é noite.
#2A1B12, superfícies #36251A e #423024, texto #F6F0E6, secundário #B9B0A6. Um destaque só, o amarelo do ipê #FFC81E: botão principal, "% combina" e o sublinhado da aba ativa. Tokens em src/app/globals.css.title (semibold, letra apertada); o tamanho do nome do lugar vem de posterNameSize em src/lib/screen.ts.btn() e BTN_* em src/components/ui/styles.ts), foto não tem canto arredondado, botão por cima de foto é um círculo claro (ROUND_LIGHT). Sem vidro, sem sombra, sem gradiente de enfeite; o véu escuro só existe embaixo de texto em cima de foto (scrim-bottom, scrim-top).src/components/Discover.tsx), passa pra cima para o próximo; a última tela da ocasião manda os 3 primeiros pra votar ou abre a lista com filtros. O servidor já manda o ranking das 6 ocasiões, então trocar de aba é instantâneo.HIDDEN em src/components/ui/TabBar.tsx).kb_did). Conta serve para listas, selo e painéis.src/lib/catalog.ts) e é renovado na hora quando o admin ou um dono muda algo. O login só é verificado quando há cookie de sessão. Dados do Google ao vivo carregam depois da página. Cada tela tem esqueleto de carregamento.<ViewTransition>); gestos (arrastar card, folha de avaliação) usam motion. Tudo respeita "reduzir movimento" do sistema.| Comando | O que faz |
|---|---|
npm run dev | servidor de desenvolvimento |
npm test | testes do índice e da importação |
npx supabase start / npx supabase db reset | banco local / recriar com dados fictícios |
npx tsx scripts/gen-seed.ts | regenera supabase/seed.sql a partir de tests/fixtures.ts |
npm run lint / npm run typecheck | qualidade |
npm run ingest -- [--dry-run] [--limit N] [--vision] [--provider gemini|anthropic] | importação pelo terminal |
npm run refresh | renova coordenadas do Google |