bible270 1.8.4 → 1.9.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +18 -0
- data/app/controllers/bible270/admin_controller.rb +1 -1
- data/app/controllers/bible270/profiles_controller.rb +16 -3
- data/app/controllers/bible270/readers_controller.rb +13 -2
- data/app/models/bible270/comment.rb +8 -1
- data/app/models/bible270/reader.rb +25 -0
- data/app/views/bible270/admin/show.html.erb +3 -1
- data/app/views/bible270/profiles/edit.html.erb +14 -0
- data/app/views/bible270/readers/index.html.erb +1 -0
- data/app/views/bible270/readers/progress.html.erb +3 -1
- data/app/views/bible270/readers/show.html.erb +4 -1
- data/app/views/bible270/shared/_reflections.html.erb +29 -4
- data/app/views/bible270/shared/_styles.html.erb +79 -34
- data/app/views/layouts/bible270/application.html.erb +1 -1
- data/db/migrate/20260101000023_add_display_mode_to_bible270_readers.rb +7 -0
- data/lib/bible270/migration_reconciler.rb +11 -0
- data/lib/bible270/reader_progress_report.rb +2 -13
- data/lib/bible270/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 251f07d9a39f657788b627258c3cf78a260f651be7e998267a3cd7028cd6e172
|
|
4
|
+
data.tar.gz: 49fc0b718a2d8625d18ba357dff2af2891de59a8daf4a113a566737f2afd6fe6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb3c3b3246e339330a180f670af37a8d558a01c7a7b4a9fb71087aecf77ecc01508fbcb329e389339a8531dd7f08a18e9b6b37a05a3d1f9bb945d72629521502
|
|
7
|
+
data.tar.gz: 83023e4b01da0ce5a7b152801a5b6de52b1409b3becb172e11ab05b69dad6a35bc0095e486c446ffaae076ee97ad0a5d886a813f5d68bb698e37d9c6400f485a
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,9 @@ All notable changes to bible270. Format follows [Keep a Changelog](https://keepa
|
|
|
7
7
|
### Changed
|
|
8
8
|
|
|
9
9
|
- keep Proverbs 7 whole and divide Proverbs 10 and 16 into four readings
|
|
10
|
+
- show likes and replies on My Progress and reader pages, with admin edit links on reader pages
|
|
11
|
+
- show each reader's pace beside their completed-day count for admins on Community
|
|
12
|
+
- add edit, like, and reply controls to reflections on Admin reader pages
|
|
10
13
|
|
|
11
14
|
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v1.8.1...main)**
|
|
12
15
|
|
data/README.md
CHANGED
|
@@ -368,6 +368,24 @@ Psalm 35: []
|
|
|
368
368
|
Psalm 78: [20, 39, 55]
|
|
369
369
|
```
|
|
370
370
|
|
|
371
|
+
## Display preference
|
|
372
|
+
|
|
373
|
+
Readers can choose **Light**, **Dark**, or **System** under **Profile → Display**, then save.
|
|
374
|
+
The preference is stored with their account and applies throughout Bible270 on every device.
|
|
375
|
+
System is the default for existing and new readers, follows the device's current appearance,
|
|
376
|
+
and responds to changes without reloading. Signed-out visitors also use System.
|
|
377
|
+
The completion dove retains its bright colors in either theme.
|
|
378
|
+
|
|
379
|
+
Upgrading hosts must install and run migration `20260101000023`:
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
bin/rails bible270:install:migrations
|
|
383
|
+
bin/rails db:migrate
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Custom engine layouts must render the shared styles and set `data-display-mode` on the
|
|
387
|
+
`.b270` element to `current_reader&.effective_display_mode || 'system'` to honor the preference.
|
|
388
|
+
|
|
371
389
|
## Start dates
|
|
372
390
|
|
|
373
391
|
The plan runs **undated** (day numbers only) or **dated** (days mapped onto a calendar):
|
|
@@ -97,7 +97,7 @@ module Bible270
|
|
|
97
97
|
@comment_notifications_available = comment_notifications_available_for?(@reader)
|
|
98
98
|
@completion_dove_preference_available = completion_dove_available_for(@reader)
|
|
99
99
|
@days_completed = @reader.days_completed
|
|
100
|
-
@comments = @reader.comments.order(created_at: :desc).limit(50)
|
|
100
|
+
@comments = @reader.comments.includes(likes: :reader).order(created_at: :desc).limit(50)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
# ---- reflections ------------------------------------------------------
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Bible270
|
|
4
|
-
# A reader editing their own
|
|
5
|
-
# change. Everything else about a reader comes from how they signed in.
|
|
4
|
+
# A reader editing their own details and preferences.
|
|
6
5
|
class ProfilesController < ApplicationController
|
|
7
6
|
before_action :require_signed_in_reader
|
|
8
7
|
|
|
9
8
|
def edit
|
|
10
9
|
@names = current_reader.suggested_names
|
|
10
|
+
@display_mode = current_reader.effective_display_mode
|
|
11
11
|
load_comment_notification_state
|
|
12
12
|
load_daily_reminder_state
|
|
13
13
|
end
|
|
@@ -16,7 +16,8 @@ module Bible270
|
|
|
16
16
|
@comment_notifications_available = comment_notifications_available?
|
|
17
17
|
@comment_notification_level = requested_comment_notification_level
|
|
18
18
|
@daily_reminders_available = daily_reminders_available?
|
|
19
|
-
|
|
19
|
+
@display_mode = params.fetch(:display_mode, current_reader.effective_display_mode).to_s
|
|
20
|
+
problems = [comment_notification_problem, display_mode_problem].compact
|
|
20
21
|
problems << 'both a first and last name' unless current_reader.update_names(params[:first_name],
|
|
21
22
|
params[:last_name])
|
|
22
23
|
if params[:bible_version].present? && !current_reader.update_bible_version(params[:bible_version])
|
|
@@ -35,6 +36,7 @@ module Bible270
|
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
if problems.empty?
|
|
39
|
+
save_display_mode
|
|
38
40
|
current_reader.update_comment_notification_level!(@comment_notification_level) if @comment_notifications_available
|
|
39
41
|
if @daily_reminders_available
|
|
40
42
|
current_reader.update!(daily_reminders: requested_daily_reminders,
|
|
@@ -57,6 +59,17 @@ module Bible270
|
|
|
57
59
|
|
|
58
60
|
private
|
|
59
61
|
|
|
62
|
+
def display_mode_problem
|
|
63
|
+
return unless Reader.display_mode_column?
|
|
64
|
+
return if Reader::DISPLAY_MODES.include?(@display_mode)
|
|
65
|
+
|
|
66
|
+
'a display preference from the list'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def save_display_mode
|
|
70
|
+
current_reader.update!(display_mode: @display_mode) if Reader.display_mode_column?
|
|
71
|
+
end
|
|
72
|
+
|
|
60
73
|
def load_comment_notification_state
|
|
61
74
|
@comment_notifications_available = comment_notifications_available?
|
|
62
75
|
@comment_notification_level = current_reader.comment_notification_level
|
|
@@ -17,7 +17,7 @@ module Bible270
|
|
|
17
17
|
def progress
|
|
18
18
|
@reader = current_reader
|
|
19
19
|
# Defaults keep the open visitor page simple and make every view input explicit.
|
|
20
|
-
@recent_comments = @reader ? @reader
|
|
20
|
+
@recent_comments = @reader ? recent_reflections(@reader, limit: 5) : []
|
|
21
21
|
@partial_day_tasks = []
|
|
22
22
|
@remaining_partial_days = 0
|
|
23
23
|
return unless @reader
|
|
@@ -34,7 +34,7 @@ module Bible270
|
|
|
34
34
|
redirect_to(community_path, alert: 'Reader not found.') and return unless @reader
|
|
35
35
|
|
|
36
36
|
@days_completed = @reader.days_completed
|
|
37
|
-
@recent_comments = @reader
|
|
37
|
+
@recent_comments = recent_reflections(@reader, limit: 20)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def mention_suggestions
|
|
@@ -43,5 +43,16 @@ module Bible270
|
|
|
43
43
|
response.headers['Cache-Control'] = 'private, no-store'
|
|
44
44
|
render json: { suggestions: Reader.mention_suggestions(params[:q], except: current_reader) }
|
|
45
45
|
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def recent_reflections(reader, limit:)
|
|
50
|
+
comments = reader.comments.recent
|
|
51
|
+
.includes(:reader, { likes: :reader }, { replies: [:reader, { likes: :reader }] })
|
|
52
|
+
.limit(limit).to_a
|
|
53
|
+
ids = comments.map(&:id)
|
|
54
|
+
# A reader's own replies already appear beneath any listed parent reflection.
|
|
55
|
+
comments.reject { |comment| ids.include?(comment.parent_id) }
|
|
56
|
+
end
|
|
46
57
|
end
|
|
47
58
|
end
|
|
@@ -150,7 +150,14 @@ module Bible270
|
|
|
150
150
|
def edited? = updated_at - created_at > 1
|
|
151
151
|
|
|
152
152
|
# Visible replies, oldest first: a conversation reads forwards.
|
|
153
|
-
def visible_replies
|
|
153
|
+
def visible_replies
|
|
154
|
+
if replies.loaded?
|
|
155
|
+
replies.select(&:approved?).sort_by { |reply| [reply.created_at, reply.id] }
|
|
156
|
+
else
|
|
157
|
+
replies.approved.order(created_at: :asc, id: :asc)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
154
161
|
def moderated? = moderated_at.present?
|
|
155
162
|
|
|
156
163
|
private
|
|
@@ -27,6 +27,8 @@ module Bible270
|
|
|
27
27
|
|
|
28
28
|
PASSAGE_SOURCES = %w[bible_com bible_gateway blue_letter_bible].freeze
|
|
29
29
|
DEFAULT_PASSAGE_SOURCE = 'bible_com'
|
|
30
|
+
DISPLAY_MODES = %w[light dark system].freeze
|
|
31
|
+
DEFAULT_DISPLAY_MODE = 'system'
|
|
30
32
|
DAILY_REMINDER_TIME_FORMAT = %r{\A(?:[01]\d|2[0-3]):[0-5]\d\z}
|
|
31
33
|
DAILY_REMINDER_MINUTES = %w[00 15 30 45].freeze
|
|
32
34
|
DAILY_REMINDER_COLUMNS = %w[daily_reminders daily_reminder_time last_daily_reminder_sent_on].freeze
|
|
@@ -39,6 +41,7 @@ module Bible270
|
|
|
39
41
|
validates :display_name, presence: true
|
|
40
42
|
validates :uid, uniqueness: { scope: :provider }, allow_nil: true
|
|
41
43
|
validates :passage_source, inclusion: { in: PASSAGE_SOURCES }
|
|
44
|
+
validates :display_mode, inclusion: { in: DISPLAY_MODES }, if: -> { has_attribute?(:display_mode) }
|
|
42
45
|
validates :daily_reminder_time,
|
|
43
46
|
format: { with: DAILY_REMINDER_TIME_FORMAT, message: 'must use 24-hour HH:MM format' },
|
|
44
47
|
if: -> { self.class.daily_reminder_columns? }
|
|
@@ -55,6 +58,17 @@ module Bible270
|
|
|
55
58
|
false
|
|
56
59
|
end
|
|
57
60
|
|
|
61
|
+
def self.display_mode_column?
|
|
62
|
+
column_names.include?('display_mode')
|
|
63
|
+
rescue ActiveRecord::StatementInvalid
|
|
64
|
+
false
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def effective_display_mode
|
|
68
|
+
value = self[:display_mode] if has_attribute?(:display_mode)
|
|
69
|
+
DISPLAY_MODES.include?(value) ? value : DEFAULT_DISPLAY_MODE
|
|
70
|
+
end
|
|
71
|
+
|
|
58
72
|
def self.valid_daily_reminder_time?(value)
|
|
59
73
|
value.to_s.match?(DAILY_REMINDER_TIME_FORMAT)
|
|
60
74
|
end
|
|
@@ -643,6 +657,17 @@ module Bible270
|
|
|
643
657
|
Plan.day_for(Bible270.today, effective_start_date)
|
|
644
658
|
end
|
|
645
659
|
|
|
660
|
+
def progress_status(completed_days: days_completed)
|
|
661
|
+
today = calendar_day
|
|
662
|
+
return 'undated' unless today
|
|
663
|
+
|
|
664
|
+
difference = completed_days - today
|
|
665
|
+
return 'on track' if difference.zero?
|
|
666
|
+
|
|
667
|
+
amount = difference.abs
|
|
668
|
+
"#{amount} #{amount == 1 ? 'day' : 'days'} #{difference.positive? ? 'ahead' : 'behind'}"
|
|
669
|
+
end
|
|
670
|
+
|
|
646
671
|
# Raw, unclamped — lets callers distinguish "not started yet" / "finished".
|
|
647
672
|
# The plan day that today actually is, or nil when today falls outside the
|
|
648
673
|
# plan's window. calendar_day clamps, so before the start date it reports day
|
|
@@ -115,7 +115,8 @@
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
<div class="b270-panel">
|
|
118
|
-
<%= render "bible270/shared/
|
|
118
|
+
<%= render "bible270/shared/interaction_status" %>
|
|
119
|
+
<%= render "bible270/shared/reflections", comments: @comments, moderate: true, editable: true, actions: true %>
|
|
119
120
|
</div>
|
|
120
121
|
|
|
121
122
|
<div class="b270-panel">
|
|
@@ -172,3 +173,4 @@
|
|
|
172
173
|
</div>
|
|
173
174
|
|
|
174
175
|
<p style="margin-top:18px"><%= link_to '← All readers', admin_path %></p>
|
|
176
|
+
<%= render "bible270/shared/interaction_ui" %>
|
|
@@ -38,6 +38,20 @@
|
|
|
38
38
|
</label>
|
|
39
39
|
<%= render "bible270/shared/passage_source", reader: current_reader %>
|
|
40
40
|
|
|
41
|
+
<% if Bible270::Reader.display_mode_column? %>
|
|
42
|
+
<label class="b270-field">
|
|
43
|
+
<span>Display</span>
|
|
44
|
+
<%= select_tag :display_mode,
|
|
45
|
+
options_for_select([["Light", "light"], ["Dark", "dark"], ["System", "system"]], @display_mode),
|
|
46
|
+
class: "b270-select", aria: { describedby: "b270-display-hint" } %>
|
|
47
|
+
<span id="b270-display-hint" class="b270-hint" style="margin:0">System follows your device's light or dark setting.</span>
|
|
48
|
+
</label>
|
|
49
|
+
<% else %>
|
|
50
|
+
<p class="b270-flash alert" style="margin:0">
|
|
51
|
+
Display settings are temporarily unavailable while this installation finishes a database update.
|
|
52
|
+
</p>
|
|
53
|
+
<% end %>
|
|
54
|
+
|
|
41
55
|
<% if @comment_notifications_available %>
|
|
42
56
|
<%= render "bible270/shared/comment_notification_choices",
|
|
43
57
|
level: @comment_notification_level,
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
joined <%= Bible270.local_time(r.created_at).strftime("%b %-d %Y") %><% if @show_admin_progress %>
|
|
15
15
|
· <%= link_to "#{@days_completed[r.id]} of #{Bible270::Plan::DAYS} days complete",
|
|
16
16
|
admin_reader_path(r), class: "b270-admin-progress" %>
|
|
17
|
+
· <%= r.progress_status(completed_days: @days_completed[r.id].to_i) %>
|
|
17
18
|
<% end %>
|
|
18
19
|
</div>
|
|
19
20
|
</div>
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
|
|
13
13
|
<% if @reader %>
|
|
14
14
|
<div class="b270-panel">
|
|
15
|
+
<%= render "bible270/shared/interaction_status" %>
|
|
15
16
|
<%= render "bible270/shared/reflections", comments: @recent_comments,
|
|
16
|
-
title: "Your recent reflections", editable: true,
|
|
17
|
+
title: "Your recent reflections", editable: true, conversations: true,
|
|
17
18
|
empty_text: "You have not written any reflections yet." %>
|
|
18
19
|
</div>
|
|
20
|
+
<%= render "bible270/shared/interaction_ui" %>
|
|
19
21
|
<% end %>
|
|
@@ -16,8 +16,11 @@
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
18
|
<div class="b270-panel">
|
|
19
|
+
<%= render "bible270/shared/interaction_status" %>
|
|
19
20
|
<%= render "bible270/shared/reflections", comments: @recent_comments,
|
|
20
|
-
title: "Their reflections", empty_text: "No reflections yet."
|
|
21
|
+
title: "Their reflections", empty_text: "No reflections yet.",
|
|
22
|
+
conversations: true, editable: Bible270.config.admin?(current_reader) %>
|
|
21
23
|
</div>
|
|
22
24
|
|
|
23
25
|
<%= render "bible270/shared/day_index", reader: @reader, expanded: true %>
|
|
26
|
+
<%= render "bible270/shared/interaction_ui" %>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%# locals: (comments:, title: "Their reflections", moderate: false, editable: false, empty_text: "None yet.") %>
|
|
1
|
+
<%# locals: (comments:, title: "Their reflections", moderate: false, editable: false, actions: false, conversations: false, reply: false, empty_text: "None yet.") %>
|
|
2
2
|
<%#
|
|
3
3
|
A reader's reflections. Three views showed near-identical markup: the admin
|
|
4
4
|
page, a reader's public page, and the progress page, differing only in whether
|
|
@@ -11,17 +11,21 @@
|
|
|
11
11
|
No ERB tags in this comment: the first close-tag would end it and the rest
|
|
12
12
|
would become live template code.
|
|
13
13
|
%>
|
|
14
|
-
|
|
14
|
+
<% if title.present? %><h2 class="b270-subhead"><%= title %></h2><% end %>
|
|
15
15
|
|
|
16
16
|
<% if comments.any? %>
|
|
17
17
|
<% comments.each do |comment| %>
|
|
18
|
-
<div class="b270-comment">
|
|
18
|
+
<div class="b270-comment <%= 'b270-reply' if reply %>" id="comment-<%= comment.id %>">
|
|
19
|
+
<% if reply %><%= b270_avatar(comment.reader, size: 36) %><% end %>
|
|
19
20
|
<div style="flex:1">
|
|
20
21
|
<div class="b270-cmeta">
|
|
22
|
+
<% if reply %>
|
|
23
|
+
<strong><%= link_to comment.reader.display_name, reader_path(comment.reader) %></strong> ·
|
|
24
|
+
<% end %>
|
|
21
25
|
<%= link_to "Day #{comment.day}", day_path(comment.day) %>
|
|
22
26
|
· <%= b270_date(comment.created_at) %>
|
|
23
27
|
<% if moderate && comment.hidden? %><span class="b270-badge">Hidden</span><% end %>
|
|
24
|
-
<% if editable && b270_can_edit?(comment) %>
|
|
28
|
+
<% if editable && !comment.hidden? && b270_can_edit?(comment) %>
|
|
25
29
|
<%= link_to "edit", day_path(comment.day, edit: comment.id, anchor: "comment-#{comment.id}"),
|
|
26
30
|
class: "b270-cedit" %>
|
|
27
31
|
<% end %>
|
|
@@ -29,6 +33,27 @@
|
|
|
29
33
|
|
|
30
34
|
<div class="b270-cbody"><%= b270_comment_body(comment) %></div>
|
|
31
35
|
|
|
36
|
+
<% if (actions || conversations) && !comment.hidden? %>
|
|
37
|
+
<div class="b270-cactions">
|
|
38
|
+
<%= render "bible270/likes/likes", comment: comment %>
|
|
39
|
+
<% if !comment.reply? && signed_in? && comment.reader_id != current_reader.id %>
|
|
40
|
+
<%= link_to "Reply", day_path(comment.day, reply_to: comment.id, anchor: "new_comment_form"),
|
|
41
|
+
class: "b270-linkbtn" %>
|
|
42
|
+
<% end %>
|
|
43
|
+
</div>
|
|
44
|
+
<% end %>
|
|
45
|
+
<% if conversations %>
|
|
46
|
+
<% unless comment.reply? %>
|
|
47
|
+
<div class="b270-replies" id="replies-<%= comment.id %>">
|
|
48
|
+
<% replies = comment.visible_replies %>
|
|
49
|
+
<% if replies.any? %>
|
|
50
|
+
<%= render "bible270/shared/reflections", comments: replies, title: nil,
|
|
51
|
+
conversations: true, reply: true, editable: editable %>
|
|
52
|
+
<% end %>
|
|
53
|
+
</div>
|
|
54
|
+
<% end %>
|
|
55
|
+
<% end %>
|
|
56
|
+
|
|
32
57
|
<% if moderate %>
|
|
33
58
|
<div style="display:flex;gap:10px;margin-top:6px">
|
|
34
59
|
<% if comment.hidden? %>
|
|
@@ -3,9 +3,52 @@
|
|
|
3
3
|
--paper:#ece6da; --card:#f4efe6; --ink:#231f18; --muted:#5d564c; --faint:#6e665b;
|
|
4
4
|
--line:#d7cfbf; --line-soft:#e2dbcd; --teal:#2f6a67; --oxblood:#8a2f3b; --gold:#80631d;
|
|
5
5
|
--gold-lite:#80631d; --shadow:0 1px 0 rgba(35,31,24,.04),0 10px 30px -18px rgba(35,31,24,.35);
|
|
6
|
+
--control:#fff; --control-line:#d7cfbf; --task-surface:rgba(255,255,255,.35); --button-hover:#3a352b;
|
|
7
|
+
--on-accent:#fff; --complete-ink:#eef3f2; --complete-dot:#f4efe6;
|
|
8
|
+
--button-surface:#231f18; --button-ink:#ece6da;
|
|
9
|
+
--teal-fill:#2f6a67; --oxblood-fill:#8a2f3b; --gold-fill:#80631d;
|
|
10
|
+
--teal-mark:#2f6a67; --oxblood-mark:#8a2f3b; --gold-mark:#80631d;
|
|
11
|
+
--alert-surface:#f6e4e2; --alert-ink:#7a2b28; --alert-line:#e6c3bf;
|
|
12
|
+
--notice-surface:#e7efe6; --notice-ink:#2f5d34; --notice-line:#c6dcc6;
|
|
13
|
+
color-scheme:light;
|
|
6
14
|
margin:0; background:radial-gradient(120% 80% at 100% 0%,rgba(165,129,44,.06),transparent 55%),var(--paper);
|
|
7
15
|
color:var(--ink); font-family:"Inter",system-ui,sans-serif; line-height:1.2; -webkit-font-smoothing:antialiased;
|
|
8
16
|
}
|
|
17
|
+
/* Explicit light keeps native controls light even when the OS is dark.
|
|
18
|
+
Only system mode follows the media query, including live OS changes. */
|
|
19
|
+
.b270[data-display-mode="light"]{color-scheme:light}
|
|
20
|
+
.b270[data-display-mode="dark"]{
|
|
21
|
+
color-scheme:dark;
|
|
22
|
+
background-image:none;
|
|
23
|
+
--control-line:#827c6e;
|
|
24
|
+
--paper:#39311f; --card:#322714; --ink:#ccc9bd; --muted:#b7b09f; --faint:#a9a28f;
|
|
25
|
+
--line:#42484a; --line-soft:#4b422d; --teal:#80b5ad; --oxblood:#d19199; --gold:#d6be79;
|
|
26
|
+
--gold-lite:#695116; --shadow:0 1px 0 rgba(0,0,0,.1);
|
|
27
|
+
--control:#181b1b; --task-surface:rgba(255,255,255,.025); --button-hover:#292316;
|
|
28
|
+
--on-accent:#ded8c5; --complete-ink:#ded8c5; --complete-dot:#ded8c5;
|
|
29
|
+
--button-surface:#201c12; --button-ink:#ded8c5;
|
|
30
|
+
--teal-fill:#2f5955; --oxblood-fill:#702731; --gold-fill:#695116;
|
|
31
|
+
--teal-mark:#409594; --oxblood-mark:#a73d50; --gold-mark:#b18b2b;
|
|
32
|
+
--alert-surface:#42282a; --alert-ink:#ffc0b8; --alert-line:#a66d68;
|
|
33
|
+
--notice-surface:#25382a; --notice-ink:#b6dfb6; --notice-line:#6d956e;
|
|
34
|
+
}
|
|
35
|
+
@media (prefers-color-scheme:dark){
|
|
36
|
+
.b270[data-display-mode="system"]{
|
|
37
|
+
color-scheme:dark;
|
|
38
|
+
background-image:none;
|
|
39
|
+
--control-line:#827c6e;
|
|
40
|
+
--paper:#39311f; --card:#322714; --ink:#ccc9bd; --muted:#b7b09f; --faint:#a9a28f;
|
|
41
|
+
--line:#42484a; --line-soft:#4b422d; --teal:#80b5ad; --oxblood:#d19199; --gold:#d6be79;
|
|
42
|
+
--gold-lite:#695116; --shadow:0 1px 0 rgba(0,0,0,.1);
|
|
43
|
+
--control:#181b1b; --task-surface:rgba(255,255,255,.025); --button-hover:#292316;
|
|
44
|
+
--on-accent:#ded8c5; --complete-ink:#ded8c5; --complete-dot:#ded8c5;
|
|
45
|
+
--button-surface:#201c12; --button-ink:#ded8c5;
|
|
46
|
+
--teal-fill:#2f5955; --oxblood-fill:#702731; --gold-fill:#695116;
|
|
47
|
+
--teal-mark:#409594; --oxblood-mark:#a73d50; --gold-mark:#b18b2b;
|
|
48
|
+
--alert-surface:#42282a; --alert-ink:#ffc0b8; --alert-line:#a66d68;
|
|
49
|
+
--notice-surface:#25382a; --notice-ink:#b6dfb6; --notice-line:#6d956e;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
9
52
|
.b270 *{box-sizing:border-box}
|
|
10
53
|
/* Blanket anchor colour. NB: this is (0,1,1), so any single-class rule
|
|
11
54
|
colouring an <a> (.b270-btn, .b270-cell, .b270-arrow ...) must be
|
|
@@ -38,7 +81,7 @@
|
|
|
38
81
|
.b270-avatarrow input[type=file]{font:inherit;font-size:13px;max-width:100%}
|
|
39
82
|
.b270-emailform{display:grid;gap:12px;max-width:340px;margin:12px 0 0}
|
|
40
83
|
.b270-field{display:grid;gap:5px;font-size:12.5px;color:var(--muted)}
|
|
41
|
-
.b270-field input{font:inherit;font-size:15px;padding:10px 12px;border:1px solid var(--line);border-radius:9px;background
|
|
84
|
+
.b270-field input{font:inherit;font-size:15px;padding:10px 12px;border:1px solid var(--control-line);border-radius:9px;background:var(--control);color:var(--ink);width:100%}
|
|
42
85
|
.b270-field input:focus{outline:none;border-color:var(--gold);box-shadow:0 0 0 3px rgba(165,129,44,.13)}
|
|
43
86
|
.b270-notification-field{margin:0;padding:0;border:0}
|
|
44
87
|
.b270-notification-field legend{padding:0;margin:0 0 3px}
|
|
@@ -63,8 +106,8 @@
|
|
|
63
106
|
.b270-time-control:active .b270-reminder-tooltip{visibility:visible;opacity:1}
|
|
64
107
|
.b270-field .b270-reminder-optin input[type=checkbox]{width:auto;margin:0;flex:none}
|
|
65
108
|
.b270-time-control{grid-template-columns:48px 5px 48px;align-items:center;gap:2px}
|
|
66
|
-
.b270-field .b270-time-select{width:48px;height:30px;margin:0;padding:4px 2px;border:1px solid var(--line);
|
|
67
|
-
border-radius:7px;background
|
|
109
|
+
.b270-field .b270-time-select{width:48px;height:30px;margin:0;padding:4px 2px;border:1px solid var(--control-line);
|
|
110
|
+
border-radius:7px;background:var(--control);color:var(--ink);font:12px/16px system-ui,sans-serif;
|
|
68
111
|
font-variant-numeric:tabular-nums;text-align:center}
|
|
69
112
|
.b270-time-select:focus{outline:none;border-color:var(--gold);box-shadow:0 0 0 3px rgba(165,129,44,.13)}
|
|
70
113
|
.b270-time-separator{display:grid;place-items:center;height:30px;font:12px/30px system-ui,sans-serif;color:var(--muted)}
|
|
@@ -72,19 +115,19 @@
|
|
|
72
115
|
.b270-or{display:flex;align-items:center;gap:12px;margin:22px 0 18px;color:var(--faint);font-size:12px;letter-spacing:.1em;text-transform:uppercase}
|
|
73
116
|
.b270-or::before,.b270-or::after{content:"";flex:1;height:1px;background:var(--line-soft)}
|
|
74
117
|
.b270-providers{display:grid;gap:10px;max-width:340px}
|
|
75
|
-
.b270 .b270-provider{display:block;width:100%;text-align:center;border:1px solid var(--line);background
|
|
118
|
+
.b270 .b270-provider{display:block;width:100%;text-align:center;border:1px solid var(--line);background:var(--control);color:var(--ink);border-radius:10px;padding:12px 16px;font:inherit;font-weight:600;font-size:14.5px;cursor:pointer;transition:border-color .15s,transform .05s}
|
|
76
119
|
.b270-provider:hover{border-color:var(--gold);color:var(--gold)}
|
|
77
120
|
.b270-provider:active{transform:translateY(1px)}
|
|
78
121
|
.b270 .b270-signinbtn{border:1px solid var(--line);background:var(--card);color:var(--ink);border-radius:8px;padding:7px 13px;font:inherit;font-weight:600;font-size:13px;cursor:pointer}
|
|
79
122
|
.b270-signinbtn:hover{border-color:var(--gold);color:var(--gold)}
|
|
80
123
|
.b270-signin-hint a{color:var(--gold);font-weight:600}
|
|
81
124
|
.b270-flash{border-radius:10px;padding:11px 15px;margin-bottom:18px;font-size:14px}
|
|
82
|
-
.b270-flash.alert{background
|
|
83
|
-
.b270-flash.notice{background
|
|
125
|
+
.b270-flash.alert{background:var(--alert-surface);color:var(--alert-ink);border:1px solid var(--alert-line)}
|
|
126
|
+
.b270-flash.notice{background:var(--notice-surface);color:var(--notice-ink);border:1px solid var(--notice-line)}
|
|
84
127
|
.b270-interaction-status{margin:8px 0 12px;padding:8px 11px;border-radius:8px;background:rgba(47,106,103,.08);color:var(--teal);font-size:12.5px}
|
|
85
128
|
.b270-interaction-status:empty{display:none}
|
|
86
129
|
.b270-interaction-status[data-state="pending"]{color:var(--muted)}
|
|
87
|
-
.b270-interaction-status[data-state="error"]{background
|
|
130
|
+
.b270-interaction-status[data-state="error"]{background:var(--alert-surface);color:var(--alert-ink)}
|
|
88
131
|
.b270 form[aria-busy="true"] [data-b270-disabled-by-submit]{cursor:wait;opacity:.55}
|
|
89
132
|
|
|
90
133
|
.b270-eyebrow{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--gold);font-weight:600;margin:0 0 10px}
|
|
@@ -103,7 +146,7 @@
|
|
|
103
146
|
|
|
104
147
|
|
|
105
148
|
.b270-task-list{list-style:none;margin:0;padding:0;display:grid;gap:8px}
|
|
106
|
-
.b270-task{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:10px 12px;border:1px solid var(--line-soft);border-radius:9px;background:
|
|
149
|
+
.b270-task{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:10px 12px;border:1px solid var(--line-soft);border-radius:9px;background:var(--task-surface)}
|
|
107
150
|
.b270-task-body{display:grid;gap:3px;min-width:0}
|
|
108
151
|
.b270-task-body strong{font-size:14px}
|
|
109
152
|
.b270-task-body span{color:var(--muted);font-size:12px;line-height:1.4}
|
|
@@ -118,7 +161,7 @@
|
|
|
118
161
|
.b270-startline{margin:0;font-size:13.5px;color:var(--muted)}
|
|
119
162
|
.b270-startline strong{color:var(--ink)}
|
|
120
163
|
.b270-startform{display:flex;align-items:flex-end;gap:10px;margin-top:10px;flex-wrap:wrap}
|
|
121
|
-
.b270-startform input[type=date]{font:inherit;font-size:14px;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background
|
|
164
|
+
.b270-startform input[type=date]{font:inherit;font-size:14px;padding:8px 10px;border:1px solid var(--control-line);border-radius:8px;background:var(--control);color:var(--ink)}
|
|
122
165
|
.b270-linkbtn{background:none;border:none;padding:0;margin-top:8px;font:inherit;font-size:12.5px;color:var(--oxblood);cursor:pointer;text-decoration:underline}
|
|
123
166
|
.b270-hint{margin:8px 0 0;font-size:12px;color:var(--faint);max-width:111ch}
|
|
124
167
|
.b270-tracks{margin-top:16px;display:grid;gap:11px}
|
|
@@ -139,7 +182,7 @@
|
|
|
139
182
|
.b270-dayhead .k{display:flex;align-items:center;flex-wrap:wrap;gap:9px;font-family:"Spectral",serif;font-size:24px;font-weight:600;line-height:1}
|
|
140
183
|
.b270-dayhead .date{display:flex;align-items:center;flex-wrap:wrap;gap:9px;color:var(--faint);font-size:13px;margin-top:4px}
|
|
141
184
|
.b270-dayhead .b270-badge{margin-left:0}
|
|
142
|
-
.b270-badge{display:inline-block;vertical-align:middle;line-height:1;font-size:10.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color
|
|
185
|
+
.b270-badge{display:inline-block;vertical-align:middle;line-height:1;font-size:10.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--on-accent);background:var(--gold-lite);padding:5px 8px 4px;border-radius:20px;margin-left:9px}
|
|
143
186
|
|
|
144
187
|
.b270-day-complete{display:flex;align-items:center;gap:14px;margin:0 0 14px;padding:11px 13px;
|
|
145
188
|
border:1px solid var(--line);border-radius:10px;background:var(--card);box-shadow:var(--shadow)}
|
|
@@ -168,15 +211,15 @@
|
|
|
168
211
|
.b270-readings{background:var(--card);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow);overflow:hidden}
|
|
169
212
|
.b270-reading{display:flex;gap:15px;align-items:center;padding:16px 20px;border-left:4px solid transparent}
|
|
170
213
|
.b270-reading+.b270-reading{border-top:1px solid var(--line-soft)}
|
|
171
|
-
.b270-reading.ot{border-left-color:var(--teal)} .b270-reading.nt{border-left-color:var(--oxblood)} .b270-reading.pp{border-left-color:var(--gold)}
|
|
214
|
+
.b270-reading.ot{border-left-color:var(--teal-mark)} .b270-reading.nt{border-left-color:var(--oxblood-mark)} .b270-reading.pp{border-left-color:var(--gold-mark)}
|
|
172
215
|
.b270-reading.rest{opacity:.65}
|
|
173
216
|
.b270-toggle{border:none;background:none;padding:0;cursor:pointer}
|
|
174
217
|
.b270-toggle form{margin:0}
|
|
175
|
-
.b270-check{width:26px;height:26px;border-radius:8px;border:2px solid var(--line);flex:none;display:grid;place-items:center;background
|
|
176
|
-
.b270-check svg{width:15px;height:15px;stroke
|
|
177
|
-
.b270-reading.done.ot .b270-check{background:var(--teal);border-color:var(--teal)}
|
|
178
|
-
.b270-reading.done.nt .b270-check{background:var(--oxblood);border-color:var(--oxblood)}
|
|
179
|
-
.b270-reading.done.pp .b270-check{background:var(--gold);border-color:var(--gold)}
|
|
218
|
+
.b270-check{width:26px;height:26px;border-radius:8px;border:2px solid var(--control-line);flex:none;display:grid;place-items:center;background:var(--control);transition:background-color .18s,border-color .18s,opacity .18s}
|
|
219
|
+
.b270-check svg{width:15px;height:15px;stroke:var(--on-accent);stroke-width:3;fill:none;stroke-linecap:round;stroke-linejoin:round;opacity:0}
|
|
220
|
+
.b270-reading.done.ot .b270-check{background:var(--teal-fill);border-color:var(--teal-mark)}
|
|
221
|
+
.b270-reading.done.nt .b270-check{background:var(--oxblood-fill);border-color:var(--oxblood-mark)}
|
|
222
|
+
.b270-reading.done.pp .b270-check{background:var(--gold-fill);border-color:var(--gold-mark)}
|
|
180
223
|
.b270-reading.done .b270-check svg{opacity:1}
|
|
181
224
|
.b270-rbody{flex:1;min-width:0}
|
|
182
225
|
.b270-cat{font-size:11px;letter-spacing:.13em;text-transform:uppercase;color:var(--faint);font-weight:600}
|
|
@@ -195,16 +238,16 @@
|
|
|
195
238
|
/* A ticked chapter looks exactly like a ticked single reading: the box fills
|
|
196
239
|
in the track's colour and the tick appears. No strikethrough — the other
|
|
197
240
|
tracks don't strike their references either. */
|
|
198
|
-
.b270-reading.ot .b270-part.done .b270-check{background:var(--teal);border-color:var(--teal)}
|
|
199
|
-
.b270-reading.nt .b270-part.done .b270-check{background:var(--oxblood);border-color:var(--oxblood)}
|
|
200
|
-
.b270-reading.pp .b270-part.done .b270-check{background:var(--gold);border-color:var(--gold)}
|
|
241
|
+
.b270-reading.ot .b270-part.done .b270-check{background:var(--teal-fill);border-color:var(--teal-mark)}
|
|
242
|
+
.b270-reading.nt .b270-part.done .b270-check{background:var(--oxblood-fill);border-color:var(--oxblood-mark)}
|
|
243
|
+
.b270-reading.pp .b270-part.done .b270-check{background:var(--gold-fill);border-color:var(--gold-mark)}
|
|
201
244
|
.b270-part.done .b270-check svg{opacity:1}
|
|
202
245
|
.b270-parts-meta{margin-top:10px;font-size:12.5px;color:var(--faint)}
|
|
203
246
|
.b270-version{font-size:11.5px;color:var(--faint);font-weight:400;letter-spacing:.02em;white-space:nowrap}
|
|
204
|
-
.b270-select{font:inherit;font-size:14px;padding:10px 30px 10px 12px;border:1px solid var(--line);border-radius:9px;background
|
|
247
|
+
.b270-select{font:inherit;font-size:14px;padding:10px 30px 10px 12px;border:1px solid var(--control-line);border-radius:9px;background:var(--control);color:var(--ink);width:100%;max-width:100%;text-overflow:ellipsis}
|
|
205
248
|
.b270-source-choices{display:flex;align-items:center;justify-content:space-between;gap:4px;flex-wrap:nowrap;width:100%;margin:8px 0 2px;color:var(--muted);font-size:clamp(10.5px,3.4vw,12.5px)}
|
|
206
249
|
.b270-source-choice{display:inline-flex;align-items:center;gap:4px;cursor:pointer;white-space:nowrap}
|
|
207
|
-
.b270-source-choice input{appearance:none;width:14px;height:14px;margin:0;border:1px solid var(--line);border-radius:3px;background
|
|
250
|
+
.b270-source-choice input{appearance:none;width:14px;height:14px;margin:0;border:1px solid var(--control-line);border-radius:3px;background:var(--control);display:grid;place-content:center;cursor:pointer;flex:none}
|
|
208
251
|
.b270-source-choice input::before{content:"";width:9px;height:9px;transform:scale(0);transition:transform .1s;clip-path:polygon(14% 44%,0 59%,38% 100%,100% 18%,82% 0,37% 65%);background:var(--paper)}
|
|
209
252
|
.b270-source-choice input:checked{background:var(--gold);border-color:var(--gold)}
|
|
210
253
|
.b270-source-choice input:checked::before{transform:scale(1)}
|
|
@@ -219,7 +262,7 @@
|
|
|
219
262
|
|
|
220
263
|
/* completers */
|
|
221
264
|
.b270-completers{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:16px;font-size:13px;color:var(--muted)}
|
|
222
|
-
.b270-avatar{border-radius:50%;object-fit:cover;display:inline-block;text-align:center;background:var(--teal);color
|
|
265
|
+
.b270-avatar{border-radius:50%;object-fit:cover;display:inline-block;text-align:center;background:var(--teal-fill);color:var(--on-accent);font-size:12px;font-weight:600}
|
|
223
266
|
.b270-avatar-fallback{border-radius:50%}
|
|
224
267
|
|
|
225
268
|
/* comments */
|
|
@@ -239,6 +282,7 @@
|
|
|
239
282
|
.b270-mention{color:var(--teal);font-weight:600;text-decoration:none}
|
|
240
283
|
.b270-mention:hover{text-decoration:underline}
|
|
241
284
|
.b270-cactions{display:flex;align-items:center;gap:12px;margin-top:6px;flex-wrap:wrap}
|
|
285
|
+
.b270-cactions .b270-linkbtn{margin:0}
|
|
242
286
|
.b270-likes{display:inline-flex;align-items:center;gap:3px;position:relative}
|
|
243
287
|
.b270-heart{font-size:13px;line-height:1;cursor:default}
|
|
244
288
|
/* The single heart is also the toggle for signed-in readers. It stays red
|
|
@@ -280,8 +324,8 @@
|
|
|
280
324
|
.b270-comment.b270-editing{background:rgba(165,129,44,.05);border-radius:10px;padding:12px}
|
|
281
325
|
.b270-editing textarea{min-height:64px}
|
|
282
326
|
.b270-cdel{font-size:12px;color:var(--oxblood);background:none;border:none;cursor:pointer;padding:0;margin-left:8px}
|
|
283
|
-
.b270-input{width:100%;padding:9px 11px;border:1px solid var(--line);border-radius:9px;background
|
|
284
|
-
.b270-form textarea{width:100%;font:inherit;font-size:15px;border:1px solid var(--line);border-radius:10px;padding:11px 13px;background
|
|
327
|
+
.b270-input{width:100%;padding:9px 11px;border:1px solid var(--control-line);border-radius:9px;background:var(--control);color:var(--ink);font:inherit;font-size:14px;margin-bottom:8px}
|
|
328
|
+
.b270-form textarea{width:100%;font:inherit;font-size:15px;border:1px solid var(--control-line);border-radius:10px;padding:11px 13px;background:var(--control);color:var(--ink);resize:vertical;min-height:76px}
|
|
285
329
|
.b270-form textarea[data-b270-autosize=false]{min-height:114px}
|
|
286
330
|
.b270-broadcast-audience{margin:8px 0}
|
|
287
331
|
.b270-editor-utilities[hidden]{display:none}
|
|
@@ -301,7 +345,7 @@
|
|
|
301
345
|
.b270-format-bold{font-weight:700}
|
|
302
346
|
.b270-format-italic{font-style:italic;font-weight:600}
|
|
303
347
|
.b270-markdown-editor[data-b270-formatting-open=true] textarea{border-radius:0 0 10px 10px}
|
|
304
|
-
.b270-markdown-preview{min-height:76px;margin:0;padding:11px 13px;border:1px solid var(--line);border-radius:10px;background
|
|
348
|
+
.b270-markdown-preview{min-height:76px;margin:0;padding:11px 13px;border:1px solid var(--control-line);border-radius:10px;background:var(--control);color:var(--ink)}
|
|
305
349
|
.b270-markdown-preview[hidden]{display:none}
|
|
306
350
|
.b270-markdown-preview>.b270-hint{margin:0}
|
|
307
351
|
.b270-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}
|
|
@@ -315,15 +359,16 @@
|
|
|
315
359
|
.b270-mention-option:hover,.b270-mention-option[aria-selected=true]{background:var(--paper);color:var(--gold)}
|
|
316
360
|
.b270-draft-status{min-height:16px;margin:4px 0 -4px;font-size:11.5px;color:var(--faint)}
|
|
317
361
|
.b270-form .row{display:flex;align-items:center;gap:10px;margin-top:10px}
|
|
318
|
-
|
|
319
|
-
.b270 .
|
|
320
|
-
.b270-btn:
|
|
362
|
+
/* Override the reflection track selector's inline background and border color. */
|
|
363
|
+
.b270-form .row select{max-width:100%;background:var(--control)!important;border-color:var(--control-line)!important;color:var(--ink)}
|
|
364
|
+
.b270 .b270-btn{border:none;border-radius:10px;padding:8px 16px;margin-top:4px;font:inherit;font-weight:600;font-size:14px;cursor:pointer;background:var(--button-surface);color:var(--button-ink);text-decoration:none;display:inline-block}
|
|
365
|
+
.b270 .b270-btn:hover{background:var(--button-hover)}
|
|
321
366
|
|
|
322
367
|
/* community */
|
|
323
368
|
.b270-admin-reader-tools{display:flex;align-items:flex-end;justify-content:flex-end;gap:8px;margin:0 0 10px;flex-wrap:wrap}
|
|
324
369
|
.b270-admin-reader-sort{display:flex;align-items:flex-end;gap:8px}
|
|
325
370
|
.b270-admin-reader-sort label{display:grid;gap:3px;font-size:11.5px;color:var(--muted)}
|
|
326
|
-
.b270-admin-reader-sort select{min-width:170px;padding:7px 25px 7px 9px;border:1px solid var(--line);border-radius:8px;background
|
|
371
|
+
.b270-admin-reader-sort select{min-width:170px;padding:7px 25px 7px 9px;border:1px solid var(--control-line);border-radius:8px;background:var(--control);color:var(--ink);font:inherit;font-size:12.5px}
|
|
327
372
|
.b270 .b270-admin-reader-sort .b270-btn,.b270 .b270-reader-stats-open{margin:0;padding:8px 13px;font-size:12.5px}
|
|
328
373
|
.b270-reader-stats-dialog{width:min(680px,calc(100vw - 28px));max-width:none;padding:0;border:1px solid var(--line);border-radius:14px;background:var(--card);color:var(--ink);box-shadow:0 18px 60px rgba(35,31,24,.35)}
|
|
329
374
|
.b270-reader-stats-dialog::backdrop{background:rgba(35,31,24,.55)}
|
|
@@ -332,7 +377,7 @@
|
|
|
332
377
|
.b270-reader-stats-head .b270-subhead{margin:0}
|
|
333
378
|
.b270-reader-stats-head form{margin:0}
|
|
334
379
|
.b270-reader-stats-head .b270-linkbtn{margin:0;color:var(--muted)}
|
|
335
|
-
.b270-reader-stats-dialog pre{max-height:65vh;overflow:auto;margin:0;padding:12px;border:1px solid var(--line-soft);border-radius:9px;background
|
|
380
|
+
.b270-reader-stats-dialog pre{max-height:65vh;overflow:auto;margin:0;padding:12px;border:1px solid var(--line-soft);border-radius:9px;background:var(--control);color:var(--ink);font:12.5px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
336
381
|
.b270-list{background:var(--card);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow);overflow:hidden}
|
|
337
382
|
.b270-li{display:flex;align-items:center;gap:14px;padding:14px 18px}
|
|
338
383
|
.b270-li+.b270-li{border-top:1px solid var(--line-soft)}
|
|
@@ -348,13 +393,13 @@
|
|
|
348
393
|
.b270 .b270-cell{aspect-ratio:1;border-radius:6px;border:1px solid var(--line);background:var(--card);font-size:10.5px;color:var(--faint);display:grid;place-items:center;text-decoration:none;font-variant-numeric:tabular-nums}
|
|
349
394
|
.b270-cell:hover{border-color:var(--gold);color:var(--gold)}
|
|
350
395
|
.b270-cell.partial{background:linear-gradient(135deg,var(--card) 50%,rgba(165,129,44,.35) 50%);color:var(--muted)}
|
|
351
|
-
.b270-cell.complete{background:var(--teal);border-color:var(--teal);color
|
|
396
|
+
.b270-cell.complete{background:var(--teal-fill);border-color:var(--teal-mark);color:var(--complete-ink);font-weight:600}
|
|
352
397
|
/* Days that have reflections carry a dot and nothing else: the fill stays
|
|
353
398
|
free to mean reading progress, so the two facts never compete. On a finished
|
|
354
|
-
square the dot
|
|
399
|
+
square the dot contrasts with teal, since oxblood on teal is hard to pick out. */
|
|
355
400
|
.b270-cell.talk{position:relative}
|
|
356
401
|
.b270-cell.talk::after{content:"";position:absolute;top:3px;right:3px;width:7px;height:7px;border-radius:50%;background:var(--oxblood)}
|
|
357
|
-
.b270-cell.talk.complete::after{background
|
|
402
|
+
.b270-cell.talk.complete::after{background:var(--complete-dot)}
|
|
358
403
|
.b270-cell.now{box-shadow:0 0 0 2px var(--gold)}
|
|
359
404
|
|
|
360
405
|
/* On the Reflections page each thread carries the day it belongs to, since
|
|
@@ -364,7 +409,7 @@
|
|
|
364
409
|
.b270-reflection-count{margin:3px 0 0;font-size:12px;color:var(--faint)}
|
|
365
410
|
.b270-reflection-filters{display:flex;align-items:flex-end;justify-content:flex-end;gap:8px;flex-wrap:wrap}
|
|
366
411
|
.b270-reflection-filters label{display:grid;gap:3px;font-size:11.5px;color:var(--muted)}
|
|
367
|
-
.b270-reflection-filters select{min-width:112px;max-width:180px;padding:7px 25px 7px 9px;border:1px solid var(--line);border-radius:8px;background
|
|
412
|
+
.b270-reflection-filters select{min-width:112px;max-width:180px;padding:7px 25px 7px 9px;border:1px solid var(--control-line);border-radius:8px;background:var(--control);color:var(--ink);font:inherit;font-size:12.5px}
|
|
368
413
|
.b270 .b270-reflection-filters .b270-btn{margin:0;padding:8px 13px;font-size:12.5px}
|
|
369
414
|
.b270 .b270-filter-clear{align-self:center;font-size:12px;color:var(--muted)}
|
|
370
415
|
.b270-thread{margin-bottom:8px}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<%= render "bible270/shared/styles" %>
|
|
14
14
|
<%= yield :head %>
|
|
15
15
|
</head>
|
|
16
|
-
<body class="b270">
|
|
16
|
+
<body class="b270" data-display-mode="<%= current_reader&.effective_display_mode || Bible270::Reader::DEFAULT_DISPLAY_MODE %>">
|
|
17
17
|
<div class="b270-wrap">
|
|
18
18
|
<%= render "bible270/shared/header" %>
|
|
19
19
|
<% if flash[:alert] %><div class="b270-flash alert"><%= flash[:alert] %></div><% end %>
|
|
@@ -32,6 +32,7 @@ module Bible270
|
|
|
32
32
|
'AddCompletionDoveDisabledToBible270Readers' => :check_completion_dove_disabled,
|
|
33
33
|
'AddBodyFormatToBible270Comments' => :check_comment_body_format,
|
|
34
34
|
'ChangeAllCommentNoticesDefaultToTrue' => :check_all_comment_notices_default,
|
|
35
|
+
'AddDisplayModeToBible270Readers' => :check_display_mode,
|
|
35
36
|
'CreateActiveStorageTables' => :check_active_storage
|
|
36
37
|
}.freeze
|
|
37
38
|
|
|
@@ -222,6 +223,16 @@ module Bible270
|
|
|
222
223
|
missing
|
|
223
224
|
end
|
|
224
225
|
|
|
226
|
+
def check_display_mode
|
|
227
|
+
missing = table_missing(:bible270_readers, columns: %i[display_mode])
|
|
228
|
+
return missing if missing.any?
|
|
229
|
+
|
|
230
|
+
column = connection.columns(:bible270_readers).find { |candidate| candidate.name == 'display_mode' }
|
|
231
|
+
missing << 'column bible270_readers.display_mode default system' unless column&.default == 'system'
|
|
232
|
+
missing << 'column bible270_readers.display_mode null false' unless column && !column.null
|
|
233
|
+
missing
|
|
234
|
+
end
|
|
235
|
+
|
|
225
236
|
def check_all_comment_notices_default
|
|
226
237
|
missing = table_missing(:bible270_readers, columns: %i[notify_on_all_comments])
|
|
227
238
|
return missing if missing.any?
|
|
@@ -28,14 +28,14 @@ module Bible270
|
|
|
28
28
|
Row.new(
|
|
29
29
|
name: reader.display_name,
|
|
30
30
|
days_read: days_read,
|
|
31
|
-
status:
|
|
31
|
+
status: reader.progress_status(completed_days: days_read),
|
|
32
32
|
last_activity: last_activity_date(reader)
|
|
33
33
|
)
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def status_for(reader)
|
|
38
|
-
|
|
38
|
+
reader.progress_status(completed_days: completed_days.fetch(reader.id, 0))
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def empty? = rows.empty?
|
|
@@ -83,16 +83,5 @@ module Bible270
|
|
|
83
83
|
occurred_at = recent_activities[reader.id]&.occurred_at
|
|
84
84
|
occurred_at ? Bible270.local_time(occurred_at).strftime('%b %-d, %Y') : '—'
|
|
85
85
|
end
|
|
86
|
-
|
|
87
|
-
def status(reader, days_read)
|
|
88
|
-
calendar_day = reader.calendar_day
|
|
89
|
-
return 'undated' unless calendar_day
|
|
90
|
-
|
|
91
|
-
difference = days_read - calendar_day
|
|
92
|
-
return 'on track' if difference.zero?
|
|
93
|
-
|
|
94
|
-
amount = difference.abs
|
|
95
|
-
"#{amount} #{amount == 1 ? 'day' : 'days'} #{difference.positive? ? 'ahead' : 'behind'}"
|
|
96
|
-
end
|
|
97
86
|
end
|
|
98
87
|
end
|
data/lib/bible270/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bible270
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew vonderLuft
|
|
@@ -217,6 +217,7 @@ files:
|
|
|
217
217
|
- db/migrate/20260101000020_add_completion_dove_disabled_to_bible270_readers.rb
|
|
218
218
|
- db/migrate/20260101000021_add_body_format_to_bible270_comments.rb
|
|
219
219
|
- db/migrate/20260101000022_change_all_comment_notices_default_to_true.rb
|
|
220
|
+
- db/migrate/20260101000023_add_display_mode_to_bible270_readers.rb
|
|
220
221
|
- lib/bible270.rb
|
|
221
222
|
- lib/bible270/avatars.rb
|
|
222
223
|
- lib/bible270/comment_formatter.rb
|