Commit f4aff304 by PLN (Algolia)

devcon theme: the DevCon26 stage palette as a fourth theme

The event's colours, extracted from the intro loop itself (not eyed):
corail 21deg in mass, magenta 296, cyan 190, all high-value on a warm
near-white page. The stage-screen recipe is sun's (bold, thick bars, no
scanline haze); the DevCon gradient lives in the spec wash, one linear
drift that now wraps through hue zero -- identity on every theme that
never crosses it.

Jersey 25 vendored beside Syne for --word-face jersey, the event's
display face; the default wordmark stays the brand's.
parent d1921a7b
...@@ -417,6 +417,32 @@ THEMES: dict[str, Theme] = { ...@@ -417,6 +417,32 @@ THEMES: dict[str, Theme] = {
# value bars, and no scanline texture to eat the little contrast there is. # value bars, and no scanline texture to eat the little contrast there is.
# It is not meant to be pretty; it is meant to be the one that still works # It is not meant to be pretty; it is meant to be the one that still works
# when the laptop is the brightest thing on a table at noon. # when the laptop is the brightest thing on a table at noon.
# The DevCon26 stage skin (PLN, 2026-09-28): the event's own palette,
# extracted from the intro loop itself (live/algolia/devcon26/assets) --
# corail #fe9d6a in mass, magenta #e69beb, cyan #25d2f4, everything at
# high value on a warm near-white page, navy type. A stage screen wants
# the `sun` recipe (bold, thick bars, no scanline haze) wearing the
# event's colours; the family hues stay the orbit identity, the DevCon
# gradient lives in the spec wash.
"devcon": Theme(
name="devcon", bg=(0xf8, 0xf5, 0xf0),
ink=Ramp(0.66, -0.40, 0.70, 0.55, 0.45, 0.80),
tint=Ramp(0.90, -0.14, 1.00, 0.16, 0.36, 1.00),
tint_t=_TINT_T_LINEAR,
chrome=Ramp(0.66, -0.50, 0.80, 0.10, 0.34, 1.00),
chrome_hue=222.0, # the navy of the DevCon display face
# The wash walks the intro's gradient in one linear drift: corail
# 21 deg down through magenta 296 to cyan ~200 -- measured, not eyed.
spec=SpecRamp(0.058, -0.45, 0.85, 0.72, -0.32, 40, 140),
spec_peak=(0x18, 0x28, 0xa0, 150),
scan=None,
bar_frac=0.34, bold=True,
),
# Direct sunlight, and built for nothing else. Near-white page, heavy ink
# at every decay level (a "dim" state outdoors is a blank state), thicker
# value bars, and no scanline texture to eat the little contrast there is.
# It is not meant to be pretty; it is meant to be the one that still works
# when the laptop is the brightest thing on a table at noon.
"sun": Theme( "sun": Theme(
name="sun", bg=(0xfc, 0xfc, 0xfa), name="sun", bg=(0xfc, 0xfc, 0xfa),
# Ink is near-black at EVERY decay level (`vg` below 1 spends the whole # Ink is near-black at EVERY decay level (`vg` below 1 spends the whole
...@@ -441,7 +467,7 @@ THEMES: dict[str, Theme] = { ...@@ -441,7 +467,7 @@ THEMES: dict[str, Theme] = {
} }
# Cycle order for the `d` key and the menu: darkest to brightest page, so the # Cycle order for the `d` key and the menu: darkest to brightest page, so the
# key has a direction and "one more press" means "one step sunnier". # key has a direction and "one more press" means "one step sunnier".
THEME_ORDER: tuple[str, ...] = ("dark", "light", "sun") THEME_ORDER: tuple[str, ...] = ("dark", "light", "devcon", "sun")
DEFAULT_THEME = "dark" DEFAULT_THEME = "dark"
# What each page is FOR, for the menu only. `dark`/`light`/`sun` stay the # What each page is FOR, for the menu only. `dark`/`light`/`sun` stay the
# vocabulary of the config file, the `--theme` flag and every test; a menu row # vocabulary of the config file, the `--theme` flag and every test; a menu row
...@@ -450,6 +476,7 @@ THEME_MENU: dict[str, str] = { ...@@ -450,6 +476,7 @@ THEME_MENU: dict[str, str] = {
"dark": "Dark · the cockpit, indoors", "dark": "Dark · the cockpit, indoors",
"light": "Light · a pale desktop", "light": "Light · a pale desktop",
"sun": "Sun · daylight, maximum ink", "sun": "Sun · daylight, maximum ink",
"devcon": "DevCon · the Algolia stage skin",
} }
TAU = 0.85 # seconds; brightness e-folding time TAU = 0.85 # seconds; brightness e-folding time
...@@ -510,6 +537,12 @@ WAVE_ALPHA = 0.12 ...@@ -510,6 +537,12 @@ WAVE_ALPHA = 0.12
# recipe) and the pink is --neon-high. The variable font's latin subset is # recipe) and the pink is --neon-high. The variable font's latin subset is
# vendored out of the built site for the same standalone reason as the wave. # vendored out of the built site for the same standalone reason as the wave.
SYNE_PATH = Path(__file__).resolve().parent / "ui" / "syne-extrabold.ttf" SYNE_PATH = Path(__file__).resolve().parent / "ui" / "syne-extrabold.ttf"
# Jersey 25 (Soft Type Project, OFL, vendored from google/fonts) — the face of
# the Algolia DevCon26 stage kit (live/algolia/devcon26). Same standalone
# reason as Syne; `--word-face jersey` puts the wordmark in it, default stays
# the brand's Syne.
JERSEY_PATH = Path(__file__).resolve().parent / "ui" / "Jersey25-Regular.ttf"
WORD_FACES = ("syne", "jersey")
WORD_ALPHA = 0.8 WORD_ALPHA = 0.8
# ── the HL feed ──────────────────────────────────────────────────────────── # ── the HL feed ────────────────────────────────────────────────────────────
...@@ -1139,7 +1172,8 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0, ...@@ -1139,7 +1172,8 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
pinned_port: str | None = None, watch: bool = False, pinned_port: str | None = None, watch: bool = False,
spectro: bool = False, theme: str = DEFAULT_THEME, spectro: bool = False, theme: str = DEFAULT_THEME,
persist: bool = False, tray: bool = False, persist: bool = False, tray: bool = False,
hl: "oscfeed.HLFeed | None" = None): hl: "oscfeed.HLFeed | None" = None,
word_face: str = "syne"):
QtCore, QtGui, QtWidgets = _qt() QtCore, QtGui, QtWidgets = _qt()
Qt = QtCore.Qt Qt = QtCore.Qt
...@@ -1226,6 +1260,14 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0, ...@@ -1226,6 +1260,14 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
str(SYNE_PATH)) str(SYNE_PATH))
except Exception: except Exception:
self._syne_id = -1 self._syne_id = -1
# Same deal, same non-crash rule: a checkout without Jersey loses
# only the alternate wordmark face, never the lens.
try:
self._jersey_id = QtGui.QFontDatabase.addApplicationFont(
str(JERSEY_PATH))
except Exception:
self._jersey_id = -1
self._word_face = word_face if word_face in WORD_FACES else "syne"
self._word_font = None self._word_font = None
self._word_font_key = None self._word_font_key = None
self._word_pm = None self._word_pm = None
...@@ -1409,7 +1451,11 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0, ...@@ -1409,7 +1451,11 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
# else, so a spectrum frame constructs no QColor. # else, so a spectrum frame constructs no QColor.
sp = th.spec sp = th.spec
self.spec_lut = [ self.spec_lut = [
QtGui.QColor.fromHsvF(sp.h0 + sp.hspan * (lv / (SPEC_LEVELS - 1)), # The hue drift wraps (`% 1.0`): a theme may walk the wheel
# through 0 — DevCon26's does, corail 21 deg descending to
# cyan past magenta — and fromHsvF rejects a negative hue.
# Identity on every theme that never crosses zero.
QtGui.QColor.fromHsvF((sp.h0 + sp.hspan * (lv / (SPEC_LEVELS - 1))) % 1.0,
sp.sat, sp.v0 + sp.vspan * (lv / (SPEC_LEVELS - 1)), sp.sat, sp.v0 + sp.vspan * (lv / (SPEC_LEVELS - 1)),
(sp.a0 + sp.aspan * (lv / (SPEC_LEVELS - 1))) / 255.0) (sp.a0 + sp.aspan * (lv / (SPEC_LEVELS - 1))) / 255.0)
for lv in range(SPEC_LEVELS) for lv in range(SPEC_LEVELS)
...@@ -1755,10 +1801,11 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0, ...@@ -1755,10 +1801,11 @@ def build_widget(port_label: str, reader: "Reader | None", scale: float = 1.0,
spacing and kerned sub-advances disagreed, and that arithmetic is spacing and kerned sub-advances disagreed, and that arithmetic is
exactly how "PARVAGU" lost its tail. The bake measures its own exactly how "PARVAGU" lost its tail. The bake measures its own
ink instead (see _word_pixmap).""" ink instead (see _word_pixmap)."""
key = (round(target_w), round(target_h)) key = (self._word_face, round(target_w), round(target_h))
if self._word_font_key == key: if self._word_font_key == key:
return self._word_font return self._word_font
fam_id = self._syne_id fam_id = self._jersey_id if self._word_face == "jersey" \
else self._syne_id
if fam_id < 0: if fam_id < 0:
return None return None
fam = QtGui.QFontDatabase.applicationFontFamilies(fam_id)[0] fam = QtGui.QFontDatabase.applicationFontFamilies(fam_id)[0]
...@@ -3657,6 +3704,9 @@ def main(argv=None) -> int: ...@@ -3657,6 +3704,9 @@ def main(argv=None) -> int:
help="theme: dark (the cockpit), light (pale desktop), " help="theme: dark (the cockpit), light (pale desktop), "
"sun (maximum contrast, for playing outdoors); " "sun (maximum contrast, for playing outdoors); "
"'d' cycles at runtime and the choice is remembered") "'d' cycles at runtime and the choice is remembered")
ap.add_argument("--word-face", choices=WORD_FACES, default="syne",
help="face of the PARVAGUES wordmark; jersey is the "
"DevCon26 stage face")
ap.add_argument("--no-tray", action="store_true", ap.add_argument("--no-tray", action="store_true",
help="skip the system-tray presence even where one exists") help="skip the system-tray presence even where one exists")
ap.add_argument("--spectro", action="store_true", ap.add_argument("--spectro", action="store_true",
...@@ -3758,7 +3808,8 @@ def main(argv=None) -> int: ...@@ -3758,7 +3808,8 @@ def main(argv=None) -> int:
app.setQuitOnLastWindowClosed(False) app.setQuitOnLastWindowClosed(False)
w = build_widget(port or "--", reader, scale=scale, w = build_widget(port or "--", reader, scale=scale,
pinned_port=pinned, watch=True, theme=theme, pinned_port=pinned, watch=True, theme=theme,
persist=True, tray=not a.no_tray, hl=hl) persist=True, tray=not a.no_tray, hl=hl,
word_face=a.word_face)
if a.spectro: if a.spectro:
src = None src = None
if a.spectro_target: if a.spectro_target:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment