Skip to main content

max / makenotwork

12.7 KB · 269 lines History Blame Raw
1 {% extends "base.html" %}
2
3 {% block title %}{{ project.title }} - {{ creator_username }}{% endblock %}
4 {% block body_attrs %} class="padded-page project-page"{% endblock %}
5
6 {% block head %}
7 <meta property="og:title" content="{{ project.title }} by {{ creator_username }}">
8 <meta property="og:description" content="{{ project.description }}">
9 <meta property="og:type" content="website">
10 <meta property="og:url" content="{{ host_url }}/p/{{ project.slug }}">
11 <link rel="canonical" href="{{ host_url }}/p/{{ project.slug }}">
12 <meta property="og:site_name" content="Makenot.work">
13 {% if project.cover_image_url.is_some() %}<meta name="twitter:card" content="summary_large_image">{% else %}<meta name="twitter:card" content="summary">{% endif %}
14 <meta name="twitter:title" content="{{ project.title }} by {{ creator_username }}">
15 <meta name="twitter:description" content="{{ project.description }}">
16 {% if let Some(img) = project.cover_image_url %}
17 <meta property="og:image" content="{{ img }}">
18 <meta name="twitter:image" content="{{ img }}">
19 {% else %}
20 <meta property="og:image" content="{{ host_url }}/static/images/og-card.png">
21 <meta name="twitter:image" content="{{ host_url }}/static/images/og-card.png">
22 {% endif %}
23 <script type="application/ld+json">
24 {
25 "@context": "https://schema.org",
26 "@type": "CollectionPage",
27 "name": "{{ project.title_json()|safe }}",
28 "description": "{{ project.description_json()|safe }}",
29 "url": "{{ host_url }}/p/{{ project.slug }}",
30 "author": {
31 "@type": "Person",
32 "name": "{{ creator_username }}",
33 "url": "{{ host_url }}/u/{{ creator_username }}"
34 },
35 "numberOfItems": {{ project.item_count }},
36 "isPartOf": {
37 "@type": "WebSite",
38 "name": "Makenot.work",
39 "url": "{{ host_url }}"
40 }
41 }
42 </script>
43 <link rel="alternate" type="application/rss+xml" title="{{ project.title }} - RSS Feed" href="/p/{{ project.slug }}/rss">
44 {% endblock %}
45
46 {% block content %}
47 {% include "partials/site_header.html" %}
48
49 <div class="container">
50 <header class="store-header">
51 {% if let Some(img_url) = project.cover_image_url %}
52 <img src="{{ img_url }}" alt="{{ project.title }}" class="store-cover">
53 {% endif %}
54 <h1 class="store-title">{{ project.title }}<span class="dot">.</span></h1>
55 <div class="store-meta">
56 by <a href="/u/{{ creator_username }}">{{ creator_username }}</a> &middot;
57 <span>{{ project.item_count }} items</span>
58 </div>
59 <p class="store-description">{{ project.description }}</p>
60 <div class="store-actions">
61 {% if is_owner %}
62 <a href="/dashboard/project/{{ project.slug }}" class="btn-secondary">Edit Project</a>
63 {% endif %}
64 {% if session_user.is_some() %}
65 {% if is_following %}
66 <button class="btn-secondary follow-btn is-selected"
67 hx-delete="/api/follow/project/{{ project_id }}"
68 hx-swap="outerHTML">Following ({{ follower_count }})</button>
69 {% else %}
70 <button class="btn-secondary follow-btn"
71 hx-post="/api/follow/project/{{ project_id }}"
72 hx-swap="outerHTML">Follow ({{ follower_count }})</button>
73 {% endif %}
74 {% else if follower_count > 0 %}
75 <span class="follower-count-muted">{{ follower_count }} followers</span>
76 {% endif %}
77 <a href="/p/{{ project.slug }}/rss" class="btn-secondary">RSS Feed</a>
78 {% if has_blog_posts %}
79 <a href="/p/{{ project.slug }}/blog" class="btn-secondary">Blog</a>
80 {% endif %}
81 {% for repo in &git_repos %}
82 <a href="{{ repo.1 }}" class="btn-secondary">Git ({{ repo.0 }})</a>
83 {% endfor %}
84 {% if let Some(url) = community_url %}
85 <a href="{{ url }}" class="btn-secondary">Community</a>
86 {% endif %}
87 {% include "partials/tip_button.html" %}
88 </div>
89 </header>
90
91 {% if !sections.is_empty() %}
92 <section class="project-sections">
93 <div class="section-tabs">
94 {% for section in sections %}
95 <button class="section-tab{% if loop.first %} is-selected{% endif %}"
96 data-tab="section-{{ section.slug }}"
97 onclick="switchSectionTab(this, 'section-{{ section.slug }}')">{{ section.title }}</button>
98 {% endfor %}
99 </div>
100 {% for section in sections %}
101 <div class="section-panel{% if loop.first %} active{% endif %}" id="section-{{ section.slug }}">
102 {{ section.body_html|safe }}
103 </div>
104 {% endfor %}
105 </section>
106 <script>
107 function switchSectionTab(btn, panelId) {
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'); });
111 document.querySelectorAll('.project-sections .section-panel').forEach(function(p) { p.classList.remove('active'); });
112 btn.classList.add('is-selected');
113 var panel = document.getElementById(panelId);
114 if (panel) panel.classList.add('active');
115 history.replaceState(null, '', '#' + panelId);
116 }
117 (function() {
118 var hash = window.location.hash.replace('#', '');
119 if (hash) {
120 var panel = document.getElementById(hash);
121 var tab = document.querySelector('.project-sections [data-tab="' + hash + '"]');
122 if (panel && tab) switchSectionTab(tab, hash);
123 }
124 })();
125 </script>
126 {% endif %}
127
128 <section class="items-section">
129 <div class="items-header">
130 <h2 class="section-header">Available Items</h2>
131 <div class="view-controls">
132 <button type="button" class="view-btn" data-view="list" title="List view">|||</button>
133 <button type="button" class="view-btn is-selected" data-view="grid" title="Grid view">:::</button>
134 </div>
135 </div>
136 <div class="items-container items-grid-view" id="items-container">
137 <div class="items-grid">
138 {% for item in items %}
139 <div class="item-card">
140 <a href="{% if item.can_access %}/i/{{ item.id }}{% else %}/purchase/{{ item.id }}{% endif %}" class="item-thumbnail">{{ item.thumbnail }}</a>
141 <div class="item-content">
142 <h3 class="item-title"><a href="{% if item.can_access %}/i/{{ item.id }}{% else %}/purchase/{{ item.id }}{% endif %}">{{ item.title }}</a></h3>
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>
144 <div class="item-tags">
145 {% for tag in item.tags %}
146 <a href="/discover?tag={{ tag.slug }}" class="tag">{{ tag.name }}</a>
147 {% endfor %}
148 </div>
149 <p class="item-description">{{ item.description }}</p>
150 <div class="item-footer">
151 <div class="item-price">{{ item.price }}</div>
152 <div class="item-stats">{{ item.sales_count }} sales</div>
153 </div>
154 {% if item.can_access %}
155 <a href="/l/{{ item.id }}" class="btn-secondary">View in library</a>
156 {% else if item.is_free %}
157 <button class="btn-primary"
158 hx-post="/api/library/add/{{ item.id }}"
159 hx-swap="outerHTML">Add to Library</button>
160 {% else if item.pwyw_enabled %}
161 <a href="/purchase/{{ item.id }}" class="btn-primary">Pay What You Want</a>
162 {% else %}
163 <a href="/purchase/{{ item.id }}" class="btn-primary">Buy Once</a>
164 {% endif %}
165 </div>
166 </div>
167 {% endfor %}
168 </div>
169
170 <!-- List View -->
171 <div class="items-list">
172 {% for item in items %}
173 <a href="{% if item.can_access %}/i/{{ item.id }}{% else %}/purchase/{{ item.id }}{% endif %}" class="list-item">
174 <div class="list-item-info">
175 <span class="list-item-title">{{ item.title }}</span>
176 <span class="list-item-meta">{{ item.item_type }}</span>
177 </div>
178 <div class="list-item-price">{{ item.price }}</div>
179 </a>
180 {% endfor %}
181 </div>
182 </div>
183 </section>
184
185 {% if !subscription_tiers.is_empty() %}
186 <section class="tiers-section">
187 <h2 class="section-header">Membership</h2>
188 <div class="tiers-grid">
189 {% for tier in subscription_tiers %}
190 <div class="tier-card">
191 <div class="tier-name">{{ tier.name }}</div>
192 <div class="tier-price">{{ tier.price }}</div>
193 {% if !tier.description.is_empty() %}
194 <p class="tier-description">{{ tier.description }}</p>
195 {% endif %}
196 {% if has_subscription %}
197 <div class="tier-active-badge">Subscribed</div>
198 {% else if session_user.is_some() %}
199 <form method="post" action="/stripe/subscribe/{{ tier.id }}">
200 <input type="hidden" name="_csrf" value="{{ csrf_token.as_deref().unwrap_or_default() }}">
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">
204 </details>
205 <button class="btn-primary" type="submit" data-loading-text="Redirecting to Stripe...">Subscribe</button>
206 </form>
207 {% else %}
208 <a href="/login" class="btn-primary">Log in to Subscribe</a>
209 {% endif %}
210 </div>
211 {% endfor %}
212 </div>
213 </section>
214 {% endif %}
215
216 <footer class="store-footer">
217 <p>Powered by <a href="/">Makenot<span class="dot">.</span>work</a> &middot; Fair distribution for creatives of all kinds</p>
218 <p class="store-footer-links">
219 <a href="/p/{{ project.slug }}" data-copy-link>Copy link</a> &middot;
220 <a href="/policy">Policy</a> &middot;
221 {% if session_user.is_some() %}
222 <a href="javascript:void(0)" onclick="document.getElementById('report-modal').classList.remove('hidden')">Report</a>
223 {% else %}
224 <a href="/login">Report</a>
225 {% endif %}
226 </p>
227 </footer>
228 </div>
229
230 {% if session_user.is_some() %}
231 {% let report_target_type = "project" %}
232 {% let report_target_id = project_id %}
233 {% let report_has_labels = false %}
234 {% include "partials/report_modal.html" %}
235 {% endif %}
236 {% endblock %}
237
238 {% block scripts %}
239 <script>
240 // View toggle with localStorage persistence
241 (function() {
242 function applyView(view) {
243 var container = document.getElementById('items-container');
244 if (container) {
245 container.className = 'items-container items-' + view + '-view';
246 }
247 document.querySelectorAll('.view-btn').forEach(function(btn) {
248 btn.classList.toggle('is-selected', btn.dataset.view === view);
249 });
250 }
251
252 // Load saved preference on page load
253 document.addEventListener('DOMContentLoaded', function() {
254 var saved = safeStorageGet('projectViewPref') || 'grid';
255 applyView(saved);
256 });
257
258 // Handle view button clicks
259 document.querySelectorAll('.view-btn').forEach(function(btn) {
260 btn.addEventListener('click', function() {
261 var view = btn.dataset.view;
262 applyView(view);
263 safeStorageSet('projectViewPref', view);
264 });
265 });
266 })();
267 </script>
268 {% endblock %}
269