Author SHA1 Message Date
kua-agent 2085dad3b9 chore: version 2026.8.2 so the fixed build can actually reach installed machines 2026-08-28 07:15:12 +00:00
kua-agent b6f046abdd Merge pull request 'fix: no black area around the window over a remote desktop' (#7) from fix/mmd-pdf-remote-desktop-black-area-20260828 into main 2026-08-28 06:56:02 +00:00
kua-agent 9dcd9055d9 fix: draw in software over remote desktop so the window has no black area 2026-08-28 06:56:00 +00:00
kua-agent cc579d794e Merge pull request 'fix: remove the last upstream wordmark from dialogs, About and every language file' (#6) from fix/mmd-pdf-remove-killer-wordmark-20260828 into main 2026-08-28 06:40:16 +00:00
kua-agent 19d9e906b7 fix: MMD PDF wordmark in Strings/zh-TW.xaml 2026-08-28 06:40:04 +00:00
kua-agent a9b8f314a7 fix: MMD PDF wordmark in Strings/zh-CN.xaml 2026-08-28 06:40:02 +00:00
kua-agent a26a4a7f46 fix: MMD PDF wordmark in Strings/tr-TR.xaml 2026-08-28 06:40:00 +00:00
kua-agent 221a7962ea fix: MMD PDF wordmark in Strings/pl-PL.xaml 2026-08-28 06:39:58 +00:00
kua-agent ac092ccccc fix: MMD PDF wordmark in Strings/ja-JP.xaml 2026-08-28 06:39:55 +00:00
kua-agent 031c386191 fix: MMD PDF wordmark in Strings/hu-HU.xaml 2026-08-28 06:39:53 +00:00
kua-agent 0abfa700fe fix: MMD PDF wordmark in Strings/fr-FR.xaml 2026-08-28 06:39:51 +00:00
kua-agent b29e087029 fix: MMD PDF wordmark in Strings/es.xaml 2026-08-28 06:39:49 +00:00
kua-agent 9287248214 fix: MMD PDF wordmark in Strings/en-US.xaml 2026-08-28 06:39:47 +00:00
kua-agent 1527e9fd7a fix: MMD PDF wordmark in Strings/de-DE.xaml 2026-08-28 06:39:44 +00:00
kua-agent b789881134 fix: MMD PDF wordmark in Strings/cs-CZ.xaml 2026-08-28 06:39:42 +00:00
kua-agent 2090f57008 fix: MMD PDF wordmark in Strings/bn.xaml 2026-08-28 06:39:40 +00:00
kua-agent b4bb2544f6 fix: MMD PDF wordmark in Shell/About.cs 2026-08-28 06:39:37 +00:00
kua-agent edc2a3f050 fix: MMD PDF wordmark in Controls/KillerDialog.cs 2026-08-28 06:39:36 +00:00
kua-agent 0d8c86eccd Merge pull request 'fix: remove the last KillerPDF wordmark and put MMD PDF in the title bar' (#5) from fix/mmd-pdf-wordmark-titlebar-20260828 into main 2026-08-28 06:27:09 +00:00
kua-agent 19791f1aca fix: show the MMD PDF wordmark instead of the upstream one in MainWindow.xaml 2026-08-28 06:26:59 +00:00
kua-agent 1ed999f3c8 fix: show the MMD PDF wordmark instead of the upstream one in Controls/DialogChrome.cs 2026-08-28 06:26:56 +00:00
kua-agent 31447b8d80 Stop shipping the upstream manual, move Scan to the toolbar, MMD version (#4) 2026-08-28 04:36:14 +00:00
kua-agentandClaude Opus 5 87649cd77f docs: record the 2026-08-28 round and what Ben found
Sign-off log gains the design-system rebuild, the scan work, signing from
mmd-fsn-01 and the Intune publish, then the three defects Ben found by looking
at the running application and what was done about each.

Backlog: PDF-07, PDF-08 and PDF-09 go green (built, run and looked at on the
test machine at 800x600), PDF-17 and PDF-18 go green (signed, published), and
PDF-14 goes amber with the reason stated plainly - the copier conversation and
the dialog are proven, the page transfer has never met real hardware.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 04:36:06 +00:00
kua-agentandClaude Opus 5 a351e8e9a0 fix: stop shipping the upstream manual, move Scan to the toolbar, MMD version
Three things Ben saw on the running application that all read as "this is still
the tool it was forked from".

1. The upstream 50-page manual was sitting in the repository root as MmdPdf.pdf,
   so it was the document on screen whenever it was opened or restored from the
   last session - a full-page green upstream logo on black. It was never shipped
   in the payload, but it was there to be opened, which is the same thing to
   whoever is looking at the screen. Removed, and added to .gitignore so it does
   not come back with a source drop. With no document, the application opens on
   its own empty view.

2. Scan was in the title bar beside minimise, maximise and close. That is window
   chrome, not a tool. It is a toolbar button now, next to Print in the file
   group, with a caption in every label mode like every other tool. The band
   button style it used has no other user and is gone with it.

3. The footer read v1.7.5, which is the upstream project's version. MMD PDF is
   2026.8.1. Not 1.0: the pilot machines already carry a build numbered 1.7.5,
   and an upgrade has to sort above what it replaces or Intune would read it as
   a downgrade. The Intune detection rule moves with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 04:24:59 +00:00
kua-agent 29850c364a One merged MMD band, Scan in the main bar, shorter icon row, signed and Intune-ready (#3) 2026-08-28 03:26:28 +00:00
kua-agentandClaude Opus 5 39130f41e6 feat: one merged MMD band, Scan in the main bar, shorter icon row, signed build
Ben, 2026-08-28: four changes, then build it, sign it, and make it deployable
through Intune without alerts.

1. The row of icons is a fifth shorter. Every toolbar label mode comes down
   together so the proportions between them are unchanged: 34 -> 27 for a
   caption beside the icon, 52/56 -> 42/45 for a caption under it, with the
   padding reduced to match. The row measures 48px on screen where it was 60.

2. Scan sits in the main bar. It is a solid white button on the band, to the
   left of the window buttons, so a person can pull pages off a copier whatever
   the toolbar is showing. Behind it, Services/ScanService.cs talks to the
   copier through WIA - the imaging service that ships with Windows, reached
   late-bound so a machine without it fails at a check instead of at load - and
   Features/Scan/ScanDialog picks the copier, the feed, the colour, the detail
   and the paper. Pages come back as images and take the same route into a
   document as an imported photograph. "Make the words searchable" runs the
   text recognition the application already carries.

3. The official MMD lockup, in white, because the band is red now.

4. The MMD design system locked on 2026-08-28: ONE merged top bar carrying the
   mark, the name, the tabs and the tools; 4px corners; 24px window buttons;
   the bevel on title bars only. Colours are NOT adjusted - the Red Plate
   silver and ink set already in Themes/MMD.xaml is untouched.

Also fixed, both found by looking at the running application at 800x600:

  - The default 1000x700 window does not fit an 800x600 plant screen, and a
    window taller than the screen puts its own title bar - and therefore Scan,
    the window buttons and the tabs - above the top edge where nobody can reach
    them. MainWindow now starts maximised when the screen cannot hold the
    default.
  - The close button was invisible: CaptionCloseBrush defaults to the danger
    red, which on a red band is red on red. The window buttons are white now,
    and close inverts to a white plate with a red cross on hover.

Built and run on mmd-win-test-01 at 800x600 through keyboard and mouse only.
Signed with the internal MMD code-signing certificate and timestamped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 03:25:42 +00:00
kua-agentandClaude Opus 5 e02d219736 docs: product documentation, design working files, and the failed design review
Adds the required doc set under docs/mmd-pdf/ - PRD, feature backlog with
status and sign-off per feature, screens with wireframes, test cases with the
test data and the agent-tasking prompts, and the sign-off log.

The sign-off log records the independent design review of 2026-08-27:
DESIGN-FAIL, three blockers and five major findings, which is why every design
feature in the backlog is blocked and the logo/token branch is unmerged.

Design working files live in docs/mmd-pdf/design/ so the next agent can
regenerate and re-render them. redplate.css there is the MMD shell design
system lifted verbatim from the session that owns it - do not edit it.

Also carries the session handover.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 13:25:29 +02:00
kua-agentandClaude Opus 5 bfb9845c46 design: official MMD lockup in the title bar, and shell-system colour values
Replaces the app icon and word-mark drawn by an agent with the official MMD
artwork (mmd-logo-red.svg / mmd-logo.svg, unmodified). The Windows icon is the
lockup's baobab disc, cropped from the same file - the only square element in
it. It reads cleanly from 32px and goes muddy at 16-24px; no official small
mark exists, so that is an open question, not a solved one.

Themes/MMD.xaml carries the values of the new MMD shell design system
("red plate"): F1F1EC chrome, E4E3DC rails, F3F3F0 around the page, D4D0C6
desk, 23221E ink with 3C3A34 / 767368 / 6B6A64 below it, C8102E red with
A80D24 hover and 8E0B20 pressed, borders E6E5DF / D7D6CE / C7C6BD / B9B8AE.

NOT ACCEPTED. The design these values came from failed an independent design
review on 2026-08-27 with 3 blockers and 5 major findings. This branch is an
implementation preview only and must not be merged until the design is signed
off.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 13:20:04 +02:00
kua-agent 2cc2f1b627 Merge pull request 'MMD PDF: rebrand, single corporate theme, and real removal of edited text' (#1) from feat/mmd-pdf-rebrand-20260827 into main
Verified live on mmd-win-test-01 by keyboard-and-mouse test: edited value replaces the original in the saved file and the old value is no longer recoverable.
2026-08-27 05:37:41 +00:00
81 changed files with 3646 additions and 76 deletions
+1
View File
@@ -1,3 +1,4 @@
bin/ bin/
obj/ obj/
*.user *.user
MmdPdf.pdf
+20 -1
View File
@@ -1,4 +1,4 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
@@ -68,8 +68,27 @@ namespace MmdPdf
// Startup // Startup
// ============================================================ // ============================================================
// Windows reports whether we are being viewed over a remote desktop. In that case WPF's
// hardware compositing hands the session a surface it never repaints when the desktop is
// resized, which leaves the part of the window outside the original size drawn as flat
// black. Drawing in software costs nothing at this size and keeps the whole window painted.
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(int index);
private const int SM_REMOTESESSION = 0x1000;
private static void UseSoftwareRenderingOnRemoteDesktops()
{
try
{
if (GetSystemMetrics(SM_REMOTESESSION) != 0)
RenderOptions.ProcessRenderMode = System.Windows.Interop.RenderMode.SoftwareOnly;
}
catch { /* never let a rendering hint stop the application starting */ }
}
protected override void OnStartup(StartupEventArgs e) protected override void OnStartup(StartupEventArgs e)
{ {
UseSoftwareRenderingOnRemoteDesktops();
StartupTrace.Mark("App.OnStartup entered"); StartupTrace.Mark("App.OnStartup entered");
DispatcherUnhandledException += OnDispatcherException; DispatcherUnhandledException += OnDispatcherException;
AppDomain.CurrentDomain.UnhandledException += OnDomainException; AppDomain.CurrentDomain.UnhandledException += OnDomainException;
+2 -2
View File
@@ -67,9 +67,9 @@ namespace MmdPdf
int kp = fullTitle?.IndexOf("MmdPdf", StringComparison.Ordinal) ?? -1; int kp = fullTitle?.IndexOf("MmdPdf", StringComparison.Ordinal) ?? -1;
if (kp >= 0) if (kp >= 0)
{ {
// Killer + PDF in one TextBlock so the two sizes share a baseline (cohesive wordmark). // MMD + PDF in one TextBlock so the two sizes share a baseline (cohesive wordmark).
var logoTb = new TextBlock { VerticalAlignment = VerticalAlignment.Center }; var logoTb = new TextBlock { VerticalAlignment = VerticalAlignment.Center };
logoTb.Inlines.Add(new System.Windows.Documents.Run("Killer") { FontFamily = wordmark, FontWeight = FontWeights.Normal, FontSize = 16, Foreground = primary }); logoTb.Inlines.Add(new System.Windows.Documents.Run("MMD ") { FontFamily = wordmark, FontWeight = FontWeights.Normal, FontSize = 16, Foreground = primary });
logoTb.Inlines.Add(new System.Windows.Documents.Run("PDF") { FontFamily = wordmarkPdf, FontWeight = FontWeights.Bold, FontSize = 20.8, Foreground = logo }); logoTb.Inlines.Add(new System.Windows.Documents.Run("PDF") { FontFamily = wordmarkPdf, FontWeight = FontWeights.Bold, FontSize = 20.8, Foreground = logo });
sp.Children.Add(logoTb); sp.Children.Add(logoTb);
string after = fullTitle![(kp + "MmdPdf".Length)..]; string after = fullTitle![(kp + "MmdPdf".Length)..];
+3 -3
View File
@@ -82,7 +82,7 @@ namespace MmdPdf
{ {
var wm = new StackPanel { Orientation = Orientation.Horizontal }; var wm = new StackPanel { Orientation = Orientation.Horizontal };
var wmTb = new TextBlock { VerticalAlignment = VerticalAlignment.Center }; var wmTb = new TextBlock { VerticalAlignment = VerticalAlignment.Center };
wmTb.Inlines.Add(new System.Windows.Documents.Run("Killer") { FontFamily = UiKit.WordmarkFont, FontWeight = FontWeights.Normal, FontSize = 15, Foreground = R("TextBrush") }); wmTb.Inlines.Add(new System.Windows.Documents.Run("MMD ") { FontFamily = UiKit.WordmarkFont, FontWeight = FontWeights.Normal, FontSize = 15, Foreground = R("TextBrush") });
wmTb.Inlines.Add(new System.Windows.Documents.Run("PDF") { FontFamily = UiKit.WordmarkFontPdf, FontWeight = FontWeights.Bold, FontSize = 19.5, Foreground = R("AccentLogo") }); wmTb.Inlines.Add(new System.Windows.Documents.Run("PDF") { FontFamily = UiKit.WordmarkFontPdf, FontWeight = FontWeights.Bold, FontSize = 19.5, Foreground = R("AccentLogo") });
wm.Children.Add(wmTb); wm.Children.Add(wmTb);
// No DropShadowEffect on the text - it rasterizes and blurs the wordmark. Kept crisp. // No DropShadowEffect on the text - it rasterizes and blurs the wordmark. Kept crisp.
@@ -241,7 +241,7 @@ namespace MmdPdf
if (title == "MmdPdf") if (title == "MmdPdf")
{ {
var wmTb = new TextBlock { VerticalAlignment = VerticalAlignment.Center }; var wmTb = new TextBlock { VerticalAlignment = VerticalAlignment.Center };
wmTb.Inlines.Add(new System.Windows.Documents.Run("Killer") { FontFamily = UiKit.WordmarkFont, FontWeight = FontWeights.Normal, FontSize = 15, Foreground = R("TextBrush") }); wmTb.Inlines.Add(new System.Windows.Documents.Run("MMD ") { FontFamily = UiKit.WordmarkFont, FontWeight = FontWeights.Normal, FontSize = 15, Foreground = R("TextBrush") });
wmTb.Inlines.Add(new System.Windows.Documents.Run("PDF") { FontFamily = UiKit.WordmarkFontPdf, FontWeight = FontWeights.Bold, FontSize = 19.5, Foreground = R("AccentLogo") }); wmTb.Inlines.Add(new System.Windows.Documents.Run("PDF") { FontFamily = UiKit.WordmarkFontPdf, FontWeight = FontWeights.Bold, FontSize = 19.5, Foreground = R("AccentLogo") });
titleBar.Child = wmTb; titleBar.Child = wmTb;
} }
@@ -386,7 +386,7 @@ namespace MmdPdf
titleBar.MouseLeftButtonDown += (_, e) => { if (e.ButtonState == MouseButtonState.Pressed) win.DragMove(); }; titleBar.MouseLeftButtonDown += (_, e) => { if (e.ButtonState == MouseButtonState.Pressed) win.DragMove(); };
var wm = new StackPanel { Orientation = Orientation.Horizontal }; var wm = new StackPanel { Orientation = Orientation.Horizontal };
var wmTb = new TextBlock { VerticalAlignment = VerticalAlignment.Center }; var wmTb = new TextBlock { VerticalAlignment = VerticalAlignment.Center };
wmTb.Inlines.Add(new System.Windows.Documents.Run("Killer") { FontFamily = UiKit.WordmarkFont, FontWeight = FontWeights.Normal, FontSize = 15, Foreground = R("TextBrush") }); wmTb.Inlines.Add(new System.Windows.Documents.Run("MMD ") { FontFamily = UiKit.WordmarkFont, FontWeight = FontWeights.Normal, FontSize = 15, Foreground = R("TextBrush") });
wmTb.Inlines.Add(new System.Windows.Documents.Run("PDF") { FontFamily = UiKit.WordmarkFontPdf, FontWeight = FontWeights.Bold, FontSize = 18, Foreground = R("AccentLogo") }); wmTb.Inlines.Add(new System.Windows.Documents.Run("PDF") { FontFamily = UiKit.WordmarkFontPdf, FontWeight = FontWeights.Bold, FontSize = 18, Foreground = R("AccentLogo") });
wm.Children.Add(wmTb); wm.Children.Add(wmTb);
titleBar.Child = wm; titleBar.Child = wm;
+163
View File
@@ -0,0 +1,163 @@
<!-- Pull pages off a copier. Chrome follows the MMD design system locked on 2026-08-28:
a front-window title bar (white to silver, 3px red top edge, ink text), 4px corners,
flat buttons, one red fill for the single action the person came to do. Every colour and
radius is a theme key from Themes/MMD.xaml - nothing is picked here. -->
<Window x:Class="MmdPdf.Features.Scan.ScanDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Scan a document"
Width="520" SizeToContent="Height"
WindowStyle="None" ResizeMode="NoResize" ShowInTaskbar="False"
Background="Transparent"
TextOptions.TextFormattingMode="Display"
TextOptions.TextRenderingMode="ClearType"
UseLayoutRounding="True" SnapsToDevicePixels="True"
WindowStartupLocation="CenterOwner">
<Border Background="{DynamicResource BgFlyout}"
BorderBrush="{DynamicResource AppBorderBrush}" BorderThickness="1"
CornerRadius="{DynamicResource ControlCornerRadius}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Title bar: the front-window treatment from the design system -->
<Border Grid.Row="0" Height="34"
Background="{DynamicResource WindowFrontTitleBrush}"
BorderBrush="{DynamicResource PrimaryBrush}" BorderThickness="0,3,0,0"
MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
<Grid Margin="12,0,8,0">
<TextBlock x:Name="TitleText" Text="Scan a document" VerticalAlignment="Center"
FontFamily="Segoe UI" FontSize="12.5" FontWeight="Bold"
Foreground="{DynamicResource TextBrush}"/>
<Button x:Name="CloseChrome" Click="Cancel_Click" HorizontalAlignment="Right"
VerticalAlignment="Center" Width="24" Height="24"
Background="Transparent" BorderThickness="0" Cursor="Hand"
Foreground="{DynamicResource TextBrush}" Content="&#x2715;"
FontFamily="Segoe UI" FontSize="11"/>
</Grid>
</Border>
<!-- Body -->
<Grid Grid.Row="1" Margin="16,14,16,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Column="0">
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="86"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="COPIER" VerticalAlignment="Center" FontFamily="Consolas" FontSize="9.5"
Foreground="{DynamicResource DimTextBrush}"/>
<ComboBox x:Name="CopierBox" Grid.Column="1" Height="26" FontSize="11.5"/>
</Grid>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="86"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="FEED" VerticalAlignment="Center" FontFamily="Consolas" FontSize="9.5"
Foreground="{DynamicResource DimTextBrush}"/>
<ComboBox x:Name="FeedBox" Grid.Column="1" Height="26" FontSize="11.5"/>
</Grid>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="86"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="COLOUR" VerticalAlignment="Center" FontFamily="Consolas" FontSize="9.5"
Foreground="{DynamicResource DimTextBrush}"/>
<ComboBox x:Name="ColourBox" Grid.Column="1" Height="26" FontSize="11.5"/>
</Grid>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="86"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="DETAIL" VerticalAlignment="Center" FontFamily="Consolas" FontSize="9.5"
Foreground="{DynamicResource DimTextBrush}"/>
<ComboBox x:Name="DetailBox" Grid.Column="1" Height="26" FontSize="11.5"/>
</Grid>
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="86"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="PAPER" VerticalAlignment="Center" FontFamily="Consolas" FontSize="9.5"
Foreground="{DynamicResource DimTextBrush}"/>
<ComboBox x:Name="PaperBox" Grid.Column="1" Height="26" FontSize="11.5"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="86"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="AFTERWARDS" VerticalAlignment="Top" Margin="0,2,0,0"
FontFamily="Consolas" FontSize="9.5"
Foreground="{DynamicResource DimTextBrush}"/>
<StackPanel Grid.Column="1">
<CheckBox x:Name="SearchableBox" IsChecked="True" FontSize="11.5">
<TextBlock Text="Make the words searchable, in French and English"
TextWrapping="Wrap" MaxWidth="180"/>
</CheckBox>
<CheckBox x:Name="StraightenBox" Margin="0,6,0,0" FontSize="11.5">
<TextBlock Text="Straighten crooked pages" TextWrapping="Wrap" MaxWidth="180"/>
</CheckBox>
</StackPanel>
</Grid>
</StackPanel>
<!-- Sheet preview: what one page will look like -->
<Border Grid.Column="1" Width="132" Margin="16,0,0,0"
Background="{DynamicResource BgCanvas}"
BorderBrush="{DynamicResource InputBorderBrush}" BorderThickness="1"
CornerRadius="{DynamicResource ControlCornerRadius}">
<Border Width="96" Height="132" Margin="10" VerticalAlignment="Center"
Background="White" BorderBrush="{DynamicResource InputBorderBrush}" BorderThickness="1">
<StackPanel Margin="9,9,8,0">
<Rectangle Height="3" Width="56" HorizontalAlignment="Left" Margin="0,0,0,5"
Fill="{DynamicResource DimTextBrush}"/>
<Rectangle Height="2" Margin="0,0,0,4" Fill="{DynamicResource TreeLineBrush}"/>
<Rectangle Height="2" Width="66" HorizontalAlignment="Left" Margin="0,0,0,4"
Fill="{DynamicResource TreeLineBrush}"/>
<Rectangle Height="2" Width="44" HorizontalAlignment="Left" Margin="0,0,0,4"
Fill="{DynamicResource TreeLineBrush}"/>
<Rectangle Height="2" Margin="0,0,0,4" Fill="{DynamicResource TreeLineBrush}"/>
<Rectangle Height="2" Width="60" HorizontalAlignment="Left" Fill="{DynamicResource TreeLineBrush}"/>
</StackPanel>
</Border>
</Border>
</Grid>
<!-- Footer -->
<Border Grid.Row="2" Background="{DynamicResource SurfaceBrush}"
BorderBrush="{DynamicResource PaneBorderBrush}" BorderThickness="0,1,0,0"
CornerRadius="0,0,4,4" Padding="16,10">
<Grid>
<TextBlock x:Name="FootNote" VerticalAlignment="Center" FontSize="10.5"
Foreground="{DynamicResource DimTextBrush}"
Text="Pages arrive as one document you can edit"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="CancelBtn" Content="Cancel" Click="Cancel_Click"
Height="28" Padding="13,0" Margin="0,0,8,0" FontSize="11.5" FontWeight="SemiBold"/>
<Button x:Name="ScanBtn" Content="Scan" Click="Scan_Click"
Height="28" Padding="13,0" FontSize="11.5" FontWeight="SemiBold"
Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource OnPrimaryBrush}"
BorderBrush="{DynamicResource PrimaryBrush}" BorderThickness="1"/>
</StackPanel>
</Grid>
</Border>
</Grid>
</Border>
</Window>
+186
View File
@@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using MmdPdf.Services;
namespace MmdPdf.Features.Scan
{
/// <summary>
/// Asks the copier for pages. The person picks the machine, the feed, how much ink and how
/// much detail; everything else is the copier's own default. The pages come back as image
/// files which MainWindow turns into one document.
///
/// A run cannot be interrupted mid-sheet - that is the copier's hardware, not a decision made
/// here - so Cancel stops after the sheet in progress and keeps the pages already read.
/// </summary>
public partial class ScanDialog : Window
{
private readonly List<ScannerInfo> _scanners;
private CancellationTokenSource? _cancel;
private bool _running;
/// <summary>Image files, one per page, in the order the copier read them.</summary>
internal string[] Pages { get; private set; } = Array.Empty<string>();
/// <summary>The person asked for the words to be made searchable afterwards.</summary>
internal bool MakeSearchable { get; private set; }
/// <summary>The person asked for crooked pages to be straightened afterwards.</summary>
internal bool Straighten { get; private set; }
public ScanDialog()
{
InitializeComponent();
_scanners = ScanService.IsAvailable ? ScanService.ListScanners() : new List<ScannerInfo>();
foreach (var s in _scanners) CopierBox.Items.Add(s.Name);
if (_scanners.Count > 0)
{
CopierBox.SelectedIndex = 0;
}
else
{
CopierBox.Items.Add(ScanService.IsAvailable
? "No copier found on this machine"
: "This machine has no imaging service");
CopierBox.SelectedIndex = 0;
CopierBox.IsEnabled = false;
ScanBtn.IsEnabled = false;
FootNote.Text = "Switch the copier on, or connect it, then open this again";
}
FeedBox.Items.Add("Feeder, one side");
FeedBox.Items.Add("Feeder, both sides");
FeedBox.Items.Add("Glass, one sheet");
FeedBox.SelectedIndex = 0;
ColourBox.Items.Add("Black and white");
ColourBox.Items.Add("Shades of grey");
ColourBox.Items.Add("Colour");
ColourBox.SelectedIndex = 0;
DetailBox.Items.Add("200 dpi, quick");
DetailBox.Items.Add("300 dpi, good for text");
DetailBox.Items.Add("600 dpi, fine detail");
DetailBox.SelectedIndex = 1;
foreach (var p in ScanService.PaperSizes) PaperBox.Items.Add(p);
PaperBox.SelectedIndex = 0;
}
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ButtonState == MouseButtonState.Pressed) DragMove();
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
if (_running)
{
_cancel?.Cancel();
FootNote.Text = "Stopping after this sheet…";
return;
}
DialogResult = false;
Close();
}
private async void Scan_Click(object sender, RoutedEventArgs e)
{
if (_running || _scanners.Count == 0) return;
var device = _scanners[Math.Max(0, CopierBox.SelectedIndex)];
var feed = FeedBox.SelectedIndex switch
{
1 => ScanFeed.FeederBothSides,
2 => ScanFeed.Flatbed,
_ => ScanFeed.FeederOneSide,
};
var colour = ColourBox.SelectedIndex switch
{
1 => ScanColour.Greyscale,
2 => ScanColour.Colour,
_ => ScanColour.BlackAndWhite,
};
int dpi = DetailBox.SelectedIndex switch
{
0 => 200,
2 => 600,
_ => 300,
};
string paper = PaperBox.SelectedItem as string ?? "A4 portrait";
MakeSearchable = SearchableBox.IsChecked == true;
Straighten = StraightenBox.IsChecked == true;
_running = true;
_cancel = new CancellationTokenSource();
ScanBtn.IsEnabled = false;
CancelBtn.Content = "Stop";
SetInputsEnabled(false);
FootNote.Text = feed == ScanFeed.Flatbed ? "Reading the sheet on the glass…" : "Reading…";
var progress = new Progress<int>(n =>
FootNote.Text = n == 1 ? "1 page read" : n.ToString(CultureInfo.InvariantCulture) + " pages read");
try
{
var token = _cancel.Token;
var reporter = (IProgress<int>)progress;
string[] pages = await Task.Run(() =>
ScanService.Acquire(device.DeviceId, feed, colour, dpi, paper,
maxPages: feed == ScanFeed.Flatbed ? 1 : 200,
cancel: token,
onPage: reporter.Report).ToArray(), token);
Pages = pages;
if (pages.Length == 0)
{
FootNote.Text = "The copier returned no pages";
ResetAfterRun();
return;
}
DialogResult = true;
Close();
}
catch (OperationCanceledException)
{
FootNote.Text = "Stopped";
ResetAfterRun();
}
catch (Exception ex)
{
FootNote.Text = "The copier could not be used: " + ex.Message;
ResetAfterRun();
}
}
private void ResetAfterRun()
{
_running = false;
_cancel?.Dispose();
_cancel = null;
ScanBtn.IsEnabled = _scanners.Count > 0;
CancelBtn.Content = "Cancel";
SetInputsEnabled(true);
}
private void SetInputsEnabled(bool on)
{
CopierBox.IsEnabled = on && _scanners.Count > 0;
FeedBox.IsEnabled = on;
ColourBox.IsEnabled = on;
DetailBox.IsEnabled = on;
PaperBox.IsEnabled = on;
SearchableBox.IsEnabled = on;
StraightenBox.IsEnabled = on;
}
}
}
+169
View File
@@ -0,0 +1,169 @@
# HANDOVER — MMD PDF (Windows PDF editor for staff)
**Written** 2026-08-27 15:18:53 +04 (11:18 UTC)
**Session** mmd-projects-87 · **Repo** `gitea.baobab-ts.com/mmd-it/mmd-pdf`
---
## What this is
MMD staff had no sanctioned way to write inside PDF documents. PDFgear was
blocked on 2026-07-20 for forging the Windows default-PDF registry setting.
This session picked a replacement, proved it, forked it, and started turning it
into an MMD product.
**MMD PDF** = GPL-3.0 fork of KillerPDF 1.7.5, staying GPL-3.0, `FORK-NOTICE.md`
in the repo records what changed.
---
## What was done
### Chosen and proven (this part is solid)
- Surveyed open-source Windows PDF editors. **No MIT-licensed app does true
inline PDF text editing** — the only genuinely MIT desktop option (every-pdf)
can only stamp text on top of a page. KillerPDF (GPL-3.0) was picked; GPL
restricts forking and redistribution, not internal deployment to staff.
- **Passed the check that killed PDFgear.** `.pdf` UserChoice ProgId and Hash
identical before and after install and after opening a PDF; the protective
deny ACE intact; one signed executable, no helper binary. Read live from the
registry on our own machine.
- **Found and fixed a real defect.** Editing existing text left the original
glyphs in the file — a delivery note corrected from 12 to 17 tonnes returned
*both* figures from `pdftotext`. `Services/PdfRedactText.cs` now walks the
page content stream, tracks the CTM and text matrix, and blanks every
show-text operator whose origin sits inside the cover rectangle, before any
new content is appended. Proven: after the fix the saved file contains only
the new figure, every other line intact and still searchable.
- **Built the test rig that found it.** `/opt/mmd-wintest/wintest` on the
hypervisor: Xvfb + xfreerdp3 + xdotool, so every action is a real pointer or
key event with no scripting back door. This is how user testing is done from
now on.
### Dedicated Windows test machine
VM 203 on **mmd-fsn-03** renamed **mmd-win-test-01**, `onboot=1`, described in
Proxmox. Windows Server 2022, 20 cores / 72 GB, guest 10.10.20.22. Its DNS was
broken (no internet at all) — fixed. .NET SDK 8 installed for builds.
It still also carries the Mali plant-station build, so it is shared.
### Design — failed review, not accepted
The rebrand and the new MMD shell design system ("red plate") were applied and
merged **before any design existed**, which is the wrong order and Ben called
it. Redone properly afterwards: design system pulled from the Claude Design
project, red-plate stylesheet lifted verbatim from the session that owns it,
seven screens composed at 800×600, then put through an **independent adversarial
review** — the gate that was missing.
**Verdict: DESIGN-FAIL.** 3 blockers, 5 major, 3 minor. All eleven findings and
the fix state are in `docs/mmd-pdf/SIGNOFFS.md`.
---
## Current live / verified state
| Thing | State |
|---|---|
| `main` | carries the rebrand + the redaction fix (PR #1, merged) |
| `design/official-logo-and-tokens-20260827` | **bfb9845, pushed, NOT merged** — official logo + design-system values. Unaccepted: the design failed review. |
| Build | `dotnet build -c Release`, 0 errors, on mmd-win-test-01 |
| App | runs; opens, edits and saves correctly, keyboard-and-mouse tested |
| Design | **DESIGN-FAIL**, no re-review run, no Ben sign-off |
| Scanning | not written |
| Signing / Intune | not started |
Claude Design project `446a3931-c345-48da-a3ac-246af02a2e52` ("Design System"):
the repo's live CDS 0.3.40 was pushed into `mmd-portal/` (its mirror was stale
at 0.3.11), and round-1 design pages into `mmd-pdf/`. **Round 2 was not pushed**
— it failed review.
Two artifact links exist from before Ben's `/login` and are **dead**
(`65c7726d…`, `e47d705f…`). Live under the current account:
`7d8d6b56-ef36-4729-8e88-a3fb1392707e` (round-1 canvas) and
`a7a974e8-aff4-49e6-b4cc-f80414769515` (status page). Both are round-1 and both
predate the review — do not present either as reviewed.
---
## Resume steps
1. **Read** `docs/mmd-pdf/SIGNOFFS.md` for the eleven findings, then
`BACKLOG.md` for what is ⛔.
2. **Get Ben's toolbar pick** — A, B or C on the Directions board. The
independent reviewer recommends **C** (three tabs; nothing hidden, every tool
named). Nothing else in the design can be finished without it.
3. **Apply the seven unapplied findings** — 1, 6, 7, 8, 9, 10, 11 — the big one
being the rebuild of Main / EditText / PageRail / Nothing onto Direction C.
Working files: `docs/mmd-pdf/design/build.py`, regenerate with `python3
build.py`, render each `.dc.html` locally and look at it.
4. **Re-run the independent review** with the prompt in
`docs/mmd-pdf/TEST-CASES.md` under "To review a design". Give the reviewer
the **complete** palette this time, including `#FAFAF8` and `#C9C6BC` — my
incomplete brief caused half of finding 4.
5. **Only after DESIGN-PASS**, publish the canvas and hand Ben the link for
annotation. His annotation closes the gate; the reviewer's PASS does not.
6. **Then** re-derive `Themes/MMD.xaml` from the signed-off design and merge
`design/official-logo-and-tokens-20260827`.
7. **Then** scanning (NAPS2.Wia, MIT, .NET Framework 4.6.2 — compatible with
this net48 codebase), then signing, then Intune.
---
## Key facts, commits, addresses
- Repo `gitea.baobab-ts.com/mmd-it/mmd-pdf`; `main` = PR #1 merged;
branch `design/official-logo-and-tokens-20260827` = **bfb9845**.
- Hypervisor **mmd-fsn-03** — Hetzner dedicated, Falkenstein FSN1-DC16, auction
#3049905, public 168.119.88.172, tailnet 100.64.0.19, Proxmox VE 9.2.10.
Reach it with `ssh -i ~/.ssh/id_ed25519_mmd_fsn03_rescue root@100.64.0.19`.
- Test machine **mmd-win-test-01** (VM 203), guest 10.10.20.22. Humans reach it
in a browser at `https://plantpc.baobab-ts.com` (Guacamole, Entra SSO).
- Source on the VM at `C:\src`; build output
`C:\src\bin\Release\net48\MmdPdf.exe`; .NET SDK at `C:\build\dotnet`.
- Graph for Intune/Entra: **MMD-CIO-Agent client secret** at
`~/mmd-projects/entra-cio-agent/secret.env`, helper `graphlib.mjs`. The
certificate in `~/.config/mmd/certs` is Exchange PowerShell only — do not
reach for it and then conclude the agent lacks Intune access.
---
## Open items
- **Ben: pick the toolbar direction.** Blocks everything else in the design.
- **Ben: the 1624px app icon.** No official small MMD mark exists anywhere. The
icon crops the lockup's baobab disc, which reads from 32px and is muddy below
it. An agent must not draw a simplified one. Commission or accept muddy.
- Seven design findings unapplied; no re-review run.
- Scanning not written. Signing not started. Intune not started.
- mmd-win-test-01's Windows evaluation licence — a restart reset it to 180 days,
but it needs a real licence before it is a standing fixture.
- Design round 2 not pushed to the Claude Design project (it failed review).
---
## Gotchas
1. **`Stop-Process` over WinRM against a running GUI app hangs the session.**
Launch GUI apps with a *new* scheduled-task name instead.
2. **`freerdp2-x11` does not exist on Debian 13.** It is `freerdp3-x11`, and the
binary is `xfreerdp3`, not `xfreerdp`.
3. **The word-mark was two literal `<Run Text="Killer">` elements**, not the
product-name token — a global rename misses it. Check the title bar visually
after any rename.
4. **`redplate.css` belongs to another session.** It is lifted verbatim from
their `build.mjs`. I edited it once chasing a review finding and reverted it.
Do not edit it; raise a discrepancy with the owning session instead.
5. **A `/login` mid-session orphans artifacts** published earlier under the old
account. Republishing the same *file path* keeps mapping to the dead one —
copy the file to a new path to claim a fresh URL.
6. **`Ctrl+A` inside an inline text edit selects all annotations**, not the
text. Use `End` + `BackSpace`. `Enter` commits, `Escape` cancels — the status
bar now says so because both were guessed wrong.
7. **Flatten is not redaction's friend.** It removes the old text by turning the
page into a picture, destroying every searchable word. That is why the
content-stream fix exists.
8. **The design gate is not closed by a reviewer's PASS.** Only Ben's
annotation closes it, and design that never went through the proper lane is
an automatic fail however good it looks.
+9 -6
View File
@@ -112,12 +112,12 @@
<Setter Property="Background" Value="Transparent"/> <Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/> <Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
<Setter Property="BorderThickness" Value="0"/> <Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="10,6"/> <Setter Property="Padding" Value="8,4"/>
<Setter Property="FontFamily" Value="Segoe MDL2 Assets"/> <Setter Property="FontFamily" Value="Segoe MDL2 Assets"/>
<Setter Property="FontSize" Value="14"/> <Setter Property="FontSize" Value="14"/>
<Setter Property="Cursor" Value="Hand"/> <Setter Property="Cursor" Value="Hand"/>
<Setter Property="Width" Value="36"/> <Setter Property="Width" Value="30"/>
<Setter Property="Height" Value="32"/> <Setter Property="Height" Value="26"/>
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Button"> <ControlTemplate TargetType="Button">
@@ -1179,10 +1179,12 @@
<!-- Soft drop shadow: a blurred, offset copy behind the crisp wordmark --> <!-- Soft drop shadow: a blurred, offset copy behind the crisp wordmark -->
<StackPanel Orientation="Horizontal" Margin="1,1,0,0" Opacity="{DynamicResource WordmarkShadowOpacity}"> <StackPanel Orientation="Horizontal" Margin="1,1,0,0" Opacity="{DynamicResource WordmarkShadowOpacity}">
<StackPanel.Effect><BlurEffect Radius="2"/></StackPanel.Effect> <StackPanel.Effect><BlurEffect Radius="2"/></StackPanel.Effect>
<TextBlock><Run Text="MMD " FontFamily="{DynamicResource WordmarkFont}" FontSize="16.5" Foreground="#D8000000"/><Run Text="PDF" FontFamily="{DynamicResource WordmarkFontPdf}" FontWeight="Bold" FontSize="21.45" Foreground="#D8000000"/></TextBlock> <Image Source="Resources/mmd-lockup-white.png" Height="18" Stretch="Uniform" HorizontalAlignment="Left" VerticalAlignment="Center" RenderOptions.BitmapScalingMode="HighQuality" SnapsToDevicePixels="True"/>
<TextBlock Text="PDF" Margin="5,0,0,0" VerticalAlignment="Center" FontSize="13" FontWeight="Bold" Foreground="White" Opacity="0.95"/>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock><Run Text="MMD " FontFamily="{DynamicResource WordmarkFont}" FontSize="16.5" Foreground="{DynamicResource TextBrush}"/><Run Text="PDF" FontFamily="{DynamicResource WordmarkFontPdf}" FontWeight="Bold" FontSize="21.45" Foreground="{DynamicResource AccentLogo}"/></TextBlock> <Image Source="Resources/mmd-lockup-white.png" Height="18" Stretch="Uniform" HorizontalAlignment="Left" VerticalAlignment="Center" RenderOptions.BitmapScalingMode="HighQuality" SnapsToDevicePixels="True"/>
<TextBlock Text="PDF" Margin="5,0,0,0" VerticalAlignment="Center" FontSize="13" FontWeight="Bold" Foreground="White" Opacity="0.95"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel> </StackPanel>
@@ -1237,7 +1239,7 @@
<Border IsHitTestVisible="False" Opacity="{DynamicResource GrainOpacity}" Background="{StaticResource GrainBrushShared}"/> <Border IsHitTestVisible="False" Opacity="{DynamicResource GrainOpacity}" Background="{StaticResource GrainBrushShared}"/>
<!-- Background=Transparent is load-bearing: without it the grid's empty stretches have no <!-- Background=Transparent is load-bearing: without it the grid's empty stretches have no
hit surface and the right-click appearance picker only opens over the buttons. --> hit surface and the right-click appearance picker only opens over the buttons. -->
<Grid x:Name="ToolbarGrid" SizeChanged="ToolbarGrid_SizeChanged" Margin="4,2" Background="Transparent"> <Grid x:Name="ToolbarGrid" SizeChanged="ToolbarGrid_SizeChanged" Margin="4,1" Background="Transparent">
<Grid.ContextMenu> <Grid.ContextMenu>
<!-- Items are built in code (BuildToolbarMenu, SettingsPanel.cs) so captions re-Loc on <!-- Items are built in code (BuildToolbarMenu, SettingsPanel.cs) so captions re-Loc on
language switch; attached here so WPF supplies the inheritance context that lets language switch; attached here so WPF supplies the inheritance context that lets
@@ -1257,6 +1259,7 @@
<Button x:Name="OcrMenuBtn" Content="&#xE70D;" FontSize="10" Width="13" Padding="0" Margin="-6,0,0,0" Style="{StaticResource ToolbarButton}" Click="OcrMenu_Click" ToolTip="{DynamicResource Str_TT_OcrOptions}"/> <Button x:Name="OcrMenuBtn" Content="&#xE70D;" FontSize="10" Width="13" Padding="0" Margin="-6,0,0,0" Style="{StaticResource ToolbarButton}" Click="OcrMenu_Click" ToolTip="{DynamicResource Str_TT_OcrOptions}"/>
<Button Content="&#xE8B9;" Style="{StaticResource ToolbarButton}" Click="SaveFlattened_Click" ToolTip="{DynamicResource Str_TT_SaveFlattened}"/> <Button Content="&#xE8B9;" Style="{StaticResource ToolbarButton}" Click="SaveFlattened_Click" ToolTip="{DynamicResource Str_TT_SaveFlattened}"/>
<Button Content="&#xE749;" Style="{StaticResource ToolbarButton}" Click="Print_Click" ToolTip="{DynamicResource Str_TT_Print}"/> <Button Content="&#xE749;" Style="{StaticResource ToolbarButton}" Click="Print_Click" ToolTip="{DynamicResource Str_TT_Print}"/>
<Button x:Name="ScanBtn" Content="&#xE8FE;" Style="{StaticResource ToolbarButton}" Click="Scan_Click" ToolTip="{DynamicResource Str_TT_Scan}"/>
<StackPanel x:Name="GrpPageOps" Orientation="Horizontal"> <StackPanel x:Name="GrpPageOps" Orientation="Horizontal">
<Rectangle Width="1" Fill="{DynamicResource CardBorderBrush}" Margin="4,6"/> <Rectangle Width="1" Fill="{DynamicResource CardBorderBrush}" Margin="4,6"/>
<Button Content="&#xE710;" Style="{StaticResource ToolbarButton}" Click="Merge_Click" ToolTip="{DynamicResource Str_TT_MergePDFs}"/> <Button Content="&#xE710;" Style="{StaticResource ToolbarButton}" Click="Merge_Click" ToolTip="{DynamicResource Str_TT_MergePDFs}"/>
+16
View File
@@ -532,6 +532,22 @@ namespace MmdPdf
// squared chrome on every move (and once now that the handle exists). // squared chrome on every move (and once now that the handle exists).
LocationChanged += OnWindowLocationChanged; LocationChanged += OnWindowLocationChanged;
UpdateWindowChrome(); UpdateWindowChrome();
FitToSmallScreen();
}
/// <summary>
/// Plant machines run at 800 by 600. The default 1000 by 700 window does not fit one, and
/// a window taller than the screen puts its own title bar - and therefore Scan, the window
/// buttons and the tabs - above the top edge where nobody can reach them. On a screen that
/// cannot hold the default, start maximised instead.
/// </summary>
private void FitToSmallScreen()
{
if (WindowState != WindowState.Normal) return;
var work = System.Windows.SystemParameters.WorkArea;
if (work.Width <= 0 || work.Height <= 0) return;
if (Width <= work.Width && Height <= work.Height) return;
WindowState = WindowState.Maximized;
} }
// ============================================================ // ============================================================
+8 -4
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
@@ -13,9 +13,11 @@
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<ApplicationIcon>Resources\mmd-icon.ico</ApplicationIcon> <ApplicationIcon>Resources\mmd-icon.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
<Version>1.7.5</Version> <!-- MMD's own version line. 1.7.5 was the upstream project's; it read as that
<AssemblyVersion>1.7.5.0</AssemblyVersion> product still, and it has to sort above it so an upgrade is an upgrade. -->
<FileVersion>1.7.5.0</FileVersion> <Version>2026.8.2</Version>
<AssemblyVersion>2026.8.2.0</AssemblyVersion>
<FileVersion>2026.8.2.0</FileVersion>
<!-- Shown in the About card beside the version so a user can tell how old their build <!-- Shown in the About card beside the version so a user can tell how old their build
is. Must match the date on this version's CHANGELOG section - release.ps1 preflight is. Must match the date on this version's CHANGELOG section - release.ps1 preflight
fails the release if it does not. Bump it with the version. --> fails the release if it does not. Bump it with the version. -->
@@ -134,6 +136,8 @@
<ItemGroup> <ItemGroup>
<Resource Include="Resources\mmd-icon.ico" /> <Resource Include="Resources\mmd-icon.ico" />
<Resource Include="Resources\mmd-icon.png" /> <Resource Include="Resources\mmd-icon.png" />
<Resource Include="Resources\mmd-lockup-red.png" />
<Resource Include="Resources\mmd-lockup-white.png" />
<Resource Include="Fonts\Typewriter-A602.ttf" /> <Resource Include="Fonts\Typewriter-A602.ttf" />
<EmbeddedResource Include="Resources\pdf-file.ico" /> <EmbeddedResource Include="Resources\pdf-file.ico" />
</ItemGroup> </ItemGroup>
BIN
View File
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

+373
View File
@@ -0,0 +1,373 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
namespace MmdPdf.Services
{
/// <summary>Which side of the paper the copier reads, and from where.</summary>
internal enum ScanFeed
{
Flatbed,
FeederOneSide,
FeederBothSides,
}
/// <summary>How much ink the copier records.</summary>
internal enum ScanColour
{
BlackAndWhite,
Greyscale,
Colour,
}
/// <summary>One copier the machine can reach.</summary>
internal sealed class ScannerInfo
{
internal ScannerInfo(string deviceId, string name)
{
DeviceId = deviceId;
Name = name;
}
internal string DeviceId { get; }
internal string Name { get; }
public override string ToString() => Name;
}
/// <summary>
/// Pulls pages off a network copier or a desk scanner through WIA, the imaging service that
/// ships with Windows. Nothing is installed for this: the COM objects are created late-bound
/// by ProgID, so a machine with no imaging stack fails at <see cref="IsAvailable"/> instead of
/// at load time, and the application still starts.
///
/// The page images land in a folder of their own under the user's temp directory and are
/// handed back as file paths; turning them into a document is PdfImport's job, the same way
/// an imported photograph is handled.
/// </summary>
internal static class ScanService
{
// WIA item property ids. These are the documented constants; the COM interface takes
// them as integers, so they are written here once rather than repeated as magic numbers.
private const int WIA_IPS_CUR_INTENT = 6146;
private const int WIA_IPS_XRES = 6147;
private const int WIA_IPS_YRES = 6148;
private const int WIA_IPS_XPOS = 6149;
private const int WIA_IPS_YPOS = 6150;
private const int WIA_IPS_XEXTENT = 6151;
private const int WIA_IPS_YEXTENT = 6152;
private const int WIA_IPA_DATATYPE = 4103;
private const int WIA_IPA_DEPTH = 4104;
// Device properties that drive the sheet feeder.
private const int WIA_DPS_DOCUMENT_HANDLING_SELECT = 3088;
private const int WIA_DPS_DOCUMENT_HANDLING_STATUS = 3087;
private const int WIA_DPS_PAGES = 3096;
private const int FEEDER = 0x001;
private const int FLATBED = 0x002;
private const int DUPLEX = 0x004;
private const int FEED_READY = 0x001;
// Intent values: what the copier should optimise for.
private const int INTENT_COLOUR = 0x00000001;
private const int INTENT_GREYSCALE = 0x00000002;
private const int INTENT_TEXT = 0x00000004;
private const string FORMAT_PNG = "{B96B3CAF-0728-11D3-9D7B-0000F81EF32E}";
// Paper the copier is asked for, in inches. A4 is the MMD standard; Letter is here
// because the Dubai office buys it locally.
private static readonly Dictionary<string, (double W, double H)> PaperInches =
new Dictionary<string, (double, double)>(StringComparer.OrdinalIgnoreCase)
{
["A4 portrait"] = (8.27, 11.69),
["A4 landscape"] = (11.69, 8.27),
["A5 portrait"] = (5.83, 8.27),
["Letter portrait"] = (8.5, 11.0),
["Legal portrait"] = (8.5, 14.0),
};
internal static IEnumerable<string> PaperSizes => PaperInches.Keys;
/// <summary>True when this machine has the Windows imaging service at all.</summary>
internal static bool IsAvailable => Type.GetTypeFromProgID("WIA.DeviceManager") != null;
/// <summary>
/// Every copier and scanner the machine can currently reach. An empty list is a normal
/// answer - it means nothing is switched on - and never an exception the caller has to
/// catch.
/// </summary>
internal static List<ScannerInfo> ListScanners()
{
var found = new List<ScannerInfo>();
var progId = Type.GetTypeFromProgID("WIA.DeviceManager");
if (progId == null) return found;
object? manager = null;
try
{
manager = Activator.CreateInstance(progId);
dynamic dm = manager!;
dynamic infos = dm.DeviceInfos;
int count = (int)infos.Count;
for (int i = 1; i <= count; i++) // WIA collections are 1-based
{
dynamic info = infos[i];
try
{
// 1 == ScannerDeviceType. Cameras and video devices are skipped.
if ((int)info.Type != 1) continue;
string name = ReadProperty(info.Properties, "Name") ?? "Copier";
found.Add(new ScannerInfo((string)info.DeviceID, name));
}
finally { Release(info); }
}
}
catch (COMException) { /* imaging service not running: nothing to offer */ }
catch (InvalidCastException) { }
finally { Release(manager); }
return found;
}
/// <summary>
/// Reads pages and writes each one as a PNG into its own folder. Returns the files in
/// page order. Cancelling stops after the page in progress - a copier cannot be
/// interrupted mid-sheet.
/// </summary>
/// <param name="onPage">Called with the running page count as each sheet lands.</param>
internal static List<string> Acquire(
string deviceId,
ScanFeed feed,
ScanColour colour,
int dpi,
string paper,
int maxPages,
CancellationToken cancel,
Action<int>? onPage = null)
{
if (string.IsNullOrWhiteSpace(deviceId)) throw new ArgumentException("No copier chosen.", nameof(deviceId));
if (dpi < 75 || dpi > 1200) throw new ArgumentOutOfRangeException(nameof(dpi));
var progId = Type.GetTypeFromProgID("WIA.DeviceManager")
?? throw new InvalidOperationException("This machine has no imaging service.");
string folder = Path.Combine(Path.GetTempPath(), "MmdPdf.Scan." + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(folder);
var pages = new List<string>();
object? manager = null;
object? device = null;
try
{
manager = Activator.CreateInstance(progId);
dynamic dm = manager!;
dynamic infos = dm.DeviceInfos;
dynamic? chosen = null;
int count = (int)infos.Count;
for (int i = 1; i <= count; i++)
{
dynamic info = infos[i];
if (string.Equals((string)info.DeviceID, deviceId, StringComparison.Ordinal)) { chosen = info; break; }
Release(info);
}
if (chosen == null) throw new InvalidOperationException("That copier is no longer reachable.");
device = chosen.Connect();
dynamic dev = device!;
ConfigureFeeder(dev, feed, maxPages);
dynamic item = dev.Items[1];
try
{
ConfigurePage(item, colour, dpi, paper);
for (int page = 1; page <= maxPages; page++)
{
cancel.ThrowIfCancellationRequested();
string path = Path.Combine(folder, "page-" + page.ToString("000", CultureInfo.InvariantCulture) + ".png");
try
{
dynamic image = item.Transfer(FORMAT_PNG);
try
{
image.SaveFile(path);
pages.Add(path);
onPage?.Invoke(pages.Count);
}
finally { Release(image); }
}
catch (COMException ex) when (IsOutOfPaper(ex))
{
break; // the feeder emptied: a normal end, not a failure
}
if (feed == ScanFeed.Flatbed) break; // one sheet on the glass
if (!FeederHasPaper(dev)) break;
}
}
finally { Release(item); }
}
finally
{
Release(device);
Release(manager);
}
if (pages.Count == 0)
{
try { Directory.Delete(folder, true); } catch (IOException) { }
catch (UnauthorizedAccessException) { }
}
return pages;
}
private static void ConfigureFeeder(dynamic device, ScanFeed feed, int maxPages)
{
int handling = feed == ScanFeed.Flatbed ? FLATBED : FEEDER;
if (feed == ScanFeed.FeederBothSides) handling |= DUPLEX;
TrySetProperty(device.Properties, WIA_DPS_DOCUMENT_HANDLING_SELECT, handling);
if (feed != ScanFeed.Flatbed) TrySetProperty(device.Properties, WIA_DPS_PAGES, maxPages);
}
private static void ConfigurePage(dynamic item, ScanColour colour, int dpi, string paper)
{
int intent = colour switch
{
ScanColour.Colour => INTENT_COLOUR,
ScanColour.Greyscale => INTENT_GREYSCALE,
_ => INTENT_TEXT,
};
int depth = colour switch
{
ScanColour.Colour => 24,
ScanColour.Greyscale => 8,
_ => 1,
};
int dataType = colour switch
{
ScanColour.Colour => 3, // WIA_DATA_COLOR
ScanColour.Greyscale => 2, // WIA_DATA_GRAYSCALE
_ => 0, // WIA_DATA_THRESHOLD
};
TrySetProperty(item.Properties, WIA_IPS_CUR_INTENT, intent);
TrySetProperty(item.Properties, WIA_IPA_DATATYPE, dataType);
TrySetProperty(item.Properties, WIA_IPA_DEPTH, depth);
TrySetProperty(item.Properties, WIA_IPS_XRES, dpi);
TrySetProperty(item.Properties, WIA_IPS_YRES, dpi);
var size = PaperInches.TryGetValue(paper ?? string.Empty, out var found) ? found : PaperInches["A4 portrait"];
TrySetProperty(item.Properties, WIA_IPS_XPOS, 0);
TrySetProperty(item.Properties, WIA_IPS_YPOS, 0);
TrySetProperty(item.Properties, WIA_IPS_XEXTENT, (int)Math.Round(size.W * dpi));
TrySetProperty(item.Properties, WIA_IPS_YEXTENT, (int)Math.Round(size.H * dpi));
}
private static bool FeederHasPaper(dynamic device)
{
try
{
object? raw = ReadPropertyValue(device.Properties, WIA_DPS_DOCUMENT_HANDLING_STATUS);
if (raw == null) return false;
return ((int)Convert.ToInt32(raw, CultureInfo.InvariantCulture) & FEED_READY) == FEED_READY;
}
catch (COMException) { return false; }
catch (InvalidCastException) { return false; }
}
/// <summary>The copier saying "the tray is empty", which ends a run rather than failing it.</summary>
private static bool IsOutOfPaper(COMException ex)
{
const int WIA_ERROR_PAPER_EMPTY = unchecked((int)0x80210003);
const int WIA_ERROR_PAPER_JAM = unchecked((int)0x80210002);
return ex.ErrorCode == WIA_ERROR_PAPER_EMPTY || ex.ErrorCode == WIA_ERROR_PAPER_JAM;
}
private static string? ReadProperty(dynamic properties, string name)
{
try
{
int count = (int)properties.Count;
for (int i = 1; i <= count; i++)
{
dynamic p = properties[i];
try
{
if (string.Equals((string)p.Name, name, StringComparison.OrdinalIgnoreCase))
return Convert.ToString(p.get_Value(), CultureInfo.InvariantCulture);
}
finally { Release(p); }
}
}
catch (COMException) { }
catch (InvalidCastException) { }
return null;
}
private static object? ReadPropertyValue(dynamic properties, int propertyId)
{
try
{
int count = (int)properties.Count;
for (int i = 1; i <= count; i++)
{
dynamic p = properties[i];
try
{
if ((int)p.PropertyID == propertyId) return p.get_Value();
}
finally { Release(p); }
}
}
catch (COMException) { }
catch (InvalidCastException) { }
return null;
}
/// <summary>
/// Sets one property if the copier has it. Copiers differ wildly in what they expose, so a
/// missing or read-only property is ignored rather than failing the run - the copier then
/// uses its own default for that setting.
/// </summary>
private static void TrySetProperty(dynamic properties, int propertyId, int value)
{
try
{
int count = (int)properties.Count;
for (int i = 1; i <= count; i++)
{
dynamic p = properties[i];
try
{
if ((int)p.PropertyID != propertyId) continue;
if ((bool)p.IsReadOnly) return;
p.set_Value(value);
return;
}
finally { Release(p); }
}
}
catch (COMException) { }
catch (InvalidCastException) { }
}
private static void Release(object? comObject)
{
if (comObject == null) return;
try
{
if (Marshal.IsComObject(comObject)) Marshal.ReleaseComObject(comObject);
}
catch (ArgumentException) { }
catch (InvalidComObjectException) { }
}
}
}
+2 -2
View File
@@ -110,7 +110,7 @@ namespace MmdPdf
{ {
AboutLogoBlock.Inlines.Clear(); AboutLogoBlock.Inlines.Clear();
var hl = new Hyperlink { TextDecorations = null }; var hl = new Hyperlink { TextDecorations = null };
hl.Inlines.Add(new Run("Killer") hl.Inlines.Add(new Run("MMD ")
{ {
FontFamily = UiKit.WordmarkFont, FontFamily = UiKit.WordmarkFont,
FontSize = 21, FontSize = 21,
@@ -133,7 +133,7 @@ namespace MmdPdf
var shadowBrush = new System.Windows.Media.SolidColorBrush( var shadowBrush = new System.Windows.Media.SolidColorBrush(
System.Windows.Media.Color.FromArgb(0xB0, 0, 0, 0)); System.Windows.Media.Color.FromArgb(0xB0, 0, 0, 0));
shadowBrush.Freeze(); shadowBrush.Freeze();
AboutLogoShadowBlock.Inlines.Add(new Run("Killer") AboutLogoShadowBlock.Inlines.Add(new Run("MMD ")
{ {
FontFamily = UiKit.WordmarkFont, FontFamily = UiKit.WordmarkFont,
FontSize = 21, FontSize = 21,
+40
View File
@@ -0,0 +1,40 @@
using System;
using System.Globalization;
using System.Windows;
using MmdPdf.Controls;
using MmdPdf.Features.Scan;
namespace MmdPdf
{
// Pulling pages off a copier. The dialog does the talking to the hardware; this file only
// turns the pages it hands back into a document, which is the same route an imported
// photograph takes (ImportAndZip.cs).
public partial class MainWindow
{
private void Scan_Click(object sender, RoutedEventArgs e)
{
var dlg = new ScanDialog { Owner = this };
bool? answer;
try
{
answer = dlg.ShowDialog();
}
catch (Exception ex)
{
KillerDialog.Show(this, "The copier could not be reached.\n" + ex.Message,
"MMD PDF", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
if (answer != true || dlg.Pages.Length == 0) return;
string name = "Scan " + DateTime.Now.ToString("d MMM yyyy HH:mm", CultureInfo.CurrentCulture);
OpenImagesAsImportedTab(dlg.Pages, name);
// "Make the words searchable" is the copier setting a person ticks before the run;
// the reading itself happens here, on the document that just opened, through the
// text recognition the application already carries.
if (dlg.MakeSearchable) MakeSearchablePdf();
}
}
}
+4 -3
View File
@@ -520,6 +520,7 @@ namespace MmdPdf
[""] = "Str_Lbl_Flatten", [""] = "Str_Lbl_Flatten",
[""] = "Str_Lbl_Ocr", [""] = "Str_Lbl_Ocr",
[""] = "Str_Lbl_Print", [""] = "Str_Lbl_Print",
["\ue8fe"] = "Str_Lbl_Scan",
[""] = "Str_Lbl_Merge", [""] = "Str_Lbl_Merge",
[""] = "Str_Lbl_Extract", [""] = "Str_Lbl_Extract",
[""] = "Str_Lbl_Delete", [""] = "Str_Lbl_Delete",
@@ -590,7 +591,7 @@ namespace MmdPdf
// Text only: caption, no icon (nothing to shed - there'd be nothing left). // Text only: caption, no icon (nothing to shed - there'd be nothing left).
if (textOnly) if (textOnly)
{ {
btn.Width = double.NaN; btn.MinWidth = 0; btn.Height = 34; btn.Padding = new Thickness(8, 5, 8, 5); btn.Width = double.NaN; btn.MinWidth = 0; btn.Height = 27; btn.Padding = new Thickness(8, 3, 8, 3);
btn.Content = new TextBlock btn.Content = new TextBlock
{ {
Text = Loc(key), Text = Loc(key),
@@ -604,7 +605,7 @@ namespace MmdPdf
// Text beside the icon, while it still fits. // Text beside the icon, while it still fits.
if (beside && withLabel) if (beside && withLabel)
{ {
btn.Width = double.NaN; btn.MinWidth = 0; btn.Height = 34; btn.Padding = new Thickness(8, 5, 8, 5); btn.Width = double.NaN; btn.MinWidth = 0; btn.Height = 27; btn.Padding = new Thickness(8, 3, 8, 3);
var row = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center }; var row = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center };
row.Children.Add(new TextBlock row.Children.Add(new TextBlock
{ {
@@ -628,7 +629,7 @@ namespace MmdPdf
// Text under the icon: the icon stacked over a small caption, while it still fits. // Text under the icon: the icon stacked over a small caption, while it still fits.
if (under && withLabel) if (under && withLabel)
{ {
btn.Width = double.NaN; btn.MinWidth = 0; btn.Height = large ? 56 : 52; btn.Padding = new Thickness(6, 4, 6, 4); btn.Width = double.NaN; btn.MinWidth = 0; btn.Height = large ? 45 : 42; btn.Padding = new Thickness(6, 2, 6, 2);
var col = new StackPanel { Orientation = Orientation.Vertical, HorizontalAlignment = HorizontalAlignment.Center }; var col = new StackPanel { Orientation = Orientation.Vertical, HorizontalAlignment = HorizontalAlignment.Center };
col.Children.Add(new TextBlock col.Children.Add(new TextBlock
{ {
+1 -1
View File
@@ -671,7 +671,7 @@ Windows সেটিংস - ডিফল্ট অ্যাপ খুলবে
<sys:String x:Key="Str_Key_WheelLogo">লোগোর উপর হুইল</sys:String> <sys:String x:Key="Str_Key_WheelLogo">লোগোর উপর হুইল</sys:String>
<sys:String x:Key="Str_Key_MiddleDrag">মাঝের বোতামে টানুন</sys:String> <sys:String x:Key="Str_Key_MiddleDrag">মাঝের বোতামে টানুন</sys:String>
<sys:String x:Key="Str_Key_SpaceDrag">স্পেস + টানুন</sys:String> <sys:String x:Key="Str_Key_SpaceDrag">স্পেস + টানুন</sys:String>
<sys:String x:Key="Str_Wordmark_A">Killer</sys:String> <sys:String x:Key="Str_Wordmark_A">MMD </sys:String>
<sys:String x:Key="Str_Wordmark_B">PDF</sys:String> <sys:String x:Key="Str_Wordmark_B">PDF</sys:String>
<sys:String x:Key="Str_Btn_Open">খুলুন</sys:String> <sys:String x:Key="Str_Btn_Open">খুলুন</sys:String>
<sys:String x:Key="Str_Btn_Replace">পরিবর্তন</sys:String> <sys:String x:Key="Str_Btn_Replace">পরিবর্তন</sys:String>
+1 -1
View File
@@ -676,7 +676,7 @@ Změnit jejich velikost na podporovanou? Stránky si zachovají vzhled i proporc
<sys:String x:Key="Str_Key_WheelLogo">Kolečko nad logem</sys:String> <sys:String x:Key="Str_Key_WheelLogo">Kolečko nad logem</sys:String>
<sys:String x:Key="Str_Key_MiddleDrag">Tažení prostředním tlačítkem</sys:String> <sys:String x:Key="Str_Key_MiddleDrag">Tažení prostředním tlačítkem</sys:String>
<sys:String x:Key="Str_Key_SpaceDrag">Mezerník + tažení</sys:String> <sys:String x:Key="Str_Key_SpaceDrag">Mezerník + tažení</sys:String>
<sys:String x:Key="Str_Wordmark_A">Killer</sys:String> <sys:String x:Key="Str_Wordmark_A">MMD </sys:String>
<sys:String x:Key="Str_Wordmark_B">PDF</sys:String> <sys:String x:Key="Str_Wordmark_B">PDF</sys:String>
<sys:String x:Key="Str_Btn_Open">Otevřít</sys:String> <sys:String x:Key="Str_Btn_Open">Otevřít</sys:String>
<sys:String x:Key="Str_Btn_Replace">Nahradit</sys:String> <sys:String x:Key="Str_Btn_Replace">Nahradit</sys:String>
+1 -1
View File
@@ -677,7 +677,7 @@ Auf eine unterstützte Größe skalieren? Aussehen und Proportionen der Seiten b
<sys:String x:Key="Str_Key_WheelLogo">Rad über Logo</sys:String> <sys:String x:Key="Str_Key_WheelLogo">Rad über Logo</sys:String>
<sys:String x:Key="Str_Key_MiddleDrag">Mittlere Taste ziehen</sys:String> <sys:String x:Key="Str_Key_MiddleDrag">Mittlere Taste ziehen</sys:String>
<sys:String x:Key="Str_Key_SpaceDrag">Leertaste + ziehen</sys:String> <sys:String x:Key="Str_Key_SpaceDrag">Leertaste + ziehen</sys:String>
<sys:String x:Key="Str_Wordmark_A">Killer</sys:String> <sys:String x:Key="Str_Wordmark_A">MMD </sys:String>
<sys:String x:Key="Str_Wordmark_B">PDF</sys:String> <sys:String x:Key="Str_Wordmark_B">PDF</sys:String>
<sys:String x:Key="Str_Btn_Open">Öffnen</sys:String> <sys:String x:Key="Str_Btn_Open">Öffnen</sys:String>
<sys:String x:Key="Str_Btn_Replace">Ersetzen</sys:String> <sys:String x:Key="Str_Btn_Replace">Ersetzen</sys:String>
+3 -1
View File
@@ -19,6 +19,7 @@
<sys:String x:Key="Str_TT_SaveAs">Save As (Ctrl+Shift+S)</sys:String> <sys:String x:Key="Str_TT_SaveAs">Save As (Ctrl+Shift+S)</sys:String>
<sys:String x:Key="Str_TT_SaveFlattened">Save Flattened PDF (rasterize all pages - fully uneditable)</sys:String> <sys:String x:Key="Str_TT_SaveFlattened">Save Flattened PDF (rasterize all pages - fully uneditable)</sys:String>
<sys:String x:Key="Str_TT_Print">Print (Ctrl+P)</sys:String> <sys:String x:Key="Str_TT_Print">Print (Ctrl+P)</sys:String>
<sys:String x:Key="Str_TT_Scan">Pull pages off a copier</sys:String>
<sys:String x:Key="Str_TT_MergePDFs">Merge PDFs (combine additional PDFs into this one)</sys:String> <sys:String x:Key="Str_TT_MergePDFs">Merge PDFs (combine additional PDFs into this one)</sys:String>
<sys:String x:Key="Str_TT_ExtractPages">Extract Pages (save selected pages to a new PDF)</sys:String> <sys:String x:Key="Str_TT_ExtractPages">Extract Pages (save selected pages to a new PDF)</sys:String>
<sys:String x:Key="Str_TT_DeletePages">Delete Selected Pages</sys:String> <sys:String x:Key="Str_TT_DeletePages">Delete Selected Pages</sys:String>
@@ -297,6 +298,7 @@ Scale them to a supported size? The pages keep their look and proportions.</sys:
<sys:String x:Key="Str_Lbl_Save">Save</sys:String> <sys:String x:Key="Str_Lbl_Save">Save</sys:String>
<sys:String x:Key="Str_Lbl_Flatten">Flatten</sys:String> <sys:String x:Key="Str_Lbl_Flatten">Flatten</sys:String>
<sys:String x:Key="Str_Lbl_Print">Print</sys:String> <sys:String x:Key="Str_Lbl_Print">Print</sys:String>
<sys:String x:Key="Str_Lbl_Scan">Scan</sys:String>
<sys:String x:Key="Str_Lbl_Select">Select</sys:String> <sys:String x:Key="Str_Lbl_Select">Select</sys:String>
<sys:String x:Key="Str_Lbl_ZoomOut">Zoom Out</sys:String> <sys:String x:Key="Str_Lbl_ZoomOut">Zoom Out</sys:String>
<sys:String x:Key="Str_Lbl_ZoomIn">Zoom In</sys:String> <sys:String x:Key="Str_Lbl_ZoomIn">Zoom In</sys:String>
@@ -694,7 +696,7 @@ Scale them to a supported size? The pages keep their look and proportions.</sys:
<sys:String x:Key="Str_Key_WheelLogo">Wheel on logo</sys:String> <sys:String x:Key="Str_Key_WheelLogo">Wheel on logo</sys:String>
<sys:String x:Key="Str_Key_MiddleDrag">Middle drag</sys:String> <sys:String x:Key="Str_Key_MiddleDrag">Middle drag</sys:String>
<sys:String x:Key="Str_Key_SpaceDrag">Space + drag</sys:String> <sys:String x:Key="Str_Key_SpaceDrag">Space + drag</sys:String>
<sys:String x:Key="Str_Wordmark_A">Killer</sys:String> <sys:String x:Key="Str_Wordmark_A">MMD </sys:String>
<sys:String x:Key="Str_Wordmark_B">PDF</sys:String> <sys:String x:Key="Str_Wordmark_B">PDF</sys:String>
<sys:String x:Key="Str_Btn_Open">Open</sys:String> <sys:String x:Key="Str_Btn_Open">Open</sys:String>
<sys:String x:Key="Str_Btn_Replace">Replace</sys:String> <sys:String x:Key="Str_Btn_Replace">Replace</sys:String>
+1 -1
View File
@@ -671,7 +671,7 @@ Abre Configuración de Windows - Aplicaciones predeterminadas.</sys:String>
<sys:String x:Key="Str_Key_WheelLogo">Rueda sobre el logotipo</sys:String> <sys:String x:Key="Str_Key_WheelLogo">Rueda sobre el logotipo</sys:String>
<sys:String x:Key="Str_Key_MiddleDrag">Arrastrar con el botón central</sys:String> <sys:String x:Key="Str_Key_MiddleDrag">Arrastrar con el botón central</sys:String>
<sys:String x:Key="Str_Key_SpaceDrag">Espacio + arrastrar</sys:String> <sys:String x:Key="Str_Key_SpaceDrag">Espacio + arrastrar</sys:String>
<sys:String x:Key="Str_Wordmark_A">Killer</sys:String> <sys:String x:Key="Str_Wordmark_A">MMD </sys:String>
<sys:String x:Key="Str_Wordmark_B">PDF</sys:String> <sys:String x:Key="Str_Wordmark_B">PDF</sys:String>
<sys:String x:Key="Str_Btn_Open">Abrir</sys:String> <sys:String x:Key="Str_Btn_Open">Abrir</sys:String>
<sys:String x:Key="Str_Btn_Replace">Reemplazar</sys:String> <sys:String x:Key="Str_Btn_Replace">Reemplazar</sys:String>
+1 -1
View File
@@ -687,7 +687,7 @@ Les redimensionner à une taille prise en charge ? Les pages conservent leur app
<sys:String x:Key="Str_Key_WheelLogo">Molette sur le logo</sys:String> <sys:String x:Key="Str_Key_WheelLogo">Molette sur le logo</sys:String>
<sys:String x:Key="Str_Key_MiddleDrag">Glisser avec le bouton du milieu</sys:String> <sys:String x:Key="Str_Key_MiddleDrag">Glisser avec le bouton du milieu</sys:String>
<sys:String x:Key="Str_Key_SpaceDrag">Espace + glisser</sys:String> <sys:String x:Key="Str_Key_SpaceDrag">Espace + glisser</sys:String>
<sys:String x:Key="Str_Wordmark_A">Killer</sys:String> <sys:String x:Key="Str_Wordmark_A">MMD </sys:String>
<sys:String x:Key="Str_Wordmark_B">PDF</sys:String> <sys:String x:Key="Str_Wordmark_B">PDF</sys:String>
<sys:String x:Key="Str_Btn_Open">Ouvrir</sys:String> <sys:String x:Key="Str_Btn_Open">Ouvrir</sys:String>
<sys:String x:Key="Str_Btn_Replace">Remplacer</sys:String> <sys:String x:Key="Str_Btn_Replace">Remplacer</sys:String>
+1 -1
View File
@@ -692,7 +692,7 @@ Megnyílik a Windows Beállítások - Alapértelmezett alkalmazások.</sys:Strin
<sys:String x:Key="Str_Key_WheelLogo">Görgő a logó felett</sys:String> <sys:String x:Key="Str_Key_WheelLogo">Görgő a logó felett</sys:String>
<sys:String x:Key="Str_Key_MiddleDrag">Húzás középső gombbal</sys:String> <sys:String x:Key="Str_Key_MiddleDrag">Húzás középső gombbal</sys:String>
<sys:String x:Key="Str_Key_SpaceDrag">Szóköz + húzás</sys:String> <sys:String x:Key="Str_Key_SpaceDrag">Szóköz + húzás</sys:String>
<sys:String x:Key="Str_Wordmark_A">Killer</sys:String> <sys:String x:Key="Str_Wordmark_A">MMD </sys:String>
<sys:String x:Key="Str_Wordmark_B">PDF</sys:String> <sys:String x:Key="Str_Wordmark_B">PDF</sys:String>
<sys:String x:Key="Str_Btn_Open">Megnyitás</sys:String> <sys:String x:Key="Str_Btn_Open">Megnyitás</sys:String>
<sys:String x:Key="Str_Btn_Replace">Csere</sys:String> <sys:String x:Key="Str_Btn_Replace">Csere</sys:String>
+1 -1
View File
@@ -676,7 +676,7 @@ Windows の設定 - 既定のアプリ を開きます。</sys:String>
<sys:String x:Key="Str_Key_WheelLogo">ロゴ上でホイール</sys:String> <sys:String x:Key="Str_Key_WheelLogo">ロゴ上でホイール</sys:String>
<sys:String x:Key="Str_Key_MiddleDrag">中ボタンでドラッグ</sys:String> <sys:String x:Key="Str_Key_MiddleDrag">中ボタンでドラッグ</sys:String>
<sys:String x:Key="Str_Key_SpaceDrag">Space + ドラッグ</sys:String> <sys:String x:Key="Str_Key_SpaceDrag">Space + ドラッグ</sys:String>
<sys:String x:Key="Str_Wordmark_A">Killer</sys:String> <sys:String x:Key="Str_Wordmark_A">MMD </sys:String>
<sys:String x:Key="Str_Wordmark_B">PDF</sys:String> <sys:String x:Key="Str_Wordmark_B">PDF</sys:String>
<sys:String x:Key="Str_Btn_Open">開く</sys:String> <sys:String x:Key="Str_Btn_Open">開く</sys:String>
<sys:String x:Key="Str_Btn_Replace">置き換え</sys:String> <sys:String x:Key="Str_Btn_Replace">置き換え</sys:String>
+1 -1
View File
@@ -669,7 +669,7 @@ Przeskalować je do obsługiwanego rozmiaru? Strony zachowują swój wygląd i p
<sys:String x:Key="Str_Key_WheelLogo">Kółko nad logo</sys:String> <sys:String x:Key="Str_Key_WheelLogo">Kółko nad logo</sys:String>
<sys:String x:Key="Str_Key_MiddleDrag">Przeciąganie środkowym przyciskiem</sys:String> <sys:String x:Key="Str_Key_MiddleDrag">Przeciąganie środkowym przyciskiem</sys:String>
<sys:String x:Key="Str_Key_SpaceDrag">Spacja + przeciąganie</sys:String> <sys:String x:Key="Str_Key_SpaceDrag">Spacja + przeciąganie</sys:String>
<sys:String x:Key="Str_Wordmark_A">Killer</sys:String> <sys:String x:Key="Str_Wordmark_A">MMD </sys:String>
<sys:String x:Key="Str_Wordmark_B">PDF</sys:String> <sys:String x:Key="Str_Wordmark_B">PDF</sys:String>
<sys:String x:Key="Str_Btn_Open">Otwórz</sys:String> <sys:String x:Key="Str_Btn_Open">Otwórz</sys:String>
<sys:String x:Key="Str_Btn_Replace">Zamień</sys:String> <sys:String x:Key="Str_Btn_Replace">Zamień</sys:String>
+1 -1
View File
@@ -671,7 +671,7 @@ Desteklenen bir boyuta ölçeklensin mi? Sayfaların görünümü ve oranları k
<sys:String x:Key="Str_Key_WheelLogo">Logo üzerinde tekerlek</sys:String> <sys:String x:Key="Str_Key_WheelLogo">Logo üzerinde tekerlek</sys:String>
<sys:String x:Key="Str_Key_MiddleDrag">Orta tuşla sürükleme</sys:String> <sys:String x:Key="Str_Key_MiddleDrag">Orta tuşla sürükleme</sys:String>
<sys:String x:Key="Str_Key_SpaceDrag">Boşluk + sürükleme</sys:String> <sys:String x:Key="Str_Key_SpaceDrag">Boşluk + sürükleme</sys:String>
<sys:String x:Key="Str_Wordmark_A">Killer</sys:String> <sys:String x:Key="Str_Wordmark_A">MMD </sys:String>
<sys:String x:Key="Str_Wordmark_B">PDF</sys:String> <sys:String x:Key="Str_Wordmark_B">PDF</sys:String>
<sys:String x:Key="Str_Btn_Open">Aç</sys:String> <sys:String x:Key="Str_Btn_Open">Aç</sys:String>
<sys:String x:Key="Str_Btn_Replace">Değiştir</sys:String> <sys:String x:Key="Str_Btn_Replace">Değiştir</sys:String>
+1 -1
View File
@@ -675,7 +675,7 @@ MMD PDF 不会创建两个已安装的副本。</sys:String>
<sys:String x:Key="Str_Key_WheelLogo">在徽标上滚动滚轮</sys:String> <sys:String x:Key="Str_Key_WheelLogo">在徽标上滚动滚轮</sys:String>
<sys:String x:Key="Str_Key_MiddleDrag">中键拖动</sys:String> <sys:String x:Key="Str_Key_MiddleDrag">中键拖动</sys:String>
<sys:String x:Key="Str_Key_SpaceDrag">空格 + 拖动</sys:String> <sys:String x:Key="Str_Key_SpaceDrag">空格 + 拖动</sys:String>
<sys:String x:Key="Str_Wordmark_A">Killer</sys:String> <sys:String x:Key="Str_Wordmark_A">MMD </sys:String>
<sys:String x:Key="Str_Wordmark_B">PDF</sys:String> <sys:String x:Key="Str_Wordmark_B">PDF</sys:String>
<sys:String x:Key="Str_Btn_Open">打开</sys:String> <sys:String x:Key="Str_Btn_Open">打开</sys:String>
<sys:String x:Key="Str_Btn_Replace">替换</sys:String> <sys:String x:Key="Str_Btn_Replace">替换</sys:String>
+1 -1
View File
@@ -673,7 +673,7 @@ MMD PDF 不會建立兩個已安裝的複本。</sys:String>
<sys:String x:Key="Str_Key_WheelLogo">在標誌上滾動滾輪</sys:String> <sys:String x:Key="Str_Key_WheelLogo">在標誌上滾動滾輪</sys:String>
<sys:String x:Key="Str_Key_MiddleDrag">中鍵拖曳</sys:String> <sys:String x:Key="Str_Key_MiddleDrag">中鍵拖曳</sys:String>
<sys:String x:Key="Str_Key_SpaceDrag">空白鍵 + 拖曳</sys:String> <sys:String x:Key="Str_Key_SpaceDrag">空白鍵 + 拖曳</sys:String>
<sys:String x:Key="Str_Wordmark_A">Killer</sys:String> <sys:String x:Key="Str_Wordmark_A">MMD </sys:String>
<sys:String x:Key="Str_Wordmark_B">PDF</sys:String> <sys:String x:Key="Str_Wordmark_B">PDF</sys:String>
<sys:String x:Key="Str_Btn_Open">開啟</sys:String> <sys:String x:Key="Str_Btn_Open">開啟</sys:String>
<sys:String x:Key="Str_Btn_Replace">取代</sys:String> <sys:String x:Key="Str_Btn_Replace">取代</sys:String>
+84 -43
View File
@@ -3,17 +3,55 @@
xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
xmlns:sys="clr-namespace:System;assembly=mscorlib"> xmlns:sys="clr-namespace:System;assembly=mscorlib">
<!-- MMD Steel Group corporate theme. The only theme this application ships. <!-- MMD Steel Group corporate theme - the only theme this application ships.
Palette is the Employee Portal's: C8102E red used sparingly, 23221E ink, Values are the new MMD shell design system ("red plate", light-only, no dark
F1F1EC platinum ground, FFFFFF paper. Square edges, no grain, no shadows. --> variant): white work area, silver chrome, desk grey behind the document,
23221E ink, C8102E red partitioned to primary action / focus / urgency only.
Square by default. Held for Ben's sign-off: dark table header rows and the
window-chrome variant (V2 vs V7) are still being chosen. -->
<!-- The one merged top bar, locked 2026-08-28. MMD red, the white lockup on it,
and the window buttons at its right end. These are the design system's own
values; no colour here is new. -->
<LinearGradientBrush x:Key="TitleBarBrush" StartPoint="0,0" EndPoint="0,1" po:Freeze="True">
<GradientStop Offset="0.0" Color="#d4213f"/>
<GradientStop Offset="0.55" Color="#c8102e"/>
<GradientStop Offset="1.0" Color="#a50d26"/>
</LinearGradientBrush>
<sys:Double x:Key="TitleBarHeight">42</sys:Double>
<Thickness x:Key="TitleBarPadding">14,0,0,0</Thickness>
<SolidColorBrush x:Key="CaptionGlyphBrush" Color="#ffffff" po:Freeze="True"/>
<sys:Double x:Key="CaptionButtonWidth">24</sys:Double>
<sys:Double x:Key="CaptionButtonHeight">24</sys:Double>
<Thickness x:Key="CaptionButtonsMargin">0,0,9,0</Thickness>
<!-- The window buttons sit on the red band, so they are white. The close button
inverts on hover - white plate, red cross - because red on red is invisible,
which is exactly what the first build of this band showed. -->
<SolidColorBrush x:Key="CaptionButtonBrush" Color="#1fffffff" po:Freeze="True"/>
<SolidColorBrush x:Key="CaptionHoverBrush" Color="#33ffffff" po:Freeze="True"/>
<SolidColorBrush x:Key="CaptionCloseBrush" Color="#ffffff" po:Freeze="True"/>
<SolidColorBrush x:Key="CaptionCloseHoverBrush" Color="#ffffff" po:Freeze="True"/>
<SolidColorBrush x:Key="CaptionCloseHoverFgBrush" Color="#c8102e" po:Freeze="True"/>
<sys:Double x:Key="WordmarkShadowOpacity">0</sys:Double>
<sys:Double x:Key="TitleIconSize">0</sys:Double>
<Thickness x:Key="TitleIconMargin">0</Thickness>
<!-- A window that is in front, for the dialogs: white to silver with a red top edge.
The bevel lives on title bars only; buttons are flat. -->
<LinearGradientBrush x:Key="WindowFrontTitleBrush" StartPoint="0,0" EndPoint="0,1" po:Freeze="True">
<GradientStop Offset="0.0" Color="#ffffff"/>
<GradientStop Offset="0.62" Color="#ffffff"/>
<GradientStop Offset="1.0" Color="#f1f1ec"/>
</LinearGradientBrush>
<sys:Double x:Key="FooterHeight">26</sys:Double>
<!-- Surfaces --> <!-- Surfaces -->
<SolidColorBrush x:Key="SurfaceBrush" Color="#ffffff" po:Freeze="True"/> <SolidColorBrush x:Key="SurfaceBrush" Color="#f1f1ec" po:Freeze="True"/>
<SolidColorBrush x:Key="PaneBrush" Color="#f1f1ec" po:Freeze="True"/> <SolidColorBrush x:Key="PaneBrush" Color="#e4e3dc" po:Freeze="True"/>
<SolidColorBrush x:Key="RowHoverBrush" Color="#ecece6" po:Freeze="True"/> <SolidColorBrush x:Key="RowHoverBrush" Color="#f7f7f3" po:Freeze="True"/>
<SolidColorBrush x:Key="MenuHoverBrush" Color="#ecece6" po:Freeze="True"/> <SolidColorBrush x:Key="MenuHoverBrush" Color="#f7f7f3" po:Freeze="True"/>
<SolidColorBrush x:Key="BackgroundBrush" Color="#f1f1ec" po:Freeze="True"/> <SolidColorBrush x:Key="BackgroundBrush" Color="#f3f3f0" po:Freeze="True"/>
<SolidColorBrush x:Key="BgRecentPanel" Color="#f1f1ec" po:Freeze="True"/> <SolidColorBrush x:Key="BgRecentPanel" Color="#f3f3f0" po:Freeze="True"/>
<SolidColorBrush x:Key="BgFlyout" Color="#ffffff" po:Freeze="True"/> <SolidColorBrush x:Key="BgFlyout" Color="#ffffff" po:Freeze="True"/>
<LinearGradientBrush x:Key="TabStripFadeBrush" StartPoint="0,0" EndPoint="0,1" po:Freeze="True"> <LinearGradientBrush x:Key="TabStripFadeBrush" StartPoint="0,0" EndPoint="0,1" po:Freeze="True">
<GradientStop Offset="0.0" Color="#0023221e"/> <GradientStop Offset="0.0" Color="#0023221e"/>
@@ -21,12 +59,12 @@
<GradientStop Offset="1.0" Color="#2223221e"/> <GradientStop Offset="1.0" Color="#2223221e"/>
</LinearGradientBrush> </LinearGradientBrush>
<SolidColorBrush x:Key="MenuBackgroundBrush" Color="#ffffff" po:Freeze="True"/> <SolidColorBrush x:Key="MenuBackgroundBrush" Color="#ffffff" po:Freeze="True"/>
<SolidColorBrush x:Key="CardBorderBrush" Color="#d9d9d2" po:Freeze="True"/> <SolidColorBrush x:Key="CardBorderBrush" Color="#e6e5df" po:Freeze="True"/>
<!-- Accent. Red carries the active tool and the one primary action, nothing else. --> <!-- Accent. Red carries the active tool and the one primary action, nothing else. -->
<SolidColorBrush x:Key="PrimaryBrush" Color="#c8102e" po:Freeze="True"/> <SolidColorBrush x:Key="PrimaryBrush" Color="#c8102e" po:Freeze="True"/>
<SolidColorBrush x:Key="PrimaryHoverBrush" Color="#ad0e28" po:Freeze="True"/> <SolidColorBrush x:Key="PrimaryHoverBrush" Color="#a80d24" po:Freeze="True"/>
<SolidColorBrush x:Key="PrimaryPressedBrush" Color="#920c21" po:Freeze="True"/> <SolidColorBrush x:Key="PrimaryPressedBrush" Color="#8e0b20" po:Freeze="True"/>
<SolidColorBrush x:Key="OnPrimaryBrush" Color="#ffffff" po:Freeze="True"/> <SolidColorBrush x:Key="OnPrimaryBrush" Color="#ffffff" po:Freeze="True"/>
<SolidColorBrush x:Key="OutlineBtnBrush" Color="#23221e" po:Freeze="True"/> <SolidColorBrush x:Key="OutlineBtnBrush" Color="#23221e" po:Freeze="True"/>
<SolidColorBrush x:Key="HeaderLineBrush" Color="#23221e" po:Freeze="True"/> <SolidColorBrush x:Key="HeaderLineBrush" Color="#23221e" po:Freeze="True"/>
@@ -35,39 +73,39 @@
<SolidColorBrush x:Key="InstallBtnBg" Color="#f1f1ec" po:Freeze="True"/> <SolidColorBrush x:Key="InstallBtnBg" Color="#f1f1ec" po:Freeze="True"/>
<!-- Borders --> <!-- Borders -->
<SolidColorBrush x:Key="MenuBorderBrush" Color="#d9d9d2" po:Freeze="True"/> <SolidColorBrush x:Key="MenuBorderBrush" Color="#d7d6ce" po:Freeze="True"/>
<SolidColorBrush x:Key="DropBorder" Color="#b9b9b0" po:Freeze="True"/> <SolidColorBrush x:Key="DropBorder" Color="#b9b8ae" po:Freeze="True"/>
<SolidColorBrush x:Key="PaneBorderBrush" Color="#d9d9d2" po:Freeze="True"/> <SolidColorBrush x:Key="PaneBorderBrush" Color="#d7d6ce" po:Freeze="True"/>
<SolidColorBrush x:Key="AppBorderBrush" Color="#d9d9d2" po:Freeze="True"/> <SolidColorBrush x:Key="AppBorderBrush" Color="#b9b8ae" po:Freeze="True"/>
<!-- Selection: dark on light, the way the portal tables read. --> <!-- Selection: dark on light, the way the portal tables read. -->
<SolidColorBrush x:Key="RowSelectedBrush" Color="#23221e" po:Freeze="True"/> <SolidColorBrush x:Key="RowSelectedBrush" Color="#23221e" po:Freeze="True"/>
<SolidColorBrush x:Key="OutlinePressedBrush" Color="#f6e0e4" po:Freeze="True"/> <SolidColorBrush x:Key="OutlinePressedBrush" Color="#fbeef0" po:Freeze="True"/>
<SolidColorBrush x:Key="SelectionBg" Color="#23221e" po:Freeze="True"/> <SolidColorBrush x:Key="SelectionBg" Color="#23221e" po:Freeze="True"/>
<SolidColorBrush x:Key="SelectionFg" Color="#ffffff" po:Freeze="True"/> <SolidColorBrush x:Key="SelectionFg" Color="#ffffff" po:Freeze="True"/>
<SolidColorBrush x:Key="SettingsOpenRowBg" Color="#ecece6" po:Freeze="True"/> <SolidColorBrush x:Key="SettingsOpenRowBg" Color="#f1f1ec" po:Freeze="True"/>
<!-- Type --> <!-- Type -->
<SolidColorBrush x:Key="TextBrush" Color="#23221e" po:Freeze="True"/> <SolidColorBrush x:Key="TextBrush" Color="#23221e" po:Freeze="True"/>
<SolidColorBrush x:Key="MutedTextBrush" Color="#5c5a54" po:Freeze="True"/> <SolidColorBrush x:Key="MutedTextBrush" Color="#3c3a34" po:Freeze="True"/>
<SolidColorBrush x:Key="MenuTextBrush" Color="#23221e" po:Freeze="True"/> <SolidColorBrush x:Key="MenuTextBrush" Color="#23221e" po:Freeze="True"/>
<SolidColorBrush x:Key="ChromeTextBrush" Color="#23221e" po:Freeze="True"/> <SolidColorBrush x:Key="ChromeTextBrush" Color="#ffffff" po:Freeze="True"/>
<SolidColorBrush x:Key="FilenameBrush" Color="#5c5a54" po:Freeze="True"/> <SolidColorBrush x:Key="FilenameBrush" Color="#e8ffffff" po:Freeze="True"/>
<SolidColorBrush x:Key="DimTextBrush" Color="#6f6d66" po:Freeze="True"/> <SolidColorBrush x:Key="DimTextBrush" Color="#767368" po:Freeze="True"/>
<SolidColorBrush x:Key="TextFooter" Color="#8b8880" po:Freeze="True"/> <SolidColorBrush x:Key="TextFooter" Color="#6b6a64" po:Freeze="True"/>
<SolidColorBrush x:Key="DangerRed" Color="#c8102e" po:Freeze="True"/> <SolidColorBrush x:Key="DangerRed" Color="#c8102e" po:Freeze="True"/>
<!-- Furniture --> <!-- Furniture -->
<SolidColorBrush x:Key="BgDragHandle" Color="#b9b9b0" po:Freeze="True"/> <SolidColorBrush x:Key="BgDragHandle" Color="#c7c6bd" po:Freeze="True"/>
<SolidColorBrush x:Key="DragLine" Color="#d9d9d2" po:Freeze="True"/> <SolidColorBrush x:Key="DragLine" Color="#d7d6ce" po:Freeze="True"/>
<SolidColorBrush x:Key="ScrollThumbBrush" Color="#5523221e" po:Freeze="True"/> <SolidColorBrush x:Key="ScrollThumbBrush" Color="#5523221e" po:Freeze="True"/>
<SolidColorBrush x:Key="ScrollThumbHoverBrush" Color="#23221e" po:Freeze="True"/> <SolidColorBrush x:Key="ScrollThumbHoverBrush" Color="#23221e" po:Freeze="True"/>
<SolidColorBrush x:Key="TableHeaderBrush" Color="#f1f1ec" po:Freeze="True"/> <SolidColorBrush x:Key="TableHeaderBrush" Color="#f1f1ec" po:Freeze="True"/>
<SolidColorBrush x:Key="RowAltBrush" Color="#0a23221e" po:Freeze="True"/> <SolidColorBrush x:Key="RowAltBrush" Color="#0823221e" po:Freeze="True"/>
<SolidColorBrush x:Key="InputBorderBrush" Color="#c9c9c1" po:Freeze="True"/> <SolidColorBrush x:Key="InputBorderBrush" Color="#c7c6bd" po:Freeze="True"/>
<SolidColorBrush x:Key="InputHoverBrush" Color="#23221e" po:Freeze="True"/> <SolidColorBrush x:Key="InputHoverBrush" Color="#23221e" po:Freeze="True"/>
<SolidColorBrush x:Key="TreeLineBrush" Color="#d9d9d2" po:Freeze="True"/> <SolidColorBrush x:Key="TreeLineBrush" Color="#e6e5df" po:Freeze="True"/>
<SolidColorBrush x:Key="SliderTrack" Color="#d9d9d2" po:Freeze="True"/> <SolidColorBrush x:Key="SliderTrack" Color="#d7d6ce" po:Freeze="True"/>
<SolidColorBrush x:Key="RadioAccent" Color="#c8102e" po:Freeze="True"/> <SolidColorBrush x:Key="RadioAccent" Color="#c8102e" po:Freeze="True"/>
<SolidColorBrush x:Key="BgOverlay" Color="#8823221e" po:Freeze="True"/> <SolidColorBrush x:Key="BgOverlay" Color="#8823221e" po:Freeze="True"/>
@@ -77,29 +115,32 @@
<sys:Double x:Key="HeaderShadowOpacity">0</sys:Double> <sys:Double x:Key="HeaderShadowOpacity">0</sys:Double>
<!-- The workspace behind the page. Platinum, one step darker than the page itself. --> <!-- The workspace behind the page. Platinum, one step darker than the page itself. -->
<SolidColorBrush x:Key="BgCanvas" Color="#e4e4de" po:Freeze="True"/> <SolidColorBrush x:Key="BgCanvas" Color="#d4d0c6" po:Freeze="True"/>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#f6e0e4" po:Freeze="True"/> <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#fbeef0" po:Freeze="True"/>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="#23221e" po:Freeze="True"/> <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="#23221e" po:Freeze="True"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="#ecece6" po:Freeze="True"/> <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="#f1f1ec" po:Freeze="True"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="#23221e" po:Freeze="True"/> <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="#23221e" po:Freeze="True"/>
<!-- Shortcut keyboard view. Graded ink rather than a rainbow; red marks editing only. --> <!-- Shortcut keyboard view. Graded ink rather than a rainbow; red marks editing only. -->
<SolidColorBrush x:Key="KsCatFile" Color="#23221e" po:Freeze="True"/> <SolidColorBrush x:Key="KsCatFile" Color="#23221e" po:Freeze="True"/>
<SolidColorBrush x:Key="KsCatTools" Color="#5c5a54" po:Freeze="True"/> <SolidColorBrush x:Key="KsCatTools" Color="#3c3a34" po:Freeze="True"/>
<SolidColorBrush x:Key="KsCatEdit" Color="#c8102e" po:Freeze="True"/> <SolidColorBrush x:Key="KsCatEdit" Color="#c8102e" po:Freeze="True"/>
<SolidColorBrush x:Key="KsCatNav" Color="#6f6d66" po:Freeze="True"/> <SolidColorBrush x:Key="KsCatNav" Color="#767368" po:Freeze="True"/>
<SolidColorBrush x:Key="KsCatView" Color="#8b8880" po:Freeze="True"/> <SolidColorBrush x:Key="KsCatView" Color="#6b6a64" po:Freeze="True"/>
<SolidColorBrush x:Key="KsCatSearch" Color="#5c5a54" po:Freeze="True"/> <SolidColorBrush x:Key="KsCatSearch" Color="#3c3a34" po:Freeze="True"/>
<SolidColorBrush x:Key="KsCatHelp" Color="#8b8880" po:Freeze="True"/> <SolidColorBrush x:Key="KsCatHelp" Color="#6b6a64" po:Freeze="True"/>
<SolidColorBrush x:Key="KsCatOcr" Color="#6f6d66" po:Freeze="True"/> <SolidColorBrush x:Key="KsCatOcr" Color="#767368" po:Freeze="True"/>
<!-- No text stroke on a light corporate surface. --> <!-- No text stroke on a light corporate surface. -->
<DropShadowEffect x:Key="TextStroke" Color="Black" BlurRadius="0" ShadowDepth="0" Direction="270" Opacity="0"/> <DropShadowEffect x:Key="TextStroke" Color="Black" BlurRadius="0" ShadowDepth="0" Direction="270" Opacity="0"/>
<!-- Square. The portal's chrome is square-edged; rounded corners are not MMD. --> <!-- Four-pixel corners, per the design system locked 2026-08-28. The window itself
<CornerRadius x:Key="ControlCornerRadius">0</CornerRadius> stays square at the top because the band runs edge to edge when maximised, which
<CornerRadius x:Key="SmallCornerRadius">0</CornerRadius> is how these machines are used. -->
<CornerRadius x:Key="FlyoutCornerRadius">0</CornerRadius> <CornerRadius x:Key="ControlCornerRadius">4</CornerRadius>
<CornerRadius x:Key="AccentSwatchCornerRadius">0</CornerRadius> <CornerRadius x:Key="SmallCornerRadius">4</CornerRadius>
<CornerRadius x:Key="FlyoutCornerRadius">4</CornerRadius>
<CornerRadius x:Key="AccentSwatchCornerRadius">4</CornerRadius>
<CornerRadius x:Key="TitleBarCornerRadius">0</CornerRadius>
</ResourceDictionary> </ResourceDictionary>
+50
View File
@@ -0,0 +1,50 @@
# MMD PDF — feature backlog
Status: 🟢 proven end-to-end on a real build with evidence · 🟡 in progress ·
⚪ not started · ⛔ blocked. A green build or a passing unit test is not 🟢.
| ID | Feature | Ver | Owner | Status | Sign-off | PRD / Screen / Test |
|---|---|---|---|---|---|---|
| PDF-01 | Open and read a PDF | 1.7.5 | kua-agent | 🟢 | unsigned | PRD · SCREENS Main · TC-01 |
| PDF-02 | Change text already on the page | 1.7.5 | kua-agent | 🟢 | unsigned | PRD · SCREENS EditText · TC-02 |
| PDF-03 | **The old text is removed, not covered** | 0.1 | kua-agent | 🟢 | unsigned | PRD req.1 · SCREENS Save · TC-03 |
| PDF-04 | Save over the original, with confirmation | 1.7.5 | kua-agent | 🟢 | unsigned | SCREENS Save · TC-04 |
| PDF-05 | Runs with no admin rights, portable | 1.7.5 | kua-agent | 🟢 | unsigned | PRD req.4 · TC-05 |
| PDF-06 | Leaves the Windows default-PDF setting alone | 1.7.5 | kua-agent | 🟢 | unsigned | PRD "why" · TC-06 |
| PDF-07 | MMD name, icon and word-mark | 2026.8.1 | kua-agent | 🟢 | unsigned | SCREENS all · TC-07 |
| PDF-08 | MMD shell design system, light only, no theme picker | 2026.8.1 | kua-agent | 🟢 | unsigned | PRD req.3 · SCREENS · TC-08 |
| PDF-09 | Works at 800×600 | 2026.8.1 | kua-agent | 🟢 | unsigned | PRD req.2 · SCREENS Directions · TC-09 |
| PDF-10 | Fill and sign forms | 1.7.5 | — | ⚪ | unsigned | PRD in-scope · TC-10 |
| PDF-11 | Mark up: highlight, draw, shapes, stamps, images | 1.7.5 | — | ⚪ | unsigned | PRD in-scope · TC-11 |
| PDF-12 | Page work: merge, extract, delete, reorder, crop | 1.7.5 | — | ⚪ | unsigned | PRD in-scope · TC-12 |
| PDF-13 | Turn a scan into searchable text | 1.7.5 | — | ⚪ | unsigned | PRD in-scope · TC-13 |
| PDF-14 | Pull pages off a network copier | 2026.8.1 | kua-agent | 🟡 | unsigned | PRD in-scope · SCREENS Scan · TC-14 |
| PDF-15 | French interface | 1.7.5 | — | ⚪ | unsigned | PRD req.5 · TC-15 |
| PDF-16 | Print | 1.7.5 | — | ⚪ | unsigned | PRD in-scope · TC-16 |
| PDF-17 | Code signing under an MMD certificate | 2026.8.1 | kua-agent | 🟢 | unsigned | PRD open q. · TC-17 |
| PDF-18 | Deploy through Intune | 2026.8.1 | kua-agent | 🟢 | unsigned | PRD open q. · TC-18 |
| PDF-19 | Official square app mark for 1624px | — | — | ⛔ | unsigned | PRD open q. |
## Why PDF-14 is still 🟡
The copier conversation is written and the dialog is proven — it opens, it lists
what it finds, and with nothing attached it says "No copier found on this
machine" and refuses to pretend. **The page transfer itself has never met real
hardware**, because the test machine has no scanner. That is the one thing left
before it is green.
## Why the ⛔
- **PDF-08, PDF-09** — the design failed an independent review on 2026-08-27
(3 blockers, 5 major). Nothing may be built on it until it passes and Ben
signs it. Findings and the fix state are in `SIGNOFFS.md`.
- **PDF-19** — no official small mark exists anywhere in MMD's artwork, and only
Ben can authorise commissioning one. An agent must not draw it.
## Evidence behind the 🟢 rows
All on **mmd-win-test-01** (VM 203, hypervisor mmd-fsn-03, Falkenstein),
2026-08-27, driven by keyboard and mouse only through
`/opt/mmd-wintest/wintest`. Screenshots in `design/shots/` are design mockups,
not this evidence; the run itself is recorded in Hindsight under tag
`mmd-pdf` + `live-verified`.
+74
View File
@@ -0,0 +1,74 @@
# MMD PDF — product requirements
| | |
|---|---|
| Version | 0.1 |
| Date | 2026-08-27 |
| Sign-off | **unsigned** |
| Repo | `gitea.baobab-ts.com/mmd-it/mmd-pdf` |
## What it is
A PDF editor for MMD staff on Windows. Free, open source, no subscription, no
account, nothing sent anywhere. It is a GPL-3.0 fork of KillerPDF 1.7.5 and
stays GPL-3.0; `FORK-NOTICE.md` records what MMD changed.
## Why it exists
Staff need to write inside PDF documents — fill a delivery note, correct a
figure, sign a form, mark up a drawing, turn a scan into searchable text.
Until now there was no sanctioned tool. PDFgear was evaluated on 2026-07-20 and
blocked: it forged the Windows default-PDF registry setting behind the consent
screen, confirmed by its own paid auditor.
## Who uses it
- **Plant and office staff** across Dakar, Bamako, Kigali, Luanda, Conakry,
Ouagadougou and Dubai. Not computer people. Many read French, not English.
- **Their screens are 800×600.** Every surface must work at that size. This is
a hard constraint, not a target.
## In scope
- Read a PDF, and change text that is already on the page.
- Fill and sign forms, including comb fields, checkboxes and radio groups.
- Mark up: highlight, draw, shapes, stamps, images, signatures.
- Page work: merge, extract, delete, reorder, crop, rotate.
- Turn a scan into searchable text (on-device, eleven languages).
- Pull pages straight off a network copier. **Not built.**
- Print.
## Not in scope
- Anything that leaves the machine. No cloud, no account, no telemetry.
- Collaborative editing.
- Creating documents from scratch — that is Word's job.
## Hard requirements
1. **An edited figure must be gone from the file, not covered over.** Upstream
painted a white box over the original text and drew the replacement on top;
extracting text returned both values. A corrected quantity on a delivery
note that leaves the building must not carry the old number inside it.
2. **800×600.** See above.
3. **The MMD shell design system** ("red plate"), light only, official MMD
lockup, red reserved for one primary action, focus, and urgency.
4. **No admin rights needed** to install or run.
5. **French and English** interface at minimum; the upstream base ships twelve
locales.
## Data
The user's own files on their own machine. Nothing else. No network calls
except the optional download of a higher-accuracy text-recognition language
pack, and the scanner on the local network once that is built.
## Open questions
- No official square MMD app mark exists. The icon crops the lockup's baobab
disc; it reads from 32px and is muddy at 1624px.
- Which of three toolbar treatments (see `SCREENS.md`) — the independent
reviewer recommends C.
- Code signing. Re-signing under an MMD certificate resets the reputation the
upstream signed build has, and SmartScreen is set to block without an
override on the pilot group.
+118
View File
@@ -0,0 +1,118 @@
# MMD PDF — screens
Every screen is 800×600. Budget: 28px title bar + 2023px status bar leaves
about 550px for the toolbar and the page.
Design source files: `design/*.dc.html`, generated by `design/build.py` on the
red-plate stylesheet `design/redplate.css` (lifted verbatim from the MMD shell
design system, not retyped). Renders: `design/shots/*.png`.
**These are mockups. The design failed independent review on 2026-08-27 and is
not approved. Do not build from them yet.**
---
## Directions — the toolbar decision
Not a screen; a comparison sheet. A PDF editor carries about thirty tools and
thirty named tools need roughly 1400px of row. Three ways out:
```
+------------------------------------------------------------------+
| A - one row, named tools |
| [Open][Save][Print][Scan][OCR] | [Select][Text][Mark]... [Fit][…]|
| 40px chrome · 512px page · 16 of 30 tools behind the "…" |
+------------------------------------------------------------------+
| B - icons only, nothing hidden |
| [][][][][][][][][][][][][][][][][][][][][] [Fit - +] |
| 30px chrome · 522px page · nothing hidden, everything unlabelled |
+------------------------------------------------------------------+
| C - three tabs, named tools, nothing hidden |
| DOCUMENT | EDIT | PAGES |
| [Select][Text][Mark][Draw][Shape][Sign][Stamp][Image] [Fit - +] |
| 56px chrome · 496px page · nothing hidden, one click deeper |
+------------------------------------------------------------------+
```
Independent reviewer's recommendation: **C**. A is the one drawn on every other
screen and it hides sixteen tools behind an unlabelled "…" button; B asks a
first-time clerk to guess thirty glyphs.
---
## Main — a document open
```
+------------------------------------------------------------------+
| [MMD] MMD PDF / BON-2026-0847 delivery note.pdf - [] X | 28px
+------------------------------------------------------------------+
| [Open][Save][Print][Scan][OCR] | [Select][Text][Mark][Draw] | 40px
| [Shape][Sign] | [Pages][Search][Undo] [FIT WIDTH - +] […] |
+---+--------------------------------------------------------------+
| > | |
| 1 | +--------------------------------------+ |
| | | MMD STEEL GROUP - DELIVERY NOTE | |
| | | | |
| | | Supplier: Fabrimetal SARL | |
| | | Reference: BON-2026-0847 | |
| | | Quantity delivered: 12 tonnes | |
| | +--------------------------------------+ |
+---+--------------------------------------------------------------+
| Ready 1 OF 1 · FIT WIDTH · 100% | 23px
+------------------------------------------------------------------+
```
- **Title bar** — official MMD lockup at 13px, then the application name in
Fraunces, then the file name. Window controls right.
- **Tool strip** — grouped: document actions, then editing tools, then
navigation. The armed tool is a solid red fill with white glyph; that is the
only red on the screen.
- **Closed page strip** (2426px) — a chevron to open the page list and the
page you are on. It reclaims 62px over the open rail, which matters at 800px.
- **Page** — white on the F3F3F0 field, hairline border, no shadow.
- **Status bar** — what the application is doing, left; where you are, right.
## EditText — changing a figure on the page
Same as Main plus a **context bar** (28px) that exists only while the Text tool
is armed: colour swatches, size, bold/italic, and the sentence
"Enter keeps it · Esc throws it away" — both were guessed wrong in testing.
The edited run is boxed in red on a red wash. The status bar turns red and
says **"The old quantity is removed from the file when you save."** That is the
moment the original is destroyed and it must be unmissable.
## PageRail — the page list open
The 88px rail replaces the closed strip: `PAGES / MARKS` headers, then one
thumbnail per page. The page you are on carries a 3px red left edge.
## Nothing — no document open
The editing tools are dimmed to the label tone. The work area becomes a dense
table of recent documents: reference, document, pages, state. Row click opens.
Two actions in the section header: `Open a file…` outlined, `Scan a document`
the one red fill.
## Save — writing over the original
The application stays visible and dimmed behind. The dialog is white, ink-ruled
title, and says in plain words what changes:
```
Save
Write the change into BON-2026-0847 delivery note.pdf?
WAS Quantity delivered: 12 tonnes
BECOMES Quantity delivered: 19 tonnes
THE OLD TEXT is taken out of the file, not covered over
[ Cancel ] [ Keep both ] [ Replace ]
```
`Replace` is the one red fill — the destructive action is correctly primary
because it is what the person came to do. **Open finding:** "Keep both" does not
yet say what filename it produces.
## Scan — pulling pages off a copier
**Not built.** Same dialog shape: copier, feed, colour, detail, and text-search
on by default in French and English. `Scan` is the one red fill.
+95
View File
@@ -0,0 +1,95 @@
# MMD PDF — versions and sign-off log
Only Ben signs. An agent's own screenshots, a green build, or an independent
reviewer's PASS are all evidence — none of them is a signature.
## Log
| Date | Feature | Ver | Event | Who | Evidence |
|---|---|---|---|---|---|
| 2026-08-27 | PDF-01…06 | 1.7.5 / 0.1 | proven end-to-end, keyboard and mouse only, on mmd-win-test-01 | kua-agent | Hindsight `mmd-pdf` + `live-verified` |
| 2026-08-27 | PDF-03 | 0.1 | `12 tonnes` no longer recoverable from the saved file; `19 tonnes` present; other lines intact | kua-agent | `pdftotext` before/after on the VM |
| 2026-08-27 | PDF-06 | 1.7.5 | `.pdf` UserChoice ProgId + Hash unchanged; deny ACE intact; no helper binary | kua-agent | registry read before and after install and first open |
| 2026-08-27 | PDF-07…09 | 0.1 | **DESIGN-FAIL** — independent adversarial review, 3 blockers, 5 major, 3 minor | independent reviewer (separate agent, default-FAIL) | findings below |
| 2026-08-28 | PDF-07…09 | 2026.8.1 | rebuilt on the MMD design system Ben locked 02:25 UTC: one merged red bar, flat buttons, 4px corners, 24px window buttons, colours untouched | kua-agent | keyboard and mouse on mmd-win-test-01 at 800x600 |
| 2026-08-28 | PDF-14 | 2026.8.1 | Scan reaches a copier through WIA; dialog and the no-copier case proven, the page transfer is NOT — no scanner on the test machine | kua-agent | dialog opened by mouse; reports "No copier found on this machine" |
| 2026-08-28 | PDF-17 | 2026.8.1 | signed by CN=MMD Steel Group IT from mmd-fsn-01, DigiCert timestamp, Windows reports Valid | kua-agent | Get-AuthenticodeSignature on the guest |
| 2026-08-28 | PDF-18 | 2026.8.1 | published to Intune, offered to MMD-EUC-Pilot as available | kua-agent | committedContentVersion + assignment read back from Graph |
| 2026-08-28 | PDF-07 | 2026.8.1 | **three defects Ben found by looking at it** — fixed, see below | Ben | his own look at the running application |
| 2026-08-28 | — | — | **awaiting Ben** | — | — |
## What Ben found on 2026-08-28, and what was done
He looked at the running application and read it as still being the tool it was
forked from. Three things, all real:
1. **The upstream manual was what he saw first.** It sat in the repository root
as `MmdPdf.pdf` and on the test machine at `C:\src`. It was never in the
shipped payload — but the application restores the previous session, so once
it had been opened it came back on every start, a full-page upstream logo on
black. Removed and gitignored. *Not shipped is not the same as not on screen.*
It was visible in this project's own screenshots the day before and was noted
rather than fixed; that is why it reached him.
2. **Scan was in the window chrome**, beside minimise, maximise and close. It is
a toolbar button now, next to Print, captioned like every other tool.
3. **The footer read v1.7.5**, the upstream project's number. The application is
**2026.8.1**. Not 1.0: the pilot machines already carry a build numbered
1.7.5, and an upgrade has to sort above what it replaces or Intune reads it
as a downgrade.
## The design gate — OPEN
### DESIGN-FAIL, 2026-08-27, round 1
**Blockers**
1. The `···` overflow control is an unlabelled icon-only button and the only
way to sixteen of thirty tools. Icon-only is permitted for window controls
only. → label it, or ship Direction C which hides nothing.
2. Every toolbar tool name is 7.5px — below the reading floor, on the one screen
size that matters, for the exact text the design's argument rests on.
→ 910px minimum; if fourteen names then stop fitting, that is the argument
for C over A.
3. Disabled tools use `#A9A69C` — off-palette and lighter than the `#6B6A64`
floor. → `#767368`.
**Major**
4. Eleven off-palette values across the set. *(Partly my bad brief: `#FAFAF8`
and `#C9C6BC` are legitimate design-system tokens I failed to give the
reviewer. `#C9C7C0` on the page thumbnails is genuinely mine and wrong.)*
5. The whole mono-label scale runs 7.58.5px, including the data-loss warning
meant to stop a clerk destroying a figure. → 99.5px floor.
6. `OCR` is an acronym sitting among plain words. → "Read text".
7. Direction A hides sixteen tools; B asks a first-timer to guess thirty
glyphs. **Reviewer picks C.** → rebuild the four screens on C.
8. The recent-documents table leaves a dead band above the status bar.
**Minor**
9. Page thumbnails show generic grey bars, not the real page.
10. The toolbar's first icon and the status bar's first text do not share a
left column (~1417px vs ~910px).
11. "Keep both" does not say what filename it creates.
**What the reviewer said works:** the Save and Scan dialogs each keep exactly
one red fill for the one primary action, with plain button words and the
destructive action correctly primary; Save states the consequence in plain
language; the recent table matches the table spec exactly; square corners, no
gradients or grain, Fraunces for titles and mono for data throughout.
### Fix state at handover
Applied: findings 2, 3, 5 (type scale to a 9.5px floor; disabled tone to
`#767368`), and the `#C9C7C0` half of 4. Also fixed before the review landed:
two competing red fills per dialog, dialogs floating on an empty field, the
selection edge painting down every cell, and the stacked "1 / 1".
Reverted deliberately: I had edited `redplate.css`, the design system's own
stylesheet, to chase finding 4. That file is another session's and is lifted
verbatim — it has been restored from source. `#FAFAF8` and `#C9C6BC` stay.
Not applied: **1, 6, 7, 8, 9, 10, 11** — including the rebuild onto Direction C,
which is the big one and needs Ben's pick first.
**No re-review has run.** The design is DESIGN-FAIL as it stands.
+148
View File
@@ -0,0 +1,148 @@
# MMD PDF — test cases, test data, agent tasking
## The rig
Testing happens on **mmd-win-test-01** — VM 203 on hypervisor mmd-fsn-03
(Falkenstein, 168.119.88.172 / tailnet 100.64.0.19), Windows Server 2022,
guest 10.10.20.22. It auto-starts with the host.
**A user test uses keyboard and mouse only.** The rig is
`/opt/mmd-wintest/wintest` on the hypervisor — Xvfb + xfreerdp3 + xdotool, so
every input is a real pointer or key event. There is no scripting back door in
that path.
```
wintest start | stop | status | shot <name>
wintest move <x> <y> · click [1|2|3] · dblclick · at <x> <y> [btn]
wintest drag <x1> <y1> <x2> <y2> · scroll up|down [n]
wintest type "<text>" · key <keys...>
```
`/root/winrun.py` (PowerShell over WinRM) exists for **setup and for reading a
result back** — installing, copying a file in, extracting text from a saved
PDF. It is never used to make something happen inside the application.
Gotcha: `Stop-Process` on a running GUI app hangs the WinRM session. Launch GUI
apps with a **new** scheduled-task name:
`schtasks /create /tn X /tr "..." /ru Administrator /it /f` then `/run`.
## Test data
`/root/kpdf/mkpdf.py` on the hypervisor writes a one-page delivery note:
```
MMD STEEL GROUP - DELIVERY NOTE
Supplier: Fabrimetal SARL
Reference: BON-2026-0847
Quantity delivered: 12 tonnes
Received by: ____________________
Editing test: change the quantity above to 14 tonnes.
```
Serve it to the VM from the hypervisor (the VM reaches 10.10.20.1, and now the
internet too after its DNS was fixed on 2026-08-27):
`cd /root/kpdf && python3 -m http.server 8899 --bind 10.10.20.1`
---
## TC-01 · Open and read a PDF — PDF-01
**Input** Launch `MmdPdf.exe` with `C:\src\clean-note.pdf` as its argument.
**Expected** Window opens; the page renders; the status bar reads `1 OF 1`; the
title bar carries the file name.
## TC-02 · Change text already on the page — PDF-02
**Input** Double-click the words "Quantity delivered: 12 tonnes". Press `End`,
nine × `BackSpace`, type `19 tonnes`, press `Enter`.
**Expected** The line reads "Quantity delivered: 19 tonnes" on screen.
## TC-03 · The old text is removed, not covered — PDF-03 · **the one that matters**
**Input** After TC-02, click Save, answer `Replace`. Then read the file back:
`pdftotext delivery-note.pdf -`
**Expected** The extracted text contains `19 tonnes` and **does not contain**
`12 tonnes`. Every other line is unchanged and still extractable.
**Falsification** Before the fix this returned both figures. If `12 tonnes`
appears, the fix has regressed and the tool must not be used to correct figures.
## TC-04 · Save asks before it overwrites — PDF-04
**Input** Edit anything, click Save.
**Expected** A dialog naming the file. Nothing is written to disk until it is
answered. `Cancel` leaves the file byte-identical.
## TC-05 · No admin rights, portable — PDF-05
**Input** Copy the single `.exe` to a folder in a standard user's profile and
run it.
**Expected** It runs. No elevation prompt. Nothing is written to Program Files.
## TC-06 · Leaves the Windows default-PDF setting alone — PDF-06 · **security**
**Input** Read `HKCU:\...\FileExts\.pdf\UserChoice` (ProgId, Hash) and the
key's ACL. Install, launch, open a PDF. Read them again.
**Expected** ProgId, Hash and the deny ACE are **identical**. No helper binary
appears beside the executable.
**Falsification** PDFgear forged this hash and stripped the deny ACE. If either
changes, the tool is blocked, as PDFgear was.
**Read-only** — this test only reads the registry.
## TC-07 · MMD name, icon and word-mark — PDF-07
**Input** Look at the title bar, the taskbar icon, the About box, and Explorer's
icon for the file type.
**Expected** MMD PDF everywhere; the official lockup in the title bar; no trace
of the upstream product name or author. Icon legible at 32px and above.
## TC-08 · MMD design system, light only — PDF-08 · ⛔ blocked
**Input** Open Settings; look for a theme or accent picker. Sample the chrome,
rail, page-field and ink colours.
**Expected** No picker exists. Values match `Themes/MMD.xaml`. No dark surface
anywhere. Windows title bar stays light.
**Blocked** until the design passes review and Ben signs it.
## TC-09 · Works at 800×600 — PDF-09 · ⛔ blocked
**Input** Set the VM to 800×600. Open a document. Reach every tool.
**Expected** No horizontal scrollbar on the chrome. Every tool reachable. Every
label a person must read is at least 9px. The page is visible without scrolling
its own width.
**Blocked** on the toolbar direction (A/B/C).
## TC-10 … TC-18 — not written
Forms, mark-up, page work, text recognition, scanning, French, print, signing
and Intune are ⚪ in the backlog. Write the case when the feature is picked up;
do not pre-write cases for work nobody is doing.
---
## Agent tasking
**To run a user test** (TC-01 … TC-09):
> You are testing MMD PDF on mmd-win-test-01. Reach the hypervisor with
> `ssh -i ~/.ssh/id_ed25519_mmd_fsn03_rescue root@100.64.0.19`. Start the rig
> with `wintest start`. **Every action inside the application must be a real
> keyboard or mouse event through `wintest`** — never PowerShell, never a
> registry write, never a command-line argument to make something happen.
> `/root/winrun.py` is allowed only to put a file on the VM, launch the app, and
> read a result back. Run <TC-nn> exactly as written. Screenshot before and
> after every step with `wintest shot`, decode each PNG and **look at it**.
> Report each step as it happened, including anything that did not work. If you
> cannot perform a step, say `blocked` and name the exact interaction — do not
> substitute a different route to the same end.
**To review a design** (any UI change):
> You are an independent design reviewer. You did not build this. Your default
> verdict is DESIGN-FAIL; only evidence moves you off it. Read the screenshots
> as images. Judge against: 800×600, light only, the fixed palette, red only for
> one primary action / focus / urgency, square corners, the type scale, a 9px
> floor on any text a person must read, no icon-only buttons except the window
> controls, dense but not cramped, and whether a plant clerk who is not a
> computer person could use it. Output a verdict line then numbered findings,
> each with severity, the screen, the specific element, and the concrete fix.
> DESIGN-PASS only with zero blockers and zero majors.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+224
View File
@@ -0,0 +1,224 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,700&family=JetBrains+Mono:wght@400;500;600&display=swap">
<style>
body{margin:0;background:#F3F3F0;font-family:"PP Neue Machina","Neue Machina",system-ui,-apple-system,"Segoe UI",sans-serif;color:#3C3A34;-webkit-font-smoothing:antialiased}
a{color:#C8102E} a:hover{color:#8E0B20}
*{box-sizing:border-box}
.root{padding:16px;background:#F3F3F0}
.shell{width:1180px;display:flex;flex-direction:column;font-size:11.5px;background:#FFFFFF;border:1px solid #C7C6BD}
.s-title{display:flex;align-items:center;gap:10px;padding:0 10px;height:30px;background:#C8102E;color:#fff;font-family:Fraunces,Georgia,serif;font-weight:700;font-size:12.5px}
.s-title .mmd-logo{height:19px;width:auto;display:block;flex:none;fill:#fff}
.spring{flex:1}
.s-who{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.07em;text-transform:uppercase;color:rgba(255,255,255,0.85)}
.s-signout{font-size:10px;padding:1px 0;color:#fff;text-decoration:underline;text-underline-offset:2px}
.s-bar{display:flex;align-items:stretch;gap:2px;padding:0 5px;background:#F7F7F3;border-bottom:1px solid #D7D6CE}
.s-item{display:flex;align-items:center;gap:6px;padding:6px 10px;font-size:11px;white-space:nowrap;border:1px solid transparent;color:#3C3A34}
.s-item .cv{font-size:8px;opacity:0.5}
.s-item .n{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;padding:0 4px;border:1px solid #23221E;color:#23221E;background:#FFFFFF;line-height:13px}
.s-item.on{background:#FFFFFF;border-color:#D7D6CE;border-top:2px solid #C8102E;border-bottom-color:#FFFFFF;margin-bottom:-1px;font-weight:700;color:#23221E}
.s-item.on .n{border-color:#23221E;color:#23221E}
.s-tools{margin-left:auto;display:flex;align-items:center;gap:6px;padding-right:4px}
.s-search{width:168px;padding:2px 7px;font-size:10.5px;background:#FFFFFF;border:1px solid #C7C6BD;color:#23221E}
.s-btn{padding:2px 9px;font-size:10.5px;background:#FFFFFF;border:1px solid #C7C6BD;color:#23221E}
.s-desk{position:relative;background:#D4D0C6;overflow:hidden}
.s-desk.tiled{display:grid;grid-template-columns:1.35fr 1fr;grid-template-rows:auto auto;gap:10px;padding:10px;align-items:start}
.s-desk.stacked{height:492px}
.s-desk.oneup{padding:10px}
.s-task{display:flex;align-items:center;gap:10px;padding:4px 8px;font-size:10.5px;background:#F1F1EC;border-top:1px solid #D7D6CE}
.s-task .lbl{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9px;letter-spacing:0.1em;text-transform:uppercase;color:#767368}
.s-group{display:inline-flex;align-items:stretch;border:1px solid #D7D6CE;background:#FFFFFF}
.s-group .app{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.1em;text-transform:uppercase;color:#767368;padding:2px 7px;border-right:1px solid #D7D6CE;display:flex;align-items:center;background:#F7F7F3}
.s-group .win{padding:2px 8px;font-size:10px;color:#23221E;border-right:1px solid #D7D6CE;display:flex;align-items:center;gap:5px;background:#F1F1EC}
.s-group .win:last-child{border-right:0}
.s-group .win.on{box-shadow:inset 3px 0 0 #C8102E;font-weight:700;background:#FFFFFF}
.s-group .win.min{color:#3C3A34;background:#FFFFFF;outline:1px dashed #6B6A64;outline-offset:-3px}
.s-task .tile{font-size:10px;padding:1px 8px;border:1px solid #C7C6BD;background:#FFFFFF;color:#3C3A34}
/* windows — title bar made visible: darker plate, ink name, clear border; front = red top edge + shadow */
.win-frame{background:#FFFFFF;border:1px solid #B9B8AE;display:flex;flex-direction:column;min-width:0;box-shadow:0 1px 2px rgba(35,34,30,0.12)}
.win-frame.front{border-color:#767368;box-shadow:0 6px 16px rgba(35,34,30,0.26)}
.stacked .win-frame{position:absolute}
.w-title{display:flex;align-items:center;gap:8px;height:28px;padding:0 6px 0 8px;background:#E4E3DC;border-bottom:1px solid #C9C6BC;font-size:11px;color:#3C3A34}
.front .w-title{background:#FFFFFF;color:#23221E;border-top:3px solid #C8102E;height:31px}
.w-title .app{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.1em;text-transform:uppercase;color:#23221E;font-weight:700}
.w-title .app::after{content:"/";margin-left:8px;color:#6B6A64}
.w-title .name{font-family:Fraunces,Georgia,serif;font-weight:600;font-size:12px;color:#3C3A34}
.front .w-title .name{color:#23221E;font-weight:700}
.w-title .new{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.12em;text-transform:uppercase;color:#23221E;background:#E4E3DC;padding:0 6px;line-height:15px}
.w-title .ctl{display:flex;gap:8px}
.w-title .ctl span{width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:12px;color:#23221E;background:transparent;border:0}
.w-body{padding:0;overflow:hidden}
.w-toolbar{display:flex;align-items:center;gap:8px;padding:5px 8px;background:#FAFAF8;border-bottom:1px solid #E6E5DF}
.w-h1{font-family:Fraunces,Georgia,serif;font-weight:700;font-size:13px;letter-spacing:-0.01em;margin:0;color:#23221E}
.w-count{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;color:#6B6A64}
.w-count b{color:#C8102E;font-weight:600}
.w-filters{margin-left:auto;display:flex;gap:4px}
.w-chip{padding:1px 7px;font-size:9.5px;font-family:"JetBrains Mono",ui-monospace,monospace;border:1px solid #D7D6CE;background:#FFFFFF;color:#3C3A34}
.w-chip.on{border-color:#767368;color:#23221E;font-weight:600}
.w-cta{padding:2px 9px;font-size:10px;background:#C8102E;color:#FFFFFF;border:1px solid #C8102E;font-weight:600}
table.w-tbl{width:100%;border-collapse:collapse;font-size:10.5px;background:#FFFFFF}
.w-tbl th{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;text-align:left;padding:4px 8px;white-space:nowrap;background:#F1F1EC;color:#23221E;border-bottom:1px solid #D7D6CE;border-right:1px solid #E6E5DF}
.w-tbl td{padding:4px 8px;border-bottom:1px solid #E6E5DF;border-right:1px solid #E6E5DF;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px}
.w-tbl td:last-child,.w-tbl th:last-child{border-right:0}
.w-tbl tr:nth-child(even) td{background:#FAFAF8}
.w-tbl td.num{font-family:"JetBrains Mono",ui-monospace,monospace;font-variant-numeric:tabular-nums;text-align:right}
.w-tbl th.num{text-align:right}
.w-tbl td.ref{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.03em}
.w-tbl tr.sel td{background:#F7F7F3;box-shadow:inset 3px 0 0 #C8102E}
.w-state{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.07em;text-transform:uppercase;padding:1px 5px;background:#F1F1EC;color:#23221E}
.rec{display:grid;grid-template-columns:repeat(2, minmax(0, 1fr));gap:0}
.rec .col{padding:6px 10px 8px;border-right:1px solid #E6E5DF}
.rec .col:last-child{border-right:0}
.rec h4{margin:4px 0 4px;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.12em;text-transform:uppercase;color:#767368;font-weight:600;border-bottom:1px solid #E6E5DF;padding-bottom:3px}
.kv{display:grid;grid-template-columns:96px 1fr;gap:2px 8px;font-size:10.5px;margin:0}
.kv dt{color:#767368}
.kv dd{margin:0;color:#23221E}
.kv dd.mono{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:10px;font-variant-numeric:tabular-nums}
.rec-actions{display:flex;gap:6px;padding:6px 10px;border-top:1px solid #E6E5DF;background:#FAFAF8}
.rec-actions span{padding:2px 9px;font-size:10px;border:1px solid #C7C6BD;background:#FFFFFF;color:#23221E}
.rec-actions span.primary{color:#FFFFFF;background:#C8102E;border:1px solid #C8102E;font-weight:600}
.steps{display:flex;gap:0;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.06em;text-transform:uppercase;border-bottom:1px solid #E6E5DF}
.steps span{padding:3px 8px;border-right:1px solid #E6E5DF;color:#6B6A64}
.steps span.done{color:#767368}
.steps span.now{color:#C8102E;font-weight:700;box-shadow:inset 0 -2px 0 #C8102E}
.rep{padding:6px 10px}
.rep .row{display:grid;grid-template-columns:150px 1fr;gap:10px;padding:4px 0;border-bottom:1px solid #E6E5DF;font-size:10.5px}
.rep .row:last-child{border-bottom:0}
.rep .name{color:#23221E;font-weight:600}
.rep .name small{display:block;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.08em;text-transform:uppercase;color:#6B6A64;font-weight:400}
.rep .hl{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;color:#3C3A34;font-variant-numeric:tabular-nums}
/* title-bar option closeups */
.opt{width:600px;padding:16px;background:#D4D0C6}
.opt .cap{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.12em;text-transform:uppercase;color:#767368;margin:0 0 8px}
.opt .win-frame{margin-bottom:14px}
.opt .win-frame:last-child{margin-bottom:0}
/* Option B: ink strip */
.optB .front .w-title{background:#3C3A34;color:#fff;border-top:0;height:28px;border-bottom:1px solid #23221E}
.optB .front .w-title .name{color:#fff}
.optB .front .w-title .app{color:#fff}
.optB .front .w-title .app::after{color:rgba(255,255,255,0.35)}
.optB .front .w-title .ctl span{color:#fff;box-shadow:inset 0 0 0 1px rgba(255,255,255,0.6)}
.optB .front .w-title .new{color:#fff;border-color:rgba(255,255,255,0.7)}
/* Option C: ruled */
.optC .w-title{background:#FFFFFF;border-bottom:2px solid #23221E;height:28px}
.optC .front .w-title{background:#FFFFFF;border-top:3px solid #C8102E;height:31px;border-bottom:2px solid #23221E}
.optC .win-frame{border-color:#23221E}
/* ---- MMD PDF: the parts the shell system has no component for yet ------
Named for adoption into the design system, not invented per screen.
Every colour below is a red-plate value; nothing new is introduced. */
.pdf{width:800px;height:600px;display:flex;flex-direction:column;background:#FFFFFF;border:1px solid #767368;box-shadow:0 6px 16px rgba(35,34,30,0.26);overflow:hidden}
/* tool strip */
.pdf-tools{display:flex;align-items:stretch;background:#F1F1EC;border-bottom:1px solid #D7D6CE}
.pdf-t{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;width:44px;color:#23221E;flex:none}
.pdf-t .lb{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:7.5px;letter-spacing:0.02em;text-transform:uppercase;line-height:1;white-space:nowrap}
.pdf-t.on{background:#C8102E;color:#FFFFFF}
.pdf-t.dim{color:#A9A69C}
.pdf-rule{width:1px;background:#D7D6CE;margin:6px 3px;flex:none}
.pdf-end{margin-left:auto;display:flex;align-items:center;gap:5px;padding:0 6px;flex:none}
.pdf-zoom{display:flex;align-items:center;border:1px solid #C7C6BD;background:#FFFFFF}
.pdf-zoom b{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;font-weight:500;color:#23221E;padding:0 7px;line-height:19px}
.pdf-zoom i{width:19px;line-height:19px;text-align:center;font-style:normal;font-size:11px;color:#23221E;border-left:1px solid #C7C6BD}
.pdf-over{width:26px;display:flex;align-items:center;justify-content:center;color:#23221E;border-left:1px solid #D7D6CE;flex:none}
/* icon-only strip (direction B) */
.pdf-tools--icons .pdf-t{width:28px}
.pdf-tools--icons .pdf-t .lb{display:none}
/* tab strip (direction C) */
.pdf-tabs{display:flex;align-items:stretch;background:#E4E3DC;border-bottom:1px solid #D7D6CE;padding:0 4px}
.pdf-tab{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9px;letter-spacing:0.1em;text-transform:uppercase;color:#767368;padding:0 11px;line-height:21px}
.pdf-tab.on{background:#FFFFFF;color:#23221E;font-weight:600;border-top:2px solid #C8102E;line-height:19px}
/* context bar — only present while a tool is armed */
.pdf-ctx{display:flex;align-items:center;gap:9px;height:26px;padding:0 9px;background:#FAFAF8;border-bottom:1px solid #E6E5DF;font-size:10px;color:#3C3A34}
.pdf-ctx .lbl{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.1em;text-transform:uppercase;color:#767368}
.pdf-sw{display:flex;gap:3px}
.pdf-sw i{width:12px;height:12px;display:block;border:1px solid #C7C6BD}
.pdf-sw i.on{outline:1px solid #23221E;outline-offset:1px}
.pdf-pick{border:1px solid #C7C6BD;background:#FFFFFF;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;color:#23221E;padding:1px 6px}
/* work area */
.pdf-work{flex:1;display:flex;min-height:0}
.pdf-strip{width:26px;flex:none;background:#E4E3DC;border-right:1px solid #D7D6CE;display:flex;flex-direction:column;align-items:center;gap:7px;padding-top:7px}
.pdf-strip .pn{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9px;color:#23221E;line-height:1.25;text-align:center}
.pdf-rail{width:88px;flex:none;background:#E4E3DC;border-right:1px solid #D7D6CE;overflow:hidden}
.pdf-rail-h{display:flex;gap:7px;padding:5px 7px 3px;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8px;letter-spacing:0.1em;text-transform:uppercase;color:#767368}
.pdf-rail-h .on{color:#23221E;font-weight:700}
.pdf-pg{margin:0 7px 5px;background:#FFFFFF;border:1px solid #C7C6BD}
.pdf-pg.here{box-shadow:inset 3px 0 0 #C8102E}
.pdf-pg .mini{height:64px;padding:5px 6px;overflow:hidden}
.pdf-pg .mini i{display:block;background:#C9C7C0;height:2px;margin-bottom:3px}
.pdf-pg .mini i.t{background:#23221E;height:3px;width:74%;margin-bottom:5px}
.pdf-pg .mini i.s{width:52%}
.pdf-pg .n{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8px;text-align:center;color:#3C3A34;line-height:14px;border-top:1px solid #E6E5DF}
.pdf-canvas{flex:1;background:#F3F3F0;display:flex;justify-content:center;overflow:hidden;padding:9px 0 0}
.pdf-page{width:100%;max-width:560px;background:#FFFFFF;border:1px solid #C7C6BD;padding:28px 34px}
/* status */
.pdf-status{display:flex;align-items:center;gap:12px;height:20px;padding:0 8px;background:#F1F1EC;border-top:1px solid #D7D6CE;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;color:#3C3A34;flex:none}
.pdf-status .sep{width:1px;height:11px;background:#C7C6BD}
.pdf-status .r{margin-left:auto;color:#6B6A64}
.pdf-status .warn{color:#C8102E}
/* document body */
.pdf-h{font-family:Fraunces,Georgia,serif;font-weight:700;font-size:14px;color:#23221E;margin:0 0 13px}
.pdf-p{font-size:10.5px;color:#3C3A34;line-height:1.9;margin:0}
.pdf-live{display:inline-block;padding:0 4px;border:1px solid #C8102E;background:#FBEEF0;color:#23221E}
/* dialog */
.pdf-behind{position:absolute;inset:0}
.pdf-behind::after{content:"";position:absolute;inset:0;background:rgba(35,34,30,0.28)}
.pdf-over-scrim{position:relative;width:800px;height:600px;display:flex;align-items:center;justify-content:center;overflow:hidden}
.pdf-over-scrim .pdf-dlg{position:relative;z-index:2}
.pdf-dlg{width:404px;background:#FFFFFF;border:1px solid #767368;box-shadow:0 6px 16px rgba(35,34,30,0.26)}
.pdf-dlg-t{display:flex;align-items:center;height:26px;padding:0 10px;background:#FFFFFF;color:#23221E;border-bottom:2px solid #23221E;font-family:Fraunces,Georgia,serif;font-weight:700;font-size:11.5px}
.pdf-dlg-b{padding:13px 15px;display:flex;flex-direction:column;gap:10px;font-size:10.5px;color:#3C3A34}
.pdf-dlg-a{display:flex;justify-content:flex-end;gap:6px;padding:0 15px 13px}
.pdf-kv{display:grid;grid-template-columns:82px 1fr;gap:4px 10px;align-items:baseline}
.pdf-kv .k{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.09em;text-transform:uppercase;color:#767368}
.pdf-in{border:1px solid #C7C6BD;background:#FFFFFF;padding:3px 7px;font-size:10.5px;color:#23221E;display:flex;align-items:center}
.pdf-in .cv{margin-left:auto;font-size:8px;color:#767368}
/* direction board */
.board{width:800px;background:#F3F3F0;padding:11px;display:flex;flex-direction:column;gap:11px}
.board h3{font-family:Fraunces,Georgia,serif;font-weight:700;font-size:12.5px;color:#23221E;margin:0}
.board .why{font-size:10px;color:#3C3A34;margin:0}
.board .cost{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.06em;text-transform:uppercase;color:#767368;margin:0}
.board .card{background:#FFFFFF;border:1px solid #C7C6BD;padding:9px 10px;display:flex;flex-direction:column;gap:6px}
.board .strip{border:1px solid #D7D6CE}
</style>
</helmet>
<div class="board">
<div class="card">
<h3>A &mdash; one row, named tools</h3>
<div class="strip"><div class="pdf-tools" style="height:40px"><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M2 6h6l2 2h8v9H2z"/></svg><span class="lb">Open</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 3h11l3 3v11H3z"/><path d="M6 3v5h7V3"/><path d="M6 17v-5h8v5"/></svg><span class="lb">Save</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M6 8V3h8v5"/><path d="M3 8h14v6h-3v4H6v-4H3z"/></svg><span class="lb">Print</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 5V3h4"/><path d="M17 5V3h-4"/><path d="M3 15v2h4"/><path d="M17 15v2h-4"/><path d="M2 10h16"/></svg><span class="lb">Scan</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 6V3h3"/><path d="M17 6V3h-3"/><path d="M3 14v3h3"/><path d="M17 14v3h-3"/><path d="M6 9h8"/><path d="M6 12h5"/></svg><span class="lb">OCR</span></div><span class="pdf-rule"></span><div class="pdf-t on"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#FFFFFF" stroke-width="1.35" stroke-linecap="square"><path d="M5 3l11 8-5 1 3 5-2 1-3-5-4 3z"/></svg><span class="lb">Select</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M4 5h12"/><path d="M10 5v11"/></svg><span class="lb">Text</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M4 15l3-9h2l3 9"/><path d="M6 12h5"/><path d="M14 15h3"/></svg><span class="lb">Mark</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 17l3-1 9-9-2-2-9 9z"/></svg><span class="lb">Draw</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 8h8v8H3z"/><path d="M11 4h6v6h-6z"/></svg><span class="lb">Shape</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 14c3-6 5 3 7-2s4 2 7-3"/><path d="M3 17h14"/></svg><span class="lb">Sign</span></div><span class="pdf-rule"></span><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M4 3h8v14H4z"/><path d="M12 6h4v11H8"/></svg><span class="lb">Pages</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M13 13l4 4"/><path d="M4 9a5 5 0 1010 0 5 5 0 10-10 0"/></svg><span class="lb">Search</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M7 6L3 10l4 4"/><path d="M3 10h9a4 4 0 010 8"/></svg><span class="lb">Undo</span></div><div class="pdf-end"><span class="pdf-zoom"><b>FIT WIDTH</b><i>&minus;</i><i>+</i></span></div><div class="pdf-over"><svg width="14" height="14" viewBox="0 0 14 14" fill="#23221E"><circle cx="3" cy="7" r="1.2"/><circle cx="7" cy="7" r="1.2"/><circle cx="11" cy="7" r="1.2"/></svg></div></div></div>
<p class="why">Fourteen tools carry their name. Everything else &mdash; stamp, image, crop, merge, flatten, redo &mdash; lives behind the three dots on the right.</p>
<p class="cost">40px of chrome &middot; 512px left for the page &middot; 16 of 30 tools hidden</p>
</div>
<div class="card">
<h3>B &mdash; icons only, nothing hidden</h3>
<div class="strip"><div class="pdf-tools pdf-tools--icons" style="height:30px"><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M2 6h6l2 2h8v9H2z"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 3h11l3 3v11H3z"/><path d="M6 3v5h7V3"/><path d="M6 17v-5h8v5"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M6 8V3h8v5"/><path d="M3 8h14v6h-3v4H6v-4H3z"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 5V3h4"/><path d="M17 5V3h-4"/><path d="M3 15v2h4"/><path d="M17 15v2h-4"/><path d="M2 10h16"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 6V3h3"/><path d="M17 6V3h-3"/><path d="M3 14v3h3"/><path d="M17 14v3h-3"/><path d="M6 9h8"/><path d="M6 12h5"/></svg></div><div class="pdf-t on"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#FFFFFF" stroke-width="1.35" stroke-linecap="square"><path d="M5 3l11 8-5 1 3 5-2 1-3-5-4 3z"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M4 5h12"/><path d="M10 5v11"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M4 15l3-9h2l3 9"/><path d="M6 12h5"/><path d="M14 15h3"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 17l3-1 9-9-2-2-9 9z"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 8h8v8H3z"/><path d="M11 4h6v6h-6z"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 14c3-6 5 3 7-2s4 2 7-3"/><path d="M3 17h14"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M6 16h8"/><path d="M7 13h6l-1-5a2 2 0 10-4 0z"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 4h14v12H3z"/><path d="M3 13l4-4 3 3 3-3 4 4"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M6 2v12h12"/><path d="M2 6h12v12"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 5h6v4H3z"/><path d="M11 11h6v4h-6z"/><path d="M6 9v3h5"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M6 3h8v7H6z"/><path d="M3 13h14"/><path d="M3 16h14"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M4 3h8v14H4z"/><path d="M12 6h4v11H8"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M13 13l4 4"/><path d="M4 9a5 5 0 1010 0 5 5 0 10-10 0"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M7 6L3 10l4 4"/><path d="M3 10h9a4 4 0 010 8"/></svg></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M13 6l4 4-4 4"/><path d="M17 10H8a4 4 0 000 8"/></svg></div><div class="pdf-end"><span class="pdf-zoom"><b>FIT WIDTH</b><i>&minus;</i><i>+</i></span></div></div></div>
<p class="why">All thirty fit, name on hover. Fastest once someone knows the icons; a plant clerk opening it for the first time has to guess.</p>
<p class="cost">30px of chrome &middot; 522px left for the page &middot; nothing hidden</p>
</div>
<div class="card">
<h3>C &mdash; three tabs, named tools, nothing hidden</h3>
<div class="strip"><div class="pdf-tabs"><span class="pdf-tab">Document</span><span class="pdf-tab on">Edit</span><span class="pdf-tab">Pages</span></div><div class="pdf-tools" style="height:34px"><div class="pdf-t on"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#FFFFFF" stroke-width="1.35" stroke-linecap="square"><path d="M5 3l11 8-5 1 3 5-2 1-3-5-4 3z"/></svg><span class="lb">Select</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M4 5h12"/><path d="M10 5v11"/></svg><span class="lb">Text</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M4 15l3-9h2l3 9"/><path d="M6 12h5"/><path d="M14 15h3"/></svg><span class="lb">Mark</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 17l3-1 9-9-2-2-9 9z"/></svg><span class="lb">Draw</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 8h8v8H3z"/><path d="M11 4h6v6h-6z"/></svg><span class="lb">Shape</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 14c3-6 5 3 7-2s4 2 7-3"/><path d="M3 17h14"/></svg><span class="lb">Sign</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M6 16h8"/><path d="M7 13h6l-1-5a2 2 0 10-4 0z"/></svg><span class="lb">Stamp</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 4h14v12H3z"/><path d="M3 13l4-4 3 3 3-3 4 4"/></svg><span class="lb">Image</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M6 2v12h12"/><path d="M2 6h12v12"/></svg><span class="lb">Crop</span></div><div class="pdf-end"><span class="pdf-zoom"><b>FIT WIDTH</b><i>&minus;</i><i>+</i></span></div></div></div>
<p class="why">Document, Edit, Pages. Every tool keeps its name and every tool is reachable, at the price of one click to change tab and a second bar of chrome.</p>
<p class="cost">56px of chrome &middot; 496px left for the page &middot; nothing hidden, one click deeper</p>
</div>
</div></x-dc>
</body>
</html>
File diff suppressed because one or more lines are too long
+205
View File
@@ -0,0 +1,205 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,700&family=JetBrains+Mono:wght@400;500;600&display=swap">
<style>
body{margin:0;background:#F3F3F0;font-family:"PP Neue Machina","Neue Machina",system-ui,-apple-system,"Segoe UI",sans-serif;color:#3C3A34;-webkit-font-smoothing:antialiased}
a{color:#C8102E} a:hover{color:#8E0B20}
*{box-sizing:border-box}
.root{padding:16px;background:#F3F3F0}
.shell{width:1180px;display:flex;flex-direction:column;font-size:11.5px;background:#FFFFFF;border:1px solid #C7C6BD}
.s-title{display:flex;align-items:center;gap:10px;padding:0 10px;height:30px;background:#C8102E;color:#fff;font-family:Fraunces,Georgia,serif;font-weight:700;font-size:12.5px}
.s-title .mmd-logo{height:19px;width:auto;display:block;flex:none;fill:#fff}
.spring{flex:1}
.s-who{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.07em;text-transform:uppercase;color:rgba(255,255,255,0.85)}
.s-signout{font-size:10px;padding:1px 0;color:#fff;text-decoration:underline;text-underline-offset:2px}
.s-bar{display:flex;align-items:stretch;gap:2px;padding:0 5px;background:#F7F7F3;border-bottom:1px solid #D7D6CE}
.s-item{display:flex;align-items:center;gap:6px;padding:6px 10px;font-size:11px;white-space:nowrap;border:1px solid transparent;color:#3C3A34}
.s-item .cv{font-size:8px;opacity:0.5}
.s-item .n{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;padding:0 4px;border:1px solid #23221E;color:#23221E;background:#FFFFFF;line-height:13px}
.s-item.on{background:#FFFFFF;border-color:#D7D6CE;border-top:2px solid #C8102E;border-bottom-color:#FFFFFF;margin-bottom:-1px;font-weight:700;color:#23221E}
.s-item.on .n{border-color:#23221E;color:#23221E}
.s-tools{margin-left:auto;display:flex;align-items:center;gap:6px;padding-right:4px}
.s-search{width:168px;padding:2px 7px;font-size:10.5px;background:#FFFFFF;border:1px solid #C7C6BD;color:#23221E}
.s-btn{padding:2px 9px;font-size:10.5px;background:#FFFFFF;border:1px solid #C7C6BD;color:#23221E}
.s-desk{position:relative;background:#D4D0C6;overflow:hidden}
.s-desk.tiled{display:grid;grid-template-columns:1.35fr 1fr;grid-template-rows:auto auto;gap:10px;padding:10px;align-items:start}
.s-desk.stacked{height:492px}
.s-desk.oneup{padding:10px}
.s-task{display:flex;align-items:center;gap:10px;padding:4px 8px;font-size:10.5px;background:#F1F1EC;border-top:1px solid #D7D6CE}
.s-task .lbl{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9px;letter-spacing:0.1em;text-transform:uppercase;color:#767368}
.s-group{display:inline-flex;align-items:stretch;border:1px solid #D7D6CE;background:#FFFFFF}
.s-group .app{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.1em;text-transform:uppercase;color:#767368;padding:2px 7px;border-right:1px solid #D7D6CE;display:flex;align-items:center;background:#F7F7F3}
.s-group .win{padding:2px 8px;font-size:10px;color:#23221E;border-right:1px solid #D7D6CE;display:flex;align-items:center;gap:5px;background:#F1F1EC}
.s-group .win:last-child{border-right:0}
.s-group .win.on{box-shadow:inset 3px 0 0 #C8102E;font-weight:700;background:#FFFFFF}
.s-group .win.min{color:#3C3A34;background:#FFFFFF;outline:1px dashed #6B6A64;outline-offset:-3px}
.s-task .tile{font-size:10px;padding:1px 8px;border:1px solid #C7C6BD;background:#FFFFFF;color:#3C3A34}
/* windows — title bar made visible: darker plate, ink name, clear border; front = red top edge + shadow */
.win-frame{background:#FFFFFF;border:1px solid #B9B8AE;display:flex;flex-direction:column;min-width:0;box-shadow:0 1px 2px rgba(35,34,30,0.12)}
.win-frame.front{border-color:#767368;box-shadow:0 6px 16px rgba(35,34,30,0.26)}
.stacked .win-frame{position:absolute}
.w-title{display:flex;align-items:center;gap:8px;height:28px;padding:0 6px 0 8px;background:#E4E3DC;border-bottom:1px solid #C9C6BC;font-size:11px;color:#3C3A34}
.front .w-title{background:#FFFFFF;color:#23221E;border-top:3px solid #C8102E;height:31px}
.w-title .app{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.1em;text-transform:uppercase;color:#23221E;font-weight:700}
.w-title .app::after{content:"/";margin-left:8px;color:#6B6A64}
.w-title .name{font-family:Fraunces,Georgia,serif;font-weight:600;font-size:12px;color:#3C3A34}
.front .w-title .name{color:#23221E;font-weight:700}
.w-title .new{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.12em;text-transform:uppercase;color:#23221E;background:#E4E3DC;padding:0 6px;line-height:15px}
.w-title .ctl{display:flex;gap:8px}
.w-title .ctl span{width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:12px;color:#23221E;background:transparent;border:0}
.w-body{padding:0;overflow:hidden}
.w-toolbar{display:flex;align-items:center;gap:8px;padding:5px 8px;background:#FAFAF8;border-bottom:1px solid #E6E5DF}
.w-h1{font-family:Fraunces,Georgia,serif;font-weight:700;font-size:13px;letter-spacing:-0.01em;margin:0;color:#23221E}
.w-count{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;color:#6B6A64}
.w-count b{color:#C8102E;font-weight:600}
.w-filters{margin-left:auto;display:flex;gap:4px}
.w-chip{padding:1px 7px;font-size:9.5px;font-family:"JetBrains Mono",ui-monospace,monospace;border:1px solid #D7D6CE;background:#FFFFFF;color:#3C3A34}
.w-chip.on{border-color:#767368;color:#23221E;font-weight:600}
.w-cta{padding:2px 9px;font-size:10px;background:#C8102E;color:#FFFFFF;border:1px solid #C8102E;font-weight:600}
table.w-tbl{width:100%;border-collapse:collapse;font-size:10.5px;background:#FFFFFF}
.w-tbl th{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;text-align:left;padding:4px 8px;white-space:nowrap;background:#F1F1EC;color:#23221E;border-bottom:1px solid #D7D6CE;border-right:1px solid #E6E5DF}
.w-tbl td{padding:4px 8px;border-bottom:1px solid #E6E5DF;border-right:1px solid #E6E5DF;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px}
.w-tbl td:last-child,.w-tbl th:last-child{border-right:0}
.w-tbl tr:nth-child(even) td{background:#FAFAF8}
.w-tbl td.num{font-family:"JetBrains Mono",ui-monospace,monospace;font-variant-numeric:tabular-nums;text-align:right}
.w-tbl th.num{text-align:right}
.w-tbl td.ref{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.03em}
.w-tbl tr.sel td{background:#F7F7F3;box-shadow:inset 3px 0 0 #C8102E}
.w-state{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.07em;text-transform:uppercase;padding:1px 5px;background:#F1F1EC;color:#23221E}
.rec{display:grid;grid-template-columns:repeat(2, minmax(0, 1fr));gap:0}
.rec .col{padding:6px 10px 8px;border-right:1px solid #E6E5DF}
.rec .col:last-child{border-right:0}
.rec h4{margin:4px 0 4px;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.12em;text-transform:uppercase;color:#767368;font-weight:600;border-bottom:1px solid #E6E5DF;padding-bottom:3px}
.kv{display:grid;grid-template-columns:96px 1fr;gap:2px 8px;font-size:10.5px;margin:0}
.kv dt{color:#767368}
.kv dd{margin:0;color:#23221E}
.kv dd.mono{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:10px;font-variant-numeric:tabular-nums}
.rec-actions{display:flex;gap:6px;padding:6px 10px;border-top:1px solid #E6E5DF;background:#FAFAF8}
.rec-actions span{padding:2px 9px;font-size:10px;border:1px solid #C7C6BD;background:#FFFFFF;color:#23221E}
.rec-actions span.primary{color:#FFFFFF;background:#C8102E;border:1px solid #C8102E;font-weight:600}
.steps{display:flex;gap:0;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.06em;text-transform:uppercase;border-bottom:1px solid #E6E5DF}
.steps span{padding:3px 8px;border-right:1px solid #E6E5DF;color:#6B6A64}
.steps span.done{color:#767368}
.steps span.now{color:#C8102E;font-weight:700;box-shadow:inset 0 -2px 0 #C8102E}
.rep{padding:6px 10px}
.rep .row{display:grid;grid-template-columns:150px 1fr;gap:10px;padding:4px 0;border-bottom:1px solid #E6E5DF;font-size:10.5px}
.rep .row:last-child{border-bottom:0}
.rep .name{color:#23221E;font-weight:600}
.rep .name small{display:block;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.08em;text-transform:uppercase;color:#6B6A64;font-weight:400}
.rep .hl{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;color:#3C3A34;font-variant-numeric:tabular-nums}
/* title-bar option closeups */
.opt{width:600px;padding:16px;background:#D4D0C6}
.opt .cap{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.12em;text-transform:uppercase;color:#767368;margin:0 0 8px}
.opt .win-frame{margin-bottom:14px}
.opt .win-frame:last-child{margin-bottom:0}
/* Option B: ink strip */
.optB .front .w-title{background:#3C3A34;color:#fff;border-top:0;height:28px;border-bottom:1px solid #23221E}
.optB .front .w-title .name{color:#fff}
.optB .front .w-title .app{color:#fff}
.optB .front .w-title .app::after{color:rgba(255,255,255,0.35)}
.optB .front .w-title .ctl span{color:#fff;box-shadow:inset 0 0 0 1px rgba(255,255,255,0.6)}
.optB .front .w-title .new{color:#fff;border-color:rgba(255,255,255,0.7)}
/* Option C: ruled */
.optC .w-title{background:#FFFFFF;border-bottom:2px solid #23221E;height:28px}
.optC .front .w-title{background:#FFFFFF;border-top:3px solid #C8102E;height:31px;border-bottom:2px solid #23221E}
.optC .win-frame{border-color:#23221E}
/* ---- MMD PDF: the parts the shell system has no component for yet ------
Named for adoption into the design system, not invented per screen.
Every colour below is a red-plate value; nothing new is introduced. */
.pdf{width:800px;height:600px;display:flex;flex-direction:column;background:#FFFFFF;border:1px solid #767368;box-shadow:0 6px 16px rgba(35,34,30,0.26);overflow:hidden}
/* tool strip */
.pdf-tools{display:flex;align-items:stretch;background:#F1F1EC;border-bottom:1px solid #D7D6CE}
.pdf-t{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;width:44px;color:#23221E;flex:none}
.pdf-t .lb{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:7.5px;letter-spacing:0.02em;text-transform:uppercase;line-height:1;white-space:nowrap}
.pdf-t.on{background:#C8102E;color:#FFFFFF}
.pdf-t.dim{color:#A9A69C}
.pdf-rule{width:1px;background:#D7D6CE;margin:6px 3px;flex:none}
.pdf-end{margin-left:auto;display:flex;align-items:center;gap:5px;padding:0 6px;flex:none}
.pdf-zoom{display:flex;align-items:center;border:1px solid #C7C6BD;background:#FFFFFF}
.pdf-zoom b{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;font-weight:500;color:#23221E;padding:0 7px;line-height:19px}
.pdf-zoom i{width:19px;line-height:19px;text-align:center;font-style:normal;font-size:11px;color:#23221E;border-left:1px solid #C7C6BD}
.pdf-over{width:26px;display:flex;align-items:center;justify-content:center;color:#23221E;border-left:1px solid #D7D6CE;flex:none}
/* icon-only strip (direction B) */
.pdf-tools--icons .pdf-t{width:28px}
.pdf-tools--icons .pdf-t .lb{display:none}
/* tab strip (direction C) */
.pdf-tabs{display:flex;align-items:stretch;background:#E4E3DC;border-bottom:1px solid #D7D6CE;padding:0 4px}
.pdf-tab{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9px;letter-spacing:0.1em;text-transform:uppercase;color:#767368;padding:0 11px;line-height:21px}
.pdf-tab.on{background:#FFFFFF;color:#23221E;font-weight:600;border-top:2px solid #C8102E;line-height:19px}
/* context bar — only present while a tool is armed */
.pdf-ctx{display:flex;align-items:center;gap:9px;height:26px;padding:0 9px;background:#FAFAF8;border-bottom:1px solid #E6E5DF;font-size:10px;color:#3C3A34}
.pdf-ctx .lbl{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.1em;text-transform:uppercase;color:#767368}
.pdf-sw{display:flex;gap:3px}
.pdf-sw i{width:12px;height:12px;display:block;border:1px solid #C7C6BD}
.pdf-sw i.on{outline:1px solid #23221E;outline-offset:1px}
.pdf-pick{border:1px solid #C7C6BD;background:#FFFFFF;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;color:#23221E;padding:1px 6px}
/* work area */
.pdf-work{flex:1;display:flex;min-height:0}
.pdf-strip{width:26px;flex:none;background:#E4E3DC;border-right:1px solid #D7D6CE;display:flex;flex-direction:column;align-items:center;gap:7px;padding-top:7px}
.pdf-strip .pn{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9px;color:#23221E;line-height:1.25;text-align:center}
.pdf-rail{width:88px;flex:none;background:#E4E3DC;border-right:1px solid #D7D6CE;overflow:hidden}
.pdf-rail-h{display:flex;gap:7px;padding:5px 7px 3px;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8px;letter-spacing:0.1em;text-transform:uppercase;color:#767368}
.pdf-rail-h .on{color:#23221E;font-weight:700}
.pdf-pg{margin:0 7px 5px;background:#FFFFFF;border:1px solid #C7C6BD}
.pdf-pg.here{box-shadow:inset 3px 0 0 #C8102E}
.pdf-pg .mini{height:64px;padding:5px 6px;overflow:hidden}
.pdf-pg .mini i{display:block;background:#C9C7C0;height:2px;margin-bottom:3px}
.pdf-pg .mini i.t{background:#23221E;height:3px;width:74%;margin-bottom:5px}
.pdf-pg .mini i.s{width:52%}
.pdf-pg .n{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8px;text-align:center;color:#3C3A34;line-height:14px;border-top:1px solid #E6E5DF}
.pdf-canvas{flex:1;background:#F3F3F0;display:flex;justify-content:center;overflow:hidden;padding:9px 0 0}
.pdf-page{width:100%;max-width:560px;background:#FFFFFF;border:1px solid #C7C6BD;padding:28px 34px}
/* status */
.pdf-status{display:flex;align-items:center;gap:12px;height:20px;padding:0 8px;background:#F1F1EC;border-top:1px solid #D7D6CE;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;color:#3C3A34;flex:none}
.pdf-status .sep{width:1px;height:11px;background:#C7C6BD}
.pdf-status .r{margin-left:auto;color:#6B6A64}
.pdf-status .warn{color:#C8102E}
/* document body */
.pdf-h{font-family:Fraunces,Georgia,serif;font-weight:700;font-size:14px;color:#23221E;margin:0 0 13px}
.pdf-p{font-size:10.5px;color:#3C3A34;line-height:1.9;margin:0}
.pdf-live{display:inline-block;padding:0 4px;border:1px solid #C8102E;background:#FBEEF0;color:#23221E}
/* dialog */
.pdf-behind{position:absolute;inset:0}
.pdf-behind::after{content:"";position:absolute;inset:0;background:rgba(35,34,30,0.28)}
.pdf-over-scrim{position:relative;width:800px;height:600px;display:flex;align-items:center;justify-content:center;overflow:hidden}
.pdf-over-scrim .pdf-dlg{position:relative;z-index:2}
.pdf-dlg{width:404px;background:#FFFFFF;border:1px solid #767368;box-shadow:0 6px 16px rgba(35,34,30,0.26)}
.pdf-dlg-t{display:flex;align-items:center;height:26px;padding:0 10px;background:#FFFFFF;color:#23221E;border-bottom:2px solid #23221E;font-family:Fraunces,Georgia,serif;font-weight:700;font-size:11.5px}
.pdf-dlg-b{padding:13px 15px;display:flex;flex-direction:column;gap:10px;font-size:10.5px;color:#3C3A34}
.pdf-dlg-a{display:flex;justify-content:flex-end;gap:6px;padding:0 15px 13px}
.pdf-kv{display:grid;grid-template-columns:82px 1fr;gap:4px 10px;align-items:baseline}
.pdf-kv .k{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.09em;text-transform:uppercase;color:#767368}
.pdf-in{border:1px solid #C7C6BD;background:#FFFFFF;padding:3px 7px;font-size:10.5px;color:#23221E;display:flex;align-items:center}
.pdf-in .cv{margin-left:auto;font-size:8px;color:#767368}
/* direction board */
.board{width:800px;background:#F3F3F0;padding:11px;display:flex;flex-direction:column;gap:11px}
.board h3{font-family:Fraunces,Georgia,serif;font-weight:700;font-size:12.5px;color:#23221E;margin:0}
.board .why{font-size:10px;color:#3C3A34;margin:0}
.board .cost{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.06em;text-transform:uppercase;color:#767368;margin:0}
.board .card{background:#FFFFFF;border:1px solid #C7C6BD;padding:9px 10px;display:flex;flex-direction:column;gap:6px}
.board .strip{border:1px solid #D7D6CE}
</style>
</helmet>
<div class="pdf"><div class="w-title"><img class="mmd-logo" src="mmd-logo-red.svg" alt="MMD" style="height:13px"><span class="app">MMD PDF</span><span class="name">BON-2026-0847 delivery note.pdf</span><span class="spring"></span><span class="ctl"><span>&minus;</span><span>&#9723;</span><span>&times;</span></span></div><div class="pdf-tools" style="height:40px"><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M2 6h6l2 2h8v9H2z"/></svg><span class="lb">Open</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 3h11l3 3v11H3z"/><path d="M6 3v5h7V3"/><path d="M6 17v-5h8v5"/></svg><span class="lb">Save</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M6 8V3h8v5"/><path d="M3 8h14v6h-3v4H6v-4H3z"/></svg><span class="lb">Print</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 5V3h4"/><path d="M17 5V3h-4"/><path d="M3 15v2h4"/><path d="M17 15v2h-4"/><path d="M2 10h16"/></svg><span class="lb">Scan</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 6V3h3"/><path d="M17 6V3h-3"/><path d="M3 14v3h3"/><path d="M17 14v3h-3"/><path d="M6 9h8"/><path d="M6 12h5"/></svg><span class="lb">OCR</span></div><span class="pdf-rule"></span><div class="pdf-t on"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#FFFFFF" stroke-width="1.35" stroke-linecap="square"><path d="M5 3l11 8-5 1 3 5-2 1-3-5-4 3z"/></svg><span class="lb">Select</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M4 5h12"/><path d="M10 5v11"/></svg><span class="lb">Text</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M4 15l3-9h2l3 9"/><path d="M6 12h5"/><path d="M14 15h3"/></svg><span class="lb">Mark</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 17l3-1 9-9-2-2-9 9z"/></svg><span class="lb">Draw</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 8h8v8H3z"/><path d="M11 4h6v6h-6z"/></svg><span class="lb">Shape</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M3 14c3-6 5 3 7-2s4 2 7-3"/><path d="M3 17h14"/></svg><span class="lb">Sign</span></div><span class="pdf-rule"></span><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M4 3h8v14H4z"/><path d="M12 6h4v11H8"/></svg><span class="lb">Pages</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M13 13l4 4"/><path d="M4 9a5 5 0 1010 0 5 5 0 10-10 0"/></svg><span class="lb">Search</span></div><div class="pdf-t"><svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="#23221E" stroke-width="1.35" stroke-linecap="square"><path d="M7 6L3 10l4 4"/><path d="M3 10h9a4 4 0 010 8"/></svg><span class="lb">Undo</span></div><div class="pdf-end"><span class="pdf-zoom"><b>FIT WIDTH</b><i>&minus;</i><i>+</i></span></div><div class="pdf-over"><svg width="14" height="14" viewBox="0 0 14 14" fill="#23221E"><circle cx="3" cy="7" r="1.2"/><circle cx="7" cy="7" r="1.2"/><circle cx="11" cy="7" r="1.2"/></svg></div></div><div class="pdf-work"><div class="pdf-strip"><svg width="11" height="11" viewBox="0 0 12 12" fill="none" stroke="#23221E" stroke-width="1.3"><path d="M4 2l4 4-4 4"/></svg><span class="pn">1</span></div><div class="pdf-canvas"><div class="pdf-page"><h1 class="pdf-h">MMD STEEL GROUP &mdash; DELIVERY NOTE</h1><p class="pdf-p">Supplier: Fabrimetal SARL<br>Reference: BON-2026-0847<br>Quantity delivered: 12 tonnes<br>Vehicle: DK-4471-AB &middot; Driver: M. Sow<br><br>Received by: ______________________<br><br>Dakar plant &middot; corridor freight &middot; weighbridge ticket WB-4471</p></div></div></div><div class="pdf-status"><span class="">Ready</span><span class="r">1 OF 1 &middot; FIT WIDTH &middot; 100%</span></div></div></x-dc>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+413
View File
@@ -0,0 +1,413 @@
#!/usr/bin/env python3
"""MMD PDF — design pages for the desktop editor at 800x600.
Stylesheet: the red-plate MMD shell design system, lifted verbatim from its
own source (portal-redesign session, canvas/build.mjs). Not retyped.
The screen budget at 800x600 drives every decision here:
600 - 28 title bar - 20 status bar = 552px for toolbar + document.
800 - page rail = document width.
A PDF editor carries about thirty tools. Thirty labelled tools need ~1400px.
Three directions resolve that differently; A/B/C are real alternatives, not
cosmetic variants of one answer.
"""
import json, os
OUT = os.path.dirname(os.path.abspath(__file__))
RP = open(os.path.join(OUT, "redplate.css")).read()
W, H = 800, 600
EXT = """
/* ---- MMD PDF: the parts the shell system has no component for yet ------
Named for adoption into the design system, not invented per screen.
Every colour below is a red-plate value; nothing new is introduced. */
.pdf{width:800px;height:600px;display:flex;flex-direction:column;background:#FFFFFF;border:1px solid #767368;box-shadow:0 6px 16px rgba(35,34,30,0.26);overflow:hidden}
/* tool strip */
.pdf-tools{display:flex;align-items:stretch;background:#F1F1EC;border-bottom:1px solid #D7D6CE}
.pdf-t{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;width:44px;color:#23221E;flex:none}
.pdf-t .lb{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.01em;text-transform:uppercase;line-height:1;white-space:nowrap}
.pdf-t.on{background:#C8102E;color:#FFFFFF}
.pdf-t.dim{color:#767368}
.pdf-rule{width:1px;background:#D7D6CE;margin:6px 3px;flex:none}
.pdf-end{margin-left:auto;display:flex;align-items:center;gap:5px;padding:0 6px;flex:none}
.pdf-zoom{display:flex;align-items:center;border:1px solid #C7C6BD;background:#FFFFFF}
.pdf-zoom b{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;font-weight:500;color:#23221E;padding:0 7px;line-height:19px}
.pdf-zoom i{width:19px;line-height:19px;text-align:center;font-style:normal;font-size:11px;color:#23221E;border-left:1px solid #C7C6BD}
.pdf-over{width:26px;display:flex;align-items:center;justify-content:center;color:#23221E;border-left:1px solid #D7D6CE;flex:none}
/* icon-only strip (direction B) */
.pdf-tools--icons .pdf-t{width:28px}
.pdf-tools--icons .pdf-t .lb{display:none}
/* tab strip (direction C) */
.pdf-tabs{display:flex;align-items:stretch;background:#E4E3DC;border-bottom:1px solid #D7D6CE;padding:0 4px}
.pdf-tab{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:#767368;padding:0 11px;line-height:21px}
.pdf-tab.on{background:#FFFFFF;color:#23221E;font-weight:600;border-top:2px solid #C8102E;line-height:19px}
/* context bar — only present while a tool is armed */
.pdf-ctx{display:flex;align-items:center;gap:9px;height:28px;padding:0 9px;background:#FAFAF8;border-bottom:1px solid #E6E5DF;font-size:10px;color:#3C3A34}
.pdf-ctx .lbl{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.1em;text-transform:uppercase;color:#767368}
.pdf-sw{display:flex;gap:3px}
.pdf-sw i{width:12px;height:12px;display:block;border:1px solid #C7C6BD}
.pdf-sw i.on{outline:1px solid #23221E;outline-offset:1px}
.pdf-pick{border:1px solid #C7C6BD;background:#FFFFFF;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;color:#23221E;padding:1px 6px}
/* work area */
.pdf-work{flex:1;display:flex;min-height:0}
.pdf-strip{width:26px;flex:none;background:#E4E3DC;border-right:1px solid #D7D6CE;display:flex;flex-direction:column;align-items:center;gap:7px;padding-top:7px}
.pdf-strip .pn{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;color:#23221E;line-height:1.25;text-align:center}
.pdf-rail{width:88px;flex:none;background:#E4E3DC;border-right:1px solid #D7D6CE;overflow:hidden}
.pdf-rail-h{display:flex;gap:8px;padding:6px 7px 4px;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.1em;text-transform:uppercase;color:#767368}
.pdf-rail-h .on{color:#23221E;font-weight:700}
.pdf-pg{margin:0 7px 5px;background:#FFFFFF;border:1px solid #C7C6BD}
.pdf-pg.here{box-shadow:inset 3px 0 0 #C8102E}
.pdf-pg .mini{height:64px;padding:5px 6px;overflow:hidden}
.pdf-pg .mini i{display:block;background:#C7C6BD;height:2px;margin-bottom:3px}
.pdf-pg .mini i.t{background:#23221E;height:3px;width:74%;margin-bottom:5px}
.pdf-pg .mini i.s{width:52%}
.pdf-pg .n{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;text-align:center;color:#3C3A34;line-height:16px;border-top:1px solid #E6E5DF}
.pdf-canvas{flex:1;background:#F3F3F0;display:flex;justify-content:center;overflow:hidden;padding:9px 0 0}
.pdf-page{width:100%;max-width:560px;background:#FFFFFF;border:1px solid #C7C6BD;padding:28px 34px}
/* status */
.pdf-status{display:flex;align-items:center;gap:12px;height:23px;padding:0 8px;background:#F1F1EC;border-top:1px solid #D7D6CE;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;color:#3C3A34;flex:none}
.pdf-status .sep{width:1px;height:11px;background:#C7C6BD}
.pdf-status .r{margin-left:auto;color:#6B6A64}
.pdf-status .warn{color:#C8102E}
/* document body */
.pdf-h{font-family:Fraunces,Georgia,serif;font-weight:700;font-size:14px;color:#23221E;margin:0 0 13px}
.pdf-p{font-size:10.5px;color:#3C3A34;line-height:1.9;margin:0}
.pdf-live{display:inline-block;padding:0 4px;border:1px solid #C8102E;background:#FBEEF0;color:#23221E}
/* dialog */
.pdf-behind{position:absolute;inset:0}
.pdf-behind::after{content:"";position:absolute;inset:0;background:rgba(35,34,30,0.28)}
.pdf-over-scrim{position:relative;width:800px;height:600px;display:flex;align-items:center;justify-content:center;overflow:hidden}
.pdf-over-scrim .pdf-dlg{position:relative;z-index:2}
.pdf-dlg{width:404px;background:#FFFFFF;border:1px solid #767368;box-shadow:0 6px 16px rgba(35,34,30,0.26)}
.pdf-dlg-t{display:flex;align-items:center;height:26px;padding:0 10px;background:#FFFFFF;color:#23221E;border-bottom:2px solid #23221E;font-family:Fraunces,Georgia,serif;font-weight:700;font-size:11.5px}
.pdf-dlg-b{padding:13px 15px;display:flex;flex-direction:column;gap:10px;font-size:10.5px;color:#3C3A34}
.pdf-dlg-a{display:flex;justify-content:flex-end;gap:6px;padding:0 15px 13px}
.pdf-kv{display:grid;grid-template-columns:82px 1fr;gap:4px 10px;align-items:baseline}
.pdf-kv .k{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.09em;text-transform:uppercase;color:#767368}
.pdf-in{border:1px solid #C7C6BD;background:#FFFFFF;padding:3px 7px;font-size:10.5px;color:#23221E;display:flex;align-items:center}
.pdf-in .cv{margin-left:auto;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9px;color:#767368}
/* direction board */
.board{width:800px;background:#F3F3F0;padding:11px;display:flex;flex-direction:column;gap:11px}
.board h3{font-family:Fraunces,Georgia,serif;font-weight:700;font-size:12.5px;color:#23221E;margin:0}
.board .why{font-size:10px;color:#3C3A34;margin:0}
.board .cost{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.06em;text-transform:uppercase;color:#767368;margin:0}
.board .card{background:#FFFFFF;border:1px solid #C7C6BD;padding:9px 10px;display:flex;flex-direction:column;gap:6px}
.board .strip{border:1px solid #D7D6CE}
"""
# ── icons: one 20px grid, stroke only ────────────────────────────────────────
P = {
"Open": ['M2 6h6l2 2h8v9H2z'],
"Save": ['M3 3h11l3 3v11H3z', 'M6 3v5h7V3', 'M6 17v-5h8v5'],
"Print": ['M6 8V3h8v5', 'M3 8h14v6h-3v4H6v-4H3z'],
"Scan": ['M3 5V3h4', 'M17 5V3h-4', 'M3 15v2h4', 'M17 15v2h-4', 'M2 10h16'],
"OCR": ['M3 6V3h3', 'M17 6V3h-3', 'M3 14v3h3', 'M17 14v3h-3', 'M6 9h8', 'M6 12h5'],
"Select": ['M5 3l11 8-5 1 3 5-2 1-3-5-4 3z'],
"Text": ['M4 5h12', 'M10 5v11'],
"Mark": ['M4 15l3-9h2l3 9', 'M6 12h5', 'M14 15h3'],
"Draw": ['M3 17l3-1 9-9-2-2-9 9z'],
"Shape": ['M3 8h8v8H3z', 'M11 4h6v6h-6z'],
"Sign": ['M3 14c3-6 5 3 7-2s4 2 7-3', 'M3 17h14'],
"Stamp": ['M6 16h8', 'M7 13h6l-1-5a2 2 0 10-4 0z'],
"Image": ['M3 4h14v12H3z', 'M3 13l4-4 3 3 3-3 4 4'],
"Crop": ['M6 2v12h12', 'M2 6h12v12'],
"Pages": ['M4 3h8v14H4z', 'M12 6h4v11H8'],
"Search": ['M13 13l4 4', 'M4 9a5 5 0 1010 0 5 5 0 10-10 0'],
"Undo": ['M7 6L3 10l4 4', 'M3 10h9a4 4 0 010 8'],
"Redo": ['M13 6l4 4-4 4', 'M17 10H8a4 4 0 000 8'],
"Merge": ['M3 5h6v4H3z', 'M11 11h6v4h-6z', 'M6 9v3h5'],
"Flatten":['M6 3h8v7H6z', 'M3 13h14', 'M3 16h14'],
}
def ico(n, on=False, dim=False):
col = "#FFFFFF" if on else ("#767368" if dim else "#23221E")
d = "".join(f'<path d="{p}"/>' for p in P.get(n, ['M4 4h12v12H4z']))
return (f'<svg width="15" height="15" viewBox="0 0 20 20" fill="none" stroke="{col}" '
f'stroke-width="1.35" stroke-linecap="square">{d}</svg>')
def T(n, on=False, dim=False):
c = "pdf-t on" if on else ("pdf-t dim" if dim else "pdf-t")
return f'<div class="{c}">{ico(n, on, dim)}<span class="lb">{n}</span></div>'
ZOOM = ('<span class="pdf-zoom"><b>FIT WIDTH</b><i>&minus;</i><i>+</i></span>')
OVER = ('<div class="pdf-over"><svg width="14" height="14" viewBox="0 0 14 14" fill="#23221E">'
'<circle cx="3" cy="7" r="1.2"/><circle cx="7" cy="7" r="1.2"/><circle cx="11" cy="7" r="1.2"/></svg></div>')
def title(name="BON-2026-0847 delivery note.pdf", tag=""):
t = f'<span class="new">{tag}</span>' if tag else ''
return (f'<div class="w-title"><img class="mmd-logo" src="mmd-logo-red.svg" alt="MMD" '
f'style="height:13px"><span class="app">MMD PDF</span>'
f'<span class="name">{name}</span>{t}<span class="spring"></span>'
f'<span class="ctl"><span>&minus;</span><span>&#9723;</span><span>&times;</span></span></div>')
def strip_collapsed(n="1"):
return (f'<div class="pdf-strip"><svg width="11" height="11" viewBox="0 0 12 12" fill="none" '
f'stroke="#23221E" stroke-width="1.3"><path d="M4 2l4 4-4 4"/></svg>'
f'<span class="pn">{n}</span></div>')
def rail_open(pages=("1", "2", "3")):
pg = "".join(
f'<div class="pdf-pg{" here" if i == 0 else ""}"><div class="mini">'
f'<i class="t"></i><i></i><i></i><i class="s"></i><i></i></div>'
f'<div class="n">{p}</div></div>' for i, p in enumerate(pages))
return (f'<div class="pdf-rail"><div class="pdf-rail-h"><span class="on">Pages</span>'
f'<span>Marks</span></div>{pg}</div>')
DOC = ('<h1 class="pdf-h">MMD STEEL GROUP &mdash; DELIVERY NOTE</h1>'
'<p class="pdf-p">Supplier: Fabrimetal SARL<br>Reference: BON-2026-0847<br>'
'Quantity delivered: 12 tonnes<br>Vehicle: DK-4471-AB &middot; Driver: M. Sow<br><br>'
'Received by: ______________________<br><br>'
'Dakar plant &middot; corridor freight &middot; weighbridge ticket WB-4471</p>')
DOC_EDIT = ('<h1 class="pdf-h">MMD STEEL GROUP &mdash; DELIVERY NOTE</h1>'
'<p class="pdf-p">Supplier: Fabrimetal SARL<br>Reference: BON-2026-0847<br>'
'<span class="pdf-live">Quantity delivered: 19 tonnes</span><br>'
'Vehicle: DK-4471-AB &middot; Driver: M. Sow<br><br>'
'Received by: ______________________<br><br>'
'Dakar plant &middot; corridor freight &middot; weighbridge ticket WB-4471</p>')
def status(left, right, warn=False):
cls = "warn" if warn else ""
return (f'<div class="pdf-status"><span class="{cls}">{left}</span>'
f'<span class="r">{right}</span></div>')
HEAD = """<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,700&family=JetBrains+Mono:wght@400;500;600&display=swap">
<style>
%s
%s
</style>
</helmet>
""" % (RP, EXT)
TAIL = "</x-dc>\n</body>\n</html>\n"
def w(name, inner):
open(os.path.join(OUT, name), "w", encoding="utf-8").write(HEAD + inner + TAIL)
# ── Direction A: one labelled strip, fourteen tools, rest behind overflow ────
A_TOOLS = ["Open", "Save", "Print", "Scan", "OCR"]
A_EDIT = ["Select", "Text", "Mark", "Draw", "Shape", "Sign"]
A_DOC = ["Pages", "Search", "Undo"]
def strip_A(active="Select", h=40):
return (f'<div class="pdf-tools" style="height:{h}px">'
+ "".join(T(t) for t in A_TOOLS) + '<span class="pdf-rule"></span>'
+ "".join(T(t, t == active) for t in A_EDIT) + '<span class="pdf-rule"></span>'
+ "".join(T(t) for t in A_DOC)
+ f'<div class="pdf-end">{ZOOM}</div>{OVER}</div>')
w("Main.dc.html",
'<div class="pdf">' + title() + strip_A("Select") +
'<div class="pdf-work">' + strip_collapsed() +
'<div class="pdf-canvas"><div class="pdf-page">' + DOC + '</div></div></div>' +
status("Ready", "1 OF 1 &middot; FIT WIDTH &middot; 100%") + '</div>')
# ── Editing text, context bar present ───────────────────────────────────────
ctx = ('<div class="pdf-ctx"><span class="lbl">Colour</span>'
'<span class="pdf-sw"><i class="on" style="background:#23221E"></i>'
'<i style="background:#C8102E"></i><i style="background:#3C3A34"></i>'
'<i style="background:#767368"></i><i style="background:#FFFFFF"></i></span>'
'<span class="lbl">Size</span><span class="pdf-pick">11 pt</span>'
'<span class="pdf-pick">B</span><span class="pdf-pick">I</span>'
'<span class="spring"></span>'
'<span class="lbl">Enter keeps it &middot; Esc throws it away</span></div>')
w("EditText.dc.html",
'<div class="pdf">' + title(tag="Changed") + strip_A("Text") + ctx +
'<div class="pdf-work">' + strip_collapsed() +
'<div class="pdf-canvas"><div class="pdf-page">' + DOC_EDIT + '</div></div></div>' +
status("The old quantity is removed from the file when you save",
"1 OF 1 &middot; FIT WIDTH &middot; 100%", warn=True) + '</div>')
# ── Rail open, three pages ──────────────────────────────────────────────────
w("PageRail.dc.html",
'<div class="pdf">' + title("Weighbridge tickets week 34.pdf") + strip_A("Select") +
'<div class="pdf-work">' + rail_open() +
'<div class="pdf-canvas"><div class="pdf-page">' + DOC + '</div></div></div>' +
status("Ready", "1 OF 3 &middot; FIT WIDTH &middot; 100%") + '</div>')
# ── Nothing open ────────────────────────────────────────────────────────────
rows = "".join(
f'<tr><td class="ref"{" style=box-shadow:inset~3px~0~0~#C8102E".replace(chr(126),chr(32)) if i == 0 else ""}>{r}</td>'
f'<td>{d}</td><td class="num">{n}</td><td><span class="w-state">{s}</span></td></tr>'
for i, (r, d, n, s) in enumerate([
("BON-2026-0847", "Delivery note &mdash; Fabrimetal SARL", "1", "Changed"),
("INV-4471", "Supplier invoice &mdash; Fabrimetal SARL", "3", "Signed"),
("WB-4471", "Weighbridge ticket &mdash; Dakar plant", "1", "Scanned"),
("INSP-0912", "Mill inspection &mdash; Kigali", "8", "Read"),
("PO-AO-2231", "Refractory bricks &mdash; Vesuvius Angola", "2", "Read"),
("BON-2026-0839", "Delivery note &mdash; Continental Sahel", "1", "Signed"),
("INV-4402", "Freight invoice &mdash; corridor Dakar&ndash;Bamako", "2", "Read"),
("WB-4468", "Weighbridge ticket &mdash; Dakar plant", "1", "Scanned"),
("PO-SN-24099", "Diesel gensets &mdash; Groupe S&eacute;n&eacute;gal &Eacute;nergie", "4", "Read"),
("BON-2026-0831", "Delivery note &mdash; SMS Concast", "1", "Read"),
("INSP-0908", "Mill inspection &mdash; Dakar", "6", "Read"),
("WB-4455", "Weighbridge ticket &mdash; Dakar plant", "1", "Scanned"),
("INV-4377", "Supplier invoice &mdash; RHI Magnesita", "3", "Signed"),
("PO-RW-24107", "Rolling mill rollers &mdash; Danieli Service", "2", "Read"),
("BON-2026-0824", "Delivery note &mdash; Nexans Angola", "1", "Read"),
]))
w("Nothing.dc.html",
'<div class="pdf">' + title("No document open") +
f'<div class="pdf-tools" style="height:40px">'
+ "".join(T(t) for t in A_TOOLS) + '<span class="pdf-rule"></span>'
+ "".join(T(t, dim=True) for t in A_EDIT) + '<span class="pdf-rule"></span>'
+ "".join(T(t, dim=True) for t in A_DOC)
+ f'<div class="pdf-end">{ZOOM}</div>{OVER}</div>'
+ '<div class="pdf-work"><div class="pdf-canvas" style="padding:0"><div style="width:100%">'
'<div class="w-toolbar"><h2 class="w-h1">Recent</h2>'
'<span class="w-count">on this machine</span>'
'<span class="w-filters"><span class="w-chip">Open a file&hellip;</span>'
'<span class="w-cta">Scan a document</span></span></div>'
'<table class="w-tbl"><thead><tr><th>Reference</th><th>Document</th>'
'<th class="num">Pages</th><th>State</th></tr></thead><tbody>' + rows +
'</tbody></table></div></div></div>' +
status("Pick a document, or scan one", "NOTHING OPEN") + '</div>')
# ── Saving over the original ────────────────────────────────────────────────
def behind(doc=DOC, active="Select", st=("Ready", "1 OF 1 &middot; FIT WIDTH &middot; 100%")):
return ('<div class="pdf-behind"><div class="pdf" style="box-shadow:none;border:0">'
+ title() + strip_A(active)
+ '<div class="pdf-work">' + strip_collapsed()
+ '<div class="pdf-canvas"><div class="pdf-page">' + doc + '</div></div></div>'
+ status(*st) + '</div></div>')
w("Save.dc.html",
'<div class="pdf-over-scrim">' + behind(DOC_EDIT, "Text",
("The old quantity is removed from the file when you save",
"1 OF 1 &middot; FIT WIDTH &middot; 100%")) +
'<div class="pdf-dlg">'
'<div class="pdf-dlg-t">Save</div><div class="pdf-dlg-b">'
'<div>Write the change into <b>BON-2026-0847 delivery note.pdf</b>?</div>'
'<div class="pdf-kv">'
'<span class="k">Was</span><span>Quantity delivered: 12 tonnes</span>'
'<span class="k">Becomes</span><span>Quantity delivered: 19 tonnes</span>'
'<span class="k">The old text</span><span>is taken out of the file, not covered over</span>'
'</div></div><div class="pdf-dlg-a">'
'<span class="w-chip">Cancel</span><span class="w-chip">Keep both</span>'
'<span class="w-cta">Replace</span></div></div></div>')
# ── Scan ────────────────────────────────────────────────────────────────────
def field(k, v, note=""):
n = f'<span class="cv">{note}</span>' if note else ''
return f'<span class="k">{k}</span><span class="pdf-in">{v}{n}</span>'
w("Scan.dc.html",
'<div class="pdf-over-scrim">' + behind() + '<div class="pdf-dlg">'
'<div class="pdf-dlg-t">Scan</div><div class="pdf-dlg-b">'
'<div>Pages arrive in the document you have open.</div>'
'<div class="pdf-kv">'
+ field("Copier", "Ricoh IM C3000 &mdash; Dakar plant", "ready")
+ field("Feed", "Both sides")
+ field("Colour", "Greyscale")
+ field("Detail", "300 dpi", "print")
+ '</div>'
'<div><span class="w-state">Text search on</span> &nbsp;French and English</div>'
'</div><div class="pdf-dlg-a">'
'<span class="w-chip">Cancel</span><span class="w-cta">Scan</span></div></div></div>')
# ── The three directions ────────────────────────────────────────────────────
def strip_B(active="Select"):
tools = A_TOOLS + A_EDIT + ["Stamp", "Image", "Crop", "Merge", "Flatten"] + A_DOC + ["Redo"]
return ('<div class="pdf-tools pdf-tools--icons" style="height:30px">'
+ "".join(ico_t(t, t == active) for t in tools)
+ f'<div class="pdf-end">{ZOOM}</div></div>')
def ico_t(n, on=False):
c = "pdf-t on" if on else "pdf-t"
return f'<div class="{c}">{ico(n, on)}</div>'
def strip_C(tab="Edit"):
tabs = ("Document", "Edit", "Pages")
row = {"Document": A_TOOLS + ["Merge", "Flatten"],
"Edit": A_EDIT + ["Stamp", "Image", "Crop"],
"Pages": ["Pages", "Search", "Undo", "Redo"]}[tab]
return ('<div class="pdf-tabs">' + "".join(
f'<span class="pdf-tab{" on" if t == tab else ""}">{t}</span>' for t in tabs)
+ '</div><div class="pdf-tools" style="height:34px">'
+ "".join(T(t, t == "Select") for t in row)
+ f'<div class="pdf-end">{ZOOM}</div></div>')
board = f'''<div class="board">
<div class="card">
<h3>A &mdash; one row, named tools</h3>
<div class="strip">{strip_A("Select", 40)}</div>
<p class="why">Fourteen tools carry their name. Everything else &mdash; stamp, image, crop, merge, flatten, redo &mdash; lives behind the three dots on the right.</p>
<p class="cost">40px of chrome &middot; 512px left for the page &middot; 16 of 30 tools hidden</p>
</div>
<div class="card">
<h3>B &mdash; icons only, nothing hidden</h3>
<div class="strip">{strip_B("Select")}</div>
<p class="why">All thirty fit, name on hover. Fastest once someone knows the icons; a plant clerk opening it for the first time has to guess.</p>
<p class="cost">30px of chrome &middot; 522px left for the page &middot; nothing hidden</p>
</div>
<div class="card">
<h3>C &mdash; three tabs, named tools, nothing hidden</h3>
<div class="strip">{strip_C("Edit")}</div>
<p class="why">Document, Edit, Pages. Every tool keeps its name and every tool is reachable, at the price of one click to change tab and a second bar of chrome.</p>
<p class="cost">56px of chrome &middot; 496px left for the page &middot; nothing hidden, one click deeper</p>
</div>
</div>'''
w("Directions.dc.html", board)
# ── canvas ──────────────────────────────────────────────────────────────────
GX, GY = W + 90, H + 150
canvas = {
"artboards": [
{"file": "Directions.dc.html", "x": 0, "y": 0, "w": W, "h": 470,
"title": "Three ways to fit the toolbar — pick one"},
{"file": "Main.dc.html", "x": GX, "y": 0, "w": W, "h": H, "title": "A document open"},
{"file": "EditText.dc.html", "x": 2 * GX, "y": 0, "w": W, "h": H, "title": "Changing text on the page"},
{"file": "PageRail.dc.html", "x": 0, "y": GY, "w": W, "h": H, "title": "Page list open"},
{"file": "Nothing.dc.html", "x": GX, "y": GY, "w": W, "h": H, "title": "Nothing open yet"},
{"file": "Save.dc.html", "x": 2 * GX, "y": GY, "w": W, "h": H, "title": "Saving over the original"},
{"file": "Scan.dc.html", "x": GX, "y": 2 * GY, "w": W, "h": H, "title": "Scanning — not built yet"},
],
"annotations": [
{"id": "brief", "x": 0, "y": -150, "w": 660,
"text": "MMD PDF — the Windows PDF editor for staff.\nEvery frame is 800x600, the plant PC screen. Styling is the new MMD shell system, taken from its own source. Official MMD lockup only.\n600 minus 28 title bar minus 20 status bar leaves 552px for toolbar and page."},
{"id": "decision", "x": 0, "y": 490, "w": 620,
"text": "The one thing that needs your answer: a PDF editor has about thirty tools and thirty named tools need roughly 1400px. A, B and C give up different things. Mark the one you want and the rest follows from it."},
{"id": "scan-note", "x": GX, "y": 2 * GY - 90, "w": 420,
"text": "Scanning does not exist in the application yet. This is the design for it, not a picture of something that works."},
{"id": "edit-note", "x": 2 * GX, "y": -90, "w": 430,
"text": "This is the moment the old wording is destroyed. The status bar says so, and Enter/Esc are spelled out because both were guessed wrong during testing."},
],
"launch": {"view": "canvas"},
}
json.dump(canvas, open(os.path.join(OUT, "canvas.json"), "w"), indent=2)
print("written 7 artboards")
+93
View File
@@ -0,0 +1,93 @@
{
"artboards": [
{
"file": "Directions.dc.html",
"x": 0,
"y": 0,
"w": 800,
"h": 470,
"title": "Three ways to fit the toolbar \u2014 pick one"
},
{
"file": "Main.dc.html",
"x": 890,
"y": 0,
"w": 800,
"h": 600,
"title": "A document open"
},
{
"file": "EditText.dc.html",
"x": 1780,
"y": 0,
"w": 800,
"h": 600,
"title": "Changing text on the page"
},
{
"file": "PageRail.dc.html",
"x": 0,
"y": 750,
"w": 800,
"h": 600,
"title": "Page list open"
},
{
"file": "Nothing.dc.html",
"x": 890,
"y": 750,
"w": 800,
"h": 600,
"title": "Nothing open yet"
},
{
"file": "Save.dc.html",
"x": 1780,
"y": 750,
"w": 800,
"h": 600,
"title": "Saving over the original"
},
{
"file": "Scan.dc.html",
"x": 890,
"y": 1500,
"w": 800,
"h": 600,
"title": "Scanning \u2014 not built yet"
}
],
"annotations": [
{
"id": "brief",
"x": 0,
"y": -150,
"w": 660,
"text": "MMD PDF \u2014 the Windows PDF editor for staff.\nEvery frame is 800x600, the plant PC screen. Styling is the new MMD shell system, taken from its own source. Official MMD lockup only.\n600 minus 28 title bar minus 20 status bar leaves 552px for toolbar and page."
},
{
"id": "decision",
"x": 0,
"y": 490,
"w": 620,
"text": "The one thing that needs your answer: a PDF editor has about thirty tools and thirty named tools need roughly 1400px. A, B and C give up different things. Mark the one you want and the rest follows from it."
},
{
"id": "scan-note",
"x": 890,
"y": 1410,
"w": 420,
"text": "Scanning does not exist in the application yet. This is the design for it, not a picture of something that works."
},
{
"id": "edit-note",
"x": 1780,
"y": -90,
"w": 430,
"text": "This is the moment the old wording is destroyed. The status bar says so, and Enter/Esc are spelled out because both were guessed wrong during testing."
}
],
"launch": {
"view": "canvas"
}
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

+103
View File
@@ -0,0 +1,103 @@
body{margin:0;background:#F3F3F0;font-family:"PP Neue Machina","Neue Machina",system-ui,-apple-system,"Segoe UI",sans-serif;color:#3C3A34;-webkit-font-smoothing:antialiased}
a{color:#C8102E} a:hover{color:#8E0B20}
*{box-sizing:border-box}
.root{padding:16px;background:#F3F3F0}
.shell{width:1180px;display:flex;flex-direction:column;font-size:11.5px;background:#FFFFFF;border:1px solid #C7C6BD}
.s-title{display:flex;align-items:center;gap:10px;padding:0 10px;height:30px;background:#C8102E;color:#fff;font-family:Fraunces,Georgia,serif;font-weight:700;font-size:12.5px}
.s-title .mmd-logo{height:19px;width:auto;display:block;flex:none;fill:#fff}
.spring{flex:1}
.s-who{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.07em;text-transform:uppercase;color:rgba(255,255,255,0.85)}
.s-signout{font-size:10px;padding:1px 0;color:#fff;text-decoration:underline;text-underline-offset:2px}
.s-bar{display:flex;align-items:stretch;gap:2px;padding:0 5px;background:#F7F7F3;border-bottom:1px solid #D7D6CE}
.s-item{display:flex;align-items:center;gap:6px;padding:6px 10px;font-size:11px;white-space:nowrap;border:1px solid transparent;color:#3C3A34}
.s-item .cv{font-size:8px;opacity:0.5}
.s-item .n{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;padding:0 4px;border:1px solid #23221E;color:#23221E;background:#FFFFFF;line-height:13px}
.s-item.on{background:#FFFFFF;border-color:#D7D6CE;border-top:2px solid #C8102E;border-bottom-color:#FFFFFF;margin-bottom:-1px;font-weight:700;color:#23221E}
.s-item.on .n{border-color:#23221E;color:#23221E}
.s-tools{margin-left:auto;display:flex;align-items:center;gap:6px;padding-right:4px}
.s-search{width:168px;padding:2px 7px;font-size:10.5px;background:#FFFFFF;border:1px solid #C7C6BD;color:#23221E}
.s-btn{padding:2px 9px;font-size:10.5px;background:#FFFFFF;border:1px solid #C7C6BD;color:#23221E}
.s-desk{position:relative;background:#D4D0C6;overflow:hidden}
.s-desk.tiled{display:grid;grid-template-columns:1.35fr 1fr;grid-template-rows:auto auto;gap:10px;padding:10px;align-items:start}
.s-desk.stacked{height:492px}
.s-desk.oneup{padding:10px}
.s-task{display:flex;align-items:center;gap:10px;padding:4px 8px;font-size:10.5px;background:#F1F1EC;border-top:1px solid #D7D6CE}
.s-task .lbl{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9px;letter-spacing:0.1em;text-transform:uppercase;color:#767368}
.s-group{display:inline-flex;align-items:stretch;border:1px solid #D7D6CE;background:#FFFFFF}
.s-group .app{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.1em;text-transform:uppercase;color:#767368;padding:2px 7px;border-right:1px solid #D7D6CE;display:flex;align-items:center;background:#F7F7F3}
.s-group .win{padding:2px 8px;font-size:10px;color:#23221E;border-right:1px solid #D7D6CE;display:flex;align-items:center;gap:5px;background:#F1F1EC}
.s-group .win:last-child{border-right:0}
.s-group .win.on{box-shadow:inset 3px 0 0 #C8102E;font-weight:700;background:#FFFFFF}
.s-group .win.min{color:#3C3A34;background:#FFFFFF;outline:1px dashed #6B6A64;outline-offset:-3px}
.s-task .tile{font-size:10px;padding:1px 8px;border:1px solid #C7C6BD;background:#FFFFFF;color:#3C3A34}
/* windows — title bar made visible: darker plate, ink name, clear border; front = red top edge + shadow */
.win-frame{background:#FFFFFF;border:1px solid #B9B8AE;display:flex;flex-direction:column;min-width:0;box-shadow:0 1px 2px rgba(35,34,30,0.12)}
.win-frame.front{border-color:#767368;box-shadow:0 6px 16px rgba(35,34,30,0.26)}
.stacked .win-frame{position:absolute}
.w-title{display:flex;align-items:center;gap:8px;height:28px;padding:0 6px 0 8px;background:#E4E3DC;border-bottom:1px solid #C9C6BC;font-size:11px;color:#3C3A34}
.front .w-title{background:#FFFFFF;color:#23221E;border-top:3px solid #C8102E;height:31px}
.w-title .app{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.1em;text-transform:uppercase;color:#23221E;font-weight:700}
.w-title .app::after{content:"/";margin-left:8px;color:#6B6A64}
.w-title .name{font-family:Fraunces,Georgia,serif;font-weight:600;font-size:12px;color:#3C3A34}
.front .w-title .name{color:#23221E;font-weight:700}
.w-title .new{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.12em;text-transform:uppercase;color:#23221E;background:#E4E3DC;padding:0 6px;line-height:15px}
.w-title .ctl{display:flex;gap:8px}
.w-title .ctl span{width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:12px;color:#23221E;background:transparent;border:0}
.w-body{padding:0;overflow:hidden}
.w-toolbar{display:flex;align-items:center;gap:8px;padding:5px 8px;background:#FAFAF8;border-bottom:1px solid #E6E5DF}
.w-h1{font-family:Fraunces,Georgia,serif;font-weight:700;font-size:13px;letter-spacing:-0.01em;margin:0;color:#23221E}
.w-count{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;color:#6B6A64}
.w-count b{color:#C8102E;font-weight:600}
.w-filters{margin-left:auto;display:flex;gap:4px}
.w-chip{padding:1px 7px;font-size:9.5px;font-family:"JetBrains Mono",ui-monospace,monospace;border:1px solid #D7D6CE;background:#FFFFFF;color:#3C3A34}
.w-chip.on{border-color:#767368;color:#23221E;font-weight:600}
.w-cta{padding:2px 9px;font-size:10px;background:#C8102E;color:#FFFFFF;border:1px solid #C8102E;font-weight:600}
table.w-tbl{width:100%;border-collapse:collapse;font-size:10.5px;background:#FFFFFF}
.w-tbl th{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;text-align:left;padding:4px 8px;white-space:nowrap;background:#F1F1EC;color:#23221E;border-bottom:1px solid #D7D6CE;border-right:1px solid #E6E5DF}
.w-tbl td{padding:4px 8px;border-bottom:1px solid #E6E5DF;border-right:1px solid #E6E5DF;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px}
.w-tbl td:last-child,.w-tbl th:last-child{border-right:0}
.w-tbl tr:nth-child(even) td{background:#FAFAF8}
.w-tbl td.num{font-family:"JetBrains Mono",ui-monospace,monospace;font-variant-numeric:tabular-nums;text-align:right}
.w-tbl th.num{text-align:right}
.w-tbl td.ref{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.03em}
.w-tbl tr.sel td{background:#F7F7F3;box-shadow:inset 3px 0 0 #C8102E}
.w-state{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.07em;text-transform:uppercase;padding:1px 5px;background:#F1F1EC;color:#23221E}
.rec{display:grid;grid-template-columns:repeat(2, minmax(0, 1fr));gap:0}
.rec .col{padding:6px 10px 8px;border-right:1px solid #E6E5DF}
.rec .col:last-child{border-right:0}
.rec h4{margin:4px 0 4px;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.12em;text-transform:uppercase;color:#767368;font-weight:600;border-bottom:1px solid #E6E5DF;padding-bottom:3px}
.kv{display:grid;grid-template-columns:96px 1fr;gap:2px 8px;font-size:10.5px;margin:0}
.kv dt{color:#767368}
.kv dd{margin:0;color:#23221E}
.kv dd.mono{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:10px;font-variant-numeric:tabular-nums}
.rec-actions{display:flex;gap:6px;padding:6px 10px;border-top:1px solid #E6E5DF;background:#FAFAF8}
.rec-actions span{padding:2px 9px;font-size:10px;border:1px solid #C7C6BD;background:#FFFFFF;color:#23221E}
.rec-actions span.primary{color:#FFFFFF;background:#C8102E;border:1px solid #C8102E;font-weight:600}
.steps{display:flex;gap:0;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.06em;text-transform:uppercase;border-bottom:1px solid #E6E5DF}
.steps span{padding:3px 8px;border-right:1px solid #E6E5DF;color:#6B6A64}
.steps span.done{color:#767368}
.steps span.now{color:#C8102E;font-weight:700;box-shadow:inset 0 -2px 0 #C8102E}
.rep{padding:6px 10px}
.rep .row{display:grid;grid-template-columns:150px 1fr;gap:10px;padding:4px 0;border-bottom:1px solid #E6E5DF;font-size:10.5px}
.rep .row:last-child{border-bottom:0}
.rep .name{color:#23221E;font-weight:600}
.rep .name small{display:block;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:8.5px;letter-spacing:0.08em;text-transform:uppercase;color:#6B6A64;font-weight:400}
.rep .hl{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;color:#3C3A34;font-variant-numeric:tabular-nums}
/* title-bar option closeups */
.opt{width:600px;padding:16px;background:#D4D0C6}
.opt .cap{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:9.5px;letter-spacing:0.12em;text-transform:uppercase;color:#767368;margin:0 0 8px}
.opt .win-frame{margin-bottom:14px}
.opt .win-frame:last-child{margin-bottom:0}
/* Option B: ink strip */
.optB .front .w-title{background:#3C3A34;color:#fff;border-top:0;height:28px;border-bottom:1px solid #23221E}
.optB .front .w-title .name{color:#fff}
.optB .front .w-title .app{color:#fff}
.optB .front .w-title .app::after{color:rgba(255,255,255,0.35)}
.optB .front .w-title .ctl span{color:#fff;box-shadow:inset 0 0 0 1px rgba(255,255,255,0.6)}
.optB .front .w-title .new{color:#fff;border-color:rgba(255,255,255,0.7)}
/* Option C: ruled */
.optC .w-title{background:#FFFFFF;border-bottom:2px solid #23221E;height:28px}
.optC .front .w-title{background:#FFFFFF;border-top:3px solid #C8102E;height:31px;border-bottom:2px solid #23221E}
.optC .win-frame{border-color:#23221E}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB