⚠️ This post was last updated on March 4, 2015 and the content may be OUTDATED!
If you encounter any issues, please feel free to reachout to me!
NOTE
Useful information that users should know, even when skimming content.
TIP
Helpful advice for doing things better or more easily.
IMPORTANT
Key information users need to know to achieve their goal.
WARNING
Urgent info that needs immediate user attention to avoid problems.
CAUTION
Advises about risks or negative outcomes of certain actions.
Code snippets
import { flattenObject, forObjectReplace } from "./lib/objects";
import tinycolor from "tinycolor2";
interface GeneratedTheme<T> {
themeCss: string;
theme: T;
}
export const generateTheme = <T extends Record<string, any>>(
vars: T,
prefix = "",
) => {
const themeCssVars = flattenObject(vars, (keys, value) => [
`${prefix}${keys.join("-")}`,
value,
]);
const theme = forObjectReplace(vars, (keys) => `var(--${keys.join("-")})`);
const generated: GeneratedTheme<T> = {
themeCss: `:root { ${Object.entries(themeCssVars)
.map(([k, v]) => `--${k}: ${v};`)
.join("\n")} }`,
theme,
};
return generated;
};
function isObject<T>(item: T) {
return item && typeof item === "object" && !Array.isArray(item);
}
export const deepMerge = <
T extends Record<string, any>,
U extends Record<string, any>,
>(
theme1: T,
theme2: U,
): T & U => {
// Deep merge as deep object
let output = Object.assign({}, theme1) as T & U;
if (isObject(theme1) && isObject(theme2)) {
Object.keys(theme2).forEach((key) => {
if (isObject(theme2[key])) {
if (!(key in theme1)) Object.assign(output, { [key]: theme2[key] });
else output[key as keyof T & U] = deepMerge(theme1[key], theme2[key]);
} else {
Object.assign(output, { [key]: theme2[key] });
}
});
}
return output;
};
const newKey = (keys: string[], value: any) =>
[`--${keys.join("-")}`, value] as [string, any];
const joinVariables = (vars: Record<string, any>) =>
Object.entries(vars)
.map(([k, v]) => `${k}: ${v};`)
.join("\n");
/**
* Final css should be
* :root { // Base vars }
* @media (prefers-color-scheme: dark) {
* :root { // Dark mode vars }
* }
* @media (prefers-color-scheme: light) {
* :root { // Light mode vars }
* }
* .dark-mode { // This is added to the body tag }
* .light-mode { // This is added to the body tag }
* The class is selected last to override preference
* when it is set specifically by user
*/
export const cssConverter = (theme: UITheme) => {
const baseCssVars = flattenObject(theme.base, newKey);
const modeVars = {
dark: flattenObject(theme.vars.dark, newKey),
light: flattenObject(theme.vars.light, newKey),
};
const baseStyles = `:root { ${joinVariables(baseCssVars)} }`;
const modeVarsStyles = ["dark", "light"]
.map((key) => {
const value = modeVars[key as keyof typeof modeVars];
return ` .${key}-mode { ${joinVariables(value)} } `;
})
.join("\n");
const mediaVarsStyles = ["dark", "light"]
.map((key) => {
const value = modeVars[key as keyof typeof modeVars];
return `
@media (prefers-color-scheme: ${key}) { :root { ${joinVariables(value)} } }
`;
})
.join("\n");
return `${baseStyles} ${mediaVarsStyles} ${modeVarsStyles}`;
};
/**
* Generates a theme from a UITheme
*
* param theme: UITheme to generate from
* param mode: ThemeMode to generate
* param prefix: Prefix to add to the css variables
*
* returns: GeneratedTheme
*
* Example:
* const custom = generateUITheme(defaultThemes[0], 'light')
* const customThemeCssVars = custom.themeCssVars
* const customTheme = custom.theme // can be imported as theme
*
* Usage of theme:
*
* - Styled:
* const StyledDiv = styled.div`
* font-size: ${theme.font.size.xl};
* `
*
* - CSS:
* div {
* font-size: var(--font-size-xl);
* }
*/
export const generateUITheme = (
theme: UITheme,
mode: ThemeMode,
prefix = "",
) => {
// Extends the base theme with the mode theme , add specific type from UITheme
const themeVars = deepMerge(theme.base, theme.vars["light"]);
const generatedTheme = forObjectReplace(
themeVars,
(keys) => `var(--${prefix}${keys.join("-")})`,
);
const generated: GeneratedTheme<typeof themeVars> = {
themeCss: cssConverter(theme),
theme: generatedTheme,
};
return generated;
};
// --step--2: clamp(0.7813rem, 0.7526rem + 0.0763vw, 0.8442rem);
// --step--1: clamp(0.9375rem, 0.8521rem + 0.2276vw, 1.1253rem);
// --step-0: clamp(1.125rem, 0.9545rem + 0.4545vw, 1.5rem);
// --step-1: clamp(1.35rem, 1.0548rem + 0.7873vw, 1.9995rem);
// --step-2: clamp(1.62rem, 1.1448rem + 1.2671vw, 2.6653rem);
// --step-3: clamp(1.944rem, 1.2127rem + 1.9502vw, 3.5529rem);
// --step-4: clamp(2.3328rem, 1.2404rem + 2.913vw, 4.736rem);
// --step-5: clamp(2.7994rem, 1.2022rem + 4.2591vw, 6.3131rem);
const fontSizes = {
// sm: "clamp(0.8rem, 0.21vw + 0.75rem, 0.94rem)",
// base: "clamp(1rem, 0.38vw + 0.9rem, 1.25rem)",
// md: "clamp(1.25rem, 0.64vw + 1.09rem, 1.67rem)",
// lg: "clamp(1.56rem, 1.01vw + 1.31rem, 2.22rem)",
// xl: "clamp(1.95rem, 1.55vw + 1.57rem, 2.96rem)",
// xxl: "clamp(2.44rem, 2.32vw + 1.86rem, 3.95rem)",
// xxxl: "clamp(3.05rem, 3.4vw + 2.2rem, 5.26rem)",
sm: "clamp(0.9375rem, 0.8521rem + 0.2276vw, 1.1253rem)",
base: "clamp(1.125rem, 0.9545rem + 0.4545vw, 1.5rem)",
md: "clamp(1.35rem, 1.0548rem + 0.7873vw, 1.9995rem)",
lg: "clamp(1.62rem, 1.1448rem + 1.2671vw, 2.6653rem)",
xl: "clamp(1.944rem, 1.2127rem + 1.9502vw, 3.5529rem)",
xxl: "clamp(2.3328rem, 1.2404rem + 2.913vw, 4.736rem)",
xxxl: "clamp(2.7994rem, 1.2022rem + 4.2591vw, 6.3131rem)",
};
const layout = {
content: {
wide: "1200px",
main: "800px",
},
nav: {
height: "3rem",
},
};
// Create union from array
export const CardTypes = [
"gradient",
"solid",
"solid-border",
"border",
"transparent",
] as const;
export type CardType = (typeof CardTypes)[number];
const baseVars = {
layout,
font: {
family: "Jost",
size: fontSizes,
},
border: {
radius: "0.5rem",
},
};
export const poemThemeVars = {
font: {
size: {
...fontSizes,
},
},
poems: {
fontFamily: "EB Garamond",
headingFont: '"Cormorant Garamond", serif',
background: "#ffffff",
heading: "#222",
text: "#444",
fadeText: "#666666",
surface: "#f5f5f5",
border: "#aaa",
},
};
const poemThemeGen =
// We add a custom prefix to the theme variables
generateTheme(poemThemeVars);
/** Use for setting css variables in the parent
* Ex: { 'poems-text' : '#444' } */
export const poemThemeCssVars = poemThemeGen.themeCss;
/** Use for declaring css styles in css-in-js
* Ex: { 'text': 'var(--poems-text)' } */
export const poemTheme = poemThemeGen.theme;
export type ThemeMode = "auto" | "light" | "dark";
export type BaseVars = typeof baseVars;
export interface UITheme {
id: string;
name: string;
base: BaseVars;
vars: {
light: ThemeVars;
dark: ThemeVars;
};
}
const defaultPaletteColors = {
primary: "#ff0000",
secondary: "#ffff00",
background: "#000000",
surface: "#222",
text: "#ffffff",
};
type PaletteColors = typeof defaultPaletteColors;
const defaultBasePalette = {
border: {
radius: "0.2rem",
},
font: {
family: "Jost",
},
};
type BasePalette = typeof defaultBasePalette;
export interface ThemePalette {
name: string;
id: string;
base: BasePalette;
card: CardType;
vars: {
light: PaletteColors;
dark: PaletteColors;
};
}
export const defaultThemePalette: ThemePalette = {
name: "Aster",
id: "default_aster",
base: {
border: {
radius: "0.2rem",
},
font: {
family: "Isoso Web",
},
},
card: "gradient",
vars: {
light: {
primary: "#613de3",
secondary: "#b55089",
background: "#dfdfed",
surface: "#9f9fe02d",
text: "#343654",
},
dark: {
primary: "#ff5370",
secondary: "#5d86ff",
background: "#0f111a",
surface: "#1e2139a0",
text: "#919DCF",
},
},
};
export const defaultPalettes: ThemePalette[] = [defaultThemePalette];
const getCard = (cardType: CardType) => {
if (cardType === "gradient") {
return {
card: {
border: "2px dashed var(--border-color)",
background:
"linear-gradient(-45deg, var(--background), var(--background), var(--surface))",
backgroundHover:
"linear-gradient(-45deg, var(--background), var(--background), var(--surface))",
backgroundPosition: "90% 0",
backgroundSize: "200%",
borderHover: "2px solid var(--border-color)",
backgroundPositionHover: "10% 20%",
},
};
} else if (cardType === "solid") {
return {
card: {
border: "none",
background: "var(--surface)",
backgroundPosition: "initial",
backgroundSize: "initial",
borderHover: "none",
backgroundHover: "var(--surface)",
backgroundPositionHover: "initial",
},
};
} else if (cardType === "solid-border") {
return {
card: {
border: "1px solid var(--border-color)",
background: "var(--surface)",
backgroundPosition: "initial",
backgroundHover: "initial",
backgroundSize: "initial",
borderHover: "1px solid var(--border-color)",
backgroundPositionHover: "initial",
},
};
} else if (cardType === "border") {
return {
card: {
border: "1px solid var(--border-color)",
background: "var(--background)",
backgroundHover: "var(--background)",
backgroundPosition: "initial",
backgroundSize: "initial",
borderHover: "1px solid var(--border-color)",
backgroundPositionHover: "initial",
},
};
} else {
// and also if (cardType === 'transparent')
return {
card: {
border: "none",
background:
"linear-gradient(-45deg, var(--background), var(--background), var(--surface))",
backgroundHover: "inherit",
backgroundPosition: "90% 0",
backgroundSize: "200%",
borderHover: "none",
backgroundPositionHover: "0% 0%",
},
};
}
};
const generateModeVarsFromPaletteColors = (
palette: PaletteColors,
cardType: CardType,
) => {
// verify if theme is dark or light by
// checking if background is dark or light
const isDark = tinycolor(palette.background).isDark();
// the color between surface and background (middle earth)
const midErth = tinycolor
.mix(palette.surface, palette.background, 50)
.toString();
const borderColor = tinycolor
.mix(palette.text, palette.background, 75)
.toString();
const tooltip = tinycolor
.mix(palette.background, tinycolor(palette.text).setAlpha(1), 15)
.toString();
return {
primary: {
color: palette.primary,
// For now, between black and white depending on primary color
// This is used to set the color of the text over the surfaces with primary background
contrast: tinycolor(palette.primary).isDark() ? "#ffffff" : "#000000",
},
secondary: { color: palette.secondary }, // Duh
colors: { purple: palette.primary /* More could be added */ },
background: palette.background,
surface: palette.surface,
surface2: tinycolor.mix(palette.surface, borderColor, 25).toString(),
tooltip,
border: {
style: "solid",
// Border color is between text and midErth
color: borderColor,
},
text: palette.text,
heading: tinycolor(palette.text)
[isDark ? "brighten" : "darken"]()
.toString(),
// Fade with midErth
fadeText: tinycolor.mix(palette.text, midErth, 30).toString(),
shadow: {
card: `0 1rem 2rem 0 rgba(0, 0, 0, ${isDark ? 0.6 : 0.2})`,
medium: `0 0.5rem 1rem 0 rgba(0, 0, 0, ${isDark ? 0.3 : 0.15})`,
small: `0 0.1rem 0.2rem 0 rgba(0, 0, 0, ${isDark ? 0.3 : 0.3})`,
cardDrop: `0 1rem 2rem rgba(0, 0, 0, ${isDark ? 0.6 : 0.2})`,
},
// We can use these inside new vars
...getCard(cardType),
// For now we use the primary color, less noise
bold: palette.primary,
italic: palette.primary,
strikethrough: palette.primary,
// Variables for variables.
gradient: {
"color-1": "var(--primary-color)",
"color-2": "var(--colors-purple)",
},
"animated-gradient":
"linear-gradient(-60deg, var(--gradient-color-1), var(--gradient-color-2), var(--gradient-color-1), var(--gradient-color-2), var(--gradient-color-1), var(--secondary-color), var(--gradient-color-1), var(--gradient-color-2))",
};
};
export type ThemeVars = ReturnType<typeof generateModeVarsFromPaletteColors>;
export const generateThemeFromPalette = (palette: ThemePalette): UITheme => {
const theme: UITheme = {
id: palette.id,
name: palette.name,
base: {
layout,
...palette.base,
font: {
family: palette.base.font.family,
size: fontSizes,
},
},
vars: {
light: generateModeVarsFromPaletteColors(
palette.vars.light,
palette.card,
),
dark: generateModeVarsFromPaletteColors(palette.vars.dark, palette.card),
},
};
// console.log('Generated theme', theme);
return theme;
};
const defaultTheme = generateUITheme(
generateThemeFromPalette(defaultThemePalette),
"dark",
);
export const themeCssVars = defaultTheme.themeCss;
export const theme = defaultTheme.theme;
This is a sample blog posts to test and tweak the components and elements present in the blog posts. Please enjoy/ignore accordingly.
This is a sample blog
test webmentions
Test 1 Test 2 Test 3 Test 4 Test 5 Test 6 Test 7 Test 8 Test 9 Test 10 Test 11 Test 12 Test 13 Test 14 Test 15 Test 16 Test 17 Test 18 Test 19 Test 20 Test 21 Test 22 Test 23Detailed Spec
h1 Heading 8-)
Paragraphs. Lorizzle ipsizzle dang ass izzle, the bizzle adipiscing elit. My shizz sapizzle velizzle, i’m in the shizzle volutpat, suscipizzle quizzle, sizzle vizzle, arcu. Sure crackalackin tortor. Sed erizzle. Mammasay mammasa mamma oo sa izzle dolor dawg tempizzle shiznit. Own yo’ pellentesque fizzle shizznit turpis. Fo shizzle my nizzle in fo shizzle. Daahng dawg sheezy rhoncus nisi. Da bomb hac ass platea away. dapibizzle. Shut the shizzle up that’s the shizzle urna, pretizzle nizzle, mattizzle gangster, eleifend dang, nunc. Its fo rizzle suscipizzle. Integizzle semper nizzle sed purus.
Bold
h2 Heading
Etiam laorizzle urna shut the shizzle up mammasay mammasa mamma oo sa. Bizzle quizzle get down get down. Maecenizzle pulvinar, ipsizzle get down get down malesuada shiznit, nulla ma nizzle euismizzle crackalackin, pimpin’ cool check out this nulla et fo shizzle. Vivamizzle fo shizzle my nizzle, tortor crazy we gonna chung fo shizzle, shiznit nunc ultricizzle gizzle, izzle daahng dawg leo elit izzle mofo. Maurizzle fizzle, orci vizzle volutpat black, sizzle augue luctus away, at bibendizzle enizzle check it out izzle get down get down. Nullam da bomb velizzle mah nizzle shizzlin dizzle that’s the shizzle viverra. Phasellus nizzle black. Curabitizzle pimpin’ my shizz vel pede yo facilisizzle.
h3 Heading
Crunk yo tortizzle in mofo yo mamma consequizzle. Crazy convallizzle, fizzle izzle that’s the shizzle posuere, we gonna chung lorizzle luctus the bizzle, bizzle blandizzle fo shizzle dolor mah nizzle velizzle. Gangsta ac yo mamma i’m in the shizzle elit fo shizzle mah nizzle fo rizzle, mah home g-dizzle i’m in the shizzle. Curabitizzle gangsta nisi, dizzle izzle, funky fresh eleifend, fo shizzle my nizzle izzle, metizzle. Nunc dope neque. Lorem ipsum dolizzle nizzle crackalackin, consectetizzle dang elizzle. Maecenas izzle elit. Fo shizzle nizzle. Vestibulum ac erat shiz velizzle shit dictizzle. Gangsta break it down mammasay mammasa mamma oo sa sizzle amizzle nibh. Cras own yo’.
h4 Heading
h5 Heading
h6 Heading
Lorizzle ipsizzle dang ass izzle, the bizzle adipiscing elit. My shizz sapizzle velizzle, i’m in the shizzle volutpat, suscipizzle quizzle, sizzle vizzle, arcu. Sure crackalackin tortor. Sed erizzle. Mammasay mammasa mamma oo sa izzle dolor dawg tempizzle shiznit. Own yo’ pellentesque fizzle shizznit turpis. Fo shizzle my nizzle in fo shizzle. Daahng dawg sheezy rhoncus nisi. Da bomb hac ass platea away. dapibizzle. Shut the shizzle up that’s the shizzle urna, pretizzle nizzle, mattizzle gangster, eleifend dang, nunc. Its fo rizzle suscipizzle. Integizzle semper nizzle sed purus.
Horizontal Rules
Typographic replacements
Enable typographer option to see result.
(c) (C) (r) (R) (tm) (TM) (p) (P) +-
test.. test… test… test?… test!…
!!!!!! ???? ,, — ---
“Smartypants, double quotes” and ‘single quotes’
Emphasis
This is bold text
This is italic text
Strikethrough
Blockquotes
Blockquotes can also be nested…
…by using additional greater-than signs right next to each other…
…or with spaces between arrows.
Lists
Unordered
- Create a list by starting a line with
+
,-
, or*
- Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
- Ac tristique libero volutpat at
- Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
- Marker character change forces new list start:
- Very easy!
Ordered
-
Lorem ipsum dolor sit amet
-
Consectetur adipiscing elit
-
Integer molestie lorem at massa
-
You can use sequential numbers…
-
…or keep all the numbers as
1.
Start numbering with offset:
- foo
- bar
Code
Inline code
Block code “fences”
Sample text here...
Syntax highlighting
var foo = function (bar) {
return bar++
}
console.log(foo(5))
Tables
Option | Description |
---|---|
data | path to data files to supply the data that will be passed into templates. |
engine | engine to be used for processing templates. Handlebars is the default. |
ext | extension to be used for dest files. |
Right aligned columns
Option | Description |
---|---|
data | path to data files to supply the data that will be passed into templates. |
engine | engine to be used for processing templates. Handlebars is the default. |
ext | extension to be used for dest files. |
Links
Autoconverted link https://github.com/nodeca/pica (enable linkify to see)
Images
Like links, Images also have a footnote style syntax
With a reference later in the document defining the URL location:
Title
Some sample text, with bold and italic and link.
An internal link would do [[no.harm]]
And some inline code `const a = 1;`
Heading 2
A long paragraph of text that will wrap and probably end up. Lorem ipsum.
lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
A proper long link:
https://example.com/this/is/a/very/long/link/that/will/likely/need/to/be/wrapped/to/multiple/lines
Quotes and Blocks
A regular blockquote:
This is a blockquote
inside a list item.
A Code block
const a = 1
const b = 2
Some hard core math:
And some inline math: $$f(x) = x^2$$
A Table
Syntax | Description | Test Text |
---|---|---|
Header | Title | Here’s this |
Paragraph | Text | And more |
Table | Data | Here’s this |
Tasks and lists
A list
- A list item
- Another list item
- A third list item
- A nested list item
- Another nested list item
-
A numbered list.
-
With multiple paragraphs.
A second paragraph in the list item. Make sure there is a blank line between paragraphs in the list item.
A bunch of Todos
- A todo
- A completed todo
- Another todo
- A nested todo
- Another nested todo
Special Syntax
- [-] A cancelled todo
- [a] A archived todo
- [b] A backlog todo
Astro goodies
I can mention blog posts as cards inside blog posts. Ex:
Sample Blog
A sample post with tons of elements to test the styles and layout
A tripple heading
A double heading
A pentapuple
Sooner or later, everything ends.