Initial commit — Meridian OS browser desktop demo
This commit is contained in:
167
js/apps/chrome.js
Normal file
167
js/apps/chrome.js
Normal file
@@ -0,0 +1,167 @@
|
||||
// Chrome spoof — static-but-believable browser UI.
|
||||
|
||||
const TABS = [
|
||||
{ title: 'New Tab', active: true },
|
||||
{ title: 'Meridian Docs', active: false },
|
||||
{ title: 'Search', active: false },
|
||||
];
|
||||
|
||||
const SHORTCUTS = [
|
||||
{ label: 'GitHub', url: 'https://github.com' },
|
||||
{ label: 'Gmail', url: 'https://mail.google.com' },
|
||||
{ label: 'YouTube', url: 'https://youtube.com' },
|
||||
{ label: 'Maps', url: 'https://maps.google.com' },
|
||||
{ label: 'Drive', url: 'https://drive.google.com' },
|
||||
{ label: 'Calendar', url: 'https://calendar.google.com' },
|
||||
{ label: 'Docs', url: 'https://docs.google.com' },
|
||||
{ label: 'Translate', url: 'https://translate.google.com' },
|
||||
];
|
||||
|
||||
export default {
|
||||
id: 'chrome',
|
||||
title: 'Chrome',
|
||||
render(container, api) {
|
||||
let activeTab = 0;
|
||||
let navUrl = '';
|
||||
|
||||
const root = document.createElement('div');
|
||||
root.style.cssText = 'height:100%;display:flex;flex-direction:column;background:var(--bg-1);';
|
||||
|
||||
// Tab strip
|
||||
const tabStrip = document.createElement('div');
|
||||
tabStrip.style.cssText = 'display:flex;background:var(--bg-2);border-bottom:1px solid var(--border);flex-shrink:0;overflow-x:auto;';
|
||||
|
||||
// New tab button
|
||||
const newTabBtn = document.createElement('button');
|
||||
newTabBtn.style.cssText = 'padding:8px 12px;font-size:16px;border:none;background:transparent;color:var(--text-dim);cursor:pointer;flex-shrink:0;';
|
||||
newTabBtn.textContent = '+';
|
||||
newTabBtn.addEventListener('click', () => {
|
||||
const newIdx = TABS.length;
|
||||
TABS.push({ title: 'New Tab', active: true });
|
||||
TABS.forEach((t) => { t.active = false; });
|
||||
TABS[newIdx].active = true;
|
||||
activeTab = newIdx;
|
||||
navUrl = '';
|
||||
updateTabBar();
|
||||
updateBody();
|
||||
});
|
||||
|
||||
function updateTabBar() {
|
||||
tabStrip.innerHTML = '';
|
||||
TABS.forEach((tab, i) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.style.cssText = 'padding:8px 16px;font-size:12px;border:none;background:transparent;color:' + (tab.active ? 'var(--text)' : 'var(--text-dim)') + ';border-right:1px solid var(--border);cursor:pointer;transition:color 150ms;max-width:180px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
|
||||
btn.textContent = tab.title;
|
||||
btn.addEventListener('click', () => {
|
||||
activeTab = i;
|
||||
navUrl = SHORTCUTS[i] ? SHORTCUTS[i].url : '';
|
||||
updateTabBar();
|
||||
updateBody();
|
||||
});
|
||||
tabStrip.appendChild(btn);
|
||||
});
|
||||
tabStrip.appendChild(newTabBtn);
|
||||
}
|
||||
|
||||
updateTabBar();
|
||||
root.appendChild(tabStrip);
|
||||
|
||||
// Address bar
|
||||
const addrBar = document.createElement('div');
|
||||
addrBar.style.cssText = 'display:flex;align-items:center;padding:6px 12px;gap:8px;background:var(--bg-2);border-bottom:1px solid var(--border);flex-shrink:0;';
|
||||
|
||||
const urlInput = document.createElement('input');
|
||||
urlInput.type = 'text';
|
||||
urlInput.value = navUrl || (SHORTCUTS[activeTab] ? SHORTCUTS[activeTab].url : '');
|
||||
urlInput.style.cssText = 'flex:1;padding:6px 12px;background:var(--bg-3);border:1px solid var(--border);border-radius:20px;color:var(--text);font-size:13px;outline:none;';
|
||||
urlInput.placeholder = 'Search or enter web address';
|
||||
urlInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
const val = urlInput.value.trim();
|
||||
if (val) {
|
||||
navUrl = val;
|
||||
updateBody();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const reloadBtn = document.createElement('button');
|
||||
reloadBtn.style.cssText = 'padding:4px 8px;background:transparent;border:none;color:var(--text-dim);cursor:pointer;font-size:14px;';
|
||||
reloadBtn.textContent = '\u{21BB}';
|
||||
reloadBtn.addEventListener('click', () => { updateBody(); });
|
||||
|
||||
addrBar.appendChild(urlInput);
|
||||
addrBar.appendChild(reloadBtn);
|
||||
root.appendChild(addrBar);
|
||||
|
||||
// Body
|
||||
const body = document.createElement('div');
|
||||
body.style.cssText = 'flex:1;overflow-y:auto;padding:40px 20px;background:var(--bg-1);';
|
||||
|
||||
function updateBody() {
|
||||
body.innerHTML = '';
|
||||
urlInput.value = navUrl || (TABS[activeTab].title === 'New Tab' ? '' : TABS[activeTab].title.toLowerCase().replace(/\s+/g, '-') + '.meridian.dev');
|
||||
|
||||
if (activeTab === 0 && !navUrl) {
|
||||
// New tab page
|
||||
const titleEl = document.createElement('h2');
|
||||
titleEl.style.cssText = 'font-size:22px;font-weight:500;color:var(--text);margin-bottom:28px;text-align:center;';
|
||||
titleEl.textContent = 'Meridian Browser';
|
||||
body.appendChild(titleEl);
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.style.cssText = 'display:grid;grid-template-columns:repeat(4,1fr);gap:12px;max-width:600px;margin:0 auto;';
|
||||
|
||||
SHORTCUTS.forEach((sc) => {
|
||||
const tile = document.createElement('div');
|
||||
tile.style.cssText = 'padding:16px 12px;background:var(--bg-2);border:1px solid var(--border);border-radius:8px;text-align:center;cursor:pointer;transition:background 150ms;';
|
||||
tile.addEventListener('mouseenter', () => { tile.style.background = 'var(--bg-3)'; });
|
||||
tile.addEventListener('mouseleave', () => { tile.style.background = 'var(--bg-2)'; });
|
||||
tile.addEventListener('click', () => {
|
||||
navUrl = sc.url;
|
||||
TABS[0].title = sc.label;
|
||||
updateTabBar();
|
||||
updateBody();
|
||||
});
|
||||
const tileIcon = document.createElement('div');
|
||||
tileIcon.style.cssText = 'font-size:18px;font-weight:600;color:var(--accent);margin-bottom:4px;';
|
||||
tileIcon.textContent = sc.label.charAt(0);
|
||||
const tileLabel = document.createElement('div');
|
||||
tileLabel.style.cssText = 'font-size:11px;color:var(--text-dim);';
|
||||
tileLabel.textContent = sc.label;
|
||||
tile.appendChild(tileIcon);
|
||||
tile.appendChild(tileLabel);
|
||||
grid.appendChild(tile);
|
||||
});
|
||||
|
||||
body.appendChild(grid);
|
||||
} else {
|
||||
// Simulated page
|
||||
const page = document.createElement('div');
|
||||
page.style.cssText = 'max-width:600px;margin:0 auto;';
|
||||
|
||||
const heading = document.createElement('h2');
|
||||
heading.style.cssText = 'font-size:22px;font-weight:500;color:var(--text);margin-bottom:12px;';
|
||||
heading.textContent = 'Simulated page for ' + (navUrl || TABS[activeTab].title);
|
||||
page.appendChild(heading);
|
||||
|
||||
const urlDisplay = document.createElement('div');
|
||||
urlDisplay.style.cssText = 'font-size:12px;color:var(--text-dim);margin-bottom:24px;font-family:monospace;padding:8px 12px;background:var(--bg-2);border:1px solid var(--border);border-radius:4px;';
|
||||
urlDisplay.textContent = navUrl || TABS[activeTab].title;
|
||||
page.appendChild(urlDisplay);
|
||||
|
||||
const para = document.createElement('p');
|
||||
para.style.cssText = 'font-size:14px;color:var(--text-dim);line-height:1.7;';
|
||||
const displayUrl = navUrl || TABS[activeTab].title;
|
||||
para.textContent = 'This is a simulated page for ' + displayUrl + '. Meridian Browser does not make actual network requests — it is a spoof. Nothing to see here unless you count the aesthetic choices.';
|
||||
page.appendChild(para);
|
||||
|
||||
body.appendChild(page);
|
||||
}
|
||||
}
|
||||
|
||||
root.appendChild(body);
|
||||
container.appendChild(root);
|
||||
updateBody();
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user