From 96bc42fafff425cf7690ad417cc6d4d4a757547b Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 19 Jul 2026 15:44:24 -0500 Subject: docs: file interface design references Record the reconciled weather work and future component direction.\nFile the clock and retro-interface reference materials for later design work. --- docs/prototypes/waybar-redesign-prototype.html | 853 +++++++++++++++++++++ todo.org | 69 ++ .../2026-07-17-clock-strip-concept-01.png | Bin 0 -> 1639047 bytes .../2026-07-17-clock-strip-concept-02.png | Bin 0 -> 1611392 bytes .../2026-07-17-clock-strip-concept-03.png | Bin 0 -> 1351841 bytes .../2026-07-17-clock-strip-concept-04.png | Bin 0 -> 1631303 bytes .../2026-07-17-clock-strip-concept-05.png | Bin 0 -> 1157684 bytes .../2026-07-17-clock-strip-variant-sheet-01.png | Bin 0 -> 1717647 bytes .../2026-07-17-clock-strip-variant-sheet-02.png | Bin 0 -> 1828740 bytes .../2026-07-17-clock-strip-variant-sheet-03.png | Bin 0 -> 1828740 bytes .../2026-07-18-chronograph-audemars-moonphase.png | Bin 0 -> 527486 bytes ...6-07-18-chronograph-blancpain-fifty-fathoms.png | Bin 0 -> 697946 bytes .../2026-07-18-chronograph-breitling-navitimer.png | Bin 0 -> 301267 bytes .../2026-07-18-chronograph-date-idea-detail.png | Bin 0 -> 29433 bytes .../2026-07-18-chronograph-date-idea.png | Bin 0 -> 1819745 bytes .../2026-07-18-chronograph-date-window-detail.png | Bin 0 -> 22156 bytes .../2026-07-18-chronograph-iwc-portugieser.png | Bin 0 -> 192781 bytes .../2026-07-18-chronograph-omega-speedmaster.png | Bin 0 -> 491327 bytes ...26-07-18-chronograph-panerai-luminor-marina.png | Bin 0 -> 128425 bytes .../2026-07-18-chronograph-patek-moonphase.png | Bin 0 -> 532449 bytes ...-07-18-chronograph-patek-perpetual-calendar.png | Bin 0 -> 198977 bytes .../2026-07-18-chronograph-patek-two-register.png | Bin 0 -> 175575 bytes .../2026-07-18-chronograph-reference-notes.txt | 20 + ...2026-07-18-chronograph-rolex-cyclops-detail.png | Bin 0 -> 28722 bytes ...026-07-18-chronograph-rolex-submariner-date.png | Bin 0 -> 492099 bytes working/clock-display-references/README.org | 6 + ...26-07-18-dual-concentric-knob-black-detail.jpeg | Bin 0 -> 11473 bytes .../2026-07-18-dual-concentric-knob-black.jpg | Bin 0 -> 13108 bytes ...6-07-18-dual-concentric-knob-fender-detail.jpeg | Bin 0 -> 12294 bytes .../2026-07-18-dual-concentric-knob-fender.webp | Bin 0 -> 95620 bytes ...6-07-18-dual-concentric-knob-guitar-detail.jpeg | Bin 0 -> 8710 bytes ...2026-07-18-dual-concentric-knob-guitar-pair.jpg | Bin 0 -> 88208 bytes ...026-07-18-dual-concentric-knob-style-sheet.jpeg | Bin 0 -> 4645 bytes ...2026-07-18-dual-concentric-knob-tone-volume.jpg | Bin 0 -> 7654 bytes .../2026-07-18-hotfx-split-flap-reference.org | 65 ++ .../2026-07-18-hotfx-split-flap-source.js | 455 +++++++++++ .../references/2026-07-18-single-knob-black.webp | Bin 0 -> 33262 bytes .../2026-07-18-single-knob-index-arrow.jpeg | Bin 0 -> 3283 bytes .../references/2026-07-18-split-flap-header.webp | Bin 0 -> 44808 bytes .../references/2026-07-18-terminal-display.jpg | Bin 0 -> 4522828 bytes working/weather-kit-integration/README.org | 119 +++ working/weather-kit-integration/weather | 215 ++++++ working/weather-kit-integration/weather-chip.html | 97 +++ working/weather-kit-integration/weather-glyphs.svg | 81 ++ 44 files changed, 1980 insertions(+) create mode 100644 docs/prototypes/waybar-redesign-prototype.html create mode 100644 working/clock-display-references/2026-07-17-clock-strip-concept-01.png create mode 100644 working/clock-display-references/2026-07-17-clock-strip-concept-02.png create mode 100644 working/clock-display-references/2026-07-17-clock-strip-concept-03.png create mode 100644 working/clock-display-references/2026-07-17-clock-strip-concept-04.png create mode 100644 working/clock-display-references/2026-07-17-clock-strip-concept-05.png create mode 100644 working/clock-display-references/2026-07-17-clock-strip-variant-sheet-01.png create mode 100644 working/clock-display-references/2026-07-17-clock-strip-variant-sheet-02.png create mode 100644 working/clock-display-references/2026-07-17-clock-strip-variant-sheet-03.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-audemars-moonphase.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-blancpain-fifty-fathoms.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-breitling-navitimer.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-date-idea-detail.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-date-idea.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-date-window-detail.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-iwc-portugieser.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-omega-speedmaster.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-panerai-luminor-marina.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-patek-moonphase.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-patek-perpetual-calendar.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-patek-two-register.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-reference-notes.txt create mode 100644 working/clock-display-references/2026-07-18-chronograph-rolex-cyclops-detail.png create mode 100644 working/clock-display-references/2026-07-18-chronograph-rolex-submariner-date.png create mode 100644 working/clock-display-references/README.org create mode 100644 working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-black-detail.jpeg create mode 100644 working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-black.jpg create mode 100644 working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-fender-detail.jpeg create mode 100644 working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-fender.webp create mode 100644 working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-guitar-detail.jpeg create mode 100644 working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-guitar-pair.jpg create mode 100644 working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-style-sheet.jpeg create mode 100644 working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-tone-volume.jpg create mode 100644 working/retro-stereo-widgets/references/2026-07-18-hotfx-split-flap-reference.org create mode 100644 working/retro-stereo-widgets/references/2026-07-18-hotfx-split-flap-source.js create mode 100644 working/retro-stereo-widgets/references/2026-07-18-single-knob-black.webp create mode 100644 working/retro-stereo-widgets/references/2026-07-18-single-knob-index-arrow.jpeg create mode 100644 working/retro-stereo-widgets/references/2026-07-18-split-flap-header.webp create mode 100644 working/retro-stereo-widgets/references/2026-07-18-terminal-display.jpg create mode 100644 working/weather-kit-integration/README.org create mode 100755 working/weather-kit-integration/weather create mode 100644 working/weather-kit-integration/weather-chip.html create mode 100644 working/weather-kit-integration/weather-glyphs.svg diff --git a/docs/prototypes/waybar-redesign-prototype.html b/docs/prototypes/waybar-redesign-prototype.html new file mode 100644 index 0000000..2307b69 --- /dev/null +++ b/docs/prototypes/waybar-redesign-prototype.html @@ -0,0 +1,853 @@ + + + + +Waybar redesign — six directions + + + + + + + +

