Reduce size of card badges, and change trade offers to show all cards by default, but in minimized mode. expand button now toggles between minimized mode and expanded mode for all cards on the trade offer

This commit is contained in:
badblocks 2025-04-03 20:54:09 -07:00
parent 01becbee48
commit fa4f38301b
16 changed files with 117 additions and 236 deletions

View file

@ -5,17 +5,18 @@ from django.utils.safestring import mark_safe
register = template.Library() register = template.Library()
@register.inclusion_tag("templatetags/card_badge.html") @register.inclusion_tag("templatetags/card_badge.html")
def card_badge(card, quantity=1): def card_badge(card, quantity=None, static=False):
return { return {
'quantity': quantity, 'quantity': quantity,
'style': card.style, 'style': card.style,
'name': card.name, 'name': card.name,
'rarity': card.rarity_icon, 'rarity': card.rarity_icon,
'cardset': card.cardset, 'cardset': card.cardset,
'static': static,
} }
@register.filter @register.filter
def card_badge_inline(card, quantity=1): def card_badge_inline(card, quantity=None):
""" """
Renders an inline card badge. Renders an inline card badge.
""" """
@ -25,5 +26,6 @@ def card_badge_inline(card, quantity=1):
'name': card.name, 'name': card.name,
'rarity': card.rarity_icon, 'rarity': card.rarity_icon,
'cardset': card.cardset, 'cardset': card.cardset,
'static': True,
}) })
return mark_safe(html) return mark_safe(html)

View file

@ -3,19 +3,19 @@
{% if group_by and groups %} {% if group_by and groups %}
{% for group in groups %} {% for group in groups %}
<div class="divider">{{ group.group }}</div> <div class="divider">{{ group.group }}</div>
<div class="flex justify-center flex-wrap gap-4"> <div class="flex justify-center flex-wrap">
{% for card in group.cards %} {% for card in group.cards %}
<a href="{% url 'cards:card_detail' card.pk %}"> <a href="{% url 'cards:card_detail' card.pk %}">
{% card_badge card "" %} {% card_badge card static=True %}
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
{% endfor %} {% endfor %}
{% else %} {% else %}
<div class="flex justify-center flex-wrap gap-4"> <div class="flex justify-center flex-wrap">
{% for card in cards %} {% for card in cards %}
<a href="{% url 'cards:card_detail' card.pk %}"> <a href="{% url 'cards:card_detail' card.pk %}">
{% card_badge card "" %} {% card_badge card static=True %}
</a> </a>
{% endfor %} {% endfor %}
</div> </div>

View file

@ -1,10 +1,8 @@
{% load trade_offer_tags %} {% load trade_offer_tags %}
{% if trade_offers %} {% if trade_offers %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="flex flex-row flex-wrap gap-1 justify-center items-start">
{% for offer in trade_offers %} {% for offer in trade_offers %}
<div class="mb-4"> {% render_trade_offer offer %}
{% render_trade_offer offer %}
</div>
{% endfor %} {% endfor %}
</div> </div>
{% else %} {% else %}

View file

@ -1,7 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static card_badge %} {% load static card_badge %}
{% block content %} {% block content %}
<div class="container mx-auto p-4">
<!-- Card header with badge and details --> <!-- Card header with badge and details -->
<div class="flex items-center mb-6"> <div class="flex items-center mb-6">
<div class="ml-4"> <div class="ml-4">
@ -95,5 +94,4 @@
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}

View file

@ -27,13 +27,13 @@
<!-- Sort Dropdown --> <!-- Sort Dropdown -->
<div class="dropdown dropdown-end m-1"> <div class="dropdown dropdown-end m-1">
<div tabindex="0" class="btn"> <div tabindex="0" class="btn">
Sort by: <span x-text="order === 'absolute' ? 'None' : (order === 'alphabetical' ? 'Alphabetical' : 'Rarity')"></span> Sort by: <span x-text="order === 'absolute' ? 'Cardset' : (order === 'alphabetical' ? 'Alphabetical' : 'Rarity')"></span>
<svg class="size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg class="size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg> </svg>
</div> </div>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52"> <ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
<li><a href="#" @click.prevent="order = 'absolute'; page = 1; loadCards()">None</a></li> <li><a href="#" @click.prevent="order = 'absolute'; page = 1; loadCards()">Cardset</a></li>
<li><a href="#" @click.prevent="order = 'alphabetical'; page = 1; loadCards()">Alphabetical</a></li> <li><a href="#" @click.prevent="order = 'alphabetical'; page = 1; loadCards()">Alphabetical</a></li>
<li><a href="#" @click.prevent="order = 'rarity'; page = 1; loadCards()">Rarity</a></li> <li><a href="#" @click.prevent="order = 'rarity'; page = 1; loadCards()">Rarity</a></li>
</ul> </ul>

