Add per-story views, trending, and reader reading-experience upgrades
Surface read counts and sharpen the reader: - story_views table + RecordStoryView on /api/article (background, filter-guarded); "Popular this week" home rail via TrendingStories; read-count badge and reading-time chip decorated onto every listing - reader: signed-in-only read-aloud (TTS), native share/copy, and an Aa typography popover (size/serif/sepia) persisted per device - real alt text on card/reader/related/search images; time-of-day Pete greeting on the home hero - harden exec() to skip (not panic) on a nil DB so background writes can't crash on a closed handle Tests: story_views_test.go, trending_test.go. Suite green, CSS rebuilt.
This commit is contained in:
@@ -232,6 +232,56 @@ html[data-phase="night"] {
|
||||
.pete-reader-btn:disabled { opacity: 0.35; cursor: default; transform: none; }
|
||||
.pete-reader-btn-open { background: var(--accent); border-color: transparent; color: #1c1305; text-decoration: none; }
|
||||
.pete-reader-btn-open:hover { background: var(--accent); filter: brightness(1.08); }
|
||||
/* Active/pressed toolbar toggle (Listen while speaking, Aa while menu open). */
|
||||
.pete-reader-btn[aria-pressed="true"],
|
||||
.pete-reader-btn[aria-expanded="true"] {
|
||||
background: var(--accent);
|
||||
border-color: transparent;
|
||||
color: #1c1305;
|
||||
}
|
||||
|
||||
/* Text-options popover, anchored under the Aa button in the reader bar. */
|
||||
.pete-reader-type { position: relative; display: inline-flex; }
|
||||
.pete-reader-typemenu {
|
||||
position: absolute;
|
||||
top: calc(100% + 0.5rem);
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
width: 15rem;
|
||||
padding: 0.75rem;
|
||||
border-radius: 1rem;
|
||||
background: var(--card);
|
||||
color: var(--ink);
|
||||
border: 2px solid rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 10px 30px rgba(20, 14, 6, 0.28);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
html[data-phase="night"] .pete-reader-typemenu { border-color: rgba(255, 255, 255, 0.12); }
|
||||
.pete-reader-typerow { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; }
|
||||
.pete-reader-typelabel { font-size: 0.8rem; font-weight: 700; opacity: 0.7; }
|
||||
.pete-reader-typebtns { display: inline-flex; gap: 0.25rem; }
|
||||
.pete-reader-typebtns button {
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0 0.55rem;
|
||||
border-radius: 0.6rem;
|
||||
background: rgba(20, 14, 6, 0.06);
|
||||
color: var(--ink);
|
||||
font-weight: 700;
|
||||
font-size: 0.82rem;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
html[data-phase="night"] .pete-reader-typebtns button { background: rgba(255, 255, 255, 0.08); }
|
||||
.pete-reader-typebtns button:hover { background: rgba(20, 14, 6, 0.12); }
|
||||
.pete-reader-typebtns button.is-active {
|
||||
background: var(--accent);
|
||||
color: #1c1305;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.pete-reader-scroll {
|
||||
flex: 1 1 auto;
|
||||
@@ -287,9 +337,33 @@ html[data-phase="night"] {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.pete-reader-body { font-size: 1.08rem; line-height: 1.75; }
|
||||
/* Reader typography, driven by the Aa popover and persisted per-device. The
|
||||
size lives as a CSS var on the scroll container; font + paper are classes. */
|
||||
.pete-reader-scroll { --reader-fs: 1.08rem; }
|
||||
.pete-reader-body { font-size: var(--reader-fs); line-height: 1.75; }
|
||||
.pete-reader-body p { margin-bottom: 1.05em; }
|
||||
.pete-reader-body p:last-child { margin-bottom: 0; }
|
||||
.pete-reader-scroll.is-size-s { --reader-fs: 0.98rem; }
|
||||
.pete-reader-scroll.is-size-m { --reader-fs: 1.08rem; }
|
||||
.pete-reader-scroll.is-size-l { --reader-fs: 1.22rem; }
|
||||
.pete-reader-scroll.is-size-xl { --reader-fs: 1.4rem; }
|
||||
.pete-reader-scroll.is-serif .pete-reader-body {
|
||||
font-family: Georgia, "Iowan Old Style", "Times New Roman", serif;
|
||||
}
|
||||
/* Sepia paper: warm background + ink regardless of day/night phase. */
|
||||
.pete-reader-scroll.is-sepia .pete-reader-article {
|
||||
background: #f6ecd6;
|
||||
color: #4a3a28;
|
||||
border-color: rgba(74, 58, 40, 0.16);
|
||||
}
|
||||
.pete-reader-scroll.is-sepia .pete-reader-note { border-top-color: rgba(74, 58, 40, 0.18); opacity: 0.8; }
|
||||
.pete-reader-scroll.is-sepia .pete-reader-hero { background: rgba(74, 58, 40, 0.08); }
|
||||
/* Currently-spoken paragraph highlight while reading aloud. */
|
||||
.pete-reader-body p.is-speaking {
|
||||
background: color-mix(in srgb, var(--accent) 32%, transparent);
|
||||
border-radius: 0.4rem;
|
||||
box-shadow: 0 0 0 0.35rem color-mix(in srgb, var(--accent) 32%, transparent);
|
||||
}
|
||||
|
||||
.pete-reader-note {
|
||||
margin-top: 1.25rem;
|
||||
@@ -313,6 +387,26 @@ html[data-phase="night"] {
|
||||
}
|
||||
.pete-reader-empty-emoji { font-size: 2.5rem; }
|
||||
|
||||
/* Transient confirmation toast (e.g. "Link copied") shown over the reader. */
|
||||
.pete-reader-toast {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 4.5rem;
|
||||
transform: translate(-50%, 0.5rem);
|
||||
z-index: 20;
|
||||
padding: 0.5rem 0.9rem;
|
||||
border-radius: 9999px;
|
||||
background: rgba(20, 14, 6, 0.9);
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
font-size: 0.85rem;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
.pete-reader-toast.is-shown { opacity: 1; transform: translate(-50%, 0); }
|
||||
|
||||
.pete-reader-hint {
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
@@ -328,6 +422,9 @@ html[data-phase="night"] {
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.pete-reader-hint { display: none; }
|
||||
/* With Listen/Share/Aa added, let the toolbar wrap instead of overflowing. */
|
||||
.pete-reader-bar { flex-wrap: wrap; }
|
||||
.pete-reader-nav { flex-wrap: wrap; justify-content: flex-end; row-gap: 0.4rem; }
|
||||
}
|
||||
|
||||
/* "You might also like" rail, shown under the article in feed mode. Lives
|
||||
|
||||
Reference in New Issue
Block a user