Prepare Lildra 0.3.1 Web Store submission

This commit is contained in:
git
2026-07-27 21:16:04 +10:00
parent 3b2626a044
commit 6ddda00eb3
21 changed files with 502 additions and 2 deletions
+200
View File
@@ -0,0 +1,200 @@
param(
[string]$Root = (Split-Path -Parent $PSScriptRoot)
)
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Drawing
$sourceDir = Join-Path $Root "screenshots"
$outputDir = Join-Path $Root "store-assets\ready-to-upload"
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
$purpleDark = [System.Drawing.ColorTranslator]::FromHtml("#281052")
$purple = [System.Drawing.ColorTranslator]::FromHtml("#6D35D8")
$purpleBright = [System.Drawing.ColorTranslator]::FromHtml("#8250E8")
$white = [System.Drawing.Color]::White
$ink = [System.Drawing.ColorTranslator]::FromHtml("#171429")
function New-Canvas([int]$Width, [int]$Height) {
$bitmap = New-Object System.Drawing.Bitmap($Width, $Height, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
$bitmap.SetResolution(96, 96)
return $bitmap
}
function Save-Png($Bitmap, [string]$Name) {
$path = Join-Path $outputDir $Name
$Bitmap.Save($path, [System.Drawing.Imaging.ImageFormat]::Png)
return $path
}
function Set-Quality($Graphics) {
$Graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
$Graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$Graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
$Graphics.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::ClearTypeGridFit
}
function Draw-Cover($Graphics, $Image, [System.Drawing.Rectangle]$Destination, [System.Drawing.Rectangle]$Source) {
$sourceRatio = $Source.Width / $Source.Height
$destinationRatio = $Destination.Width / $Destination.Height
if ($sourceRatio -gt $destinationRatio) {
$cropWidth = [int]($Source.Height * $destinationRatio)
$Source.X += [int](($Source.Width - $cropWidth) / 2)
$Source.Width = $cropWidth
} else {
$cropHeight = [int]($Source.Width / $destinationRatio)
$Source.Y += [int](($Source.Height - $cropHeight) / 2)
$Source.Height = $cropHeight
}
$Graphics.DrawImage($Image, $Destination, $Source, [System.Drawing.GraphicsUnit]::Pixel)
}
function Draw-Header($Graphics, [string]$Title, [string]$Subtitle) {
$brush = New-Object System.Drawing.Drawing2D.LinearGradientBrush(
(New-Object System.Drawing.Rectangle(0, 0, 1280, 112)),
$purpleBright,
$purpleDark,
0
)
$titleFont = New-Object System.Drawing.Font("Segoe UI", 27, [System.Drawing.FontStyle]::Bold)
$subtitleFont = New-Object System.Drawing.Font("Segoe UI", 15, [System.Drawing.FontStyle]::Regular)
try {
$Graphics.FillRectangle($brush, 0, 0, 1280, 112)
$Graphics.DrawString($Title, $titleFont, [System.Drawing.Brushes]::White, 38, 20)
$Graphics.DrawString($Subtitle, $subtitleFont, [System.Drawing.Brushes]::White, 40, 67)
} finally {
$brush.Dispose(); $titleFont.Dispose(); $subtitleFont.Dispose()
}
}
function Redact-SandboxName($Graphics) {
$cover = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
try { $Graphics.FillRectangle($cover, 128, 124, 150, 34) } finally { $cover.Dispose() }
}
function Build-Screenshot([string]$Name, [string]$SourceName, [string]$Title, [string]$Subtitle, [System.Drawing.Rectangle]$Crop, [bool]$Redact) {
$source = [System.Drawing.Image]::FromFile((Join-Path $sourceDir $SourceName))
$canvas = New-Canvas 1280 800
$graphics = [System.Drawing.Graphics]::FromImage($canvas)
try {
Set-Quality $graphics
Draw-Header $graphics $Title $Subtitle
Draw-Cover $graphics $source (New-Object System.Drawing.Rectangle(0, 112, 1280, 688)) $Crop
if ($Redact) { Redact-SandboxName $graphics }
Save-Png $canvas $Name | Out-Null
} finally {
$graphics.Dispose(); $canvas.Dispose(); $source.Dispose()
}
}
$viewportCrop = New-Object System.Drawing.Rectangle(0, 100, 1920, 1040)
Build-Screenshot "01-original-canvas-page.png" "original.png" "A normal Canvas lesson page" "Global and course navigation surround the lesson." $viewportCrop $true
Build-Screenshot "02-lildra-focused-reading.png" "lildra.png" "A calmer reading view" "Lildra hides surrounding navigation and centres the lesson." $viewportCrop $false
Build-Screenshot "03-sticky-dynamic-title.png" "lildra.png" "The lesson title stays visible" "A compact title bar follows the current Canvas page title." (New-Object System.Drawing.Rectangle(170, 100, 1580, 850)) $false
Build-Screenshot "04-original-content-preserved.png" "lildra.png" "Original lesson content remains" "Headings, text and images remain in the original page." (New-Object System.Drawing.Rectangle(270, 240, 1500, 850)) $false
$before = [System.Drawing.Image]::FromFile((Join-Path $sourceDir "original.png"))
$after = [System.Drawing.Image]::FromFile((Join-Path $sourceDir "lildra.png"))
$comparison = New-Canvas 1280 800
$graphics = [System.Drawing.Graphics]::FromImage($comparison)
try {
Set-Quality $graphics
Draw-Header $graphics "Before and after" "The same lesson, with surrounding Canvas navigation hidden."
Draw-Cover $graphics $before (New-Object System.Drawing.Rectangle(0, 112, 632, 688)) (New-Object System.Drawing.Rectangle(0, 100, 1050, 1040))
Draw-Cover $graphics $after (New-Object System.Drawing.Rectangle(648, 112, 632, 688)) (New-Object System.Drawing.Rectangle(250, 100, 1050, 1040))
$divider = New-Object System.Drawing.SolidBrush($purple)
$labelFont = New-Object System.Drawing.Font("Segoe UI", 15, [System.Drawing.FontStyle]::Bold)
try {
$graphics.FillRectangle($divider, 632, 112, 16, 688)
$graphics.FillRectangle([System.Drawing.Brushes]::White, 18, 130, 92, 34)
$graphics.FillRectangle([System.Drawing.Brushes]::White, 666, 130, 92, 34)
$graphics.DrawString("Before", $labelFont, (New-Object System.Drawing.SolidBrush($ink)), 28, 134)
$graphics.DrawString("Lildra", $labelFont, (New-Object System.Drawing.SolidBrush($purple)), 676, 134)
# Remove the private sandbox course name from the before panel.
$graphics.FillRectangle([System.Drawing.Brushes]::White, 102, 124, 145, 36)
} finally { $divider.Dispose(); $labelFont.Dispose() }
Save-Png $comparison "05-before-and-after.png" | Out-Null
} finally {
$graphics.Dispose(); $comparison.Dispose(); $before.Dispose(); $after.Dispose()
}
# Create a store icon candidate from the established logo. Only edge-connected
# near-white canvas pixels are removed; the white L inside the purple mark remains.
$originalIcon = [System.Drawing.Image]::FromFile((Join-Path $Root "icons\LildraLogoHiRes.png"))
$sourceIcon = New-Canvas $originalIcon.Width $originalIcon.Height
$sourceGraphics = [System.Drawing.Graphics]::FromImage($sourceIcon)
try {
$sourceGraphics.DrawImageUnscaled($originalIcon, 0, 0)
} finally {
$sourceGraphics.Dispose(); $originalIcon.Dispose()
}
$visited = New-Object 'bool[,]' $sourceIcon.Width, $sourceIcon.Height
$queue = New-Object 'System.Collections.Generic.Queue[System.Drawing.Point]'
for ($x = 0; $x -lt $sourceIcon.Width; $x++) {
$queue.Enqueue([System.Drawing.Point]::new($x, 0))
$queue.Enqueue([System.Drawing.Point]::new($x, ($sourceIcon.Height - 1)))
}
for ($y = 0; $y -lt $sourceIcon.Height; $y++) {
$queue.Enqueue([System.Drawing.Point]::new(0, $y))
$queue.Enqueue([System.Drawing.Point]::new(($sourceIcon.Width - 1), $y))
}
while ($queue.Count -gt 0) {
$point = $queue.Dequeue()
if ($point.X -lt 0 -or $point.Y -lt 0 -or $point.X -ge $sourceIcon.Width -or $point.Y -ge $sourceIcon.Height -or $visited[$point.X, $point.Y]) { continue }
$visited[$point.X, $point.Y] = $true
$pixel = $sourceIcon.GetPixel($point.X, $point.Y)
if ($pixel.R -lt 215 -or $pixel.G -lt 215 -or $pixel.B -lt 215) { continue }
$sourceIcon.SetPixel($point.X, $point.Y, [System.Drawing.Color]::Transparent)
$queue.Enqueue([System.Drawing.Point]::new(($point.X + 1), $point.Y))
$queue.Enqueue([System.Drawing.Point]::new(($point.X - 1), $point.Y))
$queue.Enqueue([System.Drawing.Point]::new($point.X, ($point.Y + 1)))
$queue.Enqueue([System.Drawing.Point]::new($point.X, ($point.Y - 1)))
}
$storeIcon = New-Canvas 128 128
$graphics = [System.Drawing.Graphics]::FromImage($storeIcon)
try {
Set-Quality $graphics
$graphics.Clear([System.Drawing.Color]::Transparent)
$graphics.DrawImage($sourceIcon, (New-Object System.Drawing.Rectangle(16, 16, 96, 96)))
Save-Png $storeIcon "store-icon-128.png" | Out-Null
} finally { $graphics.Dispose(); $storeIcon.Dispose(); $sourceIcon.Dispose() }
$icon = [System.Drawing.Image]::FromFile((Join-Path $outputDir "store-icon-128.png"))
$focused = [System.Drawing.Image]::FromFile((Join-Path $sourceDir "lildra.png"))
foreach ($promo in @(
@{Name="small-promo-440x280.png"; Width=440; Height=280; Icon=112; Screenshot=$false},
@{Name="marquee-promo-1400x560.png"; Width=1400; Height=560; Icon=170; Screenshot=$true}
)) {
$canvas = New-Canvas $promo.Width $promo.Height
$graphics = [System.Drawing.Graphics]::FromImage($canvas)
$gradient = New-Object System.Drawing.Drawing2D.LinearGradientBrush(
(New-Object System.Drawing.Rectangle(0, 0, $promo.Width, $promo.Height)),
$purpleBright,
$purpleDark,
12
)
$nameFont = New-Object System.Drawing.Font("Segoe UI", $(if($promo.Width -gt 500){58}else{34}), [System.Drawing.FontStyle]::Bold)
$tagFont = New-Object System.Drawing.Font("Segoe UI", $(if($promo.Width -gt 500){24}else{15}), [System.Drawing.FontStyle]::Regular)
try {
Set-Quality $graphics
$graphics.FillRectangle($gradient, 0, 0, $promo.Width, $promo.Height)
$iconX = $(if($promo.Width -gt 500){80}else{34})
$iconY = [int](($promo.Height - $promo.Icon) / 2)
$graphics.DrawImage($icon, $iconX, $iconY, $promo.Icon, $promo.Icon)
$textX = $iconX + $promo.Icon + $(if($promo.Width -gt 500){42}else{22})
$graphics.DrawString("Lildra", $nameFont, [System.Drawing.Brushes]::White, $textX, $(if($promo.Width -gt 500){170}else{105}))
if ($promo.Width -gt 500) {
$graphics.DrawString("Distraction-free lesson reading", $tagFont, [System.Drawing.Brushes]::White, $textX + 2, 250)
}
if ($promo.Screenshot) {
$panel = New-Object System.Drawing.Rectangle(785, 55, 570, 450)
Draw-Cover $graphics $focused $panel $viewportCrop
}
Save-Png $canvas $promo.Name | Out-Null
} finally {
$gradient.Dispose(); $nameFont.Dispose(); $tagFont.Dispose(); $graphics.Dispose(); $canvas.Dispose()
}
}
$icon.Dispose(); $focused.Dispose()
Get-ChildItem -LiteralPath $outputDir -File | Sort-Object Name | Select-Object Name, Length