main
erb 376 lines 20.5 KB
Raw
1 <% content_for :title, @profile_user.display_name_or_username %>
2
3 <%
4 contribution_columns = [
5 [0, 0, 1, 0, 1, 0, 0],
6 [0, 1, 1, 0, 1, 1, 0],
7 [0, 1, 2, 1, 1, 1, 0],
8 [0, 2, 1, 2, 2, 1, 0],
9 [1, 1, 2, 2, 1, 2, 1],
10 [0, 2, 2, 3, 2, 2, 1],
11 [1, 2, 3, 2, 3, 2, 1],
12 [1, 2, 3, 3, 2, 3, 2],
13 [1, 3, 2, 3, 3, 3, 2],
14 [2, 3, 3, 4, 3, 4, 3],
15 [2, 3, 4, 4, 4, 4, 3],
16 [3, 4, 4, 4, 4, 4, 4]
17 ]
18 contribution_palette = {
19 0 => { text: "#3a3d30", background: "#151713", border: "#23261e" },
20 1 => { text: "#6f7552", background: "#1b1f18", border: "#343828" },
21 2 => { text: "#9ea16b", background: "#23281d", border: "#4d5537" },
22 3 => { text: "#c9cc8a", background: "#2d3323", border: "#71794f" },
23 4 => { text: "#f1f3b2", background: "#394128", border: "#9fa96a" }
24 }
25 %>
26
27 <div class="max-w-6xl mx-auto px-4 sm:px-6 py-10"
28 data-controller="tabs"
29 data-tabs-active-value="<%= params[:tab] || 'feed' %>">
30
31 <div class="grid grid-cols-1 lg:grid-cols-4 gap-10">
32
33 <%# ── Sidebar ── %>
34 <aside class="lg:col-span-1">
35 <div class="grid grid-cols-[minmax(0,1fr)_132px] gap-4 items-start lg:block">
36 <div class="min-w-0">
37 <img src="<%= @profile_user.avatar_url_or_default %>"
38 alt="<%= @profile_user.display_name_or_username %>"
39 class="w-full max-w-[200px] rounded-full border border-surface-600 mb-4">
40 <h1 class="text-xl font-semibold text-gray-100"><%= @profile_user.display_name_or_username %></h1>
41 <p class="text-gray-400 text-sm mb-4"><%= @profile_user.username %></p>
42
43 <% if @profile_user.email.present? %>
44 <div class="flex items-center gap-2 text-sm text-gray-400">
45 <svg class="w-4 h-4 shrink-0" viewBox="0 0 16 16" fill="currentColor">
46 <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"/>
47 </svg>
48 <%= @profile_user.email %>
49 </div>
50 <% end %>
51 </div>
52
53 <div class="card p-3 lg:hidden">
54 <div class="flex items-center justify-between mb-2">
55 <span class="text-[10px] uppercase tracking-[0.22em] text-gray-500">Activity</span>
56 <span class="text-[10px] text-[#f1f3b2]">Latest peak</span>
57 </div>
58 <div class="grid gap-1 justify-end" style="grid-template-columns: repeat(<%= contribution_columns.length %>, minmax(0, 1fr));">
59 <% contribution_columns.each do |week| %>
60 <div class="grid gap-1" style="grid-template-rows: repeat(7, minmax(0, 1fr));">
61 <% week.each do |level| %>
62 <% colors = contribution_palette[level] %>
63 <div class="h-5 w-2.5 rounded-full border flex items-center justify-center font-semibold text-[11px] leading-none"
64 style="color: <%= colors[:text] %>; background-color: <%= colors[:background] %>; border-color: <%= colors[:border] %>;">
65 <span style="writing-mode: vertical-rl; transform: rotate(180deg);">u</span>
66 </div>
67 <% end %>
68 </div>
69 <% end %>
70 </div>
71 <p class="mt-2 text-[10px] leading-4 text-gray-500">Ascending recent streak, rightmost column is the freshest contribution.</p>
72 </div>
73 </div>
74
75 <div class="card mt-6 p-5 hidden lg:block">
76 <div class="flex items-center justify-between mb-4">
77 <div>
78 <p class="text-[10px] uppercase tracking-[0.22em] text-gray-500">Contribution Field</p>
79 <p class="mt-1 text-xs text-gray-400">PO-inspired ramp using vertical <span class="font-mono text-[#f1f3b2]">u</span> marks.</p>
80 </div>
81 <span class="badge-gray text-[10px]">12 weeks</span>
82 </div>
83 <div class="grid gap-1" style="grid-template-columns: repeat(<%= contribution_columns.length %>, minmax(0, 1fr));">
84 <% contribution_columns.each do |week| %>
85 <div class="grid gap-1" style="grid-template-rows: repeat(7, minmax(0, 1fr));">
86 <% week.each do |level| %>
87 <% colors = contribution_palette[level] %>
88 <div class="h-7 rounded-full border flex items-center justify-center font-semibold text-sm leading-none"
89 style="color: <%= colors[:text] %>; background-color: <%= colors[:background] %>; border-color: <%= colors[:border] %>;">
90 <span style="writing-mode: vertical-rl; transform: rotate(180deg);">u</span>
91 </div>
92 <% end %>
93 </div>
94 <% end %>
95 </div>
96 <div class="mt-4 flex items-center justify-between text-[10px] uppercase tracking-[0.18em] text-gray-500">
97 <span>Older</span>
98 <span>Recent high</span>
99 </div>
100 </div>
101 </aside>
102
103 <%# ── Main ── %>
104 <main class="lg:col-span-3">
105
106 <%# Tab navigation %>
107 <div class="flex items-center border-b border-surface-600 mb-6 gap-1">
108 <button data-tabs-target="tab"
109 data-tabs-key="feed"
110 data-action="click->tabs#switch"
111 class="tab-item active">
112 <svg class="inline-block w-3.5 h-3.5 mr-1.5 -mt-0.5" viewBox="0 0 16 16" fill="currentColor">
113 <path d="M0 3.75C0 2.784.784 2 1.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.25Zm1.75-.25a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25v-8.5a.25.25 0 0 0-.25-.25ZM3 6.25a.75.75 0 0 1 .75-.75h8.5a.75.75 0 0 1 0 1.5h-8.5A.75.75 0 0 1 3 6.25ZM3.75 9a.75.75 0 0 0 0 1.5h5a.75.75 0 0 0 0-1.5h-5Z"/>
114 </svg>
115 Feed
116 </button>
117 <button data-tabs-target="tab"
118 data-tabs-key="repositories"
119 data-action="click->tabs#switch"
120 class="tab-item">
121 <svg class="inline-block w-3.5 h-3.5 mr-1.5 -mt-0.5" viewBox="0 0 16 16" fill="currentColor">
122 <path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8Z"/>
123 </svg>
124 Repositories
125 <span class="badge-gray ml-1.5 align-middle"><%= @code_repositories.count %></span>
126 </button>
127 <button data-tabs-target="tab"
128 data-tabs-key="models"
129 data-action="click->tabs#switch"
130 class="tab-item">
131 <svg class="inline-block w-3.5 h-3.5 mr-1.5 -mt-0.5" viewBox="0 0 24 24" fill="none"
132 stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
133 <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/>
134 <path d="m3.27 6.96 8.73 5.05 8.73-5.05M12 22.08V12"/>
135 </svg>
136 Models
137 <span class="badge-gray ml-1.5 align-middle"><%= @model_repositories.count %></span>
138 </button>
139 </div>
140
141 <%# ── Feed panel ── %>
142 <div data-tabs-target="panel" data-tabs-key="feed">
143 <% if @profile_user.username == "sigit" %>
144 <%# Curated showcase feed for the demo profile. Every other profile
145 renders a real activity feed (@activity_items) built from its
146 repositories and commits. %>
147 <ol class="relative border-l border-surface-600 ml-3 space-y-0">
148
149 <%# Item: push commits %>
150 <li class="mb-8 ml-6">
151 <span class="absolute -left-3 flex h-6 w-6 items-center justify-center rounded-full border border-surface-500 bg-surface-700">
152 <svg class="w-3.5 h-3.5 text-brand-500" viewBox="0 0 16 16" fill="currentColor">
153 <path d="M1.643 3.143 .427 1.927A.25.25 0 0 0 0 2.104V5.75c0 .138.112.25.25.25h3.646a.25.25 0 0 0 .177-.427L2.715 4.215a6.5 6.5 0 1 1-1.18 4.458.75.75 0 1 0-1.493.154 8.001 8.001 0 1 0 1.6-5.684ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z"/>
154 </svg>
155 </span>
156 <div class="card px-4 py-3">
157 <p class="text-xs text-gray-500 mb-2">2 hours ago</p>
158 <p class="text-sm mb-3">
159 <span class="text-gray-400">Pushed </span>
160 <span class="font-semibold text-gray-200"><%= @feed_commits&.size || 0 %> commits</span>
161 <span class="text-gray-400"> to </span>
162 <% if @feed_repo %>
163 <%= link_to "@#{@profile_user.username}/#{@feed_repo.name}", repository_path(@profile_user.username, @feed_repo.name), class: "text-brand-500 hover:underline font-medium" %>
164 <% end %>
165 <span class="text-gray-400"> on </span>
166 <code class="bg-surface-600 px-1.5 py-0.5 rounded text-xs text-gray-200"><%= @feed_repo&.default_branch || "main" %></code>
167 </p>
168 <% if @feed_commits.present? %>
169 <ul class="space-y-1.5 border-t border-surface-600 pt-3">
170 <% @feed_commits.each do |commit| %>
171 <li class="flex items-start gap-2 text-xs text-gray-400">
172 <%= link_to commit[:short_sha], repository_commit_path(@profile_user.username, @feed_repo.name, commit[:sha]), class: "text-brand-400 shrink-0 font-mono hover:underline" %>
173 <span><%= commit[:subject] %></span>
174 </li>
175 <% end %>
176 </ul>
177 <% end %>
178 </div>
179 </li>
180
181 <%# Item: opened issue %>
182 <li class="mb-8 ml-6">
183 <span class="absolute -left-3 flex h-6 w-6 items-center justify-center rounded-full border border-surface-500 bg-surface-700">
184 <svg class="w-3.5 h-3.5 text-emerald-400" viewBox="0 0 16 16" fill="currentColor">
185 <path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"/>
186 <path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"/>
187 </svg>
188 </span>
189 <div class="card px-4 py-3">
190 <p class="text-xs text-gray-500 mb-2">yesterday</p>
191 <p class="text-sm">
192 <span class="text-gray-400">Opened issue </span>
193 <span class="text-brand-500 font-medium">#14 Add SSH key management</span>
194 <span class="text-gray-400"> in </span>
195 <%= link_to "@#{@profile_user.username}/sigit", repository_path(@profile_user.username, "sigit"), class: "text-brand-500 hover:underline font-medium" %>
196 </p>
197 </div>
198 </li>
199
200 <%# Item: merged PR %>
201 <li class="mb-8 ml-6">
202 <span class="absolute -left-3 flex h-6 w-6 items-center justify-center rounded-full border border-surface-500 bg-surface-700">
203 <svg class="w-3.5 h-3.5 text-purple-400" viewBox="0 0 16 16" fill="currentColor">
204 <path d="M5.45 5.154A4.25 4.25 0 0 0 9.25 7.5h1.378a2.251 2.251 0 1 1 0 1.5H9.25A5.734 5.734 0 0 1 5 7.123v3.505a2.25 2.25 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.95-.218ZM4.25 13.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm8.5-4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5ZM5 3.25a.75.75 0 1 0 0 .005V3.25Z"/>
205 </svg>
206 </span>
207 <div class="card px-4 py-3">
208 <p class="text-xs text-gray-500 mb-2">3 days ago</p>
209 <p class="text-sm">
210 <span class="text-gray-400">Merged pull request </span>
211 <span class="text-brand-500 font-medium">#11 Improve repository listing performance</span>
212 <span class="text-gray-400"> in </span>
213 <%= link_to "@#{@profile_user.username}/sigit", repository_path(@profile_user.username, "sigit"), class: "text-brand-500 hover:underline font-medium" %>
214 </p>
215 </div>
216 </li>
217
218 <%# Item: created repo %>
219 <li class="mb-8 ml-6">
220 <span class="absolute -left-3 flex h-6 w-6 items-center justify-center rounded-full border border-surface-500 bg-surface-700">
221 <svg class="w-3.5 h-3.5 text-gray-400" viewBox="0 0 16 16" fill="currentColor">
222 <path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8Z"/>
223 </svg>
224 </span>
225 <div class="card px-4 py-3">
226 <p class="text-xs text-gray-500 mb-2">1 week ago</p>
227 <p class="text-sm">
228 <span class="text-gray-400">Created repository </span>
229 <%= link_to "@#{@profile_user.username}/nord", repository_path(@profile_user.username, "nord"), class: "text-brand-500 hover:underline font-medium" %>
230 </p>
231 </div>
232 </li>
233
234 <%# Item: starred repo %>
235 <li class="ml-6">
236 <span class="absolute -left-3 flex h-6 w-6 items-center justify-center rounded-full border border-surface-500 bg-surface-700">
237 <svg class="w-3.5 h-3.5 text-amber-400" viewBox="0 0 16 16" fill="currentColor">
238 <path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"/>
239 </svg>
240 </span>
241 <div class="card px-4 py-3">
242 <p class="text-xs text-gray-500 mb-2">2 weeks ago</p>
243 <p class="text-sm">
244 <span class="text-gray-400">Starred </span>
245 <%= link_to "bartowski/Qwen2.5-3B-Instruct-GGUF", repository_path("bartowski", "Qwen2.5-3B-Instruct-GGUF"), class: "text-brand-500 hover:underline font-medium" %>
246 </p>
247 </div>
248 </li>
249
250 </ol>
251 <% elsif @activity_items.present? %>
252 <ol class="relative border-l border-surface-600 ml-3 space-y-0">
253 <%= render partial: "users/activity_item", collection: @activity_items, as: :item, locals: { profile_user: @profile_user } %>
254 </ol>
255 <% else %>
256 <div class="card px-4 py-8 text-center text-sm text-gray-500">
257 No activity yet.
258 </div>
259 <% end %>
260 </div>
261
262 <%# ── Repositories panel (code) ── %>
263 <div data-tabs-target="panel" data-tabs-key="repositories" class="hidden">
264 <div class="flex items-center justify-between mb-4">
265 <h2 class="text-base font-semibold text-gray-100">
266 Repositories
267 <span class="badge-gray ml-1 align-middle"><%= @code_repositories.count %></span>
268 </h2>
269 <% if signed_in? && current_user == @profile_user %>
270 <%= link_to new_repository_path, class: "btn-secondary text-xs" do %>New<% end %>
271 <% end %>
272 </div>
273
274 <% if @code_repositories.any? %>
275 <div class="space-y-3">
276 <% @code_repositories.each do |repo| %>
277 <div class="card px-5 py-4 hover:border-surface-400 transition-colors">
278 <div class="flex items-start justify-between gap-4">
279 <div class="min-w-0 flex-1">
280 <div class="flex items-center gap-2 mb-1">
281 <%= link_to repo.name,
282 repository_path(@profile_user.username, repo.name),
283 class: "font-semibold text-brand-500 hover:underline" %>
284 <% if repo.is_private %>
285 <span class="badge-gray">Private</span>
286 <% end %>
287 </div>
288 <% if repo.description.present? %>
289 <p class="text-sm text-gray-400"><%= repo.description %></p>
290 <% end %>
291 </div>
292 <div class="text-xs text-gray-500 shrink-0">
293 <%= time_ago_in_words(repo.updated_at) %> ago
294 </div>
295 </div>
296 </div>
297 <% end %>
298 </div>
299 <% else %>
300 <div class="card px-6 py-12 text-center">
301 <p class="text-sm text-gray-500">No public repositories.</p>
302 </div>
303 <% end %>
304 </div>
305
306 <%# ── Models panel ── %>
307 <div data-tabs-target="panel" data-tabs-key="models" class="hidden">
308 <div class="flex items-center justify-between mb-4">
309 <h2 class="text-base font-semibold text-gray-100">
310 Models
311 <span class="badge-gray ml-1 align-middle"><%= @model_repositories.count %></span>
312 </h2>
313 <% if signed_in? && current_user == @profile_user %>
314 <%= link_to new_repository_path(kind: "model"), class: "btn-secondary text-xs" do %>New<% end %>
315 <% end %>
316 </div>
317
318 <% if @model_repositories.any? %>
319 <div class="space-y-3">
320 <% @model_repositories.each do |repo| %>
321 <% card = repo.model_card %>
322 <div class="card px-5 py-4 hover:border-surface-400 transition-colors">
323 <div class="flex items-start justify-between gap-4">
324 <div class="min-w-0 flex-1">
325 <div class="flex items-center gap-2 mb-1">
326 <svg class="w-4 h-4 text-brand-500 shrink-0" viewBox="0 0 24 24" fill="none"
327 stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
328 <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/>
329 <path d="m3.27 6.96 8.73 5.05 8.73-5.05M12 22.08V12"/>
330 </svg>
331 <%= link_to repo.name,
332 repository_path(@profile_user.username, repo.name),
333 class: "font-semibold text-brand-500 hover:underline" %>
334 <% if repo.is_private %>
335 <span class="badge-gray">Private</span>
336 <% end %>
337 </div>
338 <div class="flex items-center gap-2 text-xs text-gray-500 flex-wrap">
339 <% if (label = pipeline_label(card.pipeline_tag)) %>
340 <span class="text-brand-400"><%= label %></span>
341 <span>&middot;</span>
342 <% end %>
343 <% if card.library_name.present? %>
344 <span><%= card.library_name %></span>
345 <span>&middot;</span>
346 <% end %>
347 <span class="flex items-center gap-1">
348 <svg class="w-3 h-3" viewBox="0 0 16 16" fill="currentColor"><path d="M7.47 10.78a.75.75 0 0 0 1.06 0l3.75-3.75a.75.75 0 0 0-1.06-1.06L8.75 8.44V1.75a.75.75 0 0 0-1.5 0v6.69L4.78 5.97a.75.75 0 0 0-1.06 1.06ZM3.75 13a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5Z"/></svg>
349 <%= humanize_count(repo.downloads_count) %>
350 </span>
351 <span class="flex items-center gap-1">
352 <svg class="w-3 h-3" viewBox="0 0 16 16" fill="currentColor"><path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"/></svg>
353 <%= humanize_count(repo.stars_count) %>
354 </span>
355 </div>
356 </div>
357 <div class="text-xs text-gray-500 shrink-0">
358 <%= time_ago_in_words(repo.updated_at) %> ago
359 </div>
360 </div>
361 </div>
362 <% end %>
363 </div>
364 <% else %>
365 <div class="card px-6 py-12 text-center">
366 <p class="text-sm text-gray-500 mb-1">No models yet.</p>
367 <% if signed_in? && current_user == @profile_user %>
368 <p class="text-xs text-gray-500"><%= link_to "Publish a model", new_repository_path(kind: "model"), class: "text-brand-500 hover:underline" %> to share open weights.</p>
369 <% end %>
370 </div>
371 <% end %>
372 </div>
373
374 </main>
375 </div>
376 </div>