Skip to content

Commit 055f5f7

Browse files
committed
Redesign UI and add cross-format navigation
- Replace radio inputs with Bootstrap btn-check button groups for mode and compression level selectors - Add nav-pills format switcher linking to sibling format domains; URLs are built dynamically from the current hostname (preserving port) - Replace alert/list feature section with a 2-column emoji feature grid - Move page header outside the container so the gradient spans full viewport width - Remove card wrapper from the app area - Increase spacing throughout: page header padding, feature grid gap, drop zone padding, and element margins
1 parent 383fbdb commit 055f5f7

2 files changed

Lines changed: 133 additions & 77 deletions

File tree

App.razor

Lines changed: 82 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,59 @@
33
@inject BlazorDownloadFile.IBlazorDownloadFileService BlazorDownloadFileService
44
@inject NavigationManager NavManager
55

6-
<div class="mb-3">
7-
<div class="form-check form-check-inline">
8-
<input id="radio-compress" class="form-check-input" type="radio"
9-
checked="@(compressionMode == CompressionMode.Compress)"
10-
@onchange="@(() => compressionMode = CompressionMode.Compress)">
11-
<label class="form-check-label" for="radio-compress">Compress</label>
12-
</div>
13-
<div class="form-check form-check-inline">
14-
<input id="radio-decompress" class="form-check-input" type="radio"
15-
checked="@(compressionMode == CompressionMode.Decompress)"
16-
@onchange="@(() => compressionMode = CompressionMode.Decompress)">
17-
<label class="form-check-label" for="radio-decompress">Decompress</label>
18-
</div>
6+
<ul class="nav nav-pills mb-4 pb-1 border-bottom" role="navigation" aria-label="Compression format">
7+
@foreach (var fmt in new[] { CompressionFormat.GZip, CompressionFormat.Brotli, CompressionFormat.Deflate, CompressionFormat.ZLib, CompressionFormat.ZStd })
8+
{
9+
<li class="nav-item">
10+
@if (fmt == _format)
11+
{
12+
<span class="nav-link active" aria-current="page">@FormatDisplayName(fmt)</span>
13+
}
14+
else
15+
{
16+
<a class="nav-link" href="@FormatUrl(fmt)">@FormatDisplayName(fmt)</a>
17+
}
18+
</li>
19+
}
20+
</ul>
21+
22+
<div class="btn-group mb-4" role="group" aria-label="Mode">
23+
<input id="radio-compress" class="btn-check" type="radio" autocomplete="off"
24+
checked="@(compressionMode == CompressionMode.Compress)"
25+
@onchange="@(() => compressionMode = CompressionMode.Compress)">
26+
<label class="btn btn-outline-primary px-4" for="radio-compress">Compress</label>
27+
<input id="radio-decompress" class="btn-check" type="radio" autocomplete="off"
28+
checked="@(compressionMode == CompressionMode.Decompress)"
29+
@onchange="@(() => compressionMode = CompressionMode.Decompress)">
30+
<label class="btn btn-outline-primary px-4" for="radio-decompress">Decompress</label>
1931
</div>
32+
2033
@if (compressionMode == CompressionMode.Compress)
2134
{
2235
<div class="mb-3">
23-
<label class="form-label">Compression Level:</label>
24-
<div>
25-
<div class="form-check form-check-inline">
26-
<input id="radio-smallest" class="form-check-input" type="radio"
27-
checked="@(compressionLevel == CompressionLevel.SmallestSize)"
28-
@onchange="@(() => compressionLevel = CompressionLevel.SmallestSize)">
29-
<label class="form-check-label" for="radio-smallest">Smallest Size</label>
30-
</div>
31-
<div class="form-check form-check-inline">
32-
<input id="radio-optimal" class="form-check-input" type="radio"
33-
checked="@(compressionLevel == CompressionLevel.Optimal)"
34-
@onchange="@(() => compressionLevel = CompressionLevel.Optimal)">
35-
<label class="form-check-label" for="radio-optimal">Optimal</label>
36-
</div>
37-
<div class="form-check form-check-inline">
38-
<input id="radio-fastest" class="form-check-input" type="radio"
39-
checked="@(compressionLevel == CompressionLevel.Fastest)"
40-
@onchange="@(() => compressionLevel = CompressionLevel.Fastest)">
41-
<label class="form-check-label" for="radio-fastest">Fastest</label>
42-
</div>
43-
<div class="form-check form-check-inline">
44-
<input id="radio-no-compression" class="form-check-input" type="radio"
45-
checked="@(compressionLevel == CompressionLevel.NoCompression)"
46-
@onchange="@(() => compressionLevel = CompressionLevel.NoCompression)">
47-
<label class="form-check-label" for="radio-no-compression">No Compression</label>
48-
</div>
36+
<label class="form-label text-muted small text-uppercase fw-semibold ls-1">Compression Level</label>
37+
<div class="btn-group d-flex btn-group-sm" role="group" aria-label="Compression level">
38+
<input id="radio-smallest" class="btn-check" type="radio" autocomplete="off"
39+
checked="@(compressionLevel == CompressionLevel.SmallestSize)"
40+
@onchange="@(() => compressionLevel = CompressionLevel.SmallestSize)">
41+
<label class="btn btn-outline-secondary w-100" for="radio-smallest">Smallest Size</label>
42+
<input id="radio-optimal" class="btn-check" type="radio" autocomplete="off"
43+
checked="@(compressionLevel == CompressionLevel.Optimal)"
44+
@onchange="@(() => compressionLevel = CompressionLevel.Optimal)">
45+
<label class="btn btn-outline-secondary w-100" for="radio-optimal">Optimal</label>
46+
<input id="radio-fastest" class="btn-check" type="radio" autocomplete="off"
47+
checked="@(compressionLevel == CompressionLevel.Fastest)"
48+
@onchange="@(() => compressionLevel = CompressionLevel.Fastest)">
49+
<label class="btn btn-outline-secondary w-100" for="radio-fastest">Fastest</label>
50+
<input id="radio-no-compression" class="btn-check" type="radio" autocomplete="off"
51+
checked="@(compressionLevel == CompressionLevel.NoCompression)"
52+
@onchange="@(() => compressionLevel = CompressionLevel.NoCompression)">
53+
<label class="btn btn-outline-secondary w-100" for="radio-no-compression">No Compression</label>
4954
</div>
5055
</div>
5156
}
5257