View file

@ -1,13 +1,13 @@
{% load card_badge %} {% load card_badge %}
{% if cards %} {% if cards %}
<div class="mx-4 grid gap-3 grid-cols-[repeat(auto-fit,minmax(150px,1fr))] justify-items-center"> <div class="mx-4 grid gap-3 grid-cols-[repeat(auto-fit,minmax(150px,1fr))] justify-items-center">
{% for card in cards %} {% for card in cards %}
<a href="{% url 'cards:card_detail' card.id %}" <a href="{% url 'cards:card_detail' card.id %}"
class="flex justify-between items-center text-primary no-underline"> class="flex justify-between items-center text-primary no-underline">
{% card_badge card card.offer_count %} {% card_badge card quantity=card.offer_count static=True %}
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
{% else %} {% else %}
<p class="text-center">No cards found</p> <p class="text-center">No cards found</p>
{% endif %} {% endif %}

View file

@ -4,7 +4,7 @@
For a TradeOffer, we use {% render_trade_offer %}; for a TradeAcceptance, {% render_trade_acceptance %}. For a TradeOffer, we use {% render_trade_offer %}; for a TradeAcceptance, {% render_trade_acceptance %}.
{% endcomment %} {% endcomment %}
<div class="flex flex-row gap-4 flex-wrap justify-center items-start"> <div class="flex flex-row gap-2 flex-wrap justify-center items-start">
{% for offer in offers %} {% for offer in offers %}
<div class="flex flex-none"> <div class="flex flex-none">
{% if offer.accepted_by %} {% if offer.accepted_by %}

View file

@ -8,7 +8,7 @@
<form method="post" novalidate> <form method="post" novalidate>
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}
<button type="submit" class="btn btn-primary">Submit Acceptance</button> <button type="submit" class="btn btn-primary">Accept Trade</button>
</form> </form>
{% if form.errors %} {% if form.errors %}
<div class="alert alert-error mt-4"> <div class="alert alert-error mt-4">

View file

