Add contribution field visualization to user profile sidebar

Seto Elkahfi committed Apr 15, 2026 at 22:47 UTC c893ad6bf3618f4fa4eaab5aa5b7b89350639775
1 file changed +89 -13
app/views/users/show.html.erb
+89 -13
index 263e513..fe183d7 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,5 +1,29 @@ <% content_for :title, @profile_user.display_name_or_username %> +<% + contribution_columns = [ + [0, 0, 1, 0, 1, 0, 0], + [0, 1, 1, 0, 1, 1, 0], + [0, 1, 2, 1, 1, 1, 0], + [0, 2, 1, 2, 2, 1, 0], + [1, 1, 2, 2, 1, 2, 1], + [0, 2, 2, 3, 2, 2, 1], + [1, 2, 3, 2, 3, 2, 1], + [1, 2, 3, 3, 2, 3, 2], + [1, 3, 2, 3, 3, 3, 2], + [2, 3, 3, 4, 3, 4, 3], + [2, 3, 4, 4, 4, 4, 3], + [3, 4, 4, 4, 4, 4, 4] + ] + contribution_palette = { + 0 => { text: "#3a3d30", background: "#151713", border: "#23261e" }, + 1 => { text: "#6f7552", background: "#1b1f18", border: "#343828" }, + 2 => { text: "#9ea16b", background: "#23281d", border: "#4d5537" }, + 3 => { text: "#c9cc8a", background: "#2d3323", border: "#71794f" }, + 4 => { text: "#f1f3b2", background: "#394128", border: "#9fa96a" } + } +%> + <div class="max-w-6xl mx-auto px-4 sm:px-6 py-10" data-controller="tabs" data-tabs-active-value="<%= params[:tab] || 'feed' %>"> @@ -8,20 +32,72 @@ <%# ── Sidebar ── %> <aside class="lg:col-span-1"> - <img src="<%= @profile_user.avatar_url_or_default %>" - alt="<%= @profile_user.display_name_or_username %>" - class="w-full max-w-[200px] rounded-full border border-surface-600 mb-4"> - <h1 class="text-xl font-semibold text-gray-100"><%= @profile_user.display_name_or_username %></h1> - <p class="text-gray-400 text-sm mb-4"><%= @profile_user.username %></p> - - <% if @profile_user.email.present? %> - <div class="flex items-center gap-2 text-sm text-gray-400"> - <svg class="w-4 h-4 shrink-0" viewBox="0 0 16 16" fill="currentColor"> - <path d="M1.75 2h12.5c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0 1 14.25 14H1.75A1.75 1.75 0 0 1 0 12.25v-8.5C0 2.784.784 2 1.75 2ZM1.5 12.251c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V5.809L8.38 9.397a.75.75 0 0 1-.76 0L1.5 5.809v6.442Zm13-8.181v-.32a.25.25 0 0 0-.25-.25H1.75a.25.25 0 0 0-.25.25v.32L8 7.88Z"/> - </svg> - <%= @profile_user.email %> + <div class="grid grid-cols-[minmax(0,1fr)_132px] gap-4 items-start lg:block"> + <div class="min-w-0"> + <img src="<%= @profile_user.avatar_url_or_default %>" + alt="<%= @profile_user.display_name_or_username %>" + class="w-full max-w-[200px] rounded-full border border-surface-600 mb-4"> + <h1 class="text-xl font-semibold text-gray-100"><%= @profile_user.display_name_or_username %></h1> + <p class="text-gray-400 text-sm mb-4"><%= @profile_user.username %></p> + + <% if @profile_user.email.present? %> + <div class="flex items-center gap-2 text-sm text-gray-400"> + <svg class="w-4 h-4 shrink-0" viewBox="0 0 16 16" fill="currentColor"> + <path d="M1.75 2h12.5c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0 1 14.25 14H1.75A1.75 1.75 0 0 1 0 12.25v-8.5C0 2.784.784 2 1.75 2ZM1.5 12.251c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V5.809L8.38 9.397a.75.75 0 0 1-.76 0L1.5 5.809v6.442Zm13-8.181v-.32a.25.25 0 0 0-.25-.25H1.75a.25.25 0 0 0-.25.25v.32L8 7.88Z"/> + </svg> + <%= @profile_user.email %> + </div> + <% end %> + </div> + + <div class="card p-3 lg:hidden"> + <div class="flex items-center justify-between mb-2"> + <span class="text-[10px] uppercase tracking-[0.22em] text-gray-500">Activity</span> + <span class="text-[10px] text-[#f1f3b2]">Latest peak</span> + </div> + <div class="grid gap-1 justify-end" style="grid-template-columns: repeat(<%= contribution_columns.length %>, minmax(0, 1fr));"> + <% contribution_columns.each do |week| %> + <div class="grid gap-1" style="grid-template-rows: repeat(7, minmax(0, 1fr));"> + <% week.each do |level| %> + <% colors = contribution_palette[level] %> + <div class="h-5 w-2.5 rounded-full border flex items-center justify-center font-semibold text-[11px] leading-none" + style="color: <%= colors[:text] %>; background-color: <%= colors[:background] %>; border-color: <%= colors[:border] %>;"> + <span style="writing-mode: vertical-rl; transform: rotate(180deg);">u</span> + </div> + <% end %> + </div> + <% end %> + </div> + <p class="mt-2 text-[10px] leading-4 text-gray-500">Ascending recent streak, rightmost column is the freshest contribution.</p> </div> - <% end %> + </div> + + <div class="card mt-6 p-5 hidden lg:block"> + <div class="flex items-center justify-between mb-4"> + <div> + <p class="text-[10px] uppercase tracking-[0.22em] text-gray-500">Contribution Field</p> + <p class="mt-1 text-xs text-gray-400">PO-inspired ramp using vertical <span class="font-mono text-[#f1f3b2]">u</span> marks.</p> + </div> + <span class="badge-gray text-[10px]">12 weeks</span> + </div> + <div class="grid gap-1" style="grid-template-columns: repeat(<%= contribution_columns.length %>, minmax(0, 1fr));"> + <% contribution_columns.each do |week| %> + <div class="grid gap-1" style="grid-template-rows: repeat(7, minmax(0, 1fr));"> + <% week.each do |level| %> + <% colors = contribution_palette[level] %> + <div class="h-7 rounded-full border flex items-center justify-center font-semibold text-sm leading-none" + style="color: <%= colors[:text] %>; background-color: <%= colors[:background] %>; border-color: <%= colors[:border] %>;"> + <span style="writing-mode: vertical-rl; transform: rotate(180deg);">u</span> + </div> + <% end %> + </div> + <% end %> + </div> + <div class="mt-4 flex items-center justify-between text-[10px] uppercase tracking-[0.18em] text-gray-500"> + <span>Older</span> + <span>Recent high</span> + </div> + </div> </aside> <%# ── Main ── %>