Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 4 additions & 5 deletions dev-server.js
Original file line number Diff line number Diff line change
@@ -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)

Expand Down Expand Up @@ -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) =>
Expand Down
46 changes: 46 additions & 0 deletions scripts/assemble-css.js
Original file line number Diff line number Diff line change
@@ -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 <destination> [custom-css ...]')
process.exitCode = 1
}
else {
writeCss(destination, customCss).catch(error => {
console.error(error)
process.exitCode = 1
})
}
}
212 changes: 212 additions & 0 deletions www/css/layout/footer.css
Original file line number Diff line number Diff line change
@@ -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;
}
}
4 changes: 4 additions & 0 deletions www/css/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"../style.css",
"layout/footer.css"
]
Loading