|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +permalink: /time-table/ |
| 4 | +title: タイムテーブル |
| 5 | +--- |
| 6 | +{% include navbar.html %} |
| 7 | + |
| 8 | +{% assign tt = site.data.time_table %} |
| 9 | +{% assign slot = tt.slot_minutes | default: 15 %} |
| 10 | +{% assign rooms = tt.rooms %} |
| 11 | +{% assign room_count = rooms | size %} |
| 12 | + |
| 13 | +{% assign day_start_min = 600 %} |
| 14 | +{% assign day_end_min = 960 %} |
| 15 | +{% assign total_minutes = day_end_min | minus: day_start_min %} |
| 16 | +{% assign slots_count = total_minutes | divided_by: slot %} |
| 17 | +{% assign last_row = slots_count | minus: 1 %} |
| 18 | + |
| 19 | +<section class="max-w-[1200px] mx-auto px-4 sm:px-8 mt-30 xl:mt-15"> |
| 20 | + <h2 class="text-4xl text-center mb-8"> |
| 21 | + Time table |
| 22 | + <span class="block mt-3 text-2xl">タイムテーブル</span> |
| 23 | + </h2> |
| 24 | + |
| 25 | + <div class="ttable-wrap" aria-label="タイムテーブル(横スクロール可)"> |
| 26 | + <table class="ttable" style="--room-count: {{ room_count }};"> |
| 27 | + <caption> |
| 28 | + {{ tt.date | default: site.date_event }} のタイムテーブル |
| 29 | + </caption> |
| 30 | + |
| 31 | + <thead> |
| 32 | + <tr> |
| 33 | + <th scope="col" class="ttable__th ttable__th--start">時間</th> |
| 34 | + {% for r in rooms %} |
| 35 | + {% assign rstyle = tt.room_styles[r] %} |
| 36 | + <th scope="col" |
| 37 | + class="ttable__th ttable__th--room" |
| 38 | + style="--room-color: {{ rstyle.color | default: '#c43b3b' }};"> |
| 39 | + <span class="ttable__room-cap">{{ r }}</span> |
| 40 | + </th> |
| 41 | + {% endfor %} |
| 42 | + </tr> |
| 43 | + </thead> |
| 44 | + |
| 45 | + <tbody> |
| 46 | + {% for i in (0..last_row) %} |
| 47 | + {% assign row_min = i | times: slot | plus: day_start_min %} |
| 48 | + {% assign h = row_min | divided_by: 60 %} |
| 49 | + {% assign mf = row_min | modulo: 60 | plus: 0 | prepend: '0' | slice: -2, 2 %} |
| 50 | + |
| 51 | + <tr> |
| 52 | + <!-- 左1列(sticky) --> |
| 53 | + <th scope="row" class="ttable__cell ttable__cell--start">{{ h }}:{{ mf }}</th> |
| 54 | + |
| 55 | + {% for r in rooms %} |
| 56 | + {% assign rstyle = tt.room_styles[r] %} |
| 57 | + {% assign events_in_room = tt.events | where: 'room', r | sort: 'start' %} |
| 58 | + |
| 59 | + {% assign active_event = nil %} |
| 60 | + {% assign active_event_start_index = nil %} |
| 61 | + |
| 62 | + {% for ev in events_in_room %} |
| 63 | + {% assign s_h = ev.start | split: ':' | first | plus: 0 %} |
| 64 | + {% assign s_m = ev.start | split: ':' | last | plus: 0 %} |
| 65 | + {% assign e_h = ev.end | split: ':' | first | plus: 0 %} |
| 66 | + {% assign e_m = ev.end | split: ':' | last | plus: 0 %} |
| 67 | + {% assign s_min = s_h | times: 60 | plus: s_m %} |
| 68 | + {% assign e_min = e_h | times: 60 | plus: e_m %} |
| 69 | + |
| 70 | + {% assign s_clamped = s_min %} |
| 71 | + {% if s_clamped < day_start_min %}{% assign s_clamped = day_start_min %}{% endif %} |
| 72 | + {% assign e_clamped = e_min %} |
| 73 | + {% if e_clamped > day_end_min %}{% assign e_clamped = day_end_min %}{% endif %} |
| 74 | + |
| 75 | + {% assign span_minutes = e_clamped | minus: s_clamped %} |
| 76 | + {% if span_minutes > 0 %} |
| 77 | + {% assign numerator = span_minutes | plus: slot | minus: 1 %} |
| 78 | + {% assign span_slots = numerator | divided_by: slot %} |
| 79 | + {% assign s_index = s_clamped | minus: day_start_min | divided_by: slot %} |
| 80 | + {% assign e_index = s_index | plus: span_slots %} |
| 81 | + {% if i >= s_index and i < e_index %} |
| 82 | + {% assign active_event = ev %} |
| 83 | + {% assign active_event_start_index = s_index %} |
| 84 | + {% endif %} |
| 85 | + {% endif %} |
| 86 | + {% endfor %} |
| 87 | + |
| 88 | + {% if active_event and i == active_event_start_index %} |
| 89 | + {%- assign s_h = active_event.start | split: ':' | first | plus: 0 -%} |
| 90 | + {%- assign s_m = active_event.start | split: ':' | last | plus: 0 -%} |
| 91 | + {%- assign e_h = active_event.end | split: ':' | first | plus: 0 -%} |
| 92 | + {%- assign e_m = active_event.end | split: ':' | last | plus: 0 -%} |
| 93 | + {%- assign s_min = s_h | times: 60 | plus: s_m -%} |
| 94 | + {%- assign e_min = e_h | times: 60 | plus: e_m -%} |
| 95 | + {%- if s_min < day_start_min -%}{%- assign s_min = day_start_min -%}{%- endif -%} |
| 96 | + {%- if e_min > day_end_min -%}{%- assign e_min = day_end_min -%}{%- endif -%} |
| 97 | + {%- assign span_minutes = e_min | minus: s_min -%} |
| 98 | + {%- assign numerator = span_minutes | plus: slot | minus: 1 -%} |
| 99 | + {%- assign span_slots = numerator | divided_by: slot -%} |
| 100 | + {%- assign accent = active_event.accent | default: rstyle.color -%} |
| 101 | + <td class="ttable__cell ttable__cell--event" rowspan="{{ span_slots }}" style="--span: {{ span_slots }};"> |
| 102 | + <div class="ttable__event" style="--accent: {{ accent | default: '#c43b3b' }};"> |
| 103 | + <div class="ttable__event-time">{{ active_event.start }}–{{ active_event.end }}</div> |
| 104 | + <div class="ttable__event-title">{{ active_event.title }}</div> |
| 105 | + {% if active_event.subtitle %} |
| 106 | + <div class="ttable__event-subtitle">{{ active_event.subtitle }}</div> |
| 107 | + {% endif %} |
| 108 | + {% if active_event.badge %} |
| 109 | + <span class="ttable__badge">{{ active_event.badge }}</span> |
| 110 | + {% endif %} |
| 111 | + </div> |
| 112 | + </td> |
| 113 | + {% elsif active_event == nil %} |
| 114 | + <td class="ttable__cell ttable__cell--empty" aria-label="空き時間"></td> |
| 115 | + {% endif %} |
| 116 | + {% endfor %} |
| 117 | + </tr> |
| 118 | + {% endfor %} |
| 119 | + </tbody> |
| 120 | + </table> |
| 121 | + </div> |
| 122 | + |
| 123 | + <script> |
| 124 | + // 重複検知(後勝ち) |
| 125 | + (function () { |
| 126 | + const data = { |
| 127 | + slot: {{ slot | jsonify }}, |
| 128 | + dayStart: {{ day_start_min | jsonify }}, |
| 129 | + dayEnd: {{ day_end_min | jsonify }}, |
| 130 | + rooms: {{ rooms | jsonify }}, |
| 131 | + events: {{ tt.events | jsonify }} |
| 132 | + }; |
| 133 | + const map = new Map(); |
| 134 | + data.rooms.forEach(r => map.set(r, [])); |
| 135 | + const toMin = t => { const [h,m]=String(t).split(':').map(Number); return h*60+m; }; |
| 136 | + for (const ev of data.events) (map.get(ev.room)||map.set(ev.room,[]).get(ev.room)).push(ev); |
| 137 | + for (const [room, list] of map) { |
| 138 | + list.sort((a,b)=>toMin(a.start)-toMin(b.start)); |
| 139 | + for (let i=0;i<list.length-1;i++){ |
| 140 | + const a=list[i], b=list[i+1]; |
| 141 | + const as=Math.max(toMin(a.start), data.dayStart); |
| 142 | + const ae=Math.min(toMin(a.end), data.dayEnd); |
| 143 | + const bs=Math.max(toMin(b.start), data.dayStart); |
| 144 | + if (ae>bs) console.warn(`[time-table] Overlap in "${room}":`, a, b, '→ later wins'); |
| 145 | + } |
| 146 | + } |
| 147 | + })(); |
| 148 | + </script> |
| 149 | +</section> |
0 commit comments