houseaccount 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/MIT-LICENSE +22 -0
  2. package/README.md +296 -0
  3. package/app/fonts/bootstrap-icons.woff2 +0 -0
  4. package/app/javascript/houseaccount/bookmark_controller.js +97 -0
  5. package/app/javascript/houseaccount/clear_controller.js +22 -0
  6. package/app/javascript/houseaccount/combobox/menu.js +93 -0
  7. package/app/javascript/houseaccount/combobox_controller.js +82 -0
  8. package/app/javascript/houseaccount/confirm.js +89 -0
  9. package/app/javascript/houseaccount/density_controller.js +21 -0
  10. package/app/javascript/houseaccount/deselect_controller.js +37 -0
  11. package/app/javascript/houseaccount/flash.js +35 -0
  12. package/app/javascript/houseaccount/limit_controller.js +23 -0
  13. package/app/javascript/houseaccount/map/loader.js +20 -0
  14. package/app/javascript/houseaccount/map_controller.js +88 -0
  15. package/app/javascript/houseaccount/otp/slots.js +27 -0
  16. package/app/javascript/houseaccount/otp_controller.js +98 -0
  17. package/app/javascript/houseaccount/phone_controller.js +89 -0
  18. package/app/javascript/houseaccount/placeholder_controller.js +28 -0
  19. package/app/javascript/houseaccount/relative_time_controller.js +45 -0
  20. package/app/javascript/houseaccount/require_controller.js +18 -0
  21. package/app/javascript/houseaccount/reveal_controller.js +16 -0
  22. package/app/javascript/houseaccount/scheme_controller.js +99 -0
  23. package/app/javascript/houseaccount/search_controller.js +78 -0
  24. package/app/javascript/houseaccount/shortcuts_controller.js +42 -0
  25. package/app/javascript/houseaccount/thread_controller.js +40 -0
  26. package/app/javascript/houseaccount/timezone_controller.js +27 -0
  27. package/app/javascript/houseaccount/toast_controller.js +35 -0
  28. package/app/javascript/houseaccount/tooltip_controller.js +23 -0
  29. package/app/javascript/houseaccount/wall/marks.js +8 -0
  30. package/app/javascript/houseaccount/wall/tools.json +75 -0
  31. package/app/javascript/houseaccount/wall_controller.js +93 -0
  32. package/app/javascript/houseaccount/written.js +20 -0
  33. package/app/javascript/houseaccount/written_controller.js +28 -0
  34. package/app/javascript/houseaccount.js +60 -0
  35. package/app/stylesheets/houseaccount/base.css +33 -0
  36. package/app/stylesheets/houseaccount/chat.css +73 -0
  37. package/app/stylesheets/houseaccount/flow.css +47 -0
  38. package/app/stylesheets/houseaccount/icons.css +186 -0
  39. package/app/stylesheets/houseaccount/lockup.css +58 -0
  40. package/app/stylesheets/houseaccount/map.css +3 -0
  41. package/app/stylesheets/houseaccount/pin.css +34 -0
  42. package/app/stylesheets/houseaccount/search.css +60 -0
  43. package/app/stylesheets/houseaccount/shell.css +197 -0
  44. package/app/stylesheets/houseaccount/table.css +105 -0
  45. package/app/stylesheets/houseaccount/values.css +41 -0
  46. package/app/stylesheets/houseaccount/wall.css +123 -0
  47. package/app/stylesheets/houseaccount.css +16 -0
  48. package/app/stylesheets/theme/bootstrap.css +11 -0
  49. package/app/stylesheets/theme/dawn.css +189 -0
  50. package/app/stylesheets/theme/dracula.css +189 -0
  51. package/app/stylesheets/theme/gruvbox.css +189 -0
  52. package/app/stylesheets/theme/monokai.css +190 -0
  53. package/app/stylesheets/theme/nord.css +189 -0
  54. package/app/stylesheets/theme/one_dark.css +189 -0
  55. package/app/stylesheets/theme/solarized.css +188 -0
  56. package/app/stylesheets/theme/tokyo_night.css +189 -0
  57. package/package.json +33 -0
  58. package/vendor/bootstrap-icons.min.css +5 -0
  59. package/vendor/bootstrap.bundle.min.js +9 -0
  60. package/vendor/bootstrap.min.css +2 -0
  61. package/vendor/fonts/OFL-quicksand.txt +93 -0
  62. package/vendor/fonts/bootstrap-icons.woff +0 -0
  63. package/vendor/fonts/bootstrap-icons.woff2 +0 -0
  64. package/vendor/fonts/quicksand-500-latin-ext.woff2 +0 -0
  65. package/vendor/fonts/quicksand-500-latin.woff2 +0 -0
  66. package/vendor/fonts/quicksand-500-vietnamese.woff2 +0 -0
