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.