+
+
@Localizer["Popular posts"]
@Localizer["Shared links"]
@@ -139,6 +140,17 @@
@code {
+ ElementReference strip;
+ int revealed = -1;
+
+ protected override void OnAfterRender(bool firstRender)
+ {
+ if ((int)tab == revealed)
+ return;
+ revealed = (int)tab;
+ CascadingState.Status.RevealActive(strip);
+ }
+
enum ExploreTab
{
Hashtags,
diff --git a/Resources/AllStrings.it.resx b/Resources/AllStrings.it.resx
index c15e576..89014ca 100644
--- a/Resources/AllStrings.it.resx
+++ b/Resources/AllStrings.it.resx
@@ -1853,4 +1853,7 @@
Tutte le persone di questo accesso escono da questo dispositivo.
+
+ Feed
+
diff --git a/Resources/AllStrings.resx b/Resources/AllStrings.resx
index 4ce7871..de4c92b 100644
--- a/Resources/AllStrings.resx
+++ b/Resources/AllStrings.resx
@@ -1853,4 +1853,7 @@
Every persona of this login is signed out on this device.
+
+ Feeds
+
diff --git a/Services/AppStatusService.cs b/Services/AppStatusService.cs
index cfc9586..125ab5f 100644
--- a/Services/AppStatusService.cs
+++ b/Services/AppStatusService.cs
@@ -1,3 +1,4 @@
+using Microsoft.AspNetCore.Components;
using decePubClient.Models;
using Microsoft.JSInterop;
@@ -41,6 +42,9 @@ public class AppStatusService(IJSRuntime jsRuntime)
public bool IsMobileMedia() => JsSyncRuntime.Invoke("isMobileMedia");
+ // a sideways-scrolling strip of tabs shows its selected tab
+ public void RevealActive(ElementReference strip) => JsSyncRuntime.InvokeVoid("revealActive", strip);
+
public bool CanShare() => JsSyncRuntime.Invoke("canShareStuff");
public async ValueTask ShareTextUrl(string title, string text, Uri uri, CancellationToken cancellationToken = default) =>
diff --git a/Styles/touch.css b/Styles/touch.css
index 9300f2f..16bc5cf 100644
--- a/Styles/touch.css
+++ b/Styles/touch.css
@@ -112,3 +112,46 @@
}
}
}
+
+/* A sideways strip fades at the edge where more of it waits: the right edge at first, both while scrolled, the left
+ at the end (a scroll-driven animation where the browser has one; without, the right edge only). */
+@property --fade-start {
+ syntax: "";
+ inherits: false;
+ initial-value: 0px;
+}
+
+@property --fade-end {
+ syntax: "";
+ inherits: false;
+ initial-value: 1.5rem;
+}
+
+@keyframes edge-fade-x {
+ from {
+ --fade-start: 0px;
+ --fade-end: 1.5rem;
+ }
+
+ 8% {
+ --fade-start: 1.5rem;
+ }
+
+ 92% {
+ --fade-end: 1.5rem;
+ }
+
+ to {
+ --fade-start: 1.5rem;
+ --fade-end: 0px;
+ }
+}
+
+@utility edge-fade-x {
+ mask-image: linear-gradient(to right, transparent, #000 var(--fade-start), #000 calc(100% - var(--fade-end)), transparent);
+
+ @supports (animation-timeline: scroll()) {
+ animation: edge-fade-x linear both;
+ animation-timeline: scroll(self inline);
+ }
+}
diff --git a/wwwroot/main.js b/wwwroot/main.js
index ae077be..97cd473 100644
--- a/wwwroot/main.js
+++ b/wwwroot/main.js
@@ -123,6 +123,15 @@ window.clearAppBadge = () => {
window.logFromJs(e.message, "clearAppBadge")
}
}
+// the selected tab of a sideways-scrolling strip, brought into its middle
+window.revealActive = strip => {
+ try {
+ strip?.querySelector("[aria-selected=true]")?.scrollIntoView({ inline: "center", block: "nearest" })
+ } catch (e) {
+ console.error(e)
+ window.logFromJs(e.message, "revealActive")
+ }
+}
// a phone: narrower than Tailwind's md, or held sideways (as short as the short: variant)
window.isMobileMedia = () => {
try {