Waybar redesign — six directions

+

Chronograph date/time as the anchor, whole-bar aesthetic around it. Each direction shows the full bar at true +proportion (3440×54), then the right cluster enlarged. Left-side functionality is unchanged everywhere — only the skin varies. +Right cluster everywhere: AUX (the future desktop-settings module) · network · audio · bluetooth · maint · timer · tray · weather · date · time. +The weather module is new: current conditions for the location, drawn with a custom glyph set (below) rather than Nerd Font icons. +The reading is a live snapshot — New Orleans, clear, 82°/feels 92°. All colours are kit tokens from the widget gallery.

+
+ + + + + +
+
+
Custom weather
glyph set
+
+
CLEAR
+
PARTLY
+
CLOUD
+
RAIN
+
STORM
+
SNOW
+
FOG
+
WINDY
+
+
+ + +
+

WWeather widget — directions

+

Standalone takes on the weather widget, every one showing the same live reading (New Orleans · clear · 82° / feels 92° · wind 6). Same custom glyph set, six different instrument idioms. The bar modules below use the compact chip; the others are candidates for a fuller panel or a click-to-expand. +
Wind collapses into one slot, constant width: below ~15 mph it shows the vane + speed (direction is the interesting part when it's calm); at ~15 mph and up it flips to the gust glyph + speed (when it's windy, "windy" is the headline, not the compass point). The two minimal chips show the calm and windy states at identical footprint.

+
+ + +
+
+ +
+ 82°CLEAR +
+
+
+ +
WNW6 mph
+
+
+
Compact chip
+
+ + +
+
+ + 82° + + 6 +
+
Chip — calm · 6 mph
+
+ + +
+
+ + 82° + + 22 +
+
Chip — windy · 22 mph
+
+ + +
+ + + + + + + + + + + + + + 82° + + WNW 6 + +
Round gauge
+
+ + +
+
+
+ + 8 + 2 + ° +
+
+ C + L + E + A + R +
+
+ WNW · 6 MPH +
+
+
Split-flap
+
+ + +
+
+
+ +
82°F
CLEAR
+
+
+ WIND WNW 6 MPH +
+
+
+
VFD screen
+
+ + +
+
+ +
+ 8 + 2 + ° +
+
+ + WNW 6 +
+
+
Nixie
+
+ + +
+ + + + + + + + + + + + + + + +
+ 82°F· + + WNW 6 +
+
Condition dial
+
+ +
+
+ + +
+

1Gold-pinstripe odometer console

+
+ ▶ ✦1 2 3 4 5 + ▦ emacs — widgets.js + AUX ᯤ ♪ ᛒ ⛭ ⏱ ⋮⋮ + SAT JUL 18 + 07:55 PM CDT +
+
+
Aux
+
Net
+
+ + +
+
Volume
+ + + + + +
+
Bluetooth
+
+ +
+
Maint
+
+ +
+
Timer
+
+
12
+
34
+
+
Tray
+
+
Weather
+
+ +
+ 82° + CLEAR +
+
+
+ +
+ WNW + 6 mph +
+
+
+
Day  ·  Month  ·  Date
+
+
+
S
A
T
+
+
+
J
U
L
+
+
+
1
8
+
+
+
+
Hour · Min · AM/PM · TZ
+
+
+
0
7
+
:
+
5
5
+
+
+
P
M
+
+
+
C
D
T
+
+
+
+

Your 11:46 concept, extended to the whole bar. One continuous black panel with the double gold pinstripe; +engraved gold labels over every module so the bar reads as a single machined faceplate. Date and time are drum groups with +knurled end-caps; the timer is a smaller drum pair; volume is a needle meter; statuses are jewel lamps. Warmest and most +unified of the six — the pinstripe frames the entire bar, not each module.

+
+ + +
+

2Chrome-bezel instrument row

+
+ ▶ ✦ 1 2 3 4 5 ▦ emacs — widgets.js + AUX ᯤ ♪ ᛒ ⛭ ⏱ ⋮⋮ + SAT JUL 18 + 07:55 PM +
+
+
Aux
+
Net
+
+
▂▄▆█
+
+
Volume
+
+ + + + +
+
Blue
+
+
+ +
+
Maint
+
+
+ +
+
Timer
+
+
--:--
+
+
Tray
+
+
Weather
+
+
+ +
+ 82° + CLEAR +
+
+
+ +
+ WNW + 6 mph +
+
+
Sat · Jul · 18
+
+
+
+
S
A
T
+
+
+
J
U
L
+
+
+
1
8
+
+
+
+
07 : 55 · PM · CDT
+
+
+
+
0
7
+
:
+
5
5
+
+
+
P
M
+
+
+
C
D
T
+
+
+
+
+

Your first concept sheet, with every module earning a chrome bezel. Individual polished frames on a +leather-black ground; silver engraved labels; the lume underline (vfd family — Super-LumiNova blue-green) marks the two +time modules as the "hands" of the bar. Cooler and more mechanical than V1: each module is its own instrument, the way +the drum-variation sheet grouped them. Net becomes a VFD signal ladder to put one lit window on the bar's left half.

+
+ + +
+

3Tool-watch dial (Speedmaster steel)

