feat: improve content editor and move hero config to JSON
This commit is contained in:
@@ -0,0 +1,399 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mozdIT Content Editor Server v2
|
||||||
|
* Szerkesztő felület a JSON tartalom fájlokhoz
|
||||||
|
* Támogatja: szöveg szerkesztés, tömbelem hozzáadás/törlés
|
||||||
|
* Futtatás: node content-editor.js
|
||||||
|
* Megnyitás: http://localhost:4001
|
||||||
|
*/
|
||||||
|
|
||||||
|
const http = require('http');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const PORT = 4001;
|
||||||
|
const CONTENT_DIR = path.join(__dirname, 'proto', 'src', 'content');
|
||||||
|
|
||||||
|
const FILES = {
|
||||||
|
common: path.join(CONTENT_DIR, 'common.json'),
|
||||||
|
home: path.join(CONTENT_DIR, 'pages', 'home.json'),
|
||||||
|
about: path.join(CONTENT_DIR, 'pages', 'about.json'),
|
||||||
|
services: path.join(CONTENT_DIR, 'pages', 'services.json'),
|
||||||
|
contact: path.join(CONTENT_DIR, 'pages', 'contact.json'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const FILE_LABELS = {
|
||||||
|
common: '⚙️ Közös szövegek',
|
||||||
|
home: '🏠 Kezdőlap',
|
||||||
|
about: '👥 Rólunk',
|
||||||
|
services: '🛠️ Szolgáltatások',
|
||||||
|
contact: '📬 Kapcsolat',
|
||||||
|
};
|
||||||
|
|
||||||
|
const HTML = (activeFile, jsonData, message) => `<!DOCTYPE html>
|
||||||
|
<html lang="hu">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>mozdIT — Tartalom Szerkesztő</title>
|
||||||
|
<style>
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f1117; color: #e2e8f0; min-height: 100vh; }
|
||||||
|
|
||||||
|
header { background: linear-gradient(135deg,#1a1f2e,#252d40); border-bottom: 1px solid #2d3748; padding: 14px 32px; display: flex; align-items: center; gap: 12px; }
|
||||||
|
header h1 { font-size: 17px; font-weight: 700; background: linear-gradient(135deg,#60a5fa,#a78bfa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
||||||
|
header span { color: #64748b; font-size: 13px; }
|
||||||
|
|
||||||
|
.tabs { display: flex; gap: 2px; padding: 14px 32px 0; border-bottom: 1px solid #2d3748; background: #13192a; }
|
||||||
|
.tab { text-decoration: none; color: #94a3b8; padding: 9px 16px; border-radius: 8px 8px 0 0; font-size: 13px; font-weight: 500; transition: all .2s; border: 1px solid transparent; border-bottom: none; margin-bottom: -1px; }
|
||||||
|
.tab:hover { color: #e2e8f0; background: #1e2535; }
|
||||||
|
.tab.active { color: #60a5fa; background: #0f1117; border-color: #2d3748; }
|
||||||
|
|
||||||
|
.page { max-width: 860px; margin: 28px auto 120px; padding: 0 24px; }
|
||||||
|
.hint { color: #475569; font-size: 12px; margin-bottom: 20px; }
|
||||||
|
|
||||||
|
/* Primitive field */
|
||||||
|
.field { background: #1a2035; border: 1px solid #2d3748; border-radius: 10px; padding: 14px 16px; transition: border-color .2s; margin-bottom: 10px; }
|
||||||
|
.field:focus-within { border-color: #60a5fa; }
|
||||||
|
.field label { display: block; font-size: 11px; font-weight: 600; color: #60a5fa; text-transform: uppercase; letter-spacing:.05em; margin-bottom: 7px; font-family: monospace; }
|
||||||
|
.field input, .field textarea { width: 100%; background: transparent; border: none; outline: none; color: #e2e8f0; font-size: 14px; line-height: 1.6; resize: vertical; font-family: inherit; }
|
||||||
|
.field textarea { min-height: 52px; }
|
||||||
|
|
||||||
|
/* Array section */
|
||||||
|
.array-section { margin-bottom: 20px; }
|
||||||
|
.array-label { font-size: 12px; font-weight: 700; color: #a78bfa; text-transform: uppercase; letter-spacing:.06em; font-family: monospace; margin-bottom: 10px; display: flex; align-items: center; gap: 8px; }
|
||||||
|
.array-label::after { content:''; flex: 1; height: 1px; background: #2d3748; }
|
||||||
|
|
||||||
|
.array-items { display: flex; flex-direction: column; gap: 8px; }
|
||||||
|
|
||||||
|
/* Simple string array item */
|
||||||
|
.str-item { display: flex; gap: 8px; align-items: flex-start; }
|
||||||
|
.str-item textarea { flex: 1; background: #1a2035; border: 1px solid #2d3748; border-radius: 8px; padding: 10px 12px; color: #e2e8f0; font-size: 14px; font-family: inherit; outline: none; resize: vertical; min-height: 44px; transition: border-color .2s; }
|
||||||
|
.str-item textarea:focus { border-color: #60a5fa; }
|
||||||
|
|
||||||
|
/* Object array item (card) */
|
||||||
|
.obj-card { background: #1a2035; border: 1px solid #2d3748; border-radius: 10px; padding: 14px; position: relative; }
|
||||||
|
.obj-card .card-header { font-size: 11px; color: #64748b; font-family: monospace; margin-bottom: 10px; }
|
||||||
|
.obj-card .inner-field { margin-bottom: 8px; }
|
||||||
|
.obj-card .inner-field:last-child { margin-bottom: 0; }
|
||||||
|
.obj-card .inner-label { font-size: 10px; font-weight: 600; color: #94a3b8; text-transform: uppercase; letter-spacing:.05em; font-family: monospace; margin-bottom: 4px; }
|
||||||
|
.obj-card input, .obj-card textarea { width: 100%; background: #0f1420; border: 1px solid #2d3748; border-radius: 6px; padding: 8px 10px; color: #e2e8f0; font-size: 13px; font-family: inherit; outline: none; resize: vertical; transition: border-color .2s; }
|
||||||
|
.obj-card input:focus, .obj-card textarea:focus { border-color: #60a5fa; }
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn-del { background: transparent; border: 1px solid #3f1c1c; color: #f87171; border-radius: 7px; padding: 6px 10px; cursor: pointer; font-size: 13px; transition: all .2s; white-space: nowrap; flex-shrink: 0; }
|
||||||
|
.btn-del:hover { background: #3f1c1c; }
|
||||||
|
.btn-del-card { position: absolute; top: 10px; right: 10px; background: transparent; border: 1px solid #3f1c1c; color: #f87171; border-radius: 6px; padding: 4px 8px; cursor: pointer; font-size: 12px; transition: all .2s; }
|
||||||
|
.btn-del-card:hover { background: #3f1c1c; }
|
||||||
|
|
||||||
|
.btn-add { background: transparent; border: 1px dashed #334155; color: #64748b; border-radius: 8px; padding: 9px 16px; cursor: pointer; font-size: 13px; width: 100%; text-align: center; transition: all .2s; margin-top: 6px; }
|
||||||
|
.btn-add:hover { border-color: #a78bfa; color: #a78bfa; background: #1a1535; }
|
||||||
|
|
||||||
|
/* Bottom bar */
|
||||||
|
.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #0f1117; border-top: 1px solid #2d3748; padding: 14px 32px; display: flex; gap: 14px; align-items: center; z-index: 50; }
|
||||||
|
.btn-save { background: linear-gradient(135deg,#3b82f6,#6366f1); color: #fff; border: none; padding: 11px 26px; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: opacity .2s, transform .1s; }
|
||||||
|
.btn-save:hover { opacity: .9; transform: translateY(-1px); }
|
||||||
|
.btn-save:active { transform: translateY(0); }
|
||||||
|
.preview-link { color: #64748b; font-size: 13px; text-decoration: none; }
|
||||||
|
.preview-link:hover { color: #94a3b8; }
|
||||||
|
.save-status { font-size: 12px; color: #10b981; display: none; }
|
||||||
|
|
||||||
|
/* Toast */
|
||||||
|
.toast { position: fixed; top: 20px; right: 20px; padding: 13px 18px; border-radius: 9px; font-size: 14px; font-weight: 500; z-index: 200; animation: slideIn .3s ease; }
|
||||||
|
.toast.ok { background: #064e3b; border: 1px solid #10b981; color: #6ee7b7; }
|
||||||
|
.toast.err { background: #450a0a; border: 1px solid #ef4444; color: #fca5a5; }
|
||||||
|
@keyframes slideIn { from { opacity:0; transform: translateX(20px); } to { opacity:1; transform: translateX(0); } }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
${message ? `<div class="toast ${message.type === 'ok' ? 'ok' : 'err'}">${message.text}</div>` : ''}
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>mozdIT Tartalom Szerkesztő</h1>
|
||||||
|
<span>— JSON fájlok szerkesztése vizuálisan</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<nav class="tabs">
|
||||||
|
${Object.entries(FILE_LABELS).map(([k, l]) =>
|
||||||
|
`<a href="/?file=${k}" class="tab ${activeFile === k ? 'active' : ''}">${l}</a>`
|
||||||
|
).join('')}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
<p class="hint">📝 Szerkeszd a mezőket. Tömbökből elemet törölhetsz (❌) vagy hozzáadhatsz (➕). Mentés gomb menti a fájlt.</p>
|
||||||
|
<div id="editor"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bottom-bar">
|
||||||
|
<button class="btn-save" onclick="save()">💾 Mentés</button>
|
||||||
|
<span class="save-status" id="saveStatus">✅ Mentve!</span>
|
||||||
|
<a href="http://localhost:3000" target="_blank" class="preview-link">🔗 Előnézet →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script id="page-data" type="application/json">${jsonData.replace(/<\//g, '<\\/')}</script>
|
||||||
|
<script>
|
||||||
|
const DATA = JSON.parse(document.getElementById('page-data').textContent);
|
||||||
|
const FILE = "${activeFile}";
|
||||||
|
|
||||||
|
// ── Render ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function render(obj, container) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
renderObject(obj, container, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderObject(obj, container, prefix) {
|
||||||
|
for (const [key, val] of Object.entries(obj)) {
|
||||||
|
const path = prefix ? prefix + '.' + key : key;
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
renderArray(key, val, container, path);
|
||||||
|
} else if (typeof val === 'object' && val !== null) {
|
||||||
|
renderObject(val, container, path);
|
||||||
|
} else {
|
||||||
|
renderPrimitive(path, val, container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPrimitive(path, val, container) {
|
||||||
|
const isLong = String(val).length > 80 || String(val).includes('<');
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'field';
|
||||||
|
div.innerHTML = \`
|
||||||
|
<label>\${path}</label>
|
||||||
|
\${isLong
|
||||||
|
? \`<textarea data-path="\${path}" rows="\${Math.min(8,Math.max(2,Math.ceil(String(val).length/80)))}">\${esc(val)}</textarea>\`
|
||||||
|
: \`<input type="text" data-path="\${path}" value="\${esc(val)}">\`}
|
||||||
|
\`;
|
||||||
|
container.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderArray(key, arr, container, path) {
|
||||||
|
const section = document.createElement('div');
|
||||||
|
section.className = 'array-section';
|
||||||
|
section.dataset.arrayPath = path;
|
||||||
|
|
||||||
|
const label = document.createElement('div');
|
||||||
|
label.className = 'array-label';
|
||||||
|
label.textContent = path;
|
||||||
|
section.appendChild(label);
|
||||||
|
|
||||||
|
const items = document.createElement('div');
|
||||||
|
items.className = 'array-items';
|
||||||
|
items.dataset.arrayItems = path;
|
||||||
|
section.appendChild(items);
|
||||||
|
|
||||||
|
arr.forEach((item, i) => {
|
||||||
|
if (typeof item === 'object' && item !== null) {
|
||||||
|
items.appendChild(makeObjCard(item, i, path));
|
||||||
|
} else {
|
||||||
|
items.appendChild(makeStrItem(item, i, path));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Template for adding new items
|
||||||
|
const sample = arr.length > 0 ? arr[arr.length - 1] : '';
|
||||||
|
const isObj = typeof sample === 'object' && sample !== null;
|
||||||
|
|
||||||
|
const addBtn = document.createElement('button');
|
||||||
|
addBtn.className = 'btn-add';
|
||||||
|
addBtn.textContent = '➕ Új elem hozzáadása';
|
||||||
|
addBtn.onclick = () => {
|
||||||
|
const idx = items.children.length;
|
||||||
|
if (isObj) {
|
||||||
|
const blank = Object.fromEntries(Object.keys(sample).map(k => [k, '']));
|
||||||
|
items.appendChild(makeObjCard(blank, idx, path));
|
||||||
|
} else {
|
||||||
|
items.appendChild(makeStrItem('', idx, path));
|
||||||
|
}
|
||||||
|
reindexItems(items);
|
||||||
|
};
|
||||||
|
section.appendChild(addBtn);
|
||||||
|
container.appendChild(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeStrItem(val, idx, path) {
|
||||||
|
const wrap = document.createElement('div');
|
||||||
|
wrap.className = 'str-item';
|
||||||
|
const ta = document.createElement('textarea');
|
||||||
|
ta.dataset.path = path + '[' + idx + ']';
|
||||||
|
ta.value = val;
|
||||||
|
ta.rows = Math.min(6, Math.max(2, Math.ceil(String(val).length / 80)));
|
||||||
|
const del = document.createElement('button');
|
||||||
|
del.className = 'btn-del';
|
||||||
|
del.textContent = '❌';
|
||||||
|
del.title = 'Törlés';
|
||||||
|
del.onclick = () => { wrap.remove(); reindexItems(wrap.closest('.array-items')); };
|
||||||
|
wrap.appendChild(ta);
|
||||||
|
wrap.appendChild(del);
|
||||||
|
return wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeObjCard(obj, idx, path) {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'obj-card';
|
||||||
|
const hdr = document.createElement('div');
|
||||||
|
hdr.className = 'card-header';
|
||||||
|
hdr.textContent = path + '[' + idx + ']';
|
||||||
|
card.appendChild(hdr);
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
|
const fieldPath = path + '[' + idx + '].' + k;
|
||||||
|
const isLong = String(v).length > 80 || String(v).includes('<');
|
||||||
|
const fd = document.createElement('div');
|
||||||
|
fd.className = 'inner-field';
|
||||||
|
fd.innerHTML = \`
|
||||||
|
<div class="inner-label">\${k}</div>
|
||||||
|
\${isLong
|
||||||
|
? \`<textarea data-path="\${fieldPath}" rows="\${Math.min(6,Math.max(2,Math.ceil(String(v).length/80)))}">\${esc(v)}</textarea>\`
|
||||||
|
: \`<input type="text" data-path="\${fieldPath}" value="\${esc(v)}">\`}
|
||||||
|
\`;
|
||||||
|
card.appendChild(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
const del = document.createElement('button');
|
||||||
|
del.className = 'btn-del-card';
|
||||||
|
del.textContent = '❌ Törlés';
|
||||||
|
del.onclick = () => { card.remove(); reindexItems(card.closest('.array-items')); };
|
||||||
|
card.appendChild(del);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reindexItems(itemsEl) {
|
||||||
|
if (!itemsEl) return;
|
||||||
|
const path = itemsEl.dataset.arrayItems;
|
||||||
|
Array.from(itemsEl.children).forEach((child, i) => {
|
||||||
|
// Update all data-path in this child
|
||||||
|
child.querySelectorAll('[data-path]').forEach(el => {
|
||||||
|
const old = el.dataset.path;
|
||||||
|
// Replace the array index part: path[old_i] → path[new_i]
|
||||||
|
el.dataset.path = old.replace(/^(.+?)\[(\d+)\]/, (_,p) => p + '[' + i + ']');
|
||||||
|
});
|
||||||
|
// Update card header
|
||||||
|
const hdr = child.querySelector('.card-header');
|
||||||
|
if (hdr) hdr.textContent = path + '[' + i + ']';
|
||||||
|
// Update str-item textarea
|
||||||
|
const ta = child.querySelector('textarea[data-path]');
|
||||||
|
if (ta && !ta.closest('.obj-card')) {
|
||||||
|
ta.dataset.path = path + '[' + i + ']';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Collect & Save ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function collect() {
|
||||||
|
const result = JSON.parse(JSON.stringify(DATA)); // deep clone as base
|
||||||
|
// Wipe all arrays so we rebuild them from DOM
|
||||||
|
clearArrays(result);
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-path]').forEach(el => {
|
||||||
|
setPath(result, el.dataset.path, el.value);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearArrays(obj) {
|
||||||
|
for (const k of Object.keys(obj)) {
|
||||||
|
if (Array.isArray(obj[k])) obj[k] = [];
|
||||||
|
else if (typeof obj[k] === 'object' && obj[k] !== null) clearArrays(obj[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPath(obj, path, value) {
|
||||||
|
// Parse "a.b[0].c" into parts
|
||||||
|
const parts = path.replace(/\[(\d+)\]/g, '.$1').split('.');
|
||||||
|
let cur = obj;
|
||||||
|
for (let i = 0; i < parts.length - 1; i++) {
|
||||||
|
const p = isNaN(parts[i]) ? parts[i] : +parts[i];
|
||||||
|
const next = isNaN(parts[i+1]) ? {} : (cur[p] || []);
|
||||||
|
if (cur[p] === undefined || cur[p] === null) cur[p] = next;
|
||||||
|
cur = cur[p];
|
||||||
|
}
|
||||||
|
const last = isNaN(parts[parts.length-1]) ? parts[parts.length-1] : +parts[parts.length-1];
|
||||||
|
cur[last] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const data = collect();
|
||||||
|
const res = await fetch('/save?file=' + FILE, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(data, null, 2)
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
const status = document.getElementById('saveStatus');
|
||||||
|
if (json.ok) {
|
||||||
|
status.textContent = '✅ Mentve!';
|
||||||
|
status.style.color = '#10b981';
|
||||||
|
} else {
|
||||||
|
status.textContent = '❌ Hiba: ' + json.error;
|
||||||
|
status.style.color = '#f87171';
|
||||||
|
}
|
||||||
|
status.style.display = 'inline';
|
||||||
|
setTimeout(() => status.style.display = 'none', 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function esc(v) {
|
||||||
|
return String(v).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boot
|
||||||
|
render(DATA, document.getElementById('editor'));
|
||||||
|
|
||||||
|
// Auto-dismiss toast
|
||||||
|
const toast = document.querySelector('.toast');
|
||||||
|
if (toast) setTimeout(() => toast.remove(), 3500);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
// ── Server ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const server = http.createServer(async (req, res) => {
|
||||||
|
const u = new URL(req.url, `http://localhost:${PORT}`);
|
||||||
|
const fileKey = u.searchParams.get('file') || 'home';
|
||||||
|
const activeFile = FILES[fileKey] ? fileKey : 'home';
|
||||||
|
|
||||||
|
// POST /save — JSON body
|
||||||
|
if (req.method === 'POST' && u.pathname === '/save') {
|
||||||
|
let body = '';
|
||||||
|
req.on('data', c => body += c);
|
||||||
|
req.on('end', () => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(body);
|
||||||
|
fs.writeFileSync(FILES[activeFile], JSON.stringify(data, null, 2) + '\n', 'utf8');
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ ok: true }));
|
||||||
|
} catch (e) {
|
||||||
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ ok: false, error: e.message }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET / — editor UI
|
||||||
|
let message = null;
|
||||||
|
let jsonData = '{}';
|
||||||
|
try {
|
||||||
|
jsonData = fs.readFileSync(FILES[activeFile], 'utf8').trim();
|
||||||
|
} catch (e) {
|
||||||
|
message = { type: 'err', text: 'Fájl olvasási hiba: ' + e.message };
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||||
|
res.end(HTML(activeFile, jsonData, message));
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(PORT, () => {
|
||||||
|
console.log(`\n✅ mozdIT Tartalom Szerkesztő fut: http://localhost:${PORT}\n`);
|
||||||
|
console.log(' Szerkeszthető fájlok:');
|
||||||
|
Object.entries(FILE_LABELS).forEach(([k, l]) => {
|
||||||
|
const rel = k === 'common' ? 'common.json' : `pages/${k}.json`;
|
||||||
|
console.log(` • ${l}: proto/src/content/${rel}`);
|
||||||
|
});
|
||||||
|
console.log('\n Ctrl+C a leállításhoz\n');
|
||||||
|
});
|
||||||
@@ -285,7 +285,7 @@ export default function ContactPage() {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold text-gray-900 mb-1">{pageContent.info.webmail.title}</h3>
|
<h3 className="font-semibold text-gray-900 mb-1">{pageContent.info.webmail.title}</h3>
|
||||||
<a
|
<a
|
||||||
href={siteConfig.hero.cta.primary.href}
|
href={content.pages.home.hero.cta.primary.href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-blue-600 hover:text-blue-700"
|
className="text-blue-600 hover:text-blue-700"
|
||||||
|
|||||||
@@ -25,22 +25,22 @@ export default function Home() {
|
|||||||
className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6 leading-tight animate-fade-in-up"
|
className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6 leading-tight animate-fade-in-up"
|
||||||
style={{ color: 'var(--color-foreground)' }}
|
style={{ color: 'var(--color-foreground)' }}
|
||||||
>
|
>
|
||||||
{siteConfig.hero.title}
|
{pageContent.hero.title}
|
||||||
</h1>
|
</h1>
|
||||||
<p
|
<p
|
||||||
className="text-lg md:text-xl max-w-4xl mx-auto mb-10 leading-relaxed animate-fade-in-up"
|
className="text-lg md:text-xl max-w-4xl mx-auto mb-10 leading-relaxed animate-fade-in-up"
|
||||||
style={{ color: 'var(--color-foreground-muted)', animationDelay: '100ms' }}
|
style={{ color: 'var(--color-foreground-muted)', animationDelay: '100ms' }}
|
||||||
>
|
>
|
||||||
{siteConfig.hero.description}
|
{pageContent.hero.description}
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
className="flex flex-col sm:flex-row gap-4 justify-center items-center animate-fade-in-up"
|
className="flex flex-col sm:flex-row gap-4 justify-center items-center animate-fade-in-up"
|
||||||
style={{ animationDelay: '200ms' }}
|
style={{ animationDelay: '200ms' }}
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={siteConfig.hero.cta.primary.href}
|
href={pageContent.hero.cta.primary.href}
|
||||||
target={siteConfig.hero.cta.primary.external ? '_blank' : undefined}
|
target={pageContent.hero.cta.primary.external ? '_blank' : undefined}
|
||||||
rel={siteConfig.hero.cta.primary.external ? 'noopener noreferrer' : undefined}
|
rel={pageContent.hero.cta.primary.external ? 'noopener noreferrer' : undefined}
|
||||||
className="btn btn-primary text-lg px-8 py-4 group"
|
className="btn btn-primary text-lg px-8 py-4 group"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -51,14 +51,14 @@ export default function Home() {
|
|||||||
>
|
>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10" />
|
||||||
</svg>
|
</svg>
|
||||||
{siteConfig.hero.cta.primary.text}
|
{pageContent.hero.cta.primary.text}
|
||||||
</a>
|
</a>
|
||||||
{siteConfig.hero.cta.secondary && (
|
{pageContent.hero.cta.secondary && (
|
||||||
<a
|
<a
|
||||||
href={siteConfig.hero.cta.secondary.href}
|
href={pageContent.hero.cta.secondary.href}
|
||||||
className="btn btn-secondary text-lg px-8 py-4"
|
className="btn btn-secondary text-lg px-8 py-4"
|
||||||
>
|
>
|
||||||
{siteConfig.hero.cta.secondary.text}
|
{pageContent.hero.cta.secondary.text}
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export default function ServicesPage() {
|
|||||||
{pageContent.cta.primaryButton}
|
{pageContent.cta.primaryButton}
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={siteConfig.hero.cta.primary.href}
|
href={content.pages.home.hero.cta.primary.href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-block border-2 border-white text-white hover:bg-white hover:text-gray-900 font-medium px-8 py-3 rounded-md transition-colors"
|
className="inline-block border-2 border-white text-white hover:bg-white hover:text-gray-900 font-medium px-8 py-3 rounded-md transition-colors"
|
||||||
|
|||||||
@@ -31,22 +31,6 @@ export const siteConfig: SiteConfig = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
hero: {
|
|
||||||
title: 'Megbízható web‑ és email‑szolgáltatás személyre szabott támogatással',
|
|
||||||
subtitle: 'Kis ügyfélkör, nagy figyelem: stabil tárhely, üzembiztos levelezés és DNS adminisztráció — gyors reakcióval.',
|
|
||||||
description: 'A mozdIT Bt. célja, hogy megbízható, támogató szolgáltatásokkal segítse ügyfeleit a digitális térben való sikerben.',
|
|
||||||
cta: {
|
|
||||||
primary: {
|
|
||||||
text: 'Webmail Ugrás',
|
|
||||||
href: process.env.NEXT_PUBLIC_WEBMAIL_URL || 'https://webmail.mozdit.hu',
|
|
||||||
external: true
|
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
text: 'Kapcsolatfelvétel',
|
|
||||||
href: '/kapcsolat'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
services: {
|
services: {
|
||||||
title: 'Szolgáltatásaink',
|
title: 'Szolgáltatásaink',
|
||||||
|
|||||||
@@ -1,49 +1,55 @@
|
|||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
|
"0": {
|
||||||
|
"$1escription": "Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit. Több mint 20 éve nyújtunk megbízható IT szolgáltatásokat."
|
||||||
|
},
|
||||||
"title": "Rólunk",
|
"title": "Rólunk",
|
||||||
"description": "Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit. Több mint 10 éve nyújtunk megbízható IT szolgáltatásokat.",
|
"description": "Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit. Több mint 20 éve nyújtunk megbízható IT szolgáltatásokat.",
|
||||||
"ogDescription": "Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit."
|
"ogDescription": "Ismerje meg a mozdIT Bt. történetét, küldetését és értékeit."
|
||||||
},
|
},
|
||||||
"hero": {
|
"hero": {
|
||||||
"title": "Rólunk",
|
"title": "Rólunk",
|
||||||
"subtitle": "Több mint 10 éve biztosítunk megbízható IT infrastruktúrát és személyes ügyfélszolgálatot"
|
"subtitle": "Több mint 20 éve biztosítunk megbízható IT infrastruktúrát és személyes ügyfélszolgálatot"
|
||||||
},
|
},
|
||||||
"story": {
|
"story": {
|
||||||
"title": "Történetünk",
|
"title": "Történetünk",
|
||||||
"paragraphs": [
|
"paragraphs": [],
|
||||||
"A <strong>mozdIT Bt.</strong> 2010-ben alakult azzal a céllal, hogy kisvállalkozások és magánszemélyek számára nyújtson megbízható, személyes IT szolgáltatásokat. Alapítóink több évtizedes tapasztalattal rendelkeznek a rendszeradminisztráció és webfejlesztés területén.",
|
"paragraphs[0]": "A <strong>mozdIT Bt.</strong> 2002-ben alakult azzal a céllal, hogy megbízható, személyes IT szolgáltatásokat nyújtson. Alapítóink több évtizedes tapasztalattal rendelkeznek a rendszeradminisztráció és informatikai fejlesztések területén.",
|
||||||
"Kezdetben néhány ügyfél weboldalának üzemeltetésével indultunk, ma pedig több száz domain és email fiók működését biztosítjuk. Növekedésünk során mindig szem előtt tartottuk az alapelveinket: <em>megbízhatóság, személyes kapcsolat és műszaki kiválóság</em>.",
|
"paragraphs[2]": "Kis csapat, nagy elköteleződés. Saját dedikált szerverünkön biztosítjuk webtárhelyét, domainjét és e-mail szolgáltatásait – kis- és középvállalkozásoknak, magánszemélyeknek. Személyes ügyfélszolgálat, emberi lépték, és mindig elérhető választ kap.",
|
||||||
"Csapatunk folyamatosan képezi magát a legújabb technológiák terén, hogy ügyfeleink mindig korszerű és biztonságos megoldásokat kapjanak. Büszkék vagyunk arra, hogy sok ügyfelünkkel évek óta tartjuk a kapcsolatot, és számos projektet vittünk sikerre közösen."
|
"paragraphs[3]": "Folyamatosan képezzük magunkat a legújabb technológiák terén, hogy ügyfeleink mindig korszerű és biztonságos megoldásokat kapjanak. Büszkék vagyunk arra, hogy több ügyfelünkkel évtizedek óta tartjuk a kapcsolatot, és számos projektet vittünk sikerre közösen."
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"mission": {
|
"mission": {
|
||||||
"title": "Küldetésünk és értékeink",
|
"title": "Küldetésünk és értékeink",
|
||||||
"subtitle": "Minden nap azért dolgozunk, hogy ügyfeleink digitális jelenléte biztonságos, stabil és hatékony legyen.",
|
"subtitle": "Minden nap azért dolgozunk, hogy ügyfeleink digitális jelenléte biztonságos, stabil és hatékony legyen.",
|
||||||
"values": [
|
"values": [],
|
||||||
{
|
"values[0]": {
|
||||||
"icon": "🛡️",
|
"0": {
|
||||||
"title": "Megbízhatóság",
|
"$1escription": "99.9% uptime és 24/7 monitoring biztosítja, hogy szolgáltatásaink mindig elérhetők legyenek."
|
||||||
"description": "99.9% uptime és 24/7 monitoring biztosítja, hogy szolgáltatásaink mindig elérhetők legyenek."
|
|
||||||
},
|
},
|
||||||
{
|
"icon": "🛡️",
|
||||||
"icon": "👥",
|
"title": "Megbízhatóság"
|
||||||
"title": "Személyes kapcsolat",
|
},
|
||||||
"description": "Minden ügyfél számít számunkra. Személyre szabott megoldásokat kínálunk és mindig elérhetők vagyunk."
|
"values[1]": {
|
||||||
|
"0": {
|
||||||
|
"$1escription": "Minden ügyfél számít számunkra. Személyre szabott megoldásokat kínálunk."
|
||||||
},
|
},
|
||||||
{
|
"icon": "👥",
|
||||||
"icon": "⚡",
|
"title": "Személyes kapcsolat"
|
||||||
"title": "Műszaki kiválóság",
|
},
|
||||||
"description": "Korszerű technológiák és bevált gyakorlatok alkalmazásával biztosítjuk a legmagasabb színvonalú szolgáltatást."
|
"values[2]": {
|
||||||
}
|
"0": {
|
||||||
]
|
"$1escription": "Korszerű technológiák és bevált gyakorlatok alkalmazásával biztosítjuk a legmagasabb színvonalú szolgáltatást."
|
||||||
|
},
|
||||||
|
"icon": "⚡",
|
||||||
|
"title": "Műszaki kiválóság"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"team": {
|
"team": {
|
||||||
"title": "Szakértő csapat",
|
"title": "Szakértő csapat",
|
||||||
"subtitle": "Tapasztalt IT szakemberek, akik szenvedélyesen dolgoznak az ügyfeleink sikeréért",
|
"subtitle": "Tapasztalt IT szakemberek, akik elkötelezetten és megbízhatóan dolgoznak az ügyfeleink sikeréért.",
|
||||||
"paragraphs": [
|
"paragraphs": [],
|
||||||
"Csapatunk rendszeradminisztrátorokból, webfejlesztőkből és ügyfélszolgálati szakértőkből áll. Mindannyian több mint 10 éves tapasztalattal rendelkeznek a maguk területén, és folyamatosan követik a technológiai újdonságokat.",
|
"paragraphs[0]": "Csapatunk tagjai több mint 20 éves tapasztalattal rendelkeznek a maguk területén, és folyamatosan követik a technológiai újdonságokat.",
|
||||||
"Hiszünk abban, hogy a jó kommunikáció és a műszaki tudás együtt teremti meg a tökéletes ügyfélélményt. Ezért minden munkatársunk nemcsak technikai szakértő, hanem kiváló kommunikátor is."
|
"paragraphs[1]": "Hiszünk abban, hogy a jó kommunikáció és a műszaki tudás együtt teremti meg a tökéletes ügyfélélményt."
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"cta": {
|
"cta": {
|
||||||
"title": "Legyen Ön is elégedett ügyfelünk!",
|
"title": "Legyen Ön is elégedett ügyfelünk!",
|
||||||
|
|||||||
@@ -1,4 +1,20 @@
|
|||||||
{
|
{
|
||||||
|
"hero": {
|
||||||
|
"title": "Megbízható web‑ és email‑szolgáltatás személyre szabott támogatással",
|
||||||
|
"subtitle": "Kis ügyfélkör, nagy figyelem: stabil tárhely, üzembiztos levelezés és DNS adminisztráció — gyors reakcióval.",
|
||||||
|
"description": "A mozdIT Bt. célja, hogy megbízható, támogató szolgáltatásokkal segítse ügyfeleit a digitális térben való sikerben.",
|
||||||
|
"cta": {
|
||||||
|
"primary": {
|
||||||
|
"text": "Webmail Ugrás",
|
||||||
|
"href": "https://webmail.mozdit.hu",
|
||||||
|
"external": true
|
||||||
|
},
|
||||||
|
"secondary": {
|
||||||
|
"text": "Kapcsolatfelvétel",
|
||||||
|
"href": "/kapcsolat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"cta": {
|
"cta": {
|
||||||
"title": "Kapcsolatfelvétel az első lépés",
|
"title": "Kapcsolatfelvétel az első lépés",
|
||||||
"subtitle": "Mutassuk meg, hogyan segíthetünk Önnek megvalósítani céljait!",
|
"subtitle": "Mutassuk meg, hogyan segíthetünk Önnek megvalósítani céljait!",
|
||||||
|
|||||||
Reference in New Issue
Block a user