Files
devplacepy/devplacepy/templates/projects.html
T
blindxfishandClaude Fable 5 8a2e0e6e70 Align the project hero with the showcase design: owner cover, logo, repo link
The hero now matches the showcase reference: the title block, tagline
(first description line), type/platform chips, author row and started
date render OVERLAID on the cover banner behind a bottom scrim, with
the optional project logo as a framed tile beside them and the Visit
Website CTA on the right. The section tab bar switches to the underline
style with Overview active.

Owners control the missing pieces from the create and edit modals,
which are now multipart: cover_image and logo_image file uploads
(stored as bare uploaded filenames via save_inline_image, the
posts.image pattern; a new upload replaces the previous one) plus a
repo_url sibling of website_url (same normalization/validation). The
cover feeds og:image and the schema image, the logo becomes schema
thumbnailUrl, and sameAs now carries website + repository. repo_url
rides ProjectOut, the Devii create/edit actions and the API docs;
tests cover repo normalization, the multipart upload round-trip and
the hero render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 22:12:25 +02:00

205 lines
11 KiB
HTML

{% extends "base.html" %}
{% from "_macros.html" import modal %}
{% block extra_head %}
<link rel="stylesheet" href="{{ static_url('/static/css/feed.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/css/post.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/css/projects.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/css/sidebar.css') }}">
{% endblock %}
{% block content %}
<div class="projects-layout">
<aside class="sidebar-card" role="complementary" aria-label="Project filters">
{% set _action = "/projects" %}{% set _placeholder = "Search projects..." %}{% set _hidden = {"tab": current_tab, "project_type": project_type} %}{% include "_sidebar_search.html" %}
<div class="sidebar-section">
<div class="sidebar-heading">Type</div>
<div class="sidebar-nav">
<a href="/projects?tab={{ current_tab }}" class="sidebar-link {% if not project_type %}active{% endif %}"><span class="icon">&#x1F4CB;</span>All</a>
<a href="/projects?tab={{ current_tab }}&project_type=game" class="sidebar-link {% if project_type == 'game' %}active{% endif %}"><span class="icon">&#x1F3AE;</span>Game</a>
<a href="/projects?tab={{ current_tab }}&project_type=software" class="sidebar-link {% if project_type == 'software' %}active{% endif %}"><span class="icon">&#x1F4BB;</span>Software</a>
<a href="/projects?tab={{ current_tab }}&project_type=mobile_app" class="sidebar-link {% if project_type == 'mobile_app' %}active{% endif %}"><span class="icon">&#x1F4F1;</span>Mobile App</a>
<a href="/projects?tab={{ current_tab }}&project_type=website" class="sidebar-link {% if project_type == 'website' %}active{% endif %}"><span class="icon">&#x1F310;</span>Website</a>
</div>
</div>
{% if user %}
<div class="sidebar-section">
<div class="sidebar-heading">My Projects</div>
<a href="/projects?user_uid={{ user['uid'] }}" class="btn btn-secondary btn-sm full-width justify-center"><span class="icon">&#x1F464;</span>View My Projects</a>
</div>
{% endif %}
</aside>
<div class="projects-main">
<div class="projects-header">
<div>
<h1>Projects</h1>
<span class="subtitle">Discover amazing projects from the DevPlace community</span>
</div>
<div class="projects-count">Showing {{ projects|length }} of {{ total_count }} projects</div>
</div>
<div class="projects-tabs">
<a href="/projects?tab=recent" class="projects-tab {% if current_tab == 'recent' or not current_tab %}active{% endif %}"><span class="icon">&#x1F550;</span> Recently Released</a>
<a href="/projects?tab=popular" class="projects-tab {% if current_tab == 'popular' %}active{% endif %}"><span class="icon">&#x1F525;</span> Most Popular</a>
<a href="/projects?tab=new" class="projects-tab {% if current_tab == 'new' %}active{% endif %}"><span class="icon">&#x1F195;</span> New This Week</a>
{% if user %}
<a href="/projects?user_uid={{ user['uid'] }}" class="projects-tab {% if request.query_params.get('user_uid') %}active{% endif %}"><span class="icon">&#x1F464;</span> My Projects</a>
{% endif %}
</div>
<div class="projects-grid">
{% for project in projects %}
<div class="project-card card-link-host">
{% set _href = "/projects/" ~ (project['slug'] or project['uid']) %}
{% set _label = project['title'] %}
{% include "_card_link.html" %}
<div class="project-card-header">
<h3 class="project-card-title">{{ render_title(project['title']) }}</h3>
{% set _type = "project" %}{% set _uid = project['uid'] %}{% set _my_vote = project.my_vote %}{% set _count = project.get('stars', 0) %}{% set _btn_class = "project-card-star" %}{% include "_star_vote.html" %}
</div>
<div class="project-card-meta">
<span>By {{ project.get('author_name', 'Unknown') }}</span>
{% if project.get('release_date') %}
<span>&#x1F4C5; {{ format_date(project['release_date']) }}</span>
{% endif %}
<span class="project-status {% if project.get('status') == 'Released' %}released{% else %}dev{% endif %}">
&#x25CF; {{ project.get('status', 'In Development') }}
</span>
</div>
<div class="project-card-desc rendered-content">{{ render_content((project.get('description') or '')[:200]) }}</div>
<div class="flex-between">
<span class="badge badge-type">{{ (project.get('project_type') or 'software').replace('_', ' ') }}</span>
{% if project.get('platforms') %}
<div class="project-card-platforms">
{% for p in project['platforms'].split(',') %}
<span class="platform-tag">{{ p.strip() }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% if project.recent_comments %}
{% from "_comment.html" import render_comment with context %}
<div class="post-card-comments">
{% for c in project.recent_comments %}
{{ render_comment(c, 0) }}
{% endfor %}
</div>
{% endif %}
</div>
{% else %}
<div class="empty-state empty-state-full">No projects found. Create one!</div>
{% endfor %}
</div>
{% include "_load_more.html" %}
</div>
</div>
{% if user %}
<button type="button" class="feed-fab" id="create-project-btn" title="Add Project" data-modal="create-project-modal" aria-label="Add Project">+</button>
{% call modal('create-project-modal', 'Create Project') %}
<form method="POST" action="/projects/create" enctype="multipart/form-data">
<div class="auth-field auth-field-gap">
<label for="title">Title</label>
<input type="text" id="title" name="title" required maxlength="200" placeholder="Project name">
</div>
<div class="auth-field auth-field-gap">
<label for="description">Description</label>
<textarea id="description" name="description" required maxlength="5000" placeholder="Describe your project" class="min-h-100" data-mention></textarea>
</div>
<div class="grid-2col">
<div class="auth-field">
<label for="release_date">Release Date</label>
<input type="text" id="release_date" name="release_date" placeholder="DD/MM/YYYY" pattern="\d{2}/\d{2}/\d{4}" inputmode="numeric" autocomplete="off">
</div>
<div class="auth-field">
<label for="demo_date">Demo Date</label>
<input type="text" id="demo_date" name="demo_date" placeholder="DD/MM/YYYY" pattern="\d{2}/\d{2}/\d{4}" inputmode="numeric" autocomplete="off">
</div>
</div>
<div class="grid-2col">
<div class="auth-field">
<label for="website_url">Website (optional)</label>
<input type="url" id="website_url" name="website_url" maxlength="500" placeholder="https://myproject.dev">
</div>
<div class="auth-field">
<label for="repo_url">Repository (optional)</label>
<input type="url" id="repo_url" name="repo_url" maxlength="500" placeholder="https://github.com/me/project">
</div>
</div>
<div class="grid-2col">
<div class="auth-field">
<label for="cover_image">Cover image (optional)</label>
<input type="file" id="cover_image" name="cover_image" accept="image/*">
</div>
<div class="auth-field">
<label for="logo_image">Project logo (optional)</label>
<input type="file" id="logo_image" name="logo_image" accept="image/*">
</div>
</div>
<div class="auth-field auth-field-gap">
<label>Type</label>
<div class="flex-wrap-gap" role="group" aria-label="Type">
{% for t in [('game', 'Game'), ('game_asset', 'Game Asset'), ('software', 'Software'), ('mobile_app', 'Mobile App'), ('website', 'Website')] %}
<label class="topic-label">
<input type="radio" name="project_type" value="{{ t[0] }}" {% if t[0] == 'software' %}checked{% endif %} class="topic-radio">
{{ t[1] }}
</label>
{% endfor %}
</div>
</div>
<div class="auth-field auth-field-gap">
<label>Status</label>
<div class="flex-wrap-gap" role="group" aria-label="Status">
<label class="topic-label">
<input type="radio" name="status" value="In Development" checked class="topic-radio"> In Development
</label>
<label class="topic-label">
<input type="radio" name="status" value="Released" class="topic-radio"> Released
</label>
</div>
</div>
<div class="auth-field auth-field-gap-last">
<label>Platforms</label>
<div id="platforms-tags" class="flex-wrap-gap-bottom"></div>
<input type="text" id="platforms-input" class="input-sm" placeholder="Add platform (press Enter)" aria-label="Add platform">
<input type="hidden" id="platforms" name="platforms" value="">
<div class="platform-presets-wrap">
{% for p in ['PC', 'Windows', 'Mac', 'Linux', 'iOS', 'Android', 'Web', 'Console'] %}
<button type="button" class="platform-preset" data-platform="{{ p }}">{{ p }}</button>
{% endfor %}
</div>
</div>
<div class="auth-field auth-field-gap">
<label class="topic-label">
<input type="checkbox" name="is_private" value="on"> Private (only you and administrators can see this project)
</label>
</div>
{% include "_attachment_form.html" %}
<div class="modal-footer">
<button type="button" class="modal-close btn btn-secondary">Cancel</button>
<button type="submit" class="btn btn-primary">Create Project</button>
</div>
</form>
{% endcall %}
{% else %}
<a href="/auth/login" class="feed-fab login-required" aria-label="Log in">+</a>
{% endif %}
{% endblock %}