+
+ ▶ ✦ 1 2 3 4 5 ▦ emacs — widgets.js + AUX ᯤ ♪ ᛒ ⛭ ⏱ ⋮⋮ + SAT 18 · 19:55 +
+
+
Aux
+
Net
+
+
+ + +
+
Volume
+ + + + + + + + +
+
Blue
+
+ +
+
Maint
+
+ +
+
Timer 30-min register
+ + + + + + + + + +
+
Tray
+
+
Weather
+
+ +
+ 82° + CLEAR +
+
+
+ +
+ WNW + 6 mph +
+
+
Date aperture
+
+ SAT +
18
+ JUL +
+
Time dial
+
+ + + + + + + + + + + + + + + + + + + + +
19:55 CDT
+
+
+

The Speedmaster direction: brushed-steel bar, black dial insets, lume dots. Time is an actual analog +dial with baton hands plus a small digital repeater; the date is a framed white aperture (the Submariner cyclops date, flattened). +Audio and timer become chronograph sub-registers — the timer literally is a 30-minute register. The steel ground makes this +the lightest bar of the six; icons engrave dark into the metal rather than glowing.

+
+ + +
+

4Navitimer registers (cream on bronze)

+
+ ▶ ✦ 1 2 3 4 5 ▦ emacs — widgets.js + AUX ᯤ ♪ ᛒ ⛭ ⏱ ⋮⋮ + SAT JUL 18 · 07:55 PM +
+
+
Aux
+
Net · slide rule
+
+ + + +
+
Volume
+ + + + + + + + +
+
Blue
+ + + +
+
Maint
+ + + + +
+
Timer
+ + + + + + + + + +
+
Tray
+
+
Weather
+
+ +
+ 82° + CLEAR +
+
+
+ +
+ WNW + 6 mph +
+
+
Date · beads-of-rice frame
+
+ SAT +
18
+ JUL +
+
Time · big register
+
+ + + + + 12369 + + + + + + +
07:55 PM
+
+
+

The Navitimer direction: cream registers on a dark bronze dial. Every instrument is a cream sub-dial with +black markings — the inverse polarity of every other variation, so statuses read as printed gauge faces, not glowing lamps. +Net is a slide-rule strip with the red index needle; the date sits in a dark framed aperture like the Navitimer's 3-o'clock +window. Warm, aviation-flavored, and the busiest of the six — closest to a real cockpit cluster.

+
+ + +
+

5Auto dashboard (gauges + tell-tales + odometer)

+
+ ▶ ✦ 1 2 3 4 5 ▦ emacs — widgets.js + AUX NET VOL BT SVC ⏱ ⋮⋮ + SAT 18 JUL · 07:55 PM +
+
+
Aux
+
Net signal
+ + + + + + + SIG + + +
+
Volume
+ + + + + + + VOL + + +
+
Tell-tales
+
+ ᛒ BT + ⛭ SVC + 🔇 MUTE + ⚠ NET +
+
Timer
+
12:34
+
Tray
+
+
Weather
+
+
+ +
+ 82° + CLEAR +
+
+
+ +
+ WNW + 6 mph +
+
+
Odometer date
+
+
+
S
A
T
+
1
+
8
+
J
U
L
+
+
Clock · backlit
+
+
+ 07:55 PM CDT +
+
+

The dashboard direction: amber-backlit gauge cluster with chrome top/bottom trim rails. Net and volume are +round needle gauges; bluetooth/maint/mute/net-fail live in a tell-tale block (lit = active, dark = off) exactly like a +cluster's warning lamps; the date is a mixed odometer — white digits on the day drums (a real trip-counter trick); the clock +glows amber behind chrome. The bar's top and bottom edges get the chrome rails, which is what makes the whole strip read +"dashboard" at a glance. The amber family is the kit's existing panel amber, so this is the variation closest to the widget gallery.

+
+ + +
+

6Perpetual-calendar dress (guilloché + apertures)

+
+ ▶ ✦ 1 2 3 4 5 ▦ emacs — widgets.js + AUX ᯤ ♪ ᛒ ⛭ ☾ ⋮⋮ + SAT JUL · 18 · 7:55 +
+
+
Aux
+
Net
+
+ + +
+
Volume
+ + + + + +
+
Blue
+
+ + +
+
Maint
+
+ + +
+
Timer · moonphase
+ + + + + + + + + + 12:34 +
+
Tray
+
+
Weather
+
+ +
+ 82° + CLEAR +
+
+
+ +
+ WNW + 6 mph +
+
+
Day · Month apertures
+
+
SAT
+
JUL
+
+
Big date
+
+
1
+
8
+
+
Time
+
+ 7:55 PM CDT +
+
+

The Patek/AP direction: guilloché ground, gold-framed apertures, a moonphase. Day and month are twin +apertures (the SAM · NOV window pair), the date is a big-date double disc in cream, and the timer becomes the moonphase — +the moon crosses the arc as the countdown runs, with the digits small beneath. Statuses are cabochon jewels, not lamps. +The most jewelry-like and the most restrained: nothing glows except what matters. Least "stereo", most "watch".

