From 4a33612c5764a2ba1d2f51144c76f16b504f0a30 Mon Sep 17 00:00:00 2001 From: karitham Date: Sun, 19 Jul 2026 12:32:16 +0200 Subject: [PATCH] styling: small layout changes in profile section, pronouns --- lexicons/app/bsky/actor/defs.json | 3 +- priv/static/style.css | 68 +++++++++++++++---------------- shared/src/gen/actor/defs.gleam | 5 ++- shared/src/profile.gleam | 35 +++++++++------- shared/test/encode_test.gleam | 2 + 5 files changed, 62 insertions(+), 51 deletions(-) diff --git a/lexicons/app/bsky/actor/defs.json b/lexicons/app/bsky/actor/defs.json index 8def4f8..2c03bed 100644 --- a/lexicons/app/bsky/actor/defs.json +++ b/lexicons/app/bsky/actor/defs.json @@ -14,7 +14,8 @@ "banner": { "type": "string", "format": "uri" }, "followersCount": { "type": "integer" }, "followsCount": { "type": "integer" }, - "postsCount": { "type": "integer" } + "postsCount": { "type": "integer" }, + "pronouns": { "type": "string" } } } } diff --git a/priv/static/style.css b/priv/static/style.css index 42ed681..73e0008 100644 --- a/priv/static/style.css +++ b/priv/static/style.css @@ -31,6 +31,10 @@ /* divider (the only "line" in the system) */ --border: #494d64; --cursor: #a6da95; + + /* surfaces — layered shadows for depth */ + --shadow-ring: 0 0 0 1px color-mix(in srgb, var(--text) 8%, transparent); + --shadow-depth: 0 2px 4px 0px color-mix(in srgb, var(--bg-nav) 50%, transparent); } /* ============================================================ @@ -259,12 +263,16 @@ footer { #banner { margin-bottom: 1rem; + border-radius: 4px; + overflow: hidden; } #banner img { display: block; width: 100%; + height: 220px; object-fit: cover; + outline: none; } /* ============================================================ @@ -272,54 +280,38 @@ footer { ============================================================ */ #profile { - padding: 0.5rem 0 0; -} - -#profile .avatar { - width: 64px; - height: 64px; - border: 1px solid var(--border); - vertical-align: middle; - margin-right: 0.85rem; - transition-property: transform; - transition-duration: 150ms; - transition-timing-function: ease-out; + padding: 0; } -#profile > a:hover { - background-color: transparent; - color: transparent; +/* Avatar + text row */ +.profile-header { + display: flex; + align-items: flex-end; + gap: 0.85rem; + padding: 0.5rem 0 0 1.5rem; } -#profile .avatar:hover { - transform: scale(1.05); +.profile-info { + padding-bottom: 2px; } -#profile .avatar:active { - transform: scale(0.96); +#profile .avatar { + width: 64px; + height: 64px; + flex-shrink: 0; + border-radius: 4px; + object-fit: cover; } .profile-name { font-size: 1.25rem; color: var(--text); font-weight: 700; -} - -.profile-name:hover { - background: none; - color: var(--accent); -} - -#profile h1 { - display: inline; - font-size: 1rem; - vertical-align: middle; margin: 0; } -#profile h1 a:hover { - background: none; - color: var(--accent); +.handle { + margin: 0.15rem 0 0; } .handle a { @@ -332,11 +324,19 @@ footer { color: var(--bg); } +.handle .pronouns { + font-size: 0.85rem; + color: var(--text-dimmer); + margin-left: 0.15rem; +} + .description { font-size: 0.88rem; color: var(--text-dim); line-height: 1.55; - margin: 0.4rem 0; + margin: 0.6rem 0 0; + padding-left: 1.5rem; + text-wrap: pretty; } .profile-stats { diff --git a/shared/src/gen/actor/defs.gleam b/shared/src/gen/actor/defs.gleam index 87ddf15..026618e 100644 --- a/shared/src/gen/actor/defs.gleam +++ b/shared/src/gen/actor/defs.gleam @@ -17,6 +17,7 @@ pub type ProfileViewDetailed { follows_count: Option(Int), handle: String, posts_count: Option(Int), + pronouns: Option(String), ) } @@ -30,6 +31,7 @@ pub fn profile_view_detailed_fields(value: ProfileViewDetailed) -> List(#(String internal.opt("followersCount", value.followers_count, json.int), internal.opt("followsCount", value.follows_count, json.int), internal.opt("postsCount", value.posts_count, json.int), + internal.opt("pronouns", value.pronouns, json.string), ]) } @@ -47,5 +49,6 @@ pub fn profile_view_detailed_decoder() -> decode.Decoder(ProfileViewDetailed) { use follows_count <- decode.optional_field("followsCount", option.None, decode.optional(decode.int)) use handle <- decode.field("handle", decode.string) use posts_count <- decode.optional_field("postsCount", option.None, decode.optional(decode.int)) - decode.success(ProfileViewDetailed(avatar:, banner:, description:, did:, display_name:, followers_count:, follows_count:, handle:, posts_count:)) + use pronouns <- decode.optional_field("pronouns", option.None, decode.optional(decode.string)) + decode.success(ProfileViewDetailed(avatar:, banner:, description:, did:, display_name:, followers_count:, follows_count:, handle:, posts_count:, pronouns:)) } diff --git a/shared/src/profile.gleam b/shared/src/profile.gleam index f6577ff..4c70e86 100644 --- a/shared/src/profile.gleam +++ b/shared/src/profile.gleam @@ -1,18 +1,15 @@ import gen/actor/defs.{type ProfileViewDetailed} -import gleam/option.{unwrap} +import gleam/option.{None, Some, unwrap} import lustre/attribute.{alt, class, href, id, src, target} import lustre/element.{type Element, fragment, none, text} -import lustre/element/html.{a, div, h1, img, p} +import lustre/element/html.{a, div, h1, img, p, span} pub fn profile(profile: ProfileViewDetailed) -> Element(msg) { let bsky_url = "https://bsky.app/profile/" <> profile.handle let avatar = case unwrap(profile.avatar, "") { "" -> none() - url -> - a([href(bsky_url), target("_blank")], [ - img([class("avatar"), src(url), alt("avatar")]), - ]) + url -> img([class("avatar"), src(url), alt("avatar")]) } let banner = case unwrap(profile.banner, "") { @@ -23,19 +20,27 @@ pub fn profile(profile: ProfileViewDetailed) -> Element(msg) { ]) } + let pronouns_el = case profile.pronouns { + Some(pronouns) -> span([class("pronouns")], [text("(" <> pronouns <> ")")]) + None -> none() + } + div([id("profile-section")], [ fragment([ banner, div([id("profile")], [ - avatar, - h1([], [ - a([href(bsky_url), target("_blank"), class("profile-name")], [ - text(unwrap(profile.display_name, profile.handle)), - ]), - ]), - p([class("handle")], [ - a([href(bsky_url), target("_blank")], [ - text("@" <> profile.handle), + div([class("profile-header")], [ + avatar, + div([class("profile-info")], [ + h1([class("profile-name")], [ + text(unwrap(profile.display_name, profile.handle)), + ]), + p([class("handle")], [ + a([href(bsky_url), target("_blank")], [ + text("@" <> profile.handle), + ]), + pronouns_el, + ]), ]), ]), p([class("description")], [text(unwrap(profile.description, ""))]), diff --git a/shared/test/encode_test.gleam b/shared/test/encode_test.gleam index 3e072c0..e6ebc9e 100644 --- a/shared/test/encode_test.gleam +++ b/shared/test/encode_test.gleam @@ -18,6 +18,7 @@ fn sample_profile() -> ProfileViewDetailed { followers_count: Some(100), follows_count: Some(50), posts_count: Some(25), + pronouns: Some("they/them"), ) } @@ -102,6 +103,7 @@ pub fn round_trip_zero_counts_test() { followers_count: Some(0), follows_count: Some(0), posts_count: Some(0), + pronouns: None, ) let original = HydrationModel(profile: profile, plays: [], repos: []) let json = encode.encode_hydration_model(original) -- 2.51.2