fix: improve rcal/rtube/rpubs demo rendering and mobile CSS

rCal: add mobile responsive breakpoints, event times with colored
borders, and Today button. rTube: auto-select first video, hide Live
Stream tab in demo, show duration/date metadata. rPubs: fix mobile
layout height calc and toolbar stacking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-28 05:57:43 +00:00
parent f9a36b9d3e
commit f1e90924c0
3 changed files with 68 additions and 16 deletions

View File

@ -186,7 +186,9 @@ class FolkCalendarView extends HTMLElement {
.moon { font-size: 10px; opacity: 0.7; }
.event-dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; margin: 1px; }
.event-dots { display: flex; flex-wrap: wrap; gap: 1px; }
.event-label { font-size: 9px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #aaa; line-height: 1.4; }
.event-label { font-size: 9px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #aaa; line-height: 1.4; padding: 1px 3px; border-radius: 3px; cursor: pointer; }
.event-label:hover { background: rgba(255,255,255,0.08); }
.event-time { color: #666; font-size: 8px; margin-right: 2px; }
.event-modal {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
@ -199,12 +201,30 @@ class FolkCalendarView extends HTMLElement {
.sources { display: flex; gap: 6px; margin-bottom: 12px; flex-wrap: wrap; }
.source-badge { font-size: 10px; padding: 3px 8px; border-radius: 10px; border: 1px solid #333; }
@media (max-width: 768px) {
.day { min-height: 56px; padding: 4px; }
.day-num { font-size: 11px; }
.event-label { display: none; }
.event-dot { width: 5px; height: 5px; }
.moon { font-size: 8px; }
.rapp-nav__title { font-size: 13px; }
}
@media (max-width: 480px) {
.day { min-height: 44px; padding: 3px; }
.day-num { font-size: 10px; }
.weekday { font-size: 9px; padding: 2px; }
.rapp-nav { gap: 4px; }
.toggle-btn { padding: 3px 6px; font-size: 10px; }
.source-badge { font-size: 8px; padding: 2px 6px; }
}
</style>
${this.error ? `<div style="color:#ef5350;text-align:center;padding:8px">${this.esc(this.error)}</div>` : ""}
<div class="rapp-nav">
<button class="rapp-nav__back" id="prev"></button>
<button class="toggle-btn" id="today">Today</button>
<span class="rapp-nav__title">${monthName} ${year}</span>
<button class="toggle-btn ${this.showLunar ? "active" : ""}" id="toggle-lunar">🌙 Lunar</button>
<button class="rapp-nav__back" id="next"></button>
@ -255,7 +275,11 @@ class FolkCalendarView extends HTMLElement {
${dayEvents.slice(0, 3).map(e => `<span class="event-dot" style="background:${e.source_color || "#6366f1"}"></span>`).join("")}
${dayEvents.length > 3 ? `<span style="font-size:9px;color:#888">+${dayEvents.length - 3}</span>` : ""}
</div>
${dayEvents.slice(0, 2).map(e => `<div class="event-label" data-event='${JSON.stringify({ id: e.id })}'>${this.esc(e.title)}</div>`).join("")}
${dayEvents.slice(0, 2).map(e => {
const t = new Date(e.start_time);
const timeStr = `${t.getHours()}:${String(t.getMinutes()).padStart(2, "0")}`;
return `<div class="event-label" style="border-left:2px solid ${e.source_color || "#6366f1"}" data-event='${JSON.stringify({ id: e.id })}'><span class="event-time">${timeStr}</span>${this.esc(e.title)}</div>`;
}).join("")}
` : ""}
</div>`;
}
@ -290,6 +314,10 @@ class FolkCalendarView extends HTMLElement {
private attachListeners() {
this.shadow.getElementById("prev")?.addEventListener("click", () => this.navigate(-1));
this.shadow.getElementById("next")?.addEventListener("click", () => this.navigate(1));
this.shadow.getElementById("today")?.addEventListener("click", () => {
this.currentDate = new Date();
if (this.space === "demo") { this.loadDemoData(); } else { this.loadMonth(); }
});
this.shadow.getElementById("toggle-lunar")?.addEventListener("click", () => {
this.showLunar = !this.showLunar;
this.render();

View File

@ -481,14 +481,27 @@ export class FolkPubsEditor extends HTMLElement {
.format-detail .pages { color: #60a5fa; }
@media (max-width: 768px) {
.editor-layout { flex-direction: column; }
:host { height: auto; min-height: calc(100vh - 92px); }
.editor-layout { flex-direction: column; height: auto; }
.editor-main { min-height: 0; }
.sidebar {
width: 100%;
border-left: none;
border-top: 1px solid #1e293b;
max-height: 50vh;
max-height: none;
padding: 0.75rem;
}
.content-area { min-height: 40vh; }
.content-area { min-height: 45vh; }
.toolbar-left { flex-direction: column; gap: 0.375rem; }
.title-input, .author-input { max-width: 100%; flex: 1; }
.editor-toolbar { gap: 0.5rem; }
.format-grid { grid-template-columns: repeat(3, 1fr); }
.btn-generate { font-size: 0.8rem; padding: 0.5rem; }
}
@media (max-width: 480px) {
.format-grid { grid-template-columns: 1fr 1fr; }
.toolbar-right { width: 100%; }
.btn-sample, .btn-upload { flex: 1; text-align: center; }
}
</style>`;
}

View File

@ -8,7 +8,7 @@
class FolkVideoPlayer extends HTMLElement {
private shadow: ShadowRoot;
private space = "demo";
private videos: Array<{ name: string; size: number }> = [];
private videos: Array<{ name: string; size: number; duration?: string; date?: string }> = [];
private currentVideo: string | null = null;
private mode: "library" | "live" = "library";
private streamKey = "";
@ -29,13 +29,14 @@ class FolkVideoPlayer extends HTMLElement {
private loadDemoData() {
this.isDemo = true;
this.videos = [
{ name: "community-meeting-2026-02-15.mp4", size: 524288000 },
{ name: "rspace-demo-walkthrough.mp4", size: 157286400 },
{ name: "design-sprint-day1.webm", size: 892108800 },
{ name: "interview-cosmolocal-founders.mp4", size: 1073741824 },
{ name: "workshop-local-first-data.mp4", size: 734003200 },
{ name: "lightning-talks-feb2026.webm", size: 445644800 },
{ name: "community-meeting-2026-02-15.mp4", size: 524288000, duration: "1:23:45", date: "Feb 15, 2026" },
{ name: "rspace-demo-walkthrough.mp4", size: 157286400, duration: "18:32", date: "Feb 12, 2026" },
{ name: "design-sprint-day1.webm", size: 892108800, duration: "2:45:10", date: "Feb 10, 2026" },
{ name: "interview-cosmolocal-founders.mp4", size: 1073741824, duration: "52:18", date: "Feb 7, 2026" },
{ name: "workshop-local-first-data.mp4", size: 734003200, duration: "1:35:42", date: "Feb 3, 2026" },
{ name: "lightning-talks-feb2026.webm", size: 445644800, duration: "42:15", date: "Jan 28, 2026" },
];
this.currentVideo = this.videos[0].name;
this.render();
}
@ -72,7 +73,7 @@ class FolkVideoPlayer extends HTMLElement {
:host { display: block; min-height: 60vh; font-family: system-ui, sans-serif; color: #e2e8f0; }
.container { max-width: 1200px; margin: 0 auto; }
.rapp-nav { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; min-height: 36px; }
.rapp-nav__title { font-size: 15px; font-weight: 600; color: #e2e8f0; flex: 1; }
.rapp-nav__title { font-size: 13px; font-weight: 500; color: #64748b; flex: 1; text-align: right; }
.tab { padding: 0.5rem 1.25rem; border-radius: 8px; border: 1px solid #334155; background: transparent; color: #94a3b8; cursor: pointer; font-size: 0.875rem; }
.tab.active { background: #ef4444; color: white; border-color: #ef4444; }
.layout { display: grid; grid-template-columns: 300px 1fr; gap: 1.5rem; }
@ -109,7 +110,8 @@ class FolkVideoPlayer extends HTMLElement {
<div class="container">
<div class="rapp-nav">
<button class="tab ${this.mode === "library" ? "active" : ""}" data-mode="library">Video Library</button>
<button class="tab ${this.mode === "live" ? "active" : ""}" data-mode="live">Live Stream</button>
${!this.isDemo ? `<button class="tab ${this.mode === "live" ? "active" : ""}" data-mode="live">Live Stream</button>` : ""}
<span class="rapp-nav__title">${this.isDemo ? `${this.videos.length} recordings` : ""}</span>
</div>
${this.mode === "library" ? this.renderLibrary() : this.renderLive()}
</div>
@ -129,7 +131,7 @@ class FolkVideoPlayer extends HTMLElement {
: filteredVideos.map((v) => `
<div class="video-item ${this.currentVideo === v.name ? "active" : ""}" data-name="${v.name}">
<div class="video-name">${v.name}</div>
<div class="video-meta">${this.getExtension(v.name).toUpperCase()} &middot; ${this.formatSize(v.size)}</div>
<div class="video-meta">${this.getExtension(v.name).toUpperCase()} &middot; ${this.formatSize(v.size)}${v.duration ? ` &middot; ${v.duration}` : ""}${v.date ? `<br>${v.date}` : ""}</div>
</div>
`).join("");
@ -141,7 +143,16 @@ class FolkVideoPlayer extends HTMLElement {
} else if (this.isDemo) {
const selectedVideo = this.videos.find(v => v.name === this.currentVideo);
const sizeStr = selectedVideo ? this.formatSize(selectedVideo.size) : "";
player = `<div class="placeholder"><div class="placeholder-icon">&#127910;</div><p style="font-size:1rem;font-weight:500;margin-bottom:0.5rem">${this.currentVideo}</p><p style="font-size:0.85rem;color:#64748b">${this.getExtension(this.currentVideo).toUpperCase()} &middot; ${sizeStr}</p><p style="font-size:0.75rem;color:#475569;margin-top:1rem">Demo mode &mdash; no actual video file</p></div>`;
const durStr = selectedVideo?.duration || "";
player = `<div class="placeholder" style="padding:2rem">
<div style="width:80px;height:80px;border-radius:50%;background:rgba(239,68,68,0.15);display:flex;align-items:center;justify-content:center;margin:0 auto 1.5rem">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
</div>
<p style="font-size:1.1rem;font-weight:600;margin-bottom:0.5rem;color:#e2e8f0">${this.currentVideo}</p>
<p style="font-size:0.85rem;color:#94a3b8">${this.getExtension(this.currentVideo).toUpperCase()} &middot; ${sizeStr}${durStr ? ` &middot; ${durStr}` : ""}</p>
${selectedVideo?.date ? `<p style="font-size:0.8rem;color:#64748b;margin-top:0.5rem">Recorded ${selectedVideo.date}</p>` : ""}
<p style="font-size:0.7rem;color:#475569;margin-top:1.5rem;opacity:0.7">Demo preview &mdash; connect rTube to stream real video</p>
</div>`;
} else if (!this.isPlayable(this.currentVideo)) {
player = `<div class="placeholder"><div class="placeholder-icon">&#9888;&#65039;</div><p><strong>${this.getExtension(this.currentVideo).toUpperCase()}</strong> files cannot play in browsers</p><p style="font-size:0.8rem;color:#475569;margin-top:0.5rem">Download to play locally</p></div>`;
} else {