539 lines
30 KiB
JavaScript
539 lines
30 KiB
JavaScript
|
|
/**
|
|||
|
|
* pc-live | Browser App
|
|||
|
|
* IC URL schema: ic://slug
|
|||
|
|
*/
|
|||
|
|
const BrowserApp = (() => {
|
|||
|
|
const WIN_ID = 'app-browser';
|
|||
|
|
const history = [];
|
|||
|
|
let histIdx = -1;
|
|||
|
|
|
|||
|
|
/* ── IC page templates ─────────────────────────────────── */
|
|||
|
|
const PageTemplates = {
|
|||
|
|
home: () => `
|
|||
|
|
<div class="ic-page fade-in">
|
|||
|
|
<div class="ic-home-hero">
|
|||
|
|
<h1>⚡ IC Browser</h1>
|
|||
|
|
<p style="color:var(--text-muted);font-size:.82rem">Browse the internal network. Enter an <code>ic://</code> address above.</p>
|
|||
|
|
<div class="ic-home-links">
|
|||
|
|
<a class="ic-link-card" onclick="BrowserApp.navigate('ic://dpa')">📰 DPA News</a>
|
|||
|
|
<a class="ic-link-card" onclick="BrowserApp.navigate('ic://police')">🚔 LSPD</a>
|
|||
|
|
<a class="ic-link-card" onclick="BrowserApp.navigate('ic://ambulance')">🚑 LSMD</a>
|
|||
|
|
<a class="ic-link-card" onclick="BrowserApp.navigate('ic://fire')">🚒 LSFD</a>
|
|||
|
|
<a class="ic-link-card" onclick="BrowserApp.navigate('ic://doj')">⚖️ DOJ</a>
|
|||
|
|
<a class="ic-link-card" onclick="BrowserApp.navigate('ic://parking')">🅿️ Parking</a>
|
|||
|
|
<a class="ic-link-card" onclick="BrowserApp.navigate('ic://weazel')">📺 Weazel News</a>
|
|||
|
|
<a class="ic-link-card" onclick="BrowserApp.navigate('ic://classifieds')">📋 LS Classifieds</a>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>`,
|
|||
|
|
|
|||
|
|
dpa: () => `
|
|||
|
|
<div class="ic-page fade-in">
|
|||
|
|
<h1>📰 DPA – Deutsche Presse-Agentur</h1>
|
|||
|
|
<div class="article">
|
|||
|
|
<div class="article-date">Today, 09:15</div>
|
|||
|
|
<h2>New residential district approved in Vinewood Hills</h2>
|
|||
|
|
<p>The city council has approved plans for 120 new residential units in the Vinewood Hills area. Construction is set to begin next month pending environmental review.</p>
|
|||
|
|
</div>
|
|||
|
|
<div class="article">
|
|||
|
|
<div class="article-date">Today, 07:42</div>
|
|||
|
|
<h2>Port Authority reports record cargo volume</h2>
|
|||
|
|
<p>Los Santos Port Authority announced a 14% increase in cargo processing year-over-year, citing improved logistics infrastructure and expanded berth capacity.</p>
|
|||
|
|
</div>
|
|||
|
|
<div class="article">
|
|||
|
|
<div class="article-date">Yesterday, 18:00</div>
|
|||
|
|
<h2>Traffic advisory: Freeway 1 maintenance</h2>
|
|||
|
|
<p>LSDOT warns of lane closures on Freeway 1 near Palomino Highlands between 22:00 and 06:00 for the next three nights.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>`,
|
|||
|
|
|
|||
|
|
psb: () => `
|
|||
|
|
<div class="ic-page fade-in">
|
|||
|
|
<h1>🚔 Los Santos Police Service Board</h1>
|
|||
|
|
<p>Official information and public notices from the Los Santos Police Department.</p>
|
|||
|
|
<div class="article">
|
|||
|
|
<div class="article-date">Official Notice</div>
|
|||
|
|
<h2>Public Safety Campaign – Q1</h2>
|
|||
|
|
<p>The LSPD is launching a public outreach campaign focused on traffic safety and community engagement. Residents are encouraged to report suspicious activity through official channels.</p>
|
|||
|
|
</div>
|
|||
|
|
<div class="article">
|
|||
|
|
<div class="article-date">Recruitment</div>
|
|||
|
|
<h2>LSPD Cadet Applications Open</h2>
|
|||
|
|
<p>Applications for the upcoming cadet class are now open. Qualified candidates should contact the department for details on requirements and the application process.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>`,
|
|||
|
|
|
|||
|
|
weazel: () => `
|
|||
|
|
<div class="ic-page fade-in">
|
|||
|
|
<h1>📺 Weazel News</h1>
|
|||
|
|
<p style="color:var(--accent);font-size:.72rem;margin-bottom:16px">Los Santos' #1 Trusted News Source</p>
|
|||
|
|
<div class="article">
|
|||
|
|
<div class="article-date">Breaking</div>
|
|||
|
|
<h2>Explosion reported near LSIA</h2>
|
|||
|
|
<p>Witnesses report a large explosion near the Los Santos International Airport. LSFD units are on scene. Air traffic has been temporarily halted as a precaution.</p>
|
|||
|
|
</div>
|
|||
|
|
<div class="article">
|
|||
|
|
<div class="article-date">Entertainment</div>
|
|||
|
|
<h2>Vinewood Music Awards – Full List of Nominees</h2>
|
|||
|
|
<p>The annual Vinewood Music Awards nominees have been announced. This year's ceremony will take place at the Maze Bank Arena. Tickets available starting next week.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>`,
|
|||
|
|
|
|||
|
|
// ── Org pages ────────────────────────────────────────────
|
|||
|
|
org_police: () => `
|
|||
|
|
<div class="ic-page fade-in">
|
|||
|
|
<div style="display:flex;align-items:center;gap:14px;margin-bottom:20px">
|
|||
|
|
<span style="font-size:2.4rem">🚔</span>
|
|||
|
|
<div><h1 style="margin:0">Los Santos Police Department</h1><p style="margin:4px 0 0;color:var(--text-muted);font-size:.8rem">ic://police · pd.gov</p></div>
|
|||
|
|
</div>
|
|||
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;margin-bottom:20px">
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);padding:14px;text-align:center">
|
|||
|
|
<div style="font-size:1.4rem">📞</div><div style="font-weight:700;color:var(--accent);margin:6px 0 2px">Notruf 110</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted)">Polizeilicher Notruf</div>
|
|||
|
|
</div>
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);padding:14px;text-align:center">
|
|||
|
|
<div style="font-size:1.4rem">✉️</div><div style="font-weight:700;color:var(--accent);margin:6px 0 2px">aktensystem@pd.gov</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted)">Offizielles Aktensystem</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="article"><div class="article-date">Dienstlich</div>
|
|||
|
|
<h2>Strafverfolgung & Öffentliche Sicherheit</h2>
|
|||
|
|
<p>Das LSPD ist zuständig für die öffentliche Sicherheit, Strafverfolgung und Verbrechensbekämpfung in Los Santos. Bürger können sich jederzeit an unsere Beamten wenden.</p>
|
|||
|
|
</div>
|
|||
|
|
<div class="article"><div class="article-date">Bewerbungen</div>
|
|||
|
|
<h2>Stellenausschreibung – Cadet Program</h2>
|
|||
|
|
<p>Das LSPD nimmt regelmäßig neue Bewerber für das Cadet-Programm an. Kontakt über das offizielle Aktensystem oder persönlich beim Department.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>`,
|
|||
|
|
|
|||
|
|
org_ambulance: () => `
|
|||
|
|
<div class="ic-page fade-in">
|
|||
|
|
<div style="display:flex;align-items:center;gap:14px;margin-bottom:20px">
|
|||
|
|
<span style="font-size:2.4rem">🚑</span>
|
|||
|
|
<div><h1 style="margin:0">Los Santos Medical Department</h1><p style="margin:4px 0 0;color:var(--text-muted);font-size:.8rem">ic://ambulance · md.gov</p></div>
|
|||
|
|
</div>
|
|||
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;margin-bottom:20px">
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);padding:14px;text-align:center">
|
|||
|
|
<div style="font-size:1.4rem">📞</div><div style="font-weight:700;color:#e53935;margin:6px 0 2px">Notruf 911</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted)">Medizinischer Notfall</div>
|
|||
|
|
</div>
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);padding:14px;text-align:center">
|
|||
|
|
<div style="font-size:1.4rem">✉️</div><div style="font-weight:700;color:var(--accent);margin:6px 0 2px">aktensystem@md.gov</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted)">Patientenakten & Anfragen</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="article"><div class="article-date">Medizinische Versorgung</div>
|
|||
|
|
<h2>Notfallversorgung rund um die Uhr</h2>
|
|||
|
|
<p>Das LSMD bietet medizinische Erstversorgung, Krankentransport und chirurgische Eingriffe. Im Notfall bitte sofort den Notruf 911 wählen.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>`,
|
|||
|
|
|
|||
|
|
org_fire: () => `
|
|||
|
|
<div class="ic-page fade-in">
|
|||
|
|
<div style="display:flex;align-items:center;gap:14px;margin-bottom:20px">
|
|||
|
|
<span style="font-size:2.4rem">🚒</span>
|
|||
|
|
<div><h1 style="margin:0">Los Santos Fire Department</h1><p style="margin:4px 0 0;color:var(--text-muted);font-size:.8rem">ic://fire · fd.gov</p></div>
|
|||
|
|
</div>
|
|||
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;margin-bottom:20px">
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);padding:14px;text-align:center">
|
|||
|
|
<div style="font-size:1.4rem">📞</div><div style="font-weight:700;color:#ff6f00;margin:6px 0 2px">Notruf 112</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted)">Feuerwehr & Technische Hilfe</div>
|
|||
|
|
</div>
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);padding:14px;text-align:center">
|
|||
|
|
<div style="font-size:1.4rem">✉️</div><div style="font-weight:700;color:var(--accent);margin:6px 0 2px">aktensystem@fd.gov</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted)">Offizielle Korrespondenz</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="article"><div class="article-date">Aufgaben</div>
|
|||
|
|
<h2>Brandbekämpfung & Technische Hilfe</h2>
|
|||
|
|
<p>Die LSFD schützt Leben und Eigentum bei Bränden, Unfällen und Naturkatastrophen. Für technische Hilfeleistungen steht das Department rund um die Uhr bereit.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>`,
|
|||
|
|
|
|||
|
|
org_doj: () => `
|
|||
|
|
<div class="ic-page fade-in">
|
|||
|
|
<div style="display:flex;align-items:center;gap:14px;margin-bottom:20px">
|
|||
|
|
<span style="font-size:2.4rem">⚖️</span>
|
|||
|
|
<div><h1 style="margin:0">Department of Justice</h1><p style="margin:4px 0 0;color:var(--text-muted);font-size:.8rem">ic://doj · doj.gov</p></div>
|
|||
|
|
</div>
|
|||
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;margin-bottom:20px">
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);padding:14px;text-align:center">
|
|||
|
|
<div style="font-size:1.4rem">📞</div><div style="font-weight:700;color:var(--accent);margin:6px 0 2px">Hotline 200</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted)">DOJ Auskunft</div>
|
|||
|
|
</div>
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);padding:14px;text-align:center">
|
|||
|
|
<div style="font-size:1.4rem">✉️</div><div style="font-weight:700;color:var(--accent);margin:6px 0 2px">aktensystem@doj.gov</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted)">Juristische Anfragen</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="article"><div class="article-date">Justizsystem</div>
|
|||
|
|
<h2>Strafrecht & Zivilrecht in Los Santos</h2>
|
|||
|
|
<p>Das Department of Justice verwaltet das Rechtssystem von Los Santos. Anwälte, Richter und Staatsanwälte koordinieren alle rechtlichen Vorgänge über dieses Department.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>`,
|
|||
|
|
|
|||
|
|
org_dpa: () => `
|
|||
|
|
<div class="ic-page fade-in">
|
|||
|
|
<div style="display:flex;align-items:center;gap:14px;margin-bottom:20px">
|
|||
|
|
<span style="font-size:2.4rem">🏛️</span>
|
|||
|
|
<div><h1 style="margin:0">Department of Public Administration</h1><p style="margin:4px 0 0;color:var(--text-muted);font-size:.8rem">ic://dpa · dpa.gov</p></div>
|
|||
|
|
</div>
|
|||
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;margin-bottom:20px">
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);padding:14px;text-align:center">
|
|||
|
|
<div style="font-size:1.4rem">✉️</div><div style="font-weight:700;color:var(--accent);margin:6px 0 2px">public@dpa.gov</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted)">Öffentliche Anfragen</div>
|
|||
|
|
</div>
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);padding:14px;text-align:center">
|
|||
|
|
<div style="font-size:1.4rem">✉️</div><div style="font-weight:700;color:var(--accent);margin:6px 0 2px">traffic@dpa.gov</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted)">Verkehr & Infrastruktur</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="article"><div class="article-date">Verwaltung</div>
|
|||
|
|
<h2>Staatliche Verwaltung & Öffentlicher Dienst</h2>
|
|||
|
|
<p>Die DPA koordiniert Verwaltungsaufgaben, Genehmigungen und öffentliche Dienstleistungen. Anfragen können per Mail oder telefonisch gestellt werden.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>`,
|
|||
|
|
|
|||
|
|
org_parking: () => `
|
|||
|
|
<div class="ic-page fade-in">
|
|||
|
|
<div style="display:flex;align-items:center;gap:14px;margin-bottom:20px">
|
|||
|
|
<span style="font-size:2.4rem">🅿️</span>
|
|||
|
|
<div><h1 style="margin:0">Parkraumüberwachung Los Santos</h1><p style="margin:4px 0 0;color:var(--text-muted);font-size:.8rem">ic://parking · parking.gov</p></div>
|
|||
|
|
</div>
|
|||
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;margin-bottom:20px">
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);padding:14px;text-align:center">
|
|||
|
|
<div style="font-size:1.4rem">✉️</div><div style="font-weight:700;color:var(--accent);margin:6px 0 2px">aktensystem@parking.gov</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted)">Einsprüche & Anfragen</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="article"><div class="article-date">Information</div>
|
|||
|
|
<h2>Knöllchen & Einsprüche</h2>
|
|||
|
|
<p>Bei Fragen zu Knöllchen oder Einsprüchen bitte schriftlich über aktensystem@parking.gov oder persönlich am Schalter.</p>
|
|||
|
|
</div>
|
|||
|
|
</div>`,
|
|||
|
|
|
|||
|
|
|
|||
|
|
classifieds: () => `
|
|||
|
|
<div class="ic-page fade-in">
|
|||
|
|
|
|||
|
|
<!-- Header -->
|
|||
|
|
<div style="background:linear-gradient(135deg,rgba(0,170,255,0.07) 0%,transparent 70%);
|
|||
|
|
border:1px solid var(--border);border-radius:8px;
|
|||
|
|
padding:22px 24px;text-align:center;margin-bottom:22px">
|
|||
|
|
<div style="font-size:1.7rem;font-weight:700;color:var(--accent);letter-spacing:.06em">
|
|||
|
|
📋 LS CLASSIFIEDS
|
|||
|
|
</div>
|
|||
|
|
<div style="color:var(--text-muted);font-size:.74rem;margin-top:5px">
|
|||
|
|
Los Santos' largest private marketplace · <span style="color:var(--text-dim)">ic://classifieds</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Category tiles -->
|
|||
|
|
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-bottom:26px">
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);
|
|||
|
|
padding:14px 8px;text-align:center;cursor:pointer;transition:border-color .15s"
|
|||
|
|
onmouseover="this.style.borderColor='var(--accent)'" onmouseout="this.style.borderColor='var(--border)'">
|
|||
|
|
<div style="font-size:1.5rem">🚗</div>
|
|||
|
|
<div style="font-size:.78rem;font-weight:600;color:var(--text);margin-top:4px">Vehicles</div>
|
|||
|
|
<div style="font-size:.65rem;color:var(--text-dim);margin-top:2px">47 listings</div>
|
|||
|
|
</div>
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);
|
|||
|
|
padding:14px 8px;text-align:center;cursor:pointer;transition:border-color .15s"
|
|||
|
|
onmouseover="this.style.borderColor='var(--accent)'" onmouseout="this.style.borderColor='var(--border)'">
|
|||
|
|
<div style="font-size:1.5rem">🏠</div>
|
|||
|
|
<div style="font-size:.78rem;font-weight:600;color:var(--text);margin-top:4px">Real Estate</div>
|
|||
|
|
<div style="font-size:.65rem;color:var(--text-dim);margin-top:2px">23 listings</div>
|
|||
|
|
</div>
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);
|
|||
|
|
padding:14px 8px;text-align:center;cursor:pointer;transition:border-color .15s"
|
|||
|
|
onmouseover="this.style.borderColor='var(--accent)'" onmouseout="this.style.borderColor='var(--border)'">
|
|||
|
|
<div style="font-size:1.5rem">💼</div>
|
|||
|
|
<div style="font-size:.78rem;font-weight:600;color:var(--text);margin-top:4px">Jobs</div>
|
|||
|
|
<div style="font-size:.65rem;color:var(--text-dim);margin-top:2px">12 listings</div>
|
|||
|
|
</div>
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);
|
|||
|
|
padding:14px 8px;text-align:center;cursor:pointer;transition:border-color .15s"
|
|||
|
|
onmouseover="this.style.borderColor='var(--accent)'" onmouseout="this.style.borderColor='var(--border)'">
|
|||
|
|
<div style="font-size:1.5rem">🔧</div>
|
|||
|
|
<div style="font-size:.78rem;font-weight:600;color:var(--text);margin-top:4px">Services</div>
|
|||
|
|
<div style="font-size:.65rem;color:var(--text-dim);margin-top:2px">31 listings</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Section: Featured -->
|
|||
|
|
<div style="font-size:.68rem;text-transform:uppercase;letter-spacing:.12em;color:var(--text-dim);
|
|||
|
|
margin-bottom:12px;padding-bottom:6px;border-bottom:1px solid var(--border)">
|
|||
|
|
⭐ Featured Listings
|
|||
|
|
</div>
|
|||
|
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(235px,1fr));gap:10px;margin-bottom:24px">
|
|||
|
|
|
|||
|
|
<!-- Listing: Car -->
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);
|
|||
|
|
padding:14px;display:flex;flex-direction:column;gap:7px">
|
|||
|
|
<div style="display:flex;justify-content:space-between;align-items:center">
|
|||
|
|
<span style="font-size:1.3rem">🚗</span>
|
|||
|
|
<span class="tag tag-accent" style="font-size:.6rem">FEATURED</span>
|
|||
|
|
</div>
|
|||
|
|
<div style="font-weight:700;color:#fff;font-size:.85rem">2023 Ocelot Pariah</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted);line-height:1.5">
|
|||
|
|
Low mileage, fully tuned at Benny's. Custom matte black wrap. Clean history, no damage.
|
|||
|
|
</div>
|
|||
|
|
<div style="font-size:.95rem;color:var(--accent);font-weight:700">$285,000</div>
|
|||
|
|
<div style="font-size:.63rem;color:var(--text-dim)">📍 Vinewood · Posted 2h ago</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Listing: Motorcycle -->
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);
|
|||
|
|
padding:14px;display:flex;flex-direction:column;gap:7px">
|
|||
|
|
<div style="display:flex;justify-content:space-between;align-items:center">
|
|||
|
|
<span style="font-size:1.3rem">🏍</span>
|
|||
|
|
<span class="tag">Vehicles</span>
|
|||
|
|
</div>
|
|||
|
|
<div style="font-weight:700;color:#fff;font-size:.85rem">Western Daemon Custom</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted);line-height:1.5">
|
|||
|
|
Full performance upgrade, custom exhaust. Runs clean. Selling due to relocation.
|
|||
|
|
</div>
|
|||
|
|
<div style="font-size:.95rem;color:var(--accent);font-weight:700">$68,500</div>
|
|||
|
|
<div style="font-size:.63rem;color:var(--text-dim)">📍 Strawberry · Posted 6h ago</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Listing: House -->
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);
|
|||
|
|
padding:14px;display:flex;flex-direction:column;gap:7px">
|
|||
|
|
<div style="display:flex;justify-content:space-between;align-items:center">
|
|||
|
|
<span style="font-size:1.3rem">🏠</span>
|
|||
|
|
<span class="tag">Real Estate</span>
|
|||
|
|
</div>
|
|||
|
|
<div style="font-weight:700;color:#fff;font-size:.85rem">3-Bed House · Mirror Park</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted);line-height:1.5">
|
|||
|
|
Modern detached house, double garage, pool. Move-in ready. Quiet, well-connected area.
|
|||
|
|
</div>
|
|||
|
|
<div style="font-size:.95rem;color:var(--accent);font-weight:700">$1,200,000</div>
|
|||
|
|
<div style="font-size:.63rem;color:var(--text-dim)">📍 Mirror Park · Posted 5h ago</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Listing: Job -->
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);
|
|||
|
|
padding:14px;display:flex;flex-direction:column;gap:7px">
|
|||
|
|
<div style="display:flex;justify-content:space-between;align-items:center">
|
|||
|
|
<span style="font-size:1.3rem">💼</span>
|
|||
|
|
<span class="tag" style="color:var(--success);border-color:rgba(34,197,94,.3)">Jobs</span>
|
|||
|
|
</div>
|
|||
|
|
<div style="font-weight:700;color:#fff;font-size:.85rem">Mechanic – Premium Auto</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted);line-height:1.5">
|
|||
|
|
Looking for an experienced mechanic. Flexible shifts, good cut. Experience required.
|
|||
|
|
</div>
|
|||
|
|
<div style="font-size:.95rem;color:var(--success);font-weight:700">$4,500 / week</div>
|
|||
|
|
<div style="font-size:.63rem;color:var(--text-dim)">📍 LSIA Area · Posted 1d ago</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Listing: Apartment -->
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);
|
|||
|
|
padding:14px;display:flex;flex-direction:column;gap:7px">
|
|||
|
|
<div style="display:flex;justify-content:space-between;align-items:center">
|
|||
|
|
<span style="font-size:1.3rem">🏢</span>
|
|||
|
|
<span class="tag">Real Estate</span>
|
|||
|
|
</div>
|
|||
|
|
<div style="font-weight:700;color:#fff;font-size:.85rem">Penthouse · Alta St.</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted);line-height:1.5">
|
|||
|
|
Floor 28, panoramic city view, 2 bed/2 bath, concierge. Fully furnished, immediate occupancy.
|
|||
|
|
</div>
|
|||
|
|
<div style="font-size:.95rem;color:var(--accent);font-weight:700">$3,800,000</div>
|
|||
|
|
<div style="font-size:.63rem;color:var(--text-dim)">📍 Downtown LS · Posted 12h ago</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Listing: Service -->
|
|||
|
|
<div style="background:var(--bg-sidebar);border:1px solid var(--border);border-radius:var(--radius);
|
|||
|
|
padding:14px;display:flex;flex-direction:column;gap:7px">
|
|||
|
|
<div style="display:flex;justify-content:space-between;align-items:center">
|
|||
|
|
<span style="font-size:1.3rem">🔧</span>
|
|||
|
|
<span class="tag">Services</span>
|
|||
|
|
</div>
|
|||
|
|
<div style="font-weight:700;color:#fff;font-size:.85rem">24/7 Mobile Mechanic</div>
|
|||
|
|
<div style="font-size:.72rem;color:var(--text-muted);line-height:1.5">
|
|||
|
|
On-call repairs, towing, custom builds. Competitive rates. Contact via IC mail.
|
|||
|
|
</div>
|
|||
|
|
<div style="font-size:.95rem;color:var(--text-muted);font-weight:600">Contact via mail</div>
|
|||
|
|
<div style="font-size:.63rem;color:var(--text-dim)">📍 Citywide · Posted 3d ago</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Footer -->
|
|||
|
|
<div style="text-align:center;color:var(--text-dim);font-size:.62rem;
|
|||
|
|
border-top:1px solid var(--border);padding-top:12px">
|
|||
|
|
LS Classifieds is not responsible for the accuracy of listings. All trades are at your own risk.
|
|||
|
|
· To post an ad, send a mail to <span style="color:var(--text-muted)">classifieds@ls-net.ic</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
</div>`,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/* Fehlerseite für ic-web-Domänen. Als DOM gebaut, weil der Fehlertext vom
|
|||
|
|
Server kommt und die Domäne aus der Adressleiste stammt. */
|
|||
|
|
function renderICError(domain, message) {
|
|||
|
|
const wrap = document.createElement('div');
|
|||
|
|
wrap.className = 'browser-404 fade-in';
|
|||
|
|
|
|||
|
|
const h = document.createElement('h2');
|
|||
|
|
h.textContent = '404';
|
|||
|
|
const p1 = document.createElement('p');
|
|||
|
|
p1.textContent = 'ic://' + domain;
|
|||
|
|
const p2 = document.createElement('p');
|
|||
|
|
p2.textContent = message || 'Diese Adresse existiert nicht.';
|
|||
|
|
|
|||
|
|
wrap.append(h, p1, p2);
|
|||
|
|
return wrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function render404(slug) {
|
|||
|
|
return `
|
|||
|
|
<div class="browser-404 fade-in">
|
|||
|
|
<h2>404</h2>
|
|||
|
|
<p>Page not found: <code>ic://${slug}</code></p>
|
|||
|
|
<p>Check the address and try again.</p>
|
|||
|
|
</div>`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── URL helpers ────────────────────────────────────────── */
|
|||
|
|
function isExternal(url) {
|
|||
|
|
return /^https?:\/\//i.test(url);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function normalizeUrl(raw) {
|
|||
|
|
raw = (raw || '').trim();
|
|||
|
|
if (!raw) return 'ic://home';
|
|||
|
|
// Already ic:// or http(s)://
|
|||
|
|
if (/^ic:\/\//i.test(raw) || /^https?:\/\//i.test(raw)) return raw;
|
|||
|
|
// Looks like a domain (contains dot) → assume https
|
|||
|
|
if (/^[a-z0-9\-]+\.[a-z]{2,}/i.test(raw)) return 'https://' + raw;
|
|||
|
|
// Otherwise treat as ic:// slug
|
|||
|
|
return 'ic://' + raw;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Navigate ─────────────────────────────────────────── */
|
|||
|
|
async function navigate(rawUrl) {
|
|||
|
|
if (!rawUrl) return;
|
|||
|
|
const url = normalizeUrl(rawUrl);
|
|||
|
|
|
|||
|
|
// Update address bar
|
|||
|
|
const bar = document.getElementById('browser-address');
|
|||
|
|
if (bar) bar.value = url;
|
|||
|
|
|
|||
|
|
// History
|
|||
|
|
if (histIdx < 0 || history[histIdx] !== url) {
|
|||
|
|
history.splice(histIdx + 1);
|
|||
|
|
history.push(url);
|
|||
|
|
histIdx = history.length - 1;
|
|||
|
|
}
|
|||
|
|
updateNavButtons();
|
|||
|
|
|
|||
|
|
const viewport = document.getElementById('browser-viewport');
|
|||
|
|
if (!viewport) return;
|
|||
|
|
|
|||
|
|
// ── External URL → iframe ──────────────────────────────
|
|||
|
|
if (isExternal(url)) {
|
|||
|
|
viewport.classList.add('browser-viewport-external');
|
|||
|
|
viewport.innerHTML =
|
|||
|
|
`<iframe class="browser-iframe"
|
|||
|
|
src="${url.replace(/"/g, '"')}"
|
|||
|
|
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
|
|||
|
|
allowfullscreen></iframe>`;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── IC internal page ───────────────────────────────────
|
|||
|
|
viewport.classList.remove('browser-viewport-external');
|
|||
|
|
const slug = url.replace(/^ic:\/\//i, '').replace(/\/$/, '').trim() || 'home';
|
|||
|
|
if (bar) bar.value = 'ic://' + slug;
|
|||
|
|
|
|||
|
|
viewport.innerHTML = '<div class="empty-state"><div class="empty-icon">⏳</div><p>Loading…</p></div>';
|
|||
|
|
|
|||
|
|
if (PageTemplates[slug]) {
|
|||
|
|
viewport.innerHTML = PageTemplates[slug]();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ── Von Spielern gepflegte Seiten (ic-web) ─────────────
|
|||
|
|
// Erkennbar an der Domänenform name.endung, optional mit Unterseite.
|
|||
|
|
// Vorrang haben die fest eingebauten Vorlagen oben.
|
|||
|
|
const parts = slug.split('/');
|
|||
|
|
if (/^[a-z0-9\-]+\.[a-z]{2,}$/i.test(parts[0]) && typeof ICWebNet !== 'undefined') {
|
|||
|
|
const res = await ICWebNet.call('getSite', parts[0].toLowerCase(), parts[1] || 'home');
|
|||
|
|
if (res && res.ok && res.data) {
|
|||
|
|
// Kein innerHTML: der Inhalt stammt von Spielern und wird als
|
|||
|
|
// DOM-Knoten aufgebaut, damit Markup darin niemals ausgeführt wird.
|
|||
|
|
viewport.replaceChildren(ICWebRender.renderPage(res.data));
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
viewport.replaceChildren(renderICError(parts[0], res && res.error));
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const resp = await fetchNui('getBrowserPage', { slug });
|
|||
|
|
if (resp && resp.found && resp.page) {
|
|||
|
|
const p = resp.page;
|
|||
|
|
if (p.content && PageTemplates[p.content]) {
|
|||
|
|
viewport.innerHTML = PageTemplates[p.content]();
|
|||
|
|
} else {
|
|||
|
|
viewport.innerHTML = `<div class="ic-page fade-in"><h1>${p.title || slug}</h1><p>This page has no content yet.</p></div>`;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
viewport.innerHTML = render404(slug);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function goBack() {
|
|||
|
|
if (histIdx > 0) {
|
|||
|
|
histIdx--;
|
|||
|
|
navigate(history[histIdx]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function goForward() {
|
|||
|
|
if (histIdx < history.length - 1) {
|
|||
|
|
histIdx++;
|
|||
|
|
navigate(history[histIdx]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function updateNavButtons() {
|
|||
|
|
const b = document.getElementById('browser-back');
|
|||
|
|
const f = document.getElementById('browser-fwd');
|
|||
|
|
if (b) b.style.opacity = histIdx > 0 ? '1' : '0.35';
|
|||
|
|
if (f) f.style.opacity = histIdx < history.length - 1 ? '1' : '0.35';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Open window ────────────────────────────────────────── */
|
|||
|
|
function open(startUrl) {
|
|||
|
|
const el = WindowManager.create({
|
|||
|
|
id: WIN_ID,
|
|||
|
|
title: 'Browser',
|
|||
|
|
icon: '🌐',
|
|||
|
|
width: 900,
|
|||
|
|
height: 600,
|
|||
|
|
content: `
|
|||
|
|
<div class="browser-chrome">
|
|||
|
|
<button class="browser-nav-btn" id="browser-back" onclick="BrowserApp.goBack()" title="Zurück">◀</button>
|
|||
|
|
<button class="browser-nav-btn" id="browser-fwd" onclick="BrowserApp.goForward()" title="Vor">▶</button>
|
|||
|
|
<button class="browser-nav-btn" id="browser-refresh" onclick="BrowserApp.refresh()" title="Neu laden">↺</button>
|
|||
|
|
<input id="browser-address" class="browser-address-input" type="text"
|
|||
|
|
placeholder="ic://home oder https://…"
|
|||
|
|
onkeydown="if(event.key==='Enter')BrowserApp.navigate(this.value)"/>
|
|||
|
|
<button class="browser-nav-btn browser-go-btn"
|
|||
|
|
onclick="BrowserApp.navigate(document.getElementById('browser-address').value)"
|
|||
|
|
title="Aufrufen">→</button>
|
|||
|
|
</div>
|
|||
|
|
<div class="browser-viewport fade-in" id="browser-viewport"></div>
|
|||
|
|
`,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!el) return;
|
|||
|
|
navigate(startUrl || 'ic://home');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function refresh() {
|
|||
|
|
if (histIdx >= 0) navigate(history[histIdx]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { open, navigate, goBack, goForward, refresh };
|
|||
|
|
})();
|