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 ── %>