615 lines
13 KiB
Typst
615 lines
13 KiB
Typst
// ==================================================
|
|
// Config Variables
|
|
// ==================================================
|
|
|
|
#let font-body = "Pretendard"
|
|
#let font-serif = ("Source Serif 4", "Source Han Serif K")
|
|
#let font-display = ("Source Serif 4 Display", "Source Han Serif K")
|
|
#let font-subhead = ("Source Serif 4 Subhead", "Source Han Serif K")
|
|
#let font-caption = ("Source Serif 4 Caption", "Source Han Serif K")
|
|
// #let font-code =
|
|
|
|
#let color-surface = white
|
|
#let color-ink = color.oklch(22%, 0.01, 250deg)
|
|
#let color-ink-inverse = color.oklch(97%, 0.01, 250deg)
|
|
#let color-ink-secondary = color.oklch(60%, 0.01, 250deg)
|
|
#let color-ink-helper = color.oklch(50%, 0.01, 250deg)
|
|
#let color-ink-faint = color.oklch(70%, 0.01, 250deg)
|
|
#let color-rule-faint = color.oklch(85%, 0.01, 250deg)
|
|
|
|
#let bleed = 1.5cm
|
|
|
|
// ==================================================
|
|
// Metadata
|
|
// ==================================================
|
|
|
|
#let meta = state(
|
|
"meta",
|
|
(
|
|
cur_domain: 0,
|
|
total_domain: 0,
|
|
domain_name: "",
|
|
domain_detail: "",
|
|
date: "",
|
|
author: "",
|
|
),
|
|
)
|
|
|
|
#let color-palette = state(
|
|
"color-palette",
|
|
(
|
|
cover-surface: white,
|
|
callout-surface: white,
|
|
callout-border: black,
|
|
rule: black,
|
|
problem: black,
|
|
accent: black,
|
|
accent-faint: white,
|
|
),
|
|
)
|
|
|
|
|
|
// ==================================================
|
|
// Util functions
|
|
// ==================================================
|
|
|
|
#let pad-zeros(num, width) = {
|
|
let s = str(num)
|
|
"0" * calc.max(0, width - s.len()) + s
|
|
}
|
|
|
|
#let make_palette(hue) = (
|
|
cover-surface: color.oklch(97%, 0.01, hue),
|
|
callout-surface: color.oklch(97%, 0.02, hue),
|
|
callout-border: color.oklch(85%, 0.05, hue),
|
|
rule: color.oklch(90%, 0.02, hue),
|
|
problem: color.oklch(75%, 0.04, hue),
|
|
accent: color.oklch(40%, 0.09, hue),
|
|
accent-faint: color.oklch(70%, 0.02, hue),
|
|
)
|
|
|
|
#let heading_text(it) = {
|
|
if it.has("text") {
|
|
it.text
|
|
} else if it.has("children") {
|
|
it.children.map(heading_text).join("")
|
|
} else if it == linebreak() {
|
|
" "
|
|
} else {
|
|
""
|
|
}
|
|
}
|
|
|
|
|
|
// ==================================================
|
|
// Body components
|
|
// ==================================================
|
|
|
|
#let callout(bg_color: none, border_color: none, body) = context {
|
|
let cp = color-palette.get()
|
|
let bg_c = if bg_color == none { cp.callout-surface } else { bg_color }
|
|
let border_c = if border_color == none { cp.callout-border } else { border_color }
|
|
|
|
let x_pad = 12pt
|
|
let y_pad = 18pt
|
|
let gap = 24pt
|
|
|
|
block(
|
|
width: 100%,
|
|
above: gap + y_pad,
|
|
below: gap + y_pad,
|
|
outset: (x: x_pad, y: y_pad),
|
|
radius: 4pt,
|
|
fill: bg_c,
|
|
stroke: (paint: border_c),
|
|
)[
|
|
#body
|
|
]
|
|
}
|
|
|
|
#let block_quote(color: none, content) = context {
|
|
let gutter = 15pt
|
|
let line-width = 2pt
|
|
let c = if color != none { color } else { color-palette.get().accent }
|
|
|
|
layout(size => {
|
|
let body-width = size.width - line-width - gutter
|
|
|
|
let body-size = measure(
|
|
content,
|
|
width: body-width,
|
|
)
|
|
|
|
grid(
|
|
columns: (line-width, 1fr),
|
|
gutter: gutter,
|
|
align: horizon,
|
|
|
|
line(
|
|
stroke: (
|
|
paint: c,
|
|
thickness: line-width,
|
|
cap: "round",
|
|
),
|
|
angle: 90deg,
|
|
length: body-size.height + 15pt,
|
|
),
|
|
)[
|
|
#content
|
|
]
|
|
})
|
|
}
|
|
|
|
#let kicker(color: color-ink-helper, content) = {
|
|
set text(
|
|
font: font-body,
|
|
weight: 700,
|
|
size: 10pt,
|
|
tracking: 1pt,
|
|
fill: color,
|
|
)
|
|
|
|
block(below: 1.5em)[
|
|
#content
|
|
]
|
|
}
|
|
|
|
#let problem(content) = context {
|
|
let c = color-palette.get().problem
|
|
|
|
block_quote(color: c)[
|
|
#kicker(color: c)[문제]
|
|
|
|
#content
|
|
]
|
|
}
|
|
|
|
#let solution(content) = context {
|
|
let c = color-palette.get().accent
|
|
|
|
block_quote(color: c)[
|
|
#kicker(color: c)[해결책]
|
|
|
|
#content
|
|
]
|
|
}
|
|
|
|
#let hl(content) = context {
|
|
let cp = color-palette.get()
|
|
|
|
// if content == "" {}
|
|
|
|
set text(
|
|
font: font-body,
|
|
weight: 700,
|
|
fill: cp.accent,
|
|
tracking: 1pt,
|
|
size: 10pt,
|
|
)
|
|
|
|
grid(
|
|
columns: (1fr, auto, 1fr),
|
|
align: horizon,
|
|
gutter: 10pt,
|
|
line(length: 100%, stroke: (paint: cp.rule)), content, line(length: 100%, stroke: (paint: cp.rule)),
|
|
)
|
|
}
|
|
|
|
#let block_equation(content) = {
|
|
callout()[
|
|
#content
|
|
]
|
|
}
|
|
|
|
#let stack(layers) = context {
|
|
let cp = color-palette.get()
|
|
let radius = 12pt
|
|
let gutter = 12pt
|
|
let gap = 24pt
|
|
|
|
for (idx, (title, body)) in layers.enumerate() {
|
|
let is_last = idx == layers.len() - 1
|
|
|
|
let content = {
|
|
v(radius - 12pt / 2)
|
|
text(
|
|
weight: 700,
|
|
)[
|
|
#title
|
|
#linebreak()
|
|
]
|
|
body
|
|
v(gap)
|
|
}
|
|
|
|
block(
|
|
above: if idx == 0 { auto } else { 0pt },
|
|
below: if is_last { auto } else { 0pt },
|
|
width: 100%,
|
|
)[
|
|
#layout(size => {
|
|
let content_width = size.width - radius * 2 - gutter
|
|
let content_size = measure(content, width: content_width)
|
|
|
|
grid(
|
|
columns: (radius * 2, 1fr),
|
|
gutter: gutter,
|
|
grid(
|
|
rows: (radius * 2, auto),
|
|
circle(radius: radius, fill: cp.accent)[
|
|
#set align(center + horizon)
|
|
#set text(font: font-serif, fill: color-ink-inverse, size: 12pt, weight: 700)
|
|
#{ idx + 1 }
|
|
],
|
|
line(
|
|
start: (50%, 0pt),
|
|
end: (50%, content_size.height - radius * 2),
|
|
stroke: (
|
|
paint: cp.accent,
|
|
thickness: 1pt,
|
|
cap: "round",
|
|
),
|
|
),
|
|
),
|
|
content,
|
|
)
|
|
})
|
|
]
|
|
}
|
|
}
|
|
|
|
#let selection(choices) = context {
|
|
let cp = color-palette.get()
|
|
|
|
for (name, selected, body) in choices {
|
|
let status = if selected [채택] else [기각]
|
|
let bg_color = if selected { cp.callout-surface } else { color-surface }
|
|
let border_color = if selected { cp.callout-border } else { color-rule-faint }
|
|
let name_color = if selected { cp.accent } else { cp.accent-faint }
|
|
let body_color = if selected { color-ink } else { color-ink-faint }
|
|
|
|
callout(bg_color: bg_color, border_color: border_color)[
|
|
#text(
|
|
weight: 700,
|
|
fill: name_color,
|
|
)[
|
|
#{ name }#h(0.5em)#text(size: 10pt)[#status]
|
|
#linebreak()
|
|
]
|
|
#text(
|
|
fill: body_color,
|
|
)[
|
|
#body
|
|
]
|
|
]
|
|
}
|
|
}
|
|
|
|
#let header_template(dark: false, font_color: none, show_activity: false) = place(
|
|
top + left,
|
|
dy: 1cm,
|
|
dx: -bleed,
|
|
)[
|
|
#let fc = if font_color != none { font_color } else if dark == true { color-ink-helper } else { color-ink-helper }
|
|
|
|
#block(width: 100% + 2 * bleed)[
|
|
#text(
|
|
font: font-caption,
|
|
size: 10pt,
|
|
fill: fc,
|
|
tracking: 0pt,
|
|
)[
|
|
#context {
|
|
let m = meta.get()
|
|
link((page: 1, x: 0pt, y: 0pt))[#m.cur_domain. #m.domain_name]
|
|
h(1fr)
|
|
if show_activity {
|
|
let level1s = query(heading.where(level: 1, outlined: true))
|
|
let n = counter(heading).get().at(0)
|
|
let cur = level1s.at(n - 1)
|
|
let prefix = str(m.cur_domain) + "." + str(n) + ". "
|
|
link(
|
|
cur.location(),
|
|
prefix + cur.body,
|
|
)
|
|
}
|
|
}
|
|
]
|
|
]
|
|
]
|
|
|
|
#let footer_template(dark: false, font_color: none) = context {
|
|
let fc = if font_color != none { font_color } else if dark == true { color-ink-helper } else { color-ink-helper }
|
|
|
|
align(center)[
|
|
#set text(
|
|
size: 10pt,
|
|
fill: fc,
|
|
)
|
|
|
|
#counter(page).display("1")
|
|
]
|
|
}
|
|
|
|
|
|
// ==================================================
|
|
// Page template
|
|
// ==================================================
|
|
|
|
// Cover template function
|
|
#let cover() = context {
|
|
let cp = color-palette.get()
|
|
|
|
page(
|
|
fill: cp.cover-surface,
|
|
)[
|
|
#set par(spacing: 0pt)
|
|
|
|
#v(3cm)
|
|
|
|
#context {
|
|
let m = meta.get()
|
|
|
|
align(center)[
|
|
#set text(
|
|
fill: cp.accent,
|
|
)
|
|
|
|
#text(font: font-caption)[
|
|
#pad-zeros(m.cur_domain, 2) / #pad-zeros(m.total_domain, 2)
|
|
]
|
|
|
|
#v(12pt)
|
|
|
|
#text(font: font-display, weight: 700, size: 40pt)[#m.domain_name]
|
|
|
|
#v(20pt)
|
|
|
|
#text(font: font-subhead, size: 15pt)[#m.domain_detail]
|
|
]
|
|
|
|
align(center + bottom)[
|
|
#text(weight: 700, size: 10pt, tracking: 1pt)[
|
|
#m.date
|
|
#v(10pt)
|
|
#upper(m.author)
|
|
]
|
|
]
|
|
}
|
|
|
|
#v(1cm)
|
|
]
|
|
}
|
|
|
|
// Table of contents templage function
|
|
#let table_of_contents() = page(
|
|
footer: footer_template(),
|
|
)[
|
|
#context {
|
|
let m = meta.get()
|
|
heading(level: 1, outlined: false, numbering: none)[#m.cur_domain. #m.domain_name]
|
|
}
|
|
|
|
#outline(title: none, depth: 2)
|
|
]
|
|
|
|
// Domain frame template function
|
|
#let domain_frame(title: "", bg_img_path: none, font_color: color-ink, chrome_color: color-ink-helper, content) = page(
|
|
background: place(
|
|
center + horizon,
|
|
if bg_img_path != none {
|
|
image(bg_img_path, width: 100.5%, height: 100.5%, fit: "cover")
|
|
},
|
|
),
|
|
header: header_template(font_color: chrome_color),
|
|
footer: footer_template(font_color: chrome_color),
|
|
)[
|
|
#set text(fill: font_color)
|
|
|
|
#heading(level: 1)[
|
|
#title
|
|
]
|
|
|
|
#content
|
|
]
|
|
|
|
// Activity cover template function
|
|
#let activity_cover(name: "", hook: "", content) = page(
|
|
header: header_template(),
|
|
footer: footer_template(),
|
|
)[
|
|
#heading(level: 1)[
|
|
#name
|
|
]
|
|
|
|
#block_quote()[
|
|
#hook
|
|
]
|
|
|
|
#content
|
|
]
|
|
|
|
// Activity intro template function
|
|
#let activity_intro(title: "", content) = page(
|
|
header: header_template(show_activity: true),
|
|
footer: footer_template(),
|
|
)[
|
|
#kicker("프로젝트 설명")
|
|
|
|
#heading(level: 2)[
|
|
#title
|
|
]
|
|
|
|
#content
|
|
]
|
|
|
|
// Activity process
|
|
#let activity_process(title: "", content) = page(
|
|
header: header_template(show_activity: true),
|
|
)[
|
|
#kicker("시행착오")
|
|
|
|
#heading(level: 2)[
|
|
#title
|
|
]
|
|
|
|
#content
|
|
]
|
|
|
|
// Activity result
|
|
#let activity_result(title: "", content) = page(
|
|
header: header_template(show_activity: true),
|
|
)[
|
|
#kicker("결론")
|
|
|
|
#heading(level: 2)[
|
|
#title
|
|
]
|
|
|
|
#content
|
|
]
|
|
|
|
|
|
// ==================================================
|
|
// Style Settings
|
|
// ==================================================
|
|
|
|
#let base(
|
|
cur_domain: 0,
|
|
total_domain: 0,
|
|
domain_name: "",
|
|
domain_detail: "",
|
|
date: "",
|
|
author: "",
|
|
main-hue: 0deg,
|
|
doc,
|
|
) = {
|
|
// meta data
|
|
meta.update(m => (
|
|
m
|
|
+ (
|
|
cur_domain: cur_domain,
|
|
total_domain: total_domain,
|
|
domain_name: domain_name,
|
|
domain_detail: domain_detail,
|
|
date: date,
|
|
author: author,
|
|
)
|
|
))
|
|
|
|
// color palette
|
|
let cp = make_palette(main-hue)
|
|
color-palette.update(m => (m + cp))
|
|
|
|
// document & page config
|
|
set document(
|
|
title: str(cur_domain) + ". " + domain_name,
|
|
description: domain_detail,
|
|
author: author,
|
|
)
|
|
|
|
set page(
|
|
paper: "a4",
|
|
margin: (x: 2.8cm, y: 4.2cm),
|
|
fill: color-surface,
|
|
)
|
|
|
|
// text config
|
|
set text(
|
|
font: font-body,
|
|
weight: 400,
|
|
fill: color-ink,
|
|
size: 12pt,
|
|
)
|
|
|
|
set par(
|
|
leading: 1.2em,
|
|
spacing: 2.4em,
|
|
)
|
|
|
|
show strong: set text(fill: cp.accent)
|
|
|
|
// list config
|
|
show list: set par(leading: 0.8em)
|
|
set list(
|
|
marker: ([--], [•]),
|
|
indent: 0.6em,
|
|
body-indent: 0.8em,
|
|
spacing: 1.2em,
|
|
)
|
|
|
|
show enum: set par(leading: 0.8em)
|
|
set enum(
|
|
indent: 0.6em,
|
|
body-indent: 0.8em,
|
|
spacing: 1.2em,
|
|
)
|
|
|
|
// heading config
|
|
show heading.where(level: 1): set heading(numbering: (..nums) => (
|
|
str(cur_domain) + "." + nums.pos().map(str).join(".") + "."
|
|
))
|
|
show heading.where(level: 1): set block(above: 0em, below: 50pt)
|
|
show heading.where(level: 1): set text(
|
|
font: font-display,
|
|
weight: 700,
|
|
size: 27pt,
|
|
tracking: 0pt,
|
|
)
|
|
|
|
show heading.where(level: 2): set block(above: 0em, below: 50pt)
|
|
show heading.where(level: 2): set par(leading: 1em)
|
|
show heading.where(level: 2): set text(
|
|
font: font-subhead,
|
|
weight: 700,
|
|
size: 18pt,
|
|
tracking: 0pt,
|
|
fill: color-ink,
|
|
)
|
|
|
|
// outline config
|
|
set outline(
|
|
indent: 1em,
|
|
)
|
|
|
|
show outline: set par(leading: 1em)
|
|
|
|
show outline.entry: it => link(
|
|
it.element.location(),
|
|
)[
|
|
#grid(
|
|
columns: (1fr, auto),
|
|
it.indented(it.prefix(), heading_text(it.body())), it.page(),
|
|
)
|
|
]
|
|
|
|
show outline.entry.where(level: 1): it => {
|
|
set text(
|
|
font: font-subhead,
|
|
size: 15pt,
|
|
weight: 700,
|
|
)
|
|
|
|
block(above: 50pt)[
|
|
#it
|
|
#line(length: 100%, stroke: (paint: color-ink-secondary, thickness: 1pt))
|
|
]
|
|
}
|
|
|
|
show outline.entry.where(level: 2): it => {
|
|
set text(
|
|
font: font-body,
|
|
size: 12pt,
|
|
weight: 700,
|
|
fill: color-ink-secondary,
|
|
)
|
|
|
|
block(above: 20pt)[
|
|
#it
|
|
]
|
|
}
|
|
|
|
// render content
|
|
doc
|
|
}
|