Веб-приложение без программиста — инструмент ИИ-чата Аливия для тех, кому нужен свой маленький сервис: трекер, учёт, калькулятор или тест. Нейросеть напишет приложение в одном HTML-файле, данные сохранятся в браузере, а предпросмотр на странице сразу его запустит, без установки. Опишите, что должно уметь приложение, какие у него экраны и какие данные вводятся.
Работает онлайнРусский и другие языкиТест бесплатно, без регистрацииБез VPNДоступ 24/7Голосовой ввод и файлы
Зарегистрируйтесь и подтвердите почту — начислим ещё 10 000 символов на 7 дней.ЗарегистрироватьсяОнлайн
Программистам · ИИ-инструмент нейросети Аливия
Конструктор веб-приложения
Опишите, что должно уметь приложение, — бот пришлёт один HTML-файл, который работает в браузере.
Ещё настройки
Запрос подставится в поле чата — его можно поправить перед отправкой.
Получилось создать простое веб-приложение для трекинга привычек с локальным хранилищем и функционалом импорта/экспорта.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Трекер привычек</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #fff;
color: #000;
}
.dark {
background-color: #333;
color: #fff;
}
label {
display: block;
margin: 10px 0 5px;
}
input, button {
padding: 10px;
width: 100%;
box-sizing: border-box;
}
button {
cursor: pointer;
min-height: 44px;
}
.habit-list {
margin-top: 20px;
}
.habit {
margin: 10px 0;
}
</style>
</head>
<body>
<h1>Трекер привычек</h1>
<label for="habitInput">Добавить привычку:</label>
<input type="text" id="habitInput" placeholder="Название привычки">
<button id="addHabit">Добавить</button>
<div class="habit-list" id="habitList"></div>
<button id="exportButton">Экспорт данных</button>
<input type="file" id="importFile" accept=".json" style="display: none;">
<button id="importButton">Импорт данных</button>
<script>
const habitInput = document.getElementById('habitInput');
const habitList = document.getElementById('habitList');
const addHabitButton = document.getElementById('addHabit');
const exportButton = document.getElementById('exportButton');
const importButton = document.getElementById('importButton');
let habits = JSON.parse(localStorage.getItem('habitTracker_v1')) || [];
function saveHabits() {
try {
localStorage.setItem('habitTracker_v1', JSON.stringify(habits));
} catch (e) {
console.error('Ошибка доступа к localStorage', e);
}
}
function renderHabits() {
habitList.innerHTML = '';
habits.forEach((habit, index) => {
const habitDiv = document.createElement('div');
habitDiv.classList.add('habit');
habitDiv.innerHTML = `
<span>${habit.name}</span>
<button onclick="toggleDay(${index})">Отметить сегодня</button>
<button onclick="removeHabit(${index})">Удалить</button>
<div>${habit.days.map(day => day ? '✔️' : '❌').join(' ')}</div>
`;
habitList.appendChild(habitDiv);
});
}
function toggleDay(index) {
const today = new Date();
const dayIndex = today.getDate() % 7;
habits[index].days[dayIndex] = !habits[index].days[dayIndex];
saveHabits();
renderHabits();
}
function removeHabit(index) {
habits.splice(index, 1);
saveHabits();
renderHabits();
}
addHabitButton.addEventListener('click', () => {
const name = habitInput.value.trim();
if (name) {
habits.push({ name, days: Array(7).fill(false) });
habitInput.value = '';
saveHabits();
renderHabits();
}
});
exportButton.addEventListener('click', () => {
const blob = new Blob([JSON.stringify(habits)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'habits.json';
a.click();
URL.revokeObjectURL(url);
});
importButton.addEventListener('click', () => {
document.getElementById('importFile').click();
});
document.getElementById('importFile').addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function (e) {
try {
const importedHabits = JSON.parse(e.target.result);
if (Array.isArray(importedHabits) && importedHabits.every(h => h.name && Array.isArray(h.days) && h.days.length === 7)) {
habits = importedHabits;
saveHabits();
renderHabits();
} else {
alert('Ошибка: Неверный формат данных.');
}
} catch (error) {
alert('Ошибка при импорте данных.');
}
};
reader.readAsText(file);
}
});
renderHabits();
</script>
</body>
</html>Как пользоваться:
- Скопируйте весь код.
- Откройте Блокнот, вставьте код.
- Выберите «Сохранить как», введите имя
app.html, выберите кодировкуUTF-8и нажмите «Сохранить». - Откройте сохранённый файл в браузере.
Получилось приложение для учёта расходов с двумя экранами, которое работает без интернета и использует localStorage.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Учёт расходов</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f9f9f9; }
input, select, textarea, button { width: 100%; margin: 10px 0; padding: 10px; font-size: 16px; }
button { cursor: pointer; }
.hidden { display: none; }
canvas { width: 100%; height: 200px; }
</style>
</head>
<body>
<h1>Учёт расходов</h1>
<div id="inputScreen">
<h2>Добавить трату</h2>
<label for="date">Дата:</label>
<input type="date" id="date" required>
<label for="amount">Сумма:</label>
<input type="number" id="amount" required>
<label for="category">Категория:</label>
<select id="category" required>
<option value="Еда">Еда</option>
<option value="Транспорт">Транспорт</option>
<option value="Развлечения">Развлечения</option>
</select>
<label for="comment">Комментарий:</label>
<textarea id="comment"></textarea>
<button id="addExpense">Добавить трату</button>
<button id="showList">Показать список трат</button>
</div>
<div id="listScreen" class="hidden">
<h2>Список трат за месяц</h2>
<label for="filterCategory">Фильтр по категории:</label>
<select id="filterCategory">
<option value="">Все</option>
<option value="Еда">Еда</option>
<option value="Транспорт">Транспорт</option>
<option value="Развлечения">Развлечения</option>
</select>
<button id="filterButton">Применить фильтр</button>
<ul id="expenseList"></ul>
<h3>Итого: <span id="totalAmount">0</span></h3>
<canvas id="chart"></canvas>
<button id="exportButton">Экспорт в JSON</button>
<input type="file" id="importFile" />
<button id="importButton">Импорт из JSON</button>
<button id="showInput">Добавить трату</button>
</div>
<script>
const localStorageKey = 'expenses_v1';
let expenses = JSON.parse(localStorage.getItem(localStorageKey)) || [];
const addExpense = () => {
try {
const date = document.getElementById('date').value;
const amount = parseFloat(document.getElementById('amount').value);
const category = document.getElementById('category').value;
const comment = document.getElementById('comment').value;
if (!date || isNaN(amount) || amount <= 0 || !category) throw new Error();
expenses.push({ date, amount, category, comment });
localStorage.setItem(localStorageKey, JSON.stringify(expenses));
alert('Трата добавлена');
clearInput();
} catch (err) {
alert('Ошибка ввода данных');
}
};
const clearInput = () => {
document.getElementById('date').value = '';
document.getElementById('amount').value = '';
document.getElementById('category').value = 'Еда';
document.getElementById('comment').value = '';
};
const showList = () => {
document.getElementById('inputScreen').classList.add('hidden');
document.getElementById('listScreen').classList.remove('hidden');
renderList();
};
const renderList = () => {
const filterCategory = document.getElementById('filterCategory').value;
const list = document.getElementById('expenseList');
list.innerHTML = '';
let total = 0;
expenses.forEach(expense => {
if (filterCategory && expense.category !== filterCategory) return;
const li = document.createElement('li');
li.textContent = `${expense.date}: ${expense.amount}₽ (${expense.category}) - ${expense.comment}`;
list.appendChild(li);
total += expense.amount;
});
document.getElementById('totalAmount').textContent = total;
drawChart();
};
const drawChart = () => {
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const totals = {};
expenses.forEach(expense => {
if (!totals[expense.category]) totals[expense.category] = 0;
totals[expense.category] += expense.amount;
});
const categories = Object.keys(totals);
const values = Object.values(totals);
const max = Math.max(...values);
ctx.clearRect(0, 0, canvas.width, canvas.height);
values.forEach((value, index) => {
const barHeight = (value / max) * canvas.height;
ctx.fillStyle = 'lightblue';
ctx.fillRect(index * (canvas.width / categories.length), canvas.height - barHeight, canvas.width / categories.length - 2, barHeight);
ctx.fillStyle = 'black';
ctx.fillText(categories[index], index * (canvas.width / categories.length) + 5, canvas.height - barHeight - 5);
});
};
const importData = () => {
const file = document.getElementById('importFile').files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
try {
const imported = JSON.parse(e.target.result);
if (!Array.isArray(imported) || imported.some(exp => !exp.date || !exp.amount || !exp.category || !exp.comment)) {
alert('Ошибка импорта: неверный формат данных');
return;
}
expenses = imported;
localStorage.setItem(localStorageKey, JSON.stringify(expenses));
alert('Данные импортированы');
renderList();
} catch (err) {
alert('Ошибка импорта: неверный формат JSON');
}
};
reader.readAsText(file);
};
document.getElementById('addExpense').addEventListener('click', addExpense);
document.getElementById('showList').addEventListener('click', showList);
document.getElementById('filterButton').addEventListener('click', renderList);
document.getElementById('exportButton').addEventListener('click', () => {
const blob = new Blob([JSON.stringify(expenses)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'expenses.json';
a.click();
URL.revokeObjectURL(url);
});
document.getElementById('importButton').addEventListener('click', importData);
document.getElementById('showInput').addEventListener('click', () => {
document.getElementById('listScreen').classList.add('hidden');
document.getElementById('inputScreen').classList.remove('hidden');
});
</script>
</body>
</html>Как пользоваться:
- Скопируйте код в Блокнот.
- Выберите «Сохранить как…», введите имя файла
app.html, выберите кодировку UTF-8 и нажмите «Сохранить». - Откройте файл в браузере, дважды щелкнув на него.
Теперь вы можете добавлять расходы, просматривать их и экспортировать/импортировать данные.
Получилось создать простой калькулятор стоимости покраски стен, который выполняет все заданные требования.
Вот полный код:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Калькулятор покраски стен</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f9f9f9;
}
input, button {
width: 100%;
padding: 15px;
margin: 10px 0;
font-size: 18px;
border: 1px solid #ccc;
border-radius: 5px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
#result {
font-size: 24px;
margin-top: 20px;
}
</style>
</head>
<body>
<h1>Калькулятор покраски стен</h1>
<label for="roomWidth">Ширина комнаты (м)</label>
<input type="number" id="roomWidth" placeholder="Введите ширину">
<label for="roomLength">Длина комнаты (м)</label>
<input type="number" id="roomLength" placeholder="Введите длину">
<label for="windowArea">Площадь окон (м²)</label>
<input type="number" id="windowArea" placeholder="Введите площадь окон">
<label for="doorArea">Площадь дверей (м²)</label>
<input type="number" id="doorArea" placeholder="Введите площадь дверей">
<label for="paintCoverage">Расход краски (м² на банку)</label>
<input type="number" id="paintCoverage" placeholder="Введите расход краски">
<label for="paintPrice">Цена банки краски (руб)</label>
<input type="number" id="paintPrice" placeholder="Введите цену">
<button id="calculateBtn">Рассчитать</button>
<div id="result"></div>
<script>
const versionKey = 'paintCalc_v1';
function saveData(data) {
try {
localStorage.setItem(versionKey, JSON.stringify(data));
} catch (e) {}
}
function loadData() {
try {
return JSON.parse(localStorage.getItem(versionKey)) || {};
} catch (e) {
return {};
}
}
function calculate() {
const roomWidth = parseFloat(document.getElementById('roomWidth').value) || 0;
const roomLength = parseFloat(document.getElementById('roomLength').value) || 0;
const windowArea = parseFloat(document.getElementById('windowArea').value) || 0;
const doorArea = parseFloat(document.getElementById('doorArea').value) || 0;
const paintCoverage = parseFloat(document.getElementById('paintCoverage').value) || 0;
const paintPrice = parseFloat(document.getElementById('paintPrice').value) || 0;
const wallArea = 2 * (roomWidth + roomLength) * 2.5; // Высота стен 2.5 м
const totalArea = wallArea - (windowArea + doorArea);
const cansNeeded = Math.ceil(totalArea / paintCoverage);
const totalPrice = cansNeeded * paintPrice;
const resultDiv = document.getElementById('result');
resultDiv.textContent = `Площадь: ${totalArea.toFixed(2)} м², Необходимое количество банок: ${cansNeeded}, Общая стоимость: ${totalPrice.toFixed(2)} руб.`;
saveData({ roomWidth, roomLength, windowArea, doorArea, paintCoverage, paintPrice });
}
document.getElementById('calculateBtn').addEventListener('click', calculate);
window.onload = () => {
const data = loadData();
document.getElementById('roomWidth').value = data.roomWidth || '';
document.getElementById('roomLength').value = data.roomLength || '';
document.getElementById('windowArea').value = data.windowArea || '';
document.getElementById('doorArea').value = data.doorArea || '';
document.getElementById('paintCoverage').value = data.paintCoverage || '';
document.getElementById('paintPrice').value = data.paintPrice || '';
};
</script>
</body>
</html>Как пользоваться:
- Введите размеры комнаты (ширина и длина).
- Укажите площадь окон и дверей.
- Введите расход краски на банку и её цену.
- Нажмите кнопку "Рассчитать", чтобы получить площадь стен, количество необходимых банок краски и общую стоимость.
Данные сохраняются в localStorage, и при перезагрузке страницы они будут загружены обратно в поля ввода.
Вот что получилось: создал тест на 10 вопросов по правилам дорожного движения с подсчетом баллов, сохранением результата и даты в localStorage. Всё работает в одном HTML-файле, без лишних зависимостей и с темной темой.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Тест по ПДД</title>
<style>
body {
background-color: #121212;
color: #ffffff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
font-family: Arial, sans-serif;
}
button {
background-color: #6200ea;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
cursor: pointer;
font-size: 18px;
width: 100%;
max-width: 300px;
min-height: 44px;
}
button:focus {
outline: 2px solid #03dac6;
}
input[type="text"], select {
padding: 10px;
border: 1px solid #6200ea;
border-radius: 4px;
width: 100%;
max-width: 300px;
margin-bottom: 16px;
}
label {
margin-bottom: 8px;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div id="questionScreen">
<label for="answer">Ваш ответ:</label>
<input type="text" id="answer" />
<button id="submitAnswer">Ответить</button>
<p id="feedback" class="hidden"></p>
<button id="nextQuestion" class="hidden">Следующий вопрос</button>
</div>
<div id="resultScreen" class="hidden">
<h2>Результаты</h2>
<p id="score"></p>
<p id="errors"></p>
<button id="restart">Пройти снова</button>
</div>
<script>
const questions = [
{ question: "Вопрос 1", options: ["Ответ 1", "Ответ 2", "Ответ 3"], correct: 0, explanation: "Правильный ответ: Ответ 1." },
{ question: "Вопрос 2", options: ["Ответ 1", "Ответ 2", "Ответ 3"], correct: 1, explanation: "Правильный ответ: Ответ 2." },
{ question: "Вопрос 3", options: ["Ответ 1", "Ответ 2", "Ответ 3"], correct: 2, explanation: "Правильный ответ: Ответ 3." },
{ question: "Вопрос 4", options: ["Ответ 1", "Ответ 2", "Ответ 3"], correct: 0, explanation: "Правильный ответ: Ответ 1." },
{ question: "Вопрос 5", options: ["Ответ 1", "Ответ 2", "Ответ 3"], correct: 1, explanation: "Правильный ответ: Ответ 2." },
{ question: "Вопрос 6", options: ["Ответ 1", "Ответ 2", "Ответ 3"], correct: 2, explanation: "Правильный ответ: Ответ 3." },
{ question: "Вопрос 7", options: ["Ответ 1", "Ответ 2", "Ответ 3"], correct: 0, explanation: "Правильный ответ: Ответ 1." },
{ question: "Вопрос 8", options: ["Ответ 1", "Ответ 2", "Ответ 3"], correct: 1, explanation: "Правильный ответ: Ответ 2." },
{ question: "Вопрос 9", options: ["Ответ 1", "Ответ 2", "Ответ 3"], correct: 2, explanation: "Правильный ответ: Ответ 3." },
{ question: "Вопрос 10", options: ["Ответ 1", "Ответ 2", "Ответ 3"], correct: 0, explanation: "Правильный ответ: Ответ 1." }
];
let currentQuestion = 0;
let score = 0;
let errors = 0;
const feedbackElement = document.getElementById("feedback");
const scoreElement = document.getElementById("score");
const errorsElement = document.getElementById("errors");
const questionScreen = document.getElementById("questionScreen");
const resultScreen = document.getElementById("resultScreen");
function saveResults() {
try {
const results = { score, date: new Date().toLocaleString() };
localStorage.setItem("pdd-test-v1", JSON.stringify(results));
} catch (e) {}
}
function showResult() {
scoreElement.textContent = `Вы набрали ${score} из 10 баллов.`;
errorsElement.textContent = `Ошибки: ${errors}.`;
saveResults();
questionScreen.classList.add("hidden");
resultScreen.classList.remove("hidden");
}
function checkAnswer() {
const userAnswer = document.getElementById("answer").value.trim();
if (userAnswer === "") return;
feedbackElement.classList.remove("hidden");
if (parseInt(userAnswer) === questions[currentQuestion].correct) {
feedbackElement.textContent = "Верно! " + questions[currentQuestion].explanation;
score++;
} else {
feedbackElement.textContent = "Неверно! " + questions[currentQuestion].explanation;
errors++;
}
document.getElementById("nextQuestion").classList.remove("hidden");
}
document.getElementById("submitAnswer").onclick = function() {
checkAnswer();
};
document.getElementById("nextQuestion").onclick = function() {
currentQuestion++;
if (currentQuestion < questions.length) {
document.getElementById("answer").value = '';
feedbackElement.classList.add("hidden");
this.classList.add("hidden");
document.getElementById("feedback").textContent = '';
} else {
showResult();
}
};
document.getElementById("restart").onclick = function() {
currentQuestion = 0;
score = 0;
errors = 0;
resultScreen.classList.add("hidden");
questionScreen.classList.remove("hidden");
};
// Инициализация
questionScreen.classList.remove("hidden");
</script>
</body>
</html>Как пользоваться:
- Откройте файл в любом браузере.
- Введите номер ответа на каждый вопрос и нажмите кнопку "Ответить".
- После каждого ответа будет показан результат и пояснение.
- После последнего вопроса вы увидите итоговые баллы и количество ошибок.
- Нажмите "Пройти снова", чтобы начать тест заново.
Данные сохраняются в localStorage под ключом pdd-test-v1 и содержат последний результат и дату.
Получилось приложение для домашней библиотеки с возможностью добавления книг, поиска и фильтрации. Вот полный код:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Домашняя библиотека</title>
<style>
body { font-family: Arial, sans-serif; background-color: inherit; color: inherit; }
.dark { background-color: #333; color: #fff; }
.light { background-color: #fff; color: #000; }
input, button { display: block; margin: 10px 0; padding: 10px; font-size: 16px; width: calc(100% - 22px); }
button { cursor: pointer; }
h2 { border-bottom: 1px solid #ccc; }
</style>
</head>
<body>
<h2>Добавить книгу</h2>
<label for="title">Название:</label>
<input type="text" id="title" required>
<label for="author">Автор:</label>
<input type="text" id="author" required>
<label for="genre">Жанр:</label>
<input type="text" id="genre" required>
<label for="read">Прочитана:</label>
<input type="checkbox" id="read">
<button id="addBook">Добавить книгу</button>
<button id="exportData">Экспортировать в JSON</button>
<input type="file" id="importFile">
<h2>Поиск и фильтр</h2>
<label for="search">Поиск по названию или автору:</label>
<input type="text" id="search">
<label for="filter">Фильтр по прочитанным:</label>
<input type="checkbox" id="filter">
<h2>Список книг</h2>
<ul id="bookList"></ul>
<p>Всего книг: <span id="bookCount">0</span></p>
<script>
const versionKey = 'library-v1';
const bookListEl = document.getElementById('bookList');
const bookCountEl = document.getElementById('bookCount');
const titleInput = document.getElementById('title');
const authorInput = document.getElementById('author');
const genreInput = document.getElementById('genre');
const readInput = document.getElementById('read');
const searchInput = document.getElementById('search');
const filterInput = document.getElementById('filter');
function loadBooks() {
try {
const data = localStorage.getItem(versionKey);
return data ? JSON.parse(data) : [];
} catch (e) {
console.error('Ошибка загрузки из localStorage', e);
return [];
}
}
function saveBooks(books) {
try {
localStorage.setItem(versionKey, JSON.stringify(books));
} catch (e) {
console.error('Ошибка сохранения в localStorage', e);
}
}
function renderBooks() {
const books = loadBooks();
const searchTerm = searchInput.value.toLowerCase();
const showRead = filterInput.checked;
const filteredBooks = books.filter(book => (
(book.title.toLowerCase().includes(searchTerm) || book.author.toLowerCase().includes(searchTerm)) &&
(!showRead || book.read)
));
bookListEl.innerHTML = '';
filteredBooks.forEach(book => {
const li = document.createElement('li');
li.textContent = `${book.title} — ${book.author} (${book.genre}) ${book.read ? '✓' : '✗'}`;
bookListEl.appendChild(li);
});
bookCountEl.textContent = filteredBooks.length;
}
document.getElementById('addBook').addEventListener('click', () => {
const title = titleInput.value;
const author = authorInput.value;
const genre = genreInput.value;
const read = readInput.checked;
if (!title || !author || !genre) {
alert('Пожалуйста, заполните все поля.');
return;
}
const books = loadBooks();
books.push({ title, author, genre, read });
saveBooks(books);
titleInput.value = '';
authorInput.value = '';
genreInput.value = '';
readInput.checked = false;
renderBooks();
});
searchInput.addEventListener('input', renderBooks);
filterInput.addEventListener('change', renderBooks);
document.getElementById('exportData').addEventListener('click', () => {
const books = loadBooks();
const json = JSON.stringify(books);
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'books.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
document.getElementById('importFile').addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
const importedBooks = JSON.parse(e.target.result);
if (Array.isArray(importedBooks) && importedBooks.every(book => (
typeof book.title === 'string' &&
typeof book.author === 'string' &&
typeof book.genre === 'string' &&
typeof book.read === 'boolean'
))) {
saveBooks(importedBooks);
renderBooks();
} else {
alert('Неверные данные в файле.');
}
} catch (error) {
alert('Ошибка импорта: ' + error.message);
}
};
reader.readAsText(file);
}
});
renderBooks(); // Инициализация
</script>
</body>
</html>Как пользоваться:
- Скопируйте код.
- Откройте Блокнот.
- Вставьте код.
- Выберите «Сохранить как», назовите файл
app.html, выберите кодировку UTF-8. - Откройте полученный файл в браузере.
Теперь вы можете добавлять книги, искать и фильтровать их, а также экспортировать и импортировать данные.
Получилось простое приложение для таймера тренировок с двумя экранами, которое сохраняет данные в localStorage, работает без интернета и соответствует всем указанным требованиям.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Таймер тренировок</title>
<style>
body { background-color: #121212; color: #ffffff; font-family: Arial, sans-serif; }
.container { width: 100%; max-width: 400px; margin: 0 auto; padding: 20px; box-shadow: 0 0 10px rgba(0,0,0,0.5); }
h1 { text-align: center; }
label { display: block; margin: 10px 0 5px; }
input { width: 100%; padding: 10px; margin-bottom: 10px; border: 1px solid #444; border-radius: 5px; }
button { width: 100%; padding: 15px; margin-top: 10px; border: none; border-radius: 5px; background-color: #555; color: #fff; font-size: 16px; cursor: pointer; }
button:disabled { background-color: #aaa; }
.hidden { display: none; }
</style>
</head>
<body>
<div class="container">
<h1>Таймер тренировок</h1>
<div id="screen-setup">
<label for="sets">Число подходов:</label>
<input type="number" id="sets" min="1" required>
<label for="work">Время работы (сек):</label>
<input type="number" id="work" min="1" required>
<label for="rest">Время отдыха (сек):</label>
<input type="number" id="rest" min="1" required>
<button id="start-button">Начать</button>
</div>
<div id="screen-timer" class="hidden">
<h2 id="timer-display">00:00</h2>
<button id="pause-button">Пауза</button>
<button id="reset-button">Сброс</button>
</div>
</div>
<script>
const setsInput = document.getElementById('sets');
const workInput = document.getElementById('work');
const restInput = document.getElementById('rest');
const startButton = document.getElementById('start-button');
const timerDisplay = document.getElementById('timer-display');
const screenSetup = document.getElementById('screen-setup');
const screenTimer = document.getElementById('screen-timer');
const pauseButton = document.getElementById('pause-button');
const resetButton = document.getElementById('reset-button');
let sets, workTime, restTime, currentSet, timer, isRunning = false;
const saveData = () => {
try {
localStorage.setItem('timerData', JSON.stringify({ sets, workTime, restTime, currentSet }));
} catch (e) { console.error('Не удалось сохранить данные', e); }
};
const loadData = () => {
try {
const data = JSON.parse(localStorage.getItem('timerData'));
if (data) {
sets = data.sets;
workTime = data.workTime;
restTime = data.restTime;
currentSet = data.currentSet;
}
} catch (e) { console.error('Не удалось загрузить данные', e); }
};
const formatTime = (time) => {
const minutes = Math.floor(time / 60);
const seconds = time % 60;
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
};
const startTimer = (duration) => {
let remainingTime = duration;
timer = setInterval(() => {
if (remainingTime <= 0) {
clearInterval(timer);
if (isRunning) {
if (currentSet < sets) {
currentSet++;
saveData();
playSound();
startTimer(workTime * 1000);
} else {
alert('Тренировка завершена!');
reset();
}
}
} else {
timerDisplay.textContent = formatTime(Math.floor(remainingTime / 1000));
remainingTime -= 1000;
}
}, 1000);
};
const playSound = () => {
const context = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = context.createOscillator();
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(440, context.currentTime);
oscillator.connect(context.destination);
oscillator.start();
oscillator.stop(context.currentTime + 0.5);
};
const reset = () => {
clearInterval(timer);
isRunning = false;
timerDisplay.textContent = '00:00';
screenSetup.classList.remove('hidden');
screenTimer.classList.add('hidden');
setsInput.value = '';
workInput.value = '';
restInput.value = '';
};
startButton.addEventListener('click', () => {
sets = parseInt(setsInput.value) || 1;
workTime = parseInt(workInput.value) || 1;
restTime = parseInt(restInput.value) || 1;
currentSet = 0;
saveData();
screenSetup.classList.add('hidden');
screenTimer.classList.remove('hidden');
startTimer(workTime * 1000);
isRunning = true;
});
pauseButton.addEventListener('click', () => {
if (isRunning) {
clearInterval(timer);
isRunning = false;
} else {
startTimer(workTime * 1000);
isRunning = true;
}
});
resetButton.addEventListener('click', reset);
loadData();
</script>
</body>
</html>Как пользоваться:
- Откройте Блокнот.
- Скопируйте код из блока и вставьте в Блокнот.
- Нажмите «Файл» → «Сохранить как...», введите имя
app.htmlи выберите кодировку UTF-8. - Дважды щелкните на файл
app.html, чтобы открыть его в браузере.
Ответы получены 15.10.2026 в этом чате и приведены без правок. Выделено то, чего не было в запросе.
Что такое приложение в одном HTML-файле
Приложение в одном HTML-файле — это программа, у которой разметка, оформление и логика лежат в одном файле с расширением .html. Его открывают двойным щелчком в любом браузере — на компьютере или телефоне, без установки, сервера и регистрации. Данные такое приложение хранит в памяти браузера.
Для многих личных и рабочих задач этого хватает: трекер привычек, учёт расходов, калькулятор стоимости заказа, тест для сотрудников, журнал тренировок. Раньше для этого приходилось искать готовый сервис и мириться с его ограничениями или просить знакомого программиста. Теперь достаточно описать задачу словами: нейросеть Аливии напишет код, а вы проверите результат прямо на странице.
Этот инструмент отличается от генератора HTML-разметки для сайта. Там — вёрстка блоков для готового сайта. Здесь — работающая программа под вашу задачу: с кнопками, расчётами и сохранением данных.
Какие приложения реально собрать в одном файле?
Граница простая: одно устройство, один пользователь, данные не нужно никому отправлять. Всё, что укладывается в эти рамки, нейросеть соберёт за один-два подхода.
| Тип приложения | Пример | Что хранит | Где упрётся в ограничения |
|---|---|---|---|
| Трекер | Привычки, вода, сон, тренировки | Отметки по датам | Нет синхронизации между телефоном и компьютером |
| Учёт | Расходы, показания счётчиков, выданный инструмент | Список записей с суммами | Несколько человек не смогут вносить данные в один учёт |
| Калькулятор | Стоимость заказа, раскрой материала, норма корма | Обычно ничего или последние настройки | Почти не упирается — лучший кандидат для первого опыта |
| Тест или тренажёр | Проверка знаний, карточки слов | Результаты прохождений | Ответы видны в коде — для экзамена с контролем не годится |
| Планировщик | Список дел, план поездки, таймер | Задачи и их статусы | Напоминания работают, только пока страница открыта |
Если нужны вход по паролю, общие данные для команды, приём оплаты или рассылка уведомлений, одного файла мало: понадобится сервер. В этом случае полезнее сначала описать продукт в техническом задании для разработчика, а для уведомлений в мессенджере посмотреть Telegram-бота под ключ.
Как сделать приложение: пошагово
- Опишите задачу в конструкторе. Что приложение должно делать, какие экраны в нём есть, какие поля вводит человек и что нужно хранить. Конструктор под чатом соберёт из этого техническое задание для одного HTML-файла с хранением данных в браузере.
- Отправьте задание в чат. Нейросеть ответит кодом файла целиком и короткой инструкцией: как сохранить и как открыть.
- Запустите предпросмотр. Скопируйте код из ответа и вставьте в окно предпросмотра под чатом. Приложение откроется в изолированной рамке прямо на странице.
- Попробуйте его как пользователь. Добавьте записи, закройте и откройте снова, введите пустые и слишком длинные значения.
- Попросите правки. «Кнопка удаления слишком близко к кнопке сохранения», «добавь итог за неделю» — нейросеть пришлёт обновлённый файл целиком.
- Сохраните файл. Вставьте код в любой текстовый редактор и сохраните с расширением .html, например
habits.html. Откройте его в браузере — это и есть ваше приложение.
Готовая формулировка запроса: «Сделай приложение учёта расходов в одном HTML-файле. Поля: дата, сумма, категория из списка (еда, транспорт, дом, другое), комментарий. Экран со списком за текущий месяц и итогом по категориям. Данные храни в localStorage. Добавь кнопки экспорта и импорта в JSON. Вёрстка под телефон, крупные кнопки».
Как описать задачу, чтобы код получился с первого раза
Нейросеть не видит, как вы работаете сейчас, поэтому опишите не только функции, но и обстановку. Хорошее описание отвечает на четыре вопроса.
- Кто и где пользуется. «Я сама, с телефона, одной рукой в магазине» даёт совсем другую вёрстку, чем «бухгалтер за компьютером».
- Что вводится. Поля и их тип: дата, сумма в рублях, выбор из списка, свободный текст.
- Что нужно увидеть. Итог за неделю, график, список просроченного, процент выполнения.
- Что важно не сломать. Например, «случайно удалить запись нельзя» или «старые записи не редактируются».
Пример реального состояния дел тоже помогает: пришлите в чат скриншот своей бумажной таблицы или заметки — нейросеть повторит привычную структуру.
Где хранятся данные и как их не потерять
Приложения на этой странице сохраняют данные в localStorage — хранилище браузера. По документации MDN у данных в localStorage нет срока истечения, а ключи и значения хранятся строками, поэтому записи переживают закрытие вкладки и перезагрузку компьютера. Списки и объекты нейросеть превращает в строку формата JSON и обратно.
У хранилища есть пределы, о которых стоит знать заранее:
- Объём. Браузеры отводят до 5 МиБ localStorage на один источник; при переполнении возникает ошибка QuotaExceededError. Для текста это тысячи записей, для фотографий — очень мало, поэтому картинки в таком приложении лучше не хранить.
- Режим инкогнито. Данные приватного окна удаляются, когда закрывается последняя приватная вкладка.
- Файл с диска. Для страниц, открытых по адресу
file:, поведение localStorage не определено стандартом и может различаться между браузерами. На практике у каждого файла своё хранилище: переименовали или переместили файл — приложение может открыться пустым. - Очистка браузера. Удаление данных сайтов стирает и записи приложения.
Типичная ошибка — неделями вести учёт в приложении без резервной копии. Одна очистка истории, и записи пропали. Всегда просите у нейросети кнопки «Экспорт» и «Импорт» в файл JSON или CSV и сохраняйте копию хотя бы раз в неделю. Пароли и данные карт в таком приложении не храните: localStorage не предназначен для секретов.
Почему предпросмотр работает в изолированном окне
Код из ответа нейросети запускается в рамке iframe с атрибутом sandbox. По описанию MDN, пустое значение sandbox включает все ограничения, а отдельные разрешения вроде allow-scripts снимают их по одному. Так приложение может выполнять свои скрипты, но не получает доступа к странице Аливии и вашим данным на ней.
У изоляции есть следствие: данные, введённые в предпросмотре, служат только для проверки. Рабочую версию открывайте из сохранённого файла — там хранилище будет постоянным. Если что-то работает в предпросмотре, но не работает в файле, опишите разницу в чате: обычно причина в одной строке кода.
Как проверить приложение перед тем, как им пользоваться
Нейросеть пишет рабочий код, но проверяет его человек. Пройдите короткий список — он ловит большинство проблем.
- Перезагрузка. Добавьте три записи, закройте вкладку, откройте файл снова. Записи на месте — хранение работает.
- Пустой ввод. Нажмите «Сохранить» с пустыми полями. Приложение должно подсказать, а не создать пустую строку.
- Числа. Введите сумму с запятой, «1 500» с пробелом и отрицательное число. Итог не должен превратиться в NaN.
- Телефон. Откройте файл на телефоне или сузьте окно: кнопки не должны наезжать друг на друга.
- Экспорт и импорт. Выгрузите данные, очистите хранилище, загрузите обратно — всё должно вернуться.
- Удаление. Проверьте, что удаляется ровно одна запись и есть подтверждение.
Если захочется разобраться в коде и дописать что-то самому, отдельные функции удобно обсуждать в генераторе скриптов на JavaScript. Аливия работает круглосуточно и без VPN из России, понимает русский и принимает файлы — можно прислать свой HTML-файл и попросить доработку. Длинные диалоги с правками расходуют дневной лимит, у платных тарифов он больше.
Веб-приложение без программиста: частые вопросы
Можно ли создать приложение без знания программирования?
Нужно ли что-то устанавливать?
Где хранятся мои данные?
Почему после переноса файла данные пропали?
Можно ли открыть приложение на телефоне?
Можно ли сделать приложение для нескольких человек?
Безопасно ли запускать код в предпросмотре?
Что делать, если приложение работает с ошибкой?
Можно ли опубликовать приложение в интернете?
Чем это отличается от генератора HTML?
Итог
Веб-приложение без программиста на Аливии — это один HTML-файл под вашу задачу: трекер, учёт, калькулятор или тест. Конструктор собирает техническое задание, нейросеть пишет код, а изолированный предпросмотр показывает результат сразу на странице. Данные живут в localStorage браузера, поэтому делайте экспорт копий и не храните в таком приложении пароли и платёжные данные.