active_hashcash 0.3.0 → 0.3.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/app/assets/config/active_hashcash_manifest.js +1 -0
  4. data/app/assets/javascripts/hashcash.js +257 -0
  5. data/app/assets/stylesheets/active_hashcash/application.css +15 -0
  6. data/app/controllers/active_hashcash/addresses_controller.rb +7 -0
  7. data/app/controllers/active_hashcash/application_controller.rb +4 -0
  8. data/app/controllers/active_hashcash/assets_controller.rb +34 -0
  9. data/app/controllers/active_hashcash/stamps_controller.rb +11 -0
  10. data/app/helpers/active_hashcash/addresses_helper.rb +4 -0
  11. data/app/helpers/active_hashcash/application_helper.rb +4 -0
  12. data/app/helpers/active_hashcash/stamps_helper.rb +4 -0
  13. data/app/jobs/active_hashcash/application_job.rb +4 -0
  14. data/app/mailers/active_hashcash/application_mailer.rb +6 -0
  15. data/app/models/active_hashcash/application_record.rb +5 -0
  16. data/app/models/active_hashcash/stamp.rb +70 -0
  17. data/app/views/active_hashcash/addresses/index.html.erb +17 -0
  18. data/app/views/active_hashcash/assets/_logo.svg.erb +1 -0
  19. data/app/views/active_hashcash/assets/_style.css +148 -0
  20. data/app/views/active_hashcash/assets/application.css.erb +1 -0
  21. data/app/views/active_hashcash/assets/ariato.css.erb +2 -0
  22. data/app/views/active_hashcash/assets/favicon.ico +0 -0
  23. data/app/views/active_hashcash/assets/favicon.svg.erb +1 -0
  24. data/app/views/active_hashcash/assets/vendor/_ariato_base.css +1297 -0
  25. data/app/views/active_hashcash/assets/vendor/_ariato_extra.css +1206 -0
  26. data/app/views/active_hashcash/stamps/_filters.html.erb +39 -0
  27. data/app/views/active_hashcash/stamps/index.html.erb +25 -0
  28. data/app/views/active_hashcash/stamps/show.html.erb +21 -0
  29. data/app/views/layouts/active_hashcash/application.html.erb +36 -0
  30. data/config/locales/de.yml +4 -0
  31. data/config/locales/en.yml +4 -0
  32. data/config/locales/es.yml +4 -0
  33. data/config/locales/fr.yml +4 -0
  34. data/config/locales/it.yml +4 -0
  35. data/config/locales/jp.yml +4 -0
  36. data/config/locales/pt.yml +4 -0
  37. data/config/routes.rb +6 -0
  38. data/db/migrate/20240215143453_create_active_hashcash_stamps.rb +25 -0
  39. data/lib/active_hashcash/version.rb +1 -1
  40. metadata +39 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29b1886eff044771c56c4eeff7dfcc4a305262fd39b9633a43522e1a8a465f7a
4
- data.tar.gz: 0ad53ba4ebdcec34064f33cc1ea2f75dc95ce6ffb57a733604174e0059c37521
3
+ metadata.gz: dbc710b7b2cc6fef0667915ac1a95ccfdd6d1cb3eaa1fa9b97d29ef81e79985d
4
+ data.tar.gz: 323c22e60aa27d2d87e39ee8f5c356d2fc6e8190426e49831a29d762d2e74f28
5
5
  SHA512:
6
- metadata.gz: 794687bc50403b03a63e4855451bec53751b6da4a20ef1674bdbf0d66dfdfacfaa68f3d1416a14a0a1bdf823b4641b8a16e6b70aba288278883170c7a73b6202
7
- data.tar.gz: 7b0ca6f84cb75b2e49b5747034393c778023a2efb6974b97ba9fab084074da57cb78f5014de5d26e20af378bb8a86788f833f02e036e5feb56e8aee52c3b87d9
6
+ metadata.gz: ed74328b7b1f91eb2dece7a29efb5b9d68079872694e589caca3e79de1a06e6d0bd5d2556dcab12f92fcede4c08b075a4dc4b9077e79c8511cb4045ce509c82c
7
+ data.tar.gz: e8f580f0668529b3bd7e220f2bdbd4b2eae6bc63272228b9cb4d1fe75ef0e30202983e3e06d91664558d36239137b69d7ba96996c80b370f4b1638300f77781a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog of ActiveHashcash
2
2
 
