From 7ec36976cbdde5409bc233e2a47e5bbdbc0536be Mon Sep 17 00:00:00 2001 From: rdrew Date: Thu, 14 May 2026 13:58:49 -0300 Subject: [PATCH] Establish cascade-layer CSS architecture and self-host theme typography MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopts a build-free modern CSS stack per todo.md: six cascade layers (reset → tokens → base → layout → components → utilities), modern-normalize in the reset layer, hand-rolled design tokens with an Open Props cherry-pick workflow, and self-hosted Adelle (serif, headings) plus Open Sans (variable, body) fonts. Existing starterkit component CSS is wrapped in @layer components; the previously-misnamed `base` library is renamed to `components`; YAML config files and architectural CSS files gain explanatory headers; CLAUDE.md documents the full setup so future work can pick it up cold. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 74 ++++++ css/base.css | 29 +++ css/components/action-links.css | 4 + css/components/breadcrumb.css | 4 + css/components/button.css | 4 + css/components/container-inline.css | 4 + css/components/details.css | 4 + css/components/dialog.css | 4 + css/components/dropbutton.css | 4 + css/components/exposed-filters.css | 4 + css/components/field.css | 4 + css/components/file.css | 4 + css/components/form.css | 4 + css/components/icons.css | 4 + css/components/image-widget.css | 4 + css/components/indented.css | 4 + css/components/inline-form.css | 4 + css/components/item-list.css | 4 + css/components/link.css | 4 + css/components/links.css | 4 + css/components/menu.css | 4 + css/components/messages.css | 4 + css/components/more-link.css | 4 + css/components/node.css | 4 + css/components/pager.css | 4 + css/components/progress.css | 4 + css/components/search-results.css | 4 + css/components/tabledrag.css | 4 + css/components/tableselect.css | 4 + css/components/tablesort.css | 4 + css/components/tabs.css | 4 + css/components/textarea.css | 4 + css/components/ui-dialog.css | 4 + css/components/user.css | 4 + css/fonts.css | 120 +++++++++ css/layers.css | 16 ++ css/layout.css | 22 ++ css/lib/modern-normalize.css | 243 ++++++++++++++++++ css/tokens.css | 69 +++++ css/utilities.css | 27 ++ druid.info.yml | 22 +- druid.libraries.yml | 63 +++++ fonts/adelle/Adelle_Bold.otf | Bin 0 -> 174764 bytes fonts/adelle/Adelle_Bold.woff2 | Bin 0 -> 64904 bytes fonts/adelle/Adelle_BoldItalic.otf | Bin 0 -> 162412 bytes fonts/adelle/Adelle_BoldItalic.woff2 | Bin 0 -> 63880 bytes fonts/adelle/Adelle_ExtraBold.otf | Bin 0 -> 177036 bytes fonts/adelle/Adelle_ExtraBold.woff2 | Bin 0 -> 64796 bytes fonts/adelle/Adelle_ExtraBoldItalic.otf | Bin 0 -> 161572 bytes fonts/adelle/Adelle_ExtraBoldItalic.woff2 | Bin 0 -> 63252 bytes fonts/adelle/Adelle_Heavy.otf | Bin 0 -> 177220 bytes fonts/adelle/Adelle_Heavy.woff2 | Bin 0 -> 64664 bytes fonts/adelle/Adelle_HeavyItalic.otf | Bin 0 -> 159608 bytes fonts/adelle/Adelle_HeavyItalic.woff2 | Bin 0 -> 63268 bytes fonts/adelle/Adelle_Italic.otf | Bin 0 -> 160608 bytes fonts/adelle/Adelle_Italic.woff2 | Bin 0 -> 64128 bytes fonts/adelle/Adelle_LightItalic.otf | Bin 0 -> 159336 bytes fonts/adelle/Adelle_LightItalic.woff2 | Bin 0 -> 61164 bytes fonts/adelle/Adelle_Reg.otf | Bin 0 -> 176380 bytes fonts/adelle/Adelle_Reg.woff2 | Bin 0 -> 64904 bytes fonts/adelle/Adelle_SemiBoldItalic.otf | Bin 0 -> 161432 bytes fonts/adelle/Adelle_SemiBoldItalic.woff2 | Bin 0 -> 63684 bytes fonts/adelle/Adelle_Semibold.otf | Bin 0 -> 177460 bytes fonts/adelle/Adelle_Semibold.woff2 | Bin 0 -> 64844 bytes fonts/adelle/Adelle_light.otf | Bin 0 -> 167444 bytes fonts/adelle/Adelle_light.woff2 | Bin 0 -> 62080 bytes fonts/open-sans/OpenSans-Italic-Latin.woff2 | Bin 0 -> 50216 bytes .../open-sans/OpenSans-Italic-LatinExt.woff2 | Bin 0 -> 37752 bytes fonts/open-sans/OpenSans-Latin.woff2 | Bin 0 -> 48320 bytes fonts/open-sans/OpenSans-LatinExt.woff2 | Bin 0 -> 35156 bytes todo.md | 210 +++++++++++++++ 71 files changed, 1022 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md create mode 100644 css/base.css create mode 100644 css/fonts.css create mode 100644 css/layers.css create mode 100644 css/layout.css create mode 100644 css/lib/modern-normalize.css create mode 100644 css/tokens.css create mode 100644 css/utilities.css create mode 100644 fonts/adelle/Adelle_Bold.otf create mode 100644 fonts/adelle/Adelle_Bold.woff2 create mode 100644 fonts/adelle/Adelle_BoldItalic.otf create mode 100644 fonts/adelle/Adelle_BoldItalic.woff2 create mode 100644 fonts/adelle/Adelle_ExtraBold.otf create mode 100644 fonts/adelle/Adelle_ExtraBold.woff2 create mode 100644 fonts/adelle/Adelle_ExtraBoldItalic.otf create mode 100644 fonts/adelle/Adelle_ExtraBoldItalic.woff2 create mode 100644 fonts/adelle/Adelle_Heavy.otf create mode 100644 fonts/adelle/Adelle_Heavy.woff2 create mode 100644 fonts/adelle/Adelle_HeavyItalic.otf create mode 100644 fonts/adelle/Adelle_HeavyItalic.woff2 create mode 100644 fonts/adelle/Adelle_Italic.otf create mode 100644 fonts/adelle/Adelle_Italic.woff2 create mode 100644 fonts/adelle/Adelle_LightItalic.otf create mode 100644 fonts/adelle/Adelle_LightItalic.woff2 create mode 100644 fonts/adelle/Adelle_Reg.otf create mode 100644 fonts/adelle/Adelle_Reg.woff2 create mode 100644 fonts/adelle/Adelle_SemiBoldItalic.otf create mode 100644 fonts/adelle/Adelle_SemiBoldItalic.woff2 create mode 100644 fonts/adelle/Adelle_Semibold.otf create mode 100644 fonts/adelle/Adelle_Semibold.woff2 create mode 100644 fonts/adelle/Adelle_light.otf create mode 100644 fonts/adelle/Adelle_light.woff2 create mode 100644 fonts/open-sans/OpenSans-Italic-Latin.woff2 create mode 100644 fonts/open-sans/OpenSans-Italic-LatinExt.woff2 create mode 100644 fonts/open-sans/OpenSans-Latin.woff2 create mode 100644 fonts/open-sans/OpenSans-LatinExt.woff2 create mode 100644 todo.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..663f9981 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,74 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +`druid` is a Drupal 11 custom theme generated from core's `starterkit_theme` (generator: `starterkit_theme:11.3.7`). It declares `stable9` as its base theme in `druid.info.yml`, so any template not overridden here falls back to `stable9`'s version. The theme lives at `web/themes/custom/druid` inside a larger Drupal site (sibling theme `orion` lives next to it; site-level `composer.json` is at the repo root). + +There is no build pipeline, no test suite, no linter, and no preprocessor in this directory. CSS is hand-authored modern CSS (custom properties, cascade layers, nesting, container queries, `:where()`, `color-mix()`, etc.) and consumed directly by Drupal. "Building" the theme means clearing Drupal's cache so it picks up `.yml` and template changes — CSS *content* edits need no rebuild. See `todo.md` for the design rationale that informed this stack choice (no Sass, no PostCSS, no Pico). + +## CSS architecture: cascade layers + +This is the central architectural decision and everything below it depends on understanding it. + +**Layer order**, declared once in `css/layers.css` and authoritative for the whole theme: + +```css +@layer reset, tokens, base, layout, components, utilities; +``` + +Later layers win against earlier ones regardless of selector specificity. Each layer is filled by exactly one file (except `components`, which is split per UI element), and each file is registered as its own library so loading is deterministic: + +| Layer | File(s) | Library | SMACSS cat. | Weight | +| ------------ | -------------------------------- | ------------------ | ----------- | ------ | +| (order decl) | `css/layers.css` | `druid/layers` | `base` | -100 | +| reset | `css/lib/modern-normalize.css` | `druid/reset` | `base` | -90 | +| (font-face) | `css/fonts.css` | `druid/fonts` | `base` | -85 | +| tokens | `css/tokens.css` | `druid/tokens` | `base` | -80 | +| base | `css/base.css` | `druid/base` | `base` | -70 | +| layout | `css/layout.css` | `druid/layout` | `layout` | -60 | +| components | `css/components/*.css` (32 files)| `druid/components` | `component` | -10/file | +| utilities | `css/utilities.css` | `druid/utilities` | `theme` | -50 | + +All eight libraries are attached on every page via `druid.info.yml` `libraries:` in cascade order. **Do not remove or reorder that list** — the layer-order declaration in `layers.css` must parse before any `@layer foo { … }` block, or the order becomes whatever Drupal happens to load first. + +`css/fonts.css` is the one architecture file that intentionally lives *outside* any `@layer` block. `@font-face` declarations register font resources rather than cascade rules — they don't compete in the cascade, so layer membership is irrelevant. The font files themselves live in `fonts//` (e.g. `fonts/adelle/`, `fonts/open-sans/`). + +**Critical footgun: unlayered CSS beats all layered CSS.** Any rule outside a `@layer { … }` block silently wins against everything inside layers — this is per the CSS spec, not a bug. Every CSS file in this theme is wrapped; any new rule you author must go *inside* the appropriate `@layer NAME { … }` block, never alongside it. + +**`!important` inverts layer order.** An `!important` declaration in an *earlier* layer beats `!important` in a later layer. Avoid `!important` inside `@layer utilities` — the layer is already designed to win. + +### Where to add a new CSS rule + +| Kind of rule | Goes in | +| --------------------------------------------------------------------- | -------------------------------------------------- | +| A `--custom-property` definition (color, size, font, radius, etc.) | `css/tokens.css` | +| A new `@font-face` / web font | `css/fonts.css` (+ woff2 file in `fonts//`)| +| Bare element selector (`body`, `a`, `h1`, `ul`, …) | `css/base.css` | +| Region/grid/container arrangement (`.layout-container`, page shell) | `css/layout.css` | +| A discrete UI widget (button, card, menu, form control, Drupal block) | `css/components/.css` | +| Single-purpose helper (`.text-center`, `.mt-4`, print toggles) | `css/utilities.css` | +| A browser default override | `css/lib/modern-normalize.css`* | + +*Only when re-vendoring a new modern-normalize release — see the file header for the workflow. + +### Tokens / Open Props + +`css/tokens.css` holds CSS custom properties on `:root`. We do **not** import the full Open Props library — instead, we cherry-pick individual tokens by copying their declarations into this file. The workflow is documented in the file's header comment; re-read it before adding tokens. The short version: browse https://open-props.style, copy the `--prop: value;` line, paste into the relevant section, preserve the Open Props name. + +## Other layout that matters + +- `druid.info.yml` — declares base theme, always-on libraries (one per cascade layer), and `libraries-extend` entries that attach extra component CSS only when the corresponding core library is in use (e.g. `core/drupal.dialog` → `druid/dialog`). The `libraries-extend` libraries (`dialog`, `dropbutton`, `file`, `item-list`, `progress`, `drupal.tablesort`, `user`) are separate from the always-on `components` library because they target widgets that don't appear on every page. +- `druid.libraries.yml` — every CSS library. New component CSS files added to `css/components/` need a corresponding entry under the `components:` library (or their own library if they should be conditional via `libraries-extend`). +- `druid.theme` — `hook_preprocess_HOOK` functions. Currently only `druid_preprocess_image_widget()`, which strips the preview container when the user lacks access. +- `templates/` — Twig overrides organized by Drupal's own taxonomy (`block/`, `content/`, `content-edit/`, `dataset/`, `field/`, `form/`, `layout/`, `misc/`, `navigation/`, `user/`, `views/`). Filenames follow Drupal's theme-hook-suggestion convention (`field--node--title.html.twig`, `block--system-menu-block.html.twig`, etc.) — renaming them breaks the override. + +## Working in this theme + +- **Cache clear**: required after any `.yml`, `.theme`, or template change — `drush cr` from the site root. CSS *content* edits do not require it; only adding/removing files or library entries does. +- **New component CSS**: create the file in `css/components/`, wrap its rules in `@layer components { … }`, register it under the `components:` library in `druid.libraries.yml` with `weight: -10`. If the styles should only load when a specific core library is in use, give it its own library and wire it via `libraries-extend` in `druid.info.yml` instead. +- **New Twig override**: copy the upstream template from `core/themes/stable9/templates/…` (or the originating module) into the matching subdirectory here — don't write from scratch; the docblock comments document available variables and are worth keeping. +- **New preprocess hook**: follow the existing signature in `druid.theme` (`array &$variables`, `: void` return type, `/** Implements hook_X(). */` docblock). +- **Drupal core / contrib CSS is unlayered.** That means it wins against any `@layer components` rule you write — this is the one place the architecture leaks. Options: bump specificity for the override, or wrap the offending core CSS via a future `hook_css_alter` (not currently implemented). Don't reach for `!important` reflexively. +- The repo is generated from a starter kit; upstream Drupal docs at https://www.drupal.org/docs/core-modules-and-themes/core-themes/starterkit-theme are the canonical reference for regeneration and theme-API conventions. diff --git a/css/base.css b/css/base.css new file mode 100644 index 00000000..94e249f3 --- /dev/null +++ b/css/base.css @@ -0,0 +1,29 @@ +/** + * @file + * Element-level defaults for the druid theme. + * + * Bare element selectors only (body, headings, links, lists, etc.). For + * discrete UI components, see css/components/. For page-level structure, + * see css/layout.css. Every rule lives in the `base` cascade layer so it + * loses cleanly to anything in layout / components / utilities. + */ +@layer base { + body { + background: var(--surface-1); + color: var(--text-1); + font-family: var(--font-sans); + line-height: 1.5; + } + + /* Headings use the serif token (Adelle). Drupal core inserts

