Startseiten/Feeds/Kategorie/Rechtsseiten ueberarbeitet, Kontakt-Modal durch schwebendes contact-launcher-Widget ersetzt, legal-language-notice ergaenzt; Platzhalter-SVGs + Theme-CSS angepasst. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
107 lines
6.3 KiB
PHP
107 lines
6.3 KiB
PHP
@php
|
|
// Selbstversorgend, damit der Ticker auf jeder Unterseite gefüllt ist
|
|
// (nicht nur auf der Startseite): jüngste PMs des Portals, kurz gecacht.
|
|
$items = app(\App\Services\Web\TickerData::class)
|
|
->items(\App\Support\WebPortalContext::portalFromHost(), app()->getLocale());
|
|
|
|
// Echte Kursdaten aus dem Cache (market:refresh); leer = Slot ausblenden.
|
|
$marketSlides = app(\App\Services\Web\MarketDataService::class)->slides();
|
|
@endphp
|
|
|
|
@if (! empty($items) || ! empty($marketSlides))
|
|
<div class="bg-topbar-grad text-ink-on-dark" aria-label="{{ __('web.ticker.aria_label') }}">
|
|
<div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 py-2.5 flex items-center gap-3 sm:gap-5 text-[12.5px]">
|
|
@if (! empty($items))
|
|
<div class="flex items-center gap-3 sm:gap-4 overflow-hidden min-w-0 flex-1">
|
|
<span class="flex items-center gap-1.5 text-accent-warm font-bold text-[10.5px] sm:text-[11px] tracking-[0.15em] flex-shrink-0">
|
|
<span class="w-[7px] h-[7px] rounded-full bg-accent-warm pulse-dot"></span>
|
|
AD-HOC
|
|
</span>
|
|
|
|
<span class="sr-only">
|
|
{{ __('web.ticker.sr_intro') }}
|
|
@foreach ($items as $item)
|
|
@if (! empty($item['href']))
|
|
<a href="{{ $item['href'] }}">{{ $item['time'] }} {{ $item['text'] }}.</a>
|
|
@else
|
|
{{ $item['time'] }} {{ $item['text'] }}.
|
|
@endif
|
|
@endforeach
|
|
</span>
|
|
|
|
<div class="overflow-hidden min-w-0 flex-1" aria-hidden="true">
|
|
<div class="ticker-marquee-track text-[12px] sm:text-[12.5px] whitespace-nowrap">
|
|
{{-- tabindex=-1: Duplikate im aria-hidden-Marquee dürfen keinen Fokus erhalten,
|
|
die zugängliche Variante steht im sr-only-Block darüber. --}}
|
|
@foreach (array_merge($items, $items) as $item)
|
|
@if (! empty($item['href']))
|
|
<a href="{{ $item['href'] }}" tabindex="-1"
|
|
class="inline-flex shrink-0 gap-2 items-baseline cursor-pointer group/ticker">
|
|
<span class="font-mono text-[11px] text-ink-on-dark-muted">{{ $item['time'] }}</span>
|
|
<span class="text-ink-on-dark group-hover/ticker:text-brand group-hover/ticker:underline underline-offset-2 transition-colors">{{ $item['text'] }}</span>
|
|
</a>
|
|
@else
|
|
<span class="inline-flex shrink-0 gap-2 items-baseline">
|
|
<span class="font-mono text-[11px] text-ink-on-dark-muted">{{ $item['time'] }}</span>
|
|
<span class="text-ink-on-dark">{{ $item['text'] }}</span>
|
|
</span>
|
|
@endif
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@else
|
|
<span class="flex-1"></span>
|
|
@endif
|
|
|
|
@if (! empty($marketSlides))
|
|
<span class="hidden sm:inline-block w-px h-[18px] bg-ink-on-dark-rule flex-shrink-0"></span>
|
|
|
|
{{-- Kein position:absolute: sonst null Breite im Fluss, bis Layout kollabiert --}}
|
|
<div class="shrink-0 flex justify-end items-center min-w-0 max-w-[min(92vw,280px)] sm:max-w-[min(42vw,280px)]"
|
|
x-data="{
|
|
slides: @js($marketSlides),
|
|
i: 0,
|
|
init() {
|
|
if (this.slides.length < 2) {
|
|
return;
|
|
}
|
|
window.setInterval(() => {
|
|
this.i = (this.i + 1) % this.slides.length;
|
|
}, 5200);
|
|
},
|
|
}"
|
|
aria-live="polite">
|
|
<div class="grid min-h-[22px] justify-items-end overflow-hidden [grid-template-columns:minmax(0,max-content)]">
|
|
<template x-for="(slide, idx) in slides" :key="idx">
|
|
<div x-show="i === idx"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 -translate-y-full"
|
|
x-transition:enter-end="opacity-100 translate-y-0"
|
|
x-transition:leave="transition ease-in duration-280"
|
|
x-transition:leave-start="opacity-100 translate-y-0"
|
|
x-transition:leave-end="opacity-0 translate-y-full"
|
|
class="col-start-1 row-start-1 flex items-center gap-2 whitespace-nowrap">
|
|
<span class="text-ink-on-dark-muted font-semibold text-[10.5px] tracking-wider" x-text="slide.label"></span>
|
|
<span class="text-ink-on-dark font-mono text-[12.5px]" x-text="slide.value"></span>
|
|
<span class="font-semibold text-[11.5px] font-mono"
|
|
:class="(slide.positive ?? true) ? 'text-gain' : 'text-loss'"
|
|
x-text="slide.delta"></span>
|
|
{{-- Dekorativer Richtungspfeil (auf/ab je Vorzeichen, keine echte Kurve) --}}
|
|
<svg width="26" height="10" viewBox="0 0 26 10"
|
|
class="hidden sm:inline-block shrink-0"
|
|
:class="(slide.positive ?? true) ? 'text-gain' : 'text-loss'"
|
|
aria-hidden="true">
|
|
<path x-show="slide.positive ?? true" d="M0,8 L4,7 L8,5 L12,6 L16,4 L20,3 L24,2"
|
|
fill="none" stroke="currentColor" stroke-width="1.2" />
|
|
<path x-show="!(slide.positive ?? true)" d="M0,2 L4,3 L8,5 L12,4 L16,6 L20,7 L24,8"
|
|
fill="none" stroke="currentColor" stroke-width="1.2" />
|
|
</svg>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endif
|