video kartice v mrezi, oglas le v vsako drugo vrstico in v visini kartice, vremenski pripomocek z naslovnice (prosil je za lokacijo)
This commit is contained in:
@@ -153,12 +153,14 @@ export function ArticleCardAd() {
|
||||
if (filled === false) return null;
|
||||
return (
|
||||
<ArtistPatternBg className="rounded-md border border-card-border h-full bg-card" seed={seed}>
|
||||
{/* Meritev 31. 8. 2026: kartica clanka je visoka 399 px, fluid in-feed
|
||||
enota pa se je ustavila pri 248 in pod njo je ostala luknja. Navaden
|
||||
auto display z minimalno visino zapolni celo celico. */}
|
||||
<AdSense
|
||||
slot="3854634730"
|
||||
format="fluid"
|
||||
layoutKey="-6r+cy-10+8a-3"
|
||||
format="auto"
|
||||
responsive={false}
|
||||
style={{ display: "block" }}
|
||||
style={{ display: "block", minHeight: "360px" }}
|
||||
onAdStatus={setFilled}
|
||||
/>
|
||||
</ArtistPatternBg>
|
||||
|
||||
@@ -12,7 +12,6 @@ import Header from "@/components/header";
|
||||
import Footer from "@/components/footer";
|
||||
import AdSense, { ArticleCardAd, SidebarAd, PageSideAds, MultiplexAd, TopBannerAd } from "@/components/adsense";
|
||||
import { NewsWidget } from "@/components/news-widget";
|
||||
import { SidebarWeatherWidget } from "@/components/weather-widget";
|
||||
import { BreakingNewsWidget } from "@/components/breaking-news-widget";
|
||||
import PushPromptBanner from "@/components/push-prompt-banner";
|
||||
import { useState, useEffect, useCallback, useRef, useMemo, lazy, Suspense } from "react";
|
||||
@@ -332,6 +331,53 @@ function MobileMosaic({ latest, popular, focalPoints }: { latest: Article[]; pop
|
||||
);
|
||||
}
|
||||
|
||||
interface YtVideo { ytId: string; slug: string; shortTitle?: string; title: string; rubrik?: string; publishedAt?: string; durationS?: number }
|
||||
|
||||
// Video kartica v mrezi: enaka velikost kot kartica clanka, da v vrstici ni
|
||||
// luknje. Pelje na /video/<slug>, kjer je oglasni prostor, ne na YouTube.
|
||||
function VideoCard({ video }: { video: YtVideo }) {
|
||||
const mm = Math.floor((video.durationS || 0) / 60);
|
||||
const ss = String((video.durationS || 0) % 60).padStart(2, "0");
|
||||
return (
|
||||
<Link href={`/video/${video.slug}`}>
|
||||
<div className="relative group cursor-pointer rounded-lg overflow-hidden h-full bg-card border border-card-border flex flex-col" data-testid={`card-video-${video.ytId}`}>
|
||||
<div className="relative flex-shrink-0">
|
||||
<img
|
||||
src={`https://i.ytimg.com/vi/${video.ytId}/hqdefault.jpg`}
|
||||
alt={video.shortTitle || video.title}
|
||||
className="w-full aspect-video object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="w-10 h-10 rounded-full bg-primary/90 flex items-center justify-center group-hover:bg-primary transition-colors">
|
||||
<Play className="w-4 h-4 text-white ml-0.5" fill="white" />
|
||||
</div>
|
||||
</div>
|
||||
{video.durationS ? (
|
||||
<span className="absolute bottom-1.5 right-1.5 rounded bg-black/75 px-1.5 py-0.5 text-[10px] font-medium text-white">
|
||||
{mm}:{ss}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="p-3.5 flex flex-col flex-1">
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<span className="text-[10px] font-medium text-primary">VIDEO</span>
|
||||
{video.publishedAt && (
|
||||
<span className="text-muted-foreground text-[10px]">{timeAgo(new Date(video.publishedAt))}</span>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="font-semibold text-card-foreground text-sm leading-snug line-clamp-2 group-hover:text-primary transition-colors">
|
||||
{video.shortTitle || video.title}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground mt-1.5 leading-relaxed flex-1">
|
||||
Jetzt ansehen auf FOLX TV
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function MediumCard({ article, focalPoints }: { article: Article; focalPoints?: Record<string, { x: number; y: number }> }) {
|
||||
const isVideo = article.category === "Video";
|
||||
const objPos = getObjectPosition(article.coverImage, focalPoints);
|
||||
@@ -785,6 +831,12 @@ export default function Home() {
|
||||
queryKey: ["/api/articles/popular"],
|
||||
});
|
||||
|
||||
// Videi v mrezi: gledalca zadrzijo pri nas in odprejo se eno stran z oglasi.
|
||||
const { data: ytData } = useQuery<{ items: YtVideo[] }>({
|
||||
queryKey: ["/api/yt-videos?limit=12"],
|
||||
});
|
||||
const videos = ytData?.items || [];
|
||||
|
||||
const { data: galleryImages } = useQuery<GalleryImage[]>({
|
||||
queryKey: ["/api/gallery", "home"],
|
||||
queryFn: () => fetch("/api/gallery?limit=20").then((r) => r.json()),
|
||||
@@ -864,20 +916,29 @@ export default function Home() {
|
||||
}
|
||||
}
|
||||
|
||||
// Oglas gre le v vsako drugo vrstico. Meritev 31. 8. 2026: od 13 enot na
|
||||
// naslovnici jih je 9 vrnilo unfilled, torej je mest vec, kot jih Google
|
||||
// za to stran zapolni. Manj mest z visjo zapolnjenostjo prinese vec.
|
||||
let adCount = 0;
|
||||
let vidCount = 0;
|
||||
const totalRows = items.length / 4;
|
||||
for (let r = 0; r < totalRows; r++) {
|
||||
const rowStart = r * 4;
|
||||
const hasWidget = items.slice(rowStart, rowStart + 4).some((it) => it.type === "widget");
|
||||
if (!hasWidget) {
|
||||
// Enako kot na straneh rubrik: v prvi vrstici oglas ni prva celica.
|
||||
const pos = r === 0 ? 3 : adCount % 2 === 0 ? 0 : 3;
|
||||
if (hasWidget) continue;
|
||||
if (r % 2 === 1) {
|
||||
const pos = r === 1 ? 3 : adCount % 2 === 0 ? 0 : 3;
|
||||
items[rowStart + pos] = { type: "ad", key: `ad-row-${adCount++}` };
|
||||
} else if (r > 0 && videos.length) {
|
||||
// video namesto zadnjega clanka v vrstici brez oglasa
|
||||
const v = videos[vidCount % videos.length];
|
||||
vidCount += 1;
|
||||
items[rowStart + 2] = { type: "video", key: `v-${v.ytId}`, video: v };
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}, [spaced, widgets]);
|
||||
}, [spaced, widgets, videos]);
|
||||
|
||||
const NA_NASLOVNICI = 48;
|
||||
const gridRows: (typeof gridItems)[] = useMemo(() => {
|
||||
@@ -977,7 +1038,6 @@ export default function Home() {
|
||||
/>
|
||||
</div>
|
||||
<div className="lg:col-span-1 space-y-4 lg:flex lg:flex-col">
|
||||
<SidebarWeatherWidget />
|
||||
{articles && articles.length > 0 && <TopStoriesList articles={razmakniPoIzvajalcu([...articles].sort((a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime())).slice(0, 8)} className="lg:flex-1 lg:min-h-0 overflow-hidden" />}
|
||||
<SidebarAd />
|
||||
</div>
|
||||
@@ -992,6 +1052,8 @@ export default function Home() {
|
||||
? <div key={item.key}>{item.widget!.el}</div>
|
||||
: item.type === "ad"
|
||||
? <div key={item.key} className="h-full" data-testid={`ad-grid-${item.key}`}><ArticleCardAd /></div>
|
||||
: item.type === "video" && item.video
|
||||
? <VideoCard key={item.key} video={item.video} />
|
||||
: item.article
|
||||
? <MediumCard key={item.key} article={item.article} focalPoints={focalPoints} />
|
||||
: null
|
||||
|
||||
Reference in New Issue
Block a user