├── .gitignore ├── .npmrc ├── package.json ├── demo ├── fullscreen-clock.html └── index.html ├── rollup └── rollup-npm.config.js ├── src ├── cistercian-clock.js └── cistercian-number.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix = "" 2 | message = "Upgrade to %s" 3 | preid = "beta" 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cistercian-numerals", 3 | "version": "1.1.0", 4 | "description": "A collection of web components to display cistercian numerals", 5 | "main": "dist/cistercian-numerals.js", 6 | "author": "Hubert Sablonnière", 7 | "repository": "github:hsablonniere/cistercian-numerals", 8 | "license": "MIT", 9 | "files": [ 10 | "src", 11 | "dist" 12 | ], 13 | "scripts": { 14 | "build": "rollup -c rollup/rollup-npm.config.js", 15 | "prepack": "npm run build", 16 | "start:dev": "wds --node-resolve -w" 17 | }, 18 | "dependencies": { 19 | "lit": "^2.0.0-pre.1" 20 | }, 21 | "devDependencies": { 22 | "@rollup/plugin-node-resolve": "^11.2.0", 23 | "@web/dev-server": "^0.1.8", 24 | "babel-plugin-template-html-minifier": "^4.1.0", 25 | "rollup": "^2.41.2", 26 | "rollup-plugin-babel": "^4.4.0", 27 | "rollup-plugin-clear": "^2.0.7", 28 | "rollup-plugin-terser": "^7.0.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /demo/fullscreen-clock.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Cistercian numerals 8 | 9 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /rollup/rollup-npm.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import clear from 'rollup-plugin-clear'; 3 | import resolve from '@rollup/plugin-node-resolve'; 4 | import { terser } from 'rollup-plugin-terser'; 5 | 6 | const outputDir = 'dist'; 7 | 8 | export default { 9 | input: { 10 | 'cistercian-numerals': 'src/cistercian-clock.js', 11 | }, 12 | output: { 13 | dir: outputDir, 14 | sourcemap: true, 15 | }, 16 | plugins: [ 17 | resolve(), 18 | clear({ 19 | targets: [outputDir], 20 | }), 21 | terser({ 22 | output: { comments: false }, 23 | }), 24 | babel({ 25 | plugins: [ 26 | // Minify HTML inside lit-html and LitElement html`` templates 27 | // Minify CSS inside LitElement css`` templates 28 | [ 29 | 'template-html-minifier', 30 | { 31 | modules: { 32 | 'lit': [ 33 | 'html', 34 | { name: 'css', encapsulation: 'style' }, 35 | ], 36 | }, 37 | htmlMinifier: { 38 | caseSensitive: true, 39 | collapseWhitespace: true, 40 | removeAttributeQuotes: true, 41 | removeComments: true, 42 | removeRedundantAttributes: true, 43 | // This clearly DOES NOT work well with template strings and lit-element 44 | sortAttributes: false, 45 | sortClassName: true, 46 | minifyCSS: { level: 2 }, 47 | }, 48 | }, 49 | ], 50 | ], 51 | }), 52 | ], 53 | }; 54 | -------------------------------------------------------------------------------- /src/cistercian-clock.js: -------------------------------------------------------------------------------- 1 | import { css, html, LitElement } from 'lit'; 2 | import './cistercian-number.js'; 3 | 4 | /** 5 | * A custom element to display a clock with cistercian numerals. 6 | * 7 | * 🎨 default CSS display: `block` 8 | * 9 | * @prop {Boolean} date - Enables displaying date (year-month-day) before displaying time. 10 | */ 11 | export class CistercianClock extends LitElement { 12 | 13 | static get properties () { 14 | return { 15 | date: { type: Boolean, reflect: true }, 16 | noSeconds: { type: Boolean, attribute: 'no-seconds', reflect: true }, 17 | _year: { type: Number }, 18 | _month: { type: Number }, 19 | _day: { type: Number }, 20 | _hours: { type: Number }, 21 | _minutes: { type: Number }, 22 | _seconds: { type: Number }, 23 | }; 24 | } 25 | 26 | constructor () { 27 | super(); 28 | this.date = false; 29 | this.noSeconds = false; 30 | } 31 | 32 | connectedCallback () { 33 | super.connectedCallback(); 34 | this._id = setInterval(() => { 35 | const currentDatetime = new Date(); 36 | this._year = currentDatetime.getFullYear(); 37 | this._month = currentDatetime.getMonth() + 1; 38 | this._day = currentDatetime.getDate(); 39 | this._hours = currentDatetime.getHours(); 40 | this._minutes = currentDatetime.getMinutes(); 41 | this._seconds = currentDatetime.getSeconds(); 42 | }, 500); 43 | } 44 | 45 | disconnectedCallback () { 46 | super.disconnectedCallback(); 47 | clearInterval(this._id); 48 | } 49 | 50 | render () { 51 | return html` 52 | ${this.date ? html` 53 | 54 | 55 | 56 | ` : ''} 57 | 58 | 59 | ${!this.noSeconds ? html` 60 | 61 | ` : ''} 62 | `; 63 | } 64 | 65 | static get styles () { 66 | return [ 67 | // language=CSS 68 | css` 69 | :host { 70 | align-items: center; 71 | display: flex; 72 | } 73 | 74 | cistercian-number:not(:last-child) { 75 | margin-right: 0.1em; 76 | } 77 | `, 78 | ]; 79 | } 80 | } 81 | 82 | window.customElements.define('cistercian-clock', CistercianClock); 83 | -------------------------------------------------------------------------------- /src/cistercian-number.js: -------------------------------------------------------------------------------- 1 | import { css, LitElement, svg } from 'lit'; 2 | 3 | /** 4 | * A custom element to display a number with cistercian numerals. 5 | * 6 | * 🎨 default CSS display: `block` 7 | * 8 | * @prop {Boolean} inline - Tries to size and align the number with the text around. 9 | * @prop {Number} value - Sets the value of the number to display (between 0-9999). 10 | * 11 | * @cssprop {Color} --cistercian-color-0 - Color of the center line (always displayed). 12 | * @cssprop {Color} --cistercian-color-1 - Color of the top line. 13 | * @cssprop {Color} --cistercian-color-2 - Color of the oblique NW-SE line. 14 | * @cssprop {Color} --cistercian-color-3 - Color of the bottom line. 15 | * @cssprop {Color} --cistercian-color-4 - Color of the oblique NE-SW line. 16 | * @cssprop {Color} --cistercian-color-5 - Color of the right line. 17 | * @cssprop {Width} --cistercian-width - Width of the line. 18 | */ 19 | export class CistercianNumber extends LitElement { 20 | 21 | static get properties () { 22 | return { 23 | value: { type: Number }, 24 | }; 25 | } 26 | 27 | constructor () { 28 | super(); 29 | this.value = 0; 30 | } 31 | 32 | _renderZone (nb) { 33 | 34 | // https://en.wikipedia.org/wiki/File:Cistercian_digits_(vertical).svg 35 | const state1 = [1, 5, 7, 9].includes(nb) ? '' : 'closed'; 36 | const state2 = [3].includes(nb) ? '' : 'closed'; 37 | const state3 = [2, 8, 9].includes(nb) ? '' : 'closed'; 38 | const state4 = [4, 5].includes(nb) ? '' : 'closed'; 39 | const state5 = [6, 7, 8, 9].includes(nb) ? '' : 'closed'; 40 | 41 | return svg` 42 | 43 | 44 | 45 | 46 | 47 | `; 48 | } 49 | 50 | render () { 51 | 52 | const units = Array 53 | .from(new Array(4)) 54 | .map(($, i) => Math.floor(this.value / 10 ** i) % 10); 55 | 56 | // https://css-tricks.com/accessible-svgs/ 57 | // TODO: test and ask around about the a11y 58 | return svg` 59 | 61 | ${this.value} 62 | ${this._renderZone(units[0])} 63 | ${this._renderZone(units[1])} 64 | ${this._renderZone(units[2])} 65 | ${this._renderZone(units[3])} 66 | 67 | 68 | `; 69 | } 70 | 71 | static get styles () { 72 | return [ 73 | // language=CSS 74 | css` 75 | :host { 76 | display: block; 77 | } 78 | 79 | :host([inline]) { 80 | display: inline-block; 81 | } 82 | 83 | svg { 84 | display: block; 85 | height: 1em; 86 | width: auto; 87 | } 88 | 89 | :host([inline]) svg { 90 | display: block; 91 | /* Those magic numbers are an attempt at aligning the number with some japanese katakana (as a reference) */ 92 | height: 0.9em; 93 | margin-bottom: -0.1em; 94 | } 95 | 96 | .unit { 97 | transform-origin: center center; 98 | } 99 | 100 | .unit-1 { 101 | transform: scale(-1, 1); 102 | } 103 | 104 | .unit-2 { 105 | transform: scale(1, -1); 106 | } 107 | 108 | .unit-3 { 109 | transform: scale(-1, -1); 110 | } 111 | 112 | line { 113 | stroke-linecap: round; 114 | stroke-linejoin: round; 115 | stroke-width: var(--cistercian-width, 3px); 116 | stroke: var(--cistercian-color-0, currentColor); 117 | transition: 150ms all ease-in-out; 118 | } 119 | 120 | .short { 121 | --lg: 10; 122 | } 123 | 124 | .long { 125 | --lg: 15; 126 | } 127 | 128 | .short, 129 | .long { 130 | stroke-dasharray: var(--lg) var(--lg); 131 | stroke-dashoffset: 0; 132 | } 133 | 134 | .short.closed, 135 | .long.closed { 136 | stroke-dashoffset: var(--lg); 137 | } 138 | 139 | .line-5.closed { 140 | transform: translateX(-10px); 141 | } 142 | 143 | .line-1 { 144 | stroke: var(--cistercian-color-1, currentColor); 145 | } 146 | 147 | .line-2 { 148 | stroke: var(--cistercian-color-2, currentColor); 149 | } 150 | 151 | .line-3 { 152 | stroke: var(--cistercian-color-3, currentColor); 153 | } 154 | 155 | .line-4 { 156 | stroke: var(--cistercian-color-4, currentColor); 157 | } 158 | 159 | .line-5 { 160 | stroke: var(--cistercian-color-5, currentColor); 161 | } 162 | `, 163 | ]; 164 | } 165 | } 166 | 167 | window.customElements.define('cistercian-number', CistercianNumber); 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cicstercian numerals components 2 | 3 | A collection of web components to display [cistercian numerals](https://en.wikipedia.org/wiki/Cistercian_numerals). 4 | 5 | You can see a live demo in this article: [A clock based on Cistercian numerals](https://www.hsablonniere.com/a-clock-based-on-cistercian-numerals--hptit8/). 6 | 7 | ## Usage (or install) 8 | 9 | You can directly load these components in your web page using a smart CDN like [jspm](https://jspm.org/), [unpkg](https://unpkg.com/) or [Skypack](https://www.skypack.dev/). 10 | 11 | ⚠️ WARNING: Remember that you need to trust this project *AND* those smart CDNs before loading some third party code like that. 12 | 13 | ### Load from jspm 14 | 15 | To load the components from jspm, add this to your ``: 16 | 17 | ```html 18 | 19 | ``` 20 | 21 | ### Load from unpkg 22 | 23 | To load the components from unpkg, add this to your ``: 24 | 25 | ```html 26 | 27 | ``` 28 | 29 | ### Load from skypack 30 | 31 | To load the components from skypack, add this to your ``: 32 | 33 | ```html 34 | 35 | ``` 36 | 37 | ### Install from npm 38 | 39 | You can also install the components in your project with npm: 40 | 41 | ```bash 42 | npm install cistercian-numerals 43 | ``` 44 | 45 | Then, you will need to import the components in your source: 46 | 47 | ```js 48 | import 'cistercian-numerals'; 49 | ``` 50 | 51 | NOTES: 52 | 53 | * You will need some kind of bundler or equivalent tool to resolve the bare identifier `cistercian-numerals` to a local file. 54 | * This will import a bundle containing all the components + LitElement's sources. 55 | 56 | You can also import the raw source if you just want the `` component or even if you're also using LitElement: 57 | 58 | ```js 59 | import 'cistercian-numerals/src/cistercian-number.js'; 60 | ``` 61 | 62 | ## Components 63 | 64 | ### `` 65 | 66 | This component displays one number with cistercian numerals. 67 | 68 | Use the `value` attribute to specify the number you want to display: 69 | 70 | ```html 71 | 72 | ``` 73 | 74 | By default, it will display the image as a block. 75 | If you want to display the number inside a paragraph of text, use the `inline` attribute. It should be sized and aligned correctly with the text: 76 | 77 | ```html 78 | Some text around the number. 79 | ``` 80 | 81 | By default, the line use the same color as the text. 82 | You can adjust the colors of the different lines with CSS custom properties: 83 | 84 | ```css 85 | cistercian-number { 86 | --cistercian-color-0: #00000000; /* Color of the center line (always displayed) */ 87 | --cistercian-color-1: #0019a7dd; /* Color of the top line */ 88 | --cistercian-color-2: #751056dd; /* Color of the oblique NW-SE line */ 89 | --cistercian-color-3: #007229dd; /* Color of the bottom line */ 90 | --cistercian-color-4: #db241fdd; /* Color of the oblique NE-SW line */ 91 | --cistercian-color-5: #fdcd01dd; /* Color of the right line */ 92 | } 93 | ``` 94 | 95 | You can also adjut the width of the lines. 96 | Use a pixel value between `1px and 3px`. 97 | 98 | ```css 99 | cistercian-number { 100 | --cistercian-width: 2px; 101 | } 102 | ``` 103 | 104 | By default, the height of a number is `1em`. 105 | You can of course adjust this by changing the font-size: 106 | 107 | ```css 108 | cistercian-number { 109 | font-size: 10em; 110 | } 111 | ``` 112 | 113 | ### `` 114 | 115 | This component displays a live clock using cistercian numerals. 116 | 117 | If you just want to display the current time (hours, minutes, seconds): 118 | 119 | ```html 120 | 121 | ``` 122 | 123 | If you just want to display the current time but without seconds (hours, minutes): 124 | 125 | ```html 126 | 127 | ``` 128 | 129 | If you just want to display the current date and time (year, month, day, hours, minutes, seconds): 130 | 131 | ```html 132 | 133 | ``` 134 | 135 | ## About this project 136 | 137 | Back in January 2021, I saw a tweet talking about [cistercian numerals](https://en.wikipedia.org/wiki/Cistercian_numerals). 138 | I was very intrigued. 139 | 140 | A week later, I saw someone who made a React component for it: https://twitter.com/aqandrew/status/1349762018639638528 141 | 142 | I thought it would be a fun side projet to create a web component for this and then reuse the single number component to create a clock. 143 | I took this opportunity to try Twitch and wrote the code for the first version live with some help from the chat. 144 | At the end of the stream, the component was mostly working but no animation and the design was a bit clunky. 145 | I improved it over the next few weeks. 146 | 147 | Since then, I saw a few fun projects with cistercian numerals: 148 | 149 | * Clairvo, a proof-of-concept font that uses OpenType Layout to implement cistercian numerals: https://github.com/TiroTypeworks/Clairvo 150 | * Cistercian SVG by Adrian Roselli: https://adrianroselli.com/2021/02/cistercian-svg.html 151 | * React based experiment by Maciej Ziarkowski: https://mz8i.com/cistercian 152 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Cistercian numerals 8 | 9 | 19 | 20 | 21 | 22 |

Cistercian numerals

23 | 24 |

Inside some text

25 | 26 |

27 | When used inside a paragraph of text, the number should be size and aligned a bit like a japanese katakana. 28 |

29 | 30 |

31 | Lorem ipsum dolor sit amet, 32 | 33 | consectetur adipiscing elit. Quisque feugiat dui at leo porta dignissim. Etiam ut purus ultrices, pulvinar tellus quis, cursus massa. Mauris dignissim accumsan ex, at vestibulum lectus fermentum id. Quisque nec magna arcu. Quisque in metus sed erat sodales euismod eget id purus. Sed sagittis rhoncus mauris. Ut sit amet urna ac nunc semper porta. Nam ut felis eu velit luctus rutrum. Nam leo nisl, molestie a varius non, ullamcorper sit amet tortor. Donec in convallis ex. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent hendrerit venenatis erat, eu malesuada nulla viverra eu. Curabitur porta risus augue, non rutrum lectus hendrerit a. 34 |

35 | 36 |

37 | Lorem ipsum dolor sit amet, 38 | 39 | consectetur adipiscing elit. Quisque feugiat dui at leo porta dignissim. Etiam ut purus ultrices, pulvinar tellus quis, cursus massa. Mauris dignissim accumsan ex, at vestibulum lectus fermentum id. Quisque nec magna arcu. Quisque in metus sed erat sodales euismod eget id purus. Sed sagittis rhoncus mauris. Ut sit amet urna ac nunc semper porta. Nam ut felis eu velit luctus rutrum. Nam leo nisl, molestie a varius non, ullamcorper sit amet tortor. Donec in convallis ex. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent hendrerit venenatis erat, eu malesuada nulla viverra eu. Curabitur porta risus augue, non rutrum lectus hendrerit a. 40 |

41 | 42 |

43 | Lorem ipsum dolor sit amet, 44 | 45 | consectetur adipiscing elit. Quisque feugiat dui at leo porta dignissim. Etiam ut purus ultrices, pulvinar tellus quis, cursus massa. Mauris dignissim accumsan ex, at vestibulum lectus fermentum id. Quisque nec magna arcu. Quisque in metus sed erat sodales euismod eget id purus. Sed sagittis rhoncus mauris. Ut sit amet urna ac nunc semper porta. Nam ut felis eu velit luctus rutrum. Nam leo nisl, molestie a varius non, ullamcorper sit amet tortor. Donec in convallis ex. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent hendrerit venenatis erat, eu malesuada nulla viverra eu. Curabitur porta risus augue, non rutrum lectus hendrerit a. 46 |

47 | 48 |

49 | Lorem ipsum dolor sit amet, 50 | 51 | consectetur adipiscing elit. Quisque feugiat dui at leo porta dignissim. Etiam ut purus ultrices, pulvinar tellus quis, cursus massa. Mauris dignissim accumsan ex, at vestibulum lectus fermentum id. Quisque nec magna arcu. Quisque in metus sed erat sodales euismod eget id purus. Sed sagittis rhoncus mauris. Ut sit amet urna ac nunc semper porta. Nam ut felis eu velit luctus rutrum. Nam leo nisl, molestie a varius non, ullamcorper sit amet tortor. Donec in convallis ex. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent hendrerit venenatis erat, eu malesuada nulla viverra eu. Curabitur porta risus augue, non rutrum lectus hendrerit a. 52 |

53 | 54 |

Using current color

55 | 56 |

57 | Lorem ipsum dolor sit amet, 58 | 59 | consectetur adipiscing elit. Quisque feugiat dui at leo porta dignissim. Etiam ut purus ultrices, pulvinar tellus quis, cursus massa. Mauris dignissim accumsan ex, at vestibulum lectus fermentum id. Quisque nec magna arcu. Quisque in metus sed erat sodales euismod eget id purus. Sed sagittis rhoncus mauris. Ut sit amet urna ac nunc semper porta. Nam ut felis eu velit luctus rutrum. Nam leo nisl, molestie a varius non, ullamcorper sit amet tortor. Donec in convallis ex. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent hendrerit venenatis erat, eu malesuada nulla viverra eu. Curabitur porta risus augue, non rutrum lectus hendrerit a. 60 |

61 | 62 |

63 | Lorem ipsum dolor sit amet, 64 | 65 | consectetur adipiscing elit. Quisque feugiat dui at leo porta dignissim. Etiam ut purus ultrices, pulvinar tellus quis, cursus massa. Mauris dignissim accumsan ex, at vestibulum lectus fermentum id. Quisque nec magna arcu. Quisque in metus sed erat sodales euismod eget id purus. Sed sagittis rhoncus mauris. Ut sit amet urna ac nunc semper porta. Nam ut felis eu velit luctus rutrum. Nam leo nisl, molestie a varius non, ullamcorper sit amet tortor. Donec in convallis ex. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent hendrerit venenatis erat, eu malesuada nulla viverra eu. Curabitur porta risus augue, non rutrum lectus hendrerit a. 66 |

67 | 68 |

In a clock

69 | 70 |

Time only

71 | 72 | 73 | 74 |

Time only (and multiple colors)

75 | 76 | 77 | 78 |

Time only but no seconds (and multiple colors)

79 | 80 | 81 | 82 |

Date and time (and multiple colors)

83 | 84 | 85 | 86 | 87 | 88 | --------------------------------------------------------------------------------