From b8c330ed3ae2706912b97dc6a5877a79b16c41d0 Mon Sep 17 00:00:00 2001 From: Melvin Carvalho Date: Thu, 11 Jun 2026 02:16:43 +0200 Subject: [PATCH] Design: SocialMediaPosting tweet card layout --- app.css | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app.js | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) diff --git a/app.css b/app.css index bc52bfe..591674d 100644 --- a/app.css +++ b/app.css @@ -3176,6 +3176,110 @@ footer a:hover { color: var(--accent-2); } } + +/* ── Per-type bespoke: SocialMediaPosting — tweet card ── */ + +.card.tweet-card { background: #fff; border-color: rgba(0,0,0,.08); } +.card.tweet-card .hd.hidden { display: none; } +.card.tweet-card .bd { padding: 1.4rem 1.6rem; background: #fff; } +.card.tweet-card .bd::before { display: none; } + +.tweet-header { + display: flex; + align-items: center; + gap: .75rem; + margin-bottom: 1rem; +} +.tweet-avatar { + width: 44px; + height: 44px; + border-radius: 50%; + background: #0ea5e9; + color: #fff; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; + font-weight: 700; + flex-shrink: 0; +} +.tweet-name-block { + display: flex; + flex-direction: column; + min-width: 0; +} +.tweet-author-name { + font-weight: 700; + font-size: .95rem; + color: #111; + line-height: 1.2; +} +.tweet-handle { + font-size: .8rem; + color: #888; +} +.tweet-icon-badge { + margin-left: auto; + font-size: 1.1rem; + opacity: .4; +} + +.tweet-body { + font-size: 1.15rem; + line-height: 1.55; + color: #111; + margin-bottom: .8rem; + white-space: pre-wrap; + word-wrap: break-word; +} + +.tweet-media { + border-radius: .75rem; + overflow: hidden; + margin-bottom: .6rem; +} +.tweet-media img { + width: 100%; + display: block; +} + +.tweet-time { + font-size: .78rem; + color: #999; + margin-bottom: .8rem; +} + +.tweet-actions { + display: flex; + gap: 2rem; + padding-top: .6rem; + border-top: 1px solid #eee; +} +.tweet-action { + display: flex; + align-items: center; + gap: .3rem; + cursor: default; +} +.tweet-action-icon { + font-size: 1.1rem; + opacity: .45; + transition: opacity .15s; +} +.tweet-action:hover .tweet-action-icon { opacity: .8; } + +/* Dark mode tweet */ +@media (prefers-color-scheme: dark) { + .card.tweet-card { background: #1c1c1e; border-color: rgba(255,255,255,.06); } + .card.tweet-card .bd { background: #1c1c1e; } + .tweet-avatar { background: #0c7bb3; } + .tweet-author-name { color: #f5f5f5; } + .tweet-handle { color: #666; } + .tweet-body { color: #e8e8e8; } + .tweet-time { color: #555; } + .tweet-actions { border-top-color: #333; } +} + /* ────────────────────────────────────────────── Responsive / Mobile — small-screen polish. Stacks label/value grid, scales header, diff --git a/app.js b/app.js index 845a93b..4e8b942 100644 --- a/app.js +++ b/app.js @@ -2121,6 +2121,96 @@ handled.add("priceCurrency"); handled.add("dateIssued"); } + // ── SOCIAL MEDIA POSTING (tweet card) ── + if (cfg.type === "SocialMediaPosting") { + const card = document.querySelector(".card"); + if (card) card.classList.add("tweet-card"); + + // Hide normal header + const hd = document.querySelector(".hd"); + if (hd) hd.classList.add("hidden"); + + // Remove generic hero if present + const heroMount = document.getElementById("hero-mount"); + if (heroMount) heroMount.querySelector(".hero")?.remove(); + + // Author header row: avatar + name/handle + const header = document.createElement("div"); header.className = "tweet-header"; + + // Avatar circle with initial + const authorRaw = data["author"]; + const authorName = (typeof authorRaw === "object" && authorRaw !== null) ? (authorRaw.name || "") : (authorRaw || ""); + const initial = authorName ? authorName.charAt(0).toUpperCase() : "?"; + const avatar = document.createElement("div"); avatar.className = "tweet-avatar"; + avatar.textContent = initial; + header.appendChild(avatar); + + // Name + handle + const nameBlock = document.createElement("div"); nameBlock.className = "tweet-name-block"; + const nameEl = document.createElement("span"); nameEl.className = "tweet-author-name"; + nameEl.textContent = authorName; + const handle = document.createElement("span"); handle.className = "tweet-handle"; + // Derive a pseudo-handle from the name + const pseudoHandle = authorName ? "@" + authorName.toLowerCase().replace(/\s+/g, ".").replace(/[^a-z0-9.]/g, "") : "@unknown"; + handle.textContent = pseudoHandle; + nameBlock.append(nameEl, handle); + header.appendChild(nameBlock); + + // Icon badge top-right + const iconBadge = document.createElement("span"); iconBadge.className = "tweet-icon-badge"; + iconBadge.textContent = cfg.icon || "💬"; + header.appendChild(iconBadge); + + view.appendChild(header); + + // Post body — large, tweet-style + const body = data["articleBody"]; + if (body) { + const postText = document.createElement("div"); postText.className = "tweet-body"; + postText.textContent = body; + view.appendChild(postText); + } + + // Image (if present) as inline media + const imgUrl = data["image"]; + if (imgUrl) { + const mediaWrap = document.createElement("div"); mediaWrap.className = "tweet-media"; + const img = document.createElement("img"); img.src = imgUrl; img.alt = data["name"] || ""; + img.loading = "lazy"; + mediaWrap.appendChild(img); + view.appendChild(mediaWrap); + } + + // Timestamp + const datePub = data["datePublished"]; + if (datePub) { + const d = new Date(datePub); + const formatted = isNaN(d.getTime()) ? datePub : d.toLocaleString("en-GB", { hour: "2-digit", minute: "2-digit", day: "numeric", month: "short", year: "numeric" }); + const timeEl = document.createElement("div"); timeEl.className = "tweet-time"; + timeEl.textContent = formatted; + view.appendChild(timeEl); + } + + // Action icons row (non-functional, decorative) + const actions = document.createElement("div"); actions.className = "tweet-actions"; + const acts = [ + { icon: "\u{1F4AC}", label: "Reply" }, + { icon: "\u{1F504}", label: "Repost" }, + { icon: "\u2764\uFE0F", label: "Like" }, + { icon: "\u{1F4E4}", label: "Share" } + ]; + acts.forEach(a => { + const btn = document.createElement("span"); btn.className = "tweet-action"; + btn.innerHTML = "" + a.icon + ""; + btn.setAttribute("title", a.label); + actions.appendChild(btn); + }); + view.appendChild(actions); + + handled.add("name"); handled.add("image"); handled.add("description"); + handled.add("articleBody"); handled.add("author"); handled.add("datePublished"); handled.add("url"); + } + return handled; }