diff --git a/hscontrol/templates/design.go b/hscontrol/templates/design.go
index da9cda51..7c9194b0 100644
--- a/hscontrol/templates/design.go
+++ b/hscontrol/templates/design.go
@@ -10,41 +10,6 @@ import (
// These constants define the visual language for all Headscale HTML templates.
// They ensure consistency across all pages and make it easy to maintain and update the design.
-// Color System
-// EXTRACTED FROM: https://headscale.net/stable/assets/stylesheets/main.342714a4.min.css
-// Material for MkDocs design system - exact values from official docs.
-const (
- // Text colors - from --md-default-fg-color CSS variables.
- colorTextPrimary = "#000000de" //nolint:unused // rgba(0,0,0,0.87) - Body text
- colorTextSecondary = "#0000008a" //nolint:unused // rgba(0,0,0,0.54) - Headings (--md-default-fg-color--light)
- colorTextTertiary = "#00000052" //nolint:unused // rgba(0,0,0,0.32) - Lighter text
- colorTextLightest = "#00000012" //nolint:unused // rgba(0,0,0,0.07) - Lightest text
-
- // Code colors - from --md-code-* CSS variables.
- colorCodeFg = "#36464e" //nolint:unused // Code text color (--md-code-fg-color)
- colorCodeBg = "#f5f5f5" //nolint:unused // Code background (--md-code-bg-color)
-
- // Border colors.
- colorBorderLight = "#e5e7eb" //nolint:unused // Light borders
- colorBorderMedium = "#d1d5db" //nolint:unused // Medium borders
-
- // Background colors.
- colorBackgroundPage = "#ffffff" //nolint:unused // Page background
- colorBackgroundCard = "#ffffff" //nolint:unused // Card/content background
-
- // Accent colors - from --md-primary/accent-fg-color.
- colorPrimaryAccent = "#4051b5" //nolint:unused // Primary accent (links)
- colorAccent = "#526cfe" //nolint:unused // Secondary accent
-
- // Success colors.
- colorSuccess = "#059669" //nolint:unused // Success states
- colorSuccessLight = "#d1fae5" //nolint:unused // Success backgrounds
-
- // Error colors.
- colorError = "#dc2626" //nolint:unused // Error states (red-600)
- colorErrorLight = "#fee2e2" //nolint:unused // Error backgrounds (red-100)
-)
-
// Spacing System
// Based on 4px/8px base unit for consistent rhythm.
// Uses rem units for scalability with user font size preferences.
@@ -60,278 +25,23 @@ const (
// Shared CSS value constants used across templates.
const (
- cssBorderHS = "1px solid var(--hs-border)" //nolint:unused // Shared HS border
- cssBreakWord = "break-word" //nolint:unused // Word wrapping
- cssCenter = "center" //nolint:unused // Center alignment
- cssOverflowWrap = "overflow-wrap" //nolint:unused // CSS property key
+ cssBorderHS = "1px solid var(--hs-border)" //nolint:unused // Shared HS border
+ cssCenter = "center" //nolint:unused // Center alignment
)
// Typography System
// EXTRACTED FROM: https://headscale.net/stable/assets/stylesheets/main.342714a4.min.css
// Material for MkDocs typography - exact values from .md-typeset CSS.
const (
- // Font families - from CSS custom properties.
- fontFamilySystem = `"Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif` //nolint:unused
- fontFamilyCode = `"Roboto Mono", "SF Mono", Monaco, "Cascadia Code", Consolas, "Courier New", monospace` //nolint:unused
-
// Font sizes - from .md-typeset CSS rules.
- fontSizeBase = "0.8rem" //nolint:unused // 12.8px - Base text (.md-typeset)
- fontSizeH1 = "2em" //nolint:unused // 2x base - Main headings
- fontSizeH2 = "1.5625em" //nolint:unused // 1.5625x base - Section headings
- fontSizeH3 = "1.25em" //nolint:unused // 1.25x base - Subsection headings
- fontSizeSmall = "0.8em" //nolint:unused // 0.8x base - Small text
- fontSizeCode = "0.85em" //nolint:unused // 0.85x base - Inline code
+ fontSizeBase = "0.8rem" //nolint:unused // 12.8px - Base text (.md-typeset)
+ fontSizeH3 = "1.25em" //nolint:unused // 1.25x base - Subsection headings
+ fontSizeSmall = "0.8em" //nolint:unused // 0.8x base - Small text
// Line heights - from .md-typeset CSS rules.
lineHeightBase = "1.6" //nolint:unused // Body text (.md-typeset)
- lineHeightH1 = "1.3" //nolint:unused // H1 headings
- lineHeightH2 = "1.4" //nolint:unused // H2 headings
- lineHeightH3 = "1.5" //nolint:unused // H3 headings
- lineHeightCode = "1.4" //nolint:unused // Code blocks (pre)
)
-// Responsive Container Component
-// Creates a centered container with responsive padding and max-width.
-// Mobile-first approach: starts at 100% width with padding, constrains on larger screens.
-//
-//nolint:unused // Reserved for future use in Phase 4.
-func responsiveContainer(children ...elem.Node) *elem.Element {
- return elem.Div(attrs.Props{
- attrs.Style: styles.Props{
- styles.Width: "100%",
- styles.MaxWidth: "min(800px, 90vw)", // Responsive: 90% of viewport or 800px max
- styles.Margin: "0 auto", // Center horizontally
- styles.Padding: "clamp(1rem, 5vw, 2.5rem)", // Fluid padding: 16px to 40px
- }.ToInline(),
- }, children...)
-}
-
-// Card Component
-// Reusable card for grouping related content with visual separation.
-// Parameters:
-// - title: Optional title for the card (empty string for no title)
-// - children: Content elements to display in the card
-//
-//nolint:unused // Reserved for future use in Phase 4.
-func card(title string, children ...elem.Node) *elem.Element {
- cardContent := children
- if title != "" {
- cardContent = append([]elem.Node{
- elem.H3(attrs.Props{
- attrs.Style: styles.Props{
- styles.MarginTop: "0",
- styles.MarginBottom: spaceM,
- }.ToInline(),
- }, elem.Text(title)),
- }, children...)
- }
-
- return elem.Div(attrs.Props{
- attrs.Style: styles.Props{
- styles.Background: "var(--hs-bg)",
- styles.Border: cssBorderHS,
- styles.BorderRadius: spaceS,
- styles.Padding: "clamp(1rem, 3vw, 1.5rem)",
- styles.MarginBottom: spaceL,
- styles.BoxShadow: "0 1px 3px rgba(0,0,0,0.1)",
- }.ToInline(),
- }, cardContent...)
-}
-
-// Code Block Component
-// EXTRACTED FROM: .md-typeset pre CSS rules
-// Exact styling from Material for MkDocs documentation.
-//
-//nolint:unused // Used across apple.go, windows.go, register_web.go templates.
-func codeBlock(code string) *elem.Element {
- return elem.Pre(attrs.Props{
- attrs.Style: styles.Props{
- styles.Display: "block",
- styles.Padding: "0.77em 1.18em", // From .md-typeset pre
- styles.Border: "none", // No border in original
- styles.BorderRadius: "0.1rem", // From .md-typeset code
- styles.BackgroundColor: colorCodeBg, // #f5f5f5
- styles.FontFamily: fontFamilyCode, // Roboto Mono
- styles.FontSize: fontSizeCode, // 0.85em
- styles.LineHeight: lineHeightCode, // 1.4
- styles.OverflowX: "auto", // Horizontal scroll
- cssOverflowWrap: cssBreakWord, // Word wrapping
- "word-wrap": cssBreakWord, // Legacy support
- styles.WhiteSpace: "pre-wrap", // Preserve whitespace
- styles.MarginTop: spaceM, // 1em
- styles.MarginBottom: spaceM, // 1em
- styles.Color: colorCodeFg, // #36464e
- styles.BoxShadow: "none", // No shadow in original
- }.ToInline(),
- },
- elem.Code(nil, elem.Text(code)),
- )
-}
-
-// Base Typeset Styles
-// Returns inline styles for the main content container that matches .md-typeset.
-// EXTRACTED FROM: .md-typeset CSS rule from Material for MkDocs.
-//
-//nolint:unused // Used in general.go for mdTypesetBody.
-func baseTypesetStyles() styles.Props {
- return styles.Props{
- styles.FontSize: fontSizeBase, // 0.8rem
- styles.LineHeight: lineHeightBase, // 1.6
- styles.Color: colorTextPrimary,
- styles.FontFamily: fontFamilySystem,
- cssOverflowWrap: cssBreakWord,
- styles.TextAlign: "left",
- }
-}
-
-// H1 Styles
-// Returns inline styles for H1 headings that match .md-typeset h1.
-// EXTRACTED FROM: .md-typeset h1 CSS rule from Material for MkDocs.
-//
-//nolint:unused // Used across templates for main headings.
-func h1Styles() styles.Props {
- return styles.Props{
- styles.Color: colorTextSecondary, // rgba(0, 0, 0, 0.54)
- styles.FontSize: fontSizeH1, // 2em
- styles.LineHeight: lineHeightH1, // 1.3
- styles.Margin: "0 0 1.25em",
- styles.FontWeight: "300",
- "letter-spacing": "-0.01em",
- styles.FontFamily: fontFamilySystem, // Roboto
- cssOverflowWrap: cssBreakWord,
- }
-}
-
-// H2 Styles
-// Returns inline styles for H2 headings that match .md-typeset h2.
-// EXTRACTED FROM: .md-typeset h2 CSS rule from Material for MkDocs.
-//
-//nolint:unused // Used across templates for section headings.
-func h2Styles() styles.Props {
- return styles.Props{
- styles.FontSize: fontSizeH2, // 1.5625em
- styles.LineHeight: lineHeightH2, // 1.4
- styles.Margin: "1.6em 0 0.64em",
- styles.FontWeight: "300",
- "letter-spacing": "-0.01em",
- styles.Color: colorTextSecondary, // rgba(0, 0, 0, 0.54)
- styles.FontFamily: fontFamilySystem, // Roboto
- cssOverflowWrap: cssBreakWord,
- }
-}
-
-// H3 Styles
-// Returns inline styles for H3 headings that match .md-typeset h3.
-// EXTRACTED FROM: .md-typeset h3 CSS rule from Material for MkDocs.
-//
-//nolint:unused // Used across templates for subsection headings.
-func h3Styles() styles.Props {
- return styles.Props{
- styles.FontSize: fontSizeH3, // 1.25em
- styles.LineHeight: lineHeightH3, // 1.5
- styles.Margin: "1.6em 0 0.8em",
- styles.FontWeight: "400",
- "letter-spacing": "-0.01em",
- styles.Color: colorTextSecondary, // rgba(0, 0, 0, 0.54)
- styles.FontFamily: fontFamilySystem, // Roboto
- cssOverflowWrap: cssBreakWord,
- }
-}
-
-// Paragraph Styles
-// Returns inline styles for paragraphs that match .md-typeset p.
-// EXTRACTED FROM: .md-typeset p CSS rule from Material for MkDocs.
-//
-//nolint:unused // Used for consistent paragraph spacing.
-func paragraphStyles() styles.Props {
- return styles.Props{
- styles.Margin: "1em 0",
- styles.FontFamily: fontFamilySystem, // Roboto
- styles.FontSize: fontSizeBase, // 0.8rem - inherited from .md-typeset
- styles.LineHeight: lineHeightBase, // 1.6 - inherited from .md-typeset
- styles.Color: colorTextPrimary, // rgba(0, 0, 0, 0.87)
- cssOverflowWrap: cssBreakWord,
- }
-}
-
-// Ordered List Styles
-// Returns inline styles for ordered lists that match .md-typeset ol.
-// EXTRACTED FROM: .md-typeset ol CSS rule from Material for MkDocs.
-//
-//nolint:unused // Used for numbered instruction lists.
-func orderedListStyles() styles.Props {
- return styles.Props{
- styles.MarginBottom: "1em",
- styles.MarginTop: "1em",
- styles.PaddingLeft: "2em",
- styles.FontFamily: fontFamilySystem, // Roboto - inherited from .md-typeset
- styles.FontSize: fontSizeBase, // 0.8rem - inherited from .md-typeset
- styles.LineHeight: lineHeightBase, // 1.6 - inherited from .md-typeset
- styles.Color: colorTextPrimary, // rgba(0, 0, 0, 0.87) - inherited from .md-typeset
- cssOverflowWrap: cssBreakWord,
- }
-}
-
-// Unordered List Styles
-// Returns inline styles for unordered lists that match .md-typeset ul.
-// EXTRACTED FROM: .md-typeset ul CSS rule from Material for MkDocs.
-//
-//nolint:unused // Used for bullet point lists.
-func unorderedListStyles() styles.Props {
- return styles.Props{
- styles.MarginBottom: "1em",
- styles.MarginTop: "1em",
- styles.PaddingLeft: "2em",
- styles.FontFamily: fontFamilySystem, // Roboto - inherited from .md-typeset
- styles.FontSize: fontSizeBase, // 0.8rem - inherited from .md-typeset
- styles.LineHeight: lineHeightBase, // 1.6 - inherited from .md-typeset
- styles.Color: colorTextPrimary, // rgba(0, 0, 0, 0.87) - inherited from .md-typeset
- cssOverflowWrap: cssBreakWord,
- }
-}
-
-// Link Styles
-// Returns inline styles for links that match .md-typeset a.
-// EXTRACTED FROM: .md-typeset a CSS rule from Material for MkDocs.
-// Note: Hover states cannot be implemented with inline styles.
-//
-//nolint:unused // Used for text links.
-func linkStyles() styles.Props {
- return styles.Props{
- styles.Color: colorPrimaryAccent, // #4051b5 - var(--md-primary-fg-color)
- styles.TextDecoration: "none",
- "word-break": cssBreakWord,
- styles.FontFamily: fontFamilySystem, // Roboto - inherited from .md-typeset
- }
-}
-
-// Inline Code Styles (updated)
-// Returns inline styles for inline code that matches .md-typeset code.
-// EXTRACTED FROM: .md-typeset code CSS rule from Material for MkDocs.
-//
-//nolint:unused // Used for inline code snippets.
-func inlineCodeStyles() styles.Props {
- return styles.Props{
- styles.BackgroundColor: colorCodeBg, // #f5f5f5
- styles.Color: colorCodeFg, // #36464e
- styles.BorderRadius: "0.1rem",
- styles.FontSize: fontSizeCode, // 0.85em
- styles.FontFamily: fontFamilyCode, // Roboto Mono
- styles.Padding: "0 0.2941176471em",
- "word-break": cssBreakWord,
- }
-}
-
-// Inline Code Component
-// For inline code snippets within text.
-//
-//nolint:unused // Reserved for future inline code usage.
-func inlineCode(code string) *elem.Element {
- return elem.Code(attrs.Props{
- attrs.Style: inlineCodeStyles().ToInline(),
- }, elem.Text(code))
-}
-
// orDivider creates a visual "or" divider between sections.
// Styled with lines on either side for better visual separation.
//
@@ -343,16 +53,17 @@ func orDivider() *elem.Element {
styles.BackgroundColor: "var(--hs-border)",
}.ToInline()
- return elem.Div(attrs.Props{
- attrs.Style: styles.Props{
- styles.Display: "flex",
- styles.AlignItems: cssCenter,
- styles.Gap: spaceM,
- styles.MarginTop: space2XL,
- styles.MarginBottom: space2XL,
- styles.Width: "100%",
- }.ToInline(),
- },
+ return elem.Div(
+ attrs.Props{
+ attrs.Style: styles.Props{
+ styles.Display: "flex",
+ styles.AlignItems: cssCenter,
+ styles.Gap: spaceM,
+ styles.MarginTop: space2XL,
+ styles.MarginBottom: space2XL,
+ styles.Width: "100%",
+ }.ToInline(),
+ },
elem.Div(attrs.Props{attrs.Style: lineStyle}),
elem.Strong(attrs.Props{
attrs.Style: styles.Props{
@@ -389,7 +100,8 @@ func successBox(heading string, children ...elem.Node) *elem.Element {
"aria-live": "polite",
},
checkboxIcon(),
- elem.Div(nil,
+ elem.Div(
+ nil,
append([]elem.Node{
elem.Strong(attrs.Props{
attrs.Style: styles.Props{
@@ -434,7 +146,8 @@ func errorBox(heading string, children ...elem.Node) *elem.Element {
"aria-live": "assertive",
},
errorIcon(),
- elem.Div(nil,
+ elem.Div(
+ nil,
append([]elem.Node{
elem.Strong(attrs.Props{
attrs.Style: styles.Props{
@@ -462,22 +175,24 @@ func errorIcon() elem.Node {
//
//nolint:unused // Used in apple.go template.
func warningBox(title, message string) *elem.Element {
- return elem.Div(attrs.Props{
- attrs.Style: styles.Props{
- styles.Display: "flex",
- styles.AlignItems: "flex-start",
- styles.Gap: spaceM,
- styles.Padding: spaceL,
- styles.BackgroundColor: "var(--hs-warning-bg)",
- styles.Border: "1px solid var(--hs-warning-border)",
- styles.BorderRadius: spaceS,
- styles.MarginTop: spaceL,
- styles.MarginBottom: spaceL,
- }.ToInline(),
- attrs.Role: "note",
- },
+ return elem.Div(
+ attrs.Props{
+ attrs.Style: styles.Props{
+ styles.Display: "flex",
+ styles.AlignItems: "flex-start",
+ styles.Gap: spaceM,
+ styles.Padding: spaceL,
+ styles.BackgroundColor: "var(--hs-warning-bg)",
+ styles.Border: "1px solid var(--hs-warning-border)",
+ styles.BorderRadius: spaceS,
+ styles.MarginTop: spaceL,
+ styles.MarginBottom: spaceL,
+ }.ToInline(),
+ attrs.Role: "note",
+ },
elem.Raw(``),
- elem.Div(nil,
+ elem.Div(
+ nil,
elem.Strong(attrs.Props{
attrs.Style: styles.Props{
styles.Display: "block",
@@ -528,35 +243,23 @@ func externalLink(href, text string) *elem.Element {
}, elem.Text(text))
}
-// Instruction Step Component
-// For numbered instruction lists with consistent formatting.
-//
-//nolint:unused // Reserved for future use in Phase 4.
-func instructionStep(_ int, text string) *elem.Element {
- return elem.Li(attrs.Props{
- attrs.Style: styles.Props{
- styles.MarginBottom: spaceS,
- styles.LineHeight: lineHeightBase,
- }.ToInline(),
- }, elem.Text(text))
-}
-
// detailsBox creates a collapsible / section.
// Styled to match the card/box component family (border, radius, CSS variables).
// Collapsed by default; the user clicks the summary to expand.
//
//nolint:unused // Used in ping.go template.
func detailsBox(summary string, children ...elem.Node) *elem.Element {
- return elem.Details(attrs.Props{
- attrs.Style: styles.Props{
- styles.Background: "var(--hs-bg)",
- styles.Border: cssBorderHS,
- styles.BorderRadius: spaceS,
- styles.Padding: spaceS + " " + spaceM,
- styles.MarginTop: spaceL,
- styles.MarginBottom: spaceL,
- }.ToInline(),
- },
+ return elem.Details(
+ attrs.Props{
+ attrs.Style: styles.Props{
+ styles.Background: "var(--hs-bg)",
+ styles.Border: cssBorderHS,
+ styles.BorderRadius: spaceS,
+ styles.Padding: spaceS + " " + spaceM,
+ styles.MarginTop: spaceL,
+ styles.MarginBottom: spaceL,
+ }.ToInline(),
+ },
elem.Summary(attrs.Props{
attrs.Style: styles.Props{
"cursor": "pointer",
@@ -573,30 +276,3 @@ func detailsBox(summary string, children ...elem.Node) *elem.Element {
}, children...),
)
}
-
-// Status Message Component
-// For displaying success/error/info messages with appropriate styling.
-//
-//nolint:unused // Reserved for future use in Phase 4.
-func statusMessage(message string, isSuccess bool) *elem.Element {
- bgColor := colorSuccessLight
- textColor := colorSuccess
-
- if !isSuccess {
- bgColor = colorErrorLight
- textColor = colorError
- }
-
- return elem.Div(attrs.Props{
- attrs.Style: styles.Props{
- styles.Padding: spaceM,
- styles.BackgroundColor: bgColor,
- styles.Color: textColor,
- styles.BorderRadius: spaceS,
- styles.Border: "1px solid " + textColor,
- styles.MarginBottom: spaceL,
- styles.FontSize: fontSizeBase,
- styles.LineHeight: lineHeightBase,
- }.ToInline(),
- }, elem.Text(message))
-}
diff --git a/hscontrol/templates/general.go b/hscontrol/templates/general.go
index a9e1ac6e..d1949ac7 100644
--- a/hscontrol/templates/general.go
+++ b/hscontrol/templates/general.go
@@ -86,35 +86,6 @@ func PreCode(code string) *elem.Element {
return elem.Code(nil, elem.Text(code))
}
-// Deprecated: use [H1], [H2], [H3] instead
-func headerOne(text string) *elem.Element {
- return H1(elem.Text(text))
-}
-
-// Deprecated: use [H1], [H2], [H3] instead
-func headerTwo(text string) *elem.Element {
- return H2(elem.Text(text))
-}
-
-// Deprecated: use [H1], [H2], [H3] instead
-func headerThree(text string) *elem.Element {
- return H3(elem.Text(text))
-}
-
-// contentContainer wraps page content with proper width.
-// Content inside is left-aligned by default.
-func contentContainer(children ...elem.Node) *elem.Element {
- containerStyle := styles.Props{
- styles.MaxWidth: "720px",
- styles.Width: "100%",
- styles.Display: "flex",
- styles.FlexDirection: "column",
- styles.AlignItems: "flex-start", // Left-align all children
- }
-
- return elem.Div(attrs.Props{attrs.Style: containerStyle.ToInline()}, children...)
-}
-
// headscaleLogo returns the Headscale SVG logo for consistent branding across all pages.
// The logo is styled by the .headscale-logo CSS class.
func headscaleLogo() elem.Node {
@@ -142,15 +113,6 @@ func pageFooter() *elem.Element {
)
}
-// listStyle provides consistent styling for ordered and unordered lists
-// EXTRACTED FROM: .md-typeset ol, .md-typeset ul CSS rules
-var listStyle = styles.Props{
- styles.LineHeight: lineHeightBase, // 1.6 - From .md-typeset
- styles.MarginTop: "1em", // From CSS: margin-top: 1em
- styles.MarginBottom: "1em", // From CSS: margin-bottom: 1em
- styles.PaddingLeft: "clamp(1.5rem, 5vw, 2.5rem)", // Responsive indentation
-}
-
// HtmlStructure creates a complete HTML document structure with proper meta tags
// and semantic HTML5 structure. The head and body elements are passed as parameters
// to allow for customization of each page.