diff --git a/nix/hosts/misc/rss.nix b/nix/hosts/misc/rss.nix index 3f15345..55b41cf 100644 --- a/nix/hosts/misc/rss.nix +++ b/nix/hosts/misc/rss.nix @@ -6,20 +6,52 @@ }: let domain = "rss.${lib.our.domains.priv}"; + cfg = config.services.tinyfeed; in { - services.tinyfeed = { enable = true; name = "Ariadne's RSS feed"; description = "Things i like"; - files = [ - { - out = "index.html"; - } - ]; + files = + let + template = "${../../../web/tinyfeed/index.html.gtpl}"; + stylesheet = "${../../../web/tinyfeed/css.css}"; + in + [ + { + name = "Ariadne's blogroll"; + description = '' + Things I find interesting, from a variety of domains. + ''; + out = "index.html"; + feedsPath = "${cfg.outputPath}/feeds.txt"; + inherit template stylesheet; + limit = 100; + } + { + name = "Ariadne's tech feed"; + description = '' + Tech-focused blogroll. + ''; + out = "tech.html"; + feedsPath = "${cfg.outputPath}/tech.txt"; + inherit template stylesheet; + limit = 100; + } + { + name = "Ariadne's video feed"; + description = '' + Videos from people I find interesting. Mostly video essays. + ''; + out = "videos.html"; + feedsPath = "${cfg.outputPath}/videos.txt"; + inherit template stylesheet; + limit = 20; + } + ]; }; services.nginx = { diff --git a/nix/modules/services/tinyfeed.nix b/nix/modules/services/tinyfeed.nix index 7311d87..c3a6f79 100644 --- a/nix/modules/services/tinyfeed.nix +++ b/nix/modules/services/tinyfeed.nix @@ -125,9 +125,9 @@ in concatStringsSep " " [ "tinyfeed " "-n" - (escapeShellArg cfg.name) + (escapeShellArg file.name) "-d" - (escapeShellArg cfg.description) + (escapeShellArg file.description) "-t" (escapeShellArg file.template) "-l" @@ -136,8 +136,8 @@ in (escapeShellArg file.feedsPath) "--output" (escapeShellArg file.out) - (optionalString (file.script != null) "-s ${toString i}.js") - (optionalString (file.stylesheet != null) "-S ${toString i}.css") + (optionalString (file.script != null) "-S ${toString i}.js") + (optionalString (file.stylesheet != null) "-s ${toString i}.css") ] ) cfg.files ); @@ -163,17 +163,14 @@ in systemd.tmpfiles.rules = [ "d ${cfg.outputPath} 0755 ${cfg.user} -" "f ${cfg.feedsPath} 0644 ${cfg.user} -" - "l ${cfg.outputPath}/ 0644 ${cfg.user} -" ] ++ (concatLists ( imap ( i: file: - (optional ( - file.script != null - ) "l ${cfg.outputPath}/${toString i}.js 0644 ${cfg.user} - ${file.script}") + (optional (file.script != null) "L+ ${cfg.outputPath}/${toString i}.js - - - - ${file.script}") ++ (optional ( file.stylesheet != null - ) "l ${cfg.outputPath}/${toString i}.css 0644 ${cfg.user} - ${file.stylesheet}") + ) "L ${cfg.outputPath}/${toString i}.css - - - - ${file.stylesheet}") ) cfg.files )); users.users.${cfg.user} = { }; diff --git a/nix/pkgs/tinyfeed-template-directory-fix.patch b/nix/pkgs/tinyfeed-template-directory-fix.patch new file mode 100644 index 0000000..85b42b8 --- /dev/null +++ b/nix/pkgs/tinyfeed-template-directory-fix.patch @@ -0,0 +1,21 @@ +diff --git a/internal/main.go b/internal/main.go +index e1bedcd..8c8defe 100644 +--- a/internal/main.go ++++ b/internal/main.go +@@ -3,6 +3,7 @@ package tinyfeed + import ( + _ "embed" + "flag" ++ "path/filepath" + "fmt" + "html" + "html/template" +@@ -218,7 +219,7 @@ func printHTML(feeds []*gofeed.Feed, items []Item) error { + Funcs(templateFuncs). + Parse(opmlTemplate) + default: +- ts, err = template.New(templatePath). ++ ts, err = template.New(filepath.Base(templatePath)). + Funcs(templateFuncs). + ParseFiles(templatePath) + } diff --git a/nix/pkgs/tinyfeed.nix b/nix/pkgs/tinyfeed.nix index ad91f3a..e36a58b 100644 --- a/nix/pkgs/tinyfeed.nix +++ b/nix/pkgs/tinyfeed.nix @@ -14,5 +14,7 @@ buildGoModule (finalAttrs: { hash = "sha256-qeHg7beGdTf0EaUAShyki8t7c7VG0iG9e0WX2Wc50RQ="; }; + patches = [ ./tinyfeed-template-directory-fix.patch ]; + vendorHash = "sha256-lwT2eD+zFI49wvdlLcZILa2X/NsEi/jDzshngH3CLyQ="; }) diff --git a/web/tinyfeed/.gitignore b/web/tinyfeed/.gitignore new file mode 100644 index 0000000..2389fd7 --- /dev/null +++ b/web/tinyfeed/.gitignore @@ -0,0 +1,3 @@ +index.html +result +.envrc diff --git a/web/tinyfeed/.project.el b/web/tinyfeed/.project.el new file mode 100644 index 0000000..e69de29 diff --git a/web/tinyfeed/css.css b/web/tinyfeed/css.css new file mode 100644 index 0000000..f0904e0 --- /dev/null +++ b/web/tinyfeed/css.css @@ -0,0 +1,1060 @@ +ol.items-list { + list-style-type: none; + padding: 0; + > :not(:last-child) { + /* border-bottom: 1px solid var(--colour-border); */ + margin-bottom: var(--spacing-6); + } +} + +.items-list li h3 { + margin-bottom: var(--spacing-2); +} + +p.secondary { + color: var(--colour-text-secondary); + font-size: var(--font-size-12); + line-height: var(--line-height-12); + margin: 0; +} + +/* GIC.css Version 1.4.0 + * (c) Copyright Ariadne (me@aria.rip) 2026. + * Made available under the MIT license */ +:root { + --font-variable: "Inter", sans-serif; + --font-weight-normal: 400; + --font-weight-bold: 700; + + /* Colour palette */ + --blue: #1d70b8; + --blue-tint-25: #5694ca; + --blue-tint-50: #8eb8dc; + --blue-tint-80: #d2e2f1; + --blue-tint-95: #f4f8fb; + --blue-shade-25: #16548a; + --blue-shade-50: #0f385c; + --blue-shade-10: #1a65a6;; + --blue-shade-95: #030B12; + --green: #0f7a52; + --green-tint-25: #4b9b7d; + --green-tint-50: #87bca8; + --green-tint-80: #cfe4dc; + --green-tint-95: #f3f8f6; + --green-shade-25: #0b5c3e; + --green-shade-50: #083d29; + --teal: #158187; + --teal-tint-25: #50a1a5; + --teal-tint-50: #8ac0c3; + --teal-tint-80: #d0e6e7; + --teal-tint-95: #f3f9f9; + --teal-shade-25: #106165; + --teal-shade-50: #0b4144; + --teal-accent: #00ffe0; + --purple: #54319f; + --purple-tint-25: #7f65b7; + --purple-tint-50: #aa98cf; + --purple-tint-80: #ddd6ec; + --purple-tint-95: #f6f5fa; + --purple-shade-25: #3f2577; + --purple-shade-50: #2a1950; + --magenta: #ca357c; + --magenta-tint-25: #d7689d; + --magenta-tint-50: #e59abe; + --magenta-tint-80: #f4d7e5; + --magenta-tint-95: #fcf5f8; + --magenta-shade-25: #98285d; + --magenta-shade-50: #651b3e; + --red: #ca3535; + --red-tint-25: #d76868; + --red-tint-50: #e59a9a; + --red-tint-80: #f4d7d7; + --red-tint-95: #fcf5f5; + --red-shade-25: #982828; + --red-shade-50: #651b1b; + --orange: #f47738; + --orange-tint-25: #f7996a; + --orange-tint-50: #fabb9c; + --orange-tint-80: #fde4d7; + --orange-tint-95: #fef8f5; + --orange-shade-25: #b7592a; + --orange-shade-50: #7a3c1c; + --yellow: #ffdd00; + --yellow-tint-25: #ffe640; + --yellow-tint-50: #ffee80; + --yellow-tint-80: #fff8cc; + --yellow-tint-95: #fffdf2; + --yellow-shade-25: #bfa600; + --yellow-shade-50: #806f00; + --brown: #99704a; + --brown-tint-25: #b39477; + --brown-tint-50: #ccb8a5; + --brown-tint-95: #faf8f6; + --black: #0b0c0c; + --black-tint-25: #484949; + --black-tint-50: #858686; + --black-tint-80: #cecece; + --black-tint-95: #f3f3f3; + --white: #ffffff; + + --colour-brand: var(--red); + --colour-brand-text: var(--white); + + --colour-text: var(--black); + --colour-body-background: var(--white); + + /* Used in for example 'muted' text and help text. */ + --colour-text-secondary: var(--black-tint-25); + + --colour-focus: var(--yellow); + --colour-focus-text: var(--black); + + --colour-error: var(--red); + --colour-success: var(--green); + + /* Used in for example borders, separators, rules and keylines. */ + --colour-border: var(--black-tint-80); + /* Used for form inputs and controls */ + --colour-input-border: var(--black); + /* Used for hover states on form controls */ + --colour-hover: var(--black-tint-80); + /* Standard links (on white) */ + --colour-link: var(--blue-shade-10); + --colour-link-visited: var(--purple); + --colour-link-hover: var(--blue-shade-50); + --colour-link-active: var(--black); + + /* 'Surfaces' are our name for components that have different colour */ + /* palettes to typical page content. This is the generic surface. */ + --colour-surface-background: var(--blue-tint-95); + --colour-surface-text: var(--black); + --colour-surface-board: var(--blue-tint-50); + --colour-button-text: var(--white); + + @media (prefers-color-scheme: dark) { + color-scheme: dark; + + /* Dark theme, from catppuccin mocha */ + --blue: #89b4fa; + --blue-base: #89b4fa; + --blue-tint-25: #6787bc; + --blue-tint-50: #455a7d; + --blue-tint-80: #1b2432; + --blue-tint-95: #07090d; + --blue-shade-10: #95bcfb; + --blue-shade-25: #a7c7fb; + --blue-shade-50: #c4dafd; + --blue-shade-95: #f9fbff; + --green: #a6e3a1; + --green-tint-25: #7daa79; + --green-tint-50: #537251; + --green-tint-80: #212d20; + --green-tint-95: #080b08; + --green-shade-25: #bceab9; + --green-shade-50: #d3f1d0; + --teal: #94e2d5; + --teal-tint-25: #6faaa0; + --teal-tint-50: #4a716b; + --teal-tint-80: #1e2d2b; + --teal-tint-95: #070b0b; + --teal-shade-25: #afe9e0; + --teal-shade-50: #caf1ea; + --purple: #cba6f7; + --purple-tint-25: #987db9; + --purple-tint-50: #66537c; + --purple-tint-80: #292131; + --purple-tint-95: #0a080c; + --purple-shade-25: #d8bcf9; + --purple-shade-50: #e5d3fb; + --pink: #f5c2e7; + --pink-tint-25: #b892ad; + --pink-tint-50: #7b6174; + --pink-tint-80: #31272e; + --pink-tint-95: #0c0a0c; + --pink-shade-25: #f8d1ed; + --pink-shade-50: #fae1f3; + --red: #f38ba8; + --red-tint-25: #b6687e; + --red-tint-50: #7a4654; + --red-tint-80: #311c22; + --red-tint-95: #0c0708; + --red-shade-25: #f6a8be; + --red-shade-50: #f9c5d4; + --orange: #fab387; + --orange-tint-25: #bc8665; + --orange-tint-50: #7d5a44; + --orange-tint-80: #32241b; + --orange-tint-95: #0d0907; + --orange-shade-25: #fbc6a5; + --orange-shade-50: #fdd9c3; + --yellow: #f9e2af; + --yellow-tint-25: #bbaa83; + --yellow-tint-50: #7d7158; + --yellow-tint-80: #322d23; + --yellow-tint-95: #0c0b09; + --yellow-shade-25: #fbe9c3; + --yellow-shade-50: #fcf1d7; + --brown: #f2cdcd; + --brown-tint-25: #b69a9a; + --brown-tint-50: #796767; + --brown-tint-95: #0c0a0a; + --black: #1e1e2e; + --black-tint-25: #171723; + --black-tint-50: #0f0f17; + --black-tint-80: #060609; + --black-tint-95: #020202; + --white: #cdd6f4; + --white-shade-25: #9aa1b7; + + /* Alternative Dark theme, from modus operandi (Vivendi) */ + /* A bit higher contrast for those who like that */ + /* --blue: #2fafff; */ + /* --blue-tint-25: #2383bf; */ + /* --blue-tint-50: #185880; */ + /* --blue-tint-80: #092333; */ + /* --blue-tint-95: #02090d; */ + /* --blue-shade-10: #44b7ff; */ + /* --blue-shade-25: #63c3ff; */ + /* --blue-shade-50: #97d7ff; */ + /* --blue-shade-95: #f5fbff; */ + /* --green: #44bc44; */ + /* --green-tint-25: #338d33; */ + /* --green-tint-50: #225e22; */ + /* --green-tint-80: #0e260e; */ + /* --green-tint-95: #030903; */ + /* --green-shade-25: #73cd73; */ + /* --green-shade-50: #a2dea2; */ + /* --teal: #00d3d0; */ + /* --teal-tint-25: #009e9c; */ + /* --teal-tint-50: #006a68; */ + /* --teal-tint-80: #002a2a; */ + /* --teal-tint-95: #000b0a; */ + /* --teal-shade-25: #40dedc; */ + /* --teal-shade-50: #80e9e8; */ + /* --purple: #9099d9; */ + /* --purple-tint-25: #6c73a3; */ + /* --purple-tint-50: #484d6d; */ + /* --purple-tint-80: #1d1f2b; */ + /* --purple-tint-95: #07080b; */ + /* --purple-shade-25: #acb3e3; */ + /* --purple-shade-50: #c8ccec; */ + /* --pink: #feacd0; */ + /* --pink-tint-25: #bf819c; */ + /* --pink-tint-50: #7f5668; */ + /* --pink-tint-80: #33222a; */ + /* --pink-tint-95: #0d090a; */ + /* --pink-shade-25: #fec1dc; */ + /* --pink-shade-50: #ffd6e8; */ + /* --red: #ff5f59; */ + /* --red-tint-25: #bf4743; */ + /* --red-tint-50: #80302d; */ + /* --red-tint-80: #331312; */ + /* --red-tint-95: #0d0504; */ + /* --red-shade-25: #ff8783; */ + /* --red-shade-50: #ffafac; */ + /* --orange: #db7b5f; */ + /* --orange-tint-25: #a45c47; */ + /* --orange-tint-50: #6e3e30; */ + /* --orange-tint-80: #2c1913; */ + /* --orange-tint-95: #0b0605; */ + /* --orange-shade-25: #e49c87; */ + /* --orange-shade-50: #edbdaf; */ + /* --yellow: #d0bc00; */ + /* --yellow-tint-25: #9c8d00; */ + /* --yellow-tint-50: #685e00; */ + /* --yellow-tint-80: #2a2600; */ + /* --yellow-tint-95: #0a0900; */ + /* --yellow-shade-25: #dccd40; */ + /* --yellow-shade-50: #e8de80; */ + /* --brown: #f2cdcd; */ + /* --brown-tint-25: #b69a9a; */ + /* --brown-tint-50: #796767; */ + /* --brown-tint-95: #0c0a0a; */ + /* --black: #000000; */ + /* --black-tint-25: #404040; */ + /* --black-tint-50: #808080; */ + /* --black-tint-80: #cccccc; */ + /* --black-tint-95: #f2f2f2; */ + /* --white: #ffffff; */ + /* --white-shade-250: #bfbfbf; */ + + --colour-text: var(--white); + --colour-body-background: var(--black); + --colour-text-secondary: var(--white-shade-25); + --colour-border: var(--white); + --colour-input-border: var(--white); + --colour-focus-text: var(--white); + --colour-link-active: var(--white); + --colour-surface-background: var(--blue-tint-80); + --colour-surface-text: #fff; + --colour-surface-board: var(--blue-tint-50); + --colour-button-text: var(--black); + --colour-brand-text: var(--black); + } + + + --width-input-border: 2px; + --width-form-group-error: 5px; + --width-focus: 3px; + + --font-size-80: 53px; + --line-height-80: 55px; + --font-size-48: 32px; + --line-height-48: 35px; + --font-size-36: 27px; + --line-height-36: 30px; + --font-size-27: 21px; + --line-height-27: 25px; + --font-size-24: 21px; + --line-height-24: 25px; + --font-size-19: 19px; + --line-height-19: 25px; + --font-size-16: 16px; + --line-height-16: 20px; + + --spacing-0: 0; + --spacing-1: 5px; + --spacing-2: 10px; + --spacing-3: 15px; + --spacing-4: 15px; + --spacing-5: 15px; + --spacing-6: 20px; + --spacing-7: 25px; + --spacing-8: 30px; + --spacing-9: 40px; + + @media screen and (min-width: 641px) { /* tablet */ + --font-size-80: 80px; + --line-height-80: 80px; + --font-size-48: 48px; + --line-height-48: 50px; + --font-size-36: 36px; + --line-height-36: 40px; + --font-size-27: 27px; + --line-height-27: 30px; + --font-size-24: 24px; + --line-height-24: 30px; + --spacing-4: 20px; + --spacing-5: 25px; + --spacing-6: 30px; + --spacing-7: 40px; + --spacing-8: 50px; + --spacing-9: 60px; + } + + @media print { + --colour-text: #000000; + --font-size-80: 53pt; + --line-height-80: 1.1; + --font-size-48: 32pt; + --line-height-48: 1.15; + --font-size-36: 24pt; + --line-height-36: 1.05; + --font-size-27: 18pt; + --line-height-27: 1.15; + --font-size-24: 18pt; + --line-height-24: 1.15; + --font-size-19: 14pt; + --line-height-19: 1.15; + --font-size-16: 14pt; + --line-height-16: 1.2; + } +} + + +/* eeeee e e eeeee eeeee eeeee eeeee eeeee eeeee e e e e */ +/* 8 8 8 8 8 8 88 8 8 8 8 8 8 8 8 8 8 8 8 */ +/* 8e 8eeee8 8eee8 8 8 8e 8eee8e 8eee8 8eee8 8eee8 8eeee8 */ +/* 88 88 88 8 8 88 "8 88 8 88 8 88 88 8 88 */ +/* 88 88 88 8eee8 88ee8 88 8 88 8 88 88 8 88 */ +html { background: var(--colour-body-background); } +html:has(footer) { background: var(--colour-surface-background); } + +body { + font-family: var(--font-variable); + font-weight: var(--font-weight-normal); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + color: var(--colour-text); + background: var(--colour-body-background); + + margin: 0; + + /* Prevent automatic text sizing, as we already cater for small devices and */ + /* would like the browser to stay on 100% text zoom by default. */ + text-size-adjust: 100%; +} + +h1,h2,h3,h4 { font-weight: var(--font-weight-bold); margin-top: 0; } +span.caption { color: var(--colour-text-secondary); display: block; } + +p { + margin-top: 0; +} + +/* sizing stuff. */ +h1 { font-size: var(--font-size-48); line-height: var(--line-height-48); margin-bottom: var(--spacing-8); } +h2 { font-size: var(--font-size-36); line-height: var(--line-height-36); margin-bottom: var(--spacing-6); } +h3 { font-size: var(--font-size-24); line-height: var(--line-height-24); margin-bottom: var(--spacing-4); } +h4 { font-size: var(--font-size-19); line-height: var(--line-height-19); margin-bottom: var(--spacing-4); } +p { font-size: var(--font-size-19); line-height: var(--line-height-19); margin-bottom: var(--spacing-4); } +p.large { font-size: var(--font-size-24); line-height: var(--line-height-24); margin-bottom: var(--spacing-6); } +p.small { font-size: var(--font-size-16); line-height: var(--line-height-16); margin-bottom: var(--spacing-4); } + +span.caption:has(+ h1) { font-size: var(--font-size-27); line-height: var(--line-height-27); margin-bottom: var(--spacing-1); } +span.caption:has(+ h2) { font-size: var(--font-size-24); line-height: var(--line-height-24); margin-bottom: var(--spacing-1); } +span.caption:has(+ h3) { font-size: var(--font-size-19); line-height: var(--line-height-19); } + +/* Add top padding to headings that appear directly after paragraphs. */ +p.large + h2 { padding-top: var(--spacing-1); } + +p + h2, +ul + h2, +ol + h2 { padding-top: var(--spacing-4); } + +p + h3, +ul + h3, +ol + h3, +p + h4, +ul + h4, +ol + h4 { padding-top: var(--spacing-1); } + +a { + text-decoration: underline; + text-decoration-thickness: max(1px, .0625rem); + text-decoration-offset: 0.1578em; + color: var(--colour-link); + + &:not(.no-visited):visited { + color: var(--colour-link-visited); + } + + &:hover { + color: var(--colour-link-hover); + text-decoration-thickness: max(3px, .1875rem, .12em); + text-decoration-skip-ink: none; + text-decoration-skip: none; + } + + &:active { + color: var(--colour-link-active); + } + + /* When focussed, the text colour needs to be darker to ensure that colour */ + /* contrast is still acceptable */ + &:focus { + color: var(--colour-focus-text); + } + + /* Show URL when printing */ + @media print { + &[href^="/"], + &[href^="http://"], + &[href^="https://"] { + &::after { + content: " (" attr(href) ")"; + font-size: 90%; + word-wrap: break-word; + } + } + } + + &.error { + &, + &:visited { + color: var(--colour-error); + } + + &:hover { + color: var(--red-shade-50); + } + + &:active { + color: var(--colour-error); + } + + &:focus { + color: var(--colour-focus-text); + } + } + + &.success { + &, + &:visited { + color: var(--colour-success); + } + + &:hover { + color: var(--green-shade-50); + } + + &:active { + color: var(--colour-success); + } + + &:focus { + color: var(--colour-focus-text); + } + } + + + &.no-underline:not(:hover):not(:active) { + text-decoration: none; + } +} + + +ul, ol { + font-size: var(--font-size-19); + line-spacing: var(--line-spacing-19); + + margin-top: 0; + margin-bottom: var(--spacing-4); + + color: var(--colour-text); + + padding-left: var(--spacing-4); + /* list-style-type: disc; */ + &.no-bullet { + padding-left: 0; + list-style-type: none; + > li { + margin-bottom: var(--spacing-1); + } + } + + /* Add a top margin for nested lists */ + ul, ol { + margin-top: var(--spacing-2); + } + + + /* Lists without numbers or bullets should always have extra space between */ + /* list items. Lists with numbers or bullets only have this extra space on */ + /* tablet and above */ + > li { + margin-bottom: 0; + @media screen and (min-width: 641px) { /* tablet */ + margin-bottom: var(--spacing-1); + } + } + + &.extra-space > li { + margin-bottom: var(--spacing-2); + @media screen and (min-width: 641px) { /* tablet */ + margin-bottom: var(--spacing-3); + } + } +} + + +/* section spacer */ +hr { margin: var(--spacing-4) 0; border: 0; } +hr.xl { margin: var(--spacing-8) 0; } +hr.l { margin: var(--spacing-6) 0; } +hr.visible { border-bottom: 1px solid var(--colour-border) } + +/* utilities */ +.break-word { word-break: break-word; } + +/* oo dP */ +/* 88 */ +/* .d8888b. 88d888b. dP .d888b88 .d8888b. */ +/* 88' `88 88' `88 88 88' `88 Y8ooooo. */ +/* 88. .88 88 88 88. .88 88 */ +/* `8888P88 dP dP `88888P8 `88888P' */ +/* .88 */ +/* d8888P */ +main { + padding: var(--spacing-6) 0; +} + +grid-container { + --size-gutter: 30px; + --size-gutter-half: calc(var(--size-gutter) / 2); + --size-page-width: 960px; + + max-width: var(--size-page-width); + + margin-left: var(--size-gutter-half); + margin-right: var(--size-gutter-half); + + /* On tablet, add full width gutters */ + @media screen and (min-width: 641px) { /* tablet */ + margin-right: var(--size-gutter); + margin-left: var(--size-gutter); + } + + /* As soon as the viewport is greater than the width of the page plus the */ + /* gutters, just centre the content instead of adding gutters. */ + @media (min-width: 1020px) { + margin-right: auto; + margin-left: auto; + } + + display: grid; + grid-template-columns: repeat(12, 1fr); + grid-gap: var(--size-gutter); +} + + +/* By default, column spans only apply past the tablet breakpoint */ +.whole-row { grid-column-start: span 12; } +.one-third { grid-column-start: span 12; } +.two-thirds { grid-column-start: span 12; } +.one-half { grid-column-start: span 12; } +.one-quarter { grid-column-start: span 12; } +.two-quarters { grid-column-start: span 12; } +.three-quarters { grid-column-start: span 12; } + +/* Force applying the whole time */ +.mobile-whole-row { grid-column-start: span 12; } +.mobile-one-third { grid-column-start: span 4; } +.mobile-two-thirds { grid-column-start: span 8; } +.mobile-one-half { grid-column-start: span 6; } +.mobile-one-quarter { grid-column-start: span 3; } +.mobile-two-quarters { grid-column-start: span 6; } +.mobile-three-quarters { grid-column-start: span 9; } + +@media screen and (min-width: 641px) { /* tablet */ + .whole-row { grid-column-start: span 12; } + .one-third { grid-column-start: span 4; } + .two-thirds { grid-column-start: span 8; } + .one-half { grid-column-start: span 6; } + .one-quarter { grid-column-start: span 3; } + .two-quarters { grid-column-start: span 6; } + .three-quarters { grid-column-start: span 9; } + + .tablet-whole-row { grid-column-start: span 12; } + .tablet-one-third { grid-column-start: span 4; } + .tablet-two-thirds { grid-column-start: span 8; } + .tablet-one-half { grid-column-start: span 6; } + .tablet-one-quarter { grid-column-start: span 3; } + .tablet-two-quarters { grid-column-start: span 6; } + .tablet-three-quarters { grid-column-start: span 9; } +} + +@media screen and (min-width: 769px) { /* desktop */ + .desktop-whole-row { grid-column-start: span 12; } + .desktop-one-third { grid-column-start: span 4; } + .desktop-two-thirds { grid-column-start: span 8; } + .desktop-one-half { grid-column-start: span 6; } + .desktop-one-quarter { grid-column-start: span 3; } + .desktop-two-quarters { grid-column-start: span 6; } + .desktop-three-quarters { grid-column-start: span 9; } +} + +/* _ _ ____ ____ ___ ____ ____ ____ ____ ____ ___ ____ ____ */ +/* |__| |___ |__| | \ |___ |__/ |___ | | | | | |___ |__/ */ +/* | | |___ | | |__/ |___ | \ | |__| |__| | |___ | \ */ +/* */ +header.masthead { + font-size: var(--font-size-16); + font-weight: var(--font-weight-bold); + line-height: 1; + + border-bottom: 1px solid transparent; + color: var(--colour-brand-text); + background: var(--colour-brand); + + &:has(> a) { padding: 16px; } + + > a { + display: inline; + margin-right: var(--spacing-2); + font-size: var(--font-size-27); + line-height: var(--line-height-27); + text-decoration: none; + color: inherit; + + &:visited { color: inherit; } + } + + nav { + display: flex; + flex-direction: row; + align-items: center; + + &:has(input:checked) { + padding-top: var(--spacing-3); + flex-direction: column; + } + + ul { + list-style-type: none; + align-items: center; + margin: 0; padding: 0; + height: 100%; + + /* On mobile layouts, hide items by default (except for the first one, which is a checkbox in disguise). + When checkbox is checked, flex in a column */ + display: flex; + flex-grow: 1; + flex-direction: row; + justify-content: flex-end; + li:not(:first-child) { display: none; } + &:has(input:checked){ + width: 100%; + display: flex; + flex-direction: column; + margin-top: var(--spacing-3); + li { + display: block; + width: 100%; + text-align: center; + border-top: 1px solid var(--colour-text); + } + } + + /* On tablet and above, show in a row */ + @media screen and (min-width: 641px) { /* tablet */ + li:has(input) { display: none !important; } + li { display: block !important; } + flex-direction: row; + justify-content: flex-start; + } + + li { + margin: 0; + box-sizing: border-box; + + /* Checkbox used for show/hide on mobile */ + input { + appearance: none; + display: block; width: 100%; height: 100%; + outline: none; + border: none; + margin: 0 auto; + &::before { + font-size: var(--font-size-16); + display: block; + width: 100%; + text-align: center; + content: "Open Navbar"; + font-weight: var(--font-weight-bold); + } + &:checked::before { content: "Close navbar"; } + } + + a,input { + padding: var(--spacing-4); + display: block; + box-sizing: border-box; + width: 100%; height: 100%; + + color: var(--colour-brand-text) !important; + text-decoration: none; + + &:hover, &:focus { + background: var(--colour-hover); + color: var(--colour-text) !important; + } + } + } + } + } +} + +footer { + font-size: var(--font-size-19); + line-height: var(--line-height-19); + + padding: var(--spacing-5) 0; + + border-top: 10px solid var(--colour-brand); + color: var(--colour-footer-text); + background: var(--colour-surface-background); +} + +/* ______ ___ ____ __ ____ __ */ +/* | || | // \\ || )) || || (( \ */ +/* || ||=|| ||=) || ||== \\ */ +/* || || || ||_)) ||__| ||___ \_)) */ +/* */ +table { + font-size: var(--font-size-19); + line-height: var(--line-height-19); + + margin-bottom: var(--spacing-6); + + width: 100%; + display: block; + overflow: auto; + + border-spacing: 0; + border-collapse: collapse; + + thead { font-weight: var(--font-weight-bold); } + + td,th { + padding: var(--spacing-2) var(--spacing-4) var(--spacing-2) 0; + border-bottom: 1px solid var(--colour-border); + text-align: left; + vertical-align: top; + + &.numeric { text-align: right; } + &:last-child { padding-right: 0; } + } + + + caption { + display: table-caption; + font-weight: var(--font-weight-bold); + text-align: left; + + + &.xl, &.l, &.m { margin-bottom: var(--spacing-3); } + + &.xl { font-size: var(--font-size-48); line-height: var(--line-height-48); } + &.l { font-size: var(--font-size-36); line-height: var(--line-height-36); } + &.m { font-size: var(--font-size-24); line-height: var(--line-height-24); } + } +} + +/* ____ ____ ____ _ _ ____ */ +/* |___ | | |__/ |\/| [__ */ +/* | |__| | \ | | ___] */ +/* */ +input { + font-size: var(--font-size-19); + line-height: var(--line-height-19); + + box-sizing: border-box; + width: 100%; + margin-top: 0; + padding: var(--spacing-1); + + + border: var(--width-input-border) var(--colour-input-border) solid; + border-radius: 0; + appearance: none; + + &:focus { + outline: var(--width-focus) solid var(--colour-focus); + outline-offset: 0; + /* Double the border by adding its width again. Use `box-shadow` for this */ + /* instead of changing `border-width` - this is for consistency with */ + /* components such as textarea where we avoid changing `border-width` as */ + /* it will change the element size. Also, `outline` cannot be utilised */ + /* here as it is already used for the yellow focus state. */ + box-shadow: inset 0 0 0 var(--width-input-border) var(--colour-input-border); + } + + &:disabled { + opacity: 0.5; + color: inherit; + background-color: transparent; + cursor: not-allowed; + } + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + margin: 0; + -webkit-appearance: none; + } + + &[type="number"] { + -moz-appearance: textfield; + } + + &.extra-letter-spacing { + font-variant-numeric: tabular-nums; + letter-spacing: 0.05em; + } + + + @media screen and (max-width: 640px) { /* until mobile */ + display: block; + max-width: 100%; + } +} + +input-wrapper { + display: flex; + input { flex: 0 1 auto; } +} + +input-prefix, input-suffix { + font-family: var(--font-size-19); + line-height: var(--line-height-19); + + box-sizing: border-box; + display: flex; + flex: 0 0 auto; + align-items: center; + justify-content: center; + min-width: 1rem; + padding: var(--spacing-1); + border: var(--width-input-border) var(--colour-input-border) solid; + + background-color: var(--black-tint-95); + text-align: center; + white-space: nowrap; + cursor: default; + +} +input-prefix { border-right: 0; } +input-suffix { border-left: 0; } + +.width-30 { max-width: 29.5em; } +.width-20 { max-width: 20.5em; } +.width-10 { max-width: 11.5em; } +.width-5 { max-width: 5.5em; } +.width-4 { max-width: 4.5em; } +.width-3 { max-width: 3.75em; } +.width-2 { max-width: 2.75em; } + + +form-group { + margin-bottom: var(--spacing-6); + display: block; + + &:last-of-type { + margin-bottom: 0; + } + + &:has(.error), &:has(input:invalid) { + padding-left: var(--spacing-3); + border-left: var(--width-form-group-error) solid var(--colour-error); + } +} + +input:invalid, +textarea:invalid, +form-group:has(.error) input, +form-group:has(.error) textarea { + border-color: var(--colour-error); + &:focus { border-color: var(--colour-input-border); } +} + +.error:not(a) { + font-size: var(--font-size-19); + line-height: var(--line-height-19); + + font-weight: var(--font-weight-bold); + + display: block; + margin-top: 0; + margin-bottom: var(--spacing-3); + clear: both; + color: var(--colour-error); +} + +label { + display: block; + margin-bottom: var(--spacing-1); + color: var(--color-text); +} + +h1 > label, +h2 > label, +h3 > label { + margin-bottom: var(--spacing-3); +} + +h1 > label { font-size: var(--font-size-48); } + +h1:has(> label), +h2:has(> label), +h3:has(> label) { + margin: 0; +} + +.hint { + font-size: var(--font-size-19); + line-height: var(--line-height-19); + + display: block; + margin-bottom: var(--spacing-3); + + color: var(--colour-secondary-text); +} + +textarea { + font-size: var(--font-size-19); + line-height: 1.25; + + display: block; + width: 100%; + min-height: 50px; + margin-bottom: var(--spacing-6); + padding: var(--spacing-1); + + resize: vertical; + border: var(--width-input-border) solid var(--colour-input-border); + border-radius: 0; + -webkit-appearance: none; + + &:focus { + outline: var(--width-focus) solid var(--colour-focus); + outline-offset: 0; + box-shadow: inset 0 0 0 var(--width-input-border) var(--colour-input-border); + } + + &:disabled { + opacity: 0.5; + color: inherit; + background-color: transparent; + cursor: not-allowed; + } +} + +button { + --button-bg: var(--green-shade-25); + --button-bg-darker: var(--green-shade-50); + --text: var(--colour-button-text); + + text-decoration: none; + font-weight: var(--font-weight-normal); + font-size: var(--font-size-80) + line-height: 1; + text-align: center; + + display: inline-block; + position: relative; + + margin: 0 0 22px; + margin-bottom: 22px; + padding: 8px 10px 7px; + border: 2px solid transparent; + + color: var(--text); + box-shadow: 0 2px 0 var(--button-bg-darker); + background-color: var(--button-bg); + + &:hover { + background-color: var(--button-bg-darker); + } + + &:active { + top: 2px; + } + + &:focus { + border-color: var(--colour-focus); + box-shadow: 0 var(--width-input-border) 0 var(--colour-focus-text); + outline: var(--width-focus) solid transparent; + } + + &.secondary { + --button-bg: var(--black-tint-95); + --button-bg-darker: var(--black-tint-80); + --text: var(--colour-text); + } + &.warning { + --button-bg: var(--red-shade-25); + --button-bg-darker: var(--red-shade-50); + } + &[disabled] { + opacity: .5; + cursor: notallowed; + + &:hover { + background-color: var(--button-bg); + } + &:active { + top: 0; + } + } +} diff --git a/web/tinyfeed/feeds.txt b/web/tinyfeed/feeds.txt new file mode 100644 index 0000000..9a5aea6 --- /dev/null +++ b/web/tinyfeed/feeds.txt @@ -0,0 +1 @@ +https://fasterthanli.me/index.xml diff --git a/web/tinyfeed/index.html.gtpl b/web/tinyfeed/index.html.gtpl new file mode 100644 index 0000000..9be5f8b --- /dev/null +++ b/web/tinyfeed/index.html.gtpl @@ -0,0 +1,87 @@ + + + + + + {{.Metadata.name}} + + + + + + + + + + {{range .Stylesheets}} + + {{end}} + + {{range .Scripts}} + + {{end}} + + + +
+ +
+
+ +
+

{{.Metadata.name}}

+ +
    + {{range .Items}} +
  1. + + {{domain .}} + +

    + {{.Title}} +

    +
  2. + {{end}} +
+
+ +
+
+ + + + diff --git a/web/tinyfeed/justfile b/web/tinyfeed/justfile new file mode 100644 index 0000000..36fe655 --- /dev/null +++ b/web/tinyfeed/justfile @@ -0,0 +1,8 @@ +build: + tinyfeed -i feeds.txt -o index.html -l 20 -s css.css -t index.html.gtpl + +watch: + watchexec -w index.html.gtpl just build + +open: build + firefox $(pwd)/index.html diff --git a/web/tinyfeed/shell.nix b/web/tinyfeed/shell.nix new file mode 100644 index 0000000..c221718 --- /dev/null +++ b/web/tinyfeed/shell.nix @@ -0,0 +1,12 @@ +{ + inputs ? import ../../nix/npins, + pkgs ? import { overlays = [ (import ../../nix/pkgs inputs) ]; }, +}: +pkgs.mkShell { + + buildInputs = [ + pkgs.tinyfeed + pkgs.watchexec + pkgs.just + ]; +}