165 lines
3.5 KiB
Typst
165 lines
3.5 KiB
Typst
// Functions
|
|
|
|
#let format-time(time) = {
|
|
let parts = time.split(":")
|
|
let hours = int(parts.at(0))
|
|
let minutes = int(parts.at(1))
|
|
let period = if hours < 12 { "AM" } else { "PM" }
|
|
hours = if hours == 0 { 12 } else if hours > 12 { hours - 12 } else { hours }
|
|
let formatted-minutes = if minutes < 10 { "0" + str(minutes) } else { str(minutes) }
|
|
str(hours) + ":" + formatted-minutes + " " + period
|
|
}
|
|
|
|
#let group-by(arr, key-func) = {
|
|
let result = (:)
|
|
for item in arr {
|
|
let key = key-func(item)
|
|
if key not in result.keys() {
|
|
result.insert(key, ())
|
|
}
|
|
result.at(key) = result.at(key) + (item,)
|
|
}
|
|
result
|
|
}
|
|
|
|
#let pluralize(word) = {
|
|
if word.ends-with("s") or word.ends-with("sh") or word.ends-with("ch") {
|
|
word + "es"
|
|
} else {
|
|
word + "s"
|
|
}
|
|
}
|
|
|
|
#let current-chapter-title() = context {
|
|
let headings = query(heading.where(level: 1))
|
|
let current-location = here()
|
|
let relevant-headings = headings.filter(heading => heading.location().page() <= current-location.page())
|
|
if relevant-headings == () { "" } else { relevant-headings.last().body }
|
|
}
|
|
|
|
// Setup page and header
|
|
|
|
#set text(size: 18pt, font: "Pixelify Sans")
|
|
#set page(
|
|
margin: (
|
|
top: 2cm,
|
|
bottom: 1.5cm,
|
|
x: 1cm,
|
|
),
|
|
header: context {
|
|
let i = here().page()
|
|
if query(heading).any(it => it.location().page() == i) {
|
|
return
|
|
}
|
|
|
|
let before = query(selector(heading).before(here()))
|
|
if before != () {
|
|
let headings = before.filter(it => it.level <= 2)
|
|
let level1 = headings.filter(it => it.level == 1).last()
|
|
let level2 = headings.filter(it => it.level == 2).last()
|
|
|
|
let title = if level2 == none {
|
|
level1.body
|
|
} else {
|
|
[#level1.body — #level2.body]
|
|
}
|
|
|
|
grid(
|
|
columns: (1fr, 1fr),
|
|
align: (left, right),
|
|
[#i], title,
|
|
)
|
|
}
|
|
},
|
|
)
|
|
|
|
// Front page
|
|
|
|
#align(center)[
|
|
#image("hope.png", width: 70%)
|
|
#v(2em)
|
|
]
|
|
|
|
#align(center)[
|
|
#image("halftone-hackerquarterly.png", width: 100%)
|
|
#v(2em)
|
|
#set text(size: 1.25em)
|
|
Program Guide
|
|
]
|
|
|
|
// TOC
|
|
|
|
#set heading()
|
|
#outline()
|
|
|
|
#pagebreak()
|
|
|
|
// Main content taken from schedule.json
|
|
|
|
#let data = json("schedule.json")
|
|
#let conference = data.schedule.conference
|
|
#let days = conference.days
|
|
|
|
#for day in days [
|
|
= Day #day.index
|
|
|
|
#let events-by-type = group-by(
|
|
day
|
|
.rooms
|
|
.keys()
|
|
.map(room => {
|
|
let events = day.rooms.at(room)
|
|
(room, events)
|
|
}),
|
|
event => event.at(1).at(0).type,
|
|
)
|
|
|
|
#for (event-type, rooms) in events-by-type [
|
|
== #pluralize(event-type)
|
|
|
|
#for (room, events) in rooms [
|
|
|
|
#for event in events [
|
|
#box(
|
|
[
|
|
#line(length: 100%, stroke: (dash: "dashed"))
|
|
_#format-time(event.start): [#event.title]_
|
|
|
|
#if event.abstract != none [
|
|
#text(size: 0.75em, event.abstract)
|
|
]
|
|
|
|
#if event.persons.len() > 0 [
|
|
_Speakers:
|
|
#(
|
|
event.persons.map(person => {
|
|
if "public_name" in person.keys() { person.public_name } else { "Unknown" }
|
|
}).join(", ")
|
|
)_
|
|
#linebreak()
|
|
_Location: #room _
|
|
]
|
|
],
|
|
)
|
|
#v(0.5em)
|
|
]
|
|
]
|
|
]
|
|
#pagebreak()
|
|
]
|
|
|
|
// Last page
|
|
|
|
#align(center)[
|
|
#image("hope16_schedule.png", width: 100%)
|
|
https://schedule.hope.net/hope16/schedule
|
|
#v(2em)
|
|
#box([
|
|
#set text(font: "JetBrains Mono")
|
|
#align(left)[
|
|
\$ typst \--version\
|
|
typst 0.13.1
|
|
#image("git.png", height: 150pt)
|
|
]
|
|
])
|
|
]
|