Skip to content

Commit 4a325bb

Browse files
committed
feat: publish slides
1 parent 0cfe616 commit 4a325bb

50 files changed

Lines changed: 415 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

PUBLISHING.md

Lines changed: 58 additions & 0 deletions

build_site.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Build the static web site (docs/) for the CNCK Kube Community Night decks.
4+
5+
For each event listed in events.config.json it converts the .pptx to per-slide
6+
PNG images and writes the manifests the web viewer reads. Run locally or in CI.
7+
8+
Requirements: libreoffice (soffice) + poppler-utils (pdftoppm) + a Korean font.
9+
10+
python3 build_site.py
11+
"""
12+
import glob
13+
import json
14+
import os
15+
import shutil
16+
import subprocess
17+
import sys
18+
19+
ROOT = os.path.dirname(os.path.abspath(__file__))
20+
DOCS = os.path.join(ROOT, "docs")
21+
DPI = "144" # ~1920px wide slides
22+
23+
24+
def run(cmd):
25+
print(" $", " ".join(cmd))
26+
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
27+
28+
29+
def render_event(ev):
30+
eid = str(ev["id"])
31+
pptx = os.path.join(ROOT, ev["pptx"])
32+
if not os.path.exists(pptx):
33+
print(f" !! pptx not found: {pptx} — skipping event {eid}")
34+
return None
35+
36+
outdir = os.path.join(DOCS, "events", eid)
37+
slides_dir = os.path.join(outdir, "slides")
38+
os.makedirs(slides_dir, exist_ok=True)
39+
for f in glob.glob(os.path.join(slides_dir, "*.png")):
40+
os.remove(f)
41+
42+
# pptx -> pdf
43+
run(["libreoffice", "--headless", "--convert-to", "pdf", "--outdir", outdir, pptx])
44+
pdf = os.path.join(outdir, os.path.splitext(os.path.basename(pptx))[0] + ".pdf")
45+
46+
# pdf -> png (one per page)
47+
run(["pdftoppm", "-png", "-r", DPI, pdf, os.path.join(slides_dir, "slide")])
48+
os.remove(pdf)
49+
50+
pages = sorted(glob.glob(os.path.join(slides_dir, "slide-*.png")))
51+
for i, p in enumerate(pages, 1):
52+
os.rename(p, os.path.join(slides_dir, f"{i:02d}.png"))
53+
54+
# copy the pptx for download
55+
shutil.copy(pptx, os.path.join(outdir, os.path.basename(pptx)))
56+
57+
meta = {
58+
"id": eid,
59+
"no": ev["no"],
60+
"title": ev["title"],
61+
"subtitle": ev.get("subtitle", ""),
62+
"date": ev["date"],
63+
"slideCount": len(pages),
64+
"pptx": os.path.basename(pptx),
65+
}
66+
with open(os.path.join(outdir, "meta.json"), "w", encoding="utf-8") as fh:
67+
json.dump(meta, fh, ensure_ascii=False, indent=2)
68+
print(f" -> event {eid}: {len(pages)} slides")
69+
return meta
70+
71+
72+
def main():
73+
with open(os.path.join(ROOT, "events.config.json"), encoding="utf-8") as fh:
74+
config = json.load(fh)
75+
76+
manifest = []
77+
for ev in config:
78+
meta = render_event(ev)
79+
if meta:
80+
manifest.append(meta)
81+
82+
manifest.sort(key=lambda m: m["no"], reverse=True)
83+
with open(os.path.join(DOCS, "events.json"), "w", encoding="utf-8") as fh:
84+
json.dump(manifest, fh, ensure_ascii=False, indent=2)
85+
86+
if not manifest:
87+
print("No events built.", file=sys.stderr)
88+
sys.exit(1)
89+
print(f"Done. {len(manifest)} event(s) -> {DOCS}")
90+
91+
92+
if __name__ == "__main__":
93+
main()

docs/assets/style.css

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
:root{
2+
--navy:#1B365D; --blue:#0080CC; --accent:#418FDE; --gold:#D4A01E;
3+
--bg:#F8FAFC; --card:#FFFFFF; --ink:#1A1A2E; --mid:#555E6E; --light:#889099;
4+
--stage:#0D1B2A; --line:#E2E8F0;
5+
--font:'Noto Sans KR','Apple SD Gothic Neo','Malgun Gothic',-apple-system,
6+
BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;
7+
}
8+
*{box-sizing:border-box;margin:0;padding:0}
9+
html,body{height:100%}
10+
body{font-family:var(--font);color:var(--ink);background:var(--bg);-webkit-font-smoothing:antialiased}
11+
a{color:inherit;text-decoration:none}
12+
13+
/* ============ INDEX ============ */
14+
body.index{display:flex;flex-direction:column;min-height:100%}
15+
.site-header{
16+
text-align:center;padding:64px 24px 40px;
17+
background:linear-gradient(180deg,#EAF2FB 0%,var(--bg) 100%);
18+
border-bottom:1px solid var(--line);
19+
}
20+
.site-header .brand{color:var(--blue);font-weight:700;letter-spacing:.12em;
21+
font-size:13px;text-transform:uppercase}
22+
.site-header h1{color:var(--navy);font-size:clamp(28px,5vw,48px);font-weight:800;margin:10px 0 6px}
23+
.site-header .tagline{color:var(--mid);font-size:16px}
24+
25+
.events-grid{
26+
flex:1;width:100%;max-width:1080px;margin:0 auto;padding:40px 24px 64px;
27+
display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:24px;
28+
align-content:start;
29+
}
30+
.event-card{
31+
background:var(--card);border:1px solid var(--line);border-radius:16px;overflow:hidden;
32+
box-shadow:0 1px 2px rgba(16,33,60,.04);
33+
transition:transform .15s ease,box-shadow .15s ease,border-color .15s ease;
34+
display:flex;flex-direction:column;
35+
}
36+
.event-card:hover{transform:translateY(-4px);box-shadow:0 12px 28px rgba(16,33,60,.12);
37+
border-color:var(--accent)}
38+
.event-card .thumb{aspect-ratio:16/9;background:#0D1B2A;overflow:hidden}
39+
.event-card .thumb img{width:100%;height:100%;object-fit:cover;display:block}
40+
.event-card .meta{padding:18px 20px 22px}
41+
.event-card .badge{display:inline-block;background:var(--blue);color:#fff;font-weight:700;
42+
font-size:12px;padding:3px 10px;border-radius:999px;margin-bottom:10px}
43+
.event-card h2{color:var(--navy);font-size:19px;font-weight:700;line-height:1.3}
44+
.event-card .date{color:var(--mid);font-size:14px;margin-top:6px}
45+
.event-card .count{color:var(--light);font-size:12px;margin-top:2px}
46+
.empty{color:var(--mid);grid-column:1/-1;text-align:center;padding:60px 0}
47+
.site-footer{text-align:center;padding:24px;color:var(--light);font-size:13px;
48+
border-top:1px solid var(--line)}
49+
.site-footer a{color:var(--blue)}
50+
51+
/* ============ VIEWER ============ */
52+
body.viewer{height:100vh;overflow:hidden;background:var(--stage)}
53+
.stage{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
54+
cursor:pointer;background:
55+
radial-gradient(120% 120% at 50% 0%,#13263b 0%,var(--stage) 60%)}
56+
.slide-img{max-width:100%;max-height:100%;object-fit:contain;
57+
box-shadow:0 10px 50px rgba(0,0,0,.45);user-select:none}
58+
.nav{position:absolute;top:50%;transform:translateY(-50%);z-index:5;
59+
width:54px;height:54px;border:none;border-radius:50%;cursor:pointer;
60+
background:rgba(255,255,255,.10);color:#fff;font-size:30px;line-height:1;
61+
backdrop-filter:blur(4px);transition:background .15s,opacity .2s;opacity:0}
62+
.nav:hover{background:rgba(255,255,255,.22)}
63+
.nav.prev{left:18px}.nav.next{right:18px}
64+
.stage:hover .nav{opacity:1}
65+
66+
.topbar{position:fixed;top:0;left:0;right:0;z-index:10;display:flex;align-items:center;gap:14px;
67+
padding:12px 18px;color:#fff;
68+
background:linear-gradient(180deg,rgba(7,15,26,.78),rgba(7,15,26,0));
69+
transition:opacity .3s;font-size:14px}
70+
.topbar.hide{opacity:0;pointer-events:none}
71+
.topbar .home{color:#cfe2f5;font-weight:600}
72+
.topbar .home:hover{color:#fff}
73+
.topbar .title{font-weight:600;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
74+
.topbar .spacer{flex:1}
75+
.topbar .counter{color:#aebdce;font-variant-numeric:tabular-nums;letter-spacing:.04em}
76+
.topbar .dl{border:1px solid rgba(255,255,255,.3);color:#fff;padding:5px 12px;border-radius:8px;
77+
font-weight:600;font-size:13px}
78+
.topbar .dl:hover{background:rgba(255,255,255,.15)}
79+
.topbar .fs{background:transparent;border:1px solid rgba(255,255,255,.3);color:#fff;
80+
width:32px;height:32px;border-radius:8px;cursor:pointer;font-size:15px}
81+
.topbar .fs:hover{background:rgba(255,255,255,.15)}
82+
83+
@media (max-width:640px){
84+
.nav{opacity:1;width:44px;height:44px;font-size:24px}
85+
.topbar .title{max-width:40vw}
86+
.site-header{padding:44px 20px 28px}
87+
}

docs/events.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[
2+
{
3+
"id": "1",
4+
"no": 1,
5+
"title": "CNCK Kube Community Night",
6+
"subtitle": "w/ Kubestronaut",
7+
"date": "2026-06-12",
8+
"slideCount": 41,
9+
"pptx": "cnck-kube-community-night.pptx"
10+
}
11+
]
3.85 MB
Binary file not shown.

docs/events/1/meta.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"id": "1",
3+
"no": 1,
4+
"title": "CNCK Kube Community Night",
5+
"subtitle": "w/ Kubestronaut",
6+
"date": "2026-06-12",
7+
"slideCount": 41,
8+
"pptx": "cnck-kube-community-night.pptx"
9+
}

docs/events/1/slides/01.png

91.1 KB

docs/events/1/slides/02.png

94.9 KB

docs/events/1/slides/03.png

97.5 KB

docs/events/1/slides/04.png

198 KB

0 commit comments

Comments
 (0)