@@ -0,0 +1,197 @@
1
+ /* A crumb that is not a link keeps its resting colors under the cursor.
2
+ Bootstrap lights every .breadcrumb-link on hover, which on a <span>
3
+ promises a click that goes nowhere — and the element is the whole
4
+ discriminator, since a crumb that links is an <a>. Redefining the hover
5
+ tokens, rather than out-cascading the hover rule, leaves Bootstrap's own
6
+ selector in charge and the active crumb its own resting color. */
7
+ span.breadcrumb-link {
8
+ --bs-breadcrumb-link-hover-color: var(--bs-breadcrumb-link-color);
9
+ --bs-breadcrumb-link-hover-bg: transparent;
10
+ }
11
+ span.breadcrumb-link.active {
12
+ --bs-breadcrumb-link-hover-color: var(--bs-breadcrumb-link-active-color);
13
+ }
14
+
15
+ /* Nothing here is meant to be wider than the window: the one thing that is, a
16
+ table, scrolls inside its own wrapper. Safari lays a stacked table's hidden
17
+ header row out at its full width all the same, and a page that can be
18
+ scrolled to it is a page Safari zooms out of and draws the navbar short on.
19
+ `clip` on the shell rather than `hidden`: a `hidden` axis turns the other into
20
+ `auto`, and the body then cut off whatever hung below it — a filter's menu, on a
21
+ page whose table had no rows to be taller than the menu. `clip` leaves the
22
+ other axis alone. */
23
+ html { overflow-x: hidden; }
24
+ .recourse-shell { overflow-x: clip; }
25
+
26
+ /* A finger does not hover. The tint a pointer leaves on the row under it, or on
27
+ the link under it, would stay on whatever was last touched — or land on whatever
28
+ the redrawn page put under the finger — so a touch screen gets none. */
29
+ @media (hover: none) {
30
+ .table-hover > tbody > tr:hover > * {
31
+ --bs-table-color-state: initial;
32
+ --bs-table-bg-state: initial;
33
+ }
34
+ .nav-link:hover:not(.active) { background-color: transparent; }
35
+ }
36
+
37
+ /* The navbar stacks over the content: a menu opened from a filter drops over the
38
+ first rows of the table rather than under them. */
39
+ .recourse-shell > nav { position: relative; z-index: 1; }
40
+
41
+ /* The shell is three things — the sidebar, the navbar, the content — written in
42
+ the order a phone reads them: a row of icons at the very top, the trail and the
43
+ search under it, the page under that, scrolling as one. From the width the
44
+ sidebar becomes a column at, a grid seats them the other way round. The navbar
45
+ runs across the top, the sidebar takes the column under it and the content the
46
+ rest, the shell is exactly the viewport, and `main` is the only thing in it that
47
+ scrolls — so the navbar and the sidebar stay where they are however far down the
48
+ table a reader is. `minmax(0, 1fr)` is what lets the second row be shorter than
49
+ what is in it; a `1fr` alone is as tall as its content, and the page would scroll
50
+ after all.
51
+ The sidebar deliberately does not scroll. A window dragged shorter than the
52
+ sidebar itself is the one case the viewport cannot hold, and there the sidebar
53
+ is allowed to spill past the shell and let the browser scroll the page — which is
54
+ what a reader expects of something taller than the screen. Were it to scroll on
55
+ its own instead, the same overflow would be hidden inside it and the page would
56
+ look whole while its foot was unreachable. */
57
+ @media (width >= 768px) {
58
+ .recourse-shell {
59
+ display: grid;
60
+ grid-template-columns: auto minmax(0, 1fr);
61
+ grid-template-rows: auto minmax(0, 1fr);
62
+ grid-template-areas: 'nav nav' 'aside main';
63
+ height: 100dvh;
64
+ }
65
+ .recourse-shell > nav { grid-area: nav; }
66
+ .recourse-sidebar { grid-area: aside; }
67
+ .recourse-shell > main { grid-area: main; overflow-y: auto; }
68
+ }
69
+
70
+ /* The sidebar rules off the navbar below it while it is the band at the top of a
71
+ phone, and off the content beside it once it is a column. v6 generates no
72
+ responsive border utilities, so the switch is written out. */
73
+ .recourse-sidebar {
74
+ border-block-end: var(--bs-border-width) solid var(--bs-border-color);
75
+ }
76
+ @media (width >= 768px) {
77
+ .recourse-sidebar {
78
+ border-block-end: 0;
79
+ border-inline-end: var(--bs-border-width) solid var(--bs-border-color);
80
+ }
81
+ }
82
+
83
+ /* Held Option marks the letter each sidebar link answers to. Marked rather than
84
+ bracketed: '[C]ontacts' would shift the whole sidebar two characters wide the
85
+ moment the key went down. Swap the two rules below for the bracketed form. */
86
+ .recourse-keys .recourse-key {
87
+ font-weight: var(--bs-font-weight-semibold);
88
+ text-decoration: underline;
89
+ text-underline-offset: 2px;
90
+ }
91
+ /* .recourse-keys .recourse-key::before { content: '['; }
92
+ .recourse-keys .recourse-key::after { content: ']'; } */
93
+
94
+ /* The foot of the sidebar holds the scheme toggle and the way out side by side,
95
+ each centered in its own half of the row: the toggle alone is in the middle, and
96
+ the two together are at a quarter and at three quarters. Each control sits in
97
+ a wrapper — the toggle's own, the form `button_to` draws — and the wrapper is
98
+ the half, so a button is only as wide as its icon and so is its hover. */
99
+ .recourse-foot { display: flex; }
100
+ .recourse-foot > * { flex: 1 1 0; display: flex; justify-content: center; }
101
+ .recourse-foot .nav-link { border-radius: var(--bs-radius-5); }
102
+ /* A hand cursor alone says little on an icon: a tint under it says it is a control.
103
+ Only where something can hover: a finger's tap leaves `:hover` set until the next
104
+ tap elsewhere, and a tint that stays reads as a button stuck down. */
105
+ @media (hover: hover) {
106
+ .recourse-foot .nav-link:hover { background-color: var(--bs-bg-1); color: var(--bs-fg-body); }
107
+ }
108
+
109
+ /* `.nav-item` ships `flex: auto`: in a row every entry would take an equal share
110
+ of the width and in a full-height column an equal share of the height, and the
111
+ foot's auto margin would never have anything left to push against. Every item is
112
+ its own size instead, in both directions. */
113
+ .recourse-sidebar .nav-item { flex: 0 0 auto; }
114
+
115
+ /* On a phone the sidebar is one row of icons across the top, centered, each entry
116
+ with less room around it than a column gives — a dozen have to fit — and wrapping
117
+ to a second line only where a host has more than a screen's width of them. */
118
+ @media (width < 768px) {
119
+ .recourse-sidebar .nav { justify-content: center; --bs-nav-link-padding-x: .5rem; }
120
+ }
121
+
122
+ /* The foot sits at the bottom of the sidebar and stretches across it while the
123
+ sidebar is a column, and is simply the last item of the row while it is not
124
+ — the query matches the `md:` the sidebar's own classes use. */
125
+ @media (width >= 768px) {
126
+ /* A floor rather than a height, and never wrapped. `.nav` ships `flex-wrap:
127
+ wrap`, and a column that may wrap, whose items are pinned to their own height
128
+ above, and which is told exactly how tall to be has one way to place a link
129
+ that does not fit: a second column. Which is what a short window used to draw.
130
+ The floor lets it be taller than the space it was given. */
131
+ .recourse-sidebar .nav { min-height: 100%; flex-wrap: nowrap; }
132
+ /* And it rides at the foot of the screen, which the auto margin alone is
133
+ enough to do: the nav's floor is the sidebar's full height, so while the
134
+ links leave room the margin takes all of it and the foot sits on the fold,
135
+ rising with it as the window is shortened. Once the links fill the nav there
136
+ is no room left to take, and the foot comes to rest immediately below the
137
+ last of them and goes under the fold with it, to be scrolled to.
138
+ Deliberately not sticky. Sticking it to the bottom of the viewport would lift
139
+ it off that resting place and hold it over the links it had passed — which is
140
+ what the background color was for, masking whichever one it covered. Both go
141
+ together: nothing is painted over, so nothing needs painting out. */
142
+ .recourse-sidebar .recourse-foot {
143
+ margin-block-start: auto;
144
+ align-self: stretch;
145
+ }
146
+ }
147
+
148
+ /* A phone reads the chrome as pictures. A sidebar entry is its icon, a crumb its
149
+ icon, a tab its icon and its figure — `17` rather than `17 franchises` — and the
150
+ foot's controls their icons, so the sidebar is one row of squares and the trail
151
+ leaves the search its room. The words are there all the same, read but not seen
152
+ the way Bootstrap's `.visually-hidden` does it, and the arrows at the foot put
153
+ them back: the cookie they write has the server draw the shell `.recourse-expanded`,
154
+ and then none of this applies. A tab or a crumb with no icon keeps its word at
155
+ every width, there being nothing else to stand for it.
156
+ The same hiding, at every width, for the foot's words once the sidebar is a
157
+ column: there is room for an icon and not for a word beside it. */
158
+ .recourse-foot-word { margin-inline-start: .4em; }
159
+ @media (width < 768px) {
160
+ .recourse-shell:not(.recourse-expanded) :is(.recourse-nav-word, .recourse-crumb-word,
161
+ .recourse-tab-word, .recourse-foot-word) {
162
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
163
+ overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
164
+ }
165
+ }
166
+ @media (width >= 768px) {
167
+ .recourse-foot-word {
168
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
169
+ overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
170
+ }
171
+ /* Nothing to expand: a column has its words already. */
172
+ .recourse-density { display: none; }
173
+ }
174
+
175
+ /* The arrows say where a tap goes, like the moon: parting, with `Expand`, while
176
+ the chrome is icons; meeting, with `Compact`, once the words are out. Both faces
177
+ are drawn and the shell's class picks, so the button needs no script to redraw. */
178
+ .recourse-density-expanded { display: none; }
179
+ .recourse-expanded .recourse-density-compact { display: none; }
180
+ .recourse-expanded .recourse-density-expanded { display: inline; }
181
+
182
+ /* The toggle draws both icons and shows the one that names where a click
183
+ would go: a moon on a light page, a sun on a dark one. Three states rather than
184
+ two, because a page nobody has chosen for carries no attribute at all, and the
185
+ media query is then the only thing that knows which mode it is in. Both are
186
+ declared at every step rather than one left to its default, so that no state
187
+ can end up hiding the pair and leaving a button with nothing in it. */
188
+ .recourse-scheme-light { display: inline; }
189
+ .recourse-scheme-dark { display: none; }
190
+ @media (prefers-color-scheme: dark) {
191
+ :root:not([data-bs-theme='light']) .recourse-scheme-light { display: none; }
192
+ :root:not([data-bs-theme='light']) .recourse-scheme-dark { display: inline; }
193
+ }
194
+ :root[data-bs-theme='light'] .recourse-scheme-light { display: inline; }
195
+ :root[data-bs-theme='light'] .recourse-scheme-dark { display: none; }
196
+ :root[data-bs-theme='dark'] .recourse-scheme-light { display: none; }
197
+ :root[data-bs-theme='dark'] .recourse-scheme-dark { display: inline; }
@@ -0,0 +1,105 @@
1
+ /* An action column is as wide as a row is tall — an icon in a square — and a
2
+ counter column starts at the same square, widening only when a figure like
3
+ 38,405 needs the room: a table laying out to 100% reads the width as a
4
+ preference, never crushes content into it, and hands what these cells do
5
+ not use to the columns carrying text. Not while the table is stacked, where
6
+ every cell is a block and a square one is a squashed one — the query
7
+ matches `.sm:table-stacked`'s. */
8
+ @container (width >= 576px) {
9
+ .recourse-actions, .recourse-counter {
10
+ width: calc(1em * var(--bs-body-line-height) + 2 * var(--bs-table-cell-padding-y));
11
+ white-space: nowrap;
12
+ text-align: center;
13
+ }
14
+ }
15
+
16
+ /* Stacked, a cell reads as one line — `Estimate hi: 600`, the heading semibold
17
+ and the value after it — where Bootstrap puts the heading on a line of its own
18
+ and leaves the row's first cell with no heading at all, bold in its place: a
19
+ status opening a row would be a bare badge. Every cell is headed here. And a
20
+ value with no space in it — an address, a token — breaks rather than widening
21
+ the row past the phone. */
22
+ @container (width < 576px) {
23
+ .sm\:table-stacked > tbody > tr > td[data-cell]::before {
24
+ content: attr(data-cell) ': ';
25
+ display: inline;
26
+ font-weight: var(--bs-font-weight-semibold);
27
+ }
28
+ .sm\:table-stacked > tbody > tr > td:first-child { font-weight: inherit; }
29
+ /* A counter's heading goes inside its link, so the whole `Franchises: 21` is
30
+ one thing to tap; the cell's own heading steps aside for it. */
31
+ .sm\:table-stacked > tbody > tr > td.recourse-counter::before { content: none; }
32
+ .sm\:table-stacked > tbody > tr > td.recourse-counter > [data-cell]::before {
33
+ content: attr(data-cell) ': ';
34
+ font-weight: var(--bs-font-weight-semibold);
35
+ }
36
+ .sm\:table-stacked > tbody > tr > td:not(:first-child) + td::before { margin-block-start: 0; }
37
+ .sm\:table-stacked > tbody > tr > td { overflow-wrap: anywhere; }
38
+ }
39
+
40
+ /* A counter says what it counts in one of two ways, and the table's own width is
41
+ what picks: the icon and a bare figure where the column is a square, the
42
+ counted model's words where there is room to read them. Bootstrap's `xl`, so
43
+ words arrive on a wide monitor and nowhere narrower — the width above is a
44
+ preference a table never crushes content into, so the column simply widens to
45
+ take them. Against `.table-responsive` like every query here, which is the
46
+ table's width rather than the window's: a sidebar takes a few hundred pixels
47
+ the window knows nothing about. */
48
+ .recourse-counter-word { display: none; }
49
+ @container (width >= 1280px) {
50
+ .recourse-counter-icon, .recourse-counter-figure { display: none; }
51
+ .recourse-counter-word { display: inline; }
52
+ }
53
+
54
+ /* A row the viewer has kept, and the only report a click ever gets, so it has to
55
+ be seen. A twelfth of the primary mixed into the page rather than
56
+ `--bs-primary-bg-subtle`: that token is a fixed step of the ramp, far enough
57
+ from the page on a low-contrast palette to cost the text a point of contrast
58
+ it has not got — mixing into the page keeps the tint the same distance from it
59
+ whatever the palette. A background rather than `.table-active`, which sets the
60
+ very variable `.table-hover` sets and would read as the row under the cursor,
61
+ and lose its tint the moment it became one; a background lets the hover shadow
62
+ lay over it. */
63
+ .table > tbody > .recourse-kept > * {
64
+ background-color: color-mix(in srgb, var(--bs-primary-fg) 12%, var(--bs-bg-body));
65
+ }
66
+
67
+ /* The row a write just landed on, tinted for as long as the message saying so
68
+ stands. On the cells rather than on the row, like the kept tint beside it: the
69
+ table collapses its borders, so the cells are what paint, and one tint across
70
+ all of them is what reads as a single row — where anything drawn per cell reads
71
+ as a box around each of them instead.
72
+ A background rather than a border. A border on a collapsing table is a width
73
+ the table then measures around, and taking it away again leaves the line it
74
+ drew sitting between two rows that no longer want one.
75
+ Mixed into the page exactly as the kept tint below is, and never
76
+ `--bs-success-bg-subtle`: that token is `light-dark(--bs-green-100,
77
+ --bs-green-900)`, and a palette builds those steps by mixing toward white or
78
+ black rather than toward the page — so how far it lands from the page is the
79
+ palette's business and not ours. Mixing here keeps it the same distance on all
80
+ nine. The success family rather than the primary one is what tells it apart
81
+ from the kept tint it may land on top of: a different hue reads as a different
82
+ thing, where a second shade of the primary would read as more of the same. */
83
+ .table > tbody > .recourse-written > *,
84
+ .table > tbody > .recourse-written-out > * {
85
+ --recourse-written: color-mix(in srgb, var(--bs-success-fg) 12%, var(--bs-bg-body));
86
+ }
87
+ .table > tbody > .recourse-written > * {
88
+ background-color: var(--recourse-written);
89
+ }
90
+ /* Let go of over the second the toast beside it takes to fade, and started by the
91
+ same event. The class above comes off as this one goes on, so the animation is
92
+ the only thing painting the row while it runs and nothing paints it afterwards
93
+ — which hands a kept row back its own tint rather than leaving it wearing this
94
+ one. */
95
+ .table > tbody > .recourse-written-out > * {
96
+ animation: recourse-written-out 1s linear;
97
+ }
98
+ @keyframes recourse-written-out {
99
+ from { background-color: var(--recourse-written); }
100
+ to { background-color: var(--bs-bg-body); }
101
+ }
102
+ /* The tint itself is not motion; only its going is. */
103
+ @media (prefers-reduced-motion: reduce) {
104
+ .table > tbody > .recourse-written-out > * { animation: none; }
105
+ }
@@ -0,0 +1,41 @@
1
+ /* One rule per row of the show page, so a value and its heading read as one thing
2
+ and the next pair as another. On the cells rather than between them: a column's
3
+ gutter is padding inside it, so two side by side draw one unbroken line.
4
+ Every row reserves the width a rule takes, in `transparent`, and the show page
5
+ only colors it in — which is what leaves a field at exactly the height of the
6
+ value it edits, row after row down two pages. */
7
+ .recourse-row {
8
+ border-block-end: var(--bs-border-width) solid transparent;
9
+ }
10
+ .recourse-values > .recourse-row {
11
+ border-block-end-color: var(--bs-border-color);
12
+ }
13
+ /* And a value stands as tall as the control that edits it, so a row on one page
14
+ is a row of the same height on the other. Bootstrap gives
15
+ `.form-control-plaintext` a control's padding and line height but no minimum,
16
+ and an icon or a badge is shorter than a box. */
17
+ .recourse-values .form-control-plaintext {
18
+ min-height: var(--bs-control-min-height);
19
+ }
20
+ /* The reveal is a word beside a value rather than a control beside it, so it takes
21
+ the line's height and not `.btn-sm`'s minimum — which would leave a masked row
22
+ taller than the field that edits the same attribute. */
23
+ .form-control-plaintext .btn {
24
+ min-height: 0;
25
+ }
26
+
27
+ /* A payload in a block of its own, bounded so it scrolls rather than grows. `pre`
28
+ for the indentation that makes JSON readable, but wrapping rather than running
29
+ off to the side: a flex item's automatic minimum size is the min-content width
30
+ of whatever is inside it, and `overflow` on this box does not lower it — so one
31
+ unbroken 4,000-character token became the width of the value's column, then of
32
+ `main`, which then wrapped below the sidebar it belongs beside. `pre-wrap` keeps
33
+ the indentation and takes the wrapping, and `anywhere` is what lowers
34
+ min-content, which `break-word` in its place would not. */
35
+ .recourse-payload {
36
+ max-height: 12rem;
37
+ margin-block-end: 0;
38
+ overflow: auto;
39
+ white-space: pre-wrap;
40
+ overflow-wrap: anywhere;
41
+ }
@@ -0,0 +1,123 @@
1
+ /* The wall: a cyanotype of trade tools drifting behind a page, drawn by the `wall`
2
+ controller from one attribute. What stands on it is the host's to draw. Every color is a
3
+ pair for light and dark: a blueprint is white on blue, so the theme inverts the pair
4
+ rather than tinting one of them. */
5
+
6
+ /*
7
+ * The wall's own values, at the root rather than on a shell: the field is rendered wherever a
8
+ * layout happens to put it, and off the shell it inherited nothing -- which left the ground
9
+ * unpainted and, with no size to read, every mark drawn at its natural size. One wall, the
10
+ * same on every page.
11
+ *
12
+ * A blueprint is white on blue, so the theme inverts the pair rather than tinting one of them:
13
+ * dark ink on pale stock in light, pale ink on deep navy in dark. Its own two blues rather
14
+ * than the theme's, so the wall reads the same whatever the primary is.
15
+ */
16
+ :root {
17
+ /* One alpha for the whole wall: the grid and the marks recede together, and the words in
18
+ front of them stay the first thing read. */
19
+ --tool-alpha: 0.16;
20
+ --tool-ground: light-dark(#c9d9ea, #0c2338);
21
+ --tool-ink: light-dark(#10314f, #cbdff2);
22
+ --tool-ink-alt: light-dark(#19507f, #a5c6e6);
23
+ --tool-size: 52px;
24
+ /* One turn of the wall. Every column takes its own fraction of this. */
25
+ --tool-drift: 300s;
26
+ }
27
+
28
+ @media (min-width: 768px) {
29
+ :root { --tool-size: 60px }
30
+ }
31
+
32
+ /*
33
+ * The ground, painted from the attribute that asks for the wall: the `wall` controller draws
34
+ * the columns a moment after the first paint, and a page that turned from white to blue as
35
+ * they arrived would read as a flash. A stroke and its caps here rather than on each mark,
36
+ * so a mark carries geometry alone.
37
+ */
38
+ [data-controller~='wall'] { position: relative; isolation: isolate; background: var(--tool-ground, #c9d9ea) }
39
+
40
+ /*
41
+ * Fixed rather than absolute, so the wall fills a phone's viewport and never lengthens the
42
+ * page. It carries the ground the card contrasts against too.
43
+ */
44
+ .tools {
45
+ position: fixed;
46
+ inset: 0;
47
+ overflow: hidden;
48
+ pointer-events: none;
49
+ background: var(--tool-ground, #c9d9ea);
50
+ /* Under everything else in the shell, which isolates it, so the wall never paints over
51
+ the sheet the controller put it beside. */
52
+ z-index: -1;
53
+ }
54
+
55
+ /* The drafting grid, drawn in the ink so it tracks the blue instead of being stated twice,
56
+ and held back by the same alpha as the marks rather than one baked into its colour. */
57
+ .tools-grid {
58
+ position: absolute;
59
+ inset: 0;
60
+ opacity: var(--tool-alpha);
61
+ background-image:
62
+ linear-gradient(to right, var(--tool-ink-alt) 1px, transparent 1px),
63
+ linear-gradient(to bottom, var(--tool-ink-alt) 1px, transparent 1px);
64
+ background-size: 20px 20px;
65
+ }
66
+
67
+ /*
68
+ * One element per column, each on its own timeline. A single field moving as a block reads as
69
+ * a sheet sliding; columns drifting at slightly different rates read as depth. Every column is
70
+ * twice the viewport with its marks drawn twice, so translating it by half its own height
71
+ * lands exactly on the repeat and the loop has no seam.
72
+ */
73
+ .tools-cols { position: absolute; inset: 0; display: flex }
74
+ .tools-col {
75
+ position: relative;
76
+ flex: 1;
77
+ height: 200%;
78
+ animation: tool-drift calc(var(--tool-drift, 300s) * var(--col-rate, 1)) linear infinite;
79
+ }
80
+
81
+ /*
82
+ * Fewer columns as the page narrows rather than the same seven squeezed together: the ones
83
+ * left widen to fill the row, so a mark keeps its size and its neighbours stay about two
84
+ * hundred pixels away whatever the window is doing.
85
+ */
86
+ @media (width < 1536px) { .tools-col:nth-child(n+7) { display: none } }
87
+ @media (width < 1280px) { .tools-col:nth-child(n+6) { display: none } }
88
+ @media (width < 1024px) { .tools-col:nth-child(n+5) { display: none } }
89
+ @media (width < 768px) { .tools-col:nth-child(n+4) { display: none } }
90
+ @media (width < 576px) { .tools-col:nth-child(n+3) { display: none } }
91
+
92
+ @keyframes tool-drift {
93
+ from { translate: 0 0 }
94
+ to { translate: 0 -50% }
95
+ }
96
+
97
+ /* A moving pattern is decoration; somebody who asked for stillness keeps the pattern. */
98
+ @media (prefers-reduced-motion: reduce) {
99
+ .tools-col { animation: none }
100
+ }
101
+
102
+ .tool {
103
+ position: absolute;
104
+ left: 50%;
105
+ width: var(--tool-size, 52px);
106
+ height: var(--tool-size, 52px);
107
+ /* Centered on its point by translate rather than a negative margin, so the mirrored columns
108
+ can use `scale` without the two fighting over one `transform`. */
109
+ translate: -50% -50%;
110
+ color: var(--tool-ink, #10314f);
111
+ opacity: var(--tool-alpha);
112
+ fill: none;
113
+ stroke: currentColor;
114
+ /* The house's stroke, on the 512 grid every mark is drawn on. */
115
+ stroke-width: 28;
116
+ /* Butt and miter rather than round: round caps are what made a wall of these read as a
117
+ nursery print rather than as a drawing. */
118
+ stroke-linecap: butt;
119
+ stroke-linejoin: miter;
120
+ }
121
+
122
+ .tool-alt { color: var(--tool-ink-alt) }
123
+
@@ -0,0 +1,16 @@
1
+ /* Everything a HouseAccount page is styled by, in one file: Bootstrap 6 as upstream ships
2
+ it, its icons cut down to the ones the house draws, and then the house's own rules, each in a file named for what it draws.
3
+ Built by esbuild into /css/houseaccount.css; the imports are the reading order. */
4
+ @import '../../vendor/bootstrap.min.css';
5
+ @import 'houseaccount/icons.css';
6
+ @import 'houseaccount/base.css';
7
+ @import 'houseaccount/lockup.css';
8
+ @import 'houseaccount/shell.css';
9
+ @import 'houseaccount/table.css';
10
+ @import 'houseaccount/map.css';
11
+ @import 'houseaccount/search.css';
12
+ @import 'houseaccount/values.css';
13
+ @import 'houseaccount/chat.css';
14
+ @import 'houseaccount/pin.css';
15
+ @import 'houseaccount/wall.css';
16
+ @import 'houseaccount/flow.css';
@@ -0,0 +1,11 @@
1
+ /* Bootstrap's own palette, and the only file here that declares nothing.
2
+
3
+ The eight beside it work by overriding upstream's `:root`, so having upstream's
4
+ palette back is a matter of dropping their block rather than of writing one — and
5
+ swapping this link in is how the sidebar's toggle drops it. `Recourse.theme = nil`
6
+ and `:bootstrap` are the same look by two routes: the first links no stylesheet at
7
+ all, the second links this one.
8
+
9
+ It is named so the toggle can reach it. A reader who rotates through nine palettes
10
+ and never finds the one the pages started in has been shown a door with no handle
11
+ on the inside. */