From 6df897ab2809245ed4e950383f9ef3919e82f839 Mon Sep 17 00:00:00 2001 From: Randall Naar Date: Mon, 31 Aug 2026 15:29:50 -0400 Subject: [PATCH] Split footer css into its own file. --- build.sh | 4 +- dev-server.js | 9 +- scripts/assemble-css.js | 46 ++++++++ www/css/layout/footer.css | 212 +++++++++++++++++++++++++++++++++++++ www/css/manifest.json | 4 + www/style.css | 213 -------------------------------------- 6 files changed, 268 insertions(+), 220 deletions(-) create mode 100644 scripts/assemble-css.js create mode 100644 www/css/layout/footer.css create mode 100644 www/css/manifest.json diff --git a/build.sh b/build.sh index a557cdb44..3bb37a98b 100755 --- a/build.sh +++ b/build.sh @@ -28,8 +28,8 @@ rm -rf $DEST/* # Static assets cp -RL www/* $CUSTOM_ASSETS $DEST/ -# CSS customizations -[ -n "$CUSTOM_CSS" ] && cat $CUSTOM_CSS >> $DEST/style.css +# Assemble base CSS modules followed by flavor customizations +node scripts/assemble-css.js "$DEST/style.css" $CUSTOM_CSS # Index HTML pug client/index.pug -o $DEST diff --git a/dev-server.js b/dev-server.js index 6ea539b4f..7f4194e9e 100644 --- a/dev-server.js +++ b/dev-server.js @@ -1,10 +1,11 @@ -import fs, { promises as fsp } from 'fs' +import fs from 'fs' import pug from 'pug' import pathu from 'path' import glob from 'glob' import express from 'express' import browserify from 'browserify-middleware' import cssjanus from 'cssjanus' +import { assembleCss } from './scripts/assemble-css' const rpath = p => pathu.join(__dirname, p) @@ -52,13 +53,11 @@ if (basePath !== '/') { router.get('/', (req, res) => res.render(rpath('client/index.pug'))) router.get('/app.js', browserify(rpath('client/src/run-browser.js'))) -// Merges the main stylesheet from www/style.css with the custom css files +// Merges the base CSS modules with the custom CSS files router.get('/style.css', p(async (req, res) => res.type('css').send(await prepCss()))) -const prepCss = async _ => - (await Promise.all([ rpath('www/style.css'), ...custom_css ].map(path => fsp.readFile(path)))) - .join('\n') +const prepCss = _ => assembleCss(custom_css) // Automatically adjust CSS for RTL using cssjanus router.get('/style-rtl.css', p(async (req, res) => diff --git a/scripts/assemble-css.js b/scripts/assemble-css.js new file mode 100644 index 000000000..fc98ce84f --- /dev/null +++ b/scripts/assemble-css.js @@ -0,0 +1,46 @@ +const { promises: fsp } = require('fs') +const path = require('path') + +const projectRoot = path.resolve(__dirname, '..') + , manifestPath = path.join(projectRoot, 'www/css/manifest.json') + , manifestRoot = path.dirname(manifestPath) + , manifest = require(manifestPath) + +const resolvePath = (root, file) => + path.isAbsolute(file) ? file : path.resolve(root, file) + +const readCss = files => Promise.all(files.map(file => fsp.readFile(file))) + +const assembleCss = async (customCss=[]) => { + const baseCss = await readCss( + manifest.map(file => resolvePath(manifestRoot, file))) + , custom = await readCss( + customCss.map(file => resolvePath(projectRoot, file))) + , separatedBaseCss = baseCss.reduce((parts, css, index) => [ + ...parts, + ...(index ? [ Buffer.from('\n') ] : []), + css + ], []) + + return Buffer.concat([ ...separatedBaseCss, ...custom ]) +} + +const writeCss = async (destination, customCss=[]) => + fsp.writeFile(destination, await assembleCss(customCss)) + +module.exports = { assembleCss, writeCss } + +if (require.main === module) { + const [ destination, ...customCss ] = process.argv.slice(2) + + if (!destination) { + console.error('Usage: node scripts/assemble-css.js [custom-css ...]') + process.exitCode = 1 + } + else { + writeCss(destination, customCss).catch(error => { + console.error(error) + process.exitCode = 1 + }) + } +} diff --git a/www/css/layout/footer.css b/www/css/layout/footer.css new file mode 100644 index 000000000..d4f0191dc --- /dev/null +++ b/www/css/layout/footer.css @@ -0,0 +1,212 @@ +/* Keep main content and its sibling footer aligned to the same parent width. */ +.explorer-container > .explorer-main, +.explorer-container > .footer { + width: 100%; + box-sizing: border-box; +} + +.explorer-container > .explorer-main { + padding-bottom: 0; +} + +.explorer-container > .blockstream-footer { + height: auto; + color: #fff; + background: var(--background-color); + border-top: 0; + align-items: stretch; + flex: 0 0 auto; +} + +.explorer-container > .blockstream-footer > .blockstream-footer-container { + width: 100%; + margin-top: 100px; + padding-bottom: 80px; + box-sizing: border-box; + display: block; +} + +.blockstream-footer-content { + display: flex; + column-gap: 32px; + align-items: flex-start; + width: 100%; +} + +.blockstream-footer a, +.blockstream-footer a:link, +.blockstream-footer a:visited { + color: #f5f2fa; +} + +.blockstream-footer a:hover, +.blockstream-footer a:focus-visible { + color: var(--accent-color); +} + +.blockstream-footer-brand { + min-width: 0; +} + +.blockstream-footer-logo { + display: block; + width: 128px; +} + +.blockstream-footer-logo img { + display: block; + width: 100%; + height: auto; + filter: grayscale(1) brightness(0) invert(1); +} + +.blockstream-footer-socials { + display: flex; + gap: 10px; + margin-top: 16px; +} + +.blockstream-footer-socials a { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + box-sizing: border-box; + color: #fff; + border: 1px solid var(--surface-secondary-color); + border-radius: 5px; + transition: border-color 0.2s ease, background-color 0.2s ease; +} + +.blockstream-footer-socials a:hover, +.blockstream-footer-socials a:focus { + color: #fff; + background: var(--accent-color); + border-color: var(--accent-color); +} + +.blockstream-footer-socials img { + display: block; + width: 17px; + height: 17px; + object-fit: contain; + filter: grayscale(1) brightness(0) invert(1); +} + +a.blockstream-footer-contact, +a.blockstream-footer-contact:link, +a.blockstream-footer-contact:visited, +a.blockstream-footer-contact:hover, +a.blockstream-footer-contact:focus { + display: inline-block; + margin-top: 12px; + color: var(--foreground-link-color); + font-size: 16px; + text-decoration: underline; + text-underline-offset: 5px; +} + +.blockstream-footer-legal { + margin-top: var(--page-gap); + color: #f5f2fa; + font-size: 12px; + display: flex; + justify-content: space-between; +} + +.blockstream-footer-policies { + display: flex; + align-items: center; + gap: 15px; +} + +.blockstream-footer-policies span { + width: 1px; + height: 22px; + background: #f5f2fa; +} + +.blockstream-footer-section { + padding-top: 14px; +} + +.blockstream-footer-product-sections { + display: flex; + margin-left: auto; + gap: 44px; +} + +.blockstream-footer-section h2 { + margin: 0; + color: #fff; + font-family: "Inter", sans-serif; + font-size: 18px; + font-weight: 400; + line-height: 1.2; +} + +.blockstream-footer-links { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 12px; + margin-top: 32px; +} + +.blockstream-footer-links a { + font-size: 11px; + font-weight: 400; + line-height: 1.5; + white-space: nowrap; +} + +.blockstream-footer-product-columns { + display: flex; + column-gap: 44px; + margin-top: 32px; +} + +.blockstream-footer-product-columns .blockstream-footer-links { + min-width: 0; + margin-top: 0; +} + +@media only screen and (max-width: 790px) { + .blockstream-footer-product-sections { + flex-wrap: wrap; + margin-left: auto; + } +} + +@media only screen and (max-width: 1328px) { + .explorer-container > .blockstream-footer > .blockstream-footer-container { + margin-top: 0; + padding-top: 72px; + padding-bottom: 56px; + } + + .blockstream-footer-content { + flex-wrap: wrap; + column-gap: 32px; + row-gap: 42px; + } + + .blockstream-footer-products { + flex-basis: 100%; + } + + .blockstream-footer-developers, + .blockstream-footer-company, + .blockstream-footer-resources { + flex: 1 1 calc(50% - 16px); + } + + .blockstream-footer-product-columns { + column-gap: 24px; + } + + .table-title-row { + display: none; + } +} diff --git a/www/css/manifest.json b/www/css/manifest.json new file mode 100644 index 000000000..685e75778 --- /dev/null +++ b/www/css/manifest.json @@ -0,0 +1,4 @@ +[ + "../style.css", + "layout/footer.css" +] diff --git a/www/style.css b/www/style.css index ccfb04bb4..cedc867b6 100644 --- a/www/style.css +++ b/www/style.css @@ -5810,216 +5810,3 @@ a.back-link img{ animation: none; } } - -/* Keep main content and its sibling footer aligned to the same parent width. */ -.explorer-container > .explorer-main, -.explorer-container > .footer { - width: 100%; - box-sizing: border-box; -} - -.explorer-container > .explorer-main { - padding-bottom: 0; -} - -.explorer-container > .blockstream-footer { - height: auto; - color: #fff; - background: var(--background-color); - border-top: 0; - align-items: stretch; - flex: 0 0 auto; -} - -.explorer-container > .blockstream-footer > .blockstream-footer-container { - width: 100%; - margin-top: 100px; - padding-bottom: 80px; - box-sizing: border-box; - display: block; -} - -.blockstream-footer-content { - display: flex; - column-gap: 32px; - align-items: flex-start; - width: 100%; -} - -.blockstream-footer a, -.blockstream-footer a:link, -.blockstream-footer a:visited { - color: #f5f2fa; -} - -.blockstream-footer a:hover, -.blockstream-footer a:focus-visible { - color: var(--accent-color); -} - -.blockstream-footer-brand { - min-width: 0; -} - -.blockstream-footer-logo { - display: block; - width: 128px; -} - -.blockstream-footer-logo img { - display: block; - width: 100%; - height: auto; - filter: grayscale(1) brightness(0) invert(1); -} - -.blockstream-footer-socials { - display: flex; - gap: 10px; - margin-top: 16px; -} - -.blockstream-footer-socials a { - display: flex; - align-items: center; - justify-content: center; - width: 36px; - height: 36px; - box-sizing: border-box; - color: #fff; - border: 1px solid #4f4268; - border-radius: 5px; - transition: border-color 0.2s ease, background-color 0.2s ease; -} - -.blockstream-footer-socials a:hover, -.blockstream-footer-socials a:focus { - color: #fff; - background: var(--accent-color); - border-color: var(--accent-color); -} - -.blockstream-footer-socials img { - display: block; - width: 17px; - height: 17px; - object-fit: contain; - filter: grayscale(1) brightness(0) invert(1); -} - -a.blockstream-footer-contact, -a.blockstream-footer-contact:link, -a.blockstream-footer-contact:visited, -a.blockstream-footer-contact:hover, -a.blockstream-footer-contact:focus { - display: inline-block; - margin-top: 12px; - color: var(--foreground-link-color); - font-size: 16px; - text-decoration: underline; - text-underline-offset: 5px; -} - -.blockstream-footer-legal { - margin-top: var(--page-gap); - color: #f5f2fa; - font-size: 12px; - display: flex; - justify-content: space-between; -} - -.blockstream-footer-policies { - display: flex; - align-items: center; - gap: 15px; -} - -.blockstream-footer-policies span { - width: 1px; - height: 22px; - background: #f5f2fa; -} - -.blockstream-footer-section { - padding-top: 14px; -} - -.blockstream-footer-product-sections { - display: flex; - margin-left: auto; - gap: 44px; -} - -.blockstream-footer-section h2 { - margin: 0; - color: #fff; - font-family: "Inter", sans-serif; - font-size: 18px; - font-weight: 400; - line-height: 1.2; -} - -.blockstream-footer-links { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 12px; - margin-top: 32px; -} - -.blockstream-footer-links a { - font-size: 11px; - font-weight: 400; - line-height: 1.5; - white-space: nowrap; -} - -.blockstream-footer-product-columns { - display: flex; - column-gap: 44px; - margin-top: 32px; -} - -.blockstream-footer-product-columns .blockstream-footer-links { - min-width: 0; - margin-top: 0; -} - -@media only screen and (max-width: 790px) { - .blockstream-footer-product-sections { - flex-wrap: wrap; - margin-left: auto; - } -} - -@media only screen and (max-width: 1328px) { - .explorer-container > .blockstream-footer > .blockstream-footer-container { - margin-top: 0; - padding-top: 72px; - padding-bottom: 56px; - } - - .blockstream-footer-content { - flex-wrap: wrap; - column-gap: 32px; - row-gap: 42px; - } - - .blockstream-footer-products { - flex-basis: 100%; - } - - .blockstream-footer-developers, - .blockstream-footer-company, - .blockstream-footer-resources { - flex: 1 1 calc(50% - 16px); - } - - .blockstream-footer-product-columns { - column-gap: 24px; - } - - .table-title-row { - display: none; - } -}