diff options
| -rw-r--r-- | app/controllers/csp_reports_controller.rb | 4 | ||||
| -rw-r--r-- | app/models/concerns/file_attachment.rb | 3 | ||||
| -rw-r--r-- | app/views/assets/show.html.erb | 10 | ||||
| -rw-r--r-- | app/views/layouts/application.html.erb | 2 | ||||
| -rw-r--r-- | app/views/layouts/events.html.erb | 17 | ||||
| -rw-r--r-- | app/views/nodes/new.html.erb | 5 | ||||
| -rw-r--r-- | config/initializers/csp_log.rb | 4 | ||||
| -rw-r--r-- | config/routes.rb | 8 | ||||
| -rw-r--r-- | doc/DESIGN_HISTORY.md | 200 | ||||
| -rw-r--r-- | public/javascripts/admin_interface.js | 21 | ||||
| -rw-r--r-- | public/javascripts/admin_search.js | 6 | ||||
| -rw-r--r-- | public/stylesheets/ccc.css | 749 | ||||
| -rw-r--r-- | public/stylesheets/ccc.css_ | 579 | ||||
| -rw-r--r-- | public/stylesheets/ccc.css_old | 257 |
14 files changed, 670 insertions, 1195 deletions
diff --git a/app/controllers/csp_reports_controller.rb b/app/controllers/csp_reports_controller.rb index a8f8edb..5a3b55e 100644 --- a/app/controllers/csp_reports_controller.rb +++ b/app/controllers/csp_reports_controller.rb | |||
| @@ -12,9 +12,9 @@ class CspReportsController < ApplicationController | |||
| 12 | if report | 12 | if report |
| 13 | directive = report["effective-directive"] || report["violated-directive"] | 13 | directive = report["effective-directive"] || report["violated-directive"] |
| 14 | at = (URI.parse(report["document-uri"]).path rescue "unparsed") | 14 | at = (URI.parse(report["document-uri"]).path rescue "unparsed") |
| 15 | Rails.logger.warn("CSP violation: #{directive} blocked=#{report['blocked-uri']} at=#{at}") | 15 | CSP_LOGGER.warn("CSP violation: #{directive} blocked=#{report['blocked-uri']} at=#{at}") |
| 16 | else | 16 | else |
| 17 | Rails.logger.warn("CSP violation: unparseable report (#{raw.to_s.bytesize} bytes)") | 17 | CSP_LOGGER.warn("CSP violation: unparseable report (#{raw.to_s.bytesize} bytes)") |
| 18 | end | 18 | end |
| 19 | 19 | ||
| 20 | head :no_content | 20 | head :no_content |
diff --git a/app/models/concerns/file_attachment.rb b/app/models/concerns/file_attachment.rb index 613a6f0..3c09456 100644 --- a/app/models/concerns/file_attachment.rb +++ b/app/models/concerns/file_attachment.rb | |||
| @@ -106,7 +106,8 @@ module FileAttachment | |||
| 106 | end | 106 | end |
| 107 | 107 | ||
| 108 | def sanitize_filename(filename) | 108 | def sanitize_filename(filename) |
| 109 | File.basename(filename).gsub(/[^\w\.\-]/, '_') | 109 | name = File.basename(filename).unicode_normalize(:nfc) |
| 110 | name.gsub(/(?u)[^\w\.\-]/, '_') | ||
| 110 | end | 111 | end |
| 111 | 112 | ||
| 112 | # Proxy object returned by asset.upload, providing the Paperclip-compatible | 113 | # Proxy object returned by asset.upload, providing the Paperclip-compatible |
diff --git a/app/views/assets/show.html.erb b/app/views/assets/show.html.erb index 5717dd9..ff00883 100644 --- a/app/views/assets/show.html.erb +++ b/app/views/assets/show.html.erb | |||
| @@ -20,7 +20,15 @@ | |||
| 20 | <div class="node_content"><%= image_tag @asset.upload.url(:medium), style: "max-width: 300px; max-height: 300px;" %></div> | 20 | <div class="node_content"><%= image_tag @asset.upload.url(:medium), style: "max-width: 300px; max-height: 300px;" %></div> |
| 21 | 21 | ||
| 22 | <div class="node_description">Public Path</div> | 22 | <div class="node_description">Public Path</div> |
| 23 | <div class="node_content"><%= @asset.upload.url.sub(/\?\d+$/, "") %></div> | 23 | <div class="node_content"> |
| 24 | <% public_path = @asset.upload.url.sub(/\?\d+$/, "") %> | ||
| 25 | <%= link_to public_path, public_path, target: "_blank", rel: "noopener" %> | ||
| 26 | <button type="button" class="action_button copy_button" | ||
| 27 | data-copy-url="<%= request.base_url + public_path %>"> | ||
| 28 | <%= icon("copy", library: "tabler", "aria-hidden": true) %> | ||
| 29 | <span class="copy_button_label">Copy URL</span> | ||
| 30 | </button> | ||
| 31 | </div> | ||
| 24 | 32 | ||
| 25 | <div class="node_description">Content Type</div> | 33 | <div class="node_description">Content Type</div> |
| 26 | <div class="node_content"><%= @asset.upload.content_type %></div> | 34 | <div class="node_content"><%= @asset.upload.content_type %></div> |
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index ca867ab..f981fc1 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb | |||
| @@ -62,12 +62,10 @@ | |||
| 62 | <div id="center_column"> | 62 | <div id="center_column"> |
| 63 | <%= yield :layout %> | 63 | <%= yield :layout %> |
| 64 | <div id="footer"> | 64 | <div id="footer"> |
| 65 | <br /> | ||
| 66 | <p style="text-align: center"> | 65 | <p style="text-align: center"> |
| 67 | <%= link_to "Impressum", content_path("imprint") %> | 66 | <%= link_to "Impressum", content_path("imprint") %> |
| 68 | <%= link_to "Datenschutz", content_path("datenschutz") %> | 67 | <%= link_to "Datenschutz", content_path("datenschutz") %> |
| 69 | <%= language_selector %> | 68 | <%= language_selector %> |
| 70 | <!-- %= link_to t(:sponsors), content_path("sponsors") % --> | ||
| 71 | </p> | 69 | </p> |
| 72 | </div> | 70 | </div> |
| 73 | </div> | 71 | </div> |
diff --git a/app/views/layouts/events.html.erb b/app/views/layouts/events.html.erb deleted file mode 100644 index 93a6c0c..0000000 --- a/app/views/layouts/events.html.erb +++ /dev/null | |||
| @@ -1,17 +0,0 @@ | |||
| 1 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" | ||
| 2 | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||
| 3 | |||
| 4 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | ||
| 5 | <head> | ||
| 6 | <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> | ||
| 7 | <title>Events: <%= controller.action_name %></title> | ||
| 8 | <%= stylesheet_link_tag 'scaffold' %> | ||
| 9 | </head> | ||
| 10 | <body> | ||
| 11 | |||
| 12 | <p style="color: green"><%= flash[:notice] %></p> | ||
| 13 | |||
| 14 | <%= yield %> | ||
| 15 | |||
| 16 | </body> | ||
| 17 | </html> | ||
diff --git a/app/views/nodes/new.html.erb b/app/views/nodes/new.html.erb index 890d46e..805fbc9 100644 --- a/app/views/nodes/new.html.erb +++ b/app/views/nodes/new.html.erb | |||
| @@ -41,7 +41,10 @@ | |||
| 41 | <div class="node_description">Resulting path</div> | 41 | <div class="node_description">Resulting path</div> |
| 42 | <div class="node_content"> | 42 | <div class="node_content"> |
| 43 | <span id="resulting_path">—</span> | 43 | <span id="resulting_path">—</span> |
| 44 | <button type="button" id="copy_resulting_path" class="unselected">copy</button> | 44 | <button type="button" id="copy_resulting_path" class="unselected copy_button" data-copy-target="#resulting_path"> |
| 45 | <span class="copy_button_label">copy</span> | ||
| 46 | </button> | ||
| 47 | |||
| 45 | <span class="field_hint">This preview updates as you type. The final path can still be changed afterward by editing the title (for the last segment) or moving the node to a different parent (for everything before it).</span> | 48 | <span class="field_hint">This preview updates as you type. The final path can still be changed afterward by editing the title (for the last segment) or moving the node to a different parent (for everything before it).</span> |
| 46 | </div> | 49 | </div> |
| 47 | 50 | ||
diff --git a/config/initializers/csp_log.rb b/config/initializers/csp_log.rb new file mode 100644 index 0000000..1dde9c4 --- /dev/null +++ b/config/initializers/csp_log.rb | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | # CSP violation reports get their own file, independent of config.logger. | ||
| 2 | CSP_LOGGER = Rails.env.production? ? | ||
| 3 | ActiveSupport::Logger.new(Rails.root.join("log", "csp_violations.log")) : | ||
| 4 | Rails.logger | ||
diff --git a/config/routes.rb b/config/routes.rb index 743b46e..6ddf48c 100644 --- a/config/routes.rb +++ b/config/routes.rb | |||
| @@ -109,11 +109,11 @@ Cccms::Application.routes.draw do | |||
| 109 | 109 | ||
| 110 | resource :session | 110 | resource :session |
| 111 | 111 | ||
| 112 | get 'rss/updates', :to => 'rss#updates', :as => :rss | 112 | get 'rss/updates', :to => 'rss#updates', :as => :rss, :defaults => { :format => :xml } |
| 113 | get 'rss/updates.:format', :to => 'rss#updates', :as => :rss_feed, | 113 | get 'rss/updates.:format', :to => 'rss#updates', :as => :rss_feed, :defaults => { :format => :xml }, |
| 114 | :constraints => { :format => /xml|rdf/ } | 114 | :constraints => { :format => /xml|rdf/ } |
| 115 | get 'rss/tags/:tag/updates', :to => 'rss#tag_updates', :as => :rss_tag | 115 | get 'rss/tags/:tag/updates', :to => 'rss#tag_updates', :as => :rss_tag, :defaults => { :format => :xml } |
| 116 | get 'rss/tags/:tag/updates.:format', :to => 'rss#tag_updates', :as => :rss_tag_feed, | 116 | get 'rss/tags/:tag/updates.:format', :to => 'rss#tag_updates', :as => :rss_tag_feed, :defaults => { :format => :xml }, |
| 117 | :constraints => { :format => /xml/ } | 117 | :constraints => { :format => /xml/ } |
| 118 | 118 | ||
| 119 | match 'galleries/*page_path' => 'content#render_gallery', :via => :get | 119 | match 'galleries/*page_path' => 'content#render_gallery', :via => :get |
diff --git a/doc/DESIGN_HISTORY.md b/doc/DESIGN_HISTORY.md new file mode 100644 index 0000000..5eb664b --- /dev/null +++ b/doc/DESIGN_HISTORY.md | |||
| @@ -0,0 +1,200 @@ | |||
| 1 | ## The head / draft / autosave / translation lifecycle | ||
| 2 | |||
| 3 | ### Era 1 — the original design (2009, `doc/README_FOR_APP`). | ||
| 4 | |||
| 5 | Two layers only: `head` (the published, public revision) and `draft` | ||
| 6 | (a full copy of head's content, created the moment someone starts | ||
| 7 | editing). | ||
| 8 | |||
| 9 | No separate lock concept. The draft's *author* was the lock. | ||
| 10 | Pessimistic, one editor at a time, enforced by ownership rather than a | ||
| 11 | distinct column. An author could withdraw authorship (the draft | ||
| 12 | becomes author-less, open for someone else to pick up) or discard the | ||
| 13 | draft entirely, reverting to head. | ||
| 14 | |||
| 15 | Admins could override any lock or remove any stuck draft on another | ||
| 16 | editor's behalf. No autosave. | ||
| 17 | |||
| 18 | Globalize wasn't part of the permission model (`Permission` grants | ||
| 19 | admin rights to steal a lock a node. | ||
| 20 | |||
| 21 | ### Why a third layer got added. | ||
| 22 | |||
| 23 | Under the original model, the moment someone started typing, a real, | ||
| 24 | numbered revision already existed: `acts_as_list` assigns revision | ||
| 25 | numbers at creation, scoped to `node_id`. | ||
| 26 | |||
| 27 | A speculative edit (open the editor, type a few words, close the tab) | ||
| 28 | still counted as a real Draft, and a real future revision if it were | ||
| 29 | ever published. | ||
| 30 | |||
| 31 | The rebuild introduced `autosave` as a genuinely separate layer: the | ||
| 32 | same `Page` model, but created with `node_id: nil` specifically so | ||
| 33 | it's excluded from `Node#pages`, the actual revision list. | ||
| 34 | |||
| 35 | Typing progress survives a crash or a closed tab, but nothing becomes | ||
| 36 | a real revision until the editor deliberately saves, which promotes | ||
| 37 | the autosave's full content, wholesale, into the draft layer via | ||
| 38 | `clone_attributes_from`. | ||
| 39 | |||
| 40 | A canonical six-state reference table (head / draft / autosave present | ||
| 41 | or absent, and what each combination permits) lives alongside the | ||
| 42 | model code as the authority for any future work on this lifecycle. | ||
| 43 | |||
| 44 | ### Translations (Globalize), layered on top | ||
| 45 | |||
| 46 | The governing principle for admin views from that rebuild: | ||
| 47 | |||
| 48 | *presentation locale* (what language the admin chrome renders in) and | ||
| 49 | *content-target locale* (which translation a given screen reads or | ||
| 50 | writes) are separate concerns that must never share a mechanism. | ||
| 51 | |||
| 52 | The route's `:locale` segment governs only the former. A locale is | ||
| 53 | either the default one — edited exclusively through the primary node | ||
| 54 | editor, pinned via `Globalize.with_locale` regardless of what the | ||
| 55 | route happens to say, so a stray `/en/` URL can't silently edit the | ||
| 56 | German content (as has been the case before the rewrite in 2026), or | ||
| 57 | one of the non-default locales, edited exclusively through a | ||
| 58 | Translations sub-resource under a deliberately distinct route | ||
| 59 | parameter (`translation_locale`), chosen specifically because it must | ||
| 60 | never leak into `default_url_options` the way the chrome locale does. | ||
| 61 | Never both paths for the same locale. | ||
| 62 | |||
| 63 | For now, admin chrome language stays tied to the route locale for now. | ||
| 64 | A per-editor preference column was considered and set aside as | ||
| 65 | revisit-later, not built. | ||
| 66 | |||
| 67 | Translations carry no independent publish state: a translation goes | ||
| 68 | live exactly when the draft containing it is promoted to head, same as | ||
| 69 | everything else in that `Page` row. Both a per-translation | ||
| 70 | `published_at` and full per-locale draft/head/autosave parity with | ||
| 71 | `Node` were considered and rejected as unneeded machinery absent a | ||
| 72 | real editorial need for staggered release timing. | ||
| 73 | |||
| 74 | ## Events and the calendar | ||
| 75 | |||
| 76 | ### Problem | ||
| 77 | |||
| 78 | Calendar entries required an internal Node — every event needed a page | ||
| 79 | somewhere in the tree. Editors avoided this: either the updates stream | ||
| 80 | absorbed non-news content, or a page got created with no natural place | ||
| 81 | in the navigation. The calendar widget went dormant rather than get | ||
| 82 | used under those terms. | ||
| 83 | |||
| 84 | ### Rebuild. | ||
| 85 | |||
| 86 | The Event model was rewritten around the RRULE humanizer | ||
| 87 | (`app/models/concerns/rrule_humanizer.rb`) and a picker UI bound to it | ||
| 88 | by a deliberate scope rule: the picker only generates or reads back | ||
| 89 | RRULE shapes the humanizer can also render as prose. A picker that | ||
| 90 | could express more than the humanizer can describe would let an editor | ||
| 91 | create a schedule that renders as blank text on the public page — | ||
| 92 | worse than a raw string the editor had to type by hand. | ||
| 93 | `Event#node_id` became optional, so an event can point outward (an | ||
| 94 | external URL) instead of requiring a page. This is what made reviving | ||
| 95 | the calendar possible at all. | ||
| 96 | |||
| 97 | ### The "where to put an event" problem | ||
| 98 | |||
| 99 | Once events no longer needed a node, turning the calendar on directly | ||
| 100 | would have meant every chapter's regular open evening becoming an | ||
| 101 | entry — a handful of one-off items buried under dozens of recurring, | ||
| 102 | low-signal rows. | ||
| 103 | |||
| 104 | ### Resolution: | ||
| 105 | |||
| 106 | Two widgets, not one: | ||
| 107 | |||
| 108 | - `open_erfas_today` took the recurring, date-filtered case — which | ||
| 109 | chapters are open today. Its label went through rejected drafts | ||
| 110 | before landing on phrasing that admits it's a curated sample, not a | ||
| 111 | complete listing. | ||
| 112 | - `div#frontpage_calendar` was reserved for the one-off case — | ||
| 113 | conferences, memorial gatherings. Its CSS carries an unconditional | ||
| 114 | `display: none` with no override anywhere in the stylesheet — not a | ||
| 115 | bug, a widget staged for a UI that hasn't been built yet. | ||
| 116 | |||
| 117 | ## The action log (`NodeAction`) | ||
| 118 | |||
| 119 | ### Origin | ||
| 120 | |||
| 121 | The action log grew directly out of an audit of what timestamps | ||
| 122 | actually mean: Two things were missing in the old model: no record of | ||
| 123 | who actually clicked Publish (`page.editor` reflects the last content | ||
| 124 | save, not necessarily that specific act), and the dashboard's | ||
| 125 | recent-changes widget was actively wrong, showing an unrelated | ||
| 126 | in-progress draft's byline next to a timestamp that didn't correspond | ||
| 127 | to why the node appeared on the list at all. | ||
| 128 | |||
| 129 | The log was designed to fill the gap and to answer "what did people | ||
| 130 | do," a question the existing timestamp fields were never designed to | ||
| 131 | answer. | ||
| 132 | |||
| 133 | ### Governing principle | ||
| 134 | |||
| 135 | A derived log is only as reliable as the discipline that maintains it. | ||
| 136 | |||
| 137 | Named explicitly before any code was written, not discovered afterward. | ||
| 138 | Three ways it can drift: | ||
| 139 | |||
| 140 | 1. A future code path can simply forget to write to it — this project | ||
| 141 | had already found that exact shape twice (`wipe_draft!`'s ungated | ||
| 142 | branch; `Page.aggregate` silently ignoring a `conditions=` argument). | ||
| 143 | 2. If the log write and the actual mutation aren't in the same | ||
| 144 | transaction, they can diverge on a crash. Resolved by writing from | ||
| 145 | *inside* the model methods themselves (`autosave!`, `save_draft!`, | ||
| 146 | `publish_draft!`, `trash!`, `destroy!`) rather than from the | ||
| 147 | controllers that call them — every future caller is covered for | ||
| 148 | free, the same reasoning already proven by the JS-autosave work. | ||
| 149 | 3. It can never retroactively reconstruct anything from before it | ||
| 150 | existed — addressed deliberately by the backfill (below), not | ||
| 151 | ignored. | ||
| 152 | |||
| 153 | ### Avoiding noise | ||
| 154 | |||
| 155 | A draft save or publish that produces no visible difference shouldn't | ||
| 156 | create a visible log entry. Solved by reusing | ||
| 157 | `Page#diff_against` / `has_changes_to?` — comparisons that already | ||
| 158 | existed and were already tested — rather than inventing new diffing | ||
| 159 | logic for the log to own. | ||
| 160 | |||
| 161 | ### The verb vocabulary | ||
| 162 | |||
| 163 | Firstly, the log deliberately excludes locks (transient, no history | ||
| 164 | value), autosave and draft-saving itself (exactly the noise the log | ||
| 165 | exists to filter out), and tag/asset/event edits made *on a draft* | ||
| 166 | (those will later surface as changed-flags at publish time, which | ||
| 167 | is when they become real). | ||
| 168 | |||
| 169 | Deliberately included, on reflection: reverts and discards (arguably | ||
| 170 | more worth tracking than promotions: "who discarded this work, and | ||
| 171 | when" is the fact someone would actually go looking for), shared | ||
| 172 | preview link generation and revocation (security-relevant, handing | ||
| 173 | unpublished content to an outsider), and slug/path changes | ||
| 174 | (link-breaking, earns its own record separate from ordinary edits). | ||
| 175 | |||
| 176 | ### Rollback isn't a separate verb. | ||
| 177 | |||
| 178 | `restore_revision!` writes a `publish` entry with a discriminator | ||
| 179 | (`via: "revision"` instead of `via: "draft"`) rather than its own | ||
| 180 | vocabulary. One verb, one meaning ("a page got promoted to head"), | ||
| 181 | distinguished by how it got there. | ||
| 182 | |||
| 183 | ### Backfill | ||
| 184 | |||
| 185 | Historical entries mirror the live vocabulary exactly. Diff content | ||
| 186 | (what actually changed) is computed from the real historical revision | ||
| 187 | data, since consecutive `Page` rows already existed to compare — only | ||
| 188 | the actor and the timestamp are ever inferred, each backfilled entry | ||
| 189 | carrying an `inferred_from` field naming the specific heuristic used | ||
| 190 | (e.g. "from a page revision," "from `published_at`"). A `null` | ||
| 191 | `inferred_from` means the entry was witnessed live, not reconstructed — | ||
| 192 | so the log can always answer, for any entry, how much to trust it. | ||
| 193 | |||
| 194 | ### The full metadata contract per verb | ||
| 195 | |||
| 196 | `create`, `publish`, `move`, `trash`, `restore_from_trash`, `destroy` | ||
| 197 | — lives as a code comment directly above `record!` in | ||
| 198 | `app/models/node.rb`. That's the authoritative, current version; this | ||
| 199 | entry explains why it's shaped the way it is, not what it says field | ||
| 200 | by field. | ||
diff --git a/public/javascripts/admin_interface.js b/public/javascripts/admin_interface.js index b7bdc10..e9f96c2 100644 --- a/public/javascripts/admin_interface.js +++ b/public/javascripts/admin_interface.js | |||
| @@ -86,6 +86,27 @@ $(document).ready(function () { | |||
| 86 | if (meta) request.setRequestHeader("X-CSRF-Token", meta.content); | 86 | if (meta) request.setRequestHeader("X-CSRF-Token", meta.content); |
| 87 | }); | 87 | }); |
| 88 | 88 | ||
| 89 | document.addEventListener('click', function (event) { | ||
| 90 | var button = event.target.closest('.copy_button'); | ||
| 91 | if (!button) return; | ||
| 92 | |||
| 93 | var text = button.dataset.copyUrl; | ||
| 94 | if (text === undefined && button.dataset.copyTarget) { | ||
| 95 | var target = document.querySelector(button.dataset.copyTarget); | ||
| 96 | text = target ? target.textContent : undefined; | ||
| 97 | } | ||
| 98 | |||
| 99 | if (!text || text === '—' || !navigator.clipboard) return; | ||
| 100 | |||
| 101 | navigator.clipboard.writeText(text).then(function () { | ||
| 102 | var label = button.querySelector('.copy_button_label'); | ||
| 103 | if (!label) return; | ||
| 104 | var original = label.textContent; | ||
| 105 | label.textContent = 'Copied!'; | ||
| 106 | setTimeout(function () { label.textContent = original; }, 1500); | ||
| 107 | }); | ||
| 108 | }); | ||
| 109 | |||
| 89 | }); | 110 | }); |
| 90 | 111 | ||
| 91 | 112 | ||
diff --git a/public/javascripts/admin_search.js b/public/javascripts/admin_search.js index b53c931..ad368cb 100644 --- a/public/javascripts/admin_search.js +++ b/public/javascripts/admin_search.js | |||
| @@ -202,12 +202,6 @@ parent_search = { | |||
| 202 | $("#title").bind("input", function() { | 202 | $("#title").bind("input", function() { |
| 203 | parent_search.update_resulting_path(); | 203 | parent_search.update_resulting_path(); |
| 204 | }); | 204 | }); |
| 205 | |||
| 206 | $("#copy_resulting_path").bind("click", function() { | ||
| 207 | var path = $("#resulting_path").text(); | ||
| 208 | if (path === "—" || !navigator.clipboard) return; | ||
| 209 | navigator.clipboard.writeText(path); | ||
| 210 | }); | ||
| 211 | }, | 205 | }, |
| 212 | 206 | ||
| 213 | update_resulting_path : function() { | 207 | update_resulting_path : function() { |
diff --git a/public/stylesheets/ccc.css b/public/stylesheets/ccc.css index cbeea17..a07cc4d 100644 --- a/public/stylesheets/ccc.css +++ b/public/stylesheets/ccc.css | |||
| @@ -1,3 +1,55 @@ | |||
| 1 | /* ========================================================================== | ||
| 2 | ccc.css — annotated demolition survey (behavior-preserving rewrite) | ||
| 3 | ========================================================================== | ||
| 4 | Sorted into nine concerns; each banner carries an ASSUMES: line naming | ||
| 5 | the tokens and images it depends on. Within any equal-specificity | ||
| 6 | collision set, relative source order is preserved verbatim. | ||
| 7 | ========================================================================== */ | ||
| 8 | |||
| 9 | /* ========================================================================== | ||
| 10 | 1. DESIGN TOKENS | ||
| 11 | ========================================================================== | ||
| 12 | The desktop layout is a fixed lattice drawn around two images: | ||
| 13 | header.png (909px wide, 145px tall) and left_column.png (continues the | ||
| 14 | header artwork down the left menu band via a wrapper-covering ::before). | ||
| 15 | Change these tokens and the two images must be regenerated — that IS | ||
| 16 | the redesign. | ||
| 17 | NOTE: custom properties cannot appear in @media conditions, so the | ||
| 18 | 1015/1016 breakpoint below stays a literal pair. Its provenance is | ||
| 19 | unexplained (roughly --page-width plus a margin allowance). | ||
| 20 | ========================================================================== */ | ||
| 21 | |||
| 22 | :root { | ||
| 23 | --page-width: 909px; /* header.png's drawn width; the wrapper. | ||
| 24 | Decomposes: 185 left band (135 col + 50 pad) | ||
| 25 | + 490 center at left 200 + 210 right band | ||
| 26 | (155 col + 55 pad) at left 690. 690 + 210 | ||
| 27 | = 900: 9px absorbed silently at the right | ||
| 28 | edge. */ | ||
| 29 | --header-height: 145px; /* header.png's pixel height; search box and | ||
| 30 | light-mode toggle hang from its bottom | ||
| 31 | edge. */ | ||
| 32 | --left-col-width: 135px; /* left band = this + --left-col-pad = 185px */ | ||
| 33 | --left-col-pad: 50px; | ||
| 34 | --center-left: 200px; /* 15px gutter beyond the 185px left band */ | ||
| 35 | --center-width: 490px; | ||
| 36 | --right-left: 690px; /* = --center-left + --center-width */ | ||
| 37 | --right-width: 155px; /* right band = this + --right-pad = 210px */ | ||
| 38 | --right-pad: 55px; | ||
| 39 | --search-left: 676px; /* provenance unknown; sits 14px left of | ||
| 40 | --right-left */ | ||
| 41 | --toggle-left: 816px; /* provenance unknown; 140px right of | ||
| 42 | --search-left */ | ||
| 43 | --burger-right: 54px; /* provenance unknown */ | ||
| 44 | } | ||
| 45 | |||
| 46 | /* ========================================================================== | ||
| 47 | 2. RESET & DOCUMENT SCAFFOLD | ||
| 48 | ========================================================================== | ||
| 49 | ASSUMES: nothing from the lattice. Element defaults and generic | ||
| 50 | utilities only. | ||
| 51 | ========================================================================== */ | ||
| 52 | |||
| 1 | html { | 53 | html { |
| 2 | height: 100%; | 54 | height: 100%; |
| 3 | line-height: 1.6rem; | 55 | line-height: 1.6rem; |
| @@ -17,6 +69,54 @@ body { | |||
| 17 | height: 100%; | 69 | height: 100%; |
| 18 | } | 70 | } |
| 19 | 71 | ||
| 72 | img { | ||
| 73 | max-width: 100%; | ||
| 74 | } | ||
| 75 | |||
| 76 | pre { | ||
| 77 | overflow: auto; | ||
| 78 | } | ||
| 79 | |||
| 80 | li { | ||
| 81 | line-height: 1.5rem; | ||
| 82 | margin-block-start: 1rem; | ||
| 83 | margin-block-end: 1rem; | ||
| 84 | } | ||
| 85 | |||
| 86 | dt { | ||
| 87 | font-weight: bold; | ||
| 88 | margin-bottom: 1em; | ||
| 89 | } | ||
| 90 | |||
| 91 | dd { | ||
| 92 | margin-bottom: 1em; | ||
| 93 | } | ||
| 94 | |||
| 95 | .pagination { | ||
| 96 | margin-bottom: .5rem; | ||
| 97 | } | ||
| 98 | |||
| 99 | + /* Single caller: _headline_image.html.erb's image-count caption | ||
| 100 | + ("N Bilder"), shown only when a gallery has more than one image. */ | ||
| 101 | .right { | ||
| 102 | text-align: right; | ||
| 103 | } | ||
| 104 | |||
| 105 | .hide-me { | ||
| 106 | display: none; | ||
| 107 | } | ||
| 108 | |||
| 109 | /* ========================================================================== | ||
| 110 | 3. COLOR SCHEME & LIGHT/DARK TOGGLE | ||
| 111 | ========================================================================== | ||
| 112 | ASSUMES: --header-height, --toggle-left (desktop toggle position); | ||
| 113 | header.png and left_column.png (the invert(50%) treatment); the nonced | ||
| 114 | inline restore script in the layout that re-checks #light-mode before | ||
| 115 | first paint (the JS contract: checkbox id "light-mode", state persisted | ||
| 116 | by the script). Symmetric blocks: each scheme inverts when the toggle | ||
| 117 | is checked. | ||
| 118 | ========================================================================== */ | ||
| 119 | |||
| 20 | @media (prefers-color-scheme: light) { | 120 | @media (prefers-color-scheme: light) { |
| 21 | body:has(#light-mode:checked) { | 121 | body:has(#light-mode:checked) { |
| 22 | color-scheme: dark; | 122 | color-scheme: dark; |
| @@ -49,114 +149,221 @@ body { | |||
| 49 | } | 149 | } |
| 50 | } | 150 | } |
| 51 | 151 | ||
| 52 | img { | 152 | input#light-mode[type="checkbox"] { |
| 53 | max-width: 100%; | 153 | display: none; |
| 54 | } | 154 | } |
| 55 | 155 | ||
| 56 | div#header img { | 156 | label[for=light-mode] { |
| 57 | border: none; | 157 | font-size: 25px; |
| 158 | user-select: none; | ||
| 159 | cursor: pointer; | ||
| 160 | filter: grayscale(1) contrast(10%); | ||
| 58 | } | 161 | } |
| 59 | 162 | ||
| 60 | @media(min-width:1016px) { | 163 | /* Light and dark mode button magic */ |
| 61 | div#wrapper { | 164 | @media(max-width:1015px) { |
| 62 | position: relative; | 165 | div#light-mode-div { |
| 63 | width: 909px; | 166 | position: absolute; |
| 64 | margin: 0 auto 0 auto; | 167 | left: 170px; /* mobile toolbar offset; provenance unknown — anchors to |
| 65 | text-align: left; | 168 | the viewport, not #toolbox (see section 5) */ |
| 66 | min-height: 100%; | ||
| 67 | } | ||
| 68 | .break-mobile { | ||
| 69 | display: block; | ||
| 70 | } | 169 | } |
| 71 | } | 170 | } |
| 72 | 171 | ||
| 73 | pre { | 172 | @media(min-width:1016px) { |
| 74 | overflow: auto; | 173 | div#light-mode-div { |
| 75 | } | 174 | position: absolute; |
| 76 | 175 | top: var(--header-height); /* bottom edge of header.png */ | |
| 77 | .right { | 176 | left: var(--toggle-left); |
| 78 | text-align: right; | 177 | } |
| 79 | } | 178 | } |
| 80 | 179 | ||
| 81 | /*------------------links-------------------*/ | 180 | /* ========================================================================== |
| 181 | 4. LINKS & HEADINGS | ||
| 182 | ========================================================================== | ||
| 183 | ASSUMES: nothing from the lattice. Site-wide type and link colors; | ||
| 184 | the orange is the one brand color that survives theming. | ||
| 185 | ========================================================================== */ | ||
| 82 | 186 | ||
| 83 | a { | 187 | a { |
| 84 | color: #F8921E; | 188 | color: #F8921E; |
| 85 | text-decoration: none; | 189 | text-decoration: none; |
| 86 | } | 190 | } |
| 87 | 191 | ||
| 88 | a:visited { | ||
| 89 | color: #D1791A; | ||
| 90 | text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); | ||
| 91 | } | ||
| 92 | |||
| 93 | a:hover { | 192 | a:hover { |
| 94 | color: #5b8ca7; | 193 | color: #5b8ca7; |
| 95 | } | 194 | } |
| 96 | 195 | ||
| 97 | /*------------------headlines-------------------*/ | ||
| 98 | |||
| 99 | h2 { | 196 | h2 { |
| 100 | font-size: 1.5rem; | 197 | font-size: 1.5rem; |
| 101 | font-family: Helvetica, Arial, sans-serif; | 198 | font-family: Helvetica, Arial, sans-serif; |
| 102 | line-height: 1.75rem; | 199 | line-height: 1.75rem; |
| 103 | } | 200 | } |
| 104 | 201 | ||
| 105 | div#center_column h2 a { | 202 | h3 { |
| 106 | color: CanvasText; | 203 | font-size: 1.3rem; |
| 107 | text-decoration: none; | 204 | text-decoration: none; |
| 205 | |||
| 108 | } | 206 | } |
| 109 | 207 | ||
| 110 | div#center_column h2 a:hover { | 208 | h4 { |
| 111 | color: color-mix(in srgb, CanvasText, #808080 50%); | 209 | font-size: 1.0rem; |
| 210 | text-decoration: none; | ||
| 112 | } | 211 | } |
| 113 | 212 | ||
| 114 | div#center_column h2.headline { | 213 | h1, h2, h3 { |
| 115 | margin-top: 10px; | 214 | word-wrap: anywhere; |
| 215 | hyphens:auto; | ||
| 116 | } | 216 | } |
| 117 | 217 | ||
| 118 | div#center_column .article_partial h2.headline { | 218 | /* ========================================================================== |
| 119 | padding-top: 30px; | 219 | 5. LAYOUT GRID |
| 120 | margin-bottom: 0.3rem; | 220 | ========================================================================== |
| 121 | border-top: 2px solid #cccccc; | 221 | ASSUMES: the full token lattice; header.png (drawn width = --page-width, |
| 222 | height = --header-height); left_column.png (continues the header art | ||
| 223 | down the left band via the ::before at z-index -10). | ||
| 224 | KNOWN STRUCTURAL DEBT: all three columns are position:absolute on | ||
| 225 | desktop, but #footer lives INSIDE #center_column — a long sidebar can | ||
| 226 | overflow past the footer. The column-height assumption is nowhere | ||
| 227 | enforced. | ||
| 228 | MOBILE ANCHORING: on max-width, #wrapper has no position, so the | ||
| 229 | absolutely positioned toolbox children (#burger-div, #search, | ||
| 230 | #light-mode-div) anchor to the viewport, not to #toolbox. | ||
| 231 | ========================================================================== */ | ||
| 232 | |||
| 233 | @media(min-width:1016px) { | ||
| 234 | div#wrapper { | ||
| 235 | position: relative; | ||
| 236 | width: var(--page-width); /* header.png's drawn width */ | ||
| 237 | margin: 0 auto 0 auto; | ||
| 238 | text-align: left; | ||
| 239 | min-height: 100%; | ||
| 240 | } | ||
| 241 | .break-mobile { | ||
| 242 | display: block; | ||
| 243 | } | ||
| 122 | } | 244 | } |
| 123 | 245 | ||
| 124 | /* except for erfa list */ | 246 | /* Header */ |
| 125 | div#center_column .chapter_partial h2.headline { | 247 | #header img, |
| 126 | border-top: none; | 248 | #header > a { |
| 249 | display: block; | ||
| 250 | line-height: 0; | ||
| 127 | } | 251 | } |
| 128 | 252 | ||
| 129 | div.article_partial h2 a { | 253 | div#header img { |
| 130 | text-decoration: none; | 254 | border: none; |
| 131 | } | 255 | } |
| 132 | 256 | ||
| 133 | h3 { | 257 | #left_column { |
| 134 | font-size: 1.3rem; | 258 | line-height: 1.5em; |
| 135 | text-decoration: none; | 259 | } |
| 260 | |||
| 261 | @media(min-width:1016px) { | ||
| 262 | div#left_column::before { | ||
| 263 | content: " "; | ||
| 264 | position: absolute; | ||
| 265 | left: 0; | ||
| 266 | top: 0; | ||
| 267 | width: 100%; | ||
| 268 | height: 100%; | ||
| 269 | box-sizing: border-box; | ||
| 270 | background-image: url(/images/left_column.png); | ||
| 271 | background-position: top right; | ||
| 272 | background-repeat: no-repeat; | ||
| 273 | z-index: -10; | ||
| 274 | } | ||
| 275 | div#left_column { | ||
| 276 | position: absolute; | ||
| 277 | left: 0px; | ||
| 278 | width: var(--left-col-width); | ||
| 279 | min-height: 100px; | ||
| 280 | text-align: right; | ||
| 281 | padding-right: var(--left-col-pad); /* left band total: 185px */ | ||
| 282 | } | ||
| 136 | 283 | ||
| 284 | div#left_column > a { | ||
| 285 | font-size: 0.8rem; | ||
| 286 | } | ||
| 137 | } | 287 | } |
| 138 | 288 | ||
| 139 | h4 { | 289 | div#center_column { |
| 140 | font-size: 1.0rem; | 290 | padding-bottom: 40px; |
| 141 | text-decoration: none; | ||
| 142 | } | 291 | } |
| 143 | 292 | ||
| 144 | #left_column { | 293 | @media(max-width: 1015px) { |
| 145 | line-height: 1.5em; | 294 | div#center_column { |
| 295 | padding: 0 15px 40px 15px; | ||
| 296 | } | ||
| 146 | } | 297 | } |
| 147 | 298 | ||
| 148 | /*------------------main-navigation-------------------*/ | 299 | @media(min-width: 1016px) { |
| 300 | div#center_column { | ||
| 301 | position: absolute; | ||
| 302 | background-color: Canvas; | ||
| 303 | left: var(--center-left); | ||
| 304 | width: var(--center-width); | ||
| 305 | } | ||
| 306 | } | ||
| 307 | |||
| 308 | @media(min-width:1016px) { | ||
| 309 | div#right_column { | ||
| 310 | position: absolute; | ||
| 311 | background-color: Canvas; | ||
| 312 | padding-left: var(--right-pad); | ||
| 313 | left: var(--right-left); /* = center-left + center-width */ | ||
| 314 | width: var(--right-width); /* right band total: 210px; 690 + 210 = 900, | ||
| 315 | 9px short of --page-width, absorbed | ||
| 316 | silently */ | ||
| 317 | height: 100px; | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | /* Mobile toolbar strip. */ | ||
| 322 | @media(max-width:1015px) { | ||
| 323 | #toolbox { | ||
| 324 | height: 30px; | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | /* Footer */ | ||
| 329 | #footer { | ||
| 330 | border-bottom: 2px solid #aeadad; | ||
| 331 | border-top: 2px solid #aeadad; | ||
| 332 | bottom: 0; | ||
| 333 | color: CanvasText; | ||
| 334 | } | ||
| 335 | |||
| 336 | #footer p { | ||
| 337 | margin: .5rem auto; | ||
| 338 | } | ||
| 339 | |||
| 340 | #footer a { | ||
| 341 | margin-left: 1rem; | ||
| 342 | margin-right: 1rem; | ||
| 343 | color: CanvasText; | ||
| 344 | } | ||
| 345 | |||
| 346 | /* ========================================================================== | ||
| 347 | 6. MAIN NAVIGATION & BURGER MENU | ||
| 348 | ========================================================================== | ||
| 349 | ASSUMES: --burger-right; the checkbox-hack markup order in the layout | ||
| 350 | (#toolbox precedes #left_column — the `#toolbox ~ #left_column` | ||
| 351 | sibling selectors break if the layout reorders them); left_column.png | ||
| 352 | behind the desktop nav. Mobile menu is pure CSS: .menu-checkbox state | ||
| 353 | drives both the burger-to-X morph and the menu slide. | ||
| 354 | COLLISION SET (order preserved): div.main_navigation ul base rule | ||
| 355 | before its min-width override — the override's `padding: 0` must win. | ||
| 356 | ========================================================================== */ | ||
| 149 | 357 | ||
| 150 | div.main_navigation ul { | 358 | div.main_navigation ul { |
| 151 | margin-left: 0; | 359 | margin-left: 0; |
| 152 | padding-left: 0; | 360 | padding-left: 15px; /* dead duplicate `padding-left: 0` removed */ |
| 153 | padding-left: 15px; | ||
| 154 | padding-right: 15px; | 361 | padding-right: 15px; |
| 155 | text-align: left; | 362 | text-align: left; |
| 156 | } | 363 | } |
| 157 | 364 | ||
| 158 | 365 | ||
| 159 | @media(max-width:1016px) { | 366 | @media(max-width:1015px) { |
| 160 | div.main_navigation li:not(:first-child):before { | 367 | div.main_navigation li:not(:first-child):before { |
| 161 | content: '•'; | 368 | content: '•'; |
| 162 | margin-left: .3rem; | 369 | margin-left: .3rem; |
| @@ -203,7 +410,125 @@ div.main_navigation a.inactive, div.main_navigation span.inactive, div#left_colu | |||
| 203 | color: color-mix(in srgb, CanvasText, #808080 25%); | 410 | color: color-mix(in srgb, CanvasText, #808080 25%); |
| 204 | } | 411 | } |
| 205 | 412 | ||
| 206 | /*------------------calendar-featured-tags-------------------*/ | 413 | .menu-checkbox { |
| 414 | display: none; | ||
| 415 | } | ||
| 416 | |||
| 417 | .burger-menu { | ||
| 418 | display: none; | ||
| 419 | cursor: pointer; | ||
| 420 | } | ||
| 421 | |||
| 422 | #burger-div { | ||
| 423 | position: absolute; | ||
| 424 | right: var(--burger-right); /* anchors to the viewport on mobile, | ||
| 425 | see section 5 */ | ||
| 426 | } | ||
| 427 | |||
| 428 | /* Mobile styles */ | ||
| 429 | @media (max-width: 1015px) { | ||
| 430 | .burger-menu { | ||
| 431 | position: absolute; | ||
| 432 | display: flex; | ||
| 433 | flex-direction: column; | ||
| 434 | transition: transform 0.3s ease, opacity 0.3s ease; | ||
| 435 | } | ||
| 436 | |||
| 437 | .burger-menu span { | ||
| 438 | display: inline-flex; | ||
| 439 | width: 26px; | ||
| 440 | height: 4px; | ||
| 441 | background: color-mix(in srgb, CanvasText, #808080 25%); | ||
| 442 | border-radius: 2px; | ||
| 443 | margin: 3px 0; | ||
| 444 | transition: max-height 0.5s ease-in-out, opacity 0.5s ease-in-out, transform 0.5s ease-in-out; | ||
| 445 | } | ||
| 446 | |||
| 447 | #left_column .main_navigation { | ||
| 448 | width: 100%; | ||
| 449 | position: relative; | ||
| 450 | } | ||
| 451 | |||
| 452 | #toolbox ~ #left_column .main_navigation ul { | ||
| 453 | transition: max-height 0.2s ease-in-out, transform 0.4s ease-in-out, opacity 0.4s ease; | ||
| 454 | overflow: hidden; | ||
| 455 | text-align: center; | ||
| 456 | } | ||
| 457 | |||
| 458 | #toolbox ~ #left_column .main_navigation:first-of-type ul { | ||
| 459 | transform: translateY(-200px); | ||
| 460 | max-height: 0; | ||
| 461 | opacity: 0; | ||
| 462 | z-index: -10; | ||
| 463 | } | ||
| 464 | |||
| 465 | /* Show menu when checkbox is checked */ | ||
| 466 | #toolbox:has(.menu-checkbox:checked) ~ #left_column .main_navigation ul { | ||
| 467 | max-height: 400px; | ||
| 468 | opacity: 1; | ||
| 469 | transform: translateY(0); | ||
| 470 | } | ||
| 471 | |||
| 472 | .menu-checkbox:checked + .burger-menu span:nth-child(1) { transform: translateY(10px) rotate(45deg); } | ||
| 473 | .menu-checkbox:checked + .burger-menu span:nth-child(2) { opacity: 0; } | ||
| 474 | .menu-checkbox:checked + .burger-menu span:nth-child(3) { transform: translateY(-10px) rotate(-45deg); } | ||
| 475 | } | ||
| 476 | |||
| 477 | /* ========================================================================== | ||
| 478 | 7. SEARCH | ||
| 479 | ========================================================================== | ||
| 480 | ASSUMES: --header-height, --search-left (desktop box hangs from | ||
| 481 | header.png's bottom edge); viewport anchoring on mobile (section 5). | ||
| 482 | ========================================================================== */ | ||
| 483 | |||
| 484 | /* Search bar */ | ||
| 485 | @media(min-width:1016px) { | ||
| 486 | div#search { | ||
| 487 | position: absolute; | ||
| 488 | top: var(--header-height); /* bottom edge of header.png */ | ||
| 489 | left: var(--search-left); | ||
| 490 | height: 25px; | ||
| 491 | vertical-align: top; | ||
| 492 | } | ||
| 493 | } | ||
| 494 | |||
| 495 | @media(max-width:1015px) { | ||
| 496 | div#search { | ||
| 497 | position: absolute; | ||
| 498 | left: 25px; /* mobile toolbar offset; anchors to the viewport */ | ||
| 499 | height: 25px; | ||
| 500 | } | ||
| 501 | } | ||
| 502 | |||
| 503 | div#search input { | ||
| 504 | color: CanvasText !important; | ||
| 505 | } | ||
| 506 | div#search input[type=search], | ||
| 507 | div#search input[type=text] { | ||
| 508 | display: block; | ||
| 509 | padding: 2px; | ||
| 510 | margin: 0px; | ||
| 511 | height: 25px; | ||
| 512 | width: 132px; | ||
| 513 | line-height: 20px; | ||
| 514 | border: solid #808080 1px; | ||
| 515 | background-color: Canvas; | ||
| 516 | border-radius: 5px; | ||
| 517 | margin-right: 5px; | ||
| 518 | text-indent: 0.5rem; | ||
| 519 | } | ||
| 520 | |||
| 521 | /* ========================================================================== | ||
| 522 | 8. SIDEBAR WIDGETS (calendar, tags, featured, open erfas) | ||
| 523 | ========================================================================== | ||
| 524 | ASSUMES: right-column geometry from section 5; div.main_navigation h2 | ||
| 525 | also picks up the grouped border rule below. | ||
| 526 | COLLISION SET (order preserved verbatim): the grouped h2 border rule, | ||
| 527 | then the same-specificity div#frontpage_calendar h2 override | ||
| 528 | (border-top: none), then the min-width block that re-adds a border-top | ||
| 529 | for desktop. Moving the override above the group rule would grow a | ||
| 530 | border on the calendar heading. | ||
| 531 | ========================================================================== */ | ||
| 207 | 532 | ||
| 208 | div#frontpage_calendar { | 533 | div#frontpage_calendar { |
| 209 | display: none; | 534 | display: none; |
| @@ -246,7 +571,7 @@ div#open_erfas_today .event_time { | |||
| 246 | color: color-mix(in srgb, CanvasText, #808080); | 571 | color: color-mix(in srgb, CanvasText, #808080); |
| 247 | } | 572 | } |
| 248 | 573 | ||
| 249 | @media(max-width:1016px) { | 574 | @media(max-width:1015px) { |
| 250 | div#tags li { | 575 | div#tags li { |
| 251 | list-style-type: none; | 576 | list-style-type: none; |
| 252 | display: inline-block; | 577 | display: inline-block; |
| @@ -278,6 +603,8 @@ div#frontpage_calendar ul, div#tags ul, div#featured_articles ul, div#open_erfas | |||
| 278 | line-height: 1.5em; | 603 | line-height: 1.5em; |
| 279 | } | 604 | } |
| 280 | 605 | ||
| 606 | /* ODDITY, kept: per-widget icon paddings — delete when these icons rotate | ||
| 607 | out of the featured box */ | ||
| 281 | div#featured_articles #ds_icon img { | 608 | div#featured_articles #ds_icon img { |
| 282 | padding-top: 10px; | 609 | padding-top: 10px; |
| 283 | } | 610 | } |
| @@ -312,302 +639,74 @@ div#featured_articles img { | |||
| 312 | filter: grayscale(1); | 639 | filter: grayscale(1); |
| 313 | } | 640 | } |
| 314 | 641 | ||
| 315 | dt { | 642 | #tags ul li { |
| 316 | font-weight: bold; | 643 | margin-block-start: 0.5rem; |
| 317 | margin-bottom: 1em; | 644 | margin-block-end: 0.5rem; |
| 318 | } | ||
| 319 | |||
| 320 | dd { | ||
| 321 | margin-bottom: 1em; | ||
| 322 | } | ||
| 323 | |||
| 324 | /*--------------------------------------------------------------*/ | ||
| 325 | |||
| 326 | div.author_and_date { | ||
| 327 | font-style: italic; | ||
| 328 | font-family: Georgia; | ||
| 329 | color: color-mix(in srgb, CanvasText, #808080); | ||
| 330 | padding-top: .2rem; | ||
| 331 | padding-bottom: .8rem; | ||
| 332 | } | ||
| 333 | |||
| 334 | @media(min-width:1016px) { | ||
| 335 | div#left_column::before { | ||
| 336 | content: " "; | ||
| 337 | position: absolute; | ||
| 338 | left: 0; | ||
| 339 | top: 0; | ||
| 340 | width: 100%; | ||
| 341 | height: 100%; | ||
| 342 | box-sizing: border-box; | ||
| 343 | background-image: url(/images/left_column.png); | ||
| 344 | background-position: top right; | ||
| 345 | background-repeat: no-repeat; | ||
| 346 | z-index: -10; | ||
| 347 | } | ||
| 348 | div#left_column { | ||
| 349 | position: absolute; | ||
| 350 | left: 0px; | ||
| 351 | width: 135px; | ||
| 352 | min-height: 100px; | ||
| 353 | text-align: right; | ||
| 354 | padding-right: 50px; | ||
| 355 | } | ||
| 356 | |||
| 357 | div#left_column > a { | ||
| 358 | font-size: 0.8rem; | ||
| 359 | } | ||
| 360 | } | ||
| 361 | |||
| 362 | .menu-checkbox { | ||
| 363 | display: none; | ||
| 364 | } | ||
| 365 | |||
| 366 | .burger-menu { | ||
| 367 | display: none; | ||
| 368 | cursor: pointer; | ||
| 369 | } | ||
| 370 | |||
| 371 | #burger-div { | ||
| 372 | position: absolute; | ||
| 373 | right: 54px; | ||
| 374 | } | ||
| 375 | |||
| 376 | /* Mobile styles */ | ||
| 377 | @media (max-width: 1016px) { | ||
| 378 | .burger-menu { | ||
| 379 | position: absolute; | ||
| 380 | display: flex; | ||
| 381 | flex-direction: column; | ||
| 382 | transition: transform 0.3s ease, opacity 0.3s ease; | ||
| 383 | } | ||
| 384 | |||
| 385 | .burger-menu span { | ||
| 386 | display: inline-flex; | ||
| 387 | width: 26px; | ||
| 388 | height: 4px; | ||
| 389 | background: color-mix(in srgb, CanvasText, #808080 25%); | ||
| 390 | border-radius: 2px; | ||
| 391 | margin: 3px 0; | ||
| 392 | transition: max-height 0.5s ease-in-out, opacity 0.5s ease-in-out, transform 0.5s ease-in-out; | ||
| 393 | } | ||
| 394 | |||
| 395 | #left_column .main_navigation { | ||
| 396 | width: 100%; | ||
| 397 | position: relative; | ||
| 398 | } | ||
| 399 | |||
| 400 | #toolbox ~ #left_column .main_navigation ul { | ||
| 401 | transition: max-height 0.2s ease-in-out, transform 0.4s ease-in-out, opacity 0.4s ease; | ||
| 402 | overflow: hidden; | ||
| 403 | text-align: center; | ||
| 404 | } | ||
| 405 | |||
| 406 | #toolbox ~ #left_column .main_navigation:first-of-type ul { | ||
| 407 | transform: translateY(-200px); | ||
| 408 | max-height: 0; | ||
| 409 | opacity: 0; | ||
| 410 | z-index: -10; | ||
| 411 | } | ||
| 412 | |||
| 413 | /* Show menu when checkbox is checked */ | ||
| 414 | #toolbox:has(.menu-checkbox:checked) ~ #left_column .main_navigation ul { | ||
| 415 | max-height: 400px; | ||
| 416 | opacity: 1; | ||
| 417 | transform: translateY(0); | ||
| 418 | } | ||
| 419 | |||
| 420 | .menu-checkbox:checked + .burger-menu span:nth-child(1) { transform: translateY(10px) rotate(45deg); } | ||
| 421 | .menu-checkbox:checked + .burger-menu span:nth-child(2) { opacity: 0; } | ||
| 422 | .menu-checkbox:checked + .burger-menu span:nth-child(3) { transform: translateY(-10px) rotate(-45deg); } | ||
| 423 | } | ||
| 424 | |||
| 425 | div#center_column { | ||
| 426 | padding-bottom: 40px; | ||
| 427 | } | ||
| 428 | |||
| 429 | @media(max-width: 1016px) { | ||
| 430 | div#center_column { | ||
| 431 | padding: 0 15px 40px 15px; | ||
| 432 | } | ||
| 433 | } | ||
| 434 | |||
| 435 | @media(min-width: 1016px) { | ||
| 436 | div#center_column { | ||
| 437 | position: absolute; | ||
| 438 | background-color: Canvas; | ||
| 439 | left: 200px; | ||
| 440 | width: 490px; | ||
| 441 | } | ||
| 442 | } | ||
| 443 | |||
| 444 | @media(min-width:1016px) { | ||
| 445 | div#right_column { | ||
| 446 | position: absolute; | ||
| 447 | background-color: Canvas; | ||
| 448 | padding-left: 55px; | ||
| 449 | left: 690px; | ||
| 450 | width: 155px; | ||
| 451 | height: 100px; | ||
| 452 | } | ||
| 453 | } | ||
| 454 | |||
| 455 | div.teaser_ruler { | ||
| 456 | border-top: 1px solid #CCCCCC; | ||
| 457 | border-bottom: 1px dashed #CCCCCC; | ||
| 458 | height: 3px; | ||
| 459 | margin-top: 30px; | ||
| 460 | } | ||
| 461 | |||
| 462 | div.article_partial { | ||
| 463 | margin-bottom: 30px; | ||
| 464 | } | ||
| 465 | |||
| 466 | |||
| 467 | div.article_partial p.excerpt { | ||
| 468 | color: CanvasText; | ||
| 469 | } | ||
| 470 | |||
| 471 | /* Search bar */ | ||
| 472 | @media(min-width:1016px) { | ||
| 473 | div#search { | ||
| 474 | position: absolute; | ||
| 475 | top: 145px; | ||
| 476 | left: 676px; | ||
| 477 | height: 25px; | ||
| 478 | vertical-align: top; | ||
| 479 | } | ||
| 480 | } | ||
| 481 | |||
| 482 | @media(max-width:1016px) { | ||
| 483 | div#search { | ||
| 484 | position: absolute; | ||
| 485 | left: 25px; | ||
| 486 | height: 25px; | ||
| 487 | } | ||
| 488 | } | ||
| 489 | |||
| 490 | div#search input { | ||
| 491 | color: CanvasText !important; | ||
| 492 | } | ||
| 493 | div#search input[type=search], | ||
| 494 | div#search input[type=text] { | ||
| 495 | display: block; | ||
| 496 | padding: 2px; | ||
| 497 | margin: 0px; | ||
| 498 | height: 25px; | ||
| 499 | width: 132px; | ||
| 500 | line-height: 20px; | ||
| 501 | border: solid #808080 1px; | ||
| 502 | background-color: Canvas; | ||
| 503 | border-radius: 5px; | ||
| 504 | margin-right: 5px; | ||
| 505 | text-indent: 0.5rem; | ||
| 506 | } | 645 | } |
| 507 | 646 | ||
| 508 | /* Header */ | 647 | /* ========================================================================== |
| 509 | #header img, | 648 | 9. ARTICLE CONTENT |
| 510 | #header > a { | 649 | ========================================================================== |
| 511 | display: block; | 650 | ASSUMES: center-column geometry from section 5; TinyMCE emits the |
| 512 | line-height: 0; | 651 | inline-image classes (style-src unsafe-inline in the CSP exists partly |
| 513 | } | 652 | for its img[style] output). |
| 653 | ========================================================================== */ | ||
| 514 | 654 | ||
| 515 | /* Main section */ | 655 | /* Main section */ |
| 516 | .article, .article_partial { | 656 | .article, .article_partial { |
| 517 | text-align: left; | 657 | text-align: left; |
| 518 | } | 658 | } |
| 519 | 659 | ||
| 520 | h1, h2, h3 { | 660 | div#center_column h2 a { |
| 521 | word-wrap: anywhere; | ||
| 522 | hyphens:auto; | ||
| 523 | } | ||
| 524 | |||
| 525 | .pagination { | ||
| 526 | margin-bottom: .5rem; | ||
| 527 | } | ||
| 528 | |||
| 529 | li { | ||
| 530 | line-height: 1.5rem; | ||
| 531 | margin-block-start: 1rem; | ||
| 532 | margin-block-end: 1rem; | ||
| 533 | } | ||
| 534 | |||
| 535 | #tags ul li { | ||
| 536 | margin-block-start: 0.5rem; | ||
| 537 | margin-block-end: 0.5rem; | ||
| 538 | } | ||
| 539 | |||
| 540 | /* Footer */ | ||
| 541 | #footer { | ||
| 542 | border-bottom: 2px solid #aeadad; | ||
| 543 | border-top: 2px solid #aeadad; | ||
| 544 | bottom: 0; | ||
| 545 | color: CanvasText; | 661 | color: CanvasText; |
| 662 | text-decoration: none; | ||
| 546 | } | 663 | } |
| 547 | 664 | ||
| 548 | #footer > br { | 665 | div#center_column h2 a:hover { |
| 549 | display: none; | 666 | color: color-mix(in srgb, CanvasText, #808080 50%); |
| 550 | } | 667 | } |
| 551 | 668 | ||
| 552 | #footer p { | 669 | div#center_column h2.headline { |
| 553 | margin: .5rem auto; | 670 | margin-top: 10px; |
| 554 | } | 671 | } |
| 555 | 672 | ||
| 556 | #footer a { | 673 | div#center_column .article_partial h2.headline { |
| 557 | margin-left: 1rem; | 674 | padding-top: 30px; |
| 558 | margin-right: 1rem; | 675 | margin-bottom: 0.3rem; |
| 559 | color: CanvasText; | 676 | border-top: 2px solid #cccccc; |
| 560 | } | 677 | } |
| 561 | 678 | ||
| 562 | @media(max-width:1016px) { | 679 | /* except for erfa list */ |
| 563 | #toolbox { | 680 | div#center_column .chapter_partial h2.headline { |
| 564 | display: relative; | 681 | border-top: none; |
| 565 | height: 30px; | ||
| 566 | } | ||
| 567 | } | 682 | } |
| 568 | 683 | ||
| 569 | /* Light and dark mode button magic */ | 684 | div.article_partial h2 a { |
| 570 | @media(max-width:1016px) { | 685 | text-decoration: none; |
| 571 | div#light-mode-div { | ||
| 572 | position: absolute; | ||
| 573 | left: 170px; | ||
| 574 | } | ||
| 575 | } | 686 | } |
| 576 | 687 | ||
| 577 | @media(min-width:1016px) { | 688 | div.article_partial { |
| 578 | div#light-mode-div { | 689 | margin-bottom: 30px; |
| 579 | position: absolute; | ||
| 580 | top: 145px; | ||
| 581 | left: 816px; | ||
| 582 | } | ||
| 583 | } | ||
| 584 | input#light-mode[type="checkbox"] { | ||
| 585 | display: none; | ||
| 586 | } | 690 | } |
| 587 | 691 | ||
| 588 | label[for=light-mode] { | ||
| 589 | font-size: 25px; | ||
| 590 | user-select: none; | ||
| 591 | cursor: pointer; | ||
| 592 | filter: grayscale(1) contrast(10%); | ||
| 593 | } | ||
| 594 | 692 | ||
| 595 | .hide-me { | 693 | div.article_partial p.excerpt { |
| 596 | display: none; | 694 | color: CanvasText; |
| 597 | } | 695 | } |
| 598 | 696 | ||
| 599 | /* Temporary glowing style for easterhegg 22 */ | 697 | div.author_and_date { |
| 600 | #eh22_icon img { | 698 | font-style: italic; |
| 601 | animation: animate 3s linear infinite; | 699 | font-family: Georgia; |
| 700 | color: color-mix(in srgb, CanvasText, #808080); | ||
| 701 | padding-top: .2rem; | ||
| 702 | padding-bottom: .8rem; | ||
| 602 | } | 703 | } |
| 603 | 704 | ||
| 604 | @keyframes animate { | 705 | div.teaser_ruler { |
| 605 | from { | 706 | border-top: 1px solid #CCCCCC; |
| 606 | filter: sepia(100%) hue-rotate(0deg); | 707 | border-bottom: 1px dashed #CCCCCC; |
| 607 | } | 708 | height: 3px; |
| 608 | to { | 709 | margin-top: 30px; |
| 609 | filter: sepia(100%) hue-rotate(360deg); | ||
| 610 | } | ||
| 611 | } | 710 | } |
| 612 | 711 | ||
| 613 | .chapter_partial_layout { | 712 | .chapter_partial_layout { |
diff --git a/public/stylesheets/ccc.css_ b/public/stylesheets/ccc.css_ deleted file mode 100644 index 9c5dbba..0000000 --- a/public/stylesheets/ccc.css_ +++ /dev/null | |||
| @@ -1,579 +0,0 @@ | |||
| 1 | html { | ||
| 2 | height: 100%; | ||
| 3 | line-height: 1.6rem; | ||
| 4 | } | ||
| 5 | |||
| 6 | body { | ||
| 7 | margin: 0; | ||
| 8 | padding: 0; | ||
| 9 | text-align: center; | ||
| 10 | font-family: Verdana, Helvetica, Arial, sans-serif; | ||
| 11 | background-color: Canvas; | ||
| 12 | color: color-mix(in srgb, CanvasText, #808080 25%); | ||
| 13 | hyphens: auto; | ||
| 14 | color-scheme: light dark; | ||
| 15 | |||
| 16 | min-height: 100%; | ||
| 17 | height: 100%; | ||
| 18 | } | ||
| 19 | |||
| 20 | @media (prefers-color-scheme: light) { | ||
| 21 | body:has(#light-mode:checked) { | ||
| 22 | color-scheme: dark; | ||
| 23 | } | ||
| 24 | #light-mode + label[for=light-mode]:before { content: '🌙'; } | ||
| 25 | #light-mode:checked + label[for=light-mode]:before { content: '☀️'; } | ||
| 26 | } | ||
| 27 | |||
| 28 | @media (prefers-color-scheme: dark) { | ||
| 29 | body:has(#light-mode:checked) { | ||
| 30 | color-scheme: light; | ||
| 31 | } | ||
| 32 | #light-mode + label[for=light-mode]:before { content: '☀️'; } | ||
| 33 | #light-mode:checked + label[for=light-mode]:before { content: '🌙'; } | ||
| 34 | |||
| 35 | /* The header images were set in the before times. Let them shine in dark | ||
| 36 | mode, too */ | ||
| 37 | body:not(:has(#light-mode:checked)) #header img, | ||
| 38 | body:not(:has(#light-mode:checked)) div#left_column::before | ||
| 39 | { | ||
| 40 | filter: invert(50%); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | img { | ||
| 45 | max-width: 100%; | ||
| 46 | } | ||
| 47 | |||
| 48 | div#header img { | ||
| 49 | border: none; | ||
| 50 | } | ||
| 51 | |||
| 52 | @media(min-width:1016px) { | ||
| 53 | div#wrapper { | ||
| 54 | position: relative; | ||
| 55 | width: 909px; | ||
| 56 | margin: 0 auto 0 auto; | ||
| 57 | text-align: left; | ||
| 58 | min-height: 100%; | ||
| 59 | } | ||
| 60 | .break-mobile { | ||
| 61 | display: block; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | pre { | ||
| 66 | overflow: auto; | ||
| 67 | } | ||
| 68 | |||
| 69 | .right { | ||
| 70 | text-align: right; | ||
| 71 | } | ||
| 72 | |||
| 73 | /*------------------links-------------------*/ | ||
| 74 | |||
| 75 | a { | ||
| 76 | color: #F8921E; | ||
| 77 | text-decoration: none; | ||
| 78 | } | ||
| 79 | |||
| 80 | a:visited { | ||
| 81 | color: #D1791A; | ||
| 82 | text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); | ||
| 83 | } | ||
| 84 | |||
| 85 | a:hover { | ||
| 86 | color: #5b8ca7; | ||
| 87 | } | ||
| 88 | |||
| 89 | /*------------------headlines-------------------*/ | ||
| 90 | |||
| 91 | h2 { | ||
| 92 | font-size: 1.5rem; | ||
| 93 | font-family: Helvetica, Arial, sans-serif; | ||
| 94 | line-height: 1.75rem; | ||
| 95 | } | ||
| 96 | |||
| 97 | div#center_column h2 a { | ||
| 98 | color: CanvasText; | ||
| 99 | text-decoration: none; | ||
| 100 | } | ||
| 101 | |||
| 102 | div#center_column h2 a:hover { | ||
| 103 | color: color-mix(in srgb, CanvasText, #808080 50%); | ||
| 104 | } | ||
| 105 | |||
| 106 | div#center_column h2.headline { | ||
| 107 | margin-top: 10px; | ||
| 108 | } | ||
| 109 | |||
| 110 | div#center_column .article_partial h2.headline { | ||
| 111 | padding-top: 30px; | ||
| 112 | margin-bottom: 0.3rem; | ||
| 113 | border-top: 2px solid #cccccc; | ||
| 114 | } | ||
| 115 | |||
| 116 | div.article_partial h2 a { | ||
| 117 | text-decoration: none; | ||
| 118 | } | ||
| 119 | |||
| 120 | h3 { | ||
| 121 | font-size: 1.3rem; | ||
| 122 | text-decoration: none; | ||
| 123 | |||
| 124 | } | ||
| 125 | |||
| 126 | h4 { | ||
| 127 | font-size: 1.0rem; | ||
| 128 | text-decoration: none; | ||
| 129 | } | ||
| 130 | |||
| 131 | #left_column { | ||
| 132 | line-height: 1.5em; | ||
| 133 | } | ||
| 134 | |||
| 135 | /*------------------main-navigation-------------------*/ | ||
| 136 | |||
| 137 | div.main_navigation ul { | ||
| 138 | margin-left: 0; | ||
| 139 | padding-left: 0; | ||
| 140 | padding-left: 15px; | ||
| 141 | padding-right: 15px; | ||
| 142 | text-align: left; | ||
| 143 | } | ||
| 144 | |||
| 145 | |||
| 146 | @media(max-width:1016px) { | ||
| 147 | div.main_navigation li:not(:first-child):before { | ||
| 148 | content: '•'; | ||
| 149 | margin-left: .3rem; | ||
| 150 | margin-right: .6rem; | ||
| 151 | } | ||
| 152 | |||
| 153 | div.main_navigation li { | ||
| 154 | list-style-type: none; | ||
| 155 | display: inline-block; | ||
| 156 | line-height: .75rem; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | @media(min-width:1016px) { | ||
| 161 | div.main_navigation ul { | ||
| 162 | padding: 0; | ||
| 163 | text-align: right; | ||
| 164 | } | ||
| 165 | |||
| 166 | div.main_navigation li { | ||
| 167 | list-style-type: none; | ||
| 168 | line-height: 1.45em; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | div.main_navigation a { | ||
| 173 | text-decoration: none; | ||
| 174 | } | ||
| 175 | |||
| 176 | div.main_navigation a.inactive:hover, div#left_column span.inactive:hover { | ||
| 177 | color: color-mix(in srgb, CanvasText, #808080 50%); | ||
| 178 | } | ||
| 179 | |||
| 180 | div.main_navigation a.active:before { | ||
| 181 | content: "▸"; | ||
| 182 | } | ||
| 183 | |||
| 184 | div.main_navigation a.active { | ||
| 185 | color: CanvasText; | ||
| 186 | text-decoration: none; | ||
| 187 | } | ||
| 188 | |||
| 189 | div.main_navigation a.inactive, div.main_navigation span.inactive, div#left_column span.inactive { | ||
| 190 | color: color-mix(in srgb, CanvasText, #808080 25%); | ||
| 191 | } | ||
| 192 | |||
| 193 | /*------------------calendar-featured-tags-------------------*/ | ||
| 194 | |||
| 195 | div#frontpage_calendar { | ||
| 196 | display: none; | ||
| 197 | margin-top: 10px; | ||
| 198 | } | ||
| 199 | |||
| 200 | @media(min-width:1016px) { | ||
| 201 | div#frontpage_calendar { | ||
| 202 | margin-top: 30px; | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | div#frontpage_calendar h2, div#tags h2, div#featured_articles h2, div.main_navigation h2 { | ||
| 207 | border-top: 2px solid; | ||
| 208 | border-bottom: 2px solid; | ||
| 209 | font-size: 1.1em; | ||
| 210 | padding-top: 2px; | ||
| 211 | padding-bottom: 2px; | ||
| 212 | } | ||
| 213 | |||
| 214 | div#frontpage_calendar h2 { | ||
| 215 | display: none; | ||
| 216 | border-top: none; | ||
| 217 | margin-left: auto; | ||
| 218 | margin-right: auto; | ||
| 219 | padding-left: 0.5em; | ||
| 220 | padding-right: 0.5em; | ||
| 221 | margin-top: 0; | ||
| 222 | } | ||
| 223 | |||
| 224 | @media(max-width:1016px) { | ||
| 225 | div#tags li { | ||
| 226 | list-style-type: none; | ||
| 227 | display: inline-block; | ||
| 228 | } | ||
| 229 | |||
| 230 | div#tags li:not(:first-child):before { | ||
| 231 | content: '•'; | ||
| 232 | margin-left: .3em; | ||
| 233 | margin-right: .6em; | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | @media(min-width:1016px) { | ||
| 238 | div#frontpage_calendar h2, div#tags h2, div#featured_articles h2 { | ||
| 239 | font-size: 1rem; | ||
| 240 | } | ||
| 241 | |||
| 242 | div#frontpage_calendar h2 { | ||
| 243 | display: block; | ||
| 244 | border-top: 2px solid #aeadad; | ||
| 245 | padding: 2px 0; | ||
| 246 | } | ||
| 247 | |||
| 248 | } | ||
| 249 | |||
| 250 | div#frontpage_calendar ul, div#tags ul, div#featured_articles ul { | ||
| 251 | padding: 0px; | ||
| 252 | font-size: 1rem; | ||
| 253 | line-height: 1.5em; | ||
| 254 | } | ||
| 255 | |||
| 256 | div#featured_articles #ds_icon img { | ||
| 257 | padding-top: 10px; | ||
| 258 | } | ||
| 259 | |||
| 260 | div#featured_articles #events_icon img { | ||
| 261 | padding-left: 10px; | ||
| 262 | } | ||
| 263 | |||
| 264 | div#frontpage_calendar li { | ||
| 265 | margin-bottom: 20px; | ||
| 266 | } | ||
| 267 | |||
| 268 | div#frontpage_calendar li, div#tags li, div#featured_articles li { | ||
| 269 | list-style-type: none; | ||
| 270 | } | ||
| 271 | |||
| 272 | div#frontpage_calendar li a, div#tags li a, div#featured_articles li a { | ||
| 273 | text-decoration: none; | ||
| 274 | color: color-mix(in srgb, CanvasText, #808080 25%); | ||
| 275 | } | ||
| 276 | |||
| 277 | div#frontpage_calendar li a:hover, | ||
| 278 | div#tags li a:hover, | ||
| 279 | div#featured_articles li a:hover { | ||
| 280 | text-decoration: none; | ||
| 281 | color: color-mix(in srgb, CanvasText, #808080 50%); | ||
| 282 | } | ||
| 283 | |||
| 284 | /* We don't want the only colourful thing on the site to be pointing | ||
| 285 | * somewhere else */ | ||
| 286 | div#featured_articles img { | ||
| 287 | filter: grayscale(1); | ||
| 288 | } | ||
| 289 | |||
| 290 | dt { | ||
| 291 | font-weight: bold; | ||
| 292 | margin-bottom: 1em; | ||
| 293 | } | ||
| 294 | |||
| 295 | dd { | ||
| 296 | margin-bottom: 1em; | ||
| 297 | } | ||
| 298 | |||
| 299 | /*--------------------------------------------------------------*/ | ||
| 300 | |||
| 301 | div.author_and_date { | ||
| 302 | font-style: italic; | ||
| 303 | font-family: Georgia; | ||
| 304 | color: color-mix(in srgb, CanvasText, #808080); | ||
| 305 | padding-top: 0; | ||
| 306 | padding-bottom: 1rem; | ||
| 307 | } | ||
| 308 | |||
| 309 | @media(min-width:1016px) { | ||
| 310 | div#left_column::before { | ||
| 311 | content: " "; | ||
| 312 | position: absolute; | ||
| 313 | left: 0; | ||
| 314 | top: 0; | ||
| 315 | width: 100%; | ||
| 316 | height: 100%; | ||
| 317 | box-sizing: border-box; | ||
| 318 | background-image: url(/images/left_column.png); | ||
| 319 | background-position: top right; | ||
| 320 | background-repeat: no-repeat; | ||
| 321 | z-index: -10; | ||
| 322 | } | ||
| 323 | div#left_column { | ||
| 324 | position: absolute; | ||
| 325 | left: 0px; | ||
| 326 | width: 135px; | ||
| 327 | min-height: 100px; | ||
| 328 | text-align: right; | ||
| 329 | padding-right: 50px; | ||
| 330 | } | ||
| 331 | |||
| 332 | div#left_column > a { | ||
| 333 | font-size: 0.8rem; | ||
| 334 | } | ||
| 335 | } | ||
| 336 | |||
| 337 | .menu-checkbox { | ||
| 338 | display: none; | ||
| 339 | } | ||
| 340 | |||
| 341 | .burger-menu { | ||
| 342 | display: none; | ||
| 343 | cursor: pointer; | ||
| 344 | } | ||
| 345 | |||
| 346 | /* Mobile styles */ | ||
| 347 | @media (max-width: 1016px) { | ||
| 348 | .main_navigation { | ||
| 349 | width: 100%; | ||
| 350 | position: relative; | ||
| 351 | } | ||
| 352 | |||
| 353 | .main_navigation ul { | ||
| 354 | transition: max-height 0.5s ease-in-out, transform .4s ease, opacity 0.4s ease; | ||
| 355 | overflow: hidden; | ||
| 356 | text-align: center; | ||
| 357 | } | ||
| 358 | |||
| 359 | |||
| 360 | .burger-menu { | ||
| 361 | position: absolute; | ||
| 362 | top: -48px; | ||
| 363 | right: 30px; | ||
| 364 | display: flex; | ||
| 365 | flex-direction: column; | ||
| 366 | transition: transform 0.3s ease, opacity 0.3s ease; | ||
| 367 | } | ||
| 368 | |||
| 369 | .burger-menu span { | ||
| 370 | display: inline-flex; | ||
| 371 | width: 30px; | ||
| 372 | height: 4px; | ||
| 373 | background: color-mix(in srgb, CanvasText, #808080 25%); | ||
| 374 | border-radius: 2px; | ||
| 375 | margin: 3px 0; | ||
| 376 | transition: max-height 0.5s ease-in-out, opacity 0.5s ease-in-out, transform 0.5s ease-in-out; | ||
| 377 | } | ||
| 378 | .main_navigation .menu-checkbox:not(:checked) ~ ul { | ||
| 379 | transform: translateY(-60px); | ||
| 380 | max-height: 0; | ||
| 381 | opacity: 0; | ||
| 382 | } | ||
| 383 | |||
| 384 | /* Show menu when checkbox is checked */ | ||
| 385 | .main_navigation .menu-checkbox ~ ul { | ||
| 386 | max-height: 400px; | ||
| 387 | opacity: 1; | ||
| 388 | transform: translateY(0); | ||
| 389 | text-align: center; | ||
| 390 | } | ||
| 391 | |||
| 392 | .menu-checkbox:checked + .burger-menu span:nth-child(1) { transform: translateY(10px) rotate(45deg); } | ||
| 393 | .menu-checkbox:checked + .burger-menu span:nth-child(2) { opacity: 0; } | ||
| 394 | .menu-checkbox:checked + .burger-menu span:nth-child(3) { transform: translateY(-10px) rotate(-45deg); } | ||
| 395 | } | ||
| 396 | |||
| 397 | div#center_column { | ||
| 398 | padding-bottom: 40px; | ||
| 399 | } | ||
| 400 | |||
| 401 | @media(max-width: 1016px) { | ||
| 402 | div#center_column { | ||
| 403 | padding: 0 15px 40px 15px; | ||
| 404 | } | ||
| 405 | } | ||
| 406 | |||
| 407 | @media(min-width: 1016px) { | ||
| 408 | div#center_column { | ||
| 409 | position: absolute; | ||
| 410 | background-color: Canvas; | ||
| 411 | left: 200px; | ||
| 412 | width: 490px; | ||
| 413 | } | ||
| 414 | } | ||
| 415 | |||
| 416 | @media(min-width:1016px) { | ||
| 417 | div#right_column { | ||
| 418 | position: absolute; | ||
| 419 | background-color: Canvas; | ||
| 420 | padding-left: 55px; | ||
| 421 | left: 690px; | ||
| 422 | width: 155px; | ||
| 423 | height: 100px; | ||
| 424 | } | ||
| 425 | } | ||
| 426 | |||
| 427 | div.teaser_ruler { | ||
| 428 | border-top: 1px solid #CCCCCC; | ||
| 429 | border-bottom: 1px dashed #CCCCCC; | ||
| 430 | height: 3px; | ||
| 431 | margin-top: 30px; | ||
| 432 | } | ||
| 433 | |||
| 434 | div.article_partial { | ||
| 435 | margin-bottom: 30px; | ||
| 436 | } | ||
| 437 | |||
| 438 | |||
| 439 | div.article_partial p.excerpt { | ||
| 440 | color: CanvasText; | ||
| 441 | } | ||
| 442 | |||
| 443 | /* Search bar */ | ||
| 444 | @media(min-width:1016px) { | ||
| 445 | div#search { | ||
| 446 | position: absolute; | ||
| 447 | top: 145px; | ||
| 448 | left: 676px; | ||
| 449 | height: 25px; | ||
| 450 | vertical-align: top; | ||
| 451 | } | ||
| 452 | } | ||
| 453 | |||
| 454 | @media(max-width:1016px) { | ||
| 455 | div#search { | ||
| 456 | position: relative; | ||
| 457 | bottom: 5px; | ||
| 458 | left: 0px; | ||
| 459 | margin-left: 25px; | ||
| 460 | height: 25px; | ||
| 461 | vertical-align: top; | ||
| 462 | } | ||
| 463 | } | ||
| 464 | |||
| 465 | div#search input { | ||
| 466 | color: CanvasText !important; | ||
| 467 | } | ||
| 468 | div#search input[type=search], | ||
| 469 | div#search input[type=text] { | ||
| 470 | display: block; | ||
| 471 | padding: 2px; | ||
| 472 | margin: 0px; | ||
| 473 | height: 25px; | ||
| 474 | width: 132px; | ||
| 475 | line-height: 20px; | ||
| 476 | border: solid #808080 1px; | ||
| 477 | background-color: Canvas; | ||
| 478 | border-radius: 5px; | ||
| 479 | margin-right: 5px; | ||
| 480 | text-indent: 0.5rem; | ||
| 481 | } | ||
| 482 | |||
| 483 | /* Header */ | ||
| 484 | #header img, | ||
| 485 | #header > a { | ||
| 486 | display: block; | ||
| 487 | line-height: 0; | ||
| 488 | } | ||
| 489 | |||
| 490 | /* Main section */ | ||
| 491 | .article, .article_partial { | ||
| 492 | text-align: left; | ||
| 493 | } | ||
| 494 | |||
| 495 | h1, h2, h3 { | ||
| 496 | word-wrap: anywhere; | ||
| 497 | hyphens:auto; | ||
| 498 | } | ||
| 499 | |||
| 500 | .pagination { | ||
| 501 | margin-bottom: .5rem; | ||
| 502 | } | ||
| 503 | |||
| 504 | li { | ||
| 505 | line-height: 1.5rem; | ||
| 506 | margin-block-start: 1rem; | ||
| 507 | margin-block-end: 1rem; | ||
| 508 | } | ||
| 509 | |||
| 510 | #tags ul li { | ||
| 511 | margin-block-start: 0.5rem; | ||
| 512 | margin-block-end: 0.5rem; | ||
| 513 | } | ||
| 514 | |||
| 515 | /* Footer */ | ||
| 516 | #footer { | ||
| 517 | border-bottom: 2px solid #aeadad; | ||
| 518 | border-top: 2px solid #aeadad; | ||
| 519 | bottom: 0; | ||
| 520 | color: CanvasText; | ||
| 521 | } | ||
| 522 | |||
| 523 | #footer > br { | ||
| 524 | display: none; | ||
| 525 | } | ||
| 526 | |||
| 527 | #footer p { | ||
| 528 | margin: .5rem auto; | ||
| 529 | } | ||
| 530 | |||
| 531 | #footer a { | ||
| 532 | margin-left: 1rem; | ||
| 533 | margin-right: 1rem; | ||
| 534 | color: CanvasText; | ||
| 535 | } | ||
| 536 | |||
| 537 | /* Light and dark mode button magic */ | ||
| 538 | @media(max-width:1016px) { | ||
| 539 | div#light-mode-li { | ||
| 540 | position: absolute; | ||
| 541 | top: 145px; | ||
| 542 | left: 25px; | ||
| 543 | } | ||
| 544 | } | ||
| 545 | |||
| 546 | @media(min-width:1016px) { | ||
| 547 | div#light-mode-li { | ||
| 548 | position: absolute; | ||
| 549 | top: 145px; | ||
| 550 | left: 816px; | ||
| 551 | } | ||
| 552 | } | ||
| 553 | input#light-mode[type="checkbox"] { | ||
| 554 | display: none; | ||
| 555 | } | ||
| 556 | |||
| 557 | label[for=light-mode] { | ||
| 558 | font-size: 25px; | ||
| 559 | user-select: none; | ||
| 560 | filter: grayscale(1); | ||
| 561 | } | ||
| 562 | |||
| 563 | .hide-me { | ||
| 564 | display: none; | ||
| 565 | } | ||
| 566 | |||
| 567 | /* Temporary glowing style for easterhegg 22 */ | ||
| 568 | #eh22_icon img { | ||
| 569 | animation: animate 3s linear infinite; | ||
| 570 | } | ||
| 571 | |||
| 572 | @keyframes animate { | ||
| 573 | from { | ||
| 574 | filter: sepia(100%) hue-rotate(0deg); | ||
| 575 | } | ||
| 576 | to { | ||
| 577 | filter: sepia(100%) hue-rotate(360deg); | ||
| 578 | } | ||
| 579 | } | ||
diff --git a/public/stylesheets/ccc.css_old b/public/stylesheets/ccc.css_old deleted file mode 100644 index 93651b9..0000000 --- a/public/stylesheets/ccc.css_old +++ /dev/null | |||
| @@ -1,257 +0,0 @@ | |||
| 1 | body { | ||
| 2 | background-color: #FFFFFF; | ||
| 3 | margin: 0; | ||
| 4 | padding: 0; | ||
| 5 | text-align: center; | ||
| 6 | font-family: Verdana, Helvetica, Arial, sans-serif; | ||
| 7 | font-size: 11px; | ||
| 8 | line-height: 16px; | ||
| 9 | background-color: #ffffff; | ||
| 10 | color: #535353; | ||
| 11 | } | ||
| 12 | |||
| 13 | div#header { | ||
| 14 | height: 224px; | ||
| 15 | } | ||
| 16 | |||
| 17 | div#header img { | ||
| 18 | border: none; | ||
| 19 | } | ||
| 20 | |||
| 21 | div#wrapper { | ||
| 22 | position: relative; | ||
| 23 | width: 909px; | ||
| 24 | margin-left: 90px; | ||
| 25 | text-align: left; | ||
| 26 | } | ||
| 27 | |||
| 28 | pre { | ||
| 29 | overflow: auto; | ||
| 30 | } | ||
| 31 | |||
| 32 | .right { | ||
| 33 | text-align: right; | ||
| 34 | } | ||
| 35 | |||
| 36 | /*------------------links-------------------*/ | ||
| 37 | |||
| 38 | a { | ||
| 39 | color: #F8921E; | ||
| 40 | text-decoration: none; | ||
| 41 | } | ||
| 42 | |||
| 43 | a:visited { | ||
| 44 | color: #5b8ca7; | ||
| 45 | } | ||
| 46 | |||
| 47 | a:hover { | ||
| 48 | color: #5b8ca7; | ||
| 49 | } | ||
| 50 | |||
| 51 | /*------------------headlines-------------------*/ | ||
| 52 | |||
| 53 | h2 { | ||
| 54 | font-size: 15px; | ||
| 55 | line-height: 20px; | ||
| 56 | font-family: Helvetica; | ||
| 57 | } | ||
| 58 | |||
| 59 | div#center_column h2 a { | ||
| 60 | color: #535353; | ||
| 61 | text-decoration: none; | ||
| 62 | } | ||
| 63 | |||
| 64 | div#center_column h2 a:hover { | ||
| 65 | color: #8e8e8e; | ||
| 66 | } | ||
| 67 | |||
| 68 | div#center_column h2.headline { | ||
| 69 | margin-top: 10px; | ||
| 70 | padding-top: 30px; | ||
| 71 | border-top: 2px solid #cccccc; | ||
| 72 | } | ||
| 73 | |||
| 74 | div.article_partial h2 a { | ||
| 75 | text-decoration: none; | ||
| 76 | } | ||
| 77 | |||
| 78 | h3 { | ||
| 79 | font-size: 13px; | ||
| 80 | text-decoration: none; | ||
| 81 | |||
| 82 | } | ||
| 83 | |||
| 84 | h4 { | ||
| 85 | font-size: 11px; | ||
| 86 | text-decoration: bold; | ||
| 87 | } | ||
| 88 | |||
| 89 | /*------------------main-navigation-------------------*/ | ||
| 90 | |||
| 91 | div.main_navigation ul { | ||
| 92 | margin-left: 0; | ||
| 93 | padding-left: 0; | ||
| 94 | line-height: 18px; | ||
| 95 | } | ||
| 96 | |||
| 97 | div.main_navigation li { | ||
| 98 | list-style-type: none; | ||
| 99 | } | ||
| 100 | |||
| 101 | div.main_navigation a { | ||
| 102 | text-decoration: none; | ||
| 103 | font-size: 14px; | ||
| 104 | } | ||
| 105 | |||
| 106 | div.main_navigation a.inactive:hover { | ||
| 107 | color: #F8921E; | ||
| 108 | } | ||
| 109 | |||
| 110 | div.main_navigation a.active { | ||
| 111 | color: #000000; | ||
| 112 | text-decoration: none; | ||
| 113 | font-size: 13px; | ||
| 114 | } | ||
| 115 | |||
| 116 | div.main_navigation a.inactive { | ||
| 117 | color: #aeadad; | ||
| 118 | font-size: 13px; | ||
| 119 | } | ||
| 120 | |||
| 121 | /*------------------calendar-featured-tags-------------------*/ | ||
| 122 | |||
| 123 | div#frontpage_calendar { | ||
| 124 | margin-top: 30px; | ||
| 125 | } | ||
| 126 | |||
| 127 | div#frontpage_calendar h2, div#tags h2, div#featured_articles h2 { | ||
| 128 | color: #aeadad; | ||
| 129 | border-top: 2px solid #aeadad; | ||
| 130 | border-bottom: 2px solid #aeadad; | ||
| 131 | font-size: 16px; | ||
| 132 | padding-top: 2px; | ||
| 133 | padding-bottom: 2px; | ||
| 134 | } | ||
| 135 | |||
| 136 | div#frontpage_calendar ul, div#tags ul, div#featured_articles ul { | ||
| 137 | padding: 0px; | ||
| 138 | font-size: 11px; | ||
| 139 | } | ||
| 140 | |||
| 141 | div#featured_articles #ds_icon img { | ||
| 142 | padding-top: 10px; | ||
| 143 | } | ||
| 144 | |||
| 145 | div#featured_articles #events_icon img { | ||
| 146 | padding-left: 10px; | ||
| 147 | } | ||
| 148 | |||
| 149 | div#frontpage_calendar li { | ||
| 150 | margin-bottom: 20px; | ||
| 151 | } | ||
| 152 | |||
| 153 | div#frontpage_calendar li, div#tags li, div#featured_articles li { | ||
| 154 | list-style-type: none; | ||
| 155 | } | ||
| 156 | |||
| 157 | div#frontpage_calendar li a, div#tags li a, div#featured_articles li a { | ||
| 158 | text-decoration: none; | ||
| 159 | color: #535353; | ||
| 160 | } | ||
| 161 | |||
| 162 | div#frontpage_calendar li a:hover, | ||
| 163 | div#tags li a:hover, | ||
| 164 | div#featured_articles li a:hover { | ||
| 165 | text-decoration: none; | ||
| 166 | color: #ff9600; | ||
| 167 | } | ||
| 168 | |||
| 169 | /*--------------------------------------------------------------*/ | ||
| 170 | |||
| 171 | div.author_and_date { | ||
| 172 | font-style: italic; | ||
| 173 | padding-left: 15px; | ||
| 174 | font-family: Georgia; | ||
| 175 | } | ||
| 176 | |||
| 177 | div#left_column { | ||
| 178 | position: absolute; | ||
| 179 | background-image: url(/images/left_column.png); | ||
| 180 | background-position: top right; | ||
| 181 | background-repeat: no-repeat; | ||
| 182 | left: 0px; | ||
| 183 | width: 115px; | ||
| 184 | min-height: 100px; | ||
| 185 | text-align: right; | ||
| 186 | padding-right: 70px; | ||
| 187 | } | ||
| 188 | |||
| 189 | |||
| 190 | div#center_column { | ||
| 191 | position: absolute; | ||
| 192 | background-color: #ffffff; | ||
| 193 | left: 200px; | ||
| 194 | width: 460px; | ||
| 195 | padding-left: 15px; | ||
| 196 | padding-right: 15px; | ||
| 197 | padding-bottom: 40px; | ||
| 198 | } | ||
| 199 | |||
| 200 | div#right_column { | ||
| 201 | position: absolute; | ||
| 202 | background-color: #ffffff; | ||
| 203 | padding-left: 70px; | ||
| 204 | left: 675px; | ||
| 205 | width: 155px; | ||
| 206 | height: 100px; | ||
| 207 | } | ||
| 208 | |||
| 209 | div.teaser_ruler { | ||
| 210 | border-top: 1px solid #CCCCCC; | ||
| 211 | border-bottom: 1px dashed #CCCCCC; | ||
| 212 | height: 3px; | ||
| 213 | margin-top: 30px; | ||
| 214 | } | ||
| 215 | |||
| 216 | div.article_partial { | ||
| 217 | margin-bottom: 30px; | ||
| 218 | } | ||
| 219 | |||
| 220 | |||
| 221 | div.article_partial p.excerpt { | ||
| 222 | color: #404040; | ||
| 223 | } | ||
| 224 | |||
| 225 | div#center_column div.body, | ||
| 226 | div#center_column div.abstract { | ||
| 227 | font-size: 11px; | ||
| 228 | line-height: 16px; | ||
| 229 | } | ||
| 230 | |||
| 231 | div#search { | ||
| 232 | position: absolute; | ||
| 233 | top: 145px; | ||
| 234 | left: 676px; | ||
| 235 | height: 20px; | ||
| 236 | vertical-align: top; | ||
| 237 | } | ||
| 238 | |||
| 239 | div#search input[type=button] { | ||
| 240 | display: block; | ||
| 241 | height: 20px; | ||
| 242 | } | ||
| 243 | |||
| 244 | div#search input[type=text] { | ||
| 245 | display: block; | ||
| 246 | padding: 0px; | ||
| 247 | margin: 0px; | ||
| 248 | height: 20px; | ||
| 249 | width: 132px; | ||
| 250 | line-height: 20px; | ||
| 251 | border: none; | ||
| 252 | background-image: url(/images/search_field.png); | ||
| 253 | background-repeat:no-repeat; | ||
| 254 | padding-right: 5px; | ||
| 255 | margin-right: 5px; | ||
| 256 | background-position: top top; | ||
| 257 | } | ||
