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,33 @@
1
+ /* Body text is Bootstrap's own: the system face at 16px. A host wanting another sets
2
+ `--bs-body-font-family` and `--bs-body-font-size` on `:root` after this stylesheet. */
3
+
4
+ /* Bootstrap reserves the scrollbar's column on every page, scrolling or not, so pages line
5
+ up whether they grow or not; a browser with classic scrollbars then paints an empty track
6
+ down the right of a page that fits. The column comes and goes with the scrollbar here. */
7
+ :root { scrollbar-gutter: auto }
8
+
9
+ /* A dialog reads from the start whatever centers the text around the link that opened it:
10
+ `dialog` draws the two side by side, so the element inherits from the link's place. */
11
+ .dialog { text-align: start }
12
+
13
+ /* Bootstrap's .15s fade reads as vanishing. Only the hide ever transitions here
14
+ — the toast is born visible — and reduced-motion still wins, because its rule
15
+ sets `transition: none` outright rather than through this variable. */
16
+ .toast { --bs-transition-fade: opacity 1s linear; }
17
+
18
+ /* A marked match is part of the word it sits in, so it carries no padding of
19
+ its own: 'Nash' in 'Nashville' must not read as a word standing apart. */
20
+ mark { padding: 0; }
21
+
22
+ /* The icon font drops every glyph an eighth of an em to sit on a text
23
+ baseline, which inside an icon link reads as the arrow resting on the
24
+ line. A sixteenth up clears the baseline without floating — on the
25
+ ::before, so the hover transform on the .bi box itself is untouched. */
26
+ .icon-link > .bi::before { vertical-align: .0625em; }
27
+
28
+ /* The page is as tall as the window, and `svh` rather than `vh` is what that means on a
29
+ phone: `vh` measures the window with the browser's own bars hidden, which they are not
30
+ until somebody scrolls — so a foot resting at the bottom of a `100vh` page rests below
31
+ what can be seen, and reaching it costs a scroll. `svh` is the window with the bars
32
+ showing, the smaller of the two, so the foot lands above the fold and stays there. */
33
+ .house-page { min-height: 100svh }
@@ -0,0 +1,73 @@
1
+ /* A conversation as a column of bubbles: what the reader said on the right in the
2
+ primary's tint, what was said to them on the left in the page's second surface. Every
3
+ HouseAccount chat is drawn by the `chat` helper and wears these. */
4
+ .chat { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.25rem }
5
+ /* Sits at the foot of its box while short, so the first turn lands just above the field
6
+ that sends the next. An auto margin and not `align-content: end`, which once the thread
7
+ overflows pushes its first message clean out of the top with no way to scroll back. */
8
+ .chat > :first-child { margin-block-start: auto }
9
+
10
+ /* Who said it and when, small, over the bubble it belongs to and on its side. */
11
+ .chat-aside { font-size: var(--bs-font-size-sm); color: var(--bs-fg-2); margin: 0 0.75rem }
12
+ .chat-aside-out { align-self: flex-end; margin-inline-end: 1.25rem }
13
+ .chat-aside-in { align-self: flex-start }
14
+ .chat-aside .fg-success, .chat-aside .fg-danger, .chat-aside .fg-secondary { font-weight: var(--bs-font-weight-semibold) }
15
+
16
+ .chat-bubble {
17
+ max-width: 85%;
18
+ margin: 0;
19
+ padding: 0.625rem 0.875rem;
20
+ border: 1px solid;
21
+ border-radius: 1rem;
22
+ /* The same sheen a message thread's bubbles wear. Set as an image so the two sides below
23
+ can name their own color without wiping it, which the `background` shorthand would. */
24
+ background-image: var(--bs-gradient);
25
+ /* An unbroken token wraps rather than widening the bubble past the phone. Whitespace is
26
+ collapsed as anywhere else: a host's line breaks arrive as markup, and the newlines and
27
+ indentation a template leaves around its words must not become room in the bubble. */
28
+ overflow-wrap: anywhere;
29
+ }
30
+ /* The reader's own turn, tinted with the primary rather than filled with it: a solid fill
31
+ under white text is what a button wears, and a question that reads as a button invites a
32
+ press. Squared at the corner nearest them, which tells the two sides apart at a glance. */
33
+ .chat-out {
34
+ align-self: flex-end;
35
+ /* Clear of the scrollbar a long thread puts down the same edge. */
36
+ margin-inline-end: 0.5rem;
37
+ color: var(--bs-primary-fg-emphasis);
38
+ background-color: var(--bs-primary-bg-subtle);
39
+ border-color: var(--bs-primary-border);
40
+ border-end-end-radius: 0.25rem;
41
+ }
42
+ /* The other side's: the page's second surface, squared at its own corner. */
43
+ .chat-in {
44
+ align-self: flex-start;
45
+ color: var(--bs-fg-1);
46
+ background-color: var(--bs-bg-2);
47
+ border-color: var(--bs-border-color);
48
+ border-end-start-radius: 0.25rem;
49
+ }
50
+
51
+ /* An answer on its way reads as one being typed. */
52
+ .chat-typing { display: flex; gap: 0.25rem; align-items: center }
53
+ .chat-typing span {
54
+ width: 0.375rem;
55
+ height: 0.375rem;
56
+ border-radius: 50%;
57
+ background: var(--bs-fg-2);
58
+ animation: chat-dot 1.2s ease-in-out infinite;
59
+ }
60
+ .chat-typing span:nth-child(2) { animation-delay: 0.15s }
61
+ .chat-typing span:nth-child(3) { animation-delay: 0.3s }
62
+ @keyframes chat-dot { 0%, 60%, 100% { opacity: 0.25 } 30% { opacity: 1 } }
63
+ @media (prefers-reduced-motion: reduce) {
64
+ .chat-typing span { animation: none; opacity: 0.5 }
65
+ }
66
+
67
+ /* The field's suggestion fades rather than cutting, so a swap mid-read is not a flicker;
68
+ the `placeholder` controller adds the class while it swaps the words. */
69
+ .chat-ask::placeholder { opacity: 1; transition: opacity 0.3s ease-in-out }
70
+ .chat-ask.is-fading::placeholder { opacity: 0 }
71
+ @media (prefers-reduced-motion: reduce) {
72
+ .chat-ask::placeholder { transition: none }
73
+ }
@@ -0,0 +1,47 @@
1
+ /* The page `flow` draws: a column taking the rest of the window so the foot rests at the
2
+ bottom of a tall screen and scrolls into view on a short one, everything centered. */
3
+ .flow { flex: 1; display: flex; flex-direction: column; text-align: center }
4
+ .flow > header { display: flex; justify-content: center; margin-bottom: 2rem }
5
+ .flow > h1 { margin: 0 0 0.75rem; font-size: clamp(1.5rem, 4vw, 2rem); font-weight: 700; letter-spacing: -0.02em }
6
+ .flow > p { margin: 0 0 1.5rem }
7
+ /* `display: contents`, so a `button_to` form stops being a box and its button becomes a flex
8
+ item, which is what lets the middot space evenly between two words. A button in the foot is
9
+ a word among words: the foot's size, no padding and no height of its own. */
10
+ .flow > footer { flex: none; margin-top: auto; padding-top: 2.5rem; display: flex; align-items: baseline; justify-content: center; gap: 0.5rem; font-size: 0.875rem }
11
+ .flow > footer form { display: contents }
12
+ .flow > footer .btn { font-size: inherit; padding: 0; min-height: 0 }
13
+ @media (max-width: 575px) { .flow > header { margin-bottom: 1.75rem } }
14
+
15
+ /* The one element on the page allowed a fill, a border and a shadow at once, because it holds
16
+ the only thing the page is for. As wide as the page said, `--flow-width`, and nine tenths of
17
+ the page color rather than `opacity`, which would fade the border and the words with it. */
18
+ .flow-card {
19
+ width: 100%;
20
+ max-width: var(--flow-width, 30rem);
21
+ background: color-mix(in srgb, var(--bs-bg-body) 90%, transparent);
22
+ border: 1px solid var(--bs-primary-border);
23
+ border-radius: 0.375rem;
24
+ box-shadow: -0.5rem 1rem 2.25rem -0.75rem rgb(6 22 40 / 40%);
25
+ }
26
+ /* Centered on the controls rather than the form, since a field does not inherit it and a
27
+ form helper may hard-code `text-start`. */
28
+ .flow-card .form-label, .flow-card .form-control, .flow-card .invalid-feedback { text-align: center }
29
+ /* The same edge the card wears, rather than Bootstrap's fainter default. */
30
+ .flow-card .dialog { --bs-dialog-border-color: var(--bs-primary-border) }
31
+
32
+ /* A card holding a chat takes the rest of the window and scrolls the thread inside itself, so
33
+ the line at its head, the field at its foot and the way out under the card stay in sight
34
+ however long the thread gets. `flex: none` before the height: the column is a flex item of
35
+ `main`, and `flex: 1` would size it to its content and let the page grow and scroll instead.
36
+ `min-height: 0` down the chain, since a flex item will not shrink below its content without
37
+ it. The 2rem is the layout's `py-4`, the gutter above and below `main`. */
38
+ .flow:has(.chat-thread) { flex: none; height: calc(100svh - 2rem) }
39
+ .flow-card:has(.chat-thread) { display: flex; flex-direction: column; flex: 1; min-height: 15rem }
40
+ .flow-card .chat-thread { display: flex; flex-direction: column; flex: 1; min-height: 0 }
41
+ .flow-card .chat { flex: 1; min-height: 0; overflow-y: auto }
42
+ .flow-card .chat-ask { text-align: center }
43
+ /* On a phone the card is the page: it breaks out of the gutter with `margin-inline` rather
44
+ than a width, so it stays in the flow, and loses the side border and the corners with it. */
45
+ @media (width < 768px) {
46
+ .flow-card:has(.chat-thread) { width: auto; margin-inline: calc(50% - 50vw); padding-inline: calc(50vw - 50%); border-inline: 0; border-radius: 0 }
47
+ }
@@ -0,0 +1,186 @@
1
+ /* Bootstrap Icons, cut down to the glyphs art/icons.txt names. Written by
2
+ build-icons-font.mjs — edit the list, not this file. */
3
+ @font-face{font-display:block;font-family:bootstrap-icons;
4
+ src:url("../../fonts/bootstrap-icons.woff2") format("woff2")}
5
+ .bi::before,[class*=" bi-"]::before,[class^=bi-]::before{display:inline-block;font-family:bootstrap-icons!important;font-style:normal;font-weight:400!important;font-variant:normal;text-transform:none;line-height:1;vertical-align:-.125em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
6
+ .bi-airplane::before{content:"\f7cd"}
7
+ .bi-alarm::before{content:"\f102"}
8
+ .bi-archive::before{content:"\f10d"}
9
+ .bi-arrow-clockwise::before{content:"\f116"}
10
+ .bi-arrow-counterclockwise::before{content:"\f117"}
11
+ .bi-arrow-down::before{content:"\f128"}
12
+ .bi-arrow-left::before{content:"\f12f"}
13
+ .bi-arrow-right::before{content:"\f138"}
14
+ .bi-arrow-up::before{content:"\f148"}
15
+ .bi-arrows-angle-contract::before{content:"\f149"}
16
+ .bi-arrows-angle-expand::before{content:"\f14a"}
17
+ .bi-award::before{content:"\f154"}
18
+ .bi-bag::before{content:"\f179"}
19
+ .bi-bank::before{content:"\f62e"}
20
+ .bi-bar-chart::before{content:"\f17e"}
21
+ .bi-battery::before{content:"\f188"}
22
+ .bi-bell::before{content:"\f18a"}
23
+ .bi-bicycle::before{content:"\f18d"}
24
+ .bi-book::before{content:"\f194"}
25
+ .bi-bookmark::before{content:"\f1a2"}
26
+ .bi-bookmark-fill::before{content:"\f199"}
27
+ .bi-box::before{content:"\f1c8"}
28
+ .bi-box-arrow-in-right::before{content:"\f1be"}
29
+ .bi-box-arrow-right::before{content:"\f1c3"}
30
+ .bi-briefcase::before{content:"\f1cc"}
31
+ .bi-brush::before{content:"\f1d8"}
32
+ .bi-building::before{content:"\f1dd"}
33
+ .bi-bus-front::before{content:"\f87f"}
34
+ .bi-c-circle::before{content:"\f7db"}
35
+ .bi-calendar::before{content:"\f1f6"}
36
+ .bi-calendar-check::before{content:"\f1e2"}
37
+ .bi-camera::before{content:"\f220"}
38
+ .bi-camera-video::before{content:"\f21f"}
39
+ .bi-car-front::before{content:"\f7e1"}
40
+ .bi-card-checklist::before{content:"\f224"}
41
+ .bi-cart::before{content:"\f242"}
42
+ .bi-cash::before{content:"\f247"}
43
+ .bi-chat::before{content:"\f268"}
44
+ .bi-check::before{content:"\f26e"}
45
+ .bi-check-circle::before{content:"\f26b"}
46
+ .bi-check-circle-fill::before{content:"\f26a"}
47
+ .bi-check2-square::before{content:"\f271"}
48
+ .bi-chevron-down::before{content:"\f282"}
49
+ .bi-chevron-left::before{content:"\f284"}
50
+ .bi-chevron-right::before{content:"\f285"}
51
+ .bi-chevron-up::before{content:"\f286"}
52
+ .bi-circle::before{content:"\f28a"}
53
+ .bi-clipboard::before{content:"\f290"}
54
+ .bi-clock::before{content:"\f293"}
55
+ .bi-cloud::before{content:"\f2c1"}
56
+ .bi-cloud-rain::before{content:"\f2b6"}
57
+ .bi-collection-play::before{content:"\f2cb"}
58
+ .bi-compass::before{content:"\f2d1"}
59
+ .bi-copy::before{content:"\f759"}
60
+ .bi-credit-card::before{content:"\f2dc"}
61
+ .bi-dash::before{content:"\f2ea"}
62
+ .bi-display::before{content:"\f302"}
63
+ .bi-download::before{content:"\f30a"}
64
+ .bi-droplet::before{content:"\f30d"}
65
+ .bi-emoji-smile::before{content:"\f327"}
66
+ .bi-envelope::before{content:"\f32f"}
67
+ .bi-exclamation-triangle::before{content:"\f33b"}
68
+ .bi-eye::before{content:"\f341"}
69
+ .bi-eye-slash::before{content:"\f340"}
70
+ .bi-file-earmark::before{content:"\f392"}
71
+ .bi-file-earmark-text::before{content:"\f38b"}
72
+ .bi-fingerprint::before{content:"\f671"}
73
+ .bi-fire::before{content:"\f7f6"}
74
+ .bi-flag::before{content:"\f3cc"}
75
+ .bi-folder::before{content:"\f3d7"}
76
+ .bi-funnel::before{content:"\f3e1"}
77
+ .bi-gear::before{content:"\f3e5"}
78
+ .bi-geo::before{content:"\f3ea"}
79
+ .bi-geo-alt::before{content:"\f3e8"}
80
+ .bi-gift::before{content:"\f3ec"}
81
+ .bi-globe::before{content:"\f3ee"}
82
+ .bi-graph-up::before{content:"\f3f2"}
83
+ .bi-grid::before{content:"\f3fc"}
84
+ .bi-grip-vertical::before{content:"\f3fe"}
85
+ .bi-hammer::before{content:"\f3ff"}
86
+ .bi-hand-thumbs-down::before{content:"\f405"}
87
+ .bi-hand-thumbs-up::before{content:"\f407"}
88
+ .bi-hdd-rack::before{content:"\f40f"}
89
+ .bi-headphones::before{content:"\f413"}
90
+ .bi-heart::before{content:"\f417"}
91
+ .bi-hospital::before{content:"\f774"}
92
+ .bi-hourglass::before{content:"\f421"}
93
+ .bi-house::before{content:"\f425"}
94
+ .bi-image::before{content:"\f42a"}
95
+ .bi-inbox::before{content:"\f42d"}
96
+ .bi-infinity::before{content:"\f69e"}
97
+ .bi-info-circle::before{content:"\f431"}
98
+ .bi-key::before{content:"\f44f"}
99
+ .bi-keyboard::before{content:"\f451"}
100
+ .bi-laptop::before{content:"\f456"}
101
+ .bi-layout-sidebar::before{content:"\f45f"}
102
+ .bi-lightbulb::before{content:"\f46b"}
103
+ .bi-lightning::before{content:"\f46f"}
104
+ .bi-link::before{content:"\f471"}
105
+ .bi-list::before{content:"\f479"}
106
+ .bi-list-ul::before{content:"\f478"}
107
+ .bi-lock::before{content:"\f47b"}
108
+ .bi-magic::before{content:"\f675"}
109
+ .bi-map::before{content:"\f47f"}
110
+ .bi-megaphone::before{content:"\f484"}
111
+ .bi-mic::before{content:"\f490"}
112
+ .bi-moon::before{content:"\f497"}
113
+ .bi-moon-fill::before{content:"\f494"}
114
+ .bi-music-note::before{content:"\f4a0"}
115
+ .bi-palette::before{content:"\f4b1"}
116
+ .bi-paperclip::before{content:"\f4b3"}
117
+ .bi-pause::before{content:"\f4c4"}
118
+ .bi-pen::before{content:"\f4c8"}
119
+ .bi-pencil::before{content:"\f4cb"}
120
+ .bi-people::before{content:"\f4d0"}
121
+ .bi-percent::before{content:"\f4d1"}
122
+ .bi-person::before{content:"\f4e1"}
123
+ .bi-person-circle::before{content:"\f4d7"}
124
+ .bi-person-dash::before{content:"\f4d9"}
125
+ .bi-person-plus::before{content:"\f4dd"}
126
+ .bi-person-rolodex::before{content:"\f676"}
127
+ .bi-person-vcard::before{content:"\f8c9"}
128
+ .bi-phone::before{content:"\f4e7"}
129
+ .bi-pie-chart::before{content:"\f4e9"}
130
+ .bi-play::before{content:"\f4f5"}
131
+ .bi-plus::before{content:"\f4fe"}
132
+ .bi-power::before{content:"\f4ff"}
133
+ .bi-printer::before{content:"\f501"}
134
+ .bi-puzzle::before{content:"\f503"}
135
+ .bi-qr-code::before{content:"\f6ae"}
136
+ .bi-question-circle::before{content:"\f505"}
137
+ .bi-receipt::before{content:"\f50f"}
138
+ .bi-repeat::before{content:"\f813"}
139
+ .bi-reply::before{content:"\f520"}
140
+ .bi-rulers::before{content:"\f523"}
141
+ .bi-scissors::before{content:"\f528"}
142
+ .bi-search::before{content:"\f52a"}
143
+ .bi-send::before{content:"\f6c0"}
144
+ .bi-share::before{content:"\f52e"}
145
+ .bi-shield::before{content:"\f53f"}
146
+ .bi-shop::before{content:"\f543"}
147
+ .bi-shuffle::before{content:"\f544"}
148
+ .bi-signpost-split::before{content:"\f549"}
149
+ .bi-skip-forward::before{content:"\f55e"}
150
+ .bi-sliders::before{content:"\f56b"}
151
+ .bi-snow::before{content:"\f56d"}
152
+ .bi-sort-down::before{content:"\f575"}
153
+ .bi-soundwave::before{content:"\f57c"}
154
+ .bi-speedometer::before{content:"\f57f"}
155
+ .bi-square::before{content:"\f584"}
156
+ .bi-star::before{content:"\f588"}
157
+ .bi-sticky::before{content:"\f58d"}
158
+ .bi-stop::before{content:"\f593"}
159
+ .bi-stopwatch::before{content:"\f597"}
160
+ .bi-sun::before{content:"\f5a2"}
161
+ .bi-sun-fill::before{content:"\f5a1"}
162
+ .bi-table::before{content:"\f5aa"}
163
+ .bi-tag::before{content:"\f5b0"}
164
+ .bi-telephone::before{content:"\f5c1"}
165
+ .bi-terminal::before{content:"\f5c3"}
166
+ .bi-three-dots::before{content:"\f5d4"}
167
+ .bi-ticket::before{content:"\f6cb"}
168
+ .bi-tools::before{content:"\f5db"}
169
+ .bi-train-front::before{content:"\f81d"}
170
+ .bi-trash::before{content:"\f5de"}
171
+ .bi-tree::before{content:"\f5e2"}
172
+ .bi-trophy::before{content:"\f5e7"}
173
+ .bi-truck::before{content:"\f5ea"}
174
+ .bi-unlock::before{content:"\f600"}
175
+ .bi-upc::before{content:"\f602"}
176
+ .bi-upload::before{content:"\f603"}
177
+ .bi-volume-up::before{content:"\f611"}
178
+ .bi-wallet::before{content:"\f614"}
179
+ .bi-wifi::before{content:"\f61c"}
180
+ .bi-window::before{content:"\f620"}
181
+ .bi-wrench::before{content:"\f621"}
182
+ .bi-x::before{content:"\f62a"}
183
+ .bi-x-circle::before{content:"\f623"}
184
+ .bi-x-lg::before{content:"\f659"}
185
+ .bi-zoom-in::before{content:"\f62c"}
186
+ .bi-zoom-out::before{content:"\f62d"}
@@ -0,0 +1,58 @@
1
+ /* The logo `houseaccount_logo` draws — a mark beside its name — and the typeface it is set in, drawn the way the pages under
2
+ `public/page` draw it, so a page served by Rails and a page served while Rails is
3
+ down carry the same mark. */
4
+
5
+ /* Quicksand 500, served from here rather than from Google: one origin fewer to reach, and
6
+ the same immutable cache every other font here sits in. Google's own three ranges, so a
7
+ browser fetches the Latin file alone for an English page. OFL, see vendor/fonts. */
8
+ @font-face {
9
+ font-family: 'Quicksand';
10
+ font-style: normal;
11
+ font-weight: 500;
12
+ font-display: swap;
13
+ src: url('../../../vendor/fonts/quicksand-500-vietnamese.woff2') format('woff2');
14
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
15
+ }
16
+ @font-face {
17
+ font-family: 'Quicksand';
18
+ font-style: normal;
19
+ font-weight: 500;
20
+ font-display: swap;
21
+ src: url('../../../vendor/fonts/quicksand-500-latin-ext.woff2') format('woff2');
22
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
23
+ }
24
+ @font-face {
25
+ font-family: 'Quicksand';
26
+ font-style: normal;
27
+ font-weight: 500;
28
+ font-display: swap;
29
+ src: url('../../../vendor/fonts/quicksand-500-latin.woff2') format('woff2');
30
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
31
+ }
32
+
33
+ /* The brand typeface, for the lockup and for a heading that stands beside it. */
34
+ .quicksand { font-family: 'Quicksand', 'Trebuchet MS', sans-serif; font-weight: 500; letter-spacing: 0.01em }
35
+
36
+ .lockup {
37
+ white-space: nowrap;
38
+ line-height: 1;
39
+ /* Its own two blues rather than the theme's: the mark reads the same in every app. The
40
+ mark is drawn in the light one and the name set in the dark; `house` alone takes the
41
+ mark's. */
42
+ color: light-dark(#1B5288, #9cc0ec);
43
+ text-decoration: none;
44
+ }
45
+ .lockup .house { color: #3b8ae8 }
46
+ /* Under the cursor the whole logo is one blue, the name, `house` and the mark's strokes
47
+ alike — which says the whole word is the link, not the name alone. A presentation
48
+ attribute loses to a rule, which is what lets the stroke written into every drawing
49
+ give way here. */
50
+ .lockup:hover { color: light-dark(#3b8ae8, #bcd6f4) }
51
+ .lockup:hover .house { color: inherit }
52
+ .lockup:hover svg [stroke] { stroke: currentColor }
53
+
54
+ /* The mark sits on the text baseline, so its box is nudged rather than aligned: the size and
55
+ the nudge are the mark's own, written by the helper from `HouseAccount::MARKS`, and the gap
56
+ to the word is the same for every mark. */
57
+ .lockup { font-size: 20px }
58
+ .lockup svg { position: relative; vertical-align: baseline; margin-right: 0.29em }
@@ -0,0 +1,3 @@
1
+ /* A map takes the height a table of a page would: tall enough to read the places
2
+ on, and short enough to leave the footer under it in view. */
3
+ .recourse-map { height: 60vh; }
@@ -0,0 +1,34 @@
1
+ /* The field a 6-digit code is typed into: one real field, drawn by Bootstrap as six slots.
2
+ `pin_field` draws the markup and the `otp` controller draws the slots. */
3
+
4
+ /* Bootstrap's OTP container is a flex row, so it has to be told to center where its card is
5
+ centered and to let the message drop onto a line of its own under the slots. `min-width: 0`,
6
+ or the grid item will not shrink below the width its slots come to, which is the whole
7
+ row hanging off the side of the card before the slots are asked to fit. */
8
+ .otp { justify-content: center; flex-wrap: wrap; min-width: 0 }
9
+ /* Six slots at a fixed width plus the five gaps between them come to 328 pixels, and a
10
+ phone's card holds 327 — so the last slot sat outside it. Sized off the row instead, they
11
+ share whatever there is and never exceed the width they were drawn at. */
12
+ .otp-slots { display: flex; justify-content: center; width: 100% }
13
+ .otp-slot { flex: 1 1 0; min-width: 0; max-width: var(--bs-otp-size) }
14
+ .otp .invalid-feedback { order: 1; flex-basis: 100%; margin-top: 0.5rem }
15
+
16
+ /* .form-control.is-invalid would outrank .otp-input on specificity and draw one red box
17
+ across all six slots; the field takes no such class, and Bootstrap reddens the slots
18
+ themselves from the container. */
19
+ .otp .otp-input.is-invalid { border: 0; box-shadow: none }
20
+
21
+ /* Bootstrap builds the six slots in JavaScript, so one box is what the server can send and
22
+ what the reader sees until they land. Held back for that moment — but on a class the
23
+ controller itself sets, so a controller that never runs leaves a field somebody can still
24
+ type a code into rather than an invisible one. */
25
+ .otp-drawing:not(.otp-rendered) .otp-input { visibility: hidden }
26
+
27
+ /* Bootstrap lays the real field transparently over the slots, so an autofilled one painted
28
+ yellow paints over them. The text stays invisible, and the fill is deferred by longer than
29
+ anybody's session rather than overridden, which is the only thing that holds it off. */
30
+ .otp-input:-webkit-autofill,
31
+ .otp-input:-webkit-autofill:focus {
32
+ -webkit-text-fill-color: transparent;
33
+ transition: background-color 0s 600000s;
34
+ }
@@ -0,0 +1,60 @@
1
+ /* The search and its filters take the width the breadcrumb and the buttons
2
+ leave, and give it back as the viewport narrows: the box shrinks to a floor
3
+ that still shows a word, and below 768px the row is allowed to wrap. */
4
+ .recourse-search { flex: 1 1 auto; max-width: 48rem; }
5
+ /* The box grows and the filters do not, so the field someone types into is the
6
+ widest thing in the row on a large viewport. A toggle is `width: 100%`, which
7
+ as a flex item would otherwise mean the whole form. */
8
+ .recourse-search-box { flex: 2 1 16rem; min-width: 8rem; }
9
+
10
+ .recourse-search .combobox-toggle { flex: 0 1 10rem; }
11
+ /* The per-page switch is a word in the sentence beside it rather than a control
12
+ next to one, so it takes the line's height and not `.btn`'s own minimum —
13
+ which would leave the row under every table taller than the words in it. */
14
+ .recourse-limit { min-height: 0; }
15
+ /* The bookmark square is an icon in a row of text, not a control in a form, so it
16
+ takes the line's height too: `.btn`'s minimum made every row with one taller
17
+ than the rows without. */
18
+ .recourse-bookmark { min-height: 0; }
19
+ /* Stacked, each control is a row of its own rather than a fragment of one:
20
+ there is no second control beside it to share the line with. */
21
+ @media (width < 768px) {
22
+ /* The room the breadcrumb leaves, and never more: a form sized by its content
23
+ widened with the words a picked option put on a toggle, and wrapped under
24
+ the crumbs. A basis of nothing and leave to shrink hold it in its row. */
25
+ .recourse-search { flex: 1 1 0; min-width: 0; max-width: none; }
26
+ .recourse-search-box,
27
+ .recourse-search .combobox-toggle { flex: 1 1 100%; min-width: 0; }
28
+ /* Safari zooms in on a field whose text is under 16px, an absolute figure: the
29
+ small controls are 14px, and so is the body on a phone, so the controls say
30
+ 16px themselves and focusing one moves nothing. */
31
+ .recourse-search .form-control { font-size: 16px; }
32
+ /* A page under a record has a trail two or three crumbs long, and what sat beside
33
+ the trail — the search, its filters, a button — was squeezed into what those
34
+ left: `Filter by na`. Under such a trail each takes a row of its own, the form
35
+ the whole width and every control in it a row in turn, and the margin that set
36
+ a button apart from the trail goes, there being no trail beside it. An index at
37
+ the top keeps its form beside its one crumb, where it fits. */
38
+ .recourse-trailed > :not(nav) { flex: 0 0 100%; margin-inline-start: 0; }
39
+ }
40
+
41
+ /* The button that empties a combobox search sits inside the field it clears. */
42
+ .combobox-search { position: relative; }
43
+ .combobox-search-input { padding-inline-end: 2rem; }
44
+ /* `:not(.d-none)`, because v6's display utilities carry no `!important` and
45
+ this rule is later in the cascade: a plain `display` here would beat the
46
+ `.d-none` the button is hidden with, and show an X over an empty field. */
47
+ .combobox-search-clear:not(.d-none) { display: flex; }
48
+ .combobox-search-clear {
49
+ position: absolute;
50
+ inset-block-start: 50%;
51
+ inset-inline-end: calc(var(--bs-menu-padding-x) + .35rem);
52
+ transform: translateY(-50%);
53
+ padding: .15rem;
54
+ border: 0;
55
+ border-radius: var(--bs-radius-5);
56
+ background: transparent;
57
+ color: var(--bs-fg-2);
58
+ line-height: 1;
59
+ }
60
+ .combobox-search-clear:hover { background-color: var(--bs-bg-1); color: var(--bs-fg-body); }