Replace CSS grid with Bootstrap table on conference list page

- Proper <table> with colgroup widths, table-sm table-hover align-middle
- Month-divider rows (MARCH 2026, APRIL 2026, …) break up long lists
- Date ranges collapsed to a single column (e.g. "25–28 Mar 2026")
- Row highlight (conf-going) for conferences marked going=true
- Topic and date columns styled text-muted small to reduce visual noise
- Trip links replaced with 🧳 emoji: shows just the emoji when the trip
  title matches the conference name (the common case), otherwise appends
  the trip title (e.g. "🧳 Budapest" for FOSDEM); full title always in
  tooltip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Edward Betts 2026-03-14 10:56:44 +00:00
parent 20f1e31119
commit ef517c98ff

View file

@ -1,22 +1,11 @@
{% extends "base.html" %} {% extends "base.html" %}
{% from "macros.html" import trip_link, conference_row with context %} {% from "macros.html" import trip_link with context %}
{% block title %}Conferences - Edward Betts{% endblock %} {% block title %}Conferences - Edward Betts{% endblock %}
{% block style %} {% block style %}
{% set column_count = 9 %}
<style> <style>
.grid-container {
display: grid;
grid-template-columns: repeat({{ column_count }}, auto);
gap: 10px;
justify-content: start;
}
.heading {
grid-column: 1 / {{ column_count + 1 }};
}
/* Timeline */ /* Timeline */
.conf-timeline { .conf-timeline {
position: relative; position: relative;
@ -67,6 +56,22 @@
color: white; color: white;
text-decoration: none; text-decoration: none;
} }
/* Conference table */
.conf-month-row td {
background: #e9ecef !important;
font-weight: 600;
font-size: 0.8em;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #495057;
padding-top: 0.5rem;
padding-bottom: 0.3rem;
border-bottom: none;
}
.conf-going {
--bs-table-bg: rgba(25, 135, 84, 0.07);
}
</style> </style>
{% endblock %} {% endblock %}
@ -74,7 +79,6 @@
{% macro render_timeline(timeline) %} {% macro render_timeline(timeline) %}
{% if timeline %} {% if timeline %}
{% set bar_h = 26 %}
{% set row_h = 32 %} {% set row_h = 32 %}
{% set header_h = 22 %} {% set header_h = 22 %}
{% set total_h = timeline.lane_count * row_h + header_h %} {% set total_h = timeline.lane_count * row_h + header_h %}
@ -82,28 +86,22 @@
<h3>Next 90 days</h3> <h3>Next 90 days</h3>
<div class="conf-timeline" style="height: {{ total_h }}px;"> <div class="conf-timeline" style="height: {{ total_h }}px;">
{# Month markers #}
{% for m in timeline.months %} {% for m in timeline.months %}
<div class="conf-tl-month" style="left: {{ m.left_pct }}%;"> <div class="conf-tl-month" style="left: {{ m.left_pct }}%;">
<span class="conf-tl-month-label">{{ m.label }}</span> <span class="conf-tl-month-label">{{ m.label }}</span>
</div> </div>
{% endfor %} {% endfor %}
{# Today marker (always at left: 0) #}
<div class="conf-tl-today" style="left: 0;"></div> <div class="conf-tl-today" style="left: 0;"></div>
{# Conference bars #}
{% for conf in timeline.confs %} {% for conf in timeline.confs %}
{% set color = tl_colors[conf.lane % tl_colors | length] %} {% set color = tl_colors[conf.lane % tl_colors | length] %}
{% set top_px = conf.lane * row_h + header_h %} {% set top_px = conf.lane * row_h + header_h %}
<div class="conf-tl-bar" <div class="conf-tl-bar"
style="left: {{ conf.left_pct }}%; width: {{ conf.width_pct }}%; top: {{ top_px }}px; background: {{ color }};" style="left: {{ conf.left_pct }}%; width: {{ conf.width_pct }}%; top: {{ top_px }}px; background: {{ color }};"
title="{{ conf.label }}"> title="{{ conf.label }}">
{% if conf.url %} {% if conf.url %}<a href="{{ conf.url }}">{{ conf.name }}</a>
<a href="{{ conf.url }}">{{ conf.name }}</a> {% else %}<span>{{ conf.name }}</span>{% endif %}
{% else %}
<span>{{ conf.name }}</span>
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
@ -112,25 +110,95 @@
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro section(heading, item_list, badge) %} {% macro conf_table(heading, item_list, badge) %}
{% if item_list %} {% if item_list %}
<div class="heading"> {% set count = item_list | length %}
<h2>{{ heading }} <small class="text-muted fs-6 fw-normal">{{ count }} conference{{ "" if count == 1 else "s" }}</small></h2>
<h2>{{ heading }}</h2> <table class="table table-sm table-hover align-middle mb-4">
<colgroup>
<p> <col style="width: 9rem">
{% set item_count = item_list|length %} <col>
{% if item_count == 1 %}{{ item_count }} conference{% else %}{{ item_count }} conferences{% endif %} <col style="width: 18rem">
</p> <col style="width: 14rem">
</div> <col style="width: 7rem">
<col style="width: 10rem">
</colgroup>
<thead class="table-light">
<tr>
<th>Dates</th>
<th>Conference</th>
<th>Topic</th>
<th>Location</th>
<th>CFP ends</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{% set ns = namespace(prev_month="") %}
{% for item in item_list %} {% for item in item_list %}
{{ conference_row(item, badge) }} {% set month_label = item.start_date.strftime("%B %Y") %}
<div class="grid-item"> {% if month_label != ns.prev_month %}
{% if item.linked_trip %} trip: {{ trip_link(item.linked_trip) }} {% endif %} {% set ns.prev_month = month_label %}
</div> <tr class="conf-month-row">
{% endfor %} <td colspan="6">{{ month_label }}</td>
</tr>
{% endif %} {% endif %}
<tr{% if item.going %} class="conf-going"{% endif %}>
<td class="text-nowrap text-muted small">
{%- if item.start_date == item.end_date -%}
{{ item.start_date.strftime("%-d %b %Y") }}
{%- elif item.start_date.year == item.end_date.year and item.start_date.month == item.end_date.month -%}
{{ item.start_date.strftime("%-d") }}{{ item.end_date.strftime("%-d %b %Y") }}
{%- else -%}
{{ item.start_date.strftime("%-d %b") }}{{ item.end_date.strftime("%-d %b %Y") }}
{%- endif -%}
</td>
<td>
{% if item.url %}<a href="{{ item.url }}">{{ item.name }}</a>
{% else %}{{ item.name }}{% endif %}
{% if item.going and not (item.accommodation_booked or item.travel_booked) %}
<span class="badge text-bg-primary ms-1">{{ badge }}</span>
{% endif %}
{% if item.accommodation_booked %}
<span class="badge text-bg-success ms-1">accommodation</span>
{% endif %}
{% if item.transport_booked %}
<span class="badge text-bg-success ms-1">transport</span>
{% endif %}
{% if item.linked_trip %}
{% set trip = item.linked_trip %}
<a href="{{ url_for('trip_page', start=trip.start.isoformat()) }}"
class="text-muted ms-1 text-decoration-none"
title="Trip: {{ trip.title }}">
🧳{% if trip.title != item.name %} {{ trip.title }}{% endif %}
</a>
{% endif %}
</td>
<td class="text-muted small">{{ item.topic }}</td>
<td class="text-nowrap">
{% set country = get_country(item.country) if item.country else None %}
{% if country %}{{ country.flag }} {{ item.location }}
{% elif item.online %}💻 Online
{% else %}{{ item.location }}{% endif %}
</td>
<td class="text-nowrap text-muted small">
{% if item.cfp_end %}{{ item.cfp_end.strftime("%-d %b %Y") }}{% endif %}
</td>
<td>
{% if item.price and item.currency %}
<span class="badge bg-info text-nowrap">{{ "{:,d}".format(item.price | int) }} {{ item.currency }}</span>
{% if item.currency != "GBP" and item.currency in fx_rate %}
<span class="badge bg-info text-nowrap">{{ "{:,.0f}".format(item.price / fx_rate[item.currency]) }} GBP</span>
{% endif %}
{% elif item.free %}
<span class="badge bg-success text-nowrap">free</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endmacro %} {% endmacro %}
{% block content %} {% block content %}
@ -140,11 +208,9 @@
{{ render_timeline(timeline) }} {{ render_timeline(timeline) }}
<div class="grid-container"> {{ conf_table("Current", current, "attending") }}
{{ section("Current", current, "attending") }} {{ conf_table("Future", future, "going") }}
{{ section("Future", future, "going") }} {{ conf_table("Past", past|reverse|list, "went") }}
{{ section("Past", past|reverse|list, "went") }}
</div>
</div> </div>
{% endblock %} {% endblock %}