feat(openclaw): add shopping, supermarket deals, geizhals, and ebay MCP tools

This commit is contained in:
Moritz Graf
2026-09-10 21:32:22 +02:00
parent 2f2f597a67
commit 1e81cdbead
4754 changed files with 1298747 additions and 896 deletions
@@ -0,0 +1,398 @@
<template>
<header class="retro-window hero-header">
<!-- Top System Window Titlebar -->
<div class="retro-window-header">
<div class="retro-window-title">
<span class="system-icon">🕹️</span>
<span>CITY TELEMETRY // GRABENGASSE 7</span>
</div>
<div class="retro-window-controls">
<span class="retro-btn-box">_</span>
<span class="retro-btn-box">□</span>
<span class="retro-btn-box">×</span>
</div>
</div>
<!-- Hero Content Board -->
<div class="retro-window-content hero-content">
<div class="hero-top-row">
<!-- Brand & Crest -->
<div class="hero-brand">
<div class="city-crest">
<span class="crest-icon">🏛️</span>
</div>
<div class="brand-text">
<h1 class="brand-title">GRABENGASSE 7</h1>
<div class="brand-meta">
<span class="meta-tag">REGENSBURG</span>
<span class="meta-separator">•</span>
<span class="meta-tag">ELEV 342M</span>
<span class="meta-separator">•</span>
<span class="meta-tag">WS-90</span>
</div>
</div>
</div>
<!-- Action / Controls Panel -->
<div class="hero-actions">
<!-- Live Station Badge -->
<div class="retro-badge badge-green action-badge">
<span class="retro-blinker"></span>
<span>WS-90 LIVE</span>
</div>
<!-- Theme Toggle (Dark / Light) -->
<button
class="retro-btn theme-toggle"
@click="toggleTheme"
:title="isDark ? 'Switch to 16-Bit Bright Theme (Amiga/SNES Day)' : 'Switch to 16-Bit Dark Theme (CRT Night)'"
>
<span>{{ isDark ? '☀️' : '🌙' }}</span>
<span>{{ isDark ? 'DAY' : 'NIGHT' }}</span>
</button>
<!-- CRT Scanline Toggle -->
<button
class="retro-btn crt-toggle"
:class="{ active: crtEnabled }"
@click="toggleCrt"
title="Toggle Retro CRT Scanline Filter"
>
<span>📺</span>
<span>{{ crtEnabled ? 'CRT ON' : 'CRT OFF' }}</span>
</button>
<!-- PWA Install App Button (When installable or on mobile) -->
<button
class="retro-btn install-btn"
:class="{ 'btn-accent': installable }"
@click="handleInstallClick"
title="Install Grabengasse 7 as a native Android/Apple Home Screen App"
>
<span>📲</span>
<span>INSTALL</span>
</button>
<!-- Manual Refresh -->
<button class="retro-btn sync-btn" @click="$emit('refresh')" title="Poll latest metrics">
<span>🔄</span>
<span>SYNC</span>
</button>
</div>
</div>
<!-- iOS / Safari Installation Guide Banner (if clicked on iOS) -->
<div v-if="showIosGuide" class="retro-well ios-install-well">
<div class="ios-guide-header">
<span>🍎 INSTALL ON APPLE iOS / SAFARI:</span>
<button class="retro-btn-box" @click="showIosGuide = false">×</button>
</div>
<div class="ios-guide-body">
<span>1. Tap the <strong>Share button (⎋)</strong> in Safari</span>
<span>2. Scroll and select <strong>'Add to Home Screen (⊞)'</strong></span>
<span>3. Tap <strong>'Add'</strong> to launch as a standalone app!</span>
</div>
</div>
<!-- Animated SimCity Weather Advisory Ticker -->
<div class="retro-well ticker-well">
<div class="ticker-label">NEWS:</div>
<div class="ticker-container">
<div class="ticker-text">
<span>*** GRABENGASSE 7 WEATHER REPORT: METEOROLOGICAL TELEMETRY ACTIVE ***</span>
<span class="ticker-item">STATION: SHELLY WS90 (PROMETHEUS)</span>
<span class="ticker-item">LOCATION: REGENSBURG HISTORIC DISTRICT</span>
<span class="ticker-item">PWA READY: INSTALLABLE AS STANDALONE ANDROID & APPLE APP</span>
<span class="ticker-item">LAST SYNC: {{ lastUpdated ? formatTime(lastUpdated) : 'CONNECTING...' }}</span>
<span class="ticker-item">ADVISORY: CLICK ANY METRIC FOR 16-BIT HISTORICAL GRAPH INSPECTOR</span>
</div>
</div>
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
defineProps<{
lastUpdated?: string
}>()
const emit = defineEmits<{
(e: 'refresh'): void
(e: 'theme-changed', theme: 'dark' | 'light'): void
(e: 'crt-changed', enabled: boolean): void
}>()
const isDark = ref(true)
const crtEnabled = ref(true)
const installable = ref(false)
const showIosGuide = ref(false)
let deferredPrompt: any = null
function formatTime(isoString?: string): string {
if (!isoString) return ''
try {
const date = new Date(isoString)
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })
} catch {
return isoString
}
}
function toggleTheme() {
isDark.value = !isDark.value
const theme = isDark.value ? 'dark' : 'light'
document.documentElement.setAttribute('data-theme', theme)
localStorage.setItem('grabengasse_theme', theme)
emit('theme-changed', theme)
}
function toggleCrt() {
crtEnabled.value = !crtEnabled.value
localStorage.setItem('grabengasse_crt', crtEnabled.value ? 'true' : 'false')
emit('crt-changed', crtEnabled.value)
}
async function handleInstallClick() {
if (deferredPrompt) {
deferredPrompt.prompt()
const { outcome } = await deferredPrompt.userChoice
if (outcome === 'accepted') {
installable.value = false
deferredPrompt = null
}
} else {
// Check if on iOS or desktop where native prompt isn't supported directly
const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream
if (isIos) {
showIosGuide.value = !showIosGuide.value
} else {
alert("To install this app on your device:\n\n• Android: Tap the browser menu (⋮) -> 'Install App' or 'Add to Home screen'\n• iPhone/iPad: Tap Share (⎋) -> 'Add to Home Screen (⊞)'\n• Chrome/Edge: Click the install icon in the URL bar (⊕)")
}
}
}
onMounted(() => {
const savedTheme = localStorage.getItem('grabengasse_theme') || 'dark'
isDark.value = savedTheme === 'dark'
document.documentElement.setAttribute('data-theme', savedTheme)
const savedCrt = localStorage.getItem('grabengasse_crt')
if (savedCrt !== null) {
crtEnabled.value = savedCrt === 'true'
}
// Listen for PWA beforeinstallprompt on Android / Chrome
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()
deferredPrompt = e
installable.value = true
})
window.addEventListener('appinstalled', () => {
installable.value = false
deferredPrompt = null
})
})
</script>
<style scoped>
.hero-header {
margin-top: 0.25rem;
}
.hero-content {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.hero-top-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
flex-wrap: wrap;
}
.hero-brand {
display: flex;
align-items: center;
gap: 0.75rem;
}
.city-crest {
width: 42px;
height: 42px;
min-width: 42px;
background: var(--retro-window-inner);
border-top: 2px solid var(--retro-bevel-dark);
border-left: 2px solid var(--retro-bevel-dark);
border-right: 2px solid var(--retro-bevel-light);
border-bottom: 2px solid var(--retro-bevel-light);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.4rem;
flex-shrink: 0;
}
.brand-text {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.brand-title {
font-family: var(--font-pixel);
font-size: clamp(0.95rem, 3.8vw, 1.35rem);
color: var(--retro-accent-amber);
letter-spacing: 0.03em;
text-shadow: 2px 2px 0px rgba(0, 0, 0, 0.5);
line-height: 1.2;
}
.brand-meta {
display: flex;
align-items: center;
gap: 0.35rem;
font-family: var(--font-ui);
font-size: clamp(0.55rem, 1.8vw, 0.65rem);
color: var(--retro-text-dim);
text-transform: uppercase;
flex-wrap: wrap;
}
.meta-tag {
color: var(--retro-accent-cyan);
}
.meta-separator {
color: var(--retro-text-muted);
}
.hero-actions {
display: flex;
align-items: center;
gap: 0.45rem;
flex-wrap: wrap;
}
.action-badge {
padding: 0.35rem 0.55rem;
}
/* iOS Guide */
.ios-install-well {
display: flex;
flex-direction: column;
gap: 0.4rem;
padding: 0.55rem 0.75rem;
border-color: #0c4a6e #38bdf8 #38bdf8 #0c4a6e;
background: var(--retro-window-inner);
}
.ios-guide-header {
display: flex;
align-items: center;
justify-content: space-between;
font-family: var(--font-pixel);
font-size: 0.65rem;
color: var(--retro-accent-cyan);
}
.ios-guide-body {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-family: var(--font-mono);
font-size: 0.7rem;
color: var(--retro-text-main);
}
/* Ticker */
.ticker-well {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.35rem 0.55rem;
overflow: hidden;
}
.ticker-label {
font-family: var(--font-pixel);
font-size: clamp(0.55rem, 2vw, 0.65rem);
color: var(--retro-accent-red);
background: var(--retro-window-bg);
padding: 0.15rem 0.35rem;
border-top: 1px solid var(--retro-bevel-light);
border-left: 1px solid var(--retro-bevel-light);
border-right: 1px solid var(--retro-bevel-dark);
border-bottom: 1px solid var(--retro-bevel-dark);
white-space: nowrap;
flex-shrink: 0;
}
.ticker-container {
overflow: hidden;
white-space: nowrap;
flex-grow: 1;
}
.ticker-text {
display: inline-block;
white-space: nowrap;
font-family: var(--font-mono);
font-size: clamp(0.68rem, 2.2vw, 0.75rem);
color: var(--retro-text-main);
animation: tickerSlide 25s linear infinite;
}
.ticker-item {
margin-left: 1.5rem;
color: var(--retro-accent-amber);
}
@keyframes tickerSlide {
0% { transform: translateX(100%); }
100% { transform: translateX(-100%); }
}
@media (max-width: 640px) {
.hero-top-row {
flex-direction: column;
align-items: stretch;
gap: 0.65rem;
}
.city-crest {
width: 36px;
height: 36px;
min-width: 36px;
font-size: 1.2rem;
}
/* Clean 2x2 Grid of Actions on Mobile for balanced layout */
.hero-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.35rem;
width: 100%;
}
.hero-actions .action-badge {
grid-column: span 2;
justify-content: center;
padding: 0.35rem;
}
.hero-actions .retro-btn {
width: 100%;
justify-content: center;
padding: 0.4rem 0.3rem;
font-size: 0.65rem;
}
}
</style>