53-
<div class="drop-zone mb-3 @(_dragCount > 0 ? "drop-zone--active" : "")"
58+
<div class="drop-zone mb-4 @(_dragCount > 0 ? "drop-zone--active" : "")"
5459
@ondragenter="@(() => _dragCount++)"
5560
@ondragleave="@(() => _dragCount--)"
5661
@ondrop="@(() => _dragCount = 0)"
@@ -71,39 +76,37 @@
7176
<div class="mb-3">
7277
@if (compressionMode == CompressionMode.Compress)
7378
{
74-
<button role="button" class="btn btn-primary" @onclick="CompressFiles" disabled="@(!anyFiles)">Compress Files</button>
79+
<button role="button" class="btn btn-primary btn-lg w-100" @onclick="CompressFiles" disabled="@(!anyFiles)">Compress Files</button>
7580
}
7681
else
7782
{
78-
<button role="button" class="btn btn-primary" @onclick="DecompressFiles" disabled="@(!anyFiles)">Decompress Files</button>
83+
<button role="button" class="btn btn-primary btn-lg w-100" @onclick="DecompressFiles" disabled="@(!anyFiles)">Decompress Files</button>
7984
}
80-
&nbsp;
8185
@if(!anyFiles){
82-
<span class="text-danger">No files selected</span>
86+
<div class="text-danger small text-center mt-1">No files selected</div>
8387
}
8488
</div>
8589