@ -4,7 +4,7 @@
{% block title %}Trade Offers{% endblock title %} {% block title %}Trade Offers{% endblock title %}
{% block content %} {% block content %}
<div class="container mx-auto max-w-4xl mt-6" x-data="{ <div x-data="{
// Initialize allExpanded from the URL if present, defaulting to false. // Initialize allExpanded from the URL if present, defaulting to false.
allExpanded: new URLSearchParams(window.location.search).get('expanded') === 'true', allExpanded: new URLSearchParams(window.location.search).get('expanded') === 'true',
page: {{ page_obj.number|default:1 }}, page: {{ page_obj.number|default:1 }},

View file

@ -46,7 +46,7 @@
</div> </div>
{% endif %} {% endif %}
<div class="mt-6 flex justify-between"> <div class="mt-6 flex justify-end">
{% if is_initiator %} {% if is_initiator %}
<a href="{{ delete_close_url }}" class="btn btn-danger">Delete/Close Trade Offer</a> <a href="{{ delete_close_url }}" class="btn btn-danger">Delete/Close Trade Offer</a>
{% elif request.user.is_authenticated %} {% elif request.user.is_authenticated %}

View file

@ -1,8 +1,19 @@
<div class="card-badge relative inline-block"> {% if not static %}
<div class="card-badge-inner freeze-bg-color grid grid-cols-4 grid-rows-2 my-2 px-2 py-2 h-14 w-36 text-white shadow-md shadow-black/50" style="{{ style }}"> <div class="relative block">
<div class="cardname row-span-1 col-span-4 truncate text-ellipsis self-start font-semibold leading-tight text-sm max-w-7/8">{{ name }}</div> <div :class="badgeExpanded ? 'grid grid-rows-2 grid-cols-4 h-[52px]' : 'flex flex-row items-center h-[32px]'" x-transition.duration.500ms class="p-1.5 w-40 text-white shadow-lg" style="{{ style }}">
<div class="rarity row-start-2 col-span-2 truncate self-end align-bottom text-xs">{{ rarity }}</div> <div :class="badgeExpanded ? 'row-start-1 col-start-1 col-span-3' : 'grow'" x-transition.duration.500ms class="truncate text-ellipsis font-semibold leading-tight text-sm">{{ name }}</div>
<div class="cardset row-start-2 col-start-3 col-span-2 text-right truncate self-end align-bottom font-semibold leading-none text-sm">{{ cardset }}</div> <div x-show="badgeExpanded" x-transition.duration.500ms x-cloak class="row-start-2 col-start-1 col-span-3 truncate align-bottom text-xs">{{ rarity }}</div>
<div x-show="true" x-transition.duration.500ms x-cloak :class="badgeExpanded ? 'row-start-2 col-start-4 col-span-1 align-bottom' : 'grow-0 shrink-0'" class="text-right truncate font-semibold leading-tight text-sm">{{ cardset }}</div>
{% if quantity != None %}<div x-show="true" x-transition.duration.500ms x-cloak :class="badgeExpanded ? 'row-start-1 col-start-4 justify-self-end -me-0.5' : 'grow-0 shrink-0'" class="relative w-fit ps-1"><div class="card-quantity-badge relative bg-gray-600 text-white text-sm font-semibold rounded-full text-center size-max px-1.5">{{ quantity }}</div></div>{% endif %}
</div> </div>
{% if quantity != "" %}<span class="card-quantity-badge freeze-bg-color absolute top-3.75 right-1.5 bg-gray-600 text-white text-sm font-semibold rounded-full px-1.5">{{ quantity }}</span>{% endif %}
</div> </div>
{% else %}
<div class="relative inline-block m-1">
<div class="grid grid-rows-2 grid-cols-4 h-[52px] p-1.5 w-40 text-white shadow-lg" style="{{ style }}">
<div class="row-start-1 col-start-1 col-span-3 truncate text-ellipsis font-semibold leading-tight text-sm">{{ name }}</div>
<div class="row-start-2 col-start-1 col-span-3 truncate align-bottom text-xs">{{ rarity }}</div>
<div class="row-start-2 col-start-4 col-span-1 align-bottom text-right truncate font-semibold leading-tight text-sm">{{ cardset }}</div>
{% if quantity != None %}<div class="row-start-1 col-start-4 justify-self-end -me-0.5 relative w-fit ps-1"><div class="card-quantity-badge relative bg-gray-600 text-white text-sm font-semibold rounded-full text-center size-max px-1.5">{{ quantity }}</div></div>{% endif %}
</div>
</div>
{% endif %}

View file

@ -1,6 +1,6 @@
{% load gravatar card_badge %} {% load gravatar card_badge %}
<div class="card card-border bg-base-100 shadow-lg w-84 mx-auto"> <div class="card card-border bg-base-100 shadow-lg w-90 mx-auto">
<!-- Header --> <!-- Header -->
<div class="py-4 mx-4"> <div class="py-4 mx-4">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
@ -40,11 +40,11 @@
</a> </a>
<!-- Footer: Only info button with acceptance hash --> <!-- Footer: Only info button with acceptance hash -->
<div class="flex justify-between px-2 pb-2"> <div class="flex justify-between p-2">
<div class="text-gray-500 text-sm"> <div class="text-gray-500 text-sm">
{{ acceptance.get_state_display }} {{ acceptance.get_state_display }}
</div> </div>
<div class="text-gray-500 text-sm tooltip tooltip-left" data-tip="Trade ID: {{ acceptance.hash }}"> <div class="text-gray-500 text-sm tooltip tooltip-left" data-tip="ID: {{ acceptance.hash }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" <path stroke-linecap="round" stroke-linejoin="round"
d="M11.25 11.25l.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0zm-9-3.75h.008v.008H12V8.25z" /> d="M11.25 11.25l.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0zm-9-3.75h.008v.008H12V8.25z" />

View file

@ -2,7 +2,7 @@
{% cache 60 trade_offer offer_pk %} {% cache 60 trade_offer offer_pk %}
<div class="trade-offer-card m-2 h-full w-auto flex justify-center"> <div class="trade-offer-card m-2 h-full w-auto flex justify-center">
<div x-data="tradeOfferCard()" x-init="defaultExpanded = {{expanded|lower}}; badgeExpanded = defaultExpanded; acceptanceExpanded = defaultExpanded; flipped = {{flipped|lower}}" class="transition-all duration-500 trade-offer-card my-auto" <div x-data="tradeOfferCard()" x-init="defaultExpanded = {{expanded|lower}}; badgeExpanded = defaultExpanded; flipped = {{flipped|lower}}" class="trade-offer-card my-auto"
@toggle-all.window="setBadge($event.detail.expanded)"> @toggle-all.window="setBadge($event.detail.expanded)">
<!-- Flip container providing perspective --> <!-- Flip container providing perspective -->
@ -11,19 +11,19 @@
The rotating element (.flip-inner) now uses CSS Grid to stack its children in a single cell. The rotating element (.flip-inner) now uses CSS Grid to stack its children in a single cell.
Persistent border, shadow, and rounding are applied here and the card rotates entirely. Persistent border, shadow, and rounding are applied here and the card rotates entirely.
--> -->
<div class="flip-inner freeze-bg-color grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg w-84 transform transition-transform duration-700 ease-in-out" <div class="flip-inner grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg w-90 transform transition-transform duration-700 ease-in-out"
:class="{'rotate-y-180': flipped}"> :class="{'rotate-y-180': flipped}">
<!-- Front Face: Trade Offer --> <!-- Front Face: Trade Offer -->
<!-- Using grid placement classes (col-start-1 row-start-1) ensures both faces overlap --> <!-- Using grid placement classes (col-start-1 row-start-1) ensures both faces overlap -->
<div class="flip-face front col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between"> <div x-show="!flipped" class="flip-face front col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between">
<!-- Header --> <!-- Header -->
<div class="flip-face-header self-start"> <div class="flip-face-header self-start">
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block"> <a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<!-- Set this container as relative to position the avatar absolutely --> <!-- Set this container as relative to position the avatar absolutely -->
<div class="relative mt-6 mb-4 mx-2 sm:mx-4"> <div class="relative mt-6 mb-4 mx-2 sm:mx-4">
<!-- Two-column grid for the labels --> <!-- Two-column grid for the labels -->
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2">
<span class="text-sm font-semibold text-center">Has</span> <span class="text-sm font-semibold text-center">Has</span>
<span class="text-sm font-semibold text-center">Wants</span> <span class="text-sm font-semibold text-center">Wants</span>
</div> </div>
@ -44,16 +44,16 @@
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block"> <a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<div class="px-2 main-badges pb-0"> <div class="px-2 main-badges pb-0">
<!-- Normal mode: just use an outer grid with 2 columns --> <!-- Normal mode: just use an outer grid with 2 columns -->
<div class="flex flex-row gap-2 justify-around"> <div class="flex flex-row justify-around">
<!-- Has Side --> <!-- Has Side -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-y-2">
{% for card in have_cards_available|slice:"0:1" %} {% for card in have_cards_available %}
{% card_badge card.card card.quantity %} {% card_badge card.card card.quantity %}
{% endfor %} {% endfor %}
</div> </div>
<!-- Wants Side --> <!-- Wants Side -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-y-2">
{% for card in want_cards_available|slice:"0:1" %} {% for card in want_cards_available %}
{% card_badge card.card card.quantity %} {% card_badge card.card card.quantity %}
{% endfor %} {% endfor %}
</div> </div>
@ -67,40 +67,17 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<!-- Extra Card Badges (Collapsible) -->
<div x-show="badgeExpanded" x-collapse.duration.500ms x-cloak class="px-2 extra-badges">
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<div class="flex flex-row gap-2 justify-around">
<!-- Has Side Extra Badges -->
<div class="flex flex-col gap-2">
{% for card in have_cards_available|slice:"1:" %}
{% card_badge card.card card.quantity %}
{% endfor %}
</div>
<!-- Wants Side Extra Badges -->
<div class="flex flex-col gap-2">
{% for card in want_cards_available|slice:"1:" %}
{% card_badge card.card card.quantity %}
{% endfor %}
</div>
</div>
</a>
</div>
</div> </div>
{% if have_cards_available|length > 1 or want_cards_available|length > 1 %}
<div @click="badgeExpanded = !badgeExpanded" class="flex justify-center h-5 cursor-pointer">
<svg x-bind:class="{ 'rotate-180': badgeExpanded }"
class="transition-transform duration-500 h-5 w-5 cursor-pointer"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7" />
</svg>
</div>
{% else %}
<div class="h-5"></div>
{% endif %}
<div class="flip-face-footer self-end"> <div class="flip-face-footer self-end">
<div class="flex justify-between px-2 pb-2"> <div class="flex justify-between px-2 pb-2">
<div @click="badgeExpanded = !badgeExpanded" class="flex justify-center h-5 cursor-pointer tooltip tooltip-right" data-tip="Expand/Collapse">
<svg x-bind:class="{ 'rotate-180': badgeExpanded }"
class="transition-transform duration-500 h-5 w-5 cursor-pointer"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
d="M19 9l-7 7-7-7" />
</svg>
</div>
<div class="text-gray-500 text-sm tooltip tooltip-right" data-tip="ID: {{ offer_hash }}"> <div class="text-gray-500 text-sm tooltip tooltip-right" data-tip="ID: {{ offer_hash }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-5"> stroke="currentColor" class="size-5">
@ -108,8 +85,7 @@
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" /> d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
</svg> </svg>
</div> </div>
<!-- Front-to-back flip button --> <div class="cursor-pointer text-gray-500 tooltip tooltip-left" data-tip="Flip to Accepted Trades" @click="flipped = true; acceptanceExpanded = defaultExpanded">
<div class="cursor-pointer text-gray-500" @click="flipped = true; acceptanceExpanded = defaultExpanded">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061A1.125 1.125 0 0 1 3 16.811V8.69ZM12.75 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061a1.125 1.125 0 0 1-1.683-.977V8.69Z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M3 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061A1.125 1.125 0 0 1 3 16.811V8.69ZM12.75 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061a1.125 1.125 0 0 1-1.683-.977V8.69Z" />
</svg> </svg>
@ -120,63 +96,30 @@
<!-- Back Face: Acceptances View --> <!-- Back Face: Acceptances View -->
<!-- Placed in the same grid cell as the front face --> <!-- Placed in the same grid cell as the front face -->
<div class="flip-face back col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between" style="transform: rotateY(180deg);"> <div x-show="flipped" class="flip-face back col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between" style="transform: rotateY(180deg);">
<div class="self-start"> <div class="self-start">
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline"> <a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<div class="py-4 mx-2 sm:mx-4"> <div class="relative mt-6 mb-4 mx-2 sm:mx-4">
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-2">
<div class="flex justify-center items-center"> <span class="text-sm font-semibold text-center">Has</span>
<span class="text-sm font-semibold">Has</span> <span class="text-sm font-semibold text-center">Wants</span>
</div> </div>
<div class="flex justify-center items-center"> <!-- The avatar is placed absolutely and centered -->
<div class="avatar"> <div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center">
<div class="w-10 rounded-full"> <div class="avatar">
<div class="w-10 rounded-full">
{{ initiated_by_email|gravatar:40 }} {{ initiated_by_email|gravatar:40 }}
</div> </div>
</div> </div>
</div> </div>
<div class="flex justify-center items-center">
<span class="text-sm font-semibold">Wants</span>
</div>
</div> </div>
</div>
</a> </a>
</div> </div>
<div class="self-start"> <div class="self-start">
<div class="px-2 pb-0"> <div class="px-2 pb-0">
<div class="overflow-hidden"> <div class="space-y-3">
{% if acceptances.0 %} {% for acceptance in acceptances %}
<div class="space-y-3"> <a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline block mb-2"
{% with acceptance=acceptances.0 %}
<a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline"
data-tooltip-html='<div class="flex items-center space-x-2">
<div class="avatar">
<div class="w-10 rounded-full">
{{ acceptance.accepted_by.user.email|gravatar:"40" }}
</div>
</div>
<div class="flex flex-col">
<span class="text-sm">Accepted by: {{ acceptance.accepted_by.user.username }}</span>
<span class="text-sm">State: {{ acceptance.state }}</span>
<span class="text-sm">Acceptance ID: {{ acceptance.hash }}</span>
</div>
</div>'>
<div class="grid grid-cols-2 gap-4 items-center">
<div>
{% card_badge acceptance.requested_card %}
</div>
<div>
{% card_badge acceptance.offered_card %}
</div>
</div>
</a>
{% endwith %}
</div>
{% endif %}
</div>
<div x-show="acceptanceExpanded" x-collapse.duration.500ms class="space-y-3">
{% for acceptance in acceptances|slice:"1:" %}
<a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline"
data-tooltip-html='<div class="flex items-center space-x-2"> data-tooltip-html='<div class="flex items-center space-x-2">
<div class="avatar"> <div class="avatar">
<div class="w-10 rounded-full"> <div class="w-10 rounded-full">
@ -189,47 +132,34 @@
<span class="text-sm">Acceptance ID: {{ acceptance.hash }}</span> <span class="text-sm">Acceptance ID: {{ acceptance.hash }}</span>
</div> </div>
</div>'> </div>'>
<div class="grid grid-cols-2 gap-4 items-center"> <div class="grid grid-cols-2 gap-1 justify-items-center">
<div> {% card_badge acceptance.requested_card %}
{% card_badge acceptance.requested_card %} {% card_badge acceptance.offered_card %}
</div>
<div>
{% card_badge acceptance.offered_card %}
</div>
</div> </div>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="flex justify-center h-5">
{% if acceptances|length > 1 %}
<svg @click="acceptanceExpanded = !acceptanceExpanded"
x-bind:class="{ 'rotate-180': acceptanceExpanded }"
class="transition-transform duration-500 h-5 w-5 cursor-pointer"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7" />
</svg>
{% endif %}
</div>
</div> </div>
<div class="flex justify-between px-2 pb-2 self-end"> <div class="flex justify-between px-2 pb-2 self-end">
<!-- Back-to-front flip button --> <!-- Back-to-front flip button -->
<div class="text-gray-500 cursor-pointer" @click="flipped = false; badgeExpanded = defaultExpanded"> <div class="text-gray-500 cursor-pointer tooltip tooltip-right" data-tip="Flip to Offered Cards" @click="flipped = false; badgeExpanded = defaultExpanded">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061A1.125 1.125 0 0 1 21 8.689v8.122ZM11.25 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061a1.125 1.125 0 0 1 1.683.977v8.122Z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M21 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061A1.125 1.125 0 0 1 21 8.689v8.122ZM11.25 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061a1.125 1.125 0 0 1 1.683.977v8.122Z" />
</svg> </svg>
</div> </div>
<div class="px-1 text-center"> <div class="px-1 text-center">
<span class="text-sm font-semibold"> <span class="text-sm font-semibold">
Acceptances ({{ acceptances|length }}) Accepted Trades ({{ acceptances|length }})
</span> </span>
</div> </div>
<div class="text-gray-500 text-sm tooltip tooltip-left" data-tip="ID: {{ offer_hash }}"> <div class="flex justify-center h-5 tooltip tooltip-left" data-tip="Expand/Collapse">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" <svg @click="badgeExpanded = !badgeExpanded"
stroke="currentColor" class="size-5"> x-bind:class="{ 'rotate-180': badgeExpanded }"
<path stroke-linecap="round" stroke-linejoin="round" class="transition-transform duration-500 h-5 w-5 cursor-pointer"
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" /> xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
d="M19 9l-7 7-7-7" />
</svg> </svg>
</div> </div>
</div> </div>

View file

@ -13,32 +13,25 @@
<!-- Flip container providing perspective --> <!-- Flip container providing perspective -->
<div class="flip-container"> <div class="flip-container">
<!-- <div class="flip-inner grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg {% if num_cards_available >= 4 %}w-192{% else %}w-96{% endif %} transform transition-transform duration-700 ease-in-out">
The rotating element (.flip-inner) now uses CSS Grid to stack its children in a single cell.
Persistent border, shadow, and rounding are applied here and the card rotates entirely.
-->
<div class="flip-inner freeze-bg-color grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg {% if num_cards_available >= 4 %}w-160{% else %}w-96 md:w-80 lg:w-96{% endif %} transform transition-transform duration-700 ease-in-out">
<!-- Front Face: Trade Offer --> <!-- Front Face: Trade Offer -->
<!-- Using grid placement classes (col-start-1 row-start-1) ensures both faces overlap -->
<div class="flip-face front mb-2 col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between"> <div class="flip-face front mb-2 col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between">
<!-- Header --> <!-- Header -->
<div class="flip-face-header self-start"> <div class="flip-face-header self-start">
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block"> <a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<!-- Set this container as relative to position the avatar absolutely --> <div class="mt-6 mb-4 mx-2 sm:mx-4">
<div class="relative mt-6 mb-4 mx-2 sm:mx-4">
<!-- Two-column grid for the labels --> <!-- Two-column grid for the labels -->
<div class="grid grid-cols-2 items-center"> <div class="flex flex-row justify-around w-full relative">
<span class="text-sm font-semibold text-center">Has</span> <div class="text-sm font-semibold text-center ms-3">Has</div>
<span class="text-sm font-semibold text-center">Wants</span> <div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center">
</div> <div class="avatar">
<!-- The avatar is placed absolutely and centered --> <div class="w-10 rounded-full">
<div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center"> {{ initiated_by_email|gravatar:40 }}
<div class="avatar"> </div>
<div class="w-10 rounded-full">
{{ initiated_by_email|gravatar:40 }}
</div> </div>
</div> </div>
<div class="text-sm font-semibold text-center {% if expanded %}ms-8{% else %}me-2{% endif %}">Wants</div>
</div> </div>
</div> </div>
</a> </a>
@ -46,14 +39,13 @@
<!-- Main Trade Offer Row --> <!-- Main Trade Offer Row -->
<div class="flip-face-body self-start"> <div class="flip-face-body self-start">
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block"> <a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<div class="px-2 main-badges pb-0"> <div class="px-4 main-badges pb-0">
{% if num_cards_available >= 4 %} {% if expanded %}
<!-- When screenshot_mode is true, use an outer grid with 3 columns: Has side, a vertical divider, and Wants side -->
<div class="flex flex-row gap-2 justify-around"> <div class="flex flex-row gap-2 justify-around">
<!-- Has Side (inner grid of 2 columns) --> <!-- Has Side (inner grid of 2 columns) -->
<div class="flex flex-row gap-2"> <div class="grid grid-cols-2 gap-2">
{% for card in have_cards_available|slice:"0:2" %} {% for card in have_cards_available %}
{% card_badge card.card card.quantity %} {% card_badge card.card card.quantity static=True %}
{% endfor %} {% endfor %}
</div> </div>
<!-- Vertical Divider --> <!-- Vertical Divider -->
@ -61,9 +53,9 @@
<div class="w-px bg-gray-300 h-full"></div> <div class="w-px bg-gray-300 h-full"></div>
</div> </div>
<!-- Wants Side (inner grid of 2 columns) --> <!-- Wants Side (inner grid of 2 columns) -->
<div class="flex flex-row gap-2"> <div class="grid grid-cols-2 gap-2">
{% for card in want_cards_available|slice:"0:2" %} {% for card in want_cards_available %}
{% card_badge card.card card.quantity %} {% card_badge card.card card.quantity static=True %}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -72,64 +64,20 @@
<div class="flex flex-row gap-2 justify-around"> <div class="flex flex-row gap-2 justify-around">
<!-- Has Side --> <!-- Has Side -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
{% for card in have_cards_available|slice:"0:1" %} {% for card in have_cards_available %}
{% card_badge card.card card.quantity %} {% card_badge card.card card.quantity static=True %}
{% endfor %} {% endfor %}
</div> </div>
<!-- Wants Side --> <!-- Wants Side -->
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
{% for card in want_cards_available|slice:"0:1" %} {% for card in want_cards_available %}
{% card_badge card.card card.quantity %} {% card_badge card.card card.quantity static=True %}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</a> </a>
<!-- Extra Card Badges (Collapsible) -->
{% if num_cards_available >= 4 %}
<div class="px-2 extra-badges">
<!-- In screenshot mode, add a vertical divider between the Has and Wants sides -->
<div class="flex flex-row gap-2 justify-around">
<!-- Has Side Extra Badges -->
<div class="grid grid-cols-2 gap-2 {% if num_cards_available >= 4 %}w-[296px]{% endif %}">
{% for card in have_cards_available|slice:"2:" %}
{% card_badge card.card card.quantity %}
{% endfor %}
</div>
<!-- Vertical Divider -->
<div class="flex justify-center">
<div class="w-px bg-gray-300 h-full"></div>
</div>
<!-- Wants Side Extra Badges -->
<div class="grid grid-cols-2 gap-2 {% if num_cards_available >= 4 %}w-[296px]{% endif %}">
{% for card in want_cards_available|slice:"2:" %}
{% card_badge card.card card.quantity %}
{% endfor %}
</div>
</div>
</div>
{% else %}
<div class="px-2 extra-badges">
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
<div class="flex flex-row gap-2 justify-around">
<!-- Has Side Extra Badges -->
<div class="flex flex-col gap-2">
{% for card in have_cards_available|slice:"1:" %}
{% card_badge card.card card.quantity %}
{% endfor %}
</div>
<!-- Wants Side Extra Badges -->
<div class="flex flex-col gap-2">
{% for card in want_cards_available|slice:"1:" %}
{% card_badge card.card card.quantity %}
{% endfor %}
</div>
</div>
</a>
</div>
{% endif %}
</div> </div>
<div class="flip-face-footer self-end"> <div class="flip-face-footer self-end">

View file

@ -97,19 +97,12 @@ def render_trade_offer_png(context, offer, show_friend_code=False):
] ]
num_cards = max(len(have_cards_available), len(want_cards_available)) num_cards = max(len(have_cards_available), len(want_cards_available))
aspect_ratio = 1.91 image_height = (num_cards * 52) + ((num_cards - 1) * 8) + 106
base_height = (round(num_cards / 2) * 56) + 138 # cards | padding between cards | header/footer
if (len(have_cards_available) + len(want_cards_available)) >= 4: if (len(have_cards_available) + len(want_cards_available)) >= 4:
base_width = (4 * 144) + 96 image_width = (4 * 160) + 160
else: else:
base_width = (2 * 144) + 128 image_width = (2 * 160) + 92
if base_height > base_width:
image_height = base_height
image_width = int(round(image_height * aspect_ratio)) + 1
else:
image_width = base_width
image_height = int(round(image_width / aspect_ratio))
request = context.get("request") request = context.get("request")
if request.get_host().startswith("localhost"): if request.get_host().startswith("localhost"):
@ -129,6 +122,7 @@ def render_trade_offer_png(context, offer, show_friend_code=False):
'friend_code': offer.initiated_by.friend_code, 'friend_code': offer.initiated_by.friend_code,
'show_friend_code': show_friend_code, 'show_friend_code': show_friend_code,
'num_cards_available': len(have_cards_available) + len(want_cards_available), 'num_cards_available': len(have_cards_available) + len(want_cards_available),
'expanded': (len(have_cards_available) + len(want_cards_available)) >= 4,
'image_width': image_width, 'image_width': image_width,
'image_height': image_height, 'image_height': image_height,
'base_url': base_url, 'base_url': base_url,