3
+ ## Unrelease
4
+
5
+ - Fix gem spec list files
6
+
3
7
  ## 0.3.0 - 2024-03-14
4
8
 
5
9
  - Increase complexity automatically to slowdown brute force attacks
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/active_hashcash .css
@@ -0,0 +1,257 @@
1
+ // http://www.hashcash.org/docs/hashcash.html
2
+ // <input type="hiden" name="hashcash" data-hashcash="{resource: 'site.example', bits: 16}"/>
3
+ Hashcash = function(input) {
4
+ options = JSON.parse(input.getAttribute("data-hashcash"))
5
+ Hashcash.disableParentForm(input, options)
6
+ input.dispatchEvent(new CustomEvent("hashcash:mint", {bubbles: true}))
7
+
8
+ Hashcash.mint(options.resource, options, function(stamp) {
9
+ input.value = stamp.toString()
10
+ Hashcash.enableParentForm(input, options)
11
+ input.dispatchEvent(new CustomEvent("hashcash:minted", {bubbles: true, detail: {stamp: stamp}}))
12
+ })
13
+ }
14
+
15
+ Hashcash.setup = function() {
16
+ if (document.readyState != "loading") {
17
+ var input = document.querySelector("input#hashcash")
18
+ input && new Hashcash(input)
19
+ } else
20
+ document.addEventListener("DOMContentLoaded", Hashcash.setup )
21
+ }
22
+
23
+ Hashcash.disableParentForm = function(input, options) {
24
+ input.form.querySelectorAll("[type=submit]").forEach(function(submit) {
25
+ submit.originalValue = submit.value
26
+ options["waiting_message"] && (submit.value = options["waiting_message"])
27
+ submit.disabled = true
28
+ })
29
+ }
30
+
31
+ Hashcash.enableParentForm = function(input, options) {
32
+ input.form.querySelectorAll("[type=submit]").forEach(function(submit) {
33
+ submit.originalValue && (submit.value = submit.originalValue)
34
+ submit.disabled = null
35
+ })
36
+ }
37
+
38
+ Hashcash.default = {
39
+ version: 1,
40
+ bits: 20,
41
+ extension: null,
42
+ }
43
+
44
+ Hashcash.mint = function(resource, options, callback) {
45
+ // Format date to YYMMDD
46
+ var date = new Date
47
+ var year = date.getFullYear().toString()
48
+ year = year.slice(year.length - 2, year.length)
49
+ var month = (date.getMonth() + 1).toString().padStart(2, "0")
50
+ var day = date.getDate().toString().padStart(2, "0")
51
+
52
+ var stamp = new Hashcash.Stamp(
53
+ options.version || Hashcash.default.version,
54
+ options.bits || Hashcash.default.bits,
55
+ options.date || year + month + day,
56
+ resource,
57
+ options.extension || Hashcash.default.extension,
58
+ options.rand || Math.random().toString(36).substr(2, 10),
59
+ )
60
+ return stamp.work(callback)
61
+ }
62
+
63
+ Hashcash.Stamp = function(version, bits, date, resource, extension, rand, counter = 0) {
64
+ this.version = version
65
+ this.bits = bits
66
+ this.date = date
67
+ this.resource = resource
68
+ this.extension = extension
69
+ this.rand = rand
70
+ this.counter = counter
71
+ }
72
+
73
+ Hashcash.Stamp.parse = function(string) {
74
+ var args = string.split(":")
75
+ return new Hashcash.Stamp(args[0], args[1], args[2], args[3], args[4], args[5], args[6])
76
+ }
77
+
78
+ Hashcash.Stamp.prototype.toString = function() {
79
+ return [this.version, this.bits, this.date, this.resource, this.extension, this.rand, this.counter].join(":")
80
+ }
81
+
82
+ // Trigger the given callback when the problem is solved.
83
+ // In order to not freeze the page, setTimeout is called every 100ms to let some CPU to other tasks.
84
+ Hashcash.Stamp.prototype.work = function(callback) {
85
+ this.startClock()
86
+ var timer = performance.now()
87
+ while (!this.check())
88
+ if (this.counter++ && performance.now() - timer > 100)
89
+ return setTimeout(this.work.bind(this), 0, callback)
90
+ this.stopClock()
91
+ callback(this)
92
+ }
93
+
94
+ Hashcash.Stamp.prototype.check = function() {
95
+ var array = Hashcash.sha1(this.toString())
96
+ return array[0] >> (160-this.bits) == 0
97
+ }
98
+
99
+ Hashcash.Stamp.prototype.startClock = function() {
100
+ this.startedAt || (this.startedAt = performance.now())
101
+ }
102
+
103
+ Hashcash.Stamp.prototype.stopClock = function() {
104
+ this.endedAt || (this.endedAt = performance.now())
105
+ var duration = this.endedAt - this.startedAt
106
+ var speed = Math.round(this.counter * 1000 / duration)
107
+ console.debug("Hashcash " + this.toString() + " minted in " + duration + "ms (" + speed + " per seconds)")
108
+ }
109
+
110
+ /**
111
+ * Secure Hash Algorithm (SHA1)
112
+ * http://www.webtoolkit.info/
113
+ **/
114
+ Hashcash.sha1 = function(msg) {
115
+ var rotate_left = Hashcash.sha1.rotate_left
116
+ var Utf8Encode = Hashcash.sha1.Utf8Encode
117
+
118
+ var blockstart;
119
+ var i, j;
120
+ var W = new Array(80);
121
+ var H0 = 0x67452301;
122
+ var H1 = 0xEFCDAB89;
123
+ var H2 = 0x98BADCFE;
124
+ var H3 = 0x10325476;
125
+ var H4 = 0xC3D2E1F0;
126
+ var A, B, C, D, E;
127
+ var temp;
128
+ msg = Utf8Encode(msg);
129
+ var msg_len = msg.length;
130
+ var word_array = new Array();
131
+ for (i = 0; i < msg_len - 3; i += 4) {
132
+ j = msg.charCodeAt(i) << 24 | msg.charCodeAt(i + 1) << 16 |
133
+ msg.charCodeAt(i + 2) << 8 | msg.charCodeAt(i + 3);
134
+ word_array.push(j);
135
+ }
136
+ switch (msg_len % 4) {
137
+ case 0:
138
+ i = 0x080000000;
139
+ break;
140
+ case 1:
141
+ i = msg.charCodeAt(msg_len - 1) << 24 | 0x0800000;
142
+ break;
143
+ case 2:
144
+ i = msg.charCodeAt(msg_len - 2) << 24 | msg.charCodeAt(msg_len - 1) << 16 | 0x08000;
145
+ break;
146
+ case 3:
147
+ i = msg.charCodeAt(msg_len - 3) << 24 | msg.charCodeAt(msg_len - 2) << 16 | msg.charCodeAt(msg_len - 1) << 8 | 0x80;
148
+ break;
149
+ }
150
+ word_array.push(i);
151
+ while ((word_array.length % 16) != 14) word_array.push(0);
152
+ word_array.push(msg_len >>> 29);
153
+ word_array.push((msg_len << 3) & 0x0ffffffff);
154
+ for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
155
+ for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i];
156
+ for (i = 16; i <= 79; i++) W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
157
+ A = H0;
158
+ B = H1;
159
+ C = H2;
160
+ D = H3;
161
+ E = H4;
162
+ for (i = 0; i <= 19; i++) {
163
+ temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;
164
+ E = D;
165
+ D = C;
166
+ C = rotate_left(B, 30);
167
+ B = A;
168
+ A = temp;
169
+ }
170
+ for (i = 20; i <= 39; i++) {
171
+ temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;
172
+ E = D;
173
+ D = C;
174
+ C = rotate_left(B, 30);
175
+ B = A;
176
+ A = temp;
177
+ }
178
+ for (i = 40; i <= 59; i++) {
179
+ temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;
180
+ E = D;
181
+ D = C;
182
+ C = rotate_left(B, 30);
183
+ B = A;
184
+ A = temp;
185
+ }
186
+ for (i = 60; i <= 79; i++) {
187
+ temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;
188
+ E = D;
189
+ D = C;
190
+ C = rotate_left(B, 30);
191
+ B = A;
192
+ A = temp;
193
+ }
194
+ H0 = (H0 + A) & 0x0ffffffff;
195
+ H1 = (H1 + B) & 0x0ffffffff;
196
+ H2 = (H2 + C) & 0x0ffffffff;
197
+ H3 = (H3 + D) & 0x0ffffffff;
198
+ H4 = (H4 + E) & 0x0ffffffff;
199
+ }
200
+ return [H0, H1, H2, H3, H4]
201
+ }
202
+
203
+ Hashcash.hexSha1 = function(msg) {
204
+ var array = Hashcash.sha1(msg)
205
+ var cvt_hex = Hashcash.sha1.cvt_hex
206
+ return cvt_hex(array[0]) + cvt_hex(array[1]) + cvt_hex(array[2]) + cvt_hex(array3) + cvt_hex(array[4])
207
+ }
208
+
209
+ Hashcash.sha1.rotate_left = function(n, s) {
210
+ var t4 = (n << s) | (n >>> (32 - s));
211
+ return t4;
212
+ };
213
+
214
+ Hashcash.sha1.lsb_hex = function(val) {
215
+ var str = '';
216
+ var i;
217
+ var vh;
218
+ var vl;
219
+ for (i = 0; i <= 6; i += 2) {
220
+ vh = (val >>> (i * 4 + 4)) & 0x0f;
221
+ vl = (val >>> (i * 4)) & 0x0f;
222
+ str += vh.toString(16) + vl.toString(16);
223
+ }
224
+ return str;
225
+ };
226
+
227
+ Hashcash.sha1.cvt_hex = function(val) {
228
+ var str = '';
229
+ var i;
230
+ var v;
231
+ for (i = 7; i >= 0; i--) {
232
+ v = (val >>> (i * 4)) & 0x0f;
233
+ str += v.toString(16);
234
+ }
235
+ return str;
236
+ };
237
+
238
+ Hashcash.sha1.Utf8Encode = function(string) {
239
+ string = string.replace(/\r\n/g, '\n');
240
+ var utftext = '';
241
+ for (var n = 0; n < string.length; n++) {
242
+ var c = string.charCodeAt(n);
243
+ if (c < 128) {
244
+ utftext += String.fromCharCode(c);
245
+ } else if ((c > 127) && (c < 2048)) {
246
+ utftext += String.fromCharCode((c >> 6) | 192);
247
+ utftext += String.fromCharCode((c & 63) | 128);
248
+ } else {
249
+ utftext += String.fromCharCode((c >> 12) | 224);
250
+ utftext += String.fromCharCode(((c >> 6) & 63) | 128);
251
+ utftext += String.fromCharCode((c & 63) | 128);
252
+ }
253
+ }
254
+ return utftext;
255
+ };
256
+
257
+ Hashcash.setup()
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,7 @@
1
+ module ActiveHashcash
2
+ class AddressesController < ApplicationController
3
+ def index
4
+ @addresses = Stamp.filter_by(params).group(:ip_address).order(count_all: :desc).limit(1000).count
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ module ActiveHashcash
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,34 @@
1
+ module ActiveHashcash
2
+ class AssetsController < ApplicationController
3
+ protect_from_forgery except: :show
4
+
5
+ Mime::Type.register "image/x-icon", :ico
6
+
7
+ def show
8
+ if endpoints.include?(file_name = File.basename(request.path))
9
+ file_path = ActiveHashcash::Engine.root.join / "app/views/active_hashcash/assets" / file_name
10
+ if File.exists?("#{file_path}.erb")
11
+ render(params[:id], mime_type: mime_type)
12
+ else
13
+ render(file: file_path)
14
+ end
15
+ expires_in(1.day, public: true)
16
+ else
17
+ raise ActionController::RoutingError.new
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def endpoints
24
+ return @endpoints if @endpoints
25
+ folder = ActiveHashcash::Engine.root.join("app/views", controller_path)
26
+ files = folder.each_child.map { |path| File.basename(path).delete_suffix(".erb") }
27
+ @endpoints = files.delete_if { |str| str.start_with?("_") }
28
+ end
29
+
30
+ def mime_type
31
+ Mime::Type.lookup_by_extension(File.extname(request.path).delete_prefix("."))
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,11 @@
1
+ module ActiveHashcash
2
+ class StampsController < ApplicationController
3
+ def index
4
+ @stamps = Stamp.filter_by(params).order(created_at: :desc).limit(1000)
5
+ end
6
+
7
+ def show
8
+ @stamp = Stamp.find(params[:id])
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module ActiveHashcash
2
+ module AddressesHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ActiveHashcash
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ActiveHashcash
2
+ module StampsHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ActiveHashcash
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module ActiveHashcash
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveHashcash
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveHashcash
4
+ class Stamp < ApplicationRecord
5
+ validates_presence_of :version, :bits, :date, :resource, :rand, :counter
6
+
7
+ scope :created_from, -> (date) { where(created_at: date..) }
8
+ scope :created_to, -> (date) { where(created_at: ..date) }
9
+
10
+ scope :bits_from, -> (value) { where(bits: value..) }
11
+ scope :bits_to, -> (value) { where(bits: ..value) }
12
+
13
+ scope :ip_address_starts_with, -> (string) { where("ip_address LIKE ?", sanitize_sql_like(string) + "%") }
14
+ scope :request_path_starts_with, -> (string) { where("request_path LIKE ?", sanitize_sql_like(string) + "%") }
15
+
16
+ def self.filter_by(params)
17
+ scope = all
18
+ scope = scope.request_path_starts_with(params[:request_path_starts_with]) if params[:request_path_starts_with].present?
19
+ scope = scope.ip_address_starts_with(params[:ip_address_starts_with]) if params[:ip_address_starts_with].present?
20
+ scope = scope.created_from(params[:created_from]) if params[:created_from].present?
21
+ scope = scope.created_to(params[:created_to]) if params[:created_to].present?
22
+ scope = scope.bits_from(params[:bits_from]) if params[:bits_from].present?
23
+ scope = scope.bits_to(params[:bits_to]) if params[:bits_to].present?
24
+ scope
25
+ end
26
+
27
+ def self.spend(string, resource, bits, date, options = {})
28
+ return false unless stamp = parse(string)
29
+ stamp.attributes = options
30
+ stamp.verify(resource, bits, date) && stamp.save
31
+ rescue ActiveRecord::RecordNotUnique
32
+ false
33
+ end
34
+
35
+ def self.parse(string)
36
+ return unless string
37
+ args = string.split(":")
38
+ return if args.size != 7
39
+ new(version: args[0], bits: args[1], date: Date.strptime(args[2], ActiveHashcash.date_format), resource: args[3], ext: args[4], rand: args[5], counter: args[6])
40
+ end
41
+
42
+ def self.mint(resource, attributes = {})
43
+ new({
44
+ version: 1,
45
+ bits: ActiveHashcash.bits,
46
+ date: Date.today.strftime(ActiveHashcash.date_format),
47
+ resource: resource,
48
+ rand: SecureRandom.alphanumeric(16),
49
+ counter: 0,
50
+ }.merge(attributes)).work
51
+ end
52
+
53
+ def work
54
+ counter.next! until authentic?
55
+ self
56
+ end
57
+
58
+ def authentic?
59
+ Digest::SHA1.hexdigest(to_s).hex >> (160-bits) == 0
60
+ end
61
+
62
+ def verify(resource, bits, date)
63
+ self.resource == resource && self.bits >= bits && self.date >= date && !self.date.future? && authentic?
64
+ end
65
+
66
+ def to_s
67
+ [version.to_i, bits, date.strftime("%y%m%d"), resource, ext, rand, counter].join(":")
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,17 @@
1
+ <details>
2
+ <summary>Filters</summary>
3
+ <%= render "active_hashcash/stamps/filters" %>
4
+ </details>
5
+
6
+ <table>
7
+ <tr>
8
+ <th><%= ActiveHashcash::Stamp.human_attribute_name(:ip_address) %></th>
9
+ <th><%= ActiveHashcash::Stamp.model_name.human.pluralize %></th>
10
+ </tr>
11
+ <% for (address, count) in @addresses %>
12
+ <tr>
13
+ <td><%= link_to address, stamps_path(ip_address_starts_with: address) %></td>
14
+ <td><%= number_with_delimiter count %></td>
15
+ </tr>
16
+ <% end %>
17
+ </table>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 50.4 50.4" xmlns="http://www.w3.org/2000/svg"><g fill="#08773c"><path d="m25.2 0c-13.9 0-25.2 11.3-25.2 25.2s11.3 25.2 25.2 25.2 25.2-11.3 25.2-25.2-11.31-25.2-25.2-25.2zm0 46.4c-11.69 0-21.2-9.51-21.2-21.2s9.51-21.2 21.2-21.2 21.2 9.51 21.2 21.2-9.51 21.2-21.2 21.2z"/><path d="m33 12.04h-4v7.58h-7.89v-7.58h-4v7.58h-4.98v4h4.98v2.69h-4.98v4h4.98v7.58h4v-7.58h7.89v7.58h4v-7.58h4.98v-4h-4.98v-2.69h4.98v-4h-4.98zm-4 14.27h-7.89v-2.69h7.89z"/></g></svg>
@@ -0,0 +1,148 @@
1
+ body {
2
+ padding: 0;
3
+ margin: 0;
4
+ width: 100%;
5
+ }
6
+
7
+ body > header nav {
8
+ width: 100%;
9
+ max-width: 1280px;
10
+ margin: 0 auto;
11
+ padding: 24px;
12
+ display: flex;
13
+ flex-wrap: wrap;
14
+ align-items: center;
15
+ min-height: 88px;
16
+ }
17
+
18
+ header nav .menubutton {
19
+ margin-left: auto;
20
+ }
21
+
22
+ header .logo {
23
+ margin-right: 8px;
24
+ }
25
+
26
+ header .logo svg {
27
+ width: 24px;
28
+ height: 24px;
29
+ fill: none;
30
+ stroke: rgba(var(--color-blue-500), 1);
31
+ stroke-width: 1;
32
+ vertical-align: middle;
33
+ }
34
+
35
+ header .is-link {
36
+ text-transform: none;
37
+ text-decoration: none;
38
+ font-size: 1rem;
39
+ letter-spacing: 0;
40
+ font-weight: 400;
41
+ padding: 0 16px;
42
+ margin: 0;
43
+ min-height: 24px;
44
+ color: rgba(var(--color-grey-700), 1);
45
+ }
46
+
47
+ header .is-link:hover {
48
+ background: rgba(var(--color-grey-50), 1);
49
+ }
50
+
51
+ main {
52
+ width: 100%;
53
+ max-width: 1280px;
54
+ margin: 0 auto;
55
+ padding: 0 24px;
56
+ }
57
+
58
+ main h2 {
59
+ word-break: break-word;
60
+ display: flex;
61
+ gap: var(--space);
62
+ }
63
+
64
+ section {
65
+ margin-bottom: var(--space-4x);
66
+ }
67
+
68
+ .tooltip-date {
69
+ color: rgba(var(--color-grey-100), 1);
70
+ }
71
+
72
+ .card h3 {
73
+ display: flex;
74
+ }
75
+
76
+ .card h3 small {
77
+ color: rgba(var(--color-red-500), 1);
78
+ }
79
+
80
+ .card h3 small.is-success {
81
+ color: rgba(var(--color-green-500), 1);
82
+ }
83
+
84
+ .card h3 a {
85
+ margin-left: auto;
86
+ font-weight: 400;
87
+ font-size: 1rem;
88
+ }
89
+
90
+ .card table {
91
+ margin: 0 -24px -24px;
92
+ width: calc(100% + 48px);
93
+ }
94
+
95
+ td a {
96
+ word-break: break-word;
97
+ }
98
+
99
+ td.number {
100
+ text-align: right;
101
+ font-variant-numeric: tabular-nums;
102
+ }
103
+
104
+ div.is-empty {
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ height: calc(100% - 72px);
109
+ width: 100%;
110
+ color: rgba(var(--color-grey-300), 1);
111
+ }
112
+
113
+ body > footer {
114
+ max-width: 1280px;
115
+ margin: 48px auto 0;
116
+ padding: 24px;
117
+ }
118
+
119
+ body > footer ul {
120
+ margin: 0;
121
+ list-style-type: none;
122
+ padding: 0;
123
+ }
124
+
125
+ body > footer .card {
126
+ margin: 0;
127
+ background: rgba(var(--color-grey-50), 1);
128
+ }
129
+
130
+ body > footer .card p {
131
+ padding: 0 0 2px;
132
+ color: rgba(var(--color-grey-400), 1);
133
+ }
134
+
135
+ @media (max-width: 480px) {
136
+ header nav .menubutton {
137
+ margin-top: 24px;
138
+ }
139
+
140
+ [role="dialog"] {
141
+ min-width: 280px;
142
+ }
143
+ }
144
+
145
+ details form {
146
+ padding: var(--space-3x) ;
147
+ padding-top: 0;
148
+ }
@@ -0,0 +1 @@
1
+ <%= render "style" %>
@@ -0,0 +1,2 @@
1
+ <%= render partial: "/active_hashcash/assets/vendor/ariato_base", formats: [:css] %>
2
+ <%= render partial: "/active_hashcash/assets/vendor/ariato_extra", formats: [:css] %>
@@ -0,0 +1 @@
1
+ <%= render "/active_hashcash/assets/logo" %>