From 0bffc5e997be88ba355342b1b134528cc6ee0fdf Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Sun, 15 Mar 2026 22:58:19 -0500 Subject: [PATCH 1/6] chore(defcon): regenerate 1x preview with new colors --- apps/defcon/defcon.webp | Bin 934 -> 1056 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/defcon/defcon.webp b/apps/defcon/defcon.webp index 53f31c17063e4866dd4d48f1cf1d8e0767aad878..eb3c4fe027d22f90b8b3161d973c067f20142173 100644 GIT binary patch literal 1056 zcmWIYbaRtnVPFV%bqWXzu!!JdU|7JLh@{>NM|YT@UwJ#QW>+r-QN z^=i20D&2kXy4J;SVepiBO~-PkJTrE5`7gR7ckz!I4<##&S#nwur$?Xp{?7D>z@CLX zs!pNDc%SlJmH#OwKKtg^1*>?px6jaCbHDb_{7-2I(hN4=e3SjnZ1)UFM@f$ZEF6yh z3a8|~=AUK}+%&N&^%+Z{hi0YC!g3c$dlN;@T3fFui=#UNbna?76&%@Y>Ne@>rinRE zc6vlveCGVW<>z@euut89K9XW!;9y|jW?PUlt~ z4;O6a-bvy=n|)4QCsHT1^Vx^M@o&&sk~!y|pT%)k)t>J$(bU=hK^z`!5`#P$pf@(c`)exAN;K;C~K0P%d?b^v)008#OR znSlwY&u0NpP~ZLlJAKr;&+qH8Z2iEx|TTD=$WYN9GZIZ{b&nf}SInPaE*)Vyb&c#p`n4tu&b{X*JCtqmD^{-0uK%rxXJN2Ll5)0|P$;1JL~pAopv4+|PZ0S-?~y z<96Fa{ob$g%}eI4efgi|$ef3hcP_TM*qAUmUuNN@tFsM3=7a&&3xmy(L@~z>WR4`r z9D@uM&7KwCdoDib&)|J??%s-T^-K}RMGUi@G&$~9dRa4ABX?y3wkvRy){}tn6I0-{HvvB zkIF=sTCNW&=hw0uJMgWr*}{J Date: Sun, 15 Mar 2026 22:57:59 -0500 Subject: [PATCH 2/6] chore(defcon): remove gif preview --- apps/defcon/defcon.gif | Bin 14753 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 apps/defcon/defcon.gif diff --git a/apps/defcon/defcon.gif b/apps/defcon/defcon.gif deleted file mode 100644 index c1d9ceb735bb5b81e9e93f78bd16a10915775598..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14753 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 LXZo*{GfDmrdP Date: Sun, 15 Mar 2026 22:52:19 -0500 Subject: [PATCH 3/6] chore(defcon): make cache const easier to read --- apps/defcon/defcon.star | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/defcon/defcon.star b/apps/defcon/defcon.star index 3eaa6cbce..3d771258f 100644 --- a/apps/defcon/defcon.star +++ b/apps/defcon/defcon.star @@ -10,7 +10,7 @@ load("render.star", "render") load("schema.star", "schema") DEF_CON_URL = "https://www.defconlevel.com/current-level" -CACHE_TTL_SECONDS = 259200 +CACHE_TTL_SECONDS = 3 * 24 * 60 * 60 FONT = "6x13" DEF_CON_COLORS = ["#fff", "#ff0000", "#ffff00", "#00ff00", "#0000ff"] From 9da03f01231450e1871c1b654f8d16d21f52b7f6 Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Sun, 15 Mar 2026 22:54:02 -0500 Subject: [PATCH 4/6] refactor(defcon): simplify rendering logic --- apps/defcon/defcon.star | 104 ++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 42 deletions(-) diff --git a/apps/defcon/defcon.star b/apps/defcon/defcon.star index 3d771258f..8c387aeab 100644 --- a/apps/defcon/defcon.star +++ b/apps/defcon/defcon.star @@ -78,72 +78,92 @@ def main(config): position = extract_defcon_level(res.body()) return render.Root( - render.Column( + delay = 1000, + child = render.Column( + expanded = True, + main_align = "space_between", children = [ - render.Row( - children = [ - render.Box(width = 64, height = 15, color = "#fff", child = render.Box(width = 64 - 2, height = 15 - 2, color = "#000", child = render.Text("DEFCON", color = "#fff", font = FONT))), - ], - ), - render.Row( - children = [ - render.Box(width = 64, height = 1, color = "#000"), - ], + render.Box( + width = 64, + height = 15, + color = "#fff", + child = render.Box( + width = 64 - 2, + height = 15 - 2, + color = "#000", + child = render.Text("DEFCON", color = "#fff", font = FONT), + ), ), render_defcon_display(config.bool("animate", True), position), ], ), - delay = 1000, ) def render_defcon_display(animate, position): if animate: return render.Animation(children = get_defcon_display(position)) else: - return render.Stack(children = get_defcon_display(position)) - -def add_padding_to_child_element(element, left = 0, top = 0, right = 0, bottom = 0): - padded_element = render.Padding( - pad = (left, top, right, bottom), - child = element, + children = get_defcon_display(position) + return children.pop() + +def render_box(i, width, height, color): + return render.Box( + width = width, + height = height, + color = color, + child = render.Box( + width = width - 2, + height = height - 2, + color = "#000", + child = render.Padding( + pad = (1, 0, 0, 0), + child = render.Text(str(i), color = color, font = FONT), + ), + ), ) - return padded_element def get_defcon_display(position): children = [] - position = int(position) + width, height = 12, 16 - #add grey outlines of the 5 conditions - temp_group_of_children = [] + # Render grey outlines + grey_children = [] for i in range(5): - color = "#333" - temp_group_of_children.insert(len(children), add_padding_to_child_element(render.Box(width = 12, height = 16, color = color, child = render.Box(width = 12 - 2, height = 16 - 2, color = "#000", child = add_padding_to_child_element(render.Text(str(i + 1), color = color, font = FONT), 1))), i * 13)) + grey_children.append(render_box(i + 1, width, height, "#333")) - grey_box = render.Stack(temp_group_of_children) - children.insert(len(children), grey_box) + grey_box = render.Row( + expanded = True, + main_align = "space_between", + children = grey_children, + ) + children.append(grey_box) + # Render colored boxes color_box = None - - # Flash each condition until the current condition - for i in range(4, -1, -1): - temp_group_of_children = [] - color = "#333" - if (i + 1 >= position): - color = DEF_CON_COLORS[i] - temp_group_of_children.insert(len(children), grey_box) - temp_group_of_children.insert(len(children), add_padding_to_child_element(render.Box(width = 12, height = 16, color = color, child = render.Box(width = 12 - 2, height = 16 - 2, color = "#000", child = add_padding_to_child_element(render.Text(str(i + 1), color = color, font = FONT), 1))), i * 13)) - color_box = render.Stack(temp_group_of_children) - children.insert(len(children), color_box) - - # flash current + for i in range(4, position - 2, -1): + color_children = [] + for j in range(5): + if i == j and j >= position - 1: + color_children.append(render_box(j + 1, width, height, DEF_CON_COLORS[j])) + else: + color_children.append(grey_children[j]) + + color_box = render.Row( + expanded = True, + main_align = "space_between", + children = color_children, + ) + children.append(color_box) + + # Flash current for _ in range(3): - children.insert(len(children), grey_box) - children.insert(len(children), color_box) + children.append(grey_box) + children.append(color_box) - # hold on current a while longer + # Hold current for a while longer for _ in range(3): - children.insert(len(children), color_box) + children.append(color_box) return children From f4a051aaf2401fe869b2d021e45f798ce35f74fd Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Sun, 15 Mar 2026 22:57:45 -0500 Subject: [PATCH 5/6] feat(defcon): add 2x support --- apps/defcon/defcon.star | 40 +++++++++++++++++++++---------------- apps/defcon/defcon@2x.webp | Bin 0 -> 1336 bytes apps/defcon/manifest.yaml | 1 + 3 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 apps/defcon/defcon@2x.webp diff --git a/apps/defcon/defcon.star b/apps/defcon/defcon.star index 8c387aeab..682783df0 100644 --- a/apps/defcon/defcon.star +++ b/apps/defcon/defcon.star @@ -6,12 +6,13 @@ Author: Robert Ison """ load("http.star", "http") -load("render.star", "render") +load("render.star", "canvas", "render") load("schema.star", "schema") DEF_CON_URL = "https://www.defconlevel.com/current-level" CACHE_TTL_SECONDS = 3 * 24 * 60 * 60 -FONT = "6x13" +SCALE = 2 if canvas.is2x() else 1 +FONT = "terminus-32-light" if canvas.is2x() else "6x13" DEF_CON_COLORS = ["#fff", "#ff0000", "#ffff00", "#00ff00", "#0000ff"] display_options = [ @@ -77,6 +78,9 @@ def main(config): position = extract_defcon_level(res.body()) + width, height = canvas.size() + defcon_height = height // 2 - 1 + return render.Root( delay = 1000, child = render.Column( @@ -84,12 +88,12 @@ def main(config): main_align = "space_between", children = [ render.Box( - width = 64, - height = 15, + width = width, + height = defcon_height, color = "#fff", child = render.Box( - width = 64 - 2, - height = 15 - 2, + width = width - 2 * SCALE, + height = defcon_height - 2 * SCALE, color = "#000", child = render.Text("DEFCON", color = "#fff", font = FONT), ), @@ -112,11 +116,11 @@ def render_box(i, width, height, color): height = height, color = color, child = render.Box( - width = width - 2, - height = height - 2, + width = width - 2 * SCALE, + height = height - 2 * SCALE, color = "#000", child = render.Padding( - pad = (1, 0, 0, 0), + pad = (0 if canvas.is2x() else 1, 0, 0, 0), child = render.Text(str(i), color = color, font = FONT), ), ), @@ -125,7 +129,8 @@ def render_box(i, width, height, color): def get_defcon_display(position): children = [] position = int(position) - width, height = 12, 16 + width = (canvas.width() // 5) - (1 if canvas.is2x() else 0) + height = canvas.height() // 2 # Render grey outlines grey_children = [] @@ -168,7 +173,8 @@ def get_defcon_display(position): return children def display_instructions(): - ############################################################################################################################################################################################################################## + width = canvas.width() + font = "terminus-16" if canvas.is2x() else "5x8" instructions_1 = "For security reasons, the U.S. military does not release the current DEFCON level. " instructions_2 = "The source for this app is defconlevel.com which uses Open Source Intelligence to estimate the DEFCON level. Default is to use the actual estimated DefCon level, but you can pick a level if you want. " instructions_3 = "Defcon level 5 is the lowest alert level. The highest level reached was level 2 during the Cuban Missle Crisis. This display is based on the movie War Games (1983)." @@ -176,27 +182,27 @@ def display_instructions(): render.Column( children = [ render.Marquee( - width = 64, - child = render.Text("DEFCON", color = DEF_CON_COLORS[0], font = "5x8"), + width = width, + child = render.Text("DEFCON", color = DEF_CON_COLORS[0], font = font), ), render.Marquee( - width = 64, + width = width, child = render.Text(instructions_1, color = DEF_CON_COLORS[1]), ), render.Marquee( offset_start = len(instructions_1) * 5, - width = 64, + width = width, child = render.Text(instructions_2, color = DEF_CON_COLORS[2]), ), render.Marquee( offset_start = (len(instructions_2) + len(instructions_1)) * 5, - width = 64, + width = width, child = render.Text(instructions_3, color = DEF_CON_COLORS[3]), ), ], ), show_full_animation = True, - delay = 45, + delay = 25 if canvas.is2x() else 45, ) def get_schema(): diff --git a/apps/defcon/defcon@2x.webp b/apps/defcon/defcon@2x.webp new file mode 100644 index 0000000000000000000000000000000000000000..d88174b6033a3aa0f1b2a92d6f2debd2412cb910 GIT binary patch literal 1336 zcmWIYbaOLcWnc(*bqWXzu!!JdU|htjditE=O;Aaq55b$Wa6`#x7=pc0EUlupZqy^u?tN$_Ai=N2U*v2;}oiXc1 zHK+f?{Phga>{c1GPV{;(Yl-UXcbDA1)H8j#9B#HTtws`Vr=YCiNy(k6%~rhrhY+ zGyhTIjdibCpY7UXG)Zoa%DduliMTToHdAKm8HY2)rmbPwHA9i#hgay{?qzps*PhnX zK4`RI>p^a_^3sXJC+LU;u_S11PLbf#Uk&2iOJp1q}axmgi~By=|uVr{Z`rXHCSVzfLay z)fM_$Y&EtV{C7y?oYo`f30-a$hdZWQFo9#R3#eNiY^ouOsYM`D4S}Y@Vz77S%$Yy= zXYlrA-R_+|eP#1E#YB;|2@~f}vs|R<`jNA#Z$%x2_O2``#HwP%(KP|JDARX^21{+tE0 zSqh7bn$TSYO;hL3{9)fCI_K)O+h+Q!odcSj1a=)!4=DMi?QWvmFU%tNcRqu+t=G#N z3~_#ys+-SD^gX-miOaU@OQMBXRorL)pV)iqrEm3R#hGGu>h`na&Us#*xA6AqiCoM| zi{4Ldx&Wzk$On2<}4En6Lu55F8T*#z62{{zHI(XV&2}(#s{9 zE8`Vooq=(&VEu$YFMIT)G}-N@NR(#`NFWlq0z{UTZFSJ$atrmB?ooeJ)b(8fv(QKYvvQYTb<#3R>qb|lB#tZl% J`G}G*1^^ Date: Sun, 15 Mar 2026 23:24:10 -0500 Subject: [PATCH 6/6] refactor(defcon): use `html.star` to parse response --- apps/defcon/defcon.star | 61 ++++++++++++----------------------------- 1 file changed, 18 insertions(+), 43 deletions(-) diff --git a/apps/defcon/defcon.star b/apps/defcon/defcon.star index 682783df0..122ce8eb6 100644 --- a/apps/defcon/defcon.star +++ b/apps/defcon/defcon.star @@ -5,6 +5,7 @@ Description: Displays the estimated DefCon (Defense Condition) alert level for t Author: Robert Ison """ +load("html.star", "html") load("http.star", "http") load("render.star", "canvas", "render") load("schema.star", "schema") @@ -24,45 +25,23 @@ display_options = [ schema.Option(value = "0", display = "Actual DEFCON Level"), ] -def trim_left(text): - """Remove leading whitespace recursively.""" - if not text or text[0] != " ": - return text - return trim_left(text[1:]) - -def trim_right(text): - """Remove trailing whitespace recursively.""" - if not text or text[-1] != " ": - return text - return trim_right(text[:-1]) - -def extract_defcon_level(html): - """Extract DEFCON number (int) from badge-number span.""" - if not html: - return 0 - - start_marker = '' - start_pos = html.find(start_marker) - if start_pos == -1: - return 0 - - content_start = start_pos + len(start_marker) - end_tag_pos = html.find("", content_start) - if end_tag_pos == -1: - return 0 - - # Extract raw content - defcon_text = html[content_start:end_tag_pos] - defcon_text = trim_left(defcon_text) - defcon_text = trim_right(defcon_text) - - # Extract just the number after "DEFCON " - if defcon_text.startswith("DEFCON "): - number_str = defcon_text[6:] # Skip "DEFCON " - else: - number_str = defcon_text +def get_defcon_level(): + """Fetch URL and extract DEFCON number (int) from badge-number span.""" + res = http.get( + url = DEF_CON_URL, + ttl_seconds = CACHE_TTL_SECONDS, + headers = { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "Accept": "text/html", + }, + ) + if res.status_code != 200: + fail("request to %s failed with status code: %d - %s" % (DEF_CON_URL, res.status_code, res.body())) - return int(number_str.strip()) + page = html(res.body()) + defcon_text = page.find("span.badge-number").text() + defcon_text = defcon_text.removeprefix("DEFCON ").strip(" ") + return int(defcon_text) def main(config): show_instructions = config.bool("instructions", False) @@ -72,11 +51,7 @@ def main(config): position = config.get("list", display_options[0].value) if position == "0": - res = http.get(url = DEF_CON_URL, ttl_seconds = CACHE_TTL_SECONDS, headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", "Accept": "text/html"}) - if res.status_code != 200: - fail("request to %s failed with status code: %d - %s" % (DEF_CON_URL, res.status_code, res.body())) - - position = extract_defcon_level(res.body()) + position = get_defcon_level() width, height = canvas.size() defcon_height = height // 2 - 1