View file

@ -11,7 +11,7 @@ from meta.views import Meta
from .models import TradeOffer, TradeAcceptance from .models import TradeOffer, TradeAcceptance
from .forms import (TradeAcceptanceCreateForm, TradeOfferCreateForm, TradeAcceptanceTransitionForm) from .forms import (TradeAcceptanceCreateForm, TradeOfferCreateForm, TradeAcceptanceTransitionForm)
from django.template.loader import render_to_string from django.template.loader import render_to_string
from trades.templatetags.trade_offer_tags import render_trade_offer from trades.templatetags.trade_offer_tags import render_trade_offer_png
from playwright.sync_api import sync_playwright from playwright.sync_api import sync_playwright
from django.conf import settings from django.conf import settings
from .mixins import FriendCodeRequiredMixin from .mixins import FriendCodeRequiredMixin
@ -514,7 +514,7 @@ class TradeOfferPNGView(View):
trade_offer.image.open() trade_offer.image.open()
return HttpResponse(trade_offer.image.read(), content_type="image/png") return HttpResponse(trade_offer.image.read(), content_type="image/png")
tag_context = trade_offer_tags.render_trade_offer_png( tag_context = render_trade_offer_png(
{'request': request}, trade_offer, show_friend_code=True {'request': request}, trade_offer, show_friend_code=True
) )
image_width = tag_context.get('image_width') image_width = tag_context.get('image_width')