Skip to main content

max / makenotwork

ux: migrate project-page + insertion_list inline styles to CSS
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-20 19:07 UTC
Commit: 21b99d5e1bb2704f9b4961f104602ecb3cd1deeb
Parent: ea5c3a4
3 files changed, +128 insertions, -96 deletions
@@ -9257,3 +9257,91 @@ button.saved { border-color: var(--highlight); color: var(--highlight); }
9257 9257 opacity: 0.6;
9258 9258 margin-top: 0.25rem;
9259 9259 }
9260 +
9261 + /* ===========================================
9262 + PROJECT PAGE (replaces inline styles in pages/project.html)
9263 + =========================================== */
9264 +
9265 + .project-page .store-cover {
9266 + width: 120px;
9267 + height: 120px;
9268 + border-radius: 8px;
9269 + object-fit: cover;
9270 + margin-bottom: var(--space-5);
9271 + }
9272 + .project-page .store-actions .secondary {
9273 + display: inline-block;
9274 + padding: 0.5rem 1rem;
9275 + text-decoration: none;
9276 + }
9277 + .project-page .follow-btn.is-following {
9278 + opacity: 0.7;
9279 + }
9280 + .project-page .follower-count-muted {
9281 + font-size: 0.85rem;
9282 + opacity: 0.7;
9283 + padding: 0.5rem 0;
9284 + }
9285 + .project-page .item-tags .tag {
9286 + text-decoration: none;
9287 + color: inherit;
9288 + }
9289 + .project-page .promo-details {
9290 + font-size: 0.85rem;
9291 + margin-bottom: var(--space-2);
9292 + }
9293 + .project-page .promo-details summary {
9294 + cursor: pointer;
9295 + opacity: 0.7;
9296 + }
9297 + .project-page .promo-input {
9298 + width: 100%;
9299 + margin-top: 0.4rem;
9300 + padding: 0.3rem 0.5rem;
9301 + font-size: 0.85rem;
9302 + text-transform: uppercase;
9303 + }
9304 + .project-page .tier-card .login-cta-btn {
9305 + width: 100%;
9306 + }
9307 + .project-page .store-footer-links {
9308 + margin-top: var(--space-2);
9309 + }
9310 +
9311 + /* ===========================================
9312 + INSERTION LIST (replaces inline styles in partials/insertion_list.html)
9313 + =========================================== */
9314 +
9315 + .insertion-list-heading {
9316 + font-family: var(--font-mono);
9317 + font-size: 1rem;
9318 + margin-bottom: var(--space-4);
9319 + }
9320 + .insertion-list-toolbar {
9321 + margin-bottom: var(--space-4);
9322 + }
9323 + .insertion-list-upload {
9324 + font-family: var(--font-mono);
9325 + }
9326 + .insertion-list-empty {
9327 + font-family: var(--font-body);
9328 + color: var(--text-muted);
9329 + }
9330 + .insertion-list-table {
9331 + width: 100%;
9332 + font-family: var(--font-body);
9333 + }
9334 + .insertion-list-table th {
9335 + text-align: left;
9336 + font-family: var(--font-mono);
9337 + font-size: 0.875rem;
9338 + }
9339 + .insertion-list-table th.insertion-list-th-actions {
9340 + text-align: right;
9341 + }
9342 + .insertion-list-table .insertion-list-actions {
9343 + text-align: right;
9344 + }
9345 + .insertion-list-table .insertion-list-action-btn {
9346 + font-family: var(--font-mono);
9347 + }
@@ -1,7 +1,7 @@
1 1 {% extends "base.html" %}
2 2
3 3 {% block title %}{{ project.title }} - {{ creator_username }}{% endblock %}
4 - {% block body_attrs %} class="padded-page"{% endblock %}
4 + {% block body_attrs %} class="padded-page project-page"{% endblock %}
5 5
6 6 {% block head %}
7 7 <meta property="og:title" content="{{ project.title }} by {{ creator_username }}">
@@ -41,60 +41,6 @@
41 41 }
42 42 </script>
43 43 <link rel="alternate" type="application/rss+xml" title="{{ project.title }} - RSS Feed" href="/p/{{ project.slug }}/rss">
44 - <style>
45 - .store-header { margin-bottom: 3rem; }
46 - .store-title { font-size: 3rem; margin-bottom: 0.5rem; }
47 - .store-meta { font-size: 0.9rem; opacity: 0.7; margin-bottom: 1.5rem; }
48 - .store-meta a { color: var(--detail); }
49 - .store-description { font-size: 1.1rem; max-width: 800px; margin-bottom: 2rem; text-align: left; }
50 - .store-actions { display: flex; gap: 1rem; flex-wrap: wrap; }
51 - .items-section { margin-bottom: 3rem; }
52 - .items-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 2rem; }
53 - .item-card { background: var(--light-background); transition: background 0.2s ease; cursor: pointer; display: flex; flex-direction: column; height: 100%; }
54 - .item-card:hover { background: var(--surface-muted); }
55 - a.item-thumbnail { width: 100%; height: 300px; background: var(--border); display: flex; align-items: center; justify-content: center; font-size: 3rem; opacity: 0.3; text-decoration: none; }
56 - a.item-thumbnail:hover { opacity: 0.5; }
57 - .item-content { padding: 1.5rem; display: flex; flex-direction: column; flex-grow: 1; }
58 - .item-title { font-size: 1.2rem; margin-bottom: 0.5rem; font-family: var(--font-heading); font-weight: bold; }
59 - .item-title a { color: inherit; text-decoration: none; }
60 - .item-title a:hover { text-decoration: underline; }
61 - .item-meta { font-size: 0.85rem; opacity: 0.7; margin-bottom: 1rem; }
62 - .item-tags { margin-bottom: 1rem; }
63 - .item-description { font-size: 0.9rem; margin-bottom: 1rem; opacity: 0.8; text-align: left; flex-grow: 1; }
64 - .item-footer { display: flex; justify-content: space-between; align-items: center; margin-top: auto; }
65 - .item-price { font-size: 1.3rem; }
66 - .item-stats { font-size: 0.85rem; opacity: 0.6; }
67 - .store-footer { margin-top: 4rem; padding-top: 2rem; border-top: 1px solid var(--border); opacity: 0.6; }
68 - .store-footer a { color: var(--detail); }
69 - .item-content button.primary { width: 100%; margin-top: 1rem; }
70 - .item-content button.secondary { width: 100%; margin-top: 1rem; }
71 - .project-sections { background: var(--light-background); padding: 2rem; margin-bottom: 2rem; }
72 - .section-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-bottom: 1.5rem; flex-wrap: wrap; }
73 - .section-tab { background: none; border: none; padding: 0.6rem 1.2rem; cursor: pointer; font-size: 0.95rem; opacity: 0.6; border-bottom: 2px solid transparent; font-family: var(--font-body); color: var(--text); }
74 - .section-tab.active { opacity: 1; border-bottom-color: var(--detail); }
75 - .section-tab:hover { opacity: 0.9; }
76 - .section-panel { display: none; }
77 - .section-panel.active { display: block; }
78 - .section-panel p { margin-bottom: 1rem; }
79 - .section-panel ul, .section-panel ol { margin-left: 1.5rem; margin-bottom: 1rem; }
80 - .section-panel li { margin-bottom: 0.5rem; }
81 - .tiers-section { margin-bottom: 3rem; }
82 - .tiers-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.5rem; }
83 - .tier-card { background: var(--light-background); padding: 2rem; display: flex; flex-direction: column; }
84 - .tier-name { font-family: var(--font-heading); font-weight: bold; font-size: 1.3rem; margin-bottom: 0.5rem; }
85 - .tier-price { font-size: 1.5rem; margin-bottom: 1rem; }
86 - .tier-description { font-size: 0.9rem; opacity: 0.8; margin-bottom: 1.5rem; flex-grow: 1; text-align: left; }
87 - .tier-card form { margin-top: auto; }
88 - .tier-card button { width: 100%; }
89 - .tier-active-badge { font-size: 0.85rem; padding: 0.5rem 1rem; background: var(--surface-muted); text-align: center; opacity: 0.7; }
90 - @media (max-width: 480px) {
91 - .store-title { font-size: 2rem; }
92 - .items-grid { grid-template-columns: 1fr; }
93 - a.item-thumbnail { height: 200px; }
94 - .store-description { font-size: 1rem; }
95 - .tiers-grid { grid-template-columns: 1fr; }
96 - }
97 - </style>
98 44 {% endblock %}
99 45
100 46 {% block content %}
@@ -103,8 +49,7 @@
103 49 <div class="container">
104 50 <header class="store-header">
105 51 {% if let Some(img_url) = project.cover_image_url %}
106 - <img src="{{ img_url }}" alt="{{ project.title }}"
107 - style="width: 120px; height: 120px; border-radius: 8px; object-fit: cover; margin-bottom: 1.5rem;">
52 + <img src="{{ img_url }}" alt="{{ project.title }}" class="store-cover">
108 53 {% endif %}
109 54 <h1 class="store-title">{{ project.title }}<span class="dot">.</span></h1>
110 55 <div class="store-meta">
@@ -114,31 +59,30 @@
114 59 <p class="store-description">{{ project.description }}</p>
115 60 <div class="store-actions">
116 61 {% if is_owner %}
117 - <a href="/dashboard/project/{{ project.slug }}" class="secondary" style="display: inline-block; padding: 0.5rem 1rem; text-decoration: none;">Edit Project</a>
62 + <a href="/dashboard/project/{{ project.slug }}" class="secondary">Edit Project</a>
118 63 {% endif %}
119 64 {% if session_user.is_some() %}
120 65 {% if is_following %}
121 - <button class="secondary follow-btn"
66 + <button class="secondary follow-btn is-following"
122 67 hx-delete="/api/follow/project/{{ project_id }}"
123 - hx-swap="outerHTML"
124 - style="opacity: 0.7;">Following ({{ follower_count }})</button>
68 + hx-swap="outerHTML">Following ({{ follower_count }})</button>
125 69 {% else %}
126 70 <button class="secondary follow-btn"
127 71 hx-post="/api/follow/project/{{ project_id }}"
128 72 hx-swap="outerHTML">Follow ({{ follower_count }})</button>
129 73 {% endif %}
130 74 {% else if follower_count > 0 %}
131 - <span style="font-size: 0.85rem; opacity: 0.7; padding: 0.5rem 0;">{{ follower_count }} followers</span>
75 + <span class="follower-count-muted">{{ follower_count }} followers</span>
132 76 {% endif %}
133 - <a href="/p/{{ project.slug }}/rss" class="secondary" style="display: inline-block; padding: 0.5rem 1rem; text-decoration: none;">RSS Feed</a>
77 + <a href="/p/{{ project.slug }}/rss" class="secondary">RSS Feed</a>
134 78 {% if has_blog_posts %}
135 - <a href="/p/{{ project.slug }}/blog" class="secondary" style="display: inline-block; padding: 0.5rem 1rem; text-decoration: none;">Blog</a>
79 + <a href="/p/{{ project.slug }}/blog" class="secondary">Blog</a>
136 80 {% endif %}
137 81 {% for repo in &git_repos %}
138 - <a href="{{ repo.1 }}" class="secondary" style="display: inline-block; padding: 0.5rem 1rem; text-decoration: none;">Git ({{ repo.0 }})</a>
82 + <a href="{{ repo.1 }}" class="secondary">Git ({{ repo.0 }})</a>
139 83 {% endfor %}
140 84 {% if let Some(url) = community_url %}
141 - <a href="{{ url }}" class="secondary" style="display: inline-block; padding: 0.5rem 1rem; text-decoration: none;">Community</a>
85 + <a href="{{ url }}" class="secondary">Community</a>
142 86 {% endif %}
143 87 {% include "partials/tip_button.html" %}
144 88 </div>
@@ -148,7 +92,7 @@
148 92 <section class="project-sections">
149 93 <div class="section-tabs">
150 94 {% for section in sections %}
151 - <button class="section-tab{% if loop.first %} active{% endif %}"
95 + <button class="section-tab{% if loop.first %} is-selected{% endif %}"
152 96 data-tab="section-{{ section.slug }}"
153 97 onclick="switchSectionTab(this, 'section-{{ section.slug }}')">{{ section.title }}</button>
154 98 {% endfor %}
@@ -161,9 +105,11 @@
161 105 </section>
162 106 <script>
163 107 function switchSectionTab(btn, panelId) {
164 - document.querySelectorAll('.project-sections .section-tab').forEach(function(t) { t.classList.remove('active'); });
108 + // Tab selection uses .is-selected (charter); panel visibility
109 + // still uses .active (display: none/block toggle).
110 + document.querySelectorAll('.project-sections .section-tab').forEach(function(t) { t.classList.remove('is-selected'); });
165 111 document.querySelectorAll('.project-sections .section-panel').forEach(function(p) { p.classList.remove('active'); });
166 - btn.classList.add('active');
112 + btn.classList.add('is-selected');
167 113 var panel = document.getElementById(panelId);
168 114 if (panel) panel.classList.add('active');
169 115 history.replaceState(null, '', '#' + panelId);
@@ -184,7 +130,7 @@
184 130 <h2 class="section-header">Available Items</h2>
185 131 <div class="view-controls">
186 132 <button type="button" class="view-btn" data-view="list" title="List view">|||</button>
187 - <button type="button" class="view-btn active" data-view="grid" title="Grid view">:::</button>
133 + <button type="button" class="view-btn is-selected" data-view="grid" title="Grid view">:::</button>
188 134 </div>
189 135 </div>
190 136 <div class="items-container items-grid-view" id="items-container">
@@ -197,7 +143,7 @@
197 143 <div class="item-meta">{{ item.item_type }}{% if item.bundle_item_count > 0 %} ({{ item.bundle_item_count }} items){% endif %} &middot; {{ item.release_date }}</div>
198 144 <div class="item-tags">
199 145 {% for tag in item.tags %}
200 - <a href="/discover?tag={{ tag.slug }}" class="tag" style="text-decoration: none; color: inherit;">{{ tag.name }}</a>
146 + <a href="/discover?tag={{ tag.slug }}" class="tag">{{ tag.name }}</a>
201 147 {% endfor %}
202 148 </div>
203 149 <p class="item-description">{{ item.description }}</p>
@@ -252,15 +198,14 @@
252 198 {% else if session_user.is_some() %}
253 199 <form method="post" action="/stripe/subscribe/{{ tier.id }}">
254 200 <input type="hidden" name="_csrf" value="{{ csrf_token.as_deref().unwrap_or_default() }}">
255 - <details style="font-size: 0.85rem; margin-bottom: 0.5rem;">
256 - <summary style="cursor: pointer; opacity: 0.7;">Have a promo code?</summary>
257 - <input type="text" name="promo_code" placeholder="e.g. TRIAL14"
258 - style="width: 100%; margin-top: 0.4rem; padding: 0.3rem 0.5rem; font-size: 0.85rem; text-transform: uppercase;">
201 + <details class="promo-details">
202 + <summary>Have a promo code?</summary>
203 + <input type="text" name="promo_code" placeholder="e.g. TRIAL14" class="promo-input">
259 204 </details>
260 - <button class="primary" type="submit">Subscribe</button>
205 + <button class="primary" type="submit" data-loading-text="Redirecting to Stripe…">Subscribe</button>
261 206 </form>
262 207 {% else %}
263 - <a href="/login"><button class="primary" style="width: 100%;">Log in to Subscribe</button></a>
208 + <a href="/login"><button class="primary login-cta-btn">Log in to Subscribe</button></a>
264 209 {% endif %}
265 210 </div>
266 211 {% endfor %}
@@ -270,11 +215,11 @@
270 215
271 216 <footer class="store-footer">
272 217 <p>Powered by <a href="/">Makenot<span class="dot">.</span>work</a> &middot; Fair distribution for creatives of all kinds</p>
273 - <p style="margin-top: 0.5rem;">
218 + <p class="store-footer-links">
274 219 <a href="javascript:void(0)" onclick="navigator.clipboard.writeText(window.location.origin + '/p/{{ project.slug }}').then(() => { this.textContent = 'Copied!'; setTimeout(() => this.textContent = 'Copy link', 1500) })">Copy link</a> &middot;
275 220 <a href="/policy">Policy</a> &middot;
276 221 {% if session_user.is_some() %}
277 - <a href="javascript:void(0)" onclick="document.getElementById('report-modal').style.display='flex'">Report</a>
222 + <a href="javascript:void(0)" onclick="document.getElementById('report-modal').classList.remove('hidden')">Report</a>
278 223 {% else %}
279 224 <a href="/login">Report</a>
280 225 {% endif %}
@@ -300,7 +245,7 @@
300 245 container.className = 'items-container items-' + view + '-view';
301 246 }
302 247 document.querySelectorAll('.view-btn').forEach(function(btn) {
303 - btn.classList.toggle('active', btn.dataset.view === view);
248 + btn.classList.toggle('is-selected', btn.dataset.view === view);
304 249 });
305 250 }
306 251
@@ -1,22 +1,22 @@
1 1 <div id="insertion-library">
2 - <h3 style="font-family: var(--font-mono); font-size: 1rem; margin-bottom: 1rem;">Clips</h3>
2 + <h3 class="insertion-list-heading">Clips</h3>
3 3
4 - <div style="margin-bottom: 1rem;">
5 - <button type="button" class="btn btn-sm" onclick="MNW.insertions.startUpload()" style="font-family: var(--font-mono);">Upload Clip</button>
6 - <input type="file" id="insertion-file-input" accept=".mp3,.wav,.flac,.ogg,.m4a" style="display: none;" onchange="MNW.insertions.handleFileSelected(this)">
4 + <div class="insertion-list-toolbar">
5 + <button type="button" class="btn btn-sm insertion-list-upload" onclick="MNW.insertions.startUpload()">Upload Clip</button>
6 + <input type="file" id="insertion-file-input" class="hidden" accept=".mp3,.wav,.flac,.ogg,.m4a" onchange="MNW.insertions.handleFileSelected(this)">
7 7 </div>
8 8
9 9 {% if insertions.is_empty() %}
10 - <p style="font-family: var(--font-body); color: var(--text-muted);">No clips yet. Upload an audio clip to use as a pre-roll, mid-roll, or post-roll on your items.</p>
10 + <p class="insertion-list-empty">No clips yet. Upload an audio clip to use as a pre-roll, mid-roll, or post-roll on your items.</p>
11 11 {% else %}
12 - <table class="data-table" style="width: 100%; font-family: var(--font-body);">
12 + <table class="data-table insertion-list-table">
13 13 <thead>
14 14 <tr>
15 - <th style="text-align: left; font-family: var(--font-mono); font-size: 0.875rem;">Title</th>
16 - <th style="text-align: left; font-family: var(--font-mono); font-size: 0.875rem;">Duration</th>
17 - <th style="text-align: left; font-family: var(--font-mono); font-size: 0.875rem;">Type</th>
18 - <th style="text-align: left; font-family: var(--font-mono); font-size: 0.875rem;">Created</th>
19 - <th style="text-align: right; font-family: var(--font-mono); font-size: 0.875rem;">Actions</th>
15 + <th>Title</th>
16 + <th>Duration</th>
17 + <th>Type</th>
18 + <th>Created</th>
19 + <th class="insertion-list-th-actions">Actions</th>
20 20 </tr>
21 21 </thead>
22 22 <tbody>
@@ -26,14 +26,13 @@
26 26 <td>{{ ins.duration_display }}</td>
27 27 <td>{{ ins.media_type }}</td>
28 28 <td>{{ ins.created_at }}</td>
29 - <td style="text-align: right;">
30 - <button type="button" class="btn btn-xs" onclick="MNW.insertions.rename('{{ ins.id }}', '{{ ins.title }}')" style="font-family: var(--font-mono);">Rename</button>
31 - <button type="button" class="btn btn-xs btn-danger"
29 + <td class="insertion-list-actions">
30 + <button type="button" class="btn btn-xs insertion-list-action-btn" onclick="MNW.insertions.rename('{{ ins.id }}', '{{ ins.title }}')">Rename</button>
31 + <button type="button" class="btn btn-xs btn-danger insertion-list-action-btn"
32 32 hx-delete="/api/insertions/{{ ins.id }}"
33 33 hx-target="#insertion-library"
34 34 hx-swap="outerHTML"
35 - hx-confirm="Delete this clip? It will be removed from all items using it."
36 - style="font-family: var(--font-mono);">Delete</button>
35 + hx-confirm="Delete this clip? It will be removed from all items using it.">Delete</button>
37 36 </td>
38 37 </tr>
39 38 {% endfor %}