From 883dbebe88d46c700df830a5c0d7bb16697544cd Mon Sep 17 00:00:00 2001 From: seung6lee Date: Sat, 22 Aug 2026 10:51:16 +0900 Subject: [PATCH] make template.typ --- .gitignore | 3 + note.md | 32 +++ template.typ | 597 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 632 insertions(+) create mode 100644 .gitignore create mode 100644 note.md create mode 100644 template.typ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c02c04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +.vscode +*.pdf diff --git a/note.md b/note.md new file mode 100644 index 0000000..1748409 --- /dev/null +++ b/note.md @@ -0,0 +1,32 @@ +## Color structure + +``` +STATIC +├── ink +├── ink-light +├── caption +└── rule + +DOMAIN — hue based +├── bg +├── callout-bg +├── accent +├── chrome +└── chrome-light +``` + + + + + +___ + +## OKLCH Hue + +| Domain | Hue | Color name | +| ----------- | ------ | -------------- | +| Research | 60deg | Earthy Orange | +| Competition | 135deg | Matte Lime | +| Challenge | 195deg | Shiny Teal | +| Leadership | 285deg | Nicole | +| KAISTian | 330deg | Comfort Orchid | diff --git a/template.typ b/template.typ new file mode 100644 index 0000000..ec6edbd --- /dev/null +++ b/template.typ @@ -0,0 +1,597 @@ +// ================================================== +// 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), +) + + +// ================================================== +// 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(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) + #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: 2em, + ) + + show outline: set par(leading: 1em) + + show outline.entry: it => link( + it.element.location(), + )[ + #grid( + columns: (1fr, auto), + it.indented(it.prefix(), 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 +}