안녕하세요!
오늘 급식
☀️ 오늘의 식단오늘의 영양
영양 정보를 불러오는 중...
메뉴
0 kcal
성분 정보
영양 분석
* 식품의약품안전처 영양성분 DB 기준 추정치
급식 달력
한 달의 식단을 한눈에
--
정보를 불러오는 중...
급식 사진
날짜별 급식 사진 기록
알레르기 · 성분
오늘 급식의 알레르기 정보
설정
알레르기 설정 및 계정 관리
알레르기 설정이 필요하신가요?
로그인하면 나에게 위험한 알레르기 음식을
미리 알려드려요
급식뭐지 v2.0.0
만든 사람들
호치민시한국국제학교
프로젝트 총괄로서 아이디어 기획 및 Figma를 이용한 UI/UX 디자인을 수행하고, Vanilla JS 프론트엔드와 Node.js 백엔드 개발, Cloudflare Pages 웹 배포, PWA 서비스 워커 및 푸시 알림 시스템 구축 등 풀스택 개발 전 과정을 직접 주도함
핵심 서비스 모델을 도출하고 유저 시나리오를 설계했으며, 인터랙션 와이어프레임 작성 및 사용자 경험(UX) 사용성 평가를 통해 전반적인 서비스 화면 흐름을 설계 및 최적화함
식단 메뉴 및 알레르기 영양 데이터베이스를 체계적으로 분류 및 정제하고, 화면 내 텍스트 콘텐츠 카피라이팅 및 시스템 품질 보증(QA)을 통해 서비스 데이터의 정확성을 검수함
실제 교내 유저들을 대상으로 온·오프라인 마케팅 프로모션을 전개하고, 사용자 피드백 조사 및 분석 데이터를 바탕으로 기능 요구사항(PRD)을 정의하여 서비스 개선 방향을 도출함
매달 정밀한 급식 식단표와 상세 영양 정보를 제공하여 앱 데이터의 핵심 콘텐츠 구성과 안정적인 서비스 운영을 지원함
맛있는 하루 되세요 · 급식뭐지 v2.0.0
홈 화면 위젯
앱을 열지 않아도 홈 화면에서
오늘 급식을 바로 확인할 수 있어요
홈 화면에 이렇게 떠요 — 탭하면 앱이 열려요
- App Store에서 Scriptable(무료) 앱을 설치해요
- 아래 코드 복사 버튼을 눌러요
- Scriptable 앱을 열고 오른쪽 위 + → 코드를 붙여넣고, 이름을 급식뭐지로 바꿔 저장해요
- 홈 화면을 길게 누르고 + → Scriptable 검색 → 원하는 크기(소·중·대)의 위젯을 추가해요
- 추가된 위젯을 길게 눌러 위젯 편집 → Script에서 급식뭐지를 선택하면 끝!
크기별로 보여주는 내용이 달라요 — 소: 중식 요약 · 중: 중식 + 석식 한 줄 · 대: 중식·석식 전체 + 칼로리 + 내일 미리보기
// 급식뭐지 위젯 (Scriptable) — 소·중·대 크기 지원
const API = "https://kislunchisallmine.pages.dev/api/today";
const res = await new Request(API).loadJSON();
const size = config.widgetFamily || "medium";
const C = {
primary: new Color("#ED5A1F"),
text: Color.dynamic(new Color("#1C1917"), new Color("#FAFAF9")),
sub: Color.dynamic(new Color("#57534E"), new Color("#D6D3D1")),
faint: Color.dynamic(new Color("#A8A29E"), new Color("#78716C")),
warn: new Color("#DC2626")
};
const w = new ListWidget();
w.url = "https://kislunchisallmine.pages.dev";
const g = new LinearGradient();
g.colors = [
Color.dynamic(new Color("#FFF7ED"), new Color("#1C1917")),
Color.dynamic(new Color("#FAF7F2"), new Color("#292524"))
];
g.locations = [0, 1];
w.backgroundGradient = g;
w.setPadding(13, 15, 13, 15);
// 머리글: 제목 + 날짜
const head = w.addStack();
head.centerAlignContent();
const title = head.addText(size === "small" ? "🍱 급식" : "🍱 오늘의 급식");
title.font = Font.boldSystemFont(size === "small" ? 11 : 13);
title.textColor = C.primary;
head.addSpacer();
const date = head.addText(res.label);
date.font = Font.mediumSystemFont(size === "small" ? 9 : 11);
date.textColor = C.faint;
w.addSpacer(size === "small" ? 5 : 8);
const lunchItems = res.lunchItems || [];
const dinnerItems = res.dinnerItems || [];
function row(item, fs, showKcal) {
const r = w.addStack();
r.centerAlignContent();
const t = r.addText((item.emoji ? item.emoji + " " : "· ") + item.name);
t.font = Font.systemFont(fs);
t.textColor = C.text;
t.lineLimit = 1;
if (item.allergy) {
r.addSpacer(3);
const a = r.addText("⚠");
a.font = Font.boldSystemFont(fs - 2);
a.textColor = C.warn;
}
r.addSpacer();
if (showKcal) {
if (item.kcal) {
const k = r.addText(item.kcal + " kcal");
k.font = Font.systemFont(fs - 2);
k.textColor = C.faint;
}
}
w.addSpacer(2);
}
function empty() {
const t = w.addText("등록된 메뉴가 없어요");
t.font = Font.systemFont(12);
t.textColor = C.sub;
}
function sectionLabel(txt) {
const s = w.addText(txt);
s.font = Font.boldSystemFont(10);
s.textColor = C.sub;
w.addSpacer(3);
}
if (size === "small") {
if (lunchItems.length === 0) { empty(); }
else { for (const it of lunchItems.slice(0, 4)) row(it, 10, false); }
} else if (size === "large") {
sectionLabel(res.lunchKcal ? "🍚 중식 · " + res.lunchKcal + " kcal" : "🍚 중식");
if (lunchItems.length === 0) { empty(); }
else { for (const it of lunchItems.slice(0, 7)) row(it, 12, true); }
if (dinnerItems.length) {
w.addSpacer(8);
sectionLabel(res.dinnerKcal ? "🌙 석식 · " + res.dinnerKcal + " kcal" : "🌙 석식");
for (const it of dinnerItems.slice(0, 5)) row(it, 12, true);
}
if (res.tomorrow) {
if (res.tomorrow.lunch.length) {
w.addSpacer(8);
const tm = w.addText("내일 · " + res.tomorrow.lunch.slice(0, 3).join(" · "));
tm.font = Font.mediumSystemFont(10);
tm.textColor = C.faint;
tm.lineLimit = 1;
}
}
} else {
if (lunchItems.length === 0) { empty(); }
else { for (const it of lunchItems.slice(0, 5)) row(it, 12, false); }
if (dinnerItems.length) {
w.addSpacer(4);
const dn = w.addText("🌙 석식 · " + dinnerItems.map(m => m.name).join(" · "));
dn.font = Font.mediumSystemFont(10);
dn.textColor = C.sub;
dn.lineLimit = 1;
}
}
w.addSpacer();
const foot = w.addStack();
foot.addSpacer();
let brand = "급식뭐지";
if (size === "medium") {
if (res.lunchKcal) brand = "🔥 " + res.lunchKcal + " kcal · 급식뭐지";
}
const f = foot.addText(brand);
f.font = Font.mediumSystemFont(9);
f.textColor = C.faint;
w.refreshAfterDate = new Date(Date.now() + 30 * 60 * 1000);
Script.setWidget(w);
if (config.runsInApp) w.presentMedium();
Script.complete();
- Play 스토어에서 KWGT(무료) 앱을 설치해요
- 홈 화면을 길게 누르고 위젯 → KWGT 위젯을 홈 화면에 추가해요
- 추가된 빈 위젯을 탭 → 편집 화면에서 텍스트 요소를 추가해요
- 텍스트 항목의 수식 입력칸에 아래 수식을 붙여넣어요
- 오른쪽 위 저장을 누르면 끝! (글꼴·크기는 취향대로 조절)
$wg("https://kislunchisallmine.pages.dev/api/today", json, .widget)$
따로 꾸미고 싶다면 텍스트 요소를 3개 만들어 아래 수식을 하나씩 넣어보세요
$wg("https://kislunchisallmine.pages.dev/api/today", json, .widgetTitle)$
$wg("https://kislunchisallmine.pages.dev/api/today", json, .widgetLunch)$
$wg("https://kislunchisallmine.pages.dev/api/today", json, .widgetDinner)$
위젯은 보통 30분~1시간 간격으로 자동 새로고침돼요.
메뉴가 바뀌면 잠시 뒤에 반영되고, 위젯을 탭하면 앱이 열려요.