aboutsummaryrefslogtreecommitdiffstats
path: root/src/style.css
diff options
context:
space:
mode:
Diffstat (limited to 'src/style.css')
-rw-r--r--src/style.css303
1 files changed, 284 insertions, 19 deletions
diff --git a/src/style.css b/src/style.css
index 2090e24..f26fac3 100644
--- a/src/style.css
+++ b/src/style.css
@@ -30,6 +30,12 @@
--accent-fg: #ffffff; /* text on accent */
--accent-ink: #000000;
--accent-soft: #ececef;
+ /* a button's fill (primary, chosen chip or tab): the accent in light; in
+ dark a lifted grey, not a white slab (Adam, 2026-10-18: "Probably
+ shouldn't have white buttons in dark mode") */
+ --fill: var(--accent);
+ --fill-hover: var(--accent-hover);
+ --fill-fg: var(--accent-fg);
--green: #14935a;
--green-soft: #e7f6ee;
--green-line: #c4e7d4;
@@ -65,6 +71,9 @@
--accent-fg: #0f1216; /* text on accent */
--accent-ink: #e7e9ee;
--accent-soft: #262b33;
+ --fill: #353b45;
+ --fill-hover: #404753;
+ --fill-fg: #f3f5f8;
--green: #2dbd80;
--green-soft: #14271d;
--green-line: #235340;
@@ -92,6 +101,12 @@ html {
one last time" after a switch. Stable gutter = no reflow when content
grows. */
scrollbar-gutter: stable;
+ /* Nothing ever scrolls the page sideways. Anything wider than the screen
+ (a strip of chips, a stray wide row) would otherwise let an iPhone
+ pinch the page out and pan to it. (On the root element, not body: a
+ body that clips becomes the scroll container a sticky rail measures
+ against, and the rail would stop sticking.) */
+ overflow-x: hidden;
}
html, body {
@@ -224,8 +239,8 @@ button, .btn {
button:hover { background: var(--surface2); }
button:active { transform: translateY(1px); }
button:disabled { opacity: .5; cursor: not-allowed; }
-.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
-.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
+.btn-primary { background: var(--fill); border-color: var(--fill); color: var(--fill-fg); }
+.btn-primary:hover { background: var(--fill-hover); border-color: var(--fill-hover); }
.btn-ghost { background: transparent; }
.btn-sm { padding: 7px 11px; font-size: 13px; border-radius: 9px; }
.btn-sm.on { border-color: var(--accent); color: var(--accent); }
@@ -236,7 +251,7 @@ button:disabled { opacity: .5; cursor: not-allowed; }
/* ---- inputs ---- */
label.field { display: block; }
.field .lab { font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 6px; display: block; }
-input[type=text], input[type=search], input[type=password], input[type=number], textarea, select {
+input[type=text], input[type=search], input[type=password], input[type=number], input[type=datetime-local], textarea, select {
width: 100%; font: inherit; color: var(--ink); background: var(--paper);
border: 1px solid var(--line-strong); border-radius: 11px; padding: 11px 13px;
}
@@ -285,6 +300,12 @@ textarea.send-dest::placeholder { font-family: var(--sans); }
.seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: 10px; overflow: hidden; }
.seg button { border: 0; border-radius: 0; padding: 9px 14px; background: var(--paper); }
.seg button.active { background: var(--accent-soft); color: var(--accent-ink); }
+/* The profile's Posts / Replies tabs sit right on white cards: a soft-grey
+ active tab beside a white one read as the WHITE one being selected (its
+ colour matched the posts under it). So these take the feed chips' look:
+ the chosen tab solid accent, the other one quiet (Adam, 2026-10-16). */
+.prof-tabs button { background: var(--surface2); color: var(--muted); }
+.prof-tabs button.active { background: var(--fill); color: var(--fill-fg); }
/* ---- address / qr ---- */
.qr { width: 220px; height: 220px; display: block; border-radius: 12px; border: 1px solid var(--line); }
@@ -338,6 +359,10 @@ textarea.send-dest::placeholder { font-family: var(--sans); }
/* ---- lists (tx / utxo) ---- */
.list { display: flex; flex-direction: column; }
.item { display: flex; align-items: center; gap: 12px; padding: 13px 2px; border-top: 1px solid var(--line); }
+/* the middle column may shrink: a long name chip beside the label then
+ ellipsizes on one line instead of breaking mid-name and pushing the
+ amount about */
+.item > .grow { min-width: 0; }
.item:first-child { border-top: 0; }
.ico { width: 34px; height: 34px; border-radius: 9px; flex: none; display: grid; place-items: center; font-size: 16px; }
.ico.in { background: var(--green-soft); color: var(--green); }
@@ -345,7 +370,7 @@ textarea.send-dest::placeholder { font-family: var(--sans); }
.amount-pos { color: var(--green); font-family: var(--mono); font-weight: 600; }
.amount-neg { color: var(--red); font-family: var(--mono); font-weight: 600; }
.amount { font-family: var(--mono); font-weight: 600; }
-.tag { font-size: 11px; font-weight: 700; padding: 2px 7px; border-radius: 6px; }
+.tag { font-size: 11px; font-weight: 700; padding: 2px 7px; border-radius: 6px; white-space: nowrap; flex: none; }
.tag.pending { background: var(--accent-soft); color: var(--accent-ink); }
.tag.conf { background: var(--green-soft); color: var(--green); }
@@ -714,7 +739,7 @@ html:has(#app .chat-card), body:has(#app .chat-card) { overflow: hidden; }
.pos-big { font-size: 34px; font-weight: 700; line-height: 1.1; letter-spacing: -.5px; }
.pos-tips { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; width: 100%; }
.pos-tip { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 12px 6px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface2); color: var(--ink); cursor: pointer; }
-.pos-tip.on { border-color: var(--accent); background: var(--accent); color: var(--accent-fg); }
+.pos-tip.on { border-color: var(--fill); background: var(--fill); color: var(--fill-fg); }
.pos-tip.on .muted { color: inherit; opacity: .8; }
.pos-tip-label { font-size: 20px; font-weight: 600; }
.pos-total { font-size: 22px; font-weight: 600; margin-top: 4px; }
@@ -723,14 +748,12 @@ html:has(#app .chat-card), body:has(#app .chat-card) { overflow: hidden; }
.pos-sale.faint { opacity: .55; }
.pos-pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; width: 100%; }
.pos-key { padding: 14px 0; font-size: 22px; font-weight: 600; border: 1px solid var(--line); border-radius: 12px; background: var(--surface2); color: var(--ink); cursor: pointer; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
-.pos-key:active { background: var(--accent); color: var(--accent-fg); }
+.pos-key:active { background: var(--fill); color: var(--fill-fg); }
.pos-key.del { font-weight: 400; }
/* the feeds row: pills that scroll sideways, the one on screen filled */
.feed-chips { gap: 6px; flex-wrap: nowrap; overflow-x: auto; margin: 0 -14px; padding: 2px 14px; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.feed-chips::-webkit-scrollbar { display: none; }
/* with a mouse: a thin bar says the strip scrolls, and it drags (app.js) */
-/* grabbing the feed to scroll it (app.js) */
-.grab-scrolling, .grab-scrolling * { cursor: grabbing !important; user-select: none !important; }
@media (hover: hover) and (pointer: fine) {
.feed-chips { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; padding-bottom: 6px; cursor: grab; }
.feed-chips::-webkit-scrollbar { display: block; height: 6px; }
@@ -739,7 +762,7 @@ html:has(#app .chat-card), body:has(#app .chat-card) { overflow: hidden; }
.feed-chips.dragging .feed-chip { pointer-events: none; }
}
.feed-chip { flex: 0 0 auto; border: 1px solid var(--line); background: var(--surface2); color: var(--muted); border-radius: 999px; padding: 5px 12px; font-size: 14px; line-height: 1.2; cursor: pointer; white-space: nowrap; max-width: 180px; overflow: hidden; text-overflow: ellipsis; }
-.feed-chip.on { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
+.feed-chip.on { background: var(--fill); color: var(--fill-fg); border-color: var(--fill); }
.feed-chip.add { font-weight: 600; padding: 5px 11px; }
.note-act {
background: none; border: 0; padding: 4px 6px; margin: 0; line-height: 1;
@@ -828,11 +851,29 @@ html.no-scroll { overflow: hidden; scrollbar-gutter: auto; }
}
.lightbox-x:hover { background: rgba(255, 255, 255, .24); }
@keyframes lightbox-in { from { opacity: 0; } to { opacity: 1; } }
-.note-video { display: block; width: 100%; height: auto; max-height: 100vh; object-fit: contain; border-radius: 10px; margin-top: 6px; background: #000; }
+.note-video { display: block; width: 100%; height: auto; max-height: var(--vid-cap); object-fit: contain; border-radius: 10px; margin-top: 6px; background: #000; }
/* a video plays by itself, muted, while it is on screen; the corner button
turns the sound on (see autoplay in messages.js) */
.note-video-box { position: relative; display: block; max-width: 100%; margin-top: 6px; }
.note-video-box .note-video { margin-top: 0; }
+/* the height a feed clip may take: the whole screen on a phone, three
+ quarters of it on a computer (a portrait clip on a 1920 screen ran off it) */
+:root { --vid-cap: 100vh; }
+@media (min-width: 900px) { :root { --vid-cap: 75vh; } }
+/* with a mouse: the big play/pause in the middle, while the pointer is over the clip */
+.vid-pp {
+ position: absolute; left: 50%; top: 50%; z-index: 2; width: 64px; height: 64px; margin: -32px 0 0 -32px; padding: 0;
+ border: 0; border-radius: 50%; background: rgba(18, 18, 18, .6); color: #fff; cursor: pointer;
+ display: none; align-items: center; justify-content: center;
+}
+.vid-pp:hover { background: rgba(18, 18, 18, .85); }
+.vid-pp-i { width: 0; height: 0; margin-left: 5px; border-style: solid; border-width: 13px 0 13px 22px; border-color: transparent transparent transparent #fff; }
+.vid-pp.playing .vid-pp-i { width: 20px; height: 24px; margin: 0; border-width: 0 7px; border-color: #fff; }
+.has-mouse .note-video-box:hover .vid-pp, .vid-pp:focus-visible { display: flex; }
+/* the moments bar is the clip's only seek bar: the middle button is its play
+ control on a touch screen too, up whenever the clip is paused */
+.note-video-box.own-bar .vid-pp:not(.playing) { display: flex; }
+.note-video-box:has(.vid-heavy) .vid-pp { display: none; }
.vid-sound {
position: absolute; top: 8px; left: 8px; z-index: 2; margin: 0; padding: 4px 10px; border: 0;
border-radius: 999px; background: rgba(18, 18, 18, .7); color: #fff; font-size: 13px; line-height: 1.3;
@@ -867,6 +908,71 @@ html.no-scroll { overflow: hidden; scrollbar-gutter: auto; }
.addr-cover { width: 100%; aspect-ratio: 2 / 1; object-fit: cover; display: block; background: var(--line); }
.addr-title { font-weight: 650; font-size: 1.05em; line-height: 1.3; }
.addr-summary { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; white-space: normal; }
+/* Live streams (NIP-53): the row of what's on above a feed, a profile's
+ "on now", a post's card, and the stream's own page (player over chat). */
+/* a face whose person is streaming now: a red ring, and LIVE under the bigger ones */
+.chat-avatar.live-ring { box-shadow: 0 0 0 2px var(--paper), 0 0 0 4px #e0245e; position: relative; overflow: visible; }
+.chat-avatar.mini.live-ring { box-shadow: 0 0 0 1px var(--paper), 0 0 0 2.5px #e0245e; }
+.chat-avatar.live-ring:not(.mini)::after { content: 'LIVE'; position: absolute; left: 50%; bottom: -7px; transform: translateX(-50%);
+ padding: 0 4px; border-radius: 3px; font-size: 9px; font-weight: 700; line-height: 13px; letter-spacing: .04em;
+ color: #fff; background: #e0245e; box-shadow: 0 0 0 2px var(--paper); text-transform: none; }
+.live-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 14px 10px; padding: 2px 0 4px; }
+.live-past-title { margin: 6px 0 0; font-size: 15px; }
+.live-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: #e0245e; margin-right: 5px; vertical-align: 1px; }
+.live-tile { display: flex; flex-direction: column; gap: 5px; padding: 0; text-align: left;
+ background: none; border: 0; color: inherit; cursor: pointer; min-width: 0; }
+.live-cover { position: relative; width: 100%; aspect-ratio: 16 / 9; border-radius: 10px; overflow: hidden;
+ background: var(--line) center / cover no-repeat; }
+.live-badge { display: inline-block; padding: 1px 6px; border-radius: 4px; font-size: 11px; font-weight: 700;
+ letter-spacing: .04em; color: #fff; background: #555; line-height: 1.5; flex-shrink: 0; }
+.live-badge.live { background: #e0245e; }
+.live-cover .live-badge { position: absolute; top: 6px; left: 6px; }
+.live-viewers { position: absolute; bottom: 6px; left: 6px; padding: 1px 6px; border-radius: 4px; font-size: 11px;
+ color: #fff; background: rgba(0, 0, 0, .6); }
+.live-tile-title { font-weight: 600; font-size: 14px; line-height: 1.25; display: -webkit-box; -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical; overflow: hidden; }
+.live-tile-host { align-items: center; min-width: 0; }
+.live-tile-host .small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.live-banner { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border: 1px solid #e0245e;
+ border-radius: 10px; background: none; color: inherit; cursor: pointer; text-align: left; min-width: 0; }
+.live-banner-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
+.live-card { flex-direction: row; align-items: center; gap: 10px; }
+.live-card .live-cover { flex: 0 0 120px; width: 120px; }
+.live-title { margin: 0; min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.live-player { width: 100%; aspect-ratio: 16 / 9; background: #000; border-radius: 10px; overflow: hidden; }
+.live-frame { width: 100%; height: 100%; }
+.live-video { width: 100%; height: 100%; display: block; background: #000; }
+.live-still { border-radius: 0; height: 100%; display: flex; flex-direction: column; gap: 10px; align-items: center; justify-content: center; }
+.live-rec-link { background: var(--paper); color: var(--ink); text-decoration: none; }
+.live-say { padding: 6px 12px; border-radius: 8px; color: #fff; background: rgba(0, 0, 0, .65); font-size: 14px; text-align: center; }
+/* the top padding is headroom for a hat on the first line's face: a scroller clips what rises out of it */
+.live-chat { display: flex; flex-direction: column; gap: 8px; max-height: 46vh; min-height: 120px; overflow-y: auto; overscroll-behavior: contain; padding-top: 12px; }
+.live-line { display: flex; gap: 8px; align-items: flex-start; min-width: 0; }
+.live-line-body { min-width: 0; overflow-wrap: anywhere; white-space: pre-wrap; line-height: 1.35; }
+.live-zap .live-line-body b { color: var(--accent); }
+.live-chat .chat-row { padding-right: 2px; }
+/* on phones the stream is the page, the way Nostrich lays one out: the
+ picture runs edge to edge and the chat under it is a plain full-width
+ list of lines, no bubbles — the card walls step outside #app's padding */
+@media (max-width: 520px) {
+ .live-page > .card {
+ margin-left: -16px; margin-right: -16px; padding: 10px 0 12px;
+ border-left: none; border-right: none; border-radius: 0; box-shadow: none;
+ }
+ .live-page > .live-chat-card { margin-top: 0; padding-top: 6px; border-top: none; }
+ .live-page > .card > :not(.live-player, .live-chat) { margin-left: 14px; margin-right: 14px; }
+ .live-page .live-player { border-radius: 0; }
+ .live-page .live-chat { padding: 10px 14px 0; max-height: 60vh; }
+ .live-page .live-title { font-size: 17px; }
+ .live-chat .chat-row { gap: 10px; }
+ .live-chat .chat-bubble { display: block; background: transparent; border: 0; border-radius: 0; padding: 0; font-size: 15px; }
+ .live-chat .chat-quote { margin-bottom: 2px; }
+}
+.bc-preview.mirror { transform: scaleX(-1); }
+.bc-bar .bc-clock { font-variant-numeric: tabular-nums; font-weight: 650; }
+.go-live-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: #e0245e; box-shadow: 0 0 0 3px rgba(224, 36, 94, .25); }
+.go-live-btn { display: inline-flex; align-items: center; gap: 8px; }
+.go-live-title { width: 100%; }
.article-title { font-size: 1.35em; font-weight: 700; line-height: 1.25; margin-bottom: 6px; white-space: normal; }
.article-h1, .article-h2, .article-h3 { font-weight: 650; line-height: 1.3; margin: 10px 0 2px; white-space: normal; }
.article-h1 { font-size: 1.2em; } .article-h2 { font-size: 1.1em; } .article-h3 { font-size: 1em; }
@@ -942,7 +1048,7 @@ html.no-scroll { overflow: hidden; scrollbar-gutter: auto; }
position: fixed; bottom: calc(env(safe-area-inset-bottom, 0px) + 18px);
left: 50%; transform: translateX(-50%);
z-index: 30; width: auto; padding: 12px 24px; border-radius: 999px;
- background: var(--accent); color: var(--accent-fg); border: none;
+ background: var(--fill); color: var(--fill-fg); border: none;
font-size: 16px; font-weight: 650; cursor: pointer;
touch-action: none; user-select: none; -webkit-user-select: none; /* it swipes away */
box-shadow: 0 3px 12px rgba(0, 0, 0, .28);
@@ -951,7 +1057,7 @@ html.no-scroll { overflow: hidden; scrollbar-gutter: auto; }
/* the generic button hover (a light surface) and press (a 1px drop, which
REPLACED the centring transform and threw the pill half its width to the
right, out from under the pointer, so the tap never landed) stay off it */
-.feed-new-pill:hover { background: var(--accent-hover); color: var(--accent-fg); }
+.feed-new-pill:hover { background: var(--fill-hover); color: var(--fill-fg); }
.feed-new-pill:active { transform: translate(-50%, 1px); }
/* the count bumps each time it changes: a fresh node per value replays it */
.feed-new-pill .n { display: inline-block; min-width: 1.1em; text-align: center; animation: pill-bump .32s cubic-bezier(.2,.9,.3,1.4); }
@@ -1113,7 +1219,8 @@ html.no-scroll { overflow: hidden; scrollbar-gutter: auto; }
.profile-banner + .row .profile-avatar {
border: 3px solid var(--paper); box-sizing: content-box;
}
-.zap-chip { display: inline-flex; gap: 5px; align-items: center; cursor: pointer; background: var(--surface2); border: 1px solid var(--line); border-radius: 999px; padding: 1px 8px 1px 2px; }
+.zap-chip { display: inline-flex; gap: 5px; align-items: center; cursor: pointer; background: var(--surface2); border: 1px solid var(--line); border-radius: 999px; padding: 1px 8px 1px 2px; min-width: 0; max-width: 100%; }
+.zap-chip > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-avatar.mini { width: 18px; height: 18px; font-size: 8px; flex-shrink: 0; }
/* A feed row is the one place a face is the point, not a label on a line —
Amethyst gives it 55dp; at our 30px it read as a bullet beside the post.
@@ -1154,6 +1261,8 @@ html.no-scroll { overflow: hidden; scrollbar-gutter: auto; }
.chat-avatar.header-ava { width: 30px; height: 30px; font-size: 11px; }
.composer-options { margin-inline-start: auto; flex-shrink: 0; }
.compose-preview-toggle[aria-pressed="true"] { background: var(--accent-soft); color: var(--ink); }
+.post-actions button[aria-pressed="true"]:not(.compose-preview-toggle) { background: var(--accent-soft); color: var(--ink); }
+.sched-pick input { min-width: 0; }
/* the post composer: Preview and Cancel stand as tall as Post and the
attach square beside them, in the same type */
.post-actions .composer-options > button { padding: 11px 16px; font-size: inherit; border-radius: 11px; min-height: 44px; }
@@ -1177,7 +1286,7 @@ html.no-scroll { overflow: hidden; scrollbar-gutter: auto; }
.hat-demo { position: relative; display: inline-flex; width: 34px; height: 34px; flex-shrink: 0; margin-top: 12px; }
.hat-demo-head { width: 100%; height: 100%; border-radius: 50%; background: var(--accent-soft); }
.hat-btn { white-space: nowrap; flex-shrink: 0; }
-.hat-btn.wearing { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
+.hat-btn.wearing { background: var(--fill); border-color: var(--fill); color: var(--fill-fg); }
.hat-btn.confirm { border-color: var(--green); color: var(--green); }
/* a small centered confirm popup over a dimmed page */
@@ -1247,7 +1356,7 @@ button, .btn { transition: filter .12s, background .15s, border-color .15s, box-
.chat-avatar.clickable:hover { transform: scale(1.07); }
.header-avatar .chat-avatar { transition: transform .15s ease; }
.header-avatar:hover .chat-avatar { transform: scale(1.08); }
-input[type=text], input[type=search], input[type=password], input[type=number], textarea, select { transition: border-color .15s, box-shadow .15s; }
+input[type=text], input[type=search], input[type=password], input[type=number], input[type=datetime-local], textarea, select { transition: border-color .15s, box-shadow .15s; }
.settings-nav::after { transition: transform .18s ease, color .18s; }
.settings-nav:hover::after { transform: translateX(3px); color: var(--ink); }
@@ -1455,7 +1564,13 @@ body::after {
.social-count.on { color: var(--ink); border-bottom-color: var(--accent); }
/* ---- feed media views: All / Images / Videos, the grid, the pager ---- */
-.media-modes { gap: 6px; }
+/* The view strip scrolls sideways like the feed chips above it: five
+ chips no longer fit a phone (Conversations joined them), and a flex row
+ that neither wraps nor scrolls made the page 42px wider than the screen
+ — on an iPhone that is a page you can pinch out of and pan, a blank
+ strip down the right (Adam, 2026-10-16). */
+.media-modes { gap: 6px; flex-wrap: nowrap; overflow-x: auto; margin: 0 -14px; padding: 2px 14px; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
+.media-modes::-webkit-scrollbar { display: none; }
.media-modes .feed-chip { padding: 4px 12px; font-size: 13px; }
.article-list { gap: 10px; }
.article-list > .quote-card { margin-top: 0; }
@@ -1465,6 +1580,20 @@ body::after {
background: var(--surface2); overflow: hidden; cursor: pointer;
}
.media-tile.video { aspect-ratio: 9 / 16; }
+/* only wide clips in it: one to a row, the poster whole */
+.media-grid.wide { grid-template-columns: 1fr; gap: 6px; }
+.media-grid.wide .media-tile.video { aspect-ratio: 16 / 9; }
+/* every shape in it: justified rows. A tile starts at its proportions at
+ the row height and grows in proportion to them, so a row's tiles share one
+ height and fill the width; the filler takes the last row's slack instead
+ of stretching its few tiles. */
+.media-grid.justified { display: flex; flex-wrap: wrap; --row-h: 140px; }
+.media-grid.justified .media-tile { aspect-ratio: var(--ar); flex: var(--ar) 1 calc(var(--ar) * var(--row-h)); min-width: 0; }
+.media-grid.justified::after { content: ''; flex: 1000 1 0; }
+/* the picture fills the tile without sizing it: a frame-grab <video>'s own
+ 320px would widen its tile, and the row's height with it */
+.media-grid.justified .media-tile > img, .media-grid.justified .media-tile > video { position: absolute; inset: 0; }
+@media (max-width: 600px) { .media-grid.justified { --row-h: 100px; } }
.media-tile img, .media-tile video { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; }
.media-tile.broken { display: none; }
.media-tile-play {
@@ -1533,6 +1662,7 @@ body::after {
display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; cursor: pointer; overflow-wrap: anywhere;
}
.mp-caption.open { -webkit-line-clamp: unset; max-height: 40vh; overflow-y: auto; }
+.mp-caption a { color: #fff; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; text-decoration-color: rgba(255, 255, 255, .5); }
.mp-counts { display: flex; flex-direction: column; align-items: center; gap: 14px; flex: 0 0 auto; }
.mp-counts button {
display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 0; margin: 0; border: 0;
@@ -1540,7 +1670,8 @@ body::after {
}
.mp-counts .i { font-size: 24px; line-height: 1; display: flex; }
.mp-counts .i svg { width: 28px; height: 28px; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .6)); }
-.mp-like.on { color: #ff4d6d; }
+/* yours: the heart goes red; the count stays white (a 🤣 of yours left only the number red) */
+.mp-like.on .i { color: #ff4d6d; }
.mp-like .i .cemoji { width: 28px; height: 28px; }
/* the emoji sheet over the media pager (z-index 200) */
.confirm-pop-backdrop.over-pager { z-index: 300; }
@@ -1555,6 +1686,17 @@ body::after {
}
.mp-sound { font-size: 15px; }
.mp-sound .off { display: none; }
+.mp-top-r { display: flex; align-items: center; gap: 8px; }
+.mp-fit { height: 40px; padding: 0 14px; font-size: 14px; font-weight: 600; border-radius: 999px; border: 0; color: #fff; background: rgba(255, 255, 255, .16); }
+.media-pager.quiet .mp-top .mp-fit { pointer-events: auto; }
+/* a post out of the carousel: its clip not ready to play yet (mp-wait), or
+ the wrong way round for the screen (mp-off). Parked far above the screen
+ (fixed: out of the scroller's flow and its snap points, no height to
+ count) rather than display:none, so its box isn't a zero rect at the
+ origin that a "what's at the top left" lookup takes for the slide on
+ screen; its clip's poster still loads there. */
+.mp-group.mp-wait, .mp-group.mp-off { position: fixed; top: -400vh; left: 0; height: 0; visibility: hidden; pointer-events: none; }
+/* over the Videos grid: which shapes */
/* A mouse's way through the pager: arrows, where there is a pointer to
hover with (a phone swipes). Faint until the pointer is over the pager;
the sideways pair only when the post has a picture that way. */
@@ -1642,9 +1784,15 @@ body::after {
.media-pager { --mp-seek-lift: calc(env(safe-area-inset-bottom, 0px) + 32px); }
.mp-seek {
position: absolute; left: 16px; right: 16px; bottom: var(--mp-seek-lift); z-index: 3; height: 28px;
- display: flex; align-items: flex-end; touch-action: none; cursor: pointer; visibility: hidden;
+ display: flex; align-items: flex-end; touch-action: none; cursor: pointer; visibility: hidden; opacity: 0;
}
-.mp-seek.ready { visibility: visible; }
+/* out of sight (and out of reach: a tap there must not jump the clip) while
+ it plays; up when paused (.show), after a tap near the foot (.reveal), or held */
+.mp-seek.ready { visibility: visible; opacity: 0; pointer-events: none; transition: opacity .18s; }
+.media-pager.show .mp-seek.ready, .mp-seek.ready.reveal, .mp-seek.ready.drag { opacity: 1; pointer-events: auto; }
+/* only the clip on screen's bar, and not while the pager is in motion (a
+ finger, a glide, a flight): it goes at once and fades back in on landing */
+.mp-slide:not(.mp-cur) .mp-seek, .media-pager.moving .mp-seek { opacity: 0; transition: none; pointer-events: none; }
.mp-seek-track { position: relative; width: 100%; height: 3px; border-radius: 3px; background: rgba(255, 255, 255, .28); transition: height .15s; }
.mp-seek-fill { position: absolute; left: 0; top: 0; bottom: 0; width: 0; border-radius: inherit; background: #fff; }
.mp-seek-fill::after {
@@ -1821,3 +1969,120 @@ body::after {
/* Always visible, including when the media controls are tucked away. */
.mp-position { position: absolute; top: calc(env(safe-area-inset-top, 0px) + 16px); left: 50%; transform: translateX(-50%); padding: 5px 10px; border-radius: 20px; background: #0009; color: #fff; font-size: 13px; font-variant-numeric: tabular-nums; pointer-events: none; z-index: 5; }
+/* the counter shows while the pager is in motion and fades out a moment
+ after it settles (the animation restarts each time `moving` comes off) */
+.media-pager .mp-position { animation: mp-position-out .4s 1.2s both; }
+.media-pager.moving .mp-position { animation: none; opacity: 1; }
+@keyframes mp-position-out { from { opacity: 1; } to { opacity: 0; } }
+.media-pager.few .mp-position { visibility: hidden; }
+
+/* A call (calls.js): over every page, its picture filling it when there is
+ one; minimized, a bar along the top. */
+.call-overlay { position: fixed; inset: 0; z-index: 1000; background: #101318; color: #fff;
+ display: flex; flex-direction: column; align-items: center; justify-content: space-between;
+ padding: max(28px, env(safe-area-inset-top)) 16px max(28px, env(safe-area-inset-bottom)); }
+/* the whole of their picture, whatever its shape and the window's: a phone's
+ portrait camera filled a laptop's wide window by cropping to a forehead */
+.call-overlay .call-remote { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: #000; display: none; }
+/* a call over the whole window: the page under it stays put */
+html:has(.call-overlay:not(.call-min)) { overflow: hidden; scrollbar-gutter: auto; }
+.call-overlay.has-video .call-remote { display: block; }
+.call-overlay.has-video .call-who { position: relative; text-shadow: 0 1px 3px rgba(0, 0, 0, .7); }
+.call-overlay.has-video .call-who .call-avatar { display: none; }
+.call-who { display: flex; flex-direction: column; align-items: center; gap: 10px; margin-top: 8vh; z-index: 1; }
+.call-who .call-avatar { width: 112px; height: 112px; font-size: 32px; }
+.call-name { font-size: 22px; font-weight: 650; }
+.call-status { font-size: 15px; opacity: .8; font-variant-numeric: tabular-nums; }
+.call-local { position: absolute; right: 16px; bottom: 120px; width: 28vw; max-width: 180px; aspect-ratio: 3 / 4; object-fit: cover;
+ border-radius: 12px; background: #000; box-shadow: 0 2px 12px rgba(0, 0, 0, .5); z-index: 2; }
+.call-local.mirror { transform: scaleX(-1); }
+.call-local.sharing { aspect-ratio: 16 / 10; object-fit: contain; background: #000; }
+.call-bar { display: flex; gap: 18px; z-index: 2; flex-wrap: wrap; justify-content: center; }
+.call-btn { width: 60px; height: 60px; border-radius: 50%; border: 0; display: flex; align-items: center; justify-content: center;
+ background: rgba(255, 255, 255, .16); color: #fff; cursor: pointer; padding: 0; }
+.call-btn:hover { background: rgba(255, 255, 255, .26); }
+.call-btn.on { background: #fff; color: #101318; }
+.call-btn.call-hang, .call-btn.call-decline { background: #e0245e; }
+.call-btn.call-accept { background: #1fa45a; }
+.call-overlay.call-min { inset: 0 0 auto 0; padding: 0; background: none; height: auto; justify-content: flex-start; pointer-events: none; }
+.call-min-bar { pointer-events: auto; margin: max(8px, env(safe-area-inset-top)) auto 0; display: flex; align-items: center; gap: 10px;
+ padding: 8px 16px; border-radius: 999px; border: 0; background: #1fa45a; color: #fff; font-weight: 600; cursor: pointer; box-shadow: 0 2px 10px rgba(0, 0, 0, .3); }
+.call-min-status { font-variant-numeric: tabular-nums; opacity: .9; }
+.call-min .call-audio { display: none; }
+/* a call in its conversation: a row of its own, centred, a tap calls back */
+.chat-row.call-log { justify-content: center; margin: 6px 0; flex-direction: column; align-items: center; gap: 3px; }
+.call-log-why { text-align: center; max-width: 320px; }
+.call-log-pill { display: inline-flex; align-items: center; gap: 8px; padding: 6px 12px; border-radius: 999px;
+ border: 1px solid var(--line); background: var(--surface2); color: inherit; font-size: 13px; cursor: pointer; }
+.call-log-pill .chat-time { margin: 0; font-size: 11px; opacity: .7; }
+.call-log-pill svg { width: 16px; height: 16px; }
+.chat-row.call-log.missed .call-log-pill { color: var(--red); border-color: color-mix(in srgb, var(--red) 40%, var(--line)); }
+
+/* A post's audio/video timeline: the waveform (audio), and what was said,
+ reacted or zapped at a moment, sitting over the bar at that moment. */
+.media-timeline { padding: 8px 0; white-space: normal; color: var(--text); }
+.note-video-box .media-timeline:not(.has-marks) { display: none; }
+.media-time { font-variant-numeric: tabular-nums; }
+.media-seek { position: relative; height: 42px; --played: 0%; }
+.media-wave { position: absolute; inset: 3px 0; display: flex; align-items: center; gap: 2px;
+ background: linear-gradient(to right, var(--accent) var(--played), var(--line) var(--played)); }
+.media-wave:empty { height: 4px; top: 19px; }
+.media-wave i { flex: 1; background: var(--accent); border-radius: 2px; min-width: 1px; }
+.has-wave .media-wave { background: none; }
+.has-wave .media-wave::after { content: ''; position: absolute; inset: 0 0 0 var(--played); background: var(--bg, #fff); opacity: .55; pointer-events: none; }
+.media-seek input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: .01; cursor: pointer; }
+.media-seek:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
+/* the marks hang just under the line, at their moment (a plain bar: inside
+ its touch strip); under a waveform, below it */
+.media-marks { position: absolute; left: 0; right: 0; top: 24px; height: 18px; pointer-events: none; }
+.has-wave .media-marks { top: 100%; height: 22px; }
+.media-seek.has-wave:has(.media-mark) { margin-bottom: 22px; }
+.media-mark { bottom: auto; top: 0; }
+.media-mark, .mp-mark {
+ position: absolute; bottom: 0; transform: translateX(-50%); padding: 0 2px; margin: 0; border: 0; background: none;
+ font-size: 16px; line-height: 1; cursor: pointer; pointer-events: auto;
+}
+.media-mark .cemoji, .mp-mark .cemoji { width: 20px; height: 20px; vertical-align: bottom; }
+.media-mark-face { display: block; width: 20px !important; height: 20px !important; min-width: 0; font-size: 8px; border-radius: 50%; }
+.mp-mark .media-mark-face { width: 26px !important; height: 26px !important; box-shadow: 0 0 0 1.5px #fff; }
+.mp-pop .media-mark-face { width: 36px !important; height: 36px !important; box-shadow: 0 0 0 2px #fff; }
+/* the pager's: above its seek bar, there when the bar is */
+.mp-marks { position: absolute; left: 0; right: 0; bottom: 10px; height: 26px; pointer-events: none; }
+.mp-mark { font-size: 20px; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .6)); }
+.mp-mark .cemoji { width: 24px; height: 24px; }
+.mp-seek-time { max-width: calc(100% - 16px); overflow: hidden; text-overflow: ellipsis; }
+.mp-seek.say .mp-seek-time { opacity: 1; }
+.mp-marks:not(:empty) ~ .mp-seek-time { bottom: 42px; }
+/* ...and a moment the clip plays through floats up off the bar */
+.mp-pop {
+ position: absolute; bottom: calc(var(--mp-seek-lift) + 12px); z-index: 4; transform: translateX(-50%);
+ font-size: 30px; line-height: 1; pointer-events: none; filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .6));
+ animation: mp-pop 1.6s ease-out forwards;
+}
+.mp-pop .cemoji { width: 34px; height: 34px; }
+@keyframes mp-pop {
+ 0% { opacity: 0; transform: translate(-50%, 8px) scale(.6); }
+ 15% { opacity: 1; transform: translate(-50%, 0) scale(1.1); }
+ 100% { opacity: 0; transform: translate(-50%, -90px) scale(1); }
+}
+@media (prefers-reduced-motion: reduce) { .mp-pop { animation: none; opacity: 0; } }
+
+/* a timed comment's time: a chip that plays the clip from there */
+.media-time-link { display: inline-flex; align-items: center; margin-right: 6px; padding: 1px 8px; border-radius: 999px;
+ background: var(--accent-soft); color: var(--accent); font-size: .85em; font-weight: 650; font-variant-numeric: tabular-nums;
+ text-decoration: none; vertical-align: 1px; white-space: nowrap; }
+.media-time-link:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }
+.media-moment-detail { overflow-wrap: anywhere; max-height: 72px; overflow-y: auto; margin-top: 5px; }
+.media-moment-detail:empty { display: none; }
+.live-watching.has-who { cursor: pointer; text-decoration: underline dotted; text-underline-offset: 3px; }
+.live-who { display: flex; flex-wrap: wrap; gap: 6px; }
+.live-who-p { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px 3px 3px; border-radius: 999px; background: var(--bg2, rgba(127,127,127,.12)); border: 0; color: inherit; cursor: pointer; }
+
+/* a gift's note: the sender's words above the amount, and the field for them */
+.gift-note {
+ margin: 0; padding: 8px 12px; max-width: 100%;
+ border-left: 3px solid var(--accent); border-radius: 4px;
+ background: color-mix(in srgb, var(--accent) 8%, transparent);
+ font-style: italic; text-align: left; white-space: pre-wrap; overflow-wrap: anywhere;
+}
+.gift-note-input { width: 100%; min-height: 0; font-family: var(--sans); }