8690
<ul class="list-group">
8791
@foreach (var file in files)
8892
{
89-
<li class="list-group-item">
90-
@file.OriginalName
93+
<li class="list-group-item d-flex align-items-center gap-2 py-2">
94+
<span class="text-truncate flex-grow-1 small fw-medium">@file.OriginalName</span>
9195
@if(file.Status == "Compressing" || file.Status == "Decompressing")
9296
{
93-
<div class="spinner-grow text-primary spinner-grow-sm" role="status">
97+
<div class="spinner-grow text-primary spinner-grow-sm flex-shrink-0" role="status">
9498
<span class="visually-hidden">@file.Status</span>
9599
</div>
100+
<small class="text-muted text-nowrap">@file.Status…</small>
96101
}
97-
&nbsp;
98102
@if(file.Status.StartsWith("Error"))
99103
{
100-
<br />
101-
<span class="text-danger">⚠ @file.Status</span>
104+
<small class="text-danger text-nowrap">⚠ @file.Status</small>
102105
}
103106
@if(file.Status == "Finished")
104107
{
105-
<span class="text-success">✔ Finished</span>
106-
<span class="text-muted ms-2 small">@FormatBytes(file.OriginalSize)@FormatBytes(file.OutputSize)</span>
108+
<span class="text-success flex-shrink-0">✔ Finished</span>
109+
<span class="text-muted small text-nowrap">@FormatBytes(file.OriginalSize)@FormatBytes(file.OutputSize)</span>
107110
@if (compressionMode == CompressionMode.Compress)
108111
{
109112
<span class="@RatioBadgeCss(file.OriginalSize, file.OutputSize) ms-1">@RatioText(file.OriginalSize, file.OutputSize)</span>
@@ -123,14 +126,40 @@
123126

124127
private CompressionFormat _format = CompressionFormat.GZip;
125128
private int _dragCount;
126-
private string FormatName => _format switch
129+
130+
private static string FormatSlug(CompressionFormat fmt) => fmt switch
127131
{
128132
CompressionFormat.Brotli => "brotli",
129133
CompressionFormat.Deflate => "deflate",
130134
CompressionFormat.ZLib => "zlib",
131135
CompressionFormat.ZStd => "zstd",
132136
_ => "gzip"
133137
};
138+
139+
private static string FormatDisplayName(CompressionFormat fmt) => fmt switch
140+
{
141+
CompressionFormat.Brotli => "Brotli",
142+
CompressionFormat.Deflate => "Deflate",
143+
CompressionFormat.ZLib => "ZLib",
144+
CompressionFormat.ZStd => "Zstandard",
145+
_ => "GZIP"
146+
};
147+
148+
private string FormatUrl(CompressionFormat fmt)
149+
{
150+
var uri = new Uri(NavManager.Uri);
151+
var host = uri.Host;
152+
var currentSlug = FormatName;
153+
if (host.Contains(currentSlug))
154+
{
155+
var newHost = host.Replace(currentSlug, FormatSlug(fmt));
156+
var port = uri.IsDefaultPort ? "" : $":{uri.Port}";
157+
return $"{uri.Scheme}://{newHost}{port}";
158+
}
159+
return "#";
160+
}
161+
162+
private string FormatName => FormatSlug(_format);
134163
private string Extension => _format switch
135164
{
136165
CompressionFormat.Brotli => ".br",

wwwroot/index.html

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
position: relative;
6868
border: 2px dashed #dee2e6;
6969
border-radius: 0.5rem;
70-
padding: 1.5rem;
70+
padding: 2.5rem 1.5rem;
7171
text-align: center;
7272
cursor: pointer;
7373
transition: border-color 0.15s ease, background-color 0.15s ease;
@@ -87,6 +87,35 @@
8787
height: 100%;
8888
}
8989

90+
.page-header {
91+
padding: 3.5rem 0 2.5rem;
92+
border-bottom: 1px solid rgba(13, 110, 253, 0.12);
93+
background: linear-gradient(135deg, rgba(13,110,253,0.08) 0%, rgba(13,110,253,0.02) 50%, transparent 100%);
94+
}
95+
96+
.feature-grid {
97+
display: grid;
98+
grid-template-columns: 1fr 1fr;
99+
gap: 0.75rem;
100+
margin-bottom: 2rem;
101+
}
102+
103+
.feature-item {
104+
display: flex;
105+
align-items: center;
106+
gap: 0.75rem;
107+
padding: 0.85rem 1.1rem;
108+
background: rgba(13, 110, 253, 0.04);
109+
border: 1px solid rgba(13, 110, 253, 0.12);
110+
border-radius: 0.5rem;
111+
font-size: 0.9rem;
112+
font-weight: 500;
113+
}
114+
115+
.feature-icon {
116+
font-size: 1.2rem;
117+
line-height: 1;
118+
}
90119

91120
@media (min-width: 1200px) {
92121

@@ -115,31 +144,29 @@
115144

116145
<body class="d-flex flex-column h-100">
117146
<main class="flex-shrink-0">
118-
<div class="container">
119-
<h1 class="display-4">Online GZIP de/compressor</h1>
120-
<p id="intro-paragraph" style="font-size: 1.1rem;">
121-
This tool can compress and decompress files using the GZIP algorithm.
122-
The files are compressed/decompress right inside of your browser without transmitting the files to a
123-
server.
124-
</p>
125-
<div class="alert alert-primary">
126-
<ul style="font-size: 1.2rem;margin: 0;padding-left: 15px;" class="fw-bold">
127-
<li>Files are compress/decompressed on your device</li>
128-
<li>No files are transmitted</li>
129-
<li>Size limit depends on the limitations of your browser</li>
130-
<li>Works offline/installable</li>
131-
</ul>
147+
<div class="page-header">
148+
<div class="container">
149+
<h1 class="display-5 fw-bold">Online GZIP de/compressor</h1>
150+
<p id="intro-paragraph" class="lead mb-0">
151+
This tool can compress and decompress files using the GZIP algorithm.
152+
The files are compressed/decompress right inside of your browser without transmitting the files to a
153+
server.
154+
</p>
155+
</div>
156+
</div>
157+
<div class="container pt-4">
158+
<div class="feature-grid">
159+
<div class="feature-item"><span class="feature-icon">🔒</span> Files processed on your device</div>
160+
<div class="feature-item"><span class="feature-icon">🚫</span> No files transmitted</div>
161+
<div class="feature-item"><span class="feature-icon">📦</span> Size limit depends on your browser</div>
162+
<div class="feature-item"><span class="feature-icon">📶</span> Works offline &amp; installable</div>
132163
</div>
133164
<div class="row">
134165
<div class="col-9">
135-
<div class="card mb-3">
136-
<div class="card-body">
137-
<div id="app">
138-
<div class="d-flex justify-content-center align-items-center py-5">
139-
<div class="spinner-border text-primary" role="status">
140-
<span class="visually-hidden">Loading...</span>
141-
</div>
142-
</div>
166+
<div id="app">
167+
<div class="d-flex justify-content-center align-items-center py-5">
168+
<div class="spinner-border text-primary" role="status">
169+
<span class="visually-hidden">Loading...</span>
143170
</div>
144171
</div>
145172
</div>
@@ -210,7 +237,7 @@ <h1 class="display-4">Online GZIP de/compressor</h1>
210237
set('meta[property="og:description"]', 'content', l.desc);
211238
set('meta[name="twitter:title"]', 'content', l.title);
212239
set('meta[name="twitter:description"]','content', l.desc);
213-
set('h1.display-4', 'textContent', l.heading);
240+
set('h1', 'textContent', l.heading);
214241
set('#intro-paragraph','textContent', l.para);
215242
})();
216243
</script>

0 commit comments

Comments
 (0)