+ in unpredictable places (admin toolbars, contextual links, system + blocks), so this targets bare heading elements — re-declare in a + component or utility if a specific UI surface needs the sans family. */ + h1, h2, h3, h4, h5, h6 { + font-family: var(--font-serif); + } + + a { + color: var(--brand); + } +} diff --git a/css/components/action-links.css b/css/components/action-links.css index 7e648df4..b81277cb 100644 --- a/css/components/action-links.css +++ b/css/components/action-links.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Styles for link buttons and action links. @@ -41,3 +43,5 @@ padding-right: 0; padding-left: 0.2em; } + +} diff --git a/css/components/breadcrumb.css b/css/components/breadcrumb.css index 1a20eaff..8ba8352b 100644 --- a/css/components/breadcrumb.css +++ b/css/components/breadcrumb.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Styles for breadcrumbs. @@ -27,3 +29,5 @@ .breadcrumb li:first-child::before { content: none; } + +} diff --git a/css/components/button.css b/css/components/button.css index 5eb4f1ac..e20abecd 100644 --- a/css/components/button.css +++ b/css/components/button.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for buttons. @@ -13,3 +15,5 @@ margin-right: 0; margin-left: 0; } + +} diff --git a/css/components/container-inline.css b/css/components/container-inline.css index 1a91f17f..8dddff37 100644 --- a/css/components/container-inline.css +++ b/css/components/container-inline.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Inline items. @@ -20,3 +22,5 @@ margin-top: 0; margin-bottom: 0; } + +} diff --git a/css/components/details.css b/css/components/details.css index b4165dcd..e3e3c0e7 100644 --- a/css/components/details.css +++ b/css/components/details.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Collapsible details. @@ -18,3 +20,5 @@ summary { padding: 0.2em 0.5em; cursor: pointer; } + +} diff --git a/css/components/dialog.css b/css/components/dialog.css index aca6afe2..eacef653 100644 --- a/css/components/dialog.css +++ b/css/components/dialog.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Presentational styles for Drupal dialogs. @@ -71,3 +73,5 @@ .ui-dialog .ajax-progress-throbber .message { display: none; } + +} diff --git a/css/components/dropbutton.css b/css/components/dropbutton.css index 5e971ba6..fac65304 100644 --- a/css/components/dropbutton.css +++ b/css/components/dropbutton.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * General styles for dropbuttons. @@ -31,3 +33,5 @@ margin-right: 0; margin-left: 0.25em; } + +} diff --git a/css/components/exposed-filters.css b/css/components/exposed-filters.css index b686902e..3acbc43d 100644 --- a/css/components/exposed-filters.css +++ b/css/components/exposed-filters.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for exposed filters. @@ -44,3 +46,5 @@ margin-right: 0; margin-left: 1em; } + +} diff --git a/css/components/field.css b/css/components/field.css index ff7e9ab1..c101848e 100644 --- a/css/components/field.css +++ b/css/components/field.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for fields. @@ -23,3 +25,5 @@ .field--label-inline .field__label::after { content: ":"; } + +} diff --git a/css/components/file.css b/css/components/file.css index 9aa90ebe..b62624a9 100644 --- a/css/components/file.css +++ b/css/components/file.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Default style for file module. @@ -60,3 +62,5 @@ .file--image { background-image: url(../../images/icons/image-x-generic.png); } + +} diff --git a/css/components/form.css b/css/components/form.css index d53d31fe..a5405aed 100644 --- a/css/components/form.css +++ b/css/components/form.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for form components. @@ -102,3 +104,5 @@ abbr.ajax-changed { background: url(../../images/icons/error.svg) no-repeat; background-size: contain; } + +} diff --git a/css/components/icons.css b/css/components/icons.css index 27337581..d63e7956 100644 --- a/css/components/icons.css +++ b/css/components/icons.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for icons. @@ -19,3 +21,5 @@ text-indent: -9999px; background: url(../../images/icons/feed.svg) no-repeat; } + +} diff --git a/css/components/image-widget.css b/css/components/image-widget.css index 72e52a2f..37d2152e 100644 --- a/css/components/image-widget.css +++ b/css/components/image-widget.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Image upload widget. @@ -20,3 +22,5 @@ .image-widget-data .text-field { width: auto; } + +} diff --git a/css/components/indented.css b/css/components/indented.css index 65ae0612..c8613f7e 100644 --- a/css/components/indented.css +++ b/css/components/indented.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Basic styling for comment module. @@ -13,3 +15,5 @@ margin-right: 25px; margin-left: 0; } + +} diff --git a/css/components/inline-form.css b/css/components/inline-form.css index b5201a78..33d15ae2 100644 --- a/css/components/inline-form.css +++ b/css/components/inline-form.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for inline forms. @@ -31,3 +33,5 @@ [dir="rtl"] .form--inline .form-actions { clear: right; } + +} diff --git a/css/components/item-list.css b/css/components/item-list.css index a8ce5d28..ed4032ad 100644 --- a/css/components/item-list.css +++ b/css/components/item-list.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for item list. @@ -30,3 +32,5 @@ [dir="rtl"] .item-list__comma-list li { margin: 0; } + +} diff --git a/css/components/link.css b/css/components/link.css index fa83f2bb..448f4906 100644 --- a/css/components/link.css +++ b/css/components/link.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Style another element as a link. @@ -14,3 +16,5 @@ button.link { label button.link { font-weight: bold; } + +} diff --git a/css/components/links.css b/css/components/links.css index e4832539..e587aad3 100644 --- a/css/components/links.css +++ b/css/components/links.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for links. @@ -21,3 +23,5 @@ ul.inline li { ul.links a.is-active { color: #000; } + +} diff --git a/css/components/menu.css b/css/components/menu.css index df733240..0f2556e4 100644 --- a/css/components/menu.css +++ b/css/components/menu.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for menu. @@ -32,3 +34,5 @@ ul.menu { ul.menu a.is-active { color: #000; } + +} diff --git a/css/components/messages.css b/css/components/messages.css index 4c41e440..25588eff 100644 --- a/css/components/messages.css +++ b/css/components/messages.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Styles for system messages. @@ -67,3 +69,5 @@ .messages--error p.error { color: #a51b00; } + +} diff --git a/css/components/more-link.css b/css/components/more-link.css index c6040613..d136730f 100644 --- a/css/components/more-link.css +++ b/css/components/more-link.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Markup generated by #type 'more_link'. @@ -10,3 +12,5 @@ [dir="rtl"] .more-link { text-align: left; } + +} diff --git a/css/components/node.css b/css/components/node.css index 6b7cd525..a0feace5 100644 --- a/css/components/node.css +++ b/css/components/node.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for nodes. @@ -6,3 +8,5 @@ .node--unpublished { background-color: #fff4f4; } + +} diff --git a/css/components/pager.css b/css/components/pager.css index a9471fc0..ac493a89 100644 --- a/css/components/pager.css +++ b/css/components/pager.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for pager. @@ -14,3 +16,5 @@ .pager__item.is-active { font-weight: bold; } + +} diff --git a/css/components/progress.css b/css/components/progress.css index 6ff2e194..d9589e71 100644 --- a/css/components/progress.css +++ b/css/components/progress.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for progress bar. @@ -56,3 +58,5 @@ -80px 0; } } + +} diff --git a/css/components/search-results.css b/css/components/search-results.css index 343ea8b5..fed98a92 100644 --- a/css/components/search-results.css +++ b/css/components/search-results.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Stylesheet for results generated by the Search module. @@ -6,3 +8,5 @@ .search-results { list-style: none; } + +} diff --git a/css/components/tabledrag.css b/css/components/tabledrag.css index a197b249..e1d02a58 100644 --- a/css/components/tabledrag.css +++ b/css/components/tabledrag.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for table drag. @@ -12,3 +14,5 @@ tr.drag-previous { body div.tabledrag-changed-warning { margin-bottom: 0.5em; } + +} diff --git a/css/components/tableselect.css b/css/components/tableselect.css index fcfb2a5a..7135cce7 100644 --- a/css/components/tableselect.css +++ b/css/components/tableselect.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Table select behavior. @@ -17,3 +19,5 @@ th.checkbox { /* This is required to win over specificity of [dir="rtl"] td */ text-align: center; } + +} diff --git a/css/components/tablesort.css b/css/components/tablesort.css index 44e53494..d18b9655 100644 --- a/css/components/tablesort.css +++ b/css/components/tablesort.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Table sort indicator. @@ -9,3 +11,5 @@ th.is-active img { td.is-active { background-color: #ddd; } + +} diff --git a/css/components/tabs.css b/css/components/tabs.css index 16fb1223..38f29e9b 100644 --- a/css/components/tabs.css +++ b/css/components/tabs.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for tabs. @@ -31,3 +33,5 @@ ul.tabs { .tabs a:hover { background-color: #f5f5f5; } + +} diff --git a/css/components/textarea.css b/css/components/textarea.css index 2661bae9..fe4c8fa1 100644 --- a/css/components/textarea.css +++ b/css/components/textarea.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Visual styles for a resizable textarea. @@ -9,3 +11,5 @@ width: 100%; margin: 0; } + +} diff --git a/css/components/ui-dialog.css b/css/components/ui-dialog.css index 164ca86b..55c2d937 100644 --- a/css/components/ui-dialog.css +++ b/css/components/ui-dialog.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Styles for modal windows. @@ -13,3 +15,5 @@ max-width: 95%; } } + +} diff --git a/css/components/user.css b/css/components/user.css index 7892fd6b..fee7d945 100644 --- a/css/components/user.css +++ b/css/components/user.css @@ -1,3 +1,5 @@ +@layer components { + /** * @file * Theme styling for user module. @@ -65,3 +67,5 @@ color: #a51b00; font-weight: bold; } + +} diff --git a/css/fonts.css b/css/fonts.css new file mode 100644 index 00000000..e9662085 --- /dev/null +++ b/css/fonts.css @@ -0,0 +1,120 @@ +/** + * @file + * @font-face declarations for the druid theme. + * + * Two families are registered: Adelle (serif, headings) and Open Sans + * (sans, body). All files are self-hosted under /fonts — no external + * requests at runtime. The token mapping (`--font-serif`, `--font-sans`) + * lives in css/tokens.css; rules that *apply* the fonts live in + * css/base.css. + * + * @font-face is intentionally NOT wrapped in a cascade layer. @font-face + * registers a font resource — it does not produce cascade rules that + * compete with anything, so layer membership is irrelevant. + * + * ───────────────────────────────────────────────────────────────────────── + * Adelle + * ───────────────────────────────────────────────────────────────────────── + * + * Self-hosted from /fonts/adelle/. Four weights/styles registered to keep + * the per-page font payload reasonable: Regular, Italic, Bold, BoldItalic. + * The /fonts/adelle/ folder ships additional weights (Light, Semibold, + * ExtraBold, Heavy, etc.) — to enable one, add a matching @font-face + * block below, pointing at the corresponding _.woff2 file. + * + * ───────────────────────────────────────────────────────────────────────── + * Open Sans + * ───────────────────────────────────────────────────────────────────────── + * + * Self-hosted from /fonts/open-sans/. Each .woff2 is a VARIABLE font — + * a single file covers the full weight range (300 to 800), so we declare + * the weight as a range and let the browser pick a weight per element. + * + * Two unicode-range subsets are registered per style: `latin` for English + * and standard western glyphs, `latin-ext` for accented characters, + * Esperanto, etc. The browser only downloads the subset it needs, based + * on the actual code points used on the page. + * + * To re-vendor (or add subsets like cyrillic/greek/vietnamese): + * 1. Visit https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap + * with a modern browser UA (curl -A "Mozilla/5.0 ... Chrome/..."). + * 2. Download the woff2 URLs Google returns for the subsets you want. + * 3. Save into /fonts/open-sans/ and add @font-face blocks below + * matching the unicode-range from Google's CSS. + */ + +/* Adelle — Regular */ +@font-face { + font-family: 'Adelle'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('../fonts/adelle/Adelle_Reg.woff2') format('woff2'); +} + +/* Adelle — Italic */ +@font-face { + font-family: 'Adelle'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url('../fonts/adelle/Adelle_Italic.woff2') format('woff2'); +} + +/* Adelle — Bold */ +@font-face { + font-family: 'Adelle'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('../fonts/adelle/Adelle_Bold.woff2') format('woff2'); +} + +/* Adelle — Bold Italic */ +@font-face { + font-family: 'Adelle'; + font-style: italic; + font-weight: 700; + font-display: swap; + src: url('../fonts/adelle/Adelle_BoldItalic.woff2') format('woff2'); +} + +/* Open Sans — Upright, latin */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300 800; + font-display: swap; + src: url('../fonts/open-sans/OpenSans-Latin.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* Open Sans — Upright, latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300 800; + font-display: swap; + src: url('../fonts/open-sans/OpenSans-LatinExt.woff2') format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* Open Sans — Italic, latin */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300 800; + font-display: swap; + src: url('../fonts/open-sans/OpenSans-Italic-Latin.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* Open Sans — Italic, latin-ext */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300 800; + font-display: swap; + src: url('../fonts/open-sans/OpenSans-Italic-LatinExt.woff2') format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} diff --git a/css/layers.css b/css/layers.css new file mode 100644 index 00000000..5448f23a --- /dev/null +++ b/css/layers.css @@ -0,0 +1,16 @@ +/** + * @file + * Cascade layer order for the druid theme. + * + * Earlier layers lose to later ones in the cascade, regardless of selector + * specificity. Unlayered CSS beats all layered CSS — if you author a new + * stylesheet for this theme, wrap it in the appropriate @layer block. + * + * - reset: modern-normalize and any browser-default resets. + * - tokens: :root custom property definitions (colors, spacing, type). + * - base: element-level defaults (body, headings, links, lists). + * - layout: page-level structural rules (regions, grids, containers). + * - components: discrete UI components and Drupal core overrides. + * - utilities: single-purpose helpers; intentionally wins last. + */ +@layer reset, tokens, base, layout, components, utilities; diff --git a/css/layout.css b/css/layout.css new file mode 100644 index 00000000..72757b31 --- /dev/null +++ b/css/layout.css @@ -0,0 +1,22 @@ +/** + * @file + * Page-level structural rules for the druid theme. + * + * This layer holds rules that arrange regions, not rules that style their + * contents — grids, flex containers, max-widths, page padding, sidebar + * positioning. If a rule could move from `
` to `