diff --git a/src/DnsmasqWebUI/Models/Config/DnsmasqOptionTooltips.cs b/src/DnsmasqWebUI/Models/Config/DnsmasqOptionTooltips.cs
new file mode 100644
index 0000000..0ec3c66
--- /dev/null
+++ b/src/DnsmasqWebUI/Models/Config/DnsmasqOptionTooltips.cs
@@ -0,0 +1,67 @@
+using System.Collections.Frozen;
+
+namespace DnsmasqWebUI.Models.Config;
+
+///
+/// Short explainer tooltips for dnsmasq option names. Shown on hover over the field label.
+/// One entry per effective-config key for consistency.
+///
+public static class DnsmasqOptionTooltips
+{
+ ///
Display label used in EffectiveConfigFieldBuilder for server/local multi-value.
+ public const string ServerLocalLabel = "server / local";
+
+ private static readonly FrozenDictionary
Tooltips = new Dictionary(StringComparer.Ordinal)
+ {
+ // --- Hosts ---
+ [DnsmasqConfKeys.NoHosts] = "Do not read /etc/hosts; only use addn-hosts and DHCP/hosts.",
+ [DnsmasqConfKeys.AddnHosts] = "Additional hosts files (one per line).",
+
+ // --- Resolver / DNS ---
+ [DnsmasqConfKeys.ExpandHosts] = "Expand simple names in /etc/hosts with the local domain.",
+ [DnsmasqConfKeys.BogusPriv] = "Do not forward reverse lookups for private IP ranges.",
+ [DnsmasqConfKeys.StrictOrder] = "Query servers in the order specified; do not try the next on failure.",
+ [DnsmasqConfKeys.NoResolv] = "Do not read /etc/resolv.conf; use only server= and resolv-file=.",
+ [DnsmasqConfKeys.DomainNeeded] = "Never forward plain names (without a domain) to upstream.",
+ [DnsmasqConfKeys.Port] = "Listen on this port for DNS queries (default 53).",
+ [ServerLocalLabel] = "Upstream DNS servers (server=) and local-only domains (local=). Order is preserved.",
+ [DnsmasqConfKeys.Address] = "Map a domain or hostname to an IP (e.g. for ad blocking or local names). Can repeat.",
+ [DnsmasqConfKeys.ResolvFile] = "File(s) to read upstream server addresses from (e.g. from DHCP). Can repeat.",
+
+ // --- DHCP ---
+ [DnsmasqConfKeys.DhcpAuthoritative] = "DHCP server is authoritative; required for DHCP to work.",
+ [DnsmasqConfKeys.LeasefileRo] = "Do not create or truncate the DHCP lease file.",
+ [DnsmasqConfKeys.DhcpLeasefile] = "Path to the DHCP lease file.",
+ [DnsmasqConfKeys.DhcpLeaseMax] = "Maximum number of DHCP leases to hold.",
+ [DnsmasqConfKeys.DhcpTtl] = "TTL for DHCP names in the DNS cache.",
+ [DnsmasqConfKeys.DhcpRange] = "DHCP address range(s) and lease time. Can repeat.",
+ [DnsmasqConfKeys.DhcpHost] = "DHCP host entries (reservations by MAC or client-id). Can repeat.",
+ [DnsmasqConfKeys.DhcpOption] = "DHCP option lines sent to clients. Can repeat.",
+
+ // --- Cache ---
+ [DnsmasqConfKeys.CacheSize] = "Maximum number of DNS cache entries (0 to disable).",
+ [DnsmasqConfKeys.LocalTtl] = "TTL for entries from /etc/hosts and DHCP.",
+ [DnsmasqConfKeys.NoNegcache] = "Do not cache negative (NXDOMAIN) responses.",
+ [DnsmasqConfKeys.NegTtl] = "TTL for negative (cached miss) responses.",
+ [DnsmasqConfKeys.MaxTtl] = "Maximum TTL for cached responses (cap on upstream TTL).",
+ [DnsmasqConfKeys.MaxCacheTtl] = "Maximum TTL for positive cache entries.",
+ [DnsmasqConfKeys.MinCacheTtl] = "Minimum TTL for positive cache entries.",
+
+ // --- Process & networking ---
+ [DnsmasqConfKeys.NoPoll] = "Do not poll /etc/resolv.conf for changes.",
+ [DnsmasqConfKeys.BindInterfaces] = "Only bind to interfaces in use; do not bind to wildcard.",
+ [DnsmasqConfKeys.Interface] = "Only use these interfaces for DHCP/DNS. Can repeat.",
+ [DnsmasqConfKeys.ListenAddress] = "Listen on these addresses for DNS/DHCP. Can repeat.",
+ [DnsmasqConfKeys.ExceptInterface] = "Do not use these interfaces. Can repeat.",
+ [DnsmasqConfKeys.PidFile] = "Write process ID to this file.",
+ [DnsmasqConfKeys.User] = "Run as this user after startup (requires root).",
+ [DnsmasqConfKeys.Group] = "Run as this group after startup (requires root).",
+ [DnsmasqConfKeys.LogFacility] = "Syslog facility for dnsmasq logs (e.g. local0, daemon).",
+ }.ToFrozenDictionary();
+
+ /// Returns a short tooltip for the option, or null if none is defined.
+ public static string? Get(string optionName)
+ {
+ return Tooltips.TryGetValue(optionName, out var tip) ? tip : null;
+ }
+}
diff --git a/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigDisplayMode.cs b/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigDisplayMode.cs
new file mode 100644
index 0000000..71f7a0d
--- /dev/null
+++ b/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigDisplayMode.cs
@@ -0,0 +1,14 @@
+namespace DnsmasqWebUI.Models.EffectiveConfig;
+
+///
+/// Display mode for effective-config fields. Cascaded from the parent so that
+/// OptionItem, MultiValueRow, and custom value displays can render view vs edit appropriately.
+///
+public enum EffectiveConfigDisplayMode
+{
+ /// Show values as read-only text.
+ View,
+
+ /// Show editable controls (inputs, toggles). Not yet implemented; plumbing only.
+ Edit,
+}
diff --git a/src/DnsmasqWebUI/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs b/src/DnsmasqWebUI/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs
new file mode 100644
index 0000000..f3a5f78
--- /dev/null
+++ b/src/DnsmasqWebUI/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Components;
+using DnsmasqWebUI.Models.EffectiveConfig;
+
+namespace DnsmasqWebUI.Services.Abstractions;
+
+///
+/// Lookup for custom display fragments per effective-config field (section + option).
+/// When non-null, the fragment renders the value portion of the row; label and source badge stay shared.
+/// Registered as singleton via assembly scanning ().
+///
+public interface IEffectiveConfigRenderFragmentRegistry : IApplicationSingleton
+{
+ ///
+ /// Returns a fragment that renders the custom value UI for this field, or null to use default rendering.
+ ///
+ RenderFragment? GetDisplayFragment(string sectionId, string optionName);
+}
diff --git a/src/DnsmasqWebUI/Services/EffectiveConfigRenderFragmentRegistry.cs b/src/DnsmasqWebUI/Services/EffectiveConfigRenderFragmentRegistry.cs
new file mode 100644
index 0000000..05d8ee4
--- /dev/null
+++ b/src/DnsmasqWebUI/Services/EffectiveConfigRenderFragmentRegistry.cs
@@ -0,0 +1,54 @@
+using Microsoft.AspNetCore.Components;
+using DnsmasqWebUI.Models.Config;
+using DnsmasqWebUI.Models.EffectiveConfig;
+using DnsmasqWebUI.Services.Abstractions;
+using DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig.CustomDisplays;
+
+namespace DnsmasqWebUI.Services;
+
+///
+/// Maps (sectionId, optionName) to custom effective-config value display component types and builds
+/// RenderFragments that render those components with the descriptor.
+///
+public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragmentRegistry
+{
+ private readonly Dictionary<(string SectionId, string OptionName), Type> _displayComponents = new();
+
+ public EffectiveConfigRenderFragmentRegistry()
+ {
+ // Port: single-field display (e.g. "53 (default DNS port)").
+ _displayComponents[(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.Port)] = typeof(PortValueDisplay);
+
+ // Boolean flags: shared display ("Enabled" / "Disabled") for all flag options.
+ RegisterFlag(EffectiveConfigFieldBuilder.SectionHosts, DnsmasqConfKeys.NoHosts);
+ RegisterFlag(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.ExpandHosts);
+ RegisterFlag(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.BogusPriv);
+ RegisterFlag(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.StrictOrder);
+ RegisterFlag(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.NoResolv);
+ RegisterFlag(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.DomainNeeded);
+ RegisterFlag(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.NoNegcache);
+ RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.NoPoll);
+ RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.BindInterfaces);
+ RegisterFlag(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpAuthoritative);
+ RegisterFlag(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.LeasefileRo);
+ }
+
+ private void RegisterFlag(string sectionId, string optionName)
+ {
+ _displayComponents[(sectionId, optionName)] = typeof(FlagValueDisplay);
+ }
+
+ ///
+ public RenderFragment? GetDisplayFragment(string sectionId, string optionName)
+ {
+ if (!_displayComponents.TryGetValue((sectionId, optionName), out var componentType))
+ return null;
+
+ return descriptor => builder =>
+ {
+ builder.OpenComponent(0, componentType);
+ builder.AddAttribute(1, "Descriptor", descriptor);
+ builder.CloseComponent();
+ };
+ }
+}
diff --git a/src/DnsmasqWebUI/wwwroot/apple-touch-icon.png b/src/DnsmasqWebUI/wwwroot/apple-touch-icon.png
new file mode 100644
index 0000000..77d523c
Binary files /dev/null and b/src/DnsmasqWebUI/wwwroot/apple-touch-icon.png differ
diff --git a/src/DnsmasqWebUI/wwwroot/favicon-16.png b/src/DnsmasqWebUI/wwwroot/favicon-16.png
new file mode 100644
index 0000000..141ccd1
Binary files /dev/null and b/src/DnsmasqWebUI/wwwroot/favicon-16.png differ
diff --git a/src/DnsmasqWebUI/wwwroot/favicon-192.png b/src/DnsmasqWebUI/wwwroot/favicon-192.png
new file mode 100644
index 0000000..74a583e
Binary files /dev/null and b/src/DnsmasqWebUI/wwwroot/favicon-192.png differ
diff --git a/src/DnsmasqWebUI/wwwroot/favicon-32.png b/src/DnsmasqWebUI/wwwroot/favicon-32.png
new file mode 100644
index 0000000..eaaef5f
Binary files /dev/null and b/src/DnsmasqWebUI/wwwroot/favicon-32.png differ
diff --git a/src/DnsmasqWebUI/wwwroot/favicon-48.png b/src/DnsmasqWebUI/wwwroot/favicon-48.png
new file mode 100644
index 0000000..7d5a73c
Binary files /dev/null and b/src/DnsmasqWebUI/wwwroot/favicon-48.png differ
diff --git a/src/DnsmasqWebUI/wwwroot/favicon-512.png b/src/DnsmasqWebUI/wwwroot/favicon-512.png
new file mode 100644
index 0000000..f2f8470
Binary files /dev/null and b/src/DnsmasqWebUI/wwwroot/favicon-512.png differ
diff --git a/src/DnsmasqWebUI/wwwroot/favicon.png b/src/DnsmasqWebUI/wwwroot/favicon.png
index 182561c..46dc665 100644
Binary files a/src/DnsmasqWebUI/wwwroot/favicon.png and b/src/DnsmasqWebUI/wwwroot/favicon.png differ
diff --git a/src/DnsmasqWebUI/wwwroot/logo-source.png b/src/DnsmasqWebUI/wwwroot/logo-source.png
new file mode 100644
index 0000000..ff51718
Binary files /dev/null and b/src/DnsmasqWebUI/wwwroot/logo-source.png differ
diff --git a/src/DnsmasqWebUI/wwwroot/logo-transparent-source.png b/src/DnsmasqWebUI/wwwroot/logo-transparent-source.png
new file mode 100644
index 0000000..86c91c0
Binary files /dev/null and b/src/DnsmasqWebUI/wwwroot/logo-transparent-source.png differ
diff --git a/src/DnsmasqWebUI/wwwroot/logo.png b/src/DnsmasqWebUI/wwwroot/logo.png
deleted file mode 100644
index bfc9b7f..0000000
Binary files a/src/DnsmasqWebUI/wwwroot/logo.png and /dev/null differ
diff --git a/src/DnsmasqWebUI/wwwroot/logo.webp b/src/DnsmasqWebUI/wwwroot/logo.webp
new file mode 100644
index 0000000..e9ccb77
Binary files /dev/null and b/src/DnsmasqWebUI/wwwroot/logo.webp differ