+
+ +
+Shared decisions across all six, for discussion: +(1) AUX knob at the head of the right cluster is the future desktop-settings module (caffeine + dim + touchpad fold in) — +a knob in every variation since "settings" is the one module that IS a knob. +(2) Left half stays functionally identical everywhere; it takes each variation's ground treatment only. +(3) Date and time stay two separate modules (your current click/scroll contracts), even where they visually pair. +(4) The drum content is the ordinal-free format — drums can't render "18th" gracefully; worth deciding whether the +chronograph keeps ordinal at all. +(5) True-proportion strips show the honest 3440×54 balance: the right cluster occupies roughly a quarter of the bar. +(6) Weather module: the compact chip is the chosen bar form (2026-07-18) — it's the only one of the six weather takes legible at bar scale. The gauge, split-flap, VFD, nixie, and condition dial are kept as candidates for a larger panel or a click-to-expand view, not the bar. +
+ + + diff --git a/todo.org b/todo.org index 1ac2e76..b4b1509 100644 --- a/todo.org +++ b/todo.org @@ -74,6 +74,8 @@ Date and time are separate fixed-position controls. The time display cycles its own formats, including UTC; the date/calendar control cycles date-only formats and never displays a second time. Implement the dedicated format rings, tooltip behavior, and tests together in the dotfiles Waybar configuration. +Reference material for the compact clock/chronograph treatment is filed in +[[file:working/clock-display-references/][working/clock-display-references/]]. *** 2026-07-19 Sun @ 04:36:26 -0500 Folded clock-panel interaction direction The clock-panel handoff settled the prior open question: UTC belongs only to @@ -85,6 +87,54 @@ Reduce notification-sound playback loudness by 40% (0.6 gain, approximately -4.4 dB). Change the =NOTIFY_VOLUME= playback control rather than re-encoding the normalized sound files; verify each notification type still plays clearly. +** TODO [#B] Assess a Hyprland left-drag window gesture :feature:hyprland: +Evaluate whether a global left-click drag can move ordinary windows without +breaking application selection, text interaction, or Wayland security +expectations. Document the safe modifier/gesture alternatives before changing +any binding. + +** TODO [#C] Add a time selector to the timer panel :feature:timer: +Offer a period-appropriate selector for timer duration, likely drawing on the +tape-counter idiom, while preserving the existing direct-entry path. + +** TODO [#C] Show the active wired interface in the Waybar network module :feature:waybar:network: +When Ethernet is active, replace the offline-WiFi presentation with the wired +interface glyph and interface name. + +** TODO [#C] Order network-panel connections by availability :feature:network: +Present saved and currently available networks in this order: available saved +profiles, available unsaved networks, then saved profiles that are unavailable. + +** TODO [#C] Let the clock panel dismiss itself on click :feature:clock:waybar: +Make a left click inside the open clock panel toggle it closed; the Waybar time +module remains the explicit way to reopen it. + +** TODO [#C] Indicate hotspot or metered WiFi in amber :feature:network:waybar: +Detect hotspot/metered connectivity and render the WiFi icon plus SSID amber, +while ordinary WiFi stays white. + +** TODO [#C] Make the WiFi toggle connect the best available profile :feature:network: +When enabling WiFi, automatically connect to the highest-priority available +saved network instead of requiring a panel selection first. + +** TODO [#B] Reconcile panel keybindings around Super+N :feature:hyprland: +Swap the notification and networking bindings so primary panels are one +Super-plus-letter chord away, audit the other exceptions, and bring the +proposed family to Craig for a final mapping decision. + +** TODO [#B] Add storage-capacity signals to the maintenance module :feature:maint: +Investigate capacity and growth diagnostics for full disks, identify the +appropriate remedies, and incorporate a clear storage signal into the +maintenance console. + +** TODO [#B] Add per-channel controls to the audio panel :feature:audio: +Expose channel-level input and output volume controls without losing the +existing device-level workflow. + +** TODO [#C] Add a whole-display dim mode :feature:hyprland: +Extend auto-dim with an explicit “dim everything” setting for bright +non-dark-mode contexts, with a security/usability review of its scope. + ** DOING [#B] Widget gallery upgrades :feature:design: :PROPERTIES: :LAST_REVIEWED: 2026-07-13 @@ -254,6 +304,25 @@ The cross's cell assignments were read off card names and spec sheets, not audit The panel widget gallery ([[file:docs/prototypes/panel-widget-gallery.html][docs/prototypes/panel-widget-gallery.html]]) grows into a retro-instrument component catalogue: reference photos of period hardware → gallery cards (the visual + behavioral spec) → reusable components for three targets (emacs svg.el, web/React, waybar). Tokens single-sourced in [[file:docs/prototypes/tokens.json][tokens.json]] (gen_tokens.py emits web/waybar/elisp); svg.el proof widget shipped (gallery-widget.el, needle gauge). Reference photos live in [[file:working/retro-stereo-widgets/][working/retro-stereo-widgets/]]. Collection converged at R56, then reopened at R57 as the taxonomy found empty cells (110 cards, all behaviorally verified; probes in [[file:tests/gallery-probes/][tests/gallery-probes/]]). Build runs per the [[file:docs/specs/2026-07-12-component-generation-spec.org][component-generation spec]] (DOING; reviewed + decomposed 2026-07-12): web extraction first (ungated, lossless), then demand-gated Emacs/waybar ports. Banked variant/composition ledger lives in the 2026-07-11/12 session archive. +*** TODO [#B] Rebuild the magic-eye tube component :feature:design: +Reinstate the magic-eye tuning/level indicator (EM34/EM84/6E5 family), but +replace the earlier weak UI rather than reviving it unchanged. The component +must read as a real glass tube: P1-green phosphor bloom, a smooth shadow-angle +response, and a clearly different tuning-minimum / record-level interaction. +Build it to the current extraction-readiness bar with probes and a visual +comparison before proposing ports. Reference: [[https://en.wikipedia.org/wiki/File:Em11-ani.gif][EM11 animation]]. +*** TODO [#B] Add a wind-direction rose component :feature:weather:design: +Bank the weather companion from the Home handoff: a 16-point meteorological +wind rose that reads source direction and optional speed, distinct from the +aviation heading selector. Revisit when the weather surface needs more than +the compact vane/speed slot; no implementation before that demand exists. +*** 2026-07-19 Sun @ 15:40:01 -0500 Filed component and hardware references +Inbox zero filed the clock/chronograph concepts in +[[file:working/clock-display-references/][working/clock-display-references/]] +and the knob, split-flap, and display references in the existing +[[file:working/retro-stereo-widgets/references/][widget references]]. The Home +component handoff was reconciled: Craig reinstated the magic eye subject to a +better tube UI; the wind rose is banked for a future richer weather surface. *** DOING [#B] Weather kit integration :feature:weather:dotfiles: [[file:working/weather-kit-integration/][Working package]] received from Home on 2026-07-18. Land the shared, dependency-free =weather= CLI in dotfiles first, with cache/location/rendering tests; then wire a compact Dupre Waybar surface and port the supplied chip renderer to svg.el when its placement and interaction contract are settled. The chip itself remains current-conditions-only. Its hover exposes the next six hours; clicking it toggles a separate five-day forecast panel with each day's high/low and sunny/rainy condition. No coordinates ship in the repository: machines configure =$WEATHER_LAT=/$WEATHER_LON= or =~/.config/weather/config.json= (with =--geo= for travellers). diff --git a/working/clock-display-references/2026-07-17-clock-strip-concept-01.png b/working/clock-display-references/2026-07-17-clock-strip-concept-01.png new file mode 100644 index 0000000..6cd91c8 Binary files /dev/null and b/working/clock-display-references/2026-07-17-clock-strip-concept-01.png differ diff --git a/working/clock-display-references/2026-07-17-clock-strip-concept-02.png b/working/clock-display-references/2026-07-17-clock-strip-concept-02.png new file mode 100644 index 0000000..2edcebe Binary files /dev/null and b/working/clock-display-references/2026-07-17-clock-strip-concept-02.png differ diff --git a/working/clock-display-references/2026-07-17-clock-strip-concept-03.png b/working/clock-display-references/2026-07-17-clock-strip-concept-03.png new file mode 100644 index 0000000..fb14c0c Binary files /dev/null and b/working/clock-display-references/2026-07-17-clock-strip-concept-03.png differ diff --git a/working/clock-display-references/2026-07-17-clock-strip-concept-04.png b/working/clock-display-references/2026-07-17-clock-strip-concept-04.png new file mode 100644 index 0000000..d6bdd15 Binary files /dev/null and b/working/clock-display-references/2026-07-17-clock-strip-concept-04.png differ diff --git a/working/clock-display-references/2026-07-17-clock-strip-concept-05.png b/working/clock-display-references/2026-07-17-clock-strip-concept-05.png new file mode 100644 index 0000000..a2d3dda Binary files /dev/null and b/working/clock-display-references/2026-07-17-clock-strip-concept-05.png differ diff --git a/working/clock-display-references/2026-07-17-clock-strip-variant-sheet-01.png b/working/clock-display-references/2026-07-17-clock-strip-variant-sheet-01.png new file mode 100644 index 0000000..6ae249a Binary files /dev/null and b/working/clock-display-references/2026-07-17-clock-strip-variant-sheet-01.png differ diff --git a/working/clock-display-references/2026-07-17-clock-strip-variant-sheet-02.png b/working/clock-display-references/2026-07-17-clock-strip-variant-sheet-02.png new file mode 100644 index 0000000..eb82fd1 Binary files /dev/null and b/working/clock-display-references/2026-07-17-clock-strip-variant-sheet-02.png differ diff --git a/working/clock-display-references/2026-07-17-clock-strip-variant-sheet-03.png b/working/clock-display-references/2026-07-17-clock-strip-variant-sheet-03.png new file mode 100644 index 0000000..eb82fd1 Binary files /dev/null and b/working/clock-display-references/2026-07-17-clock-strip-variant-sheet-03.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-audemars-moonphase.png b/working/clock-display-references/2026-07-18-chronograph-audemars-moonphase.png new file mode 100644 index 0000000..bbf3404 Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-audemars-moonphase.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-blancpain-fifty-fathoms.png b/working/clock-display-references/2026-07-18-chronograph-blancpain-fifty-fathoms.png new file mode 100644 index 0000000..f4026c0 Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-blancpain-fifty-fathoms.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-breitling-navitimer.png b/working/clock-display-references/2026-07-18-chronograph-breitling-navitimer.png new file mode 100644 index 0000000..e518b2f Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-breitling-navitimer.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-date-idea-detail.png b/working/clock-display-references/2026-07-18-chronograph-date-idea-detail.png new file mode 100644 index 0000000..7f3c8a9 Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-date-idea-detail.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-date-idea.png b/working/clock-display-references/2026-07-18-chronograph-date-idea.png new file mode 100644 index 0000000..8bc8a4b Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-date-idea.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-date-window-detail.png b/working/clock-display-references/2026-07-18-chronograph-date-window-detail.png new file mode 100644 index 0000000..4ae7212 Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-date-window-detail.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-iwc-portugieser.png b/working/clock-display-references/2026-07-18-chronograph-iwc-portugieser.png new file mode 100644 index 0000000..d9d4616 Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-iwc-portugieser.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-omega-speedmaster.png b/working/clock-display-references/2026-07-18-chronograph-omega-speedmaster.png new file mode 100644 index 0000000..c62bf25 Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-omega-speedmaster.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-panerai-luminor-marina.png b/working/clock-display-references/2026-07-18-chronograph-panerai-luminor-marina.png new file mode 100644 index 0000000..b5a8d97 Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-panerai-luminor-marina.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-patek-moonphase.png b/working/clock-display-references/2026-07-18-chronograph-patek-moonphase.png new file mode 100644 index 0000000..a701931 Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-patek-moonphase.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-patek-perpetual-calendar.png b/working/clock-display-references/2026-07-18-chronograph-patek-perpetual-calendar.png new file mode 100644 index 0000000..11a5ac8 Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-patek-perpetual-calendar.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-patek-two-register.png b/working/clock-display-references/2026-07-18-chronograph-patek-two-register.png new file mode 100644 index 0000000..3cfa626 Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-patek-two-register.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-reference-notes.txt b/working/clock-display-references/2026-07-18-chronograph-reference-notes.txt new file mode 100644 index 0000000..b94cafe --- /dev/null +++ b/working/clock-display-references/2026-07-18-chronograph-reference-notes.txt @@ -0,0 +1,20 @@ +Chronograph / watch-face design inspiration — 12 reference images. + +Collected by Craig and dropped from a home session on 2026-07-18 for the +chronograph / clock-panel UI work. Each file is chronograph-inspiration-NN-.png. + +Identified: +01 Blancpain Fifty Fathoms +02 Omega Speedmaster Professional (Moonwatch) +03 Rolex Submariner Date +05 Panerai Luminor Marina +07 IWC Portugieser Chronograph +08 Breitling Navitimer +09 Patek Philippe perpetual calendar chronograph (resembles ref 1518) +10 Audemars Piguet perpetual calendar moonphase +11 Patek Philippe two-register chronograph (resembles ref 130) +12 Patek Philippe perpetual calendar moonphase (3940/5140 family) + +Uncertain from the crop (relabel if the model is known): +04 dial date-window detail — model unidentified +06 Rolex fluted bezel + Cyclops date — Datejust or Day-Date, not certain diff --git a/working/clock-display-references/2026-07-18-chronograph-rolex-cyclops-detail.png b/working/clock-display-references/2026-07-18-chronograph-rolex-cyclops-detail.png new file mode 100644 index 0000000..1050032 Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-rolex-cyclops-detail.png differ diff --git a/working/clock-display-references/2026-07-18-chronograph-rolex-submariner-date.png b/working/clock-display-references/2026-07-18-chronograph-rolex-submariner-date.png new file mode 100644 index 0000000..e7bd485 Binary files /dev/null and b/working/clock-display-references/2026-07-18-chronograph-rolex-submariner-date.png differ diff --git a/working/clock-display-references/README.org b/working/clock-display-references/README.org new file mode 100644 index 0000000..f3b9bf2 --- /dev/null +++ b/working/clock-display-references/README.org @@ -0,0 +1,6 @@ +#+TITLE: Clock Display Reference Set + +Clock and chronograph reference material received during the 2026-07-19 inbox +zero pass. The concept renders establish the compact date/time-strip direction; +the watch photographs are reference-only for dial hierarchy, date treatment, +and complication spacing. They are not assets to reproduce or redistribute. diff --git a/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-black-detail.jpeg b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-black-detail.jpeg new file mode 100644 index 0000000..d723827 Binary files /dev/null and b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-black-detail.jpeg differ diff --git a/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-black.jpg b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-black.jpg new file mode 100644 index 0000000..8b2ed8e Binary files /dev/null and b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-black.jpg differ diff --git a/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-fender-detail.jpeg b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-fender-detail.jpeg new file mode 100644 index 0000000..6a07d1d Binary files /dev/null and b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-fender-detail.jpeg differ diff --git a/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-fender.webp b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-fender.webp new file mode 100644 index 0000000..8223796 Binary files /dev/null and b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-fender.webp differ diff --git a/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-guitar-detail.jpeg b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-guitar-detail.jpeg new file mode 100644 index 0000000..2f2d72c Binary files /dev/null and b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-guitar-detail.jpeg differ diff --git a/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-guitar-pair.jpg b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-guitar-pair.jpg new file mode 100644 index 0000000..fecefdd Binary files /dev/null and b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-guitar-pair.jpg differ diff --git a/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-style-sheet.jpeg b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-style-sheet.jpeg new file mode 100644 index 0000000..3962c9f Binary files /dev/null and b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-style-sheet.jpeg differ diff --git a/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-tone-volume.jpg b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-tone-volume.jpg new file mode 100644 index 0000000..e6e0cc0 Binary files /dev/null and b/working/retro-stereo-widgets/references/2026-07-18-dual-concentric-knob-tone-volume.jpg differ diff --git a/working/retro-stereo-widgets/references/2026-07-18-hotfx-split-flap-reference.org b/working/retro-stereo-widgets/references/2026-07-18-hotfx-split-flap-reference.org new file mode 100644 index 0000000..5fa7acd --- /dev/null +++ b/working/retro-stereo-widgets/references/2026-07-18-hotfx-split-flap-reference.org @@ -0,0 +1,65 @@ +#+TITLE: HotFX split-flap web component — technique reference +#+DATE: [2026-07-18 Sat] + +Reference for the split-flap display work (gallery card N20, and any flip/drum +date-time module the waybar chronograph grows). Craig supplied both links. + +- Annotated walkthrough: https://fx.hot.page/split-flap/source +- Source on GitHub: https://github.com/hot-page/fx/blob/main/hotfx-split-flap/hotfx-split-flap.js +- Local study copy: [[file:2026-07-18-hotfx-split-flap-source.js]] + +LICENSE CAUTION: the fx repo declares NO license (checked via the GitHub API +2026-07-18), so the code is all-rights-reserved by default. Study the +technique, re-derive our own implementation in the GW namespace — do not copy +code or CSS verbatim into the gallery or dotfiles. + +* The mechanics worth stealing (as ideas) + +** Four-layer character cell +Each character is four absolutely-stacked half-panels: =.top= / =.bottom= +(current char) and =.top-next= / =.bottom-next= (incoming char), the tops +clipped to the upper half and bottoms to the lower half, with a divider line +at 50%. =transform-style: preserve-3d= on the cell, +=backface-visibility: hidden= on flaps so a flap's reverse never shows. + +** One flip = two synchronized quarter-animations +Only two elements move per flip, via the Web Animations API with +=Promise.all= on their =.finished=: +- current =.top= rotates =rotateX(0deg)= → =rotateX(-180deg)= (falls forward) +- =.bottom-next= rotates =rotateX(180deg)= → =rotateX(0deg)= (lands) +Both =ease-in=, default 150ms per flap. After landing, the static halves get +the new character's text and the cycle repeats. This is the minimal honest +split-flap kinematic. + +** The charset IS the drum +It never jumps to the target character: it steps =currentIndex + 1= (wrapping) +through the declared alphabet until it arrives, one flip per step — the +Solari constraint that a board can only advance one flap at a time. The +alphabet is a declared attribute (default =' ABC…Z0-9!.,:?"'/$'=), so the +character order is data, exactly our R58 LAYOUT-as-data pattern: rewrite the +alphabet and the physical behavior follows. + +** Live-update semantics (relevant for a clock) +A MutationObserver rebuilds the target grid on text change; an in-flight flip +cascade aborts itself by identity-checking the target grid each step, and the +new animation starts only after awaiting every running animation's finish. +That's the answer to "the minute changed while the drum was still spinning" — +abort-and-retarget, never queue. + +** Also noted +IntersectionObserver defers the animation until the element is half in view; +=::part(flap-N-M)= attributes expose every flap and divider for external +styling. ~300 lines total for all of it. + +* How it maps to our targets + +- Gallery N20 (split-flap board): the four-layer cell + two-animation flip is + the web implementation; the step-through-charset rule and abort-on-retarget + are behaviors our probe checks can pin regardless of implementation. +- Waybar chronograph drums (the redesign mock's date/time): our drums are + odometer rollers, not flaps — different mechanism — but the charset-as-drum + ordering and abort-and-retarget rules carry over unchanged. +- Emacs svg.el port: no Web Animations API; the flip becomes frame-stepped + whole-image regeneration per the render contract, with the same + one-step-at-a-time rule. Reduced-motion gate applies (widget-owned + animation per the gallery tick contract). diff --git a/working/retro-stereo-widgets/references/2026-07-18-hotfx-split-flap-source.js b/working/retro-stereo-widgets/references/2026-07-18-hotfx-split-flap-source.js new file mode 100644 index 0000000..40539ad --- /dev/null +++ b/working/retro-stereo-widgets/references/2026-07-18-hotfx-split-flap-source.js @@ -0,0 +1,455 @@ +// Create a custom element by extending `HTMLElement`. +export class HotFXSplitFlap extends HTMLElement { + // The internal state of the component is handled in private instance + // variables, which in JavaScript become private when they start with `#`. + // + // `hasAnimated` is a flag that keeps track of whether the animation has + // already completed. + #hasAnimated = false + // `isVisible` is a flag that keeps track of whether the element is currently + // visible in the viewport. + #isVisible = false + // The `IntersectionObserver` fires a callback that lets us know if the element + // is visible in the window or not. + #intersectionObserver + // The `MutationObserver` tells the element when it's content changes, so we + // can re-render the grid and animate it again. + #mutationObserver + // The `#currentGrid` is the current state of the text. The characters are + // stored as a two-dimensional array: each row is an array of characters and + // the grid is an array of rows. + #currentGrid + // The `#targetGrid` shows the ultimate destination of the + #targetGrid + + // Here we tell the browser to fire `attributeChangedCallback` when one of + // the `width`, `height`, or `duration` attributes changes. + static get observedAttributes() { + return ['width', 'height', 'duration', 'characcters'] + } + + constructor() { + // Always call your `super()` or the landlord will be mad. + super() + // Create a shadow DOM for this element that will hold all the elements and + // stlyes needed to create the flaps. + this.attachShadow({ mode: 'open' }) + } + + connectedCallback() { + // First do some validation on the attributes. Although all of them are + // optional, they are required to be positive integers if set. + ['width', 'height', 'duration'].forEach(name => { + if (!this.hasAttribute(name)) return + const value = parseInt(this.getAttribute(name)) + if (isNaN(value) || value <= 0) this.#printNumberError(name) + }) + + // If this element has already been mounted elsewhere the `#hasAnimated` + // flag may be set even though we're coming in fresh here. Let's reset it. + this.#hasAnimated = false + // The `#render()` function will create the grid and the flaps inside the + // shadow DOM. + this.#render() + + // Setup our `IntersectionObserver`, which will fire a callback function + // when this element scrolls into view. + this.#intersectionObserver = new IntersectionObserver( + (entries) => { + // Since we are only observing one element, the callback should always + // fire with one value in `entries`. + this.#isVisible = entries[0].isIntersecting + // The animation should only run the first time that we scoll onto it + // so only trigger the animation it if #hasAnimated is false. + if (this.#isVisible && !this.#hasAnimated) this.#animate() + }, + // Fire the callback when 50% of this element has scrolled into view. + { threshold: 0.5 } + ) + // Observe this element + this.#intersectionObserver.observe(this) + + // Setup the `MutationObserver` that will re-run the animation when the + // text inside this element changes. + this.#mutationObserver = new MutationObserver(async () => { + // The `#targetGrid` is the string of text that the element will animate + // towards. + this.#updateTargetGrid() + // First wait for the current animations to finish so there's no glitch + // when the new animations start. + await Promise.all( + Array.from(this.shadowRoot.querySelectorAll('.char')) + .map(char => char.getAnimations()) + .flat() + .map(animation => animation.finish()) + ) + // If the element is currently in view, start the animation. + if (this.#isVisible) this.#animate() + }) + // Observe this element, along with the entire tree under it. + this.#mutationObserver.observe( + this, + { childList: true, characterData: true, subtree: true }, + ) + } + + // Clean up the observers so that memory doesn't leak out of the browser, + // causing it to lose all of it's life force. + disconnectedCallback() { + this.#intersectionObserver?.disconnect() + this.#intersectionObserver = undefined + this.#mutationObserver?.disconnect() + this.#mutationObserver = undefined + } + + // The browser will call this when one of the `observedAttributes` changes. + attributeChangedCallback(name, oldValue, newValue) { + // Do some validation of the new value to warn the page author if they've + // added an invalid value. + if (['width', 'height', 'duration'].includes(name)) { + const value = parseInt(this.getAttribute(name)) + if (isNaN(value) || value <= 0) return this.#printNumberError(name) + } + // If the attribute that was changed is `duration` we don't neeed to do + // anything because it will be picked up in the next animation. + if (name == 'duration' || oldValue == newValue) return + // If `width` or `height` change, we will need to re-render the structure + // of the shadow DOM to account for the new size. + this.#render() + // Finally, restart the animation if this element is currently in the + // viewport. After rendering, the tiles are all on the first letter which is + // the space character by default. + if (this.#isVisible) this.#animate() + } + + // Getter for `width` property called when reading `element.width`. + get width() { + // If there is a `#currentGrid`, report this size. + if (this.#currentGrid) return this.#currentGrid[0].length + // Otherwise, try to use the attribute if set. + if (this.hasAttribute('width')) { + const width = parseInt(this.getAttribute('width')) + if (Number.isInteger(width) && width > 0) return width + } + // Finally, if there is no grid and no attribute, we default to the size of + // the text inside this element, specifically the longest line. + return Math.max(...this.textContent.split('\n').map(line => line.length)) + } + + // Reflect the `.width` property back to the `width=` attribute. + set width(value) { + // Validate the new value which should be a positive integer. + if (!Number.isInteger(value) || value <= 0) this.#printNumberError('width') + // Reflect the new value to the attribute and re-render the shadow DOM + this.setAttribute('width', value) + this.#render() + // If the element is in the viewport, run the animation now. + if (this.#isVisible) this.#animate() + } + + // Getter for `.height` property similar to above for `width`, but returning + // the number of lines in the text content instead of the longest line. + get height() { + if (this.#currentGrid) return this.#currentGrid.length + if (this.hasAttribute('height')) { + const height = parseInt(this.getAttribute('height')) + if (Number.isInteger(height) && height > 0) return height + } + return this.textContent.split('\n').length + } + + // Setter for `.height` property, same as above for `width`. + set height(value) { + if (!Number.isInteger(value) || value <= 0) this.#printNumberError('height') + this.setAttribute('height', value) + this.#render() + if (this.#isVisible) this.#animate() + } + + // Get the duration of the animation which is how long it takes for one flap + // to fall down from top to bottom. + get duration() { + // The duration defaults to 150 milliseconds or 0.15 seconds for each flap + // to fall once. + let duration = 150 + // Check if there's a `duration` attribute and use that value if it's a + // positive integer. + if (this.hasAttribute('duration')) { + const durationAttr = parseInt(this.getAttribute('duration')) + if (Number.isInteger(durationAttr) && durationAttr > 0) duration = durationAttr + } + return duration + } + + // Setter for duration that reflects to the attribute with valdiation logic. + set duration(value) { + if (typeof value !== 'number' || value <= 0) return this.#printNumberError('duration') + this.setAttribute('duration', value) + } + + // Getter for `.characters` property. This is the alphabet used for the flaps. + // Here we're just pulling from the attribute which has the current value or + // using the default Latin alphabet. + get characters() { + return this.getAttribute('characters') || ' ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!.,:?"\'/$' + } + + // Setter for `.characters` property that reflects to the attribute. + set characters(value) { + this.setAttribute('characters', value) + } + + // Helper function that turns the text of this element into the grid of + // letters that we're going to animate towards. The grid is stored as a + // two-dimensional array of characters. + #updateTargetGrid() { + // Split the text on newline to get an array of lines. + const lines = this.textContent.split('\n') + // Note that we're using the `.width` and `.height` properties because the + // dimensions of the grid could have been set explicitly using attributes. + this.#targetGrid = Array.from({ length: this.height }).map((_, row) => { + const line = lines[row] || '' + return Array.from({ length: this.width }).map( + (_, col) => line[col]?.toUpperCase() || ' ', + ) + }) + } + + // Render the grid of letters into the shadow DOM. + #render() { + // First set `#currentGrid` to null because it will be used to calculate the + // width and height of the next grid and we need to start fresh. + this.#currentGrid = null + // The `#currentGrid` is the current state of the letters displayed. Since + // we're starting over fresh here, we just make it all space characters. + this.#currentGrid = Array.from({ length: this.height }, () => + Array.from({ length: this.width }, () => ' ') + ) + this.#updateTargetGrid() + + this.shadowRoot.innerHTML = [` + +
`, + // Create the grid of characters by mapping over the `#currentGrid` + // (in both dimensions) + this.#currentGrid.map((row, rowNumber) => { + // Row and column numbers used in `::part()` start at 1 as per CSS + // convention, e.g. `nth-child(1)` + rowNumber += 1 + return row.map((char, columnNumber) => { + columnNumber += 1 + const currentChar = char === ' ' ? ' ' : char + // Each character and its flaps have several part= attribute values + // so they can be addressed in CSS, for example + // - `::part(char-1)` for all the characters in the first column + // - `::part(flap-3-4)` for the flaps in the third row and + // fourth column + return ` + + + + + ${currentChar} + + + + + + ` + }).join('') + }).join(''), + `
` + ].join('') + } + + // To animate, the element must rotate each flap, then swap out the characters + // in the DOM and then repeat through the whole alphabet until it arrives at + // the correct end character. + #animate() { + // If the animation has already run, don't run it again. + this.#hasAnimated = true + + // Create a copy of the target grid so that we can check if it since been + // updated and the animation should be aborted (since another one will + // be triggered). + const targetGrid = this.#targetGrid + + // Animate each character. + this.shadowRoot.querySelectorAll('.char').forEach((char, index) => { + // Find the index of this charcter in the grid. + const row = Math.floor(index / this.width) + const col = index % this.width + + // The index of the target character in our alphabet. + const targetCharIndex = this.characters.includes(targetGrid[row][col]) + ? this.characters.indexOf(targetGrid[row][col]) + : 0 + + // The current character index in the alphabet. + let currentCharIndex = this.characters.indexOf(this.#currentGrid[row][col]) + + // If the indexes are the same, it's already finished and this character + // should not be animated at all + if (currentCharIndex == targetCharIndex) return + + // This function will run one flap and wait for it to finsih before + // calling itself again to animate the next flap. + const flap = async () => { + // If the target grid has changed since we started animating, abort + // this animation since another one will be triggered. + if (targetGrid != this.#targetGrid) return + + // Find the index of the character following the current one, wrapping + // around to the start if needed. + const nextCharIndex = currentCharIndex < this.characters.length - 1 + ? currentCharIndex + 1 + : 0 + + // The next character to animate towards + const nextChar = this.characters[nextCharIndex] + + // Set the text content of the top and bottom flaps to the next character + char.querySelector('.top-next').childNodes[0].textContent = nextChar + char.querySelector('.bottom-next').childNodes[0].textContent = nextChar + + // Anaimte the top flap and the bottom of the next flap to fall from the + // top. Using `await Promise.all()` to wait for both animations to + // finish. + const options = { duration: this.duration, easing: 'ease-in' } + await Promise.all([ + char.querySelector('.top').animate([ + { transform: 'rotateX(0deg)' }, + { transform: 'rotateX(-180deg)' } + ], options).finished, + char.querySelector('.bottom-next').animate([ + { transform: 'rotateX(180deg)' }, + { transform: 'rotateX(0deg)' } + ], options).finished + ]) + + // Swap out the top and bottom flaps and set the `currentCharIndex` value + // so that the next character is now the currect character. + char.querySelector('.top').childNodes[0].textContent = nextChar + char.querySelector('.bottom').childNodes[0].textContent = nextChar + currentCharIndex = nextCharIndex + // Update the #currentGrid with the new character. + this.#currentGrid[row][col] = this.characters[currentCharIndex] + // If the new currrent character is still not the target character, + // continue animating by calling this `flap()` function again. + if (currentCharIndex !== targetCharIndex) flap() + } + + // Trigger the first animation for this character. + flap() + }) + } + + // Helper function to print errors for invalid attributes. + #printNumberError(name) { + console.error(` ${name} attribute must be a positive integer; got "${this.getAttribute(name)}"`) + } + +} + +// Checks for a `?define` query parameter in the URL that was used to load this +// script. So, for instance, if you load the script with ` + + diff --git a/working/weather-kit-integration/weather-glyphs.svg b/working/weather-kit-integration/weather-glyphs.svg new file mode 100644 index 0000000..0cc3189 --- /dev/null +++ b/working/weather-kit-integration/weather-glyphs.svg @@ -0,0 +1,81 @@ + + + -- cgit v1.2.3