fix(demo): robust startup, timeline sync and cleanup in record.sh
- commit
- 87924a1cf937b8f6fbad38ce94b645b8bc1fa2fd
- author
- brice · · 2026-10-21 05:26 UTC
- parent
- 9d71793
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
12 files · +153 -57
M docs/dev/headless.md +2 -1
| @@ -28,7 +28,8 @@ For a GIF, convert `demo.mp4` with ffmpeg's `palettegen` and `paletteuse` filter | |||
| 28 | 28 | Run `tools/demo/record.sh` with Go 1.27, rootless Docker and ffmpeg installed. | |
| 29 | 29 | It builds an Arch app image and the host compositor, then records the 1080p README demo. | |
| 30 | 30 | Apps run in a disposable, offline container; only the headless Wayland socket is mounted. | |
| 31 | - | The shell timeline launches apps, while `input.txt` drives keys; captures stay at the printed path. | |
| 31 | + | A FIFO synchronizes `input.txt` with app launches after container startup. | |
| 32 | + | Temporary captures are removed by default; set `KEEP_CAPTURE=1` to keep successful runs. | |
| 32 | 33 | The recording and poster replace `docs/media/demo.mp4` and `docs/media/demo.png`. | |
| 33 | 34 | ||
| 34 | 35 | ## Input scripts | |
| @@ -28,7 +28,8 @@ For a GIF, convert `demo.mp4` with ffmpeg's `palettegen` and `paletteuse` filter | |||||
| 28 | Run `tools/demo/record.sh` with Go 1.27, rootless Docker and ffmpeg installed. | 28 | Run `tools/demo/record.sh` with Go 1.27, rootless Docker and ffmpeg installed. | ||
| 29 | It builds an Arch app image and the host compositor, then records the 1080p README demo. | 29 | It builds an Arch app image and the host compositor, then records the 1080p README demo. | ||
| 30 | Apps run in a disposable, offline container; only the headless Wayland socket is mounted. | 30 | Apps run in a disposable, offline container; only the headless Wayland socket is mounted. | ||
| 31 | - | The shell timeline launches apps, while `input.txt` drives keys; captures stay at the printed path. | 31 | + | A FIFO synchronizes `input.txt` with app launches after container startup. |
| 32 | + | Temporary captures are removed by default; set `KEEP_CAPTURE=1` to keep successful runs. | |||
| 32 | The recording and poster replace `docs/media/demo.mp4` and `docs/media/demo.png`. | 33 | The recording and poster replace `docs/media/demo.mp4` and `docs/media/demo.png`. | ||
| 33 | 34 | ||||
| 34 | ## Input scripts | 35 | ## Input scripts | ||
M docs/media/demo.mp4 +0 -0 binary file: no text diff
binary file: no text diff
M docs/media/demo.png +0 -0 binary file: no text diff
binary file: no text diff
M tools/demo/Dockerfile +3 -1
| @@ -3,7 +3,7 @@ FROM archlinux:latest | |||
| 3 | 3 | RUN --mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \ | |
| 4 | 4 | pacman -Sy --noconfirm archlinux-keyring \ | |
| 5 | 5 | && pacman -Syu --noconfirm --needed \ | |
| 6 | - | firefox foot btop fastfetch eza bat mousepad \ | |
| 6 | + | firefox foot btop fastfetch eza mousepad \ | |
| 7 | 7 | ttf-jetbrains-mono-nerd noto-fonts noto-fonts-emoji dbus mesa vulkan-swrast | |
| 8 | 8 | # Arch's btop carries file capabilities that rootless Docker cannot grant. | |
| 9 | 9 | RUN install -m 0755 /usr/bin/btop /usr/local/bin/btop | |
| @@ -11,8 +11,10 @@ RUN useradd -m -u 1000 demo \ | |||
| 11 | 11 | && mkdir -p /home/demo/firefox /home/demo/.config/btop /run/wl \ | |
| 12 | 12 | && chmod 0700 /run/wl | |
| 13 | 13 | COPY firefox/user.js /home/demo/firefox/user.js | |
| 14 | + | COPY firefox/userChrome.css /home/demo/firefox/chrome/userChrome.css | |
| 14 | 15 | COPY foot.ini /home/demo/.config/foot/foot.ini | |
| 15 | 16 | COPY btop.conf /home/demo/.config/btop/btop.conf | |
| 17 | + | COPY btop-vesper.theme /home/demo/.config/btop/themes/vesper.theme | |
| 16 | 18 | COPY site /home/demo/site | |
| 17 | 19 | COPY config /home/demo/config | |
| 18 | 20 | RUN chown -R demo:demo /home/demo /run/wl | |
| @@ -3,7 +3,7 @@ FROM archlinux:latest | |||||
| 3 | RUN --mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \ | 3 | RUN --mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \ | ||
| 4 | pacman -Sy --noconfirm archlinux-keyring \ | 4 | pacman -Sy --noconfirm archlinux-keyring \ | ||
| 5 | && pacman -Syu --noconfirm --needed \ | 5 | && pacman -Syu --noconfirm --needed \ | ||
| 6 | - | firefox foot btop fastfetch eza bat mousepad \ | 6 | + | firefox foot btop fastfetch eza mousepad \ |
| 7 | ttf-jetbrains-mono-nerd noto-fonts noto-fonts-emoji dbus mesa vulkan-swrast | 7 | ttf-jetbrains-mono-nerd noto-fonts noto-fonts-emoji dbus mesa vulkan-swrast | ||
| 8 | # Arch's btop carries file capabilities that rootless Docker cannot grant. | 8 | # Arch's btop carries file capabilities that rootless Docker cannot grant. | ||
| 9 | RUN install -m 0755 /usr/bin/btop /usr/local/bin/btop | 9 | RUN install -m 0755 /usr/bin/btop /usr/local/bin/btop | ||
| @@ -11,8 +11,10 @@ RUN useradd -m -u 1000 demo \ | |||||
| 11 | && mkdir -p /home/demo/firefox /home/demo/.config/btop /run/wl \ | 11 | && mkdir -p /home/demo/firefox /home/demo/.config/btop /run/wl \ | ||
| 12 | && chmod 0700 /run/wl | 12 | && chmod 0700 /run/wl | ||
| 13 | COPY firefox/user.js /home/demo/firefox/user.js | 13 | COPY firefox/user.js /home/demo/firefox/user.js | ||
| 14 | + | COPY firefox/userChrome.css /home/demo/firefox/chrome/userChrome.css | |||
| 14 | COPY foot.ini /home/demo/.config/foot/foot.ini | 15 | COPY foot.ini /home/demo/.config/foot/foot.ini | ||
| 15 | COPY btop.conf /home/demo/.config/btop/btop.conf | 16 | COPY btop.conf /home/demo/.config/btop/btop.conf | ||
| 17 | + | COPY btop-vesper.theme /home/demo/.config/btop/themes/vesper.theme | |||
| 16 | COPY site /home/demo/site | 18 | COPY site /home/demo/site | ||
| 17 | COPY config /home/demo/config | 19 | COPY config /home/demo/config | ||
| 18 | RUN chown -R demo:demo /home/demo /run/wl | 20 | RUN chown -R demo:demo /home/demo /run/wl | ||
A tools/demo/btop-vesper.theme +43 -0
| @@ -0,0 +1,43 @@ | |||
| 1 | + | # Vesper-inspired warm monochrome, with peach highlights. | |
| 2 | + | theme[main_bg]="#101010" | |
| 3 | + | theme[main_fg]="#ffffff" | |
| 4 | + | theme[title]="#ffc799" | |
| 5 | + | theme[hi_fg]="#ffc799" | |
| 6 | + | theme[selected_bg]="#282828" | |
| 7 | + | theme[selected_fg]="#ffffff" | |
| 8 | + | theme[inactive_fg]="#7e7e7e" | |
| 9 | + | theme[proc_misc]="#ffc799" | |
| 10 | + | theme[cpu_box]="#7e7e7e" | |
| 11 | + | theme[mem_box]="#7e7e7e" | |
| 12 | + | theme[net_box]="#7e7e7e" | |
| 13 | + | theme[proc_box]="#7e7e7e" | |
| 14 | + | theme[div_line]="#282828" | |
| 15 | + | theme[meter_bg]="#282828" | |
| 16 | + | theme[graph_text]="#a0a0a0" | |
| 17 | + | theme[temp_start]="#7e7e7e" | |
| 18 | + | theme[temp_mid]="#e6b99d" | |
| 19 | + | theme[temp_end]="#ffc799" | |
| 20 | + | theme[cpu_start]="#7e7e7e" | |
| 21 | + | theme[cpu_mid]="#e6b99d" | |
| 22 | + | theme[cpu_end]="#ffc799" | |
| 23 | + | theme[free_start]="#7e7e7e" | |
| 24 | + | theme[free_mid]="#e6b99d" | |
| 25 | + | theme[free_end]="#ffc799" | |
| 26 | + | theme[cached_start]="#7e7e7e" | |
| 27 | + | theme[cached_mid]="#e6b99d" | |
| 28 | + | theme[cached_end]="#ffc799" | |
| 29 | + | theme[available_start]="#7e7e7e" | |
| 30 | + | theme[available_mid]="#e6b99d" | |
| 31 | + | theme[available_end]="#ffc799" | |
| 32 | + | theme[used_start]="#7e7e7e" | |
| 33 | + | theme[used_mid]="#e6b99d" | |
| 34 | + | theme[used_end]="#ffc799" | |
| 35 | + | theme[download_start]="#7e7e7e" | |
| 36 | + | theme[download_mid]="#e6b99d" | |
| 37 | + | theme[download_end]="#ffc799" | |
| 38 | + | theme[upload_start]="#7e7e7e" | |
| 39 | + | theme[upload_mid]="#e6b99d" | |
| 40 | + | theme[upload_end]="#ffc799" | |
| 41 | + | theme[process_start]="#7e7e7e" | |
| 42 | + | theme[process_mid]="#e6b99d" | |
| 43 | + | theme[process_end]="#ffc799" | |
| @@ -0,0 +1,43 @@ | |||||
| 1 | + | # Vesper-inspired warm monochrome, with peach highlights. | |||
| 2 | + | theme[main_bg]="#101010" | |||
| 3 | + | theme[main_fg]="#ffffff" | |||
| 4 | + | theme[title]="#ffc799" | |||
| 5 | + | theme[hi_fg]="#ffc799" | |||
| 6 | + | theme[selected_bg]="#282828" | |||
| 7 | + | theme[selected_fg]="#ffffff" | |||
| 8 | + | theme[inactive_fg]="#7e7e7e" | |||
| 9 | + | theme[proc_misc]="#ffc799" | |||
| 10 | + | theme[cpu_box]="#7e7e7e" | |||
| 11 | + | theme[mem_box]="#7e7e7e" | |||
| 12 | + | theme[net_box]="#7e7e7e" | |||
| 13 | + | theme[proc_box]="#7e7e7e" | |||
| 14 | + | theme[div_line]="#282828" | |||
| 15 | + | theme[meter_bg]="#282828" | |||
| 16 | + | theme[graph_text]="#a0a0a0" | |||
| 17 | + | theme[temp_start]="#7e7e7e" | |||
| 18 | + | theme[temp_mid]="#e6b99d" | |||
| 19 | + | theme[temp_end]="#ffc799" | |||
| 20 | + | theme[cpu_start]="#7e7e7e" | |||
| 21 | + | theme[cpu_mid]="#e6b99d" | |||
| 22 | + | theme[cpu_end]="#ffc799" | |||
| 23 | + | theme[free_start]="#7e7e7e" | |||
| 24 | + | theme[free_mid]="#e6b99d" | |||
| 25 | + | theme[free_end]="#ffc799" | |||
| 26 | + | theme[cached_start]="#7e7e7e" | |||
| 27 | + | theme[cached_mid]="#e6b99d" | |||
| 28 | + | theme[cached_end]="#ffc799" | |||
| 29 | + | theme[available_start]="#7e7e7e" | |||
| 30 | + | theme[available_mid]="#e6b99d" | |||
| 31 | + | theme[available_end]="#ffc799" | |||
| 32 | + | theme[used_start]="#7e7e7e" | |||
| 33 | + | theme[used_mid]="#e6b99d" | |||
| 34 | + | theme[used_end]="#ffc799" | |||
| 35 | + | theme[download_start]="#7e7e7e" | |||
| 36 | + | theme[download_mid]="#e6b99d" | |||
| 37 | + | theme[download_end]="#ffc799" | |||
| 38 | + | theme[upload_start]="#7e7e7e" | |||
| 39 | + | theme[upload_mid]="#e6b99d" | |||
| 40 | + | theme[upload_end]="#ffc799" | |||
| 41 | + | theme[process_start]="#7e7e7e" | |||
| 42 | + | theme[process_mid]="#e6b99d" | |||
| 43 | + | theme[process_end]="#ffc799" | |||
M tools/demo/btop.conf +1 -1
| @@ -1,4 +1,4 @@ | |||
| 1 | - | color_theme = "tokyo-night" | |
| 1 | + | color_theme = "/home/demo/.config/btop/themes/vesper.theme" | |
| 2 | 2 | theme_background = False | |
| 3 | 3 | truecolor = True | |
| 4 | 4 | update_ms = 500 | |
| @@ -1,4 +1,4 @@ | |||||
| 1 | - | color_theme = "tokyo-night" | 1 | + | color_theme = "/home/demo/.config/btop/themes/vesper.theme" |
| 2 | theme_background = False | 2 | theme_background = False | ||
| 3 | truecolor = True | 3 | truecolor = True | ||
| 4 | update_ms = 500 | 4 | update_ms = 500 | ||
M tools/demo/config +4 -4
| @@ -1,12 +1,12 @@ | |||
| 1 | 1 | # NeferWL · a little space to think | |
| 2 | 2 | animations = on | |
| 3 | - | layout.gaps = 8 | |
| 3 | + | layout.gaps = 0 | |
| 4 | 4 | layout.max-columns = 2 | |
| 5 | 5 | layout.overflow = cascade | |
| 6 | 6 | border.width = 2 | |
| 7 | - | border.active = #7aa2f7 | |
| 8 | - | border.inactive = #414868 | |
| 9 | - | background = #1a1b26 | |
| 7 | + | border.active = #ffc799 | |
| 8 | + | border.inactive = #282828 | |
| 9 | + | background = #101010 | |
| 10 | 10 | terminal.auto-open = off | |
| 11 | 11 | stash.spawn-terminal = off | |
| 12 | 12 | stash.capture = off | |
| @@ -1,12 +1,12 @@ | |||||
| 1 | # NeferWL · a little space to think | 1 | # NeferWL · a little space to think | ||
| 2 | animations = on | 2 | animations = on | ||
| 3 | - | layout.gaps = 8 | 3 | + | layout.gaps = 0 |
| 4 | layout.max-columns = 2 | 4 | layout.max-columns = 2 | ||
| 5 | layout.overflow = cascade | 5 | layout.overflow = cascade | ||
| 6 | border.width = 2 | 6 | border.width = 2 | ||
| 7 | - | border.active = #7aa2f7 | 7 | + | border.active = #ffc799 |
| 8 | - | border.inactive = #414868 | 8 | + | border.inactive = #282828 |
| 9 | - | background = #1a1b26 | 9 | + | background = #101010 |
| 10 | terminal.auto-open = off | 10 | terminal.auto-open = off | ||
| 11 | stash.spawn-terminal = off | 11 | stash.spawn-terminal = off | ||
| 12 | stash.capture = off | 12 | stash.capture = off | ||
M tools/demo/firefox/user.js +2 -0
| @@ -11,3 +11,5 @@ user_pref("toolkit.telemetry.reportingpolicy.firstRun", false); | |||
| 11 | 11 | user_pref("browser.toolbars.bookmarks.visibility", "never"); | |
| 12 | 12 | user_pref("widget.gtk.overlay-scrollbars.enabled", true); | |
| 13 | 13 | user_pref("browser.tabs.warnOnClose", false); | |
| 14 | + | // Keep browser chrome neutral rather than Firefox's purple dark-theme tint. | |
| 15 | + | user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true); | |
| @@ -11,3 +11,5 @@ user_pref("toolkit.telemetry.reportingpolicy.firstRun", false); | |||||
| 11 | user_pref("browser.toolbars.bookmarks.visibility", "never"); | 11 | user_pref("browser.toolbars.bookmarks.visibility", "never"); | ||
| 12 | user_pref("widget.gtk.overlay-scrollbars.enabled", true); | 12 | user_pref("widget.gtk.overlay-scrollbars.enabled", true); | ||
| 13 | user_pref("browser.tabs.warnOnClose", false); | 13 | user_pref("browser.tabs.warnOnClose", false); | ||
| 14 | + | // Keep browser chrome neutral rather than Firefox's purple dark-theme tint. | |||
| 15 | + | user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true); | |||
A tools/demo/firefox/userChrome.css +18 -0
| @@ -0,0 +1,18 @@ | |||
| 1 | + | /* Vesper chrome: neutral near-black surfaces and one peach selection accent. */ | |
| 2 | + | :root { | |
| 3 | + | --toolbar-bgcolor: #161616 !important; | |
| 4 | + | --toolbar-color: #ffffff !important; | |
| 5 | + | --lwt-accent-color: #101010 !important; | |
| 6 | + | --lwt-text-color: #ffffff !important; | |
| 7 | + | --lwt-selected-tab-background-color: #282828 !important; | |
| 8 | + | --toolbar-field-background-color: #101010 !important; | |
| 9 | + | --toolbar-field-color: #ffffff !important; | |
| 10 | + | --toolbar-field-focus-border-color: #ffc799 !important; | |
| 11 | + | } | |
| 12 | + | #navigator-toolbox, #TabsToolbar, #nav-bar { | |
| 13 | + | background: #161616 !important; | |
| 14 | + | background-image: none !important; | |
| 15 | + | color: #ffffff !important; | |
| 16 | + | } | |
| 17 | + | .tab-background[selected] { background: #282828 !important; outline-color: #ffc799 !important; } | |
| 18 | + | #urlbar-background { background: #101010 !important; border-color: #282828 !important; } | |
| @@ -0,0 +1,18 @@ | |||||
| 1 | + | /* Vesper chrome: neutral near-black surfaces and one peach selection accent. */ | |||
| 2 | + | :root { | |||
| 3 | + | --toolbar-bgcolor: #161616 !important; | |||
| 4 | + | --toolbar-color: #ffffff !important; | |||
| 5 | + | --lwt-accent-color: #101010 !important; | |||
| 6 | + | --lwt-text-color: #ffffff !important; | |||
| 7 | + | --lwt-selected-tab-background-color: #282828 !important; | |||
| 8 | + | --toolbar-field-background-color: #101010 !important; | |||
| 9 | + | --toolbar-field-color: #ffffff !important; | |||
| 10 | + | --toolbar-field-focus-border-color: #ffc799 !important; | |||
| 11 | + | } | |||
| 12 | + | #navigator-toolbox, #TabsToolbar, #nav-bar { | |||
| 13 | + | background: #161616 !important; | |||
| 14 | + | background-image: none !important; | |||
| 15 | + | color: #ffffff !important; | |||
| 16 | + | } | |||
| 17 | + | .tab-background[selected] { background: #282828 !important; outline-color: #ffc799 !important; } | |||
| 18 | + | #urlbar-background { background: #101010 !important; border-color: #282828 !important; } | |||
M tools/demo/foot.ini +19 -18
| @@ -3,21 +3,22 @@ font=JetBrainsMono Nerd Font:size=15 | |||
| 3 | 3 | pad=20x20 | |
| 4 | 4 | ||
| 5 | 5 | [colors-dark] | |
| 6 | - | background=1a1b26 | |
| 7 | - | foreground=c0caf5 | |
| 8 | - | regular0=15161e | |
| 9 | - | regular1=f7768e | |
| 10 | - | regular2=9ece6a | |
| 11 | - | regular3=e0af68 | |
| 12 | - | regular4=7aa2f7 | |
| 13 | - | regular5=bb9af7 | |
| 14 | - | regular6=7dcfff | |
| 15 | - | regular7=a9b1d6 | |
| 16 | - | bright0=414868 | |
| 17 | - | bright1=f7768e | |
| 18 | - | bright2=9ece6a | |
| 19 | - | bright3=e0af68 | |
| 20 | - | bright4=7aa2f7 | |
| 21 | - | bright5=bb9af7 | |
| 22 | - | bright6=7dcfff | |
| 23 | - | bright7=c0caf5 | |
| 6 | + | cursor=101010 ffc799 | |
| 7 | + | background=101010 | |
| 8 | + | foreground=ffffff | |
| 9 | + | regular0=101010 | |
| 10 | + | regular1=f5a191 | |
| 11 | + | regular2=90b99f | |
| 12 | + | regular3=e6b99d | |
| 13 | + | regular4=aca1cf | |
| 14 | + | regular5=e29eca | |
| 15 | + | regular6=ea83a5 | |
| 16 | + | regular7=a0a0a0 | |
| 17 | + | bright0=7e7e7e | |
| 18 | + | bright1=ff8080 | |
| 19 | + | bright2=99ffe4 | |
| 20 | + | bright3=ffc799 | |
| 21 | + | bright4=b9aeda | |
| 22 | + | bright5=ecaad6 | |
| 23 | + | bright6=f591b2 | |
| 24 | + | bright7=ffffff | |
| @@ -3,21 +3,22 @@ font=JetBrainsMono Nerd Font:size=15 | |||||
| 3 | pad=20x20 | 3 | pad=20x20 | ||
| 4 | 4 | ||||
| 5 | [colors-dark] | 5 | [colors-dark] | ||
| 6 | - | background=1a1b26 | 6 | + | cursor=101010 ffc799 |
| 7 | - | foreground=c0caf5 | 7 | + | background=101010 |
| 8 | - | regular0=15161e | 8 | + | foreground=ffffff |
| 9 | - | regular1=f7768e | 9 | + | regular0=101010 |
| 10 | - | regular2=9ece6a | 10 | + | regular1=f5a191 |
| 11 | - | regular3=e0af68 | 11 | + | regular2=90b99f |
| 12 | - | regular4=7aa2f7 | 12 | + | regular3=e6b99d |
| 13 | - | regular5=bb9af7 | 13 | + | regular4=aca1cf |
| 14 | - | regular6=7dcfff | 14 | + | regular5=e29eca |
| 15 | - | regular7=a9b1d6 | 15 | + | regular6=ea83a5 |
| 16 | - | bright0=414868 | 16 | + | regular7=a0a0a0 |
| 17 | - | bright1=f7768e | 17 | + | bright0=7e7e7e |
| 18 | - | bright2=9ece6a | 18 | + | bright1=ff8080 |
| 19 | - | bright3=e0af68 | 19 | + | bright2=99ffe4 |
| 20 | - | bright4=7aa2f7 | 20 | + | bright3=ffc799 |
| 21 | - | bright5=bb9af7 | 21 | + | bright4=b9aeda |
| 22 | - | bright6=7dcfff | 22 | + | bright5=ecaad6 |
| 23 | - | bright7=c0caf5 | 23 | + | bright6=f591b2 |
| 24 | + | bright7=ffffff | |||
M tools/demo/record.sh +60 -31
| @@ -1,68 +1,93 @@ | |||
| 1 | 1 | #!/usr/bin/env bash | |
| 2 | 2 | # Requires Go 1.27, rootless Docker, bash and ffmpeg. Run from any directory. | |
| 3 | + | # KEEP_CAPTURE=1 preserves temporary captures on success; failures always clean up. | |
| 3 | 4 | set -euo pipefail | |
| 4 | 5 | root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) | |
| 5 | 6 | cd "$root" | |
| 7 | + | for command in docker go ffmpeg; do | |
| 8 | + | command -v "$command" >/dev/null || { echo "Missing required command: $command" >&2; exit 1; } | |
| 9 | + | done | |
| 10 | + | docker info >/dev/null 2>&1 || { echo 'Docker daemon unavailable; start rootless Docker first.' >&2; exit 1; } | |
| 6 | 11 | work=$(mktemp -d "${TMPDIR:-/tmp}/neferwl-demo.XXXXXX") | |
| 7 | 12 | printf 'Capture directory: %s\n' "$work" | |
| 8 | 13 | mkdir -p "$work/shots" docs/media | |
| 9 | - | compositor='' launcher='' container_started='' | |
| 14 | + | compositor='' launcher='' feeder='' success='' | |
| 15 | + | container=neferwl-demo-$$ | |
| 10 | 16 | cleanup() { | |
| 11 | - | if [[ -n $launcher ]]; then kill "$launcher" 2>/dev/null || true; fi | |
| 12 | - | if [[ -n $container_started ]]; then docker rm -f neferwl-demo >/dev/null 2>&1 || true; fi | |
| 13 | - | if [[ -n $compositor ]]; then kill "$compositor" 2>/dev/null || true; fi | |
| 17 | + | local status=$? | |
| 18 | + | trap - EXIT | |
| 19 | + | for pid in "$launcher" "$feeder" "$compositor"; do | |
| 20 | + | if [[ -n $pid ]]; then kill "$pid" 2>/dev/null || true; fi | |
| 21 | + | done | |
| 22 | + | docker rm -f "$container" >/dev/null 2>&1 || true | |
| 23 | + | for pid in "$launcher" "$feeder" "$compositor"; do | |
| 24 | + | if [[ -n $pid ]]; then wait "$pid" 2>/dev/null || true; fi | |
| 25 | + | done | |
| 26 | + | exec 3>&- | |
| 27 | + | if [[ $success != yes || ${KEEP_CAPTURE:-0} != 1 ]]; then | |
| 28 | + | rm -rf -- "$work" | |
| 29 | + | fi | |
| 30 | + | exit "$status" | |
| 14 | 31 | } | |
| 15 | 32 | trap cleanup EXIT | |
| 16 | 33 | trap 'exit 130' INT | |
| 17 | 34 | trap 'exit 143' TERM | |
| 18 | - | # Refuse to replace someone else's running container. | |
| 19 | - | if [[ -n $(docker ps -aq --filter 'name=^/neferwl-demo$') ]]; then | |
| 20 | - | echo 'Container neferwl-demo already exists; remove it before recording.' >&2 | |
| 21 | - | exit 1 | |
| 22 | - | fi | |
| 23 | 35 | docker build -t neferwl-demo -f tools/demo/Dockerfile tools/demo | |
| 24 | 36 | CGO_ENABLED=0 go build -o "$work/neferwl" ./cmd/neferwl | |
| 25 | - | log=${XDG_STATE_HOME:-$HOME/.local/state}/neferwl/runs/headless/latest.log | |
| 26 | - | env -u NEFERWL_STATE -u NEFERWL_SLOT -u WAYLAND_DISPLAY "$work/neferwl" \ | |
| 37 | + | state=$work/state | |
| 38 | + | log=$state/neferwl/runs/headless/latest.log | |
| 39 | + | mkfifo "$work/input" | |
| 40 | + | # Hold both ends open so the FIFO cannot block compositor startup or produce | |
| 41 | + | # premature EOF. No input is fed until Docker and the first app are ready. | |
| 42 | + | exec 3<>"$work/input" | |
| 43 | + | env -u NEFERWL_STATE -u NEFERWL_SLOT -u WAYLAND_DISPLAY XDG_STATE_HOME="$state" "$work/neferwl" \ | |
| 27 | 44 | --backend=headless --size 1920x1080 --config tools/demo/config \ | |
| 28 | - | --no-xwayland --screenshot "$work/shots" --input tools/demo/input.txt --timeout 45s & | |
| 45 | + | --no-xwayland --screenshot "$work/shots" --input - --timeout 75s <"$work/input" & | |
| 29 | 46 | compositor=$! | |
| 30 | - | SECONDS=0 | |
| 31 | - | # The host starts only the compositor; all apps and their content stay in Docker. | |
| 32 | - | # A fixed startup allowance avoids reading a previous run's socket from the log. | |
| 33 | - | sleep 1 | |
| 34 | - | kill -0 "$compositor" | |
| 35 | - | display=$(sed -n '/"message":"listening"/s/.*"WAYLAND_DISPLAY":"\([^"]*\)".*/\1/p' "$log" | tail -1) | |
| 36 | - | [[ -n $display ]] || { echo "No listening socket in $log" >&2; exit 1; } | |
| 37 | - | socket=$XDG_RUNTIME_DIR/${display##*/} | |
| 38 | - | [[ -S $socket ]] || { echo "Not a socket: $socket" >&2; exit 1; } | |
| 47 | + | socket='' | |
| 48 | + | for ((attempt = 0; attempt < 150; attempt++)); do | |
| 49 | + | kill -0 "$compositor" 2>/dev/null || { echo "Compositor exited before readiness; see $log" >&2; exit 1; } | |
| 50 | + | if [[ -f $log ]]; then | |
| 51 | + | display=$(sed -n '/"message":"listening"/s/.*"WAYLAND_DISPLAY":"\([^" ]*\)".*/\1/p' "$log" | tail -1) | |
| 52 | + | if [[ -n $display && -S $XDG_RUNTIME_DIR/${display##*/} ]]; then | |
| 53 | + | socket=$XDG_RUNTIME_DIR/${display##*/} | |
| 54 | + | break | |
| 55 | + | fi | |
| 56 | + | fi | |
| 57 | + | sleep 0.1 | |
| 58 | + | done | |
| 59 | + | [[ -n $socket ]] || { echo "Timed out after 15s waiting for compositor socket; see $log" >&2; exit 1; } | |
| 39 | 60 | # Container root maps to the host uid under rootless Docker. Grant demo access | |
| 40 | 61 | # to this disposable socket only; Firefox rejects root with a demo-owned HOME. | |
| 41 | - | docker run -d --name neferwl-demo --hostname neferwl-demo --network none \ | |
| 62 | + | docker run -d --name "$container" --hostname neferwl-demo --network none \ | |
| 42 | 63 | -v "$socket:/run/wl/wayland-0" neferwl-demo | |
| 43 | - | container_started=yes | |
| 44 | - | docker exec neferwl-demo chmod 666 /run/wl/wayland-0 | |
| 64 | + | docker exec "$container" chmod 666 /run/wl/wayland-0 | |
| 45 | 65 | at() { local delay=$(($1 - SECONDS)); if ((delay > 0)); then sleep "$delay"; fi; } | |
| 46 | 66 | launch_apps() { | |
| 47 | 67 | # This shell timeline shares t=0 with input.txt. Only keys live in input.txt; | |
| 48 | 68 | # Docker launches cannot be invoked by compositor input scripts. | |
| 49 | - | docker exec -d --user demo neferwl-demo foot --title 'System / NeferWL' bash -c \ | |
| 50 | - | 'fastfetch --logo small --structure OS:Kernel:WM:Terminal:Memory:Colors; eza -la --no-user --icons --color=always /etc | head -24; exec sleep infinity' | |
| 51 | 69 | at 3 | |
| 52 | - | docker exec -d --user demo neferwl-demo dbus-run-session -- firefox --no-remote \ | |
| 70 | + | docker exec -d --user demo "$container" dbus-run-session -- firefox --no-remote \ | |
| 53 | 71 | --profile /home/demo/firefox file:///home/demo/site/index.html | |
| 54 | 72 | # btop's first per-core sample is bogus in this container. Let it collect | |
| 55 | 73 | # real deltas on workspace 2; input.txt brings it into view at t=17. | |
| 56 | 74 | at 5 | |
| 57 | - | docker exec -d --user demo neferwl-demo foot --app-id demo-monitor \ | |
| 75 | + | docker exec -d --user demo "$container" foot --app-id demo-monitor \ | |
| 58 | 76 | --title 'Monitor / btop' /usr/local/bin/btop | |
| 59 | 77 | at 9 | |
| 60 | - | docker exec -d --user demo neferwl-demo dbus-run-session -- bash -c \ | |
| 78 | + | docker exec -d --user demo "$container" dbus-run-session -- bash -c \ | |
| 61 | 79 | 'gsettings set org.xfce.mousepad.preferences.view use-default-monospace-font false; gsettings set org.xfce.mousepad.preferences.view font-name "JetBrainsMono Nerd Font 15"; gsettings set org.xfce.mousepad.preferences.window menubar-visible false; mousepad /home/demo/config' | |
| 62 | 80 | at 30 | |
| 63 | - | docker exec -d --user demo neferwl-demo foot --title 'Fonts / workspace 2' bash -c \ | |
| 81 | + | docker exec -d --user demo "$container" foot --title 'Fonts / workspace 2' bash -c \ | |
| 64 | 82 | 'eza --tree --level=2 --icons --color=always /usr/share/fonts | head -30; exec sleep infinity' | |
| 65 | 83 | } | |
| 84 | + | docker exec -d --user demo "$container" foot --title 'System / NeferWL' bash -c \ | |
| 85 | + | 'fastfetch --logo small --structure OS:Kernel:WM:Terminal:Memory:Colors; eza -la --no-user --icons --color=always /etc | head -24; exec sleep infinity' | |
| 86 | + | # The first app is launched before t=0. FIFO input and shell launches now | |
| 87 | + | # share this epoch, independent of compositor/image/container startup time. | |
| 88 | + | SECONDS=0 | |
| 89 | + | { cat tools/demo/input.txt; printf '\nkey Ctrl+Alt+BackSpace\n'; } >&3 & | |
| 90 | + | feeder=$! | |
| 66 | 91 | launch_apps & | |
| 67 | 92 | launcher=$! | |
| 68 | 93 | # --timeout exits nonzero; accept only its explicit timeout log entry. | |
| @@ -70,6 +95,8 @@ if ! wait "$compositor"; then | |||
| 70 | 95 | grep -q '"reason":"timeout"' "$log" || exit 1 | |
| 71 | 96 | fi | |
| 72 | 97 | compositor= | |
| 98 | + | wait "$feeder" | |
| 99 | + | feeder= | |
| 73 | 100 | wait "$launcher" | |
| 74 | 101 | launcher= | |
| 75 | 102 | ffmpeg -hide_banner -loglevel warning -f concat -safe 0 \ | |
| @@ -77,4 +104,6 @@ ffmpeg -hide_banner -loglevel warning -f concat -safe 0 \ | |||
| 77 | 104 | -c:v libx264 -crf 23 -movflags +faststart docs/media/demo.mp4 -y | |
| 78 | 105 | ffmpeg -hide_banner -loglevel warning -ss 7 -i docs/media/demo.mp4 \ | |
| 79 | 106 | -frames:v 1 -update 1 docs/media/demo.png -y | |
| 80 | - | printf 'Recorded: %s\n' "$work" | |
| 107 | + | success=yes | |
| 108 | + | if [[ ${KEEP_CAPTURE:-0} == 1 ]]; then printf 'Captures kept: %s\n' "$work"; fi | |
| 109 | + | printf 'Recorded: docs/media/demo.mp4 and docs/media/demo.png\n' | |
| @@ -1,68 +1,93 @@ | |||||
| 1 | #!/usr/bin/env bash | 1 | #!/usr/bin/env bash | ||
| 2 | # Requires Go 1.27, rootless Docker, bash and ffmpeg. Run from any directory. | 2 | # Requires Go 1.27, rootless Docker, bash and ffmpeg. Run from any directory. | ||
| 3 | + | # KEEP_CAPTURE=1 preserves temporary captures on success; failures always clean up. | |||
| 3 | set -euo pipefail | 4 | set -euo pipefail | ||
| 4 | root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) | 5 | root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) | ||
| 5 | cd "$root" | 6 | cd "$root" | ||
| 7 | + | for command in docker go ffmpeg; do | |||
| 8 | + | command -v "$command" >/dev/null || { echo "Missing required command: $command" >&2; exit 1; } | |||
| 9 | + | done | |||
| 10 | + | docker info >/dev/null 2>&1 || { echo 'Docker daemon unavailable; start rootless Docker first.' >&2; exit 1; } | |||
| 6 | work=$(mktemp -d "${TMPDIR:-/tmp}/neferwl-demo.XXXXXX") | 11 | work=$(mktemp -d "${TMPDIR:-/tmp}/neferwl-demo.XXXXXX") | ||
| 7 | printf 'Capture directory: %s\n' "$work" | 12 | printf 'Capture directory: %s\n' "$work" | ||
| 8 | mkdir -p "$work/shots" docs/media | 13 | mkdir -p "$work/shots" docs/media | ||
| 9 | - | compositor='' launcher='' container_started='' | 14 | + | compositor='' launcher='' feeder='' success='' |
| 15 | + | container=neferwl-demo-$$ | |||
| 10 | cleanup() { | 16 | cleanup() { | ||
| 11 | - | if [[ -n $launcher ]]; then kill "$launcher" 2>/dev/null || true; fi | 17 | + | local status=$? |
| 12 | - | if [[ -n $container_started ]]; then docker rm -f neferwl-demo >/dev/null 2>&1 || true; fi | 18 | + | trap - EXIT |
| 13 | - | if [[ -n $compositor ]]; then kill "$compositor" 2>/dev/null || true; fi | 19 | + | for pid in "$launcher" "$feeder" "$compositor"; do |
| 20 | + | if [[ -n $pid ]]; then kill "$pid" 2>/dev/null || true; fi | |||
| 21 | + | done | |||
| 22 | + | docker rm -f "$container" >/dev/null 2>&1 || true | |||
| 23 | + | for pid in "$launcher" "$feeder" "$compositor"; do | |||
| 24 | + | if [[ -n $pid ]]; then wait "$pid" 2>/dev/null || true; fi | |||
| 25 | + | done | |||
| 26 | + | exec 3>&- | |||
| 27 | + | if [[ $success != yes || ${KEEP_CAPTURE:-0} != 1 ]]; then | |||
| 28 | + | rm -rf -- "$work" | |||
| 29 | + | fi | |||
| 30 | + | exit "$status" | |||
| 14 | } | 31 | } | ||
| 15 | trap cleanup EXIT | 32 | trap cleanup EXIT | ||
| 16 | trap 'exit 130' INT | 33 | trap 'exit 130' INT | ||
| 17 | trap 'exit 143' TERM | 34 | trap 'exit 143' TERM | ||
| 18 | - | # Refuse to replace someone else's running container. | |||
| 19 | - | if [[ -n $(docker ps -aq --filter 'name=^/neferwl-demo$') ]]; then | |||
| 20 | - | echo 'Container neferwl-demo already exists; remove it before recording.' >&2 | |||
| 21 | - | exit 1 | |||
| 22 | - | fi | |||
| 23 | docker build -t neferwl-demo -f tools/demo/Dockerfile tools/demo | 35 | docker build -t neferwl-demo -f tools/demo/Dockerfile tools/demo | ||
| 24 | CGO_ENABLED=0 go build -o "$work/neferwl" ./cmd/neferwl | 36 | CGO_ENABLED=0 go build -o "$work/neferwl" ./cmd/neferwl | ||
| 25 | - | log=${XDG_STATE_HOME:-$HOME/.local/state}/neferwl/runs/headless/latest.log | 37 | + | state=$work/state |
| 26 | - | env -u NEFERWL_STATE -u NEFERWL_SLOT -u WAYLAND_DISPLAY "$work/neferwl" \ | 38 | + | log=$state/neferwl/runs/headless/latest.log |
| 39 | + | mkfifo "$work/input" | |||
| 40 | + | # Hold both ends open so the FIFO cannot block compositor startup or produce | |||
| 41 | + | # premature EOF. No input is fed until Docker and the first app are ready. | |||
| 42 | + | exec 3<>"$work/input" | |||
| 43 | + | env -u NEFERWL_STATE -u NEFERWL_SLOT -u WAYLAND_DISPLAY XDG_STATE_HOME="$state" "$work/neferwl" \ | |||
| 27 | --backend=headless --size 1920x1080 --config tools/demo/config \ | 44 | --backend=headless --size 1920x1080 --config tools/demo/config \ | ||
| 28 | - | --no-xwayland --screenshot "$work/shots" --input tools/demo/input.txt --timeout 45s & | 45 | + | --no-xwayland --screenshot "$work/shots" --input - --timeout 75s <"$work/input" & |
| 29 | compositor=$! | 46 | compositor=$! | ||
| 30 | - | SECONDS=0 | 47 | + | socket='' |
| 31 | - | # The host starts only the compositor; all apps and their content stay in Docker. | 48 | + | for ((attempt = 0; attempt < 150; attempt++)); do |
| 32 | - | # A fixed startup allowance avoids reading a previous run's socket from the log. | 49 | + | kill -0 "$compositor" 2>/dev/null || { echo "Compositor exited before readiness; see $log" >&2; exit 1; } |
| 33 | - | sleep 1 | 50 | + | if [[ -f $log ]]; then |
| 34 | - | kill -0 "$compositor" | 51 | + | display=$(sed -n '/"message":"listening"/s/.*"WAYLAND_DISPLAY":"\([^" ]*\)".*/\1/p' "$log" | tail -1) |
| 35 | - | display=$(sed -n '/"message":"listening"/s/.*"WAYLAND_DISPLAY":"\([^"]*\)".*/\1/p' "$log" | tail -1) | 52 | + | if [[ -n $display && -S $XDG_RUNTIME_DIR/${display##*/} ]]; then |
| 36 | - | [[ -n $display ]] || { echo "No listening socket in $log" >&2; exit 1; } | 53 | + | socket=$XDG_RUNTIME_DIR/${display##*/} |
| 37 | - | socket=$XDG_RUNTIME_DIR/${display##*/} | 54 | + | break |
| 38 | - | [[ -S $socket ]] || { echo "Not a socket: $socket" >&2; exit 1; } | 55 | + | fi |
| 56 | + | fi | |||
| 57 | + | sleep 0.1 | |||
| 58 | + | done | |||
| 59 | + | [[ -n $socket ]] || { echo "Timed out after 15s waiting for compositor socket; see $log" >&2; exit 1; } | |||
| 39 | # Container root maps to the host uid under rootless Docker. Grant demo access | 60 | # Container root maps to the host uid under rootless Docker. Grant demo access | ||
| 40 | # to this disposable socket only; Firefox rejects root with a demo-owned HOME. | 61 | # to this disposable socket only; Firefox rejects root with a demo-owned HOME. | ||
| 41 | - | docker run -d --name neferwl-demo --hostname neferwl-demo --network none \ | 62 | + | docker run -d --name "$container" --hostname neferwl-demo --network none \ |
| 42 | -v "$socket:/run/wl/wayland-0" neferwl-demo | 63 | -v "$socket:/run/wl/wayland-0" neferwl-demo | ||
| 43 | - | container_started=yes | 64 | + | docker exec "$container" chmod 666 /run/wl/wayland-0 |
| 44 | - | docker exec neferwl-demo chmod 666 /run/wl/wayland-0 | |||
| 45 | at() { local delay=$(($1 - SECONDS)); if ((delay > 0)); then sleep "$delay"; fi; } | 65 | at() { local delay=$(($1 - SECONDS)); if ((delay > 0)); then sleep "$delay"; fi; } | ||
| 46 | launch_apps() { | 66 | launch_apps() { | ||
| 47 | # This shell timeline shares t=0 with input.txt. Only keys live in input.txt; | 67 | # This shell timeline shares t=0 with input.txt. Only keys live in input.txt; | ||
| 48 | # Docker launches cannot be invoked by compositor input scripts. | 68 | # Docker launches cannot be invoked by compositor input scripts. | ||
| 49 | - | docker exec -d --user demo neferwl-demo foot --title 'System / NeferWL' bash -c \ | |||
| 50 | - | 'fastfetch --logo small --structure OS:Kernel:WM:Terminal:Memory:Colors; eza -la --no-user --icons --color=always /etc | head -24; exec sleep infinity' | |||
| 51 | at 3 | 69 | at 3 | ||
| 52 | - | docker exec -d --user demo neferwl-demo dbus-run-session -- firefox --no-remote \ | 70 | + | docker exec -d --user demo "$container" dbus-run-session -- firefox --no-remote \ |
| 53 | --profile /home/demo/firefox file:///home/demo/site/index.html | 71 | --profile /home/demo/firefox file:///home/demo/site/index.html | ||
| 54 | # btop's first per-core sample is bogus in this container. Let it collect | 72 | # btop's first per-core sample is bogus in this container. Let it collect | ||
| 55 | # real deltas on workspace 2; input.txt brings it into view at t=17. | 73 | # real deltas on workspace 2; input.txt brings it into view at t=17. | ||
| 56 | at 5 | 74 | at 5 | ||
| 57 | - | docker exec -d --user demo neferwl-demo foot --app-id demo-monitor \ | 75 | + | docker exec -d --user demo "$container" foot --app-id demo-monitor \ |
| 58 | --title 'Monitor / btop' /usr/local/bin/btop | 76 | --title 'Monitor / btop' /usr/local/bin/btop | ||
| 59 | at 9 | 77 | at 9 | ||
| 60 | - | docker exec -d --user demo neferwl-demo dbus-run-session -- bash -c \ | 78 | + | docker exec -d --user demo "$container" dbus-run-session -- bash -c \ |
| 61 | 'gsettings set org.xfce.mousepad.preferences.view use-default-monospace-font false; gsettings set org.xfce.mousepad.preferences.view font-name "JetBrainsMono Nerd Font 15"; gsettings set org.xfce.mousepad.preferences.window menubar-visible false; mousepad /home/demo/config' | 79 | 'gsettings set org.xfce.mousepad.preferences.view use-default-monospace-font false; gsettings set org.xfce.mousepad.preferences.view font-name "JetBrainsMono Nerd Font 15"; gsettings set org.xfce.mousepad.preferences.window menubar-visible false; mousepad /home/demo/config' | ||
| 62 | at 30 | 80 | at 30 | ||
| 63 | - | docker exec -d --user demo neferwl-demo foot --title 'Fonts / workspace 2' bash -c \ | 81 | + | docker exec -d --user demo "$container" foot --title 'Fonts / workspace 2' bash -c \ |
| 64 | 'eza --tree --level=2 --icons --color=always /usr/share/fonts | head -30; exec sleep infinity' | 82 | 'eza --tree --level=2 --icons --color=always /usr/share/fonts | head -30; exec sleep infinity' | ||
| 65 | } | 83 | } | ||
| 84 | + | docker exec -d --user demo "$container" foot --title 'System / NeferWL' bash -c \ | |||
| 85 | + | 'fastfetch --logo small --structure OS:Kernel:WM:Terminal:Memory:Colors; eza -la --no-user --icons --color=always /etc | head -24; exec sleep infinity' | |||
| 86 | + | # The first app is launched before t=0. FIFO input and shell launches now | |||
| 87 | + | # share this epoch, independent of compositor/image/container startup time. | |||
| 88 | + | SECONDS=0 | |||
| 89 | + | { cat tools/demo/input.txt; printf '\nkey Ctrl+Alt+BackSpace\n'; } >&3 & | |||
| 90 | + | feeder=$! | |||
| 66 | launch_apps & | 91 | launch_apps & | ||
| 67 | launcher=$! | 92 | launcher=$! | ||
| 68 | # --timeout exits nonzero; accept only its explicit timeout log entry. | 93 | # --timeout exits nonzero; accept only its explicit timeout log entry. | ||
| @@ -70,6 +95,8 @@ if ! wait "$compositor"; then | |||||
| 70 | grep -q '"reason":"timeout"' "$log" || exit 1 | 95 | grep -q '"reason":"timeout"' "$log" || exit 1 | ||
| 71 | fi | 96 | fi | ||
| 72 | compositor= | 97 | compositor= | ||
| 98 | + | wait "$feeder" | |||
| 99 | + | feeder= | |||
| 73 | wait "$launcher" | 100 | wait "$launcher" | ||
| 74 | launcher= | 101 | launcher= | ||
| 75 | ffmpeg -hide_banner -loglevel warning -f concat -safe 0 \ | 102 | ffmpeg -hide_banner -loglevel warning -f concat -safe 0 \ | ||
| @@ -77,4 +104,6 @@ ffmpeg -hide_banner -loglevel warning -f concat -safe 0 \ | |||||
| 77 | -c:v libx264 -crf 23 -movflags +faststart docs/media/demo.mp4 -y | 104 | -c:v libx264 -crf 23 -movflags +faststart docs/media/demo.mp4 -y | ||
| 78 | ffmpeg -hide_banner -loglevel warning -ss 7 -i docs/media/demo.mp4 \ | 105 | ffmpeg -hide_banner -loglevel warning -ss 7 -i docs/media/demo.mp4 \ | ||
| 79 | -frames:v 1 -update 1 docs/media/demo.png -y | 106 | -frames:v 1 -update 1 docs/media/demo.png -y | ||
| 80 | - | printf 'Recorded: %s\n' "$work" | 107 | + | success=yes |
| 108 | + | if [[ ${KEEP_CAPTURE:-0} == 1 ]]; then printf 'Captures kept: %s\n' "$work"; fi | |||
| 109 | + | printf 'Recorded: docs/media/demo.mp4 and docs/media/demo.png\n' | |||
M tools/demo/site/index.html +1 -1
| @@ -4,7 +4,7 @@ | |||
| 4 | 4 | <meta name="color-scheme" content="dark"> | |
| 5 | 5 | <title>NeferWL — Space to think</title> | |
| 6 | 6 | <style> | |
| 7 | - | *{box-sizing:border-box}body{margin:0;background:#1a1b26;color:#c0caf5;font:18px/1.6 sans-serif}main{max-width:960px;margin:auto;padding:60px 38px}header{color:#7aa2f7;letter-spacing:.2em;font:700 18px monospace}small{color:#9ece6a}h1{font-size:54px;line-height:1.12;letter-spacing:-2px;margin:40px 0 24px}p{color:#a9b1d6}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:36px 0}.card{padding:18px;background:#24283b;border:1px solid #414868;border-radius:12px}.card b{color:#7dcfff}.card p{font-size:15px;margin-bottom:0}pre{background:#16161e;border-left:3px solid #7aa2f7;border-radius:6px;padding:24px;font:16px/1.8 monospace;color:#9ece6a}footer{color:#737aa2;font:14px monospace} | |
| 7 | + | *{box-sizing:border-box}body{margin:0;background:#101010;color:#ffffff;font:18px/1.6 sans-serif}main{max-width:960px;margin:auto;padding:60px 38px}header{color:#ffc799;letter-spacing:.2em;font:700 18px monospace}small{color:#ffc799}h1{font-size:54px;line-height:1.12;letter-spacing:-2px;margin:40px 0 24px}p{color:#a0a0a0}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:36px 0}.card{padding:18px;background:#161616;border:1px solid #282828;border-radius:12px}.card b{color:#ffc799}.card p{font-size:15px;margin-bottom:0}pre{background:#161616;border-left:3px solid #ffc799;border-radius:6px;padding:24px;font:16px/1.8 monospace;color:#ffc799}footer{color:#a0a0a0;font:14px monospace} | |
| 8 | 8 | </style> | |
| 9 | 9 | <main><header>◈ NEFERWL</header><h1>Your desktop.<br>A little more focused.</h1><p>A Wayland compositor with room for your work.<br>Tile naturally, set things aside, see the whole picture.</p><div class="cards"><section class="card"><b>01 / Performance</b><p>Small, responsive.<br>Built in Go. Made to stay out of your way.</p></section><section class="card"><b>02 / Stash</b><p>Keep tools close.<br>Set aside a window. Bring it back in a keystroke.</p></section><section class="card"><b>03 / Overview</b><p>Find your focus.<br>Every workspace and stashed window, at a glance.</p></section></div><pre># A desktop that flows with you | |
| 10 | 10 | layout.max-columns = 2 | |
| @@ -4,7 +4,7 @@ | |||||
| 4 | <meta name="color-scheme" content="dark"> | 4 | <meta name="color-scheme" content="dark"> | ||
| 5 | <title>NeferWL — Space to think</title> | 5 | <title>NeferWL — Space to think</title> | ||
| 6 | <style> | 6 | <style> | ||
| 7 | - | *{box-sizing:border-box}body{margin:0;background:#1a1b26;color:#c0caf5;font:18px/1.6 sans-serif}main{max-width:960px;margin:auto;padding:60px 38px}header{color:#7aa2f7;letter-spacing:.2em;font:700 18px monospace}small{color:#9ece6a}h1{font-size:54px;line-height:1.12;letter-spacing:-2px;margin:40px 0 24px}p{color:#a9b1d6}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:36px 0}.card{padding:18px;background:#24283b;border:1px solid #414868;border-radius:12px}.card b{color:#7dcfff}.card p{font-size:15px;margin-bottom:0}pre{background:#16161e;border-left:3px solid #7aa2f7;border-radius:6px;padding:24px;font:16px/1.8 monospace;color:#9ece6a}footer{color:#737aa2;font:14px monospace} | 7 | + | *{box-sizing:border-box}body{margin:0;background:#101010;color:#ffffff;font:18px/1.6 sans-serif}main{max-width:960px;margin:auto;padding:60px 38px}header{color:#ffc799;letter-spacing:.2em;font:700 18px monospace}small{color:#ffc799}h1{font-size:54px;line-height:1.12;letter-spacing:-2px;margin:40px 0 24px}p{color:#a0a0a0}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:36px 0}.card{padding:18px;background:#161616;border:1px solid #282828;border-radius:12px}.card b{color:#ffc799}.card p{font-size:15px;margin-bottom:0}pre{background:#161616;border-left:3px solid #ffc799;border-radius:6px;padding:24px;font:16px/1.8 monospace;color:#ffc799}footer{color:#a0a0a0;font:14px monospace} |
| 8 | </style> | 8 | </style> | ||
| 9 | <main><header>◈ NEFERWL</header><h1>Your desktop.<br>A little more focused.</h1><p>A Wayland compositor with room for your work.<br>Tile naturally, set things aside, see the whole picture.</p><div class="cards"><section class="card"><b>01 / Performance</b><p>Small, responsive.<br>Built in Go. Made to stay out of your way.</p></section><section class="card"><b>02 / Stash</b><p>Keep tools close.<br>Set aside a window. Bring it back in a keystroke.</p></section><section class="card"><b>03 / Overview</b><p>Find your focus.<br>Every workspace and stashed window, at a glance.</p></section></div><pre># A desktop that flows with you | 9 | <main><header>◈ NEFERWL</header><h1>Your desktop.<br>A little more focused.</h1><p>A Wayland compositor with room for your work.<br>Tile naturally, set things aside, see the whole picture.</p><div class="cards"><section class="card"><b>01 / Performance</b><p>Small, responsive.<br>Built in Go. Made to stay out of your way.</p></section><section class="card"><b>02 / Stash</b><p>Keep tools close.<br>Set aside a window. Bring it back in a keystroke.</p></section><section class="card"><b>03 / Overview</b><p>Find your focus.<br>Every workspace and stashed window, at a glance.</p></section></div><pre># A desktop that flows with you | ||
| 10 | layout.max-columns = 2 | 10 | layout.max-columns = 2 | ||