diff --git a/.codespell-ignore.txt b/.codespell-ignore.txt index 7415d0c78e6..917f068e298 100644 --- a/.codespell-ignore.txt +++ b/.codespell-ignore.txt @@ -42,6 +42,7 @@ Udo Yau Mathes Smal +Asai # Technical or code related terms aCount @@ -98,3 +99,9 @@ ons sie tru ublication +doubleclick + +# German glossary label +Referenz +pre-selected +jus diff --git a/.codespellrc b/.codespellrc index 324ecf2ae0c..16ee12ffa9e 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,6 +1,6 @@ [codespell] # Skip these files and directories -skip = .git,*.png,*.jpg,*.jpeg,*.gif,*.svg,*.ico,*.woff,*.woff2,*.ttf,*.eot,*.min.js,*.min.css,themes,node_modules,public,resources,static/admin,static/newsletters,*.lock,package-lock.json,.hugo_build.lock,go.sum,*.json,*.css,*.scss,*.toml,content/glossary/german,content/glossary/portuguese,content/glossary/arabic,content/glossary/spanish,content/glossary/french,content/glossary/turkish,data,*.pdf,content/glossary/english/open_material.md +skip = .git,assets/three,assets/three/*,scripts/rebrand/baseline,scripts/rebrand/baseline/*,*.png,*.jpg,*.jpeg,*.gif,*.svg,*.ico,*.woff,*.woff2,*.ttf,*.eot,*.min.js,*.bundle.js,*.min.css,themes,node_modules,public,resources,static/admin,static/newsletters,*.lock,package-lock.json,.hugo_build.lock,go.sum,*.json,*.css,*.scss,*.toml,content/glossary/german,content/glossary/portuguese,content/glossary/arabic,content/glossary/spanish,content/glossary/french,content/glossary/turkish,data,*.pdf,content/glossary/english/open_material.md # Ignore these words (project-specific terms and proper nouns) ignore-words = .codespell-ignore.txt diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 3f64a1477ff..81134ada7dc 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -20,7 +20,7 @@ This document describes the GitHub Actions workflows used in the FORRT website r ## Deployment Workflows -### [deploy.yaml](workflows/deploy.yaml) +### [deploy.yaml](deploy.yaml) **Production deployment to forrt.org** | Trigger | Target | Schedule | @@ -33,7 +33,7 @@ Builds the Hugo site and deploys to GitHub Pages (`gh-pages` branch). It uses the newest `data-artifact` and `announcements-artifact` from runs on `main` only. Runs of `data-processing.yml` or `create_announcements.yml` dispatched from another branch, for example to test a pipeline change, upload `*-preview` artifacts (#1030) that production never uses (#960). Staging can opt in to a branch's data artifact only; it never uses branch announcement artifacts. To stage branch data, run `data-processing.yml` on the branch, then dispatch `staging-aggregate.yaml` with `data_branch` set to that branch (combine with `single_pr` to stage only the PR that changes the pipeline). Production never uses it. If a branch other than `main` is named and has no data artifact, staging stops with an error rather than substituting `main`'s data (`data_branch: main` behaves like leaving it empty). For default staging runs and production, a missing data artifact makes the fallback dispatch `data-processing.yml` on `main`; a missing announcements artifact is not regenerated by these workflows. -### [staging-aggregate.yaml](workflows/staging-aggregate.yaml) +### [staging-aggregate.yaml](staging-aggregate.yaml) **Staging deployment for pull requests** | Trigger | Target | Schedule | @@ -49,9 +49,23 @@ Features: - Queues builds instead of canceling (concurrency) - Auto-cleans old staging branches (keeps 2) +### [rebrand-preview.yaml](rebrand-preview.yaml) +**Preview of the Doors redesign, while it is in progress** + +| Trigger | Target | Schedule | +|---------|--------|----------| +| Push to `rebrand-doors` | forrt.org/rebrand-doors/ (`forrtproject/rebrand-doors`) | On push | +| PR into `rebrand-doors` | Checks only, no deployment | On PR events | + +Features: +- Uses the same data and announcement artifacts as `deploy.yaml`, with noindex and no analytics +- Compares each build with its merge base with `main`: URL list, structured data, SEO tags and internal links. See `scripts/rebrand/README.md` +- A failed check marks the run red but still updates the preview +- Delete this workflow when the redesign launches + ## Data Workflows -### [data-processing.yml](workflows/data-processing.yml) +### [data-processing.yml](data-processing.yml) **Fetches and processes external data sources** | Trigger | Schedule | @@ -78,7 +92,7 @@ Triggers `deploy.yaml` after successful processing via `repository_dispatch`. ## Quality Checks -### [spell-check.yaml](workflows/spell-check.yaml) +### [spell-check.yaml](spell-check.yaml) **Spelling validation using codespell** | Trigger | Runs on | @@ -94,7 +108,7 @@ Triggers `deploy.yaml` after successful processing via `repository_dispatch`. |---------|---------| | Pull requests changing the clusters data, parser or tests | PR branches | -### [check_images.yaml](workflows/check_images.yaml) +### [check_images.yaml](check_images.yaml) **Image validation in PRs** | Trigger | Runs on | @@ -105,7 +119,7 @@ Validates image files and references. ## Repository Maintenance -### [cleanup-branches.yml](workflows/cleanup-branches.yml) +### [cleanup-branches.yml](cleanup-branches.yml) **Automated branch cleanup** | Trigger | Schedule | diff --git a/.github/workflows/rebrand-preview.yaml b/.github/workflows/rebrand-preview.yaml new file mode 100644 index 00000000000..158a0036269 --- /dev/null +++ b/.github/workflows/rebrand-preview.yaml @@ -0,0 +1,228 @@ +name: Doors preview + +# ======================= +# Doors Redesign Preview +# ======================= +# Purpose: Builds the rebrand-doors branch and publishes it at forrt.org/rebrand-doors/ +# Triggers: Push to rebrand-doors, and PRs into it (checks only, no deployment) +# Target: forrtproject/rebrand-doors, on GitHub Pages under the forrt.org domain +# Checks: Every run also builds the merge base with main from the same data, then compares +# the two builds. +# A failed check shows on the run but does not hold back the preview. +# Note: Uses the same data and announcement artifacts as deploy.yaml. Delete this +# workflow when the redesign launches. + +on: + push: + branches: + - rebrand-doors + pull_request: + branches: + - rebrand-doors + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + +concurrency: + group: rebrand-preview-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build and check + runs-on: ubuntu-22.04 + # Fork PRs get no artifacts token and have no preview to update. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + permissions: + contents: read + actions: read # Needed for artifact access + env: + HUGO_VERSION: "0.158.0" + HUGO_EXTENDED: true + PREVIEW_BASE: /rebrand-doors/ + PREVIEW_URL: https://forrt.org/rebrand-doors/ + outputs: + source-rules: ${{ steps.source-rules.outcome }} + compare: ${{ steps.compare.outcome }} + links: ${{ steps.links.outcome }} + seo: ${{ steps.seo.outcome }} + design: ${{ steps.design.outcome }} + axe: ${{ steps.axe.outcome }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + # See deploy.yaml: Git quotes non-ASCII paths unless this is off, and Hugo + # then finds no date for them. + - name: Let Git report non-ASCII paths unquoted + run: git config core.quotepath false + + # The point where the branch last took main, not main's tip: otherwise anything landing + # on main shows up here as a change, until main is next merged in. + - name: Check out the merge base with main as the reference + run: git worktree add --detach "$RUNNER_TEMP/reference" "$(git merge-base origin/main HEAD)" + + # ======================= + # Artifacts, as in deploy.yaml + # ======================= + - name: Download data artifact + uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 + continue-on-error: true + with: + workflow: data-processing.yml + name: data-artifact + path: ${{ runner.temp }}/artifacts/data + github_token: ${{ secrets.GITHUB_TOKEN }} + search_artifacts: true + branch: main # as in deploy.yaml: only main's runs, never a branch test run (#960) + if_no_artifact_found: warn + + - name: Download announcements artifact + uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 + continue-on-error: true + with: + workflow: create_announcements.yml + name: announcements-artifact + path: ${{ runner.temp }}/artifacts/announcements + github_token: ${{ secrets.GITHUB_TOKEN }} + search_artifacts: true + branch: main # as in deploy.yaml: only main's runs, never a branch test run (#960) + if_no_artifact_found: warn + + - name: Apply artifacts to both checkouts + run: | + for checkout in . "$RUNNER_TEMP/reference"; do + scripts/rebrand/apply_artifacts.sh "$checkout" \ + "$RUNNER_TEMP/artifacts/data" "$RUNNER_TEMP/artifacts/announcements" + done + + # ======================= + # Builds + # ======================= + - name: Setup Hugo + uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f + with: + hugo-version: ${{ env.HUGO_VERSION }} + extended: ${{ env.HUGO_EXTENDED }} + + # HUGO_ENV=staging adds noindex and leaves out Google Analytics. + - name: Build the branch + run: hugo --gc --minify --cleanDestinationDir --destination public --baseURL "$PREVIEW_URL" + env: + HUGO_ENV: staging + + - name: Build the reference from the same data + working-directory: ${{ runner.temp }}/reference + run: hugo --gc --minify --destination "$RUNNER_TEMP/public-reference" --baseURL "$PREVIEW_URL" + env: + HUGO_ENV: staging + + - name: Point root-relative links at the subfolder + run: | + python3 scripts/rebrand/preview_paths.py public --base "$PREVIEW_BASE" + python3 scripts/rebrand/preview_paths.py "$RUNNER_TEMP/public-reference" --base "$PREVIEW_BASE" + + # ======================= + # Checks + # ======================= + - name: Templates and generated paths + id: source-rules + continue-on-error: true + run: python3 scripts/rebrand/check_source_rules.py --base origin/main + + - name: URL list, structured data and SEO tags against the reference + id: compare + continue-on-error: true + run: python3 scripts/rebrand/compare_builds.py "$RUNNER_TEMP/public-reference" public + + - name: Internal links + id: links + continue-on-error: true + run: | + python3 scripts/rebrand/check_internal_links.py public --base "$PREVIEW_BASE" \ + --reference "$RUNNER_TEMP/public-reference" + + - name: Built HTML (headings, conditional assets, image alt and dimensions) + id: seo + continue-on-error: true + run: python3 scripts/check_seo_html.py public + + - name: Designed screens in a browser (overflow, position, layout shift, pause, directory) + id: design + continue-on-error: true + run: | + pip install --quiet playwright==1.63.0 pillow + # The runner image already has Chromium's libraries; --with-deps adds fonts from the + # Ubuntu mirror. When the mirror is unreachable, install the browser without them. + playwright install --with-deps chromium || { + echo "::warning::Could not install Chromium's system packages; installing the browser without them" + playwright install chromium + } + python3 scripts/rebrand/check_design.py public --base "$PREVIEW_BASE" + + - name: Accessibility rules in a browser (axe-core, by WCAG level) + id: axe + continue-on-error: true + run: | + curl -fsSL https://cdn.jsdelivr.net/npm/axe-core@4.11.1/axe.min.js -o "$RUNNER_TEMP/axe.min.js" + python3 scripts/rebrand/check_axe.py public --base "$PREVIEW_BASE" --axe "$RUNNER_TEMP/axe.min.js" + + - name: Upload site artifact + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v7 + with: + name: rebrand-preview-${{ github.run_number }} + path: public/ + retention-days: 1 + if-no-files-found: error + + deploy: + name: Deploy preview + runs-on: ubuntu-22.04 + needs: build + if: github.event_name != 'pull_request' + timeout-minutes: 15 + steps: + - name: Download site artifact + uses: actions/download-artifact@v7 + with: + name: rebrand-preview-${{ github.run_number }} + path: public + + # No cname: the repository inherits the organisation site's forrt.org domain, which + # is what puts it at forrt.org/rebrand-doors/. force_orphan keeps one commit, so the + # repository does not grow by the size of the site on every push. + - name: Deploy to forrtproject/rebrand-doors + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e + with: + personal_token: ${{ secrets.STAGING_GITHUB_TOKEN }} + external_repository: forrtproject/rebrand-doors + publish_dir: public + publish_branch: gh-pages + force_orphan: true + + checks: + name: Check results + runs-on: ubuntu-22.04 + needs: build + steps: + - name: Fail if any check failed + env: + SOURCE_RULES: ${{ needs.build.outputs.source-rules }} + COMPARE: ${{ needs.build.outputs.compare }} + LINKS: ${{ needs.build.outputs.links }} + SEO: ${{ needs.build.outputs.seo }} + DESIGN: ${{ needs.build.outputs.design }} + AXE: ${{ needs.build.outputs.axe }} + run: | + echo "Templates and generated paths: $SOURCE_RULES" + echo "URL list, structured data and SEO tags: $COMPARE" + echo "Internal links: $LINKS" + echo "Built HTML: $SEO" + echo "Designed screens: $DESIGN" + echo "Accessibility rules: $AXE" + for result in "$SOURCE_RULES" "$COMPARE" "$LINKS" "$SEO" "$DESIGN" "$AXE"; do + [ "$result" = "success" ] || exit 1 + done diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc615cdad71..8cfa0854da9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,10 +16,12 @@ Please follow the [FORRT Code of Conduct](https://forrt.org/coc/) in issues, rev - [Questions, Ideas & Suggestions](#questions-ideas--suggestions) - [Edit content in your browser](#edit-content-in-your-browser) + - [Linking glossary terms](#linking-glossary-terms) - [Local Development Setup](#local-development-setup) - [Standard Setup](#standard-setup-git--hugo) - [RStudio Setup](#rstudio-setup) - [Contribution Workflow](#contribution-workflow) + - [Citing references](#citing-references) - [Contributing with AI tools](#contributing-with-ai-tools) - [Content, Licensing & Fact-Checking](#content-licensing--fact-checking) - [Deployment & Staging](#deployment--staging) @@ -47,6 +49,12 @@ You do not need to install Git or Hugo to fix text or a broken link. Find the re Preserve the YAML/TOML front matter at the top of the file and existing Hugo shortcodes. Describe the affected page and link to it in your PR. Generated paths include `content/curated_resources/` (except `_index.md`), `content/glossary/`, `content/contributors/tenzing.md`, `content/contributor-analysis/`, `content/publications/citation_chart.webp`, announcement bundles in `content/post/`, `static/data/fred_citation.txt`, and generated files in `data/`. The manually maintained `data/publications.yaml` is editable; follow the [publication instructions](content/publications/README). Generated content is refreshed during deployment. Use the source submission form/sheet linked from the public page or open an issue rather than editing the generated output. Local previews use committed copies and can differ from production. For layout or code changes, use the local setup below. +To set a short note beside the text, put `{{% callout title="A title" %}}Your note{{% /callout %}}` just before the paragraph it goes with. On articles and most other text pages it sits in the right-hand column on wide screens, and in the text on phones and on pages set to full width. `title` is optional, and `tone="example"` or `tone="warning"` changes its colour. Keep it short: a long note runs on past the text it goes with. + +### Linking glossary terms + +On hand-written pages, use `{{< term "Preregistration" >}}` to link an English glossary term. To match an existing inflection, use `{{< term "Preregistration" "preregistered" >}}`. Preserve the author's exact wording and spelling; add markup only, and skip an occurrence if it cannot be marked without changing the text. Titles are matched without regard to case. Mark only the first occurrence of each term on a page, and keep existing resource links intact. Hover, keyboard focus, or a first touch tap shows a short definition; the popover links to the full entry. A repeat tap on the open trigger, mouse clicks, and Enter follow the term link, which also works without JavaScript. Unknown terms render as plain text and produce a Hugo build warning; check the [English glossary](https://forrt.org/glossary/english/) for the exact title. Do not add these shortcodes to generated content. + ## Local Development Setup Choose the setup method that suits your workflow. @@ -145,6 +153,22 @@ All proposed changes must be made on a feature branch and submitted via a Pull R For more on Git, see the [official documentation](https://docs.github.com/en/get-started/using-git/about-git). +### Citing references + +Hand-written pages can cite works in [`content/glossary/apa_lookup.json`](content/glossary/apa_lookup.json), the shared bibliography generated from the Glossary BibTeX Google Doc. Use its exact keys: + +```text +{{< cite "Nosek2018" "Simmons2011" >}} +{{< cite key="Nosek2018" narrative="true" >}} +(e.g. Smith, 2019; {{< cite key="Nosek2018" bare="true" >}}) +``` + +These render as “(Nosek et al., 2018; Simmons et al., 2011)”, “Nosek et al. (2018)” and “(e.g. Smith, 2019; Nosek et al., 2018)”. Use `bare` inside brackets you write yourself, for example alongside a work that isn't in the bibliography, so the author's wording stays as it was. Every page that cites must include `{{< references >}}` once, after its last citation, to add an alphabetical reference list. The built-HTML check fails citation links without matching reference entries. Clicking a citation opens its full APA reference with source and copy actions; without JavaScript it jumps to the reference list. Unknown keys produce a build warning and appear in square brackets. + +To ask readers to cite a work, show it as a card with a copy button: `{{< citation-card key="Asai2026OpenScholar" />}}` takes the reference from the bibliography, and `{{< citation-card >}}…{{< /citation-card >}}` takes one written out. In reference lists, cards and the “cite us” blocks, a reference with more than five authors shows the first three and “et al.”, with a button that shows the rest. Copying always gives the full reference. + +Do not edit the generated bibliography or glossary terms. Request additions to the Glossary BibTeX Doc through an issue; the bibliography generator supplies the APA text. Glossary definitions automatically link citations only when their first author and year identify exactly one of the term’s listed references and their author lists agree. Other citations stay plain text. Citations already linked to a source are left alone. + ### Contributing with AI tools Contributions co-authored with AI agents are welcome, but only if you have **fully reviewed both the code and the content** yourself and can stand behind them. diff --git a/assets/css/citations.css b/assets/css/citations.css deleted file mode 100644 index 96b81498dab..00000000000 --- a/assets/css/citations.css +++ /dev/null @@ -1,357 +0,0 @@ -/* Reset and Base Styles */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - /* font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; */ - line-height: 1.6; - color: #333; - background-color: #f8fafc; -} - -.container { - max-width: 1400px; - margin: 0 auto; - padding: 1rem 1rem; -} - -/* Header Styles */ -.header { - text-align: center; - margin-bottom: 3rem; -} - -.main-title { - font-size: 3rem; - font-weight: 700; - margin-bottom: 0.5rem; - background: linear-gradient(135deg, #3498db, #2c3e50); - -webkit-background-clip: text; - background-clip: text; - color: transparent; -} - -.subtitle { - font-size: 1.1rem; - color: #6b7280; - max-width: 600px; - margin: 0 auto; -} - -/* Main Layout */ -.main-layout { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.mobile-image { - display: flex; - justify-content: center; - margin-bottom: 1rem; -} - -.center-column { - display: none; -} - -.toolkit-image { - max-width: 100%; - height: auto; - border-radius: 12px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); -} - -/* Column Styles */ -.column { - flex: 1; -} - -.right-column { - display: flex; - flex-direction: column; - gap: 2rem; -} - -/* Stage Box Styles */ -.stage-box { - background: white; - border-radius: 12px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); - padding: 0.7rem; - transition: all 0.3s ease; - border-top: 4px solid; - height: fit-content; -} - -.stage-box:hover { - transform: translateY(-4px); - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15); -} - -.stage-box.paper { - border-top-color: #e74c3c; -} - -.stage-box.planning { - border-top-color: #ffc107; -} - -.stage-box.project { - border-top-color: #019e74; -} - -/* Stage Title Styles */ -h3 { - margin: 0.1rem; -} -.stage-title { - text-align: center; - color: white; - padding: 0.5rem 0.5rem; - border-radius: 8px; - margin-bottom: 1rem; - font-size: 1.1rem; - font-weight: 600; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15); -} - -.paper .stage-title { - background: linear-gradient(135deg, #d55e00, #d55e00); -} - -.planning .stage-title { - background: linear-gradient(135deg, #ffc107, #ffc107); -} - -.project .stage-title { - background: linear-gradient(135deg, #019e74, #019e74); -} - -/* Content Styles */ -.section-title { - font-size: 0.9rem; - color: #2c3e50; - margin-bottom: 1rem; - padding-bottom: 0.5rem; - border-bottom: 2px dashed #ecf0f1; - font-weight: 500; -} - -.tools-grid { - display: grid; - grid-template-columns: 1fr; - gap: 0.75rem; -} - -/* Tool Box Styles */ -.tool-box { - background: white; - border: 1px solid #e5e7eb; - border-radius: 8px; - padding: 0.5rem; - transition: all 0.3s ease; - position: relative; - border-top: 3px solid; - min-height: 60px; - display: flex; - align-items: center; -} - -.tool-box:hover { - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(160, 34, 69, 0.2); -} - -.tool-box.tool { - border-top-color: #F9F4CB; - background: linear-gradient(to bottom, #ffffff, #F9F4CB ); -} - -.tool-box.practice { - border-top-color: #E7E5E4; - background: linear-gradient(to bottom, #ffffff, #E7E5E4); -} - -.tool-box a { - text-decoration: none; - color: #374151; - font-weight: 500; - font-size: 0.85rem; - display: block; - width: 100%; -} - -.tool-box a:hover { - color: #b3005a; -} - -/* Category Badges */ -.tool-box::after { - content: attr(data-category); - position: absolute; - top: 1px; - right: 1px; - font-size: 0.5rem; - font-weight: 600; - padding: 1px 3px; - border-radius: 4px; - text-transform: uppercase; -} - -.tool-box.tool::after { - content: "TOOL"; - background-color: #f9f4cb; - color: #3e3e3e; -} - -.tool-box.practice::after { - content: "PRACTICE"; - background-color: #E7E5E4; - color: #3e3e3e; -} - -/* Legend Styles */ -.legend { - margin-top: 3rem; - display: flex; - justify-content: center; - gap: 2rem; - background: white; - padding: 1rem 2rem; - border-radius: 50px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); - max-width: 400px; - margin-left: auto; - margin-right: auto; - flex-wrap: wrap; -} - -.legend-item { - display: flex; - align-items: center; - gap: 0.5rem; - font-weight: 500; - font-size: 0.9rem; -} - -.legend-color { - width: 20px; - height: 20px; - border-radius: 4px; - border: 1px solid; -} - -.legend-item.practice .legend-color { - background-color: #E7E5E4; - border-color: #E7E5E4; -} - -.legend-item.tool .legend-color { - background-color: #f9f4cb; - border-color: #FEFDF6; -} - -/* Large Screen Styles */ -@media (min-width: 1024px) { - .main-layout { - display: grid; - grid-template-columns: 1fr auto 1fr; - gap: 2rem; - align-items: start; - } - - .mobile-image { - display: none; - } - - .center-column { - display: flex; - justify-content: center; - align-items: flex-start; - padding: 0 1rem; - } - - .center-column .toolkit-image { - max-width: 500px; - width: 100%; - } - - .tools-grid { - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - } - - .main-title { - font-size: 4rem; - } -} - -/* Medium Screen Styles */ -@media (min-width: 768px) and (max-width: 1023px) { - .tools-grid { - grid-template-columns: repeat(2, 1fr); - } - - .main-title { - font-size: 3.5rem; - } -} - -/* Small Screen Styles */ -@media (max-width: 767px) { - .container { - padding: 1rem; - } - - .main-title { - font-size: 2.5rem; - } - - .subtitle { - font-size: 1rem; - } - - .legend { - flex-direction: column; - align-items: center; - gap: 1rem; - border-radius: 20px; - padding: 1.5rem; - } - - .stage-box { - padding: 1.25rem; - } - - .mobile-image .toolkit-image { - max-width: min(350px, 100%); - } -} - -/* Animation */ -@keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(30px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.stage-box { - animation: fadeInUp 0.6s ease-out forwards; -} - -.stage-box:nth-child(1) { animation-delay: 0.1s; } -.stage-box:nth-child(2) { animation-delay: 0.2s; } -.stage-box:nth-child(3) { animation-delay: 0.3s; } - -.legend { - animation: fadeInUp 0.6s ease-out 0.4s forwards; - opacity: 0; -} diff --git a/assets/css/cite_us.css b/assets/css/cite_us.css index 27e480fdd0b..039090c8973 100644 --- a/assets/css/cite_us.css +++ b/assets/css/cite_us.css @@ -1,64 +1,13 @@ -/* styles for the cite us */ - .cite-us-section { - margin-bottom: 1rem; - } - - .section-title { - font-size: 1rem; - padding: 0.5rem; - } - - .cite-us-item { - background: #f9fafb; - border: 1px solid #e5e7eb; - border-radius: 8px; - padding: 1rem; - margin-bottom: 1rem; - position: relative; - } - - .cite-us-text { - font-family: Georgia, serif; - font-size: 0.95rem; - line-height: 1.7; - color: #374151; - margin-bottom: 1rem; - } - - .copy-button { - position: absolute; - bottom: 0.5rem; - right: 0.5rem; - background: linear-gradient(to bottom, #ffffff, #F9F4CB ); - color: #374151; - border: none; - border-radius: 6px; - padding: 0.2rem 0.5rem; - font-size: 0.875rem; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - gap: 0.5rem; - } - - .copy-button:hover { - background: linear-gradient(to bottom, #ffffff, #E7E5E4); - color: #b3005a; - transform: translateY(-1px); - } - - .copy-button.copied { - background: linear-gradient(to bottom, #ffffff, #F9F4CB ); - } - - .copy-icon { - width: 16px; - height: 16px; - } - - @media (max-width: 640px) { - .cite-us-text { padding-right: 0; margin-bottom: 3rem; } - .copy-button { position: static; margin-top: 1rem; width: 100%; justify-content: center; } - } +/* The citation boxes on /about/cite_us/. */ +.cite-us-section{margin:0 0 28px} +.cite-us-item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:start;margin:0 0 12px;padding:14px 16px;background:var(--surface);border-radius:var(--r-small)} +.cite-us-text{font-size:16px;line-height:1.5;color:var(--ink);overflow-wrap:anywhere} +.article-layout .article-style .cite-us-section .section-title{margin:0 0 10px;font-size:21px} +.copy-button{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;background:#fff;border:1.5px solid var(--ink);border-radius:999px;color:var(--ink);font:600 15px/1.3 var(--text);cursor:pointer} +.copy-button:hover{background:var(--ink);color:#fff} +.copy-icon{width:14px;height:14px} +@media(max-width:760px){.cite-us-item{grid-template-columns:minmax(0,1fr)}.copy-button{justify-self:start}} +/* Reference text keeps to the reading measure, under 80 characters a line, whatever the page's width. */ +.cite-us-text p,.article-layout .article-style .cite-us-text p{max-width:28.4em;margin:0;font-size:inherit} +.cite-us-text .ref-note,.article-layout .article-style .cite-us-text .ref-note{margin-top:8px} +.disciplines-attribution .cite-us-item{max-width:640px;margin-inline:auto} diff --git a/assets/css/doors/analysis.css b/assets/css/doors/analysis.css new file mode 100644 index 00000000000..747c6305c84 --- /dev/null +++ b/assets/css/doors/analysis.css @@ -0,0 +1,134 @@ +/* The contributor analysis page: figures, charts and the network drawn by doors/network.js. */ +.analysis-page{padding:24px 0 56px} +.analysis-head h1{font-size:clamp(36px,4.2vw,58px);line-height:1.06;letter-spacing:-.01em;margin:14px 0 0;text-wrap:balance} +.analysis-standfirst{font:500 clamp(21px,1.9vw,25px)/1.4 var(--display);margin:18px 0 0;max-width:30em} +.analysis-note{margin:0;font-size:16px;color:var(--soft);max-width:44em} +.analysis-page :is(p,li) a{text-decoration:underline;text-underline-offset:3px} +.analysis-page h2{font-size:clamp(27px,2.6vw,36px);line-height:1.15;margin:0} + +.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(10em,100%),1fr));gap:12px;margin:32px 0 14px} +.stat{display:flex;flex-direction:column-reverse;justify-content:flex-end;gap:6px;background:var(--surface);border-radius:var(--r-large);padding:18px 20px;min-width:0} +.stat dt{font:400 16px/1.3 var(--text);color:var(--ink)} +.stat dd{margin:0;font:var(--display-weight) clamp(36px,4vw,52px)/1 var(--display);color:var(--ink)} + +.analysis-section{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,2fr);gap:20px 56px;align-items:start;margin-top:56px;padding-top:28px;border-top:1px solid var(--line)} +.analysis-intro p{margin:12px 0 0;max-width:30em} +.analysis-chart{min-width:0} +.analysis-summary{margin-top:24px} +.analysis-summary p{max-width:42em} +.analysis-summary img{display:block;width:100%;max-width:760px;height:auto;margin:20px 0} + +.bars{list-style:none;margin:0;padding:0} +/* Wraps by the size of its text: name above, with the bar and number below, when they cannot share a line. */ +.bar{display:flex;flex-wrap:wrap;align-items:center;gap:3px 14px;padding:5px 0;font-size:17px;line-height:1.3} +.bar-name{flex:0 1 18em;min-width:0;max-width:100%} +.bar-track{flex:1 1 9em;order:1;display:block;height:12px} +.bar-value{flex:0 0 3em;order:2;text-align:right;font-weight:600;font-variant-numeric:tabular-nums} +.bar-fill{display:block;width:var(--share);min-width:2px;height:100%;background:var(--res);border-radius:0 4px 4px 0} + +.share-bar{display:flex;gap:2px;height:28px} +.share-part{flex:0 1 0;min-width:3px;border-radius:4px} +.share-0{background:#90bbd8} +.share-1{background:#6aa3c9} +.share-2{background:#4489b5} +.share-3{background:#1f709f} +.share-4{background:#005a87} +.share-list{list-style:none;margin:14px 0 0;padding:0;display:grid;grid-template-columns:repeat(auto-fit,minmax(min(8em,100%),1fr));gap:12px} +.share-list li{display:grid;grid-template-columns:14px minmax(0,1fr);gap:0 8px;align-items:baseline;line-height:1.3} +.share-swatch{width:14px;height:14px;border-radius:4px;align-self:center} +.share-label{font-size:16px} +.share-number{grid-column:2;font:var(--display-weight) 28px/1.2 var(--display)} +.share-people{grid-column:2;font-size:16px;color:var(--soft)} + +.analysis-more{margin-top:14px} +.analysis-more>summary{font-weight:700;color:var(--link);cursor:pointer} +.analysis-more[open]>summary{margin-bottom:10px} + +.analysis-network{margin-top:56px;padding-top:28px;border-top:1px solid var(--line)} +.network-intro p,.network-intro .analysis-note{margin:12px 0 0;max-width:580px} +.network-intro .analysis-note{margin-top:8px} +.network-panel{display:grid;grid-template-columns:min(18em,45%) minmax(0,1fr);height:clamp(520px,calc(100vh - 48px),760px);margin-top:20px;background:var(--surface);border-radius:var(--r-large);overflow:hidden} +.network-panel[hidden]{display:none} +.network-controls{display:flex;flex-direction:column;gap:14px;padding:18px;min-height:0;min-width:0;overflow:auto} +.network-field label{display:block;font-weight:700;margin-bottom:6px;line-height:1.3} +.network-combo{position:relative} +.network-input{width:100%;background:#fff;border:1.5px solid var(--soft);border-radius:var(--r-small);padding:10px 12px;font:400 17px/1.3 var(--text);color:var(--ink)} +.network-input::placeholder{color:var(--soft);opacity:1} +.network-options{position:fixed;z-index:20;max-height:280px;overflow:auto;margin:0;padding:4px;list-style:none;background:#fff;border:1.5px solid var(--soft);border-radius:var(--r-small);box-shadow:0 8px 24px rgba(38,45,53,.18)} +.network-options[hidden]{display:none} +.network-options li{display:flex;justify-content:space-between;gap:10px;padding:7px 9px;border-radius:5px;font-size:16px;line-height:1.3;cursor:pointer} +.network-options li span:last-child{color:var(--soft);white-space:nowrap} +.network-options li:hover,.network-options li[aria-selected=true]{background:var(--res-bg)} +.network-chips{list-style:none;display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 0;padding:0} +.network-chips[hidden]{display:none} +.network-chip{display:inline-flex;align-items:baseline;gap:7px;background:var(--res);border:1.5px solid var(--res);border-radius:999px;padding:5px 11px;font:400 15px/1.3 var(--text);color:#fff;text-align:start;cursor:pointer} +.network-chip:hover{background:var(--ink);border-color:var(--ink)} +.network-status{margin:0;font-weight:600;font-size:16px;line-height:1.35} +.network-detail{min-height:0;font-size:16px;line-height:1.4} +.network-detail[hidden]{display:none} +.network-detail h3{font:var(--display-weight) 22px/1.2 var(--display);margin:0 0 6px} +.network-detail h4{font:700 16px/1.3 var(--text);margin:12px 0 4px} +.network-detail p{margin:0 0 6px} +.network-detail ul{list-style:none;margin:0;padding:0} +.network-detail li{padding:1px 0} +.network-link{background:none;border:0;padding:0;font:400 16px/1.4 var(--text);color:var(--link);text-align:start;text-decoration:underline;text-underline-offset:3px;cursor:pointer} +.network-link:hover{color:var(--ink)} +.network-clear{font-weight:700} +.network-guide h3{font:var(--display-weight) 20px/1.2 var(--display);margin:0 0 8px} +.network-legend{list-style:none;margin:0;padding:0;font-size:16px;line-height:1.35} +.network-legend li{display:grid;grid-template-columns:22px minmax(0,1fr);gap:8px;align-items:center;padding:3px 0} +.legend-person{justify-self:center;width:10px;height:10px;border-radius:50%;background:var(--ink)} +.legend-project{justify-self:center;width:14px;height:14px;border-radius:3px;background:var(--res)} +.legend-line{justify-self:center;width:22px;height:0;border-top:1.5px solid var(--soft)} +.network-hint{margin:10px 0 0;font-size:16px;line-height:1.4} +.network-stage{position:relative;min-width:0;min-height:0;background:#fff} +.network-canvas{display:block;width:100%;height:100%;cursor:grab;touch-action:pan-y} +.network-canvas.dragging{cursor:grabbing} +.network-canvas:focus-visible{outline:3px solid var(--ink);outline-offset:-3px} +.network-tools{position:absolute;left:12px;bottom:12px;display:flex;gap:6px} +.network-tool{min-width:40px;height:40px;padding:0 12px;background:#fff;border:1.5px solid var(--ink);border-radius:999px;font:600 16px/1 var(--text);color:var(--ink);cursor:pointer} +.network-tool:hover{background:var(--ink);color:#fff} +.network-tip{position:absolute;z-index:5;max-width:280px;padding:7px 10px;background:var(--ink);border-radius:var(--r-small);color:#fff;font-size:15px;line-height:1.3} +.network-tip b{display:block;font-weight:700} +.network-tip[hidden]{display:none} +.network-list{margin-top:18px} +.network-projects{list-style:none;margin:0;padding:0;columns:2;column-gap:32px} +.network-projects>li{break-inside:avoid;padding:3px 0} +.network-projects summary{cursor:pointer} +.network-projects p{margin:4px 0 8px;font-size:16px;line-height:1.45} +.network-count{color:var(--soft);font-size:16px;white-space:nowrap} + +.analysis-foot{margin-top:56px;padding-top:28px;border-top:1px solid var(--line)} +.analysis-foot h2{margin-bottom:12px} +.analysis-foot p{margin:0 0 10px;max-width:44em} + +@media(max-width:1000px){ + .analysis-section{grid-template-columns:minmax(0,1fr)} + .network-projects{columns:1} +} +@media(max-width:860px){ + /* The panel stacks: controls, then the drawing, then what the selection shows. */ + .network-panel{display:flex;flex-direction:column;gap:14px;height:auto;padding:16px;overflow:visible} + .network-controls{display:contents} + .network-stage{order:1;height:min(62vh,440px);border-radius:var(--r-small);overflow:hidden} + .network-detail,.network-guide{order:2} +} +@media(max-width:760px){ + .analysis-page{padding:18px 0 40px} + .stats{gap:10px;margin-top:24px} + .stat{padding:14px} + .stat dt{font-size:15px} + .analysis-section,.analysis-network,.analysis-foot{margin-top:36px;padding-top:22px} + .bar{gap:3px 10px;font-size:16px} + .bar-name{flex-basis:100%} + .bar-track{height:10px} + .share-list{grid-template-columns:minmax(0,1fr)} + .share-list li{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 10px} + .share-label{flex:1 1 9em} + .share-number{font-size:20px} + .share-people{flex:0 0 auto;min-width:5.5em;text-align:right} + .network-stage{height:300px} + .network-canvas{cursor:default;touch-action:auto} + .network-tools{display:none} + .network-hint{display:none} +} diff --git a/assets/css/doors/article.css b/assets/css/doors/article.css new file mode 100644 index 00000000000..4bfef085d22 --- /dev/null +++ b/assets/css/doors/article.css @@ -0,0 +1,459 @@ +/* Articles and plain content, every rule under .article-layout. Content here is Markdown written + for the old theme, so lists, tables and embeds are styled but not rebuilt. */ +.article-layout{display:grid;grid-template-columns:minmax(0,580px) 300px;justify-content:center;gap:64px;padding:32px 0 32px;align-items:start} +.article-layout-single{grid-template-columns:minmax(0,580px)} +.article-layout-wide{grid-template-columns:minmax(0,1fr)} +.article-layout .reading{min-width:0} +.article-layout .crumbs{margin-bottom:18px} +.article-layout .article-head{margin-bottom:22px} +.article-layout .article-head:has(.byline:empty){margin-bottom:0} +.article-layout .article-meta{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:18px;font-size:15px;font-weight:600} +.article-layout .article-meta span{background:var(--surface);padding:3px 10px;border-radius:var(--r-small)} +.article-layout .reading h1{font-size:clamp(36px,4.2vw,58px);line-height:1.06;letter-spacing:-.01em;margin:0;text-wrap:balance} +/* In the narrow column a bigger screen must not mean a taller stack of title lines. */ +.article-layout:not(.article-layout-wide) .reading h1{font-size:clamp(34px,3.2vw,44px);line-height:1.1} +.article-layout .reading h1.long-title{font-size:clamp(28px,3vw,40px);line-height:1.12} +/* For long compounds. Not in English, where Safari hyphenates any word at a line end. */ +.article-layout .reading h1:lang(de),.article-layout .reading h1:lang(tr){hyphens:auto} +.article-layout .standfirst{font:500 clamp(21px,1.9vw,23px)/1.4 var(--display);margin:18px 0 0;max-width:30em;color:var(--ink)} +.article-layout .byline{display:flex;flex-wrap:wrap;gap:4px 18px;margin:16px 0 0;font-size:16px} +.article-layout .byline a{color:var(--link);text-underline-offset:3px;text-decoration:underline} +.article-layout .byline-authors>*{display:inline} +.article-layout .article-image{margin:0 0 28px} +.article-layout .article-image img{display:block;width:100%;height:auto;border-radius:var(--r-small)} +.article-layout figcaption{margin-top:10px;font-size:15px;text-align:center} +.article-layout .article-style{font-size:18px;line-height:1.6;color:var(--ink);text-align:start;overflow-wrap:break-word} +.article-layout .article-style p,.article-layout .article-style li{font-size:inherit;line-height:inherit} +/* Wide pages keep the reading width for their prose, and give the rest of the page to tables and charts. */ +.article-layout-wide .article-style p,.article-layout-wide .article-style li{max-width:29em} +.article-layout .article-style p{margin:0 0 16px;max-width:28.4em} +/* Under 80 characters a line (checked by check_design.py). The column is wider so pictures, tables and + embeds get the room; plain lists and reference lists keep to the text's measure plus their indent. */ +.article-layout:not(.article-layout-wide) .article-style :is(ul:not([class]),ol:not([class]),.references-list){max-width:29.8em} +/* Old pages put
on its own line between blocks to add space under the previous theme. Between + blocks it only opens a gap (up to 1,600px on a page), so drop it. A
inside a paragraph, or one that + clears floats (Awards), is untouched. */ +.article-layout .article-style>br:not([clear],[style*="clear"]),.article-layout .article-style>.doc-section>br:not([clear],[style*="clear"]){display:none} +.article-layout .article-style ul,.article-layout .article-style ol{margin:0 0 18px;padding-inline-start:1.4em} +.article-layout .article-style li{margin-bottom:6px} +.article-layout .article-style :is(h2,h3,h4){line-height:1.15;margin:34px 0 12px;text-align:start} +.article-layout .article-style h2{font-size:28px} +.article-layout .article-style h3{font-size:23px} +.article-layout .article-style h4{font-size:20px} +.article-layout .article-style a:not([class]){color:var(--link);text-decoration:underline;text-underline-offset:3px} +.article-layout .article-style a:not([class]):hover{color:var(--ink)} +.article-layout .article-style blockquote{margin:28px 0;padding:18px 22px;background:var(--edu-bg);border:0;border-radius:var(--r-large);color:var(--ink);font-size:18px!important;max-width:600px} +.article-layout .article-style blockquote p:last-child{margin-bottom:0} +.article-layout .article-style figure{margin:28px 0} +.article-layout .article-style img{max-width:100%;height:auto} +.article-layout .article-style div[style*="display:flex"]{flex-wrap:wrap} +.article-layout .article-style iframe,.article-layout .article-style video{max-width:100%} +/* A hand-coloured red button in one lesson. Our red keeps its meaning and passes on the page. */ +.article-layout .article-style .btn[style*="color:red"]{color:var(--urgent)!important} +.article-layout .article-style div.alert,.article-layout .article-style div.alert>div{font-size:inherit;color:var(--ink)} +.article-layout .article-style div[style*="width"]{max-width:100%} +.article-layout .article-style table{display:block;max-width:100%;overflow-x:auto;margin:0 0 18px;font-size:16px;line-height:1.45;border-collapse:collapse} +.article-layout .article-style th,.article-layout .article-style td{padding:6px 10px;border-bottom:1px solid var(--line);text-align:start} +.article-layout .article-style pre{max-width:100%;overflow-x:auto;font-size:15px} +.article-layout .article-style code{font-size:max(15px,.9em)} +main:lang(ar) .article-layout .article-style,main:lang(ar) .article-layout .article-style p,main:lang(ar) .article-layout .article-style li{font-size:21px} +/* The theme's root size shrinks on phones, so rem and small-text rules from it and from older + Markdown come out as low as 10 px. */ +.article-layout .article-style :is(small,.small,[style*="font-size:."],[style*="font-size:0."]),.article-layout .article-style .btn:not([style*="font-size"]),.article-layout .btn-links .btn{font-size:15px!important;line-height:1.35} +.article-layout .article-foot{margin-top:32px;font-size:16px} +/* Two or more buttons in one paragraph keep a gap between them when they wrap onto separate lines. */ +.article-layout .article-style p:has(>.btn~.btn){display:flex;flex-wrap:wrap;gap:10px;align-items:center} +.article-layout .article-style p:has(>.btn~.btn)>.btn{margin:0} +.article-layout .share-link{margin:0 0 18px} +.article-layout .copy-link{background:#fff;border:1.5px solid var(--ink);border-radius:999px;padding:8px 12px;font:600 15px/1.3 var(--text);color:var(--ink);cursor:pointer} +.article-layout .copy-link:hover{background:var(--ink);color:#fff} +.article-layout .article-tags .badge{background:var(--surface);color:var(--ink);font:600 15px/1.4 var(--text);padding:3px 10px;border-radius:var(--r-small);margin:0 6px 6px 0} +.article-layout .article-widget h3{font-size:24px;margin:28px 0 10px} +.article-layout .article-foot .meta-nav{font-size:15px;font-weight:700;letter-spacing:0;text-transform:none;color:var(--soft)} +.article-layout .article-foot .post-nav-item p,.article-layout .article-foot .article-widget li{font-size:16px;line-height:1.4;margin:0} +.article-layout .article-foot :is(.card-subtitle,.card-text){font-size:16px;max-width:30em} +.article-layout .article-widget a{color:var(--link)} +.article-layout .article-card{position:sticky;top:24px;max-height:calc(100vh - 48px - var(--banner,0px));overflow-y:auto;background:var(--surface);border-radius:var(--r-large);padding:22px;font-size:16px;line-height:1.45} +.article-layout .article-card h2{font-size:23px;line-height:1.15;margin:0 0 8px} +.article-layout .article-card dl{margin:0 0 20px} +.article-layout .article-card dl:last-child{margin-bottom:0} +.article-layout .article-card dt{font-weight:700;font-size:15px;margin-top:10px} +.article-layout .article-card dt:first-child{margin-top:0} +.article-layout .article-card dd{margin:1px 0 0} +.article-layout .article-toc ul{list-style:none;margin:0;padding:0} +.article-layout .article-toc ul ul{padding-inline-start:12px} +.article-layout .article-toc #TableOfContents a{display:block;padding:5px 0;font-size:16px;line-height:1.35;color:var(--link);text-underline-offset:3px;text-decoration:underline} +/* On a desktop the side card (contents, date, term list, profile) sits to the left of the text and the + pair is centred, so spare width goes to the outer margins. It may come after the text in the page; + the grid places it all the same. */ +@media(min-width:1001px){ + .article-layout:not(.article-layout-wide){grid-template-columns:minmax(0,640px)} + .article-layout:not(.article-layout-wide):has(>.article-card){grid-template-columns:220px minmax(0,640px);column-gap:48px} + .article-layout:not(.article-layout-wide):has(>.article-card)>.reading{grid-column:2;grid-row:1} + .article-layout:not(.article-layout-wide)>.article-card{grid-column:1;grid-row:1;box-sizing:border-box;width:220px;padding:18px} +} +@media(max-width:1000px){ + .article-layout{grid-template-columns:minmax(0,640px);gap:36px} + .article-layout-wide{grid-template-columns:minmax(0,1fr)} + .article-layout .article-card{position:static;max-height:none} +} +@media(max-width:760px){ + .article-layout{padding:20px 0 24px} + .article-layout .article-style{font-size:17px} + main:lang(ar) .article-layout .article-style,main:lang(ar) .article-layout .article-style p,main:lang(ar) .article-layout .article-style li{font-size:20px} +} +/* Call-outs: in the text up to 1240px. On wider screens they float into a third column on the right, + where clear keeps them under each other; the contents card stays on the left and in view. */ +.article-layout .callout{box-sizing:border-box;margin:28px 0;padding:18px 20px;background:var(--res-bg);border-radius:var(--r-large);font-size:17px;line-height:1.5;color:var(--ink)} +.article-layout .callout-example{background:var(--edu-bg)} +.article-layout .callout-warning{background:var(--com-bg)} +.article-layout .article-style .callout p,.article-layout .article-style .callout ul,.article-layout .article-style .callout ol{margin:0 0 10px;max-width:none} +/* On a full-width page a call-out keeps to the reading width of the text around it. */ +.article-layout-wide .article-style .callout{max-width:34em} +.article-layout .article-style .callout>:last-child{margin-bottom:0} +.article-layout .article-style .callout-title{font:500 21px/1.2 var(--display)} +@media(min-width:1240px){ + .article-layout.article-layout-callouts{grid-template-columns:minmax(0,600px) 260px;column-gap:40px} + .article-layout.article-layout-callouts:has(>.article-card){grid-template-columns:220px minmax(0,600px) 260px} + .article-layout-callouts .callout{float:right;float:inline-end;clear:right;clear:inline-end;width:260px;margin:4px -300px 18px 0;margin-inline:0 -300px} + .article-layout-callouts .doc-section-tinted .callout{margin-inline-end:-324px} +} +@media(max-width:1000px){.article-layout .article-card{order:1}} +/* Sections that open: the spoiler and expand shortcodes. */ +.article-layout .article-style .spoiler{margin:0 0 12px;background:var(--surface);border-radius:var(--r-small)} +.article-layout .article-style .spoiler>summary{padding:10px 14px;font-weight:600;font-size:18px;line-height:1.35;color:var(--link);cursor:pointer} +.article-layout .article-style .spoiler>summary:hover{color:var(--ink)} +.article-layout .article-style .spoiler-body{padding:2px 14px 12px;font-size:18px;line-height:1.55} +.article-layout .article-style .spoiler-body>:last-child{margin-bottom:0} +/* Disclosures hold prose, so they stay at reading width unless a PDF is open inside one. */ +.article-layout-wide .article-style .spoiler:not([open]),.article-layout-wide .article-style .spoiler:not(:has(.pdf-viewer)){max-width:580px} +.article-layout .article-style [style*="justify"]{text-align:start!important} +/* A lesson's activity takes more room than the text around it, without growing to the full page. */ +.article-layout-wide .article-style>div[style*="padding-bottom"]:has(>.click-to-load,>iframe),.article-layout-wide .article-style :is(.video-poster,.video-frame){max-width:960px} +.article-layout-wide .article-style>div[style*="padding-bottom"]:has(>iframe){height:auto!important;padding-bottom:0!important;aspect-ratio:3/2} +/* Tables and text panels fit what they hold, so a wide page does not leave them mostly empty. */ +.article-layout-wide .article-style table{width:fit-content} +.article-layout-wide .article-style>div[style*="border"]:not([style*="padding-bottom"]){max-width:640px;margin-inline:0!important;padding:14px 18px!important} +.article-layout-wide .article-style blockquote{max-width:640px} +.article-layout-wide .article-style>hr{max-width:580px;margin-inline:0} +/* Buttons in Markdown keep their own colours, against the rule for links in text. */ +/* Glossary pages: the reading layout, with the list of terms in the side card. */ +.glossary-layout .language-switcher{margin:10px 0 14px;font-size:16px} +.glossary-layout .language-switcher a{color:var(--link);text-decoration:underline;text-underline-offset:3px} +.glossary-layout .term-definition{padding:0 0 14px;border-bottom:1px solid var(--line)} +.glossary-layout .article-style .term-line{margin-bottom:8px;font-size:18px;line-height:1.5} +.glossary-layout .references-list,.glossary-layout .references-list li{font-size:18px!important} +.glossary-layout .article-style .btn-group{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 18px} +.glossary-layout .article-style .btn-group .btn{margin:0!important;padding:7px 14px!important;background:#fff;border-radius:999px!important;font-size:16px!important;color:var(--link)} +.glossary-layout .article-style .btn-group .btn.active{background:var(--ink);border-color:var(--ink);color:#fff} +.glossary-card{display:flex;flex-direction:column;overflow:hidden!important} +.glossary-card .form-control{flex:none;margin-bottom:10px} +.term-list{flex:1;min-height:0;overflow-y:auto;margin-inline:-6px;padding-inline:6px;font-size:16px;line-height:1.3} +.term-list ul{margin:0;padding:0;list-style:none} +.term-list a{display:block;padding:5px 0;color:var(--link);text-decoration:underline;text-underline-offset:3px} +.term-list a:hover{color:var(--ink)} +.term-list a[aria-current]{color:var(--ink);font-weight:700;text-decoration:none} +@media(max-width:1000px){.term-list{max-height:340px}} +.resource-open{display:flex;flex-wrap:wrap;align-items:center;gap:6px 14px;margin:0 0 24px} +.resource-open .btn{padding:10px 22px;font-size:17px} +.resource-open .btn .fa-external-link-alt{margin-inline-start:4px;font-size:14px} +.resource-host{font-size:16px;color:var(--soft);overflow-wrap:anywhere} +/* A resource entry: the entry, then its details card, then Previous and Next. On a desktop the card + sits in the left column beside both; the second row takes any extra height of a long card. */ +.article-layout .resource-kind{margin:0 0 12px;max-width:60ch;font-size:16px;font-weight:600;color:var(--soft)} +.article-layout .byline-more{display:inline} +.article-layout .byline-more>summary{display:inline-block;margin-inline-start:.3em;color:var(--link);font-weight:600;text-decoration:underline;text-underline-offset:3px;cursor:pointer} +.article-layout .byline-more>summary:hover{color:var(--ink)} +.article-layout .byline-more>p{margin:6px 0 0} +.article-layout.article-layout-resource .resource-keywords{margin-top:24px} +.article-layout-resource .article-card dd a{color:var(--link);text-decoration:underline;text-underline-offset:3px} +.article-layout-resource>.article-foot{margin-top:0} +@media(min-width:1001px){ + .article-layout-resource:not(.article-layout-wide){grid-template-rows:auto 1fr;row-gap:32px} + .article-layout-resource:not(.article-layout-wide)>.article-card{grid-row:1/span 2} + .article-layout-resource>.article-foot{grid-column:2;grid-row:2} +} +@media(max-width:1000px){ + .article-layout-resource .article-card{order:0} + .article-layout-resource .article-card .resource-type{display:none} + .article-layout-resource .article-card .resource-type+dd+dt{margin-top:0} + .article-layout .byline-more>summary{min-height:44px;display:inline-flex;align-items:center} +} +.article-layout .article-tags{margin:0 0 18px} +.article-layout .resource-keywords{max-width:32em;margin:0 0 18px;font-size:16px;line-height:1.6;color:var(--soft)} +.article-layout .resource-keywords strong{margin-inline-end:6px;color:var(--ink)} +.article-layout .resource-keywords a{color:var(--link);text-decoration:underline;text-underline-offset:3px} +/* Author profiles: the person's details in the side card. */ +.profile-card .profile-photo{display:block;width:135px;height:135px;margin:0 0 12px;object-fit:cover;border-radius:50%} +.profile-card .avatar-square{border-radius:var(--r-small)} +.profile-card p{margin:0 0 6px} +.profile-card .profile-role{font-weight:700} +.profile-card a{color:var(--link);text-decoration:underline;text-underline-offset:3px} +.profile-card ul.network-icon a{text-decoration:none} +.sc-page{padding:32px 0 40px} +.sc-page h1{font-size:clamp(36px,4.2vw,58px);line-height:1.06;margin:0 0 18px} +.sc-page .crumbs{margin-bottom:18px} +.sc-page #sc-container{padding:0;font-family:var(--text);max-width:none} +/* A team's name heads its own row, so it cannot end one row with its people starting the next. */ +.sc-page .sc-grid .sc-title-card{flex:0 0 100%;grid-column:1/-1;width:100%;aspect-ratio:auto;padding:10px 16px;flex-direction:row;align-items:baseline;justify-content:flex-start;gap:10px} +.sc-page .sc-title-card :is(.sc-title-label,.sc-title-text){margin:0} +.sc-page .sc-title-card .sc-title-text{font-size:17px} +.sc-page .sc-title-card .sc-cbar{display:none} +.sc-team{display:contents} +@media(min-width:761px){ + .sc-page .sc-grid{gap:28px 40px} + .sc-page .sc-team{display:flex;flex-wrap:wrap;align-content:flex-start;gap:1rem;width:calc(var(--sc-members)*190px + (var(--sc-members) - 1)*1rem);max-width:100%} + .sc-page .sc-grid .sc-card{width:190px} + .sc-page .sc-grid .sc-title-card{flex-wrap:wrap} +} +.article-layout .post-nav{display:flex;flex-wrap:wrap;justify-content:space-between;gap:12px 24px;margin:0 0 18px} +.article-layout .post-nav-item{max-width:calc(50% - 12px);min-width:0} +.article-layout .post-nav-item:only-child{max-width:none} +.article-layout .post-nav-item:last-child:not(:first-child){text-align:end} +.article-logo{float:inline-end;width:96px;height:96px;margin:0 0 12px 18px;border-radius:var(--r-small)} +@media print{ + .site-head,.site-foot,.article-foot,.skip,.cc-window,.article-toc,.article-card h2{display:none!important} + body{background:#fff;color:#000;font-size:12pt} + .wrap{width:100%} + .article-layout{display:block;padding:0} + .article-layout .article-card{position:static;max-height:none;background:none;padding:0} + .article-layout .article-style,.article-layout .article-style :is(p,li){font-size:12pt} + a{color:#000} +} +/* A video before it plays, the player after, and a section of a long summaries page. */ +.video-poster{position:relative;display:block;margin:0 0 18px;aspect-ratio:16/9;overflow:hidden;background:var(--surface);border-radius:var(--r-small)} +.article-layout .article-style .video-poster img{display:block;width:100%;height:100%;object-fit:cover} +.video-play{position:absolute;inset:50% auto auto 50%;display:flex;align-items:center;justify-content:center;width:64px;height:64px;margin:-32px 0 0 -32px;background:var(--ink);border-radius:50%;color:#fff;font-size:24px} +.video-poster:hover .video-play,.video-poster:focus-visible .video-play{background:var(--hot);color:var(--ink)} +.video-frame{display:block;width:100%;aspect-ratio:16/9;margin:0 0 18px;border:0;border-radius:var(--r-small)} +.video-poster:has(+ .video-note){margin-bottom:6px} +.video-note,.article-layout .article-style .video-note{margin:0 0 18px;color:var(--soft);font-size:16px;line-height:1.4} +.video-note a{text-decoration:underline} +.article-layout .article-style .summary-entry{margin:0;border-bottom:1px solid var(--line)} +.article-layout .article-style .summary-entry>summary{padding:12px 0;cursor:pointer} +.article-layout .article-style .summary-entry>summary h2{display:inline;margin:0;font-size:22px;line-height:1.25} +.article-layout .article-foot .article-widget li{margin-bottom:8px} +.doi-link,.cite-us-text a{text-decoration:underline;text-underline-offset:3px} +.toc-phone{display:none} +@media(max-width:1000px){ + .toc-phone{display:block;margin:0 0 20px;padding:12px 16px;background:var(--surface);border-radius:var(--r-small)} + .toc-phone-label{margin:0 0 4px;font:700 15px/1.3 var(--text);letter-spacing:.05em;text-transform:uppercase;color:var(--soft)} + .toc-phone.is-short .toc-extra{display:none} + .toc-phone-more{margin:6px 0 0;padding:4px 0;border:0;background:none;font:600 16px/1.35 var(--text);color:var(--ink);text-decoration:underline;text-underline-offset:3px;cursor:pointer} + .toc-phone a{display:block;padding:5px 0;font-size:16px;line-height:1.35;color:var(--link);text-decoration:underline} + .article-layout:has(.toc-phone) .article-card :is(h2,.article-toc){display:none} + .article-layout:has(.toc-phone) .article-card:not(:has(dl)){display:none} + .glossary-index .reading{display:contents} + .glossary-index .article-head{order:-2;margin-bottom:0} + .glossary-index .term-list{max-height:220px} + .glossary-index .glossary-card{order:1} + .glossary-index .article-foot{order:2} +} +.summary-tools{margin:0 0 14px;padding:14px 18px;background:var(--surface);border-radius:var(--r-large)} +.summary-tools label{display:block;margin:0;font-size:16px;font-weight:700} +.summary-tools input{display:block;width:100%;margin-top:6px;padding:8px 14px;background:#fff;border:1.5px solid var(--soft);border-radius:999px;font:400 16px/1.3 var(--text)} +.article-layout .article-style .summary-count{margin:8px 0 0;font-size:16px} +.summary-more{margin-top:14px} +.summary-more[hidden]{display:none} +/* The list of entries is its own contents, so a second list beside it only repeats the titles. */ +.article-layout:has(.summary-entry) .article-card :is(h2,.article-toc){display:none} +.dei-mark{padding:0 6px;background:var(--jus-bg);border-radius:var(--r-small);font:600 15px/1.3 var(--text);color:var(--ink);vertical-align:.1em} +@media print{.toc-phone{display:none!important}} +.glossary-feedback-header>*{min-width:0;overflow-wrap:anywhere} +.glossary-feedback-header>:lang(de),.glossary-feedback-header>:lang(tr){hyphens:auto} + +/* Catalogues (Syllabus): groups of links flow in columns across a wide page. */ +.article-style .catalogue{columns:3 300px;column-gap:48px;margin-top:28px} +.article-style .catalogue-group{break-inside:avoid-column;margin-bottom:28px} +.article-style .catalogue-group h3{margin:0 0 10px;font-size:23px;line-height:1.2} +.article-style .catalogue-group ul{margin:0;padding-inline-start:1.1em} +.article-style .catalogue-group li{margin-bottom:8px;font-size:17px;line-height:1.4} +/* A long group may run on into the next column rather than leave a gap. */ +.article-style .catalogue-group:has(li:nth-child(15)){break-inside:auto} +.article-style .catalogue-group:has(li:nth-child(15)) h3{break-after:avoid-column} + +/* Lesson bank: one card per lesson, with its plan and texts. */ +.article-layout .article-style .lesson-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,300px),1fr));gap:18px;margin:20px 0 40px;padding:0;list-style:none} +.article-style .lesson-grid>.lesson-card{max-width:none;margin:0;padding:18px 20px;background:var(--surface);border-radius:var(--r-large)} +.article-style .lesson-card .lesson-num{margin:0 0 4px;font-size:15px;font-weight:700;color:var(--soft)} +.article-style .lesson-card h3{margin:0 0 10px;font-size:22px;line-height:1.2} +.article-style .lesson-card .lesson-materials{margin:0;padding:0;list-style:none} +.article-style .lesson-card .lesson-materials li{margin:0 0 4px;font-size:16px} +.article-style .lesson-card .lesson-note{margin:0;font-size:16px;color:var(--soft)} + +/* A tool page's direct actions, under its title (doors/page_actions). */ +.article-layout .page-actions{display:flex;flex-wrap:wrap;gap:10px;margin:0 0 28px} +.article-layout .page-actions .btn{padding:10px 22px;font-size:17px} +/* On phones a tall cover image pushed the page's contents a screen down, so it is held to part of the screen. */ +@media(max-width:760px){.article-layout .article-image img{width:auto;max-width:100%;max-height:280px;margin-inline:auto}} +/* Old pages put a rule under each heading (### Title, then ---) and at the very end. The heading already + separates the section, and the rule pushed it away from its text, so neither rule is drawn. */ +.article-style :is(h2,h3,h4)+hr,.article-style>hr:last-child{display:none} +.article-layout .post-nav-next{margin-inline-start:auto;text-align:end} +.post-nav .meta-nav>span{display:inline-block} +[dir=rtl] .post-nav .meta-nav>span{transform:scaleX(-1)} +/* Author profiles on a narrow screen: portrait, role and links under the name, in place of the side card. */ +.profile-head{display:none} +@media(max-width:1000px){ + .article-layout .profile-card{display:none} + .profile-head{display:flex;align-items:center;gap:14px;margin-top:14px} + .profile-head .profile-photo{width:72px;height:72px;border-radius:50%;object-fit:cover;flex:none} + .profile-head .avatar-square{border-radius:var(--r-small)} + .profile-head p{margin:0 0 4px;font-weight:700} +} +/* One primary button: old pages give theirs inline sizes, corners and full widths; all take the "Open this resource" pill. */ +.article-layout .article-style .btn-primary,.article-layout .article-style .btn-lg{padding:10px 22px!important;border-radius:999px!important;font-size:17px!important;line-height:1.3!important} +.article-layout .article-style .btn-block{display:inline-block;width:auto} +.booklet-open .btn{margin-inline-end:10px} +@media(max-width:1000px){.profile-head .profile-org{font-weight:400}.profile-head ul.network-icon{margin:4px 0 0}.profile-head ul.network-icon a{text-decoration:none}} +.article-layout .article-style .btn.btn-primary,.article-layout .article-style .btn.btn-lg{padding:10px 22px!important;font-size:17px!important} +.byline-date{display:none} +@media(max-width:1000px){.article-layout .byline-date{display:inline}} +/* A search over a long list on the page (shortcodes/list_filter.html). */ +.article-layout .article-style .list-filter{max-width:520px;margin:8px 0 22px;padding:16px 18px;background:var(--surface);border-radius:var(--r-large)} +.article-layout .article-style .list-filter label{display:block;margin:0 0 8px;font:600 17px/1.3 var(--text)} +.article-layout .article-style .list-filter input{width:100%;box-sizing:border-box;padding:10px 16px;border:1.5px solid var(--ink);border-radius:999px;background:#fff;font:400 17px/1.3 var(--text);color:var(--ink)} +.article-layout .article-style .list-filter-count{margin:8px 0 0;font-size:16px;color:var(--soft)} +.article-layout .article-style [hidden]{display:none!important} +/* A licence note is a quiet line with its badge, as the site's other notes, not a bordered, centred box. */ +.article-layout .article-style .license-note{margin:28px 0 0!important;padding:14px 18px!important;border:0!important;border-radius:var(--r-large)!important;background:var(--surface)!important;text-align:start!important} +.article-layout .article-style .license-note .license-content{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 18px} +.article-layout .article-style .license-note p{margin:0!important;font-size:16px!important;text-align:start!important} +/* AWoP: the supplemental materials read as a list: each title with its OSF link beside it, then what it is. */ +.article-layout .article-style .awop-material{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:16px;margin:0;padding:14px 0 4px;border-bottom:1px solid var(--line)} +.article-layout .article-style p+.awop-material{border-top:1px solid var(--line);margin-top:12px} +.article-layout .article-style .awop-material>*{grid-column:1/-1} +.article-layout .article-style .awop-material>p:first-child{grid-column:1;grid-row:1} +.article-layout .article-style .awop-material>p:nth-child(3){grid-column:2;grid-row:1;white-space:nowrap} +.article-layout .article-style .awop-material p{margin:0 0 8px} +.article-layout .article-style .awop-material .spoiler{margin:0 0 10px;background:none} +.article-layout .article-style .awop-material .spoiler>summary{padding:4px 0;font-size:16px} + +/* Starting points at the top of a page (shortcodes/action_cards.html). */ +/* Starting points (the action_cards shortcode and the Replication Hub's Start here): solid bricks with studs, + laid edge to edge like a short wall, in the area colours (below). The bricks are for a handful of actions; long lists are rows (hub.css). */ +/* A row runs through the four area colours, starting with its own area's (`area` on the shortcode), so a + page's bricks are related but not identical. */ +.action-cards{--c1:var(--res);--c2:var(--edu);--c3:var(--jus);--c4:var(--com)} +.action-cards.area-edu{--c1:var(--edu);--c2:var(--res);--c3:var(--jus);--c4:var(--com)} +.action-cards.area-jus{--c1:var(--jus);--c2:var(--res);--c3:var(--edu);--c4:var(--com)} +.action-cards.area-com{--c1:var(--com);--c2:var(--res);--c3:var(--edu);--c4:var(--jus)} +.action-cards li:nth-child(4n+1){--tone:var(--c1)}.action-cards li:nth-child(4n+2){--tone:var(--c2)} +.action-cards li:nth-child(4n+3){--tone:var(--c3)}.action-cards li:nth-child(4n+4){--tone:var(--c4)} +:is(.article-layout .article-style,.hub-start) .action-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,190px),1fr));gap:16px 5px;list-style:none;margin:26px 0 32px;padding:0;max-width:none} +:is(.article-layout .article-style,.hub-start) .action-cards li{position:relative;margin:0;--brick:var(--tone)} +/* The studs: short blocks along the top edge, on a pseudo-element so the text sits on a plain fill. */ +:is(.article-layout .article-style,.hub-start) .action-cards li::before{content:"";position:absolute;left:14px;right:14px;top:-8px;height:8px;background:linear-gradient(90deg,transparent 6px,var(--brick) 6px 28px,transparent 28px) left top/34px 8px space no-repeat;border-radius:3px 3px 0 0;pointer-events:none} +:is(.article-layout .article-style,.hub-start) .action-cards a{display:flex;flex-direction:column;gap:4px;height:100%;box-sizing:border-box;min-height:96px;padding:16px 18px 22px;background:var(--brick);border-radius:3px;box-shadow:inset 0 -6px 0 rgb(0 0 0/.24);color:#fff;text-decoration:none;-webkit-print-color-adjust:exact;print-color-adjust:exact} +:is(.article-layout .article-style,.hub-start) .action-cards strong{font:500 24px/1.15 var(--display);color:#fff} +:is(.article-layout .article-style,.hub-start) .action-cards span{font:400 17px/1.4 var(--text);color:#fff} +:is(.article-layout .article-style,.hub-start) .action-cards a:hover strong{text-decoration:underline;text-decoration-thickness:1.5px;text-underline-offset:4px} +:is(.article-layout .article-style,.hub-start) .action-cards a:focus-visible{outline:3px solid var(--ink);outline-offset:3px} +@media(prefers-reduced-motion:no-preference){ + :is(.article-layout .article-style,.hub-start) .action-cards li{transition:transform .15s} + :is(.article-layout .article-style,.hub-start) .action-cards li:hover{transform:translateY(-3px)} +} +/* A disclosure inside a list item (the effects on /reversals/) is a small toggle under the entry, not a full-width bar. */ +.article-layout .article-style li .spoiler{margin:4px 0 12px;background:none;border-radius:0} +.article-layout .article-style li .spoiler>summary{display:inline-flex;padding:4px 0;font-size:15px;color:var(--link);text-decoration:underline;text-underline-offset:3px} +.article-layout .article-style li .spoiler[open]{padding:4px 14px 2px;background:var(--surface);border-radius:var(--r-small)} +.article-layout .article-style li .spoiler[open]>summary{text-decoration:none} +.article-layout .article-style li .spoiler{margin-top:0} +.article-layout .article-style li .spoiler>summary::before{content:"▸";display:inline-block;margin-inline-end:6px;text-decoration:none;transition:transform .15s} +.article-layout .article-style li .spoiler[open]>summary::before{transform:rotate(90deg)} +.article-layout .article-style .btn.btn-outline-primary{padding:10px 22px!important;border-radius:999px!important;font-size:17px!important;line-height:1.3!important} +/* Replication status beside an effect (site.js): tinted by outcome, dark text throughout. */ +.status-chip{display:inline-block;margin:0 2px 0 0;padding:1px 9px;border-radius:999px;font:600 15px/1.4 var(--text);color:var(--ink);background:var(--surface);vertical-align:1px} +.status-replicated{background:var(--edu-bg)} +.status-not-replicated{background:var(--com-bg)} +.status-reversed{background:var(--jus-bg)} +.status-mixed{background:var(--res-bg)} +/* A list with a search over it (the effects on /reversals/) reads as rows: no bullets, a rule between entries. */ +.article-layout .article-style .list-filter~ul{list-style:none;padding-inline-start:0} +.article-layout .article-style .list-filter~ul>li{margin:0;padding:12px 0 6px;border-bottom:1px solid var(--line)} +.article-layout .article-style .list-filter~ul>li>p{margin:0 0 4px} +.article-layout .article-style .list-filter-jump{display:flex;align-items:center;gap:8px;margin:10px 0 0;font:600 16px/1.3 var(--text)} +.article-layout .article-style .list-filter-jump select{flex:1;padding:8px 10px;border:1.5px solid var(--line);border-radius:var(--r-small);background:#fff;font:400 16px/1.3 var(--text);color:var(--ink)} +.article-layout .article-style .list-filter-status{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 0} +.article-layout .article-style .list-filter-status button{border:1.5px solid transparent;cursor:pointer;padding:3px 10px} +.article-layout .article-style .list-filter-status button[aria-pressed=true]{border-color:var(--ink)} +.status-no-status{background:var(--surface);color:var(--soft)} + +/* A page's opening with its picture in view (partials/doors/page_hero.html: the AWoP and citation wheels, a banner): on a + desktop the title, lede and actions sit beside a large image and span the side card and text below; + on a phone the image follows the actions at a size that keeps them on the first screen. */ +.article-layout .page-hero{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,400px);column-gap:48px;align-items:center} +/* A full-width page (AI in Metascience) gives a landscape banner the width of the cards below it. */ +.article-layout-wide .page-hero{grid-template-columns:minmax(0,1fr) minmax(0,520px);column-gap:56px} +.article-layout .page-hero h1{font-size:clamp(34px,3.2vw,44px);line-height:1.1;letter-spacing:-.01em;margin:0;text-wrap:balance} +.article-layout .page-hero .page-actions{margin-bottom:0} +.article-layout .page-hero-media{margin:0;text-align:center} +.article-layout .page-hero-media img{display:block;width:100%;height:auto} +.article-layout .page-hero-media a{display:block;border-radius:var(--r-small);overflow:hidden} +.article-layout .page-hero-media p{margin:6px 0 0;font-size:15px} +.article-layout .page-hero-media p a{display:inline;color:var(--link);text-decoration:underline;text-underline-offset:3px} +@media(min-width:1001px){ + .article-layout:has(>.page-hero){row-gap:40px} + .article-layout:not(.article-layout-wide)>.page-hero{grid-column:1/-1;grid-row:1} + .article-layout:not(.article-layout-wide):has(>.page-hero)>:is(.article-card,.reading){grid-row:2} +} +@media(max-width:1000px){ + .article-layout:has(>.page-hero){row-gap:24px} + .article-layout .page-hero{grid-template-columns:minmax(0,1fr);row-gap:24px} + .article-layout .page-hero-media{width:min(100%,340px)} + .article-layout .page-hero-landscape .page-hero-media{width:100%} +} +@media(max-width:600px){ + .article-layout .page-hero{row-gap:18px} + .article-layout .page-hero:not(.page-hero-landscape) .page-hero-media{width:min(100%,280px)} +} + +/* /reversals/ (site.js): the search, outcome and Go to controls are one toolbar that stays at the top of the screen + while the effects scroll, up to the end of the list; on a phone only the search row and count stay. Rows reached + by Tab or Go to stop below it (the scroll padding is its measured height). The entries are rows, not prose, so + they keep the reading width of a paragraph, with the Statistics toggle inside it. */ +html:has(.list-filter-section){scroll-padding-top:calc(var(--list-filter-bar,0px) + 16px)} +.article-layout .article-style .list-filter-section{max-width:27em} +.article-layout .article-style .list-filter-section>ul{max-width:none} +.article-layout .article-style .list-filter-section>.list-filter{position:sticky;top:0;z-index:6;display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;max-width:none;margin:8px 0 18px;padding:10px 14px;box-shadow:0 2px 0 var(--page)} +/* The bar itself is square and page-coloured, with its rounded panel drawn behind the controls, so text scrolling + under the stuck bar does not show at its corners. */ +@media(min-width:761px){ + .article-layout .article-style .list-filter-section>.list-filter{background:var(--page);border-radius:0} + .article-layout .article-style .list-filter-section>.list-filter::before{content:"";position:absolute;inset:0;z-index:-1;border-radius:var(--r-large);background:var(--surface)} +} +.article-layout .article-style .list-filter-section .list-filter-search{flex:1 1 300px;display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;column-gap:12px} +.article-layout .article-style .list-filter-section .list-filter label{grid-row:1;margin:0;font-size:16px;line-height:1.2} +.article-layout .article-style .list-filter-section .list-filter-count{grid-row:2;margin:0;font-size:15px;line-height:1.2;white-space:nowrap} +.article-layout .article-style .list-filter-section .list-filter input{grid-column:2;grid-row:1/3;padding:7px 14px;font-size:16px} +.article-layout .article-style .list-filter-section .list-filter-jump{margin:0;font-size:16px} +.article-layout .article-style .list-filter-section .list-filter-jump select{flex:none;max-width:170px;padding:6px 8px;font-size:15px} +.article-layout .article-style .list-filter-section .list-filter-status{flex:1 1 100%;gap:4px;margin:0} +/* Beside the "On this page" card, which lists every section, the toolbar needs no Go to menu. */ +@media(min-width:1001px){.article-layout:has(>.article-card) .article-style .list-filter-section .list-filter-jump{display:none}} +.article-layout .article-style .list-filter-section .list-filter-status button{padding:0 6px;font-size:15px} +@media(max-width:760px){ + .article-layout .article-style .list-filter-section>.list-filter{display:contents} + .article-layout .article-style .list-filter-section .list-filter-search{position:sticky;top:0;z-index:6;grid-template-columns:1fr auto;align-items:baseline;gap:4px 10px;margin:8px -20px 10px;padding:8px 20px;background:var(--surface);box-shadow:0 1px 0 var(--line)} + .article-layout .article-style .list-filter-section .list-filter label,.article-layout .article-style .list-filter-section .list-filter-count{grid-row:1} + .article-layout .article-style .list-filter-section .list-filter input{grid-column:1/-1;grid-row:2;padding:7px 14px} + .article-layout .article-style .list-filter-section .list-filter label.list-filter-jump{margin:4px 0 14px} + .article-layout .article-style .list-filter-section .list-filter-jump select{flex:1;max-width:none} + /* Below the sticky row there is room, so the outcome buttons get their usual size and spacing back. */ + .article-layout .article-style .list-filter-section .list-filter-status{gap:8px;margin:0 0 24px} + .article-layout .article-style .list-filter-section .list-filter-status button{padding:3px 10px} +} +/* Each effect shows its first lines (three on a phone), with the Statistics toggle on the name's line and a + "more" button where the text runs longer; while a search is typed the matching effects show in full. */ +.article-layout .article-style .list-filter-section>ul>li{position:relative;--entry-lines:2} +.article-layout .article-style .list-filter-section .entry-text{max-width:none;max-height:calc(var(--entry-lines) * 1.6em);max-height:calc(var(--entry-lines) * 1lh);overflow:hidden} +.article-layout .article-style .list-filter-section .entry-text::before{content:"";float:inline-end;width:96px;height:1px} +.article-layout .article-style .list-filter-section :is(.is-open,.is-searching li)>.entry-text{max-height:none} +.article-layout .article-style .list-filter-section li:has(>.entry-text)>.spoiler>summary{position:absolute;top:14px;inset-inline-end:0} +.article-layout .article-style .list-filter-section .entry-more{display:none} +.article-layout .article-style .list-filter-section .is-long>.entry-more{display:block;position:absolute;top:calc(12px + (var(--entry-lines) - 1) * 1lh);inset-inline-end:0;padding:0;padding-inline-start:2.4em;border:0;background:linear-gradient(to right,transparent,var(--page) 2em);font:inherit;font-weight:600;color:var(--link);text-decoration:underline;text-underline-offset:3px;cursor:pointer} +/* In right-to-left text the fade runs the other way. */ +.article-layout .article-style .list-filter-section .is-long>.entry-more:dir(rtl){background:linear-gradient(to left,transparent,var(--page) 2em)} +.article-layout .article-style .list-filter-section .is-open>.entry-more{position:static;display:inline-block;margin:0 0 6px;padding:0;background:none;font-size:16px} +.article-layout .article-style .list-filter-section.is-searching .entry-more{display:none} +@media(max-width:760px){.article-layout .article-style .list-filter-section>ul>li{--entry-lines:3}} diff --git a/assets/css/doors/base.css b/assets/css/doors/base.css new file mode 100644 index 00000000000..fef2c5d3055 --- /dev/null +++ b/assets/css/doors/base.css @@ -0,0 +1,40 @@ +/* Site-wide type and colour. */ +body{margin-top:0;background:var(--page);color:var(--ink);font:400 var(--text-size)/1.6 var(--text);overflow-wrap:break-word} +h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:var(--display);font-weight:var(--display-weight);color:var(--ink)} +/* When the window is narrow for its text size (below about 300px at normal text, or 600px with text at 200%, whether the whole page or only text is zoomed), long words in headings no longer fit; hyphenate them rather than break at any letter (#983). */ +@media (max-width:19em){h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{hyphens:auto}} +a{color:var(--link)} +a:hover{color:var(--ink)} +a:focus-visible,button:focus-visible,input:focus-visible,summary:focus-visible,select:focus-visible,table:focus-visible{outline:3px solid var(--ink);outline-offset:3px;border-radius:2px} +main:focus{outline:none} +/* The cookie banner sits over the foot of the screen until dismissed; site.js sets its height. */ +html{scroll-padding-block-end:var(--banner,0px)} +/* The site's content in system fonts where the Doors faces have no glyphs (decision D18). */ +main:lang(ar),main:lang(zh){font-family:var(--text)} +main:lang(ar){--display:'Newsreader','Noto Naskh Arabic','Geeza Pro','Traditional Arabic','Times New Roman',serif;--text:'Noto Sans Arabic','Segoe UI','Geeza Pro',Tahoma,sans-serif} +main:lang(zh){--display:'Newsreader','Noto Serif CJK SC','Songti SC','STSong',SimSun,serif;--text:'Source Sans 3','Noto Sans CJK SC','PingFang SC','Microsoft YaHei','Hiragino Sans GB',sans-serif} + +/* Upright everywhere, with emphasis as semi-bold. Real italics only inside reference lists + (decision D15). Font Awesome's icons set their own weight, so they are left alone. */ +*,*::before,*::after,.font-italic{font-style:normal!important} +em,i:not([class]),dfn,cite,var{font-weight:600} +.references-list :is(em,i),.references-page .article-style :is(em,i), +:is(h2,h3,h4)[id^=references i]~:is(ul,ol,p) :is(em,i),.csl-bib-body :is(em,i), +.ref-apa :is(em,i),.rl-detail-apa :is(em,i){font-style:italic!important;font-weight:400} +/* Publication titles from those lists keep their journal names and species in italics, at the title's weight. */ +:is(.fr-title,.fc-title,.ref-popup-title,.rl-panel-item-title) i{font-style:italic!important;font-weight:inherit} + +.wrap{width:min(1280px,calc(100% - 96px));margin-inline:auto} +.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap} +/* Small capitals in publication titles from Crossref (), see clusters/rich_text.html */ +.scp{font-variant:small-caps} +.skip{position:fixed;top:-100px;inset-inline-start:12px;z-index:1100;background:var(--ink);color:#fff;padding:12px 16px} +.skip:focus,.skip:hover{top:8px;color:#fff} +.crumbs{font-size:15px;line-height:1.4;color:var(--ink)} +.crumbs a{color:inherit;text-decoration:underline;text-underline-offset:3px} +.more{font-weight:700;color:var(--link);text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:4px} +.more:hover{color:var(--ink)} + +@media(max-width:1240px){.wrap{width:calc(100% - 64px)}} +@media(max-width:760px){:root{--text-size:16px}.wrap{width:calc(100% - 40px)}} +@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*::before,*::after{transition:none!important;animation:none!important}} diff --git a/assets/css/doors/chatbot.css b/assets/css/doors/chatbot.css new file mode 100644 index 00000000000..0fa1c262aaf --- /dev/null +++ b/assets/css/doors/chatbot.css @@ -0,0 +1,716 @@ +/* The JUST-OS chat window and full-page chat, loaded on demand: the first click on the launcher, or + the chatbot page. */ +/* The chat window — default: docked right panel */ +#just-os-window { + display: none; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: auto; + width: 33vw; + min-width: 320px; + background: white; + border-left: 2px solid var(--ink); + box-shadow: -4px 0 20px rgba(0, 0, 0, 0.15); + overflow: hidden; + flex-direction: column; + z-index: 10000; +} + +/* Floating mode — centered, resizable */ +#just-os-window.floating { + top: 5vh; + right: auto; + bottom: auto; + left: 50%; + transform: translateX(-50%); + width: 60vw; + height: 80vh; + min-height: 300px; + border: 2px solid var(--ink); + border-radius: 12px; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25); +} + +/* Custom resize handle (bottom-left corner, floating only) */ +.just-os-resize-handle { + display: none; + position: absolute; + bottom: 0; + left: 0; + width: 18px; + height: 18px; + cursor: nesw-resize; + z-index: 10; +} + +#just-os-window.floating .just-os-resize-handle { + display: block; +} + +.just-os-resize-handle::after { + content: ''; + position: absolute; + bottom: 3px; + left: 3px; + width: 10px; + height: 10px; + border-left: 2px solid #bbb; + border-bottom: 2px solid #bbb; +} + +.just-os-resize-handle:hover::after { + border-color: var(--soft); +} + +.chat-header { + background: var(--surface); + padding: 10px 15px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #eee; + gap: 8px; +} + +.chat-header span { + font-weight: bold; + color: var(--link); +} + +.close-btn { + background: none; + border: none; + font-size: 1.5rem; + line-height: 1; + cursor: pointer; + color: var(--soft); +} + +.close-btn:hover { + color: #000; +} + +/* ---- Chat UI styles ---- */ +.just-os-messages { + flex: 1; + overflow-y: auto; + padding: 12px; + display: flex; + flex-direction: column; + gap: 10px; + min-height: 0; +} + +.just-os-msg { + max-width: 85%; + position: relative; +} + +.just-os-msg.user { + align-self: flex-end; +} + +.just-os-msg.bot { + align-self: flex-start; +} + +.just-os-bubble-content { + padding: 8px 12px; + border-radius: 12px; + line-height: 1.45; + font-size: 0.9rem; + word-wrap: break-word; + overflow-wrap: break-word; +} + +.just-os-msg.user .just-os-bubble-content { + background: var(--link); + color: white; + border-bottom-right-radius: 4px; +} + +.just-os-msg.bot .just-os-bubble-content { + background: var(--surface); + color: var(--ink); + border-bottom-left-radius: 4px; +} + +.just-os-msg.bot .just-os-bubble-content p { + margin: 0 0 8px; +} + +.just-os-msg.bot .just-os-bubble-content p:last-child { + margin-bottom: 0; +} + +.just-os-msg.bot .just-os-bubble-content a { + color: var(--link); + text-decoration: underline; +} + +/* Inline citation links open the supporting retrieved chunk */ +.just-os-msg.bot .just-os-bubble-content a.reference-link { + color: var(--link); + text-decoration: none; + cursor: pointer; + font-size: 0.85em; + font-weight: 600; +} + +.just-os-msg.bot .just-os-bubble-content a.reference-link:hover { + text-decoration: underline; +} + +.just-os-reference-tooltip { + position: fixed; + width: min(500px, calc(100vw - 24px)); + max-height: min(420px, calc(100vh - 24px)); + overflow-y: auto; + padding: 14px; + background: white; + border: 1px solid #d9dde3; + border-radius: 8px; + box-shadow: 0 4px 18px rgba(0, 0, 0, 0.18); + color: var(--ink); + font-size: 0.85rem; + line-height: 1.45; + z-index: 10001; +} + +.just-os-reference-tooltip .title { + font-weight: 700; + margin-bottom: 4px; +} + +.just-os-reference-tooltip .metadata { + color: var(--soft); + font-size: 0.8rem; + margin-bottom: 10px; +} + +.just-os-reference-tooltip .content { + padding: 10px; + background: var(--surface); + border-radius: 6px; +} + +/* Rendered markdown inside the supporting chunk — keep it compact so a + chunk heading doesn't dwarf the tooltip. */ +.just-os-reference-tooltip .content > :first-child { + margin-top: 0; +} + +.just-os-reference-tooltip .content > :last-child { + margin-bottom: 0; +} + +.just-os-reference-tooltip .content p { + margin: 0 0 8px; +} + +.just-os-reference-tooltip .content h1, +.just-os-reference-tooltip .content h2, +.just-os-reference-tooltip .content h3, +.just-os-reference-tooltip .content h4, +.just-os-reference-tooltip .content h5, +.just-os-reference-tooltip .content h6 { + margin: 10px 0 6px; + font-size: 0.95rem; + font-weight: 700; + line-height: 1.3; +} + +.just-os-reference-tooltip .content ul, +.just-os-reference-tooltip .content ol { + margin: 0 0 8px; + padding-left: 20px; +} + +.just-os-reference-tooltip .content li { + margin-bottom: 2px; +} + +.just-os-reference-tooltip .content a { + color: var(--link); + text-decoration: underline; +} + +.just-os-reference-tooltip .content code { + background: #eceff3; + border-radius: 3px; + padding: 1px 4px; + font-size: 0.85em; +} + +.just-os-reference-tooltip .source-link { + display: inline-block; + margin-top: 10px; + color: var(--link); + font-weight: 600; +} + +.just-os-copy-btn { + display: block; + margin-top: 4px; + margin-left: auto; + background: none; + border: none; + cursor: pointer; + font-size: 0.85rem; + padding: 2px 4px; + color: #aaa; + border-radius: 4px; + opacity: 0; + transition: opacity 0.2s; + line-height: 1; +} + +.just-os-msg.bot:hover .just-os-copy-btn, +.just-os-msg.bot:focus-within .just-os-copy-btn { + opacity: 1; +} + +.just-os-copy-btn:hover { + color: var(--soft); +} + +.just-os-copy-btn svg { + width: 15px; + height: 15px; + display: block; +} + +/* Feedback — always-visible row beneath bot message */ +.rating-bar { + display: flex; + align-items: center; + gap: 6px; + margin-top: 8px; +} + +.rating-bar .rating-btn { + background: none; + border: 1px solid var(--link); + color: var(--link); + cursor: pointer; + font-size: 15px; + padding: 2px 8px; + line-height: 1.4; + border-radius: 12px; + font-weight: 500; + transition: background 0.15s, color 0.15s; +} + +.rating-bar .rating-btn:hover { + background: var(--link); + color: #fff; +} + +/* Info (i) — subtle disclosure of what feedback is stored, with instant tooltip */ +.rating-bar .rating-info { + position: relative; + display: inline-flex; + align-items: center; + color: #bbb; + cursor: help; + margin-left: 3px; +} + +.rating-bar .rating-info svg { + width: 14px; + height: 14px; + display: block; +} + +.rating-bar .rating-info:hover, +.rating-bar .rating-info:focus { + color: var(--link); + outline: none; +} + +.rating-bar .rating-tip { + position: absolute; + bottom: calc(100% + 7px); + /* Centre the tip over the (i) icon, which sits near the panel centre, so the + full text stays inside the narrow docked chat panel (its overflow clips). */ + left: 50%; + transform: translateX(-50%) translateY(3px); + width: max-content; + max-width: 230px; + background: var(--link); + color: #fff; + font-size: 0.7rem; + font-style: normal; + font-weight: 400; + line-height: 1.4; + text-align: left; + padding: 7px 9px; + border-radius: 6px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + opacity: 0; + visibility: hidden; + transition: opacity 0.1s ease, transform 0.1s ease; + pointer-events: none; + white-space: normal; + z-index: 10; +} + +/* small arrow pointing down to the icon */ +.rating-bar .rating-tip::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + border: 5px solid transparent; + border-top-color: var(--link); +} + +.rating-bar .rating-info:hover .rating-tip, +.rating-bar .rating-info:focus .rating-tip, +.rating-bar .rating-info:focus-within .rating-tip { + opacity: 1; + visibility: visible; + transform: translateX(-50%) translateY(0); +} + +.rating-thanks { + font-size: 0.75rem; + color: #4caf50; + font-style: italic; +} + +/* Comment box — appears after "Bad" rating */ +.comment-box { + margin-top: 5px; +} + +.comment-box-inner { + display: flex; + align-items: center; + gap: 6px; +} + +.comment-box-inner input[type="text"] { + flex: 1; + min-width: 0; + border: none; + border-bottom: 1px solid #ddd; + padding: 2px 4px; + font-size: 0.75rem; + font-family: inherit; + background: transparent; + outline: none; + color: var(--ink); +} + +.comment-box-inner input[type="text"]:focus { + border-bottom-color: var(--link); +} + +.comment-submit { + background: none; + border: none; + color: var(--link); + font-size: 15px; + cursor: pointer; + padding: 0; + white-space: nowrap; + font-weight: 500; + opacity: 0.8; +} + +.comment-submit:hover { + opacity: 1; + text-decoration: underline; +} + +/* References list below bot message */ +.just-os-references { + margin-top: 8px; + padding: 8px 10px; + background: var(--surface); + border-radius: 8px; + border: 1px solid #e8eaed; + font-size: 0.8rem; + line-height: 1.4; +} + +.just-os-references summary { + cursor: pointer; + font-weight: 600; + color: var(--soft); + font-size: 0.8rem; + user-select: none; +} + +.just-os-references ol { + margin: 6px 0 0; + padding-left: 18px; +} + +.just-os-references li { + margin-bottom: 4px; + color: var(--soft); +} + +.just-os-references li a { + color: var(--link); + text-decoration: none; +} + +.just-os-references li a:hover { + text-decoration: underline; +} + +.just-os-typing { + display: none; + padding: 6px 12px; + color: var(--soft); + font-size: 0.85rem; + font-style: italic; +} + +.just-os-error { + color: #c0392b; + font-style: italic; +} + +.just-os-input-area { + display: flex; + align-items: flex-end; + gap: 8px; + padding: 10px 12px; + border-top: 1px solid #eee; + background: #fafafa; +} + +.just-os-input { + flex: 1; + border: 1px solid #ccc; + border-radius: 8px; + padding: 8px 10px; + font-size: 0.9rem; + resize: none; + max-height: 120px; + font-family: inherit; + line-height: 1.4; +} + +.just-os-input:focus { + outline: 3px solid var(--ink); + outline-offset: 1px; + border-color: var(--link); +} + +.just-os-send-btn { + background: var(--link); + color: white; + border: none; + border-radius: 8px; + padding: 8px 14px; + cursor: pointer; + font-size: 0.9rem; + white-space: nowrap; + flex-shrink: 0; +} + +.just-os-send-btn:hover { + background: var(--ink); +} + +.just-os-send-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* New Chat / History buttons */ +.just-os-new-chat-btn, +.just-os-history-btn { + background: none; + border: none; + cursor: pointer; + font-size: 0.8rem; + color: var(--soft); + padding: 4px 8px; + border-radius: 4px; +} + +.just-os-new-chat-btn:hover, +.just-os-history-btn:hover { + background: #eee; + color: var(--ink); +} + +/* History button pushed to the right in the widget header */ +.chat-header .just-os-history-btn { + margin-left: auto; +} + +/* History panel — overlays the chat area */ +.just-os-history-panel { + position: absolute; + inset: 0; + background: #fff; + z-index: 20; + flex-direction: column; + overflow: hidden; +} + +.just-os-history-header { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 15px; + border-bottom: 1px solid #eee; + background: var(--surface); + flex-shrink: 0; +} + +.just-os-history-header span { + font-weight: bold; + color: var(--link); +} + +.just-os-history-back { + background: none; + border: none; + cursor: pointer; + font-size: 0.9rem; + color: var(--link); + padding: 4px 6px; + border-radius: 4px; +} + +.just-os-history-back:hover { + background: #eee; +} + +.just-os-history-list { + flex: 1; + overflow-y: auto; + padding: 8px; + min-height: 0; +} + +.just-os-history-empty { + color: var(--soft); + font-size: 0.85rem; + text-align: center; + margin: 24px 0; +} + +.just-os-history-item { + display: flex; + align-items: stretch; + border-radius: 8px; + margin-bottom: 4px; +} + +.just-os-history-item:hover { + background: var(--surface); +} + +.just-os-history-item.active { + background: var(--res-bg); +} + +.just-os-history-open { + flex: 1; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 2px; + background: none; + border: none; + cursor: pointer; + text-align: left; + padding: 8px 10px; + min-width: 0; +} + +.just-os-history-title { + font-size: 0.88rem; + color: var(--ink); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +} + +.just-os-history-meta { + font-size: 15px; + color: var(--soft); +} + +.just-os-history-delete { + background: none; + border: none; + cursor: pointer; + color: #bbb; + font-size: 0.9rem; + padding: 0 10px; + border-radius: 8px; + flex-shrink: 0; +} + +.just-os-history-delete:hover { + color: #c0392b; +} + +/* Standalone page offline notice */ +.just-os-offline-page { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 48px 32px; + border: 1px solid #f1d6d2; + border-radius: 12px; + background: #fdf5f4; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + color: #4a1f1a; +} + +.just-os-offline-page__icon { + width: 64px; + height: 64px; + color: #c0392b; + margin-bottom: 16px; +} + +.just-os-offline-page__title { + font-size: 1.4rem; + font-weight: 700; + margin: 0 0 8px; + color: #c0392b; +} + +.just-os-offline-page__body { + font-size: 1rem; + line-height: 1.5; + max-width: 640px; + margin: 0; +} + +/* Responsive */ +@media (max-width: 768px) { + #just-os-window { + width: 100vw; + } + + #just-os-window.floating { + width: 95vw; + height: 80vh; + top: 5vh; + } +} +/* Send is the site's primary pill. */ +.just-os-send-btn,#just-os-fullpage .just-os-send-btn{background:var(--link);border-radius:999px;padding:9px 18px;font:600 16px/1.3 var(--text)} +.just-os-send-btn:hover,#just-os-fullpage .just-os-send-btn:hover{background:var(--ink)} +.just-os-input{font-size:16px} diff --git a/assets/css/doors/citations.css b/assets/css/doors/citations.css new file mode 100644 index 00000000000..1ef82adb73a --- /dev/null +++ b/assets/css/doors/citations.css @@ -0,0 +1,5 @@ +a.cite{color:inherit;text-decoration:underline dotted 1px var(--soft);text-underline-offset:3px} +a.cite:hover,a.cite:focus{color:var(--link)} +/* The button that shows a reference's full author list (layouts/partials/citations/authors.html). */ +.ref-authors-toggle{display:inline;margin:0 .15em;padding:0 .55em;background:#fff;border:1px solid var(--line);border-radius:999px;color:var(--link);font:600 15px/1.45 var(--text);white-space:nowrap;cursor:pointer;vertical-align:baseline} +.ref-authors-toggle:hover{border-color:var(--link);text-decoration:underline} diff --git a/assets/css/doors/clusters.css b/assets/css/doors/clusters.css new file mode 100644 index 00000000000..c778c11b676 --- /dev/null +++ b/assets/css/doors/clusters.css @@ -0,0 +1,2408 @@ +/* Clusters and disciplines. Carried over from the theme-era stylesheet with Doors colours and type; + a bespoke redraw is left for after launch (migration plan, Phase 3 "Cut to fit"). */ +.clusters-head{padding:32px 0 8px} +.clusters-head .crumbs{margin-bottom:18px} +.clusters-head h1{font-size:clamp(36px,4.2vw,58px);line-height:1.06;letter-spacing:-.01em;margin:0} +.clusters-head .standfirst{font:500 clamp(19px,1.6vw,22px)/1.35 var(--display);margin:12px 0 0;max-width:34em} +.clusters-layout .row{margin-inline:0} +.clusters-layout .row>*{padding-inline:0} +@media(min-width:992px){.clusters-layout .row{flex-wrap:nowrap;gap:24px}.clusters-sidebar.col-lg-4,.clusters-sidebar.col-xl-3{flex:0 0 280px;width:280px}.clusters-content.col-lg-8,.clusters-content.col-xl-9{flex:1;width:auto;min-width:0}} +@media(max-width:760px){.clusters-head{padding:20px 0 8px}} +/* Clusters v4 page styles */ +/* -------------------------------------------------- */ + +/* The site header doesn't travel with the page, so nothing sits above pinned parts. */ +.clusters-layout { + margin-top: 0.5rem; + --clusters-sticky-offset: 0px; + --clusters-sticky-gap: 0px; + --clusters-controls-height: 0px; +} + +.clusters-controls { + position: sticky; + top: calc(var(--clusters-sticky-offset) + env(safe-area-inset-top, 0px) + var(--clusters-sticky-gap)); + z-index: 1020; + display: grid; + gap: 0.45rem; + background: #fefdf6; + padding: 0.5rem; + border: 1px solid var(--surface); + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05); +} + +/* ---- Mobile nav toggle ---- */ +.clusters-mobile-toggle { + width: 100%; + margin-top: 0; + text-align: left; + font-size: 0.9rem; + padding: 0.5rem 1rem; + border-radius: 6px; +} + +.clusters-mobile-toggle.active { + background: var(--ink); + color: #fff; + border-color: var(--ink); +} + +/* ---- Sidebar ---- */ +.clusters-sidebar { + display: none; +} + +@media (min-width: 992px) { + .clusters-sidebar { + display: block; + } +} + +.clusters-sidebar.sidebar-open { + display: block; +} + +.clusters-sidebar-inner { + position: sticky; + top: calc( + var(--clusters-sticky-offset) + + env(safe-area-inset-top, 0px) + + var(--clusters-sticky-gap) + + var(--clusters-controls-height) + + 8px + ); + max-height: calc( + 100vh - var(--clusters-sticky-offset) - var(--clusters-sticky-gap) - + var(--clusters-controls-height) - 24px - var(--banner, 0px) + ); + overflow-y: auto; + padding: 0 0.75rem 0.75rem; + background: var(--surface); + border: 1px solid var(--surface); + border-radius: 8px; + font-size: 0.82rem; +} + +.sidebar-header { + position: sticky; + top: 0; + z-index: 2; + background: var(--surface); + padding: 0; + border-bottom: 1px solid var(--surface); + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--soft); + margin: 0; +} + +/* No air between section label and cluster list (hub + subpages) */ +.clusters-sidebar-inner #cluster-nav { + margin: 0; + padding: 0; +} + +/* First cluster row: remove top padding (main source of “gap” below header border) */ +.clusters-sidebar-inner #cluster-nav > .cluster-nav-group:first-child > .cluster-nav-heading { + padding-top: 0; +} + + +.cluster-nav-heading { + display: flex; + align-items: center; + gap: 0.3rem; + padding: 0.35rem 0.25rem; + font-weight: 600; + color: var(--ink); + text-decoration: none !important; + font-size: 0.82rem; + cursor: pointer; + border-radius: 4px; + line-height: 1.3; +} + +.cluster-nav-heading:hover { + background: rgba(0,0,0,0.04); + color: #111; +} + +/* Active cluster on subpages — no special styling, looks same as hub */ + +.cluster-nav-toggle { + display: inline-block; + width: 1em; + flex-shrink: 0; + font-size: 0.9em; + transition: transform 0.15s; +} + +.cluster-nav-subs { + list-style: none; + padding-left: 1.6rem; + margin: 0 0 0.25rem 0; +} + +.cluster-nav-subs .nav-link { + padding: 0.2rem 0.35rem; + font-size: 0.78rem; + color: var(--soft); + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 0.5rem; + line-height: 1.3; + border-radius: 3px; +} + +.cluster-nav-subs .nav-link:hover { + background: rgba(0,0,0,0.04); + color: #111; + text-decoration: none; +} + +.cluster-nav-subs .badge { + font-size: 15px; + font-weight: 400; + color: var(--soft); + background: transparent; + flex-shrink: 0; +} + +/* ---- Cluster sections ---- */ +.cluster-section { + padding: 1.5rem 1.25rem; + margin-bottom: 0.75rem; + border-radius: 8px; + border: 1px solid rgba(0,0,0,0.06); +} + +.cluster-header { + margin-bottom: 0.5rem; +} + +.cluster-title { + font-size: 0.95rem; + font-weight: 600; + margin-bottom: 0.1rem; + display: flex; + align-items: center; + gap: 0.35rem; + line-height: 1.3; +} + +.cluster-number { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.45em; + height: 1.45em; + border-radius: 50%; + background: rgba(0,0,0,0.1); + font-size: 0.75em; + flex-shrink: 0; +} + +.cluster-stats { + font-size: 0.7rem; + color: var(--soft); +} + +.cluster-description { + font-size: 0.8rem; + line-height: 1.5; + color: var(--soft); + margin-bottom: 0.75rem; +} + +/* ---- Inline clusters list search (toolbar; not Algolia/site search) ---- */ +.clusters-inline-search { + position: relative; +} + +.clusters-inline-search__row { + display: flex; + flex-wrap: wrap; + align-items: stretch; + gap: 0.4rem; + position: relative; + z-index: 2; + background: #fefdf6; + padding-bottom: 0.45rem; +} + +.clusters-inline-search__input { + flex: 1 1 auto; + min-width: 0; + max-width: 22rem; + font-size: 0.88rem; + padding: 0.45rem 0.65rem; + border-radius: 6px; +} + +.clusters-inline-search__submit { + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 2.35rem; + padding: 0.35rem 0.55rem; + border-radius: 6px; +} + +.clusters-inline-search__option-label { + display: inline-flex; + align-items: center; + gap: 0.35rem; + font-size: 0.78rem; + color: var(--soft); + cursor: pointer; + user-select: none; + margin: 0 0 0 0.5rem; + white-space: nowrap; +} + +.clusters-inline-search__option-label input[type="checkbox"] { + margin: 0; + cursor: pointer; +} + +.clusters-inline-search__hit-icon { + flex-shrink: 0; + color: var(--soft); + font-size: 0.7rem; + align-self: flex-start; + margin-top: 0.2rem; +} + +.clusters-inline-search__toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + padding: 0.35rem 0.5rem 0.35rem 0.75rem; +} + +.clusters-inline-search__close { + flex-shrink: 0; + line-height: 1; + padding: 0.25rem 0.45rem !important; +} + +/* Like browser Ctrl+F: flat highlight, no ring/border */ +.clusters-inline-search-highlight { + outline: none !important; + box-shadow: none !important; + background-color: rgba(255, 220, 0, 0.55) !important; + border-radius: 2px; + transition: background-color 0.35s ease; +} + +/* Panel below input row: toolbar fixed, only hit list scrolls (no overlap with search box) */ +.clusters-inline-search__panel { + display: none; + flex-direction: column; + width: 100%; + max-height: 360px; + margin-top: 0.75rem; + background: #fff; + border: 1px solid var(--line); + border-radius: 6px; + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08); + overflow: hidden; + position: relative; + z-index: 1; +} + +.clusters-inline-search__panel.clusters-inline-search__panel--open { + display: flex; +} + +.clusters-inline-search__toolbar-host { + flex-shrink: 0; + border-bottom: 1px solid var(--line); + background: #fafafa; +} + +.clusters-inline-search__results-body { + flex: 1 1 auto; + min-height: 0; + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +.clusters-inline-search__count { + font-size: 15px; + color: var(--soft); + font-weight: 600; + margin: 0; +} + +.clusters-inline-search__hit { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 0.4rem; + padding: 0.5rem 0.75rem; + border-bottom: 1px solid #f0f0f0; + color: var(--ink); + text-decoration: none; + font-size: 0.8rem; + text-align: left; +} + +.clusters-inline-search__hit-text { + display: flex; + flex-direction: column; + gap: 0.15rem; + min-width: 0; +} + +.clusters-inline-search__hit-label { + line-height: 1.35; +} + +.clusters-inline-search__hit--publication .clusters-inline-search__hit-label { + font-size: 15px; +} + +.clusters-inline-search__hit-meta { + font-size: 15px; + color: var(--soft); + line-height: 1.25; +} + +.clusters-inline-search__hit:hover { + background: #f5f7fa; + text-decoration: none; + color: #111; +} + +.clusters-inline-search__empty, +.clusters-inline-search__more { + padding: 0.5rem 0.75rem; + font-size: 0.78rem; + color: var(--soft); +} + +.clusters-cluster-lead { + max-width: 48rem; +} + +/* ---- Accordion sub-cluster sections ---- */ +.acc-list { + margin-top: 0.25rem; +} +.acc-section + .acc-section { + border-top: 1px solid rgba(0,0,0,0.06); +} +.acc-header { + display: flex; + align-items: center; + gap: 0.35rem; + padding: 0.4rem 0.15rem; + cursor: pointer; + font-size: 0.76rem; + font-weight: 600; + color: var(--soft); + transition: color 0.15s; + user-select: none; +} +.acc-header:hover { color: var(--ink); } +.acc-trigger { padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: start; cursor: pointer; } +.acc-header--rec { color: #004055; } +.acc-header--rec:hover { color: #00556e; } +.acc-header.acc-disabled { + color: #bbb; + cursor: default; +} +.acc-header.acc-disabled:hover { color: #bbb; } +.acc-chevron { + font-size: 0.55rem; + color: #bbb; + transition: transform 0.25s ease; + flex-shrink: 0; + display: inline-block; +} +.acc-chevron.acc-open { transform: rotate(90deg); color: var(--soft); } +.acc-disabled .acc-chevron { color: var(--line); } +.acc-label { + flex: 1; + min-width: 0; + line-height: 1.3; + /* Rendered as an

for document semantics/SEO; strip heading defaults so it + reads as the compact accordion label (inherits .acc-header sizing/colour). */ + margin: 0; + padding: 0; + font-size: inherit; + font-weight: inherit; + color: inherit; + font-family: inherit; +} +.acc-leads { + flex: 0 1 auto; + max-width: 42%; + padding: 0.12rem 0.4rem; + border-left: 2px solid rgba(0,0,0,0.12); + color: rgba(0,0,0,0.52); + font-size: 15px; + font-weight: 400; + line-height: 1.3; +} +.acc-leads__label { + color: rgba(0,0,0,0.66); + font-weight: 600; +} +.acc-leads p { + display: inline; + margin: 0; +} +.acc-count { + margin-left: auto; + font-size: 0.55rem; + font-weight: 400; + color: #bbb; + flex-shrink: 0; + white-space: nowrap; +} +.acc-copy-link { + position: relative; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.4rem; + height: 1.4rem; + padding: 0; + margin-left: 0.15rem; + border: none; + background: none; + color: #bbb; + cursor: pointer; + border-radius: 3px; + opacity: 0.4; + transition: opacity 0.15s, color 0.15s, background 0.15s; +} +.acc-copy-link svg { width: 0.8rem; height: 0.8rem; } +.acc-header:hover .acc-copy-link, +.acc-header:focus-within .acc-copy-link { opacity: 1; } +.acc-copy-link:hover { color: var(--soft); background: rgba(0,0,0,0.05); } +.acc-copy-link:focus-visible { opacity: 1; outline: 2px solid rgba(0,0,0,0.25); outline-offset: 1px; } +.acc-copy-link.is-copied { color: #2e7d32; opacity: 1; } +.acc-copy-link.is-copied::after { + content: "Copied!"; + position: absolute; + bottom: calc(100% + 4px); + right: 0; + background: #2e7d32; + color: #fff; + font-size: 0.55rem; + font-weight: 600; + line-height: 1; + padding: 0.25rem 0.4rem; + border-radius: 3px; + white-space: nowrap; + pointer-events: none; + z-index: 5; +} +.acc-body { + padding: 0 0.15rem 0.45rem 1rem; + overflow: hidden; + transition: max-height 0.3s ease, opacity 0.25s ease; +} +.acc-body.acc-collapsed { + max-height: 0 !important; + opacity: 0; + padding-top: 0; + padding-bottom: 0; +} +.acc-toggle-all { + font-family: inherit; + font-size: 0.58rem; + font-weight: 600; + color: var(--soft); + background: none; + border: 1px solid rgba(0,0,0,0.1); + border-radius: 3px; + padding: 0.15rem 0.45rem; + cursor: pointer; + transition: color 0.15s, border-color 0.15s; + float: right; + margin-top: 0.15rem; +} +.acc-toggle-all:hover { color: var(--soft); border-color: rgba(0,0,0,0.2); } + +/* ---- Sub-cluster heading & description (inside accordion body) ---- */ +.sc-heading { + font-size: 0.9rem; + font-weight: 600; + margin: 0.5rem 0 0.35rem; + line-height: 1.3; +} + +.sc-description { + font-size: 0.75rem; + color: var(--soft); + margin-bottom: 0.45rem; + line-height: 1.45; +} + +.sc-description p { + margin-bottom: 0.45rem; +} + +.sc-description p:last-child { + margin-bottom: 0; +} + +.sc-description__lede { + font-style: normal; + color: var(--ink); + margin-bottom: 0.25rem; +} + +.sc-description a { + color: #1c7ed6; + word-break: break-word; +} + +.sc-description a:hover { + color: #1264ab; +} + +/* ---- Publication list ---- */ +.publication-list { + padding-left: 1.5rem; + margin: 0; +} + +.publication-item { + padding: 0.35rem 0; + font-size: 0.82rem; + line-height: 1.5; + margin-bottom: 0.3rem; +} + +.publication-item a { + color: #1c7ed6; + word-break: break-all; +} + +.publication-item a:hover { + color: #1264ab; +} + +/* ---- Responsive ---- */ +@media (max-width: 1199px) { + .clusters-controls { + padding: 0.4rem; + } + + .clusters-inline-search__panel { + max-height: 320px; + } + + .cluster-section { + padding: 1rem 0.75rem; + } + +} + +@media (max-width: 991px) { + /* Flush under sticky controls (no mb-3 gap) + drawer meets toggle bar */ + .clusters-layout .clusters-controls { + margin-bottom: 0 !important; + } + + /* In-flow sidebar hidden until open; when open use fixed drawer so it stays visible after page scroll */ + .clusters-sidebar-inner { + position: static; + max-height: 50vh; + margin-bottom: 1rem; + } + + .sidebar-header { + position: static; + border-bottom: 0; + padding: 0; + } + + .clusters-sidebar.sidebar-open { + display: block; + position: fixed; + left: 0; + right: 0; + width: 100%; + max-width: 100vw; + margin: 0; + padding: 0 0.75rem calc(0.75rem + env(safe-area-inset-bottom, 0px)); + box-sizing: border-box; + top: calc( + var(--clusters-sticky-offset) + + env(safe-area-inset-top, 0px) + + var(--clusters-sticky-gap) + + var(--clusters-controls-height) + ); + bottom: 0; + /* Above .clusters-controls (z-index: 1020) so the open drawer fully covers + the sticky search bar instead of it showing through on top. */ + z-index: 1030; + overflow-y: auto; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; + background: #fefdf6; + border-top: 1px solid var(--surface); + box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.12); + border-radius: 0; + } + + /* The JUST-OS chat widget (z-index: 9999, layouts/partials/chatbot.html) would + otherwise float on top of the open drawer regardless of the z-index above. */ + body.clusters-mobile-nav-open #just-os-widget { + display: none; + } + + .clusters-sidebar.sidebar-open .clusters-sidebar-inner { + position: static; + max-height: none; + margin-bottom: 0; + margin-top: 0; + overflow: visible; + padding-top: 0; + border-radius: 0; + border: none; + border-bottom: 1px solid var(--surface); + } + + .clusters-sidebar.sidebar-open .sidebar-header { + position: sticky; + top: 0; + z-index: 1; + background: var(--surface); + padding: 0; + border-bottom: 1px solid var(--surface); + margin: 0; + } + +} + +@media (max-width: 768px) { + .clusters-controls { + border-radius: 6px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + } + + .clusters-inline-search__input { + font-size: 0.86rem; + padding: 0.46rem 0.62rem; + } + + .clusters-inline-search__panel { + max-height: 280px; + } + + .cluster-section { + padding: 0.9rem 0.65rem; + border-radius: 7px; + } + + .cluster-title { + align-items: flex-start; + gap: 0.35rem; + } + + .cluster-number { + margin-top: 0.1rem; + } + + .cluster-stats { + display: block; + margin-top: 0.2rem; + } + + .publication-list { + padding-left: 1rem; + } + + .publication-item { + margin-bottom: 0.2rem; + } +} + +@media (max-width: 576px) { + .clusters-controls { + padding: 0.35rem; + margin-bottom: 0.6rem !important; + gap: 0.35rem; + } + + .cluster-title { + font-size: 0.86rem; + line-height: 1.35; + } + + .cluster-description { + font-size: 0.78rem; + } + + .publication-item { + font-size: 0.75rem; + line-height: 1.45; + } + +} + +@media (max-width: 420px) { + .clusters-layout { + margin-top: 0.25rem; + } + + .clusters-controls { + padding: 0.3rem; + border-radius: 5px; + } + + .clusters-inline-search__input { + font-size: 0.82rem; + padding: 0.42rem 0.55rem; + max-width: 100%; + } + + .clusters-inline-search__panel { + max-height: 240px; + } + + .clusters-mobile-toggle { + font-size: 0.82rem; + padding: 0.45rem 0.8rem; + } + + .cluster-section { + padding: 0.8rem 0.55rem; + margin-bottom: 0.6rem; + } + + .cluster-title { + font-size: 0.8rem; + } + + .cluster-number { + width: 1.35em; + height: 1.35em; + } + + .cluster-stats { + font-size: 15px; + } + + .sc-heading { + font-size: 0.88rem; + } +} + +@media (max-width: 390px) { + .clusters-mobile-toggle { + margin-top: 0.45rem; + } + + .publication-item { + font-size: 15px; + line-height: 1.4; + } + + .clusters-back-to-top { + width: 2.35rem; + height: 2.35rem; + font-size: 0.85rem; + } +} +/* -------------------------------------------------- */ +/* Clusters Intro Section Styles */ +/* -------------------------------------------------- */ + +.clusters-intro { + margin-bottom: 0; +} + +/* Hero: text left, image right */ +.intro-hero { + display: flex; + align-items: center; + gap: 2rem; +} + +.intro-hero-text { + flex: 1 1 60%; +} + +.intro-hero-image { + flex: 0 0 auto; + max-width: 360px; +} + +.intro-paragraph { + font-size: 0.95rem; + color: var(--ink); + margin-bottom: 0; + line-height: 1.7; +} + +/* Partner logo beside the disciplines intro (plain image, no lightbox) */ +.disciplines-partner { + text-align: center; + max-width: 220px; + /* Sit beside the opening paragraph rather than centred against the whole column. */ + align-self: flex-start; + margin-top: 1rem; +} + +.disciplines-partner img { + width: 160px; + height: auto; +} + +.disciplines-partner__caption { + font-size: 0.8rem; + color: var(--soft); + margin-bottom: 0.4rem; + white-space: nowrap; +} + +/* Lead-in label in front of each citation in the disciplines "How to cite" box */ +.disciplines-cite-lead { + display: block; + font-weight: 600; + font-size: 0.82rem; + margin-bottom: 0.2rem; +} + +/* Keep citation guidance discoverable without letting a growing author list + * dominate the page. The native details element is collapsed by default. */ +.disciplines-attribution { + margin: 0 0 1.25rem; +} + +.disciplines-citation-summary { + cursor: pointer; + color: #b3005a; + font-size: 1.05rem; + font-weight: 700; + text-align: center; + padding: 0.6rem; + list-style-position: inside; +} + +.disciplines-citation-summary:hover, +.disciplines-citation-summary:focus-visible { + color: #8e0048; + text-decoration: underline; +} + +.disciplines-attribution[open] .disciplines-citation-summary { + margin-bottom: 0.35rem; +} + +.clusters-display-section > .wrap > .disciplines-attribution { + max-width: 760px; + margin: 1.5rem auto 0; +} + +/* Discipline / field leads, shown on the coloured field background */ +.disciplines-leads { + font-size: 0.9rem; + margin: 0 0 0.55rem; +} + +.disciplines-leads p { + margin: 0; + display: inline; +} + +.disciplines-leads__label { + font-weight: 600; +} + +@media (max-width: 600px) { + .acc-header { + flex-wrap: wrap; + } + + .acc-leads { + flex-basis: 100%; + max-width: none; + margin-left: 1rem; + padding-top: 0; + padding-bottom: 0.1rem; + border-left: 0; + } +} + +/* Clusters map: a mind map drawn by partials/clusters/map.html */ +.clusters-map { margin: 1.75rem 0 0.5rem; } +.clusters-map__frame { overflow: hidden; } +.clusters-map__art { display: block; width: 100%; height: auto; font-family: var(--text); } +/* geometricPrecision: Chromium on Linux otherwise rounds each glyph to whole pixels, which sets the + text about 6% wider than the advance widths the build wraps it with (clusters/map_wrap.html). */ +.clusters-map__art text { fill: #fff; text-anchor: middle; pointer-events: none; text-rendering: geometricPrecision; } +.clusters-map__line { fill: none; stroke: var(--soft); stroke-width: 2; stroke-linecap: round; } +.clusters-map__seg path:first-of-type, .clusters-map__label rect, .clusters-map__pill rect { fill: var(--c); } +.clusters-map__icon { fill: #fff; } +.clusters-map__label text { font-size: 20px; font-weight: 700; } +.clusters-map__pill text { font-size: 17px; font-weight: 400; } +.clusters-map__disc { fill: #fff; stroke: var(--res); stroke-width: 7; } +.clusters-map__cluster { transition: opacity 0.15s; } +.clusters-map__art.is-pointing .clusters-map__cluster:not(.is-active) { opacity: 0.35; } +.clusters-map__art a:hover rect { fill: color-mix(in srgb, var(--c) 80%, #000); } +.clusters-map__caption { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem 1.5rem; margin-top: 0.75rem; } +.clusters-map__enlarge, .clusters-map__close { + font: inherit; font-size: 0.9rem; font-weight: 600; color: var(--ink); background: var(--page); + border: 1px solid var(--line); border-radius: var(--r-small); padding: 0.35rem 0.9rem; cursor: pointer; +} +.clusters-map__enlarge:hover, .clusters-map__close:hover { border-color: var(--ink); } +.clusters-map__list { flex: 1 1 20rem; } +.clusters-map__list summary { cursor: pointer; color: var(--link); font-size: 0.95rem; } +.clusters-map__list ol { margin: 0.75rem 0 0; padding-left: 1.4rem; columns: 2 22rem; column-gap: 2rem; font-size: 0.95rem; } +.clusters-map__list li { break-inside: avoid; margin-bottom: 0.5rem; } +.clusters-map__list ul { margin: 0.2rem 0 0; padding-left: 1.1rem; font-size: 0.9rem; } +.clusters-map__list ul li { margin: 0; } +.clusters-map__list a { color: var(--ink); } +.clusters-map__dialog { width: 96vw; max-width: none; height: 94vh; max-height: none; padding: 0; border: 0; border-radius: var(--r-large); background: var(--page); } +.clusters-map__dialog::backdrop { background: rgba(38, 45, 53, 0.6); } +.clusters-map__close { position: absolute; top: 0.75rem; right: 0.75rem; z-index: 1; box-shadow: 0 1px 4px rgba(38, 45, 53, 0.25); } +.clusters-map__scroll { height: 100%; overflow: auto; } +.clusters-map__scroll .clusters-map__art { width: auto; max-width: none; height: auto; margin: 0 auto; } +.clusters-map__compact { display: none; } +.clusters-map__hub { display: block; width: min(240px, 70%); margin: 0 auto 1rem; } +.clusters-map__segment { fill: var(--c); } +.clusters-map__chips { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; } +.clusters-map__chips a { + display: flex; align-items: center; gap: 0.8rem; padding: 0.55rem 0.9rem; border-radius: 12px; + background: var(--c); color: #fff; font-weight: 700; line-height: 1.25; text-decoration: none; +} +.clusters-map__chips svg { flex: 0 0 1.4rem; height: 1.4rem; fill: currentColor; } +.clusters-map__chips small { display: block; font-size: 0.85rem; font-weight: 400; } +/* Phones: the hub and the cluster labels; the pills are in the list and the full-size view. */ +@media (max-width: 760px) { + .clusters-map__frame { display: none; } + .clusters-map__compact { display: block; } +} +@media (forced-colors: active) { + .clusters-map__art text, .clusters-map__icon { fill: CanvasText; } + .clusters-map__seg path:first-of-type, .clusters-map__segment, .clusters-map__label rect, .clusters-map__pill rect { fill: Canvas; stroke: CanvasText; } + .clusters-map__chips a { border: 1px solid CanvasText; } +} + +/* Stack on small screens */ +@media (max-width: 768px) { + .intro-hero { + flex-direction: column; + gap: 1.25rem; + } + + .intro-hero-image { + max-width: 260px; + } + + .disciplines-partner { + align-self: center; + margin-top: 0; + } + + .intro-paragraph { + font-size: 0.9rem; + } + +} + + +/* -------------------------------------------------- */ +/* Clusters Page Section Styles */ +/* -------------------------------------------------- */ + +/* Full-width cluster layout */ +.cluster-full-width { + max-width: 100% !important; + padding: 15px !important; + margin-left: 15px !important; + margin-right: 15px !important; + margin-top: 0 !important; + margin-bottom: 0 !important; +} + + +.clusters-intro-section { + background: linear-gradient(to bottom, #fefdf6 0%, #f9f8f5 100%); + padding: 0.5rem 0 0.5rem; + margin: 0.15rem 0 0; +} + +.clusters-display-section { + background: white; + padding: 0.75rem 0 2rem; + margin: 0 0 1.5rem; + border-top: 1px solid var(--surface); + border-bottom: 1px solid var(--surface); +} + +/* Hub + /clusters/cluster-N/ — smaller than default Academic article H1 */ +h1.clusters-site-heading { + font-size: 1.45rem; + font-weight: 700; + line-height: 1.28; + margin-bottom: 0.35rem; + letter-spacing: -0.015em; +} + +/* /clusters/ section index only — larger hero title (see layouts/clusters/single.html) */ +h1.clusters-site-heading.clusters-hub-page__title { + font-size: 2.35rem; + line-height: 1.18; + margin-bottom: 0.5rem; + letter-spacing: -0.02em; +} + +/* Per-cluster page: FORRT clusters / Cluster N: Name — one flow (wraps on narrow screens) */ +.clusters-heading-row__crumbs { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.35rem 0.5rem; + margin: 0; + padding: 0; + list-style: none; +} + +.clusters-heading-row__parent { + font-size: 1.45rem; + font-weight: 700; + line-height: 1.28; + letter-spacing: -0.015em; + color: #8b0000; + text-decoration: none; + flex-shrink: 0; +} + +.clusters-heading-row__parent:hover { + text-decoration: underline; + color: #5a0000; +} + +.clusters-heading-row__sep { + font-size: 1.45rem; + font-weight: 400; + line-height: 1.28; + color: #adb5bd; + flex-shrink: 0; + user-select: none; +} + +.clusters-heading-row__title.clusters-site-heading { + margin: 0; + padding: 0; + font-size: 1.45rem; + flex: 1 1 14rem; + min-width: 0; +} + +@media (max-width: 768px) { + h1.clusters-site-heading { + font-size: 1.28rem; + } + + h1.clusters-site-heading.clusters-hub-page__title { + font-size: 1.95rem; + line-height: 1.2; + /* Reserve space for the fixed JUST-OS chat widget (top-right, ~80px wide + + 20px offset), which sits at top: 80px at this breakpoint and otherwise + overlaps long titles that wrap to two lines here. */ + padding-right: 104px; + } + + .clusters-heading-row__parent, + .clusters-heading-row__sep, + .clusters-heading-row__title.clusters-site-heading { + font-size: 1.28rem; + line-height: 1.3; + } +} + +@media (max-width: 576px) { + h1.clusters-site-heading { + font-size: 1.12rem; + line-height: 1.32; + } + + h1.clusters-site-heading.clusters-hub-page__title { + font-size: 1.65rem; + line-height: 1.22; + } + + .clusters-heading-row__parent, + .clusters-heading-row__sep, + .clusters-heading-row__title.clusters-site-heading { + font-size: 1.12rem; + line-height: 1.32; + } +} + +.clusters-back-to-top { + position: fixed; + right: 1.25rem; + bottom: 1.25rem; + z-index: 1050; + display: inline-flex; + align-items: center; + justify-content: center; + border: none; + border-radius: 999px; + background: #2f3f66; + color: #fff; + width: 2.75rem; + height: 2.75rem; + padding: 0; + font-size: 1rem; + line-height: 1; + box-shadow: 0 8px 18px rgba(47, 63, 102, 0.3); + cursor: pointer; + opacity: 0; + visibility: hidden; + transform: translateY(8px); + transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease, background 0.2s ease; +} + +.clusters-back-to-top i { + line-height: 1; +} + +.clusters-back-to-top:hover { + background: #233456; +} + +.clusters-back-to-top:focus { + outline: 2px solid #1c7ed6; + outline-offset: 2px; +} + +.clusters-back-to-top.is-visible { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +@media (max-width: 768px) { + .clusters-back-to-top { + right: calc(0.85rem + env(safe-area-inset-right, 0px)); + bottom: calc(0.85rem + env(safe-area-inset-bottom, 0px)); + width: 2.5rem; + height: 2.5rem; + font-size: 0.92rem; + } +} + +/* -------------------------------------------------- */ +/* Disciplines page styles */ +/* -------------------------------------------------- */ +.disciplines-category-badge { + font-size: 0.7rem; + font-weight: 600; + padding: 0.15em 0.5em; + border-radius: 3px; + margin-right: 0.4em; + vertical-align: middle; + white-space: nowrap; +} + +.disciplines-resource-item { + margin-bottom: 0.35rem; + line-height: 1.5; +} + +.disciplines-resource-item a { + word-break: break-word; +} + +/* -------------------------------------------------- */ +/* Featured Resources: cards, filters, popups, lists */ +/* -------------------------------------------------- */ + +/* ---- Tags ---- */ +.tag { + display: inline-block; + font-size: 15px; + font-weight: 600; + padding: 0.15rem 0.55rem; + border-radius: 3px; + line-height: 1.4; + letter-spacing: 0.3px; + text-transform: uppercase; +} +.tag-focus { background: #004055; color: #fff; } +.tag-type { background: #b8336a; color: #fff; } + +/* ---- Featured tab accent ---- */ +.fr-featured-tab.active { + color: #004055 !important; + font-weight: 700; + border-bottom: 2px solid #004055; +} + +/* ---- Sidebar "Recommended" link ---- */ +.fr-nav-featured { + font-weight: 600; + color: #004055 !important; +} + +/* ---- Filter bar ---- */ +.fr-filter-bar { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + align-items: flex-start; + margin-bottom: 0.75rem; + padding: 0.65rem 0.75rem; + background: rgba(255,255,255,0.5); + border: 1px solid rgba(0,0,0,0.06); + border-radius: 6px; +} +.fr-filter-group { + display: flex; + align-items: center; + gap: 0.35rem; + flex-wrap: wrap; +} +.fr-filter-label { + font-size: 15px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--soft); + flex-shrink: 0; +} +.fr-filter-tags { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; +} +.fr-tag-btn { + font-family: inherit; + font-size: 15px; + font-weight: 600; + padding: 0.18rem 0.5rem; + border-radius: 3px; + border: 1px solid rgba(0,0,0,0.1); + background: transparent; + color: var(--soft); + cursor: pointer; + transition: background 0.15s, color 0.15s, border-color 0.15s; + text-transform: capitalize; +} +.fr-tag-btn:hover { + background: rgba(0,64,85,0.06); + color: #004055; + border-color: rgba(0,64,85,0.2); +} +.fr-tag-btn.active { + background: #004055; + color: #fff; + border-color: #004055; +} +.fr-specificity-toggle { + display: flex; + align-items: center; + gap: 0.3rem; + font-size: 15px; + color: var(--soft); + cursor: pointer; +} +.fr-specificity-toggle input { + margin: 0; +} +.fr-clear-btn { + margin-left: auto; + align-self: center; + font-family: inherit; + font-size: 15px; + font-weight: 600; + padding: 0.18rem 0.5rem; + border-radius: 3px; + border: 1px solid rgba(0,64,85,0.25); + background: transparent; + color: #004055; + cursor: pointer; + white-space: nowrap; + transition: background 0.15s, color 0.15s, border-color 0.15s; +} +.fr-clear-btn::before { + content: "\00d7"; + margin-right: 0.3rem; + font-weight: 700; +} +.fr-clear-btn:hover { + background: #004055; + color: #fff; + border-color: #004055; +} +.fr-clear-btn[hidden] { + display: none; +} + +/* ---- Search input ---- */ +.fr-search-wrap { + margin-bottom: 0.65rem; +} +.fr-search-input { + width: 100%; + font-family: inherit; + font-size: 0.78rem; + padding: 0.4rem 0.65rem; + border: 1px solid rgba(0,0,0,0.1); + border-radius: 5px; + background: #fff; + color: var(--ink); + outline: none; + transition: border-color 0.15s; +} +.fr-search-input:focus { + border-color: rgba(0,64,85,0.4); +} +.fr-search-input::placeholder { + color: #aaa; +} + +/* ---- Side-by-side card (with testimonial) ---- */ +.fr-card { + display: grid; + grid-template-columns: 1fr 340px; + background: #fff; + border: 1px solid rgba(0,0,0,0.08); + border-radius: 6px; + overflow: hidden; + margin-bottom: 0.65rem; + transition: box-shadow 0.2s; +} +.fr-card:hover { + box-shadow: 0 3px 12px rgba(0,0,0,0.07); +} +.fr-resource { + padding: 0.85rem 1rem; +} +.fr-tags { + display: flex; + gap: 0.35rem; + margin-bottom: 0.4rem; +} +.fr-title { + font-size: 0.78rem; + font-weight: 600; + line-height: 1.35; + margin-bottom: 0.3rem; + color: #222; +} +.fr-summary { + font-size: 0.7rem; + color: var(--soft); + line-height: 1.55; + margin-bottom: 0.5rem; +} +.fr-actions { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 0.15rem; +} + +/* -- Testimonial carousel panel -- */ +.fr-testimonial { + background: rgba(0,64,85,0.04); + border-left: 3px solid #004055; + padding: 0.75rem 0.85rem; + display: flex; + flex-direction: column; + justify-content: center; + position: relative; + overflow: hidden; +} +.fr-rec-label { + font-size: 15px; + color: var(--soft); + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 600; + margin-bottom: 0.35rem; +} +.fr-carousel { + position: relative; + overflow: hidden; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; +} +.fr-carousel-track { + display: flex; + transition: transform 0.35s cubic-bezier(0.25, 0.1, 0.25, 1); +} +.fr-carousel-slide { + flex: 0 0 100%; + min-width: 0; +} +.fr-rec-header { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.3rem; +} +.fr-rec-name { + font-size: 0.7rem; + font-weight: 600; + color: #222; + line-height: 1.25; +} +.fr-rec-bio { + font-size: 15px; + color: var(--soft); + line-height: 1.25; + margin-top: 0.05rem; +} +.fr-rec-text { + font-size: 0.7rem; + color: var(--ink); + line-height: 1.5; + font-style: italic; +} +.fr-carousel-nav { + display: flex; + align-items: center; + gap: 0.3rem; + margin-top: 0.45rem; +} +.fr-carousel-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: rgba(0,0,0,0.12); + border: none; + padding: 0; + cursor: pointer; + transition: background 0.2s, transform 0.2s; +} +.fr-carousel-dot.active { + background: #004055; + transform: scale(1.35); +} +.fr-carousel-arrows { + margin-left: auto; + display: flex; + gap: 0.2rem; +} +.fr-carousel-arrow { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 50%; + border: 1px solid rgba(0,0,0,0.12); + background: rgba(255,255,255,0.7); + cursor: pointer; + color: var(--soft); + font-size: 0.55rem; + font-family: inherit; + transition: background 0.15s, border-color 0.15s; + line-height: 1; +} +.fr-carousel-arrow:hover { + background: #fff; + border-color: rgba(0,0,0,0.25); + color: #222; +} +.fr-carousel-nav.single { display: none; } + +/* Avatars for recommenders */ +.avatar-photo { + width: 38px; + height: 38px; + object-fit: cover; + border-radius: 50%; + flex-shrink: 0; + border: 2px solid rgba(255,255,255,0.6); + box-shadow: 0 1px 4px rgba(0,0,0,0.1); +} +.avatar { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 50%; + font-weight: 700; + color: #fff; + flex-shrink: 0; + line-height: 1; +} +.avatar-sm { width: 28px; height: 28px; font-size: 0.55rem; } +.avatar-1 { background: #004055; } + +/* ---- Compact card ---- */ +.fc-card { + display: flex; + align-items: flex-start; + gap: 0.75rem; + background: #fff; + border: 1px solid rgba(0,0,0,0.08); + border-radius: 6px; + padding: 0.65rem 0.85rem; + margin-bottom: 0.45rem; + transition: box-shadow 0.2s; +} +.fc-card:hover { + box-shadow: 0 2px 10px rgba(0,0,0,0.06); +} +.fc-body { + flex: 1; + min-width: 0; +} +.fc-top-row { + display: flex; + align-items: flex-start; + gap: 0.5rem; + margin-bottom: 0.2rem; +} +.fc-tags { + display: flex; + gap: 0.25rem; + flex-shrink: 0; + margin-top: 0.05rem; +} +.fc-tags .tag { + font-size: 0.55rem; + padding: 0.1rem 0.4rem; +} +.fc-title { + font-size: 0.78rem; + font-weight: 600; + line-height: 1.35; + color: #222; + flex: 1; + min-width: 0; +} +.fc-summary { + font-size: 0.7rem; + color: var(--soft); + line-height: 1.5; + margin-bottom: 0.35rem; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} +.fc-actions { + display: flex; + align-items: center; + gap: 0.5rem; +} + +/* ---- Reference trigger button ---- */ +.fr-ref-trigger { + display: inline-flex; + align-items: center; + gap: 0.3rem; + font-family: inherit; + font-size: 15px; + color: #8e0000; + background: none; + border: none; + padding: 0; + cursor: pointer; + border-bottom: 1px dashed rgba(142,0,0,0.3); + transition: border-color 0.15s, color 0.15s; +} +.fr-ref-trigger:hover { + color: #b8336a; + border-bottom-color: rgba(184,51,106,0.4); +} +.fr-ref-trigger[aria-busy="true"] { + cursor: progress; +} +.fr-ref-trigger svg { + width: 11px; + height: 11px; + flex-shrink: 0; +} + +/* ---- Vote button ---- */ +.fc-vote-btn { + display: inline-flex; + align-items: center; + gap: 0.25rem; + font-family: inherit; + font-size: 15px; + color: var(--soft); + background: none; + border: 1px solid rgba(0,0,0,0.1); + border-radius: 4px; + padding: 0.18rem 0.45rem; + cursor: pointer; + transition: color 0.15s, border-color 0.15s, background 0.15s; + line-height: 1.3; +} +.fc-vote-btn:hover { + color: #004055; + border-color: rgba(0,64,85,0.25); + background: rgba(0,64,85,0.04); +} +.fc-vote-btn.voted { + color: #004055; + border-color: rgba(0,64,85,0.3); + background: rgba(0,64,85,0.08); + cursor: default; + pointer-events: none; +} +.fc-vote-btn svg { + width: 13px; + height: 13px; +} +.fc-vote-count { + font-weight: 600; + font-size: 15px; + min-width: 0.8em; + text-align: center; +} + +/* ---- Bookmark / Save button ---- */ +.fr-bookmark-btn { + display: inline-flex; + align-items: center; + gap: 0.25rem; + font-family: inherit; + font-size: 15px; + color: var(--soft); + background: none; + border: 1px solid rgba(0,0,0,0.1); + border-radius: 4px; + padding: 0.2rem 0.5rem; + cursor: pointer; + transition: color 0.15s, border-color 0.15s, background 0.15s; +} +.fr-bookmark-btn:hover { + color: #004055; + border-color: rgba(0,64,85,0.25); + background: rgba(0,64,85,0.04); +} +.fr-bookmark-btn.saved { + color: #004055; + border-color: rgba(0,64,85,0.3); + background: rgba(0,64,85,0.08); +} +.fr-bookmark-btn svg { + width: 12px; + height: 12px; +} + +/* ---- No results message ---- */ +.fr-no-results { + text-align: center; + padding: 2rem 1rem; + font-size: 0.8rem; + color: var(--soft); + font-style: italic; +} + +/* ---- Reference popup ---- */ +.ref-popup-backdrop { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.2); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + z-index: 2000; + align-items: center; + justify-content: center; + padding: 1.5rem; +} +.ref-popup-backdrop.open { display: flex; } + +.ref-popup { + background: #fff; + border-radius: 8px; + box-shadow: 0 12px 44px rgba(0,0,0,0.18); + max-width: 36rem; + width: 100%; + max-height: 85vh; + overflow-y: auto; + position: relative; +} +.ref-popup-close { + position: absolute; + top: 0.6rem; + right: 0.75rem; + background: none; + border: none; + font-size: 1.1rem; + color: var(--soft); + cursor: pointer; + line-height: 1; + font-family: inherit; + z-index: 1; +} +.ref-popup-close:hover { color: var(--ink); } + +.ref-popup-body { + padding: 1.25rem 1.35rem 1rem; +} + +.ref-popup-tags { + display: flex; + gap: 0.35rem; + margin-bottom: 0.4rem; +} +.ref-popup-title { + font-size: 0.88rem; + font-weight: 600; + color: #222; + line-height: 1.35; + margin-bottom: 0.75rem; + padding-bottom: 0.65rem; + border-bottom: 1px solid rgba(0,0,0,0.06); +} + +.ref-section { + margin-bottom: 0.85rem; +} +.ref-section-label { + font-size: 15px; + text-transform: uppercase; + letter-spacing: 0.6px; + font-weight: 700; + color: var(--soft); + margin-bottom: 0.3rem; +} +.ref-apa { + font-size: 0.78rem; + line-height: 1.6; + color: var(--ink); + padding: 0.6rem 0.75rem; + background: #f8f8f5; + border: 1px solid rgba(0,0,0,0.06); + border-radius: 4px; + position: relative; +} +.ref-copy-btn { + display: inline-flex; + align-items: center; + gap: 0.3rem; + font-family: inherit; + font-size: 15px; + font-weight: 600; + color: #004055; + background: rgba(0,64,85,0.08); + border: 1px solid rgba(0,64,85,0.15); + border-radius: 4px; + padding: 0.25rem 0.55rem; + cursor: pointer; + transition: background 0.15s, color 0.15s; + text-transform: uppercase; + letter-spacing: 0.3px; + margin-top: 0.35rem; +} +.ref-copy-btn:hover { + background: rgba(0,64,85,0.14); +} +.ref-copy-btn.copied { + background: #d4e4d9; + color: #2a6a3a; + border-color: rgba(42,106,58,0.2); +} +.ref-copy-btn svg { + width: 11px; + height: 11px; +} +.ref-copy-row { + display: flex; + gap: 0.4rem; + margin-top: 0.35rem; +} +.ref-abstract { + font-size: 0.75rem; + line-height: 1.65; + color: var(--ink); + font-style: italic; +} +.ref-doi-row { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.4rem; + margin-top: 0.2rem; + font-size: 0.75rem; +} +.ref-doi-link { + color: #1c7ed6; + word-break: break-all; + text-decoration: none; +} +.ref-doi-link:hover { text-decoration: underline; } + +.ref-oa-badge { + display: inline-flex; + align-items: center; + gap: 0.2rem; + font-size: 15px; + font-weight: 600; + padding: 0.1rem 0.45rem; + border-radius: 3px; + text-decoration: none; + transition: opacity 0.15s; +} +.ref-oa-badge:hover { opacity: 0.8; } +.ref-oa-badge svg { + width: 12px; + height: 12px; +} +.ref-oa-badge.open { + background: rgba(42,106,58,0.1); + color: #2a6a3a; +} +.ref-oa-badge.closed { + background: rgba(0,0,0,0.06); + color: var(--soft); + cursor: default; +} +.ref-oa-link { + display: inline-flex; + align-items: center; + gap: 0.2rem; + font-size: 15px; + font-weight: 600; + color: #2a6a3a; + text-decoration: none; + border-bottom: 1px solid rgba(42,106,58,0.3); + transition: border-color 0.15s; +} +.ref-oa-link:hover { border-bottom-color: #2a6a3a; } +.ref-oa-link svg { + width: 11px; + height: 11px; +} +.ref-divider { + height: 1px; + background: rgba(0,0,0,0.06); + margin: 0.65rem 0; +} + +/* ---- Reading list: pill ---- */ +.rl-pill { + position: fixed; + top: 1rem; + right: 1.25rem; + z-index: 3000; + display: flex; + align-items: center; + gap: 0.4rem; + background: #004055; + color: #fff; + padding: 0.35rem 0.7rem 0.35rem 0.55rem; + border-radius: 20px; + box-shadow: 0 3px 14px rgba(0,0,0,0.18); + cursor: pointer; + font-size: 0.7rem; + font-weight: 600; + opacity: 0; + transform: translateY(-8px); + pointer-events: none; + transition: opacity 0.3s, transform 0.3s; +} +.rl-pill.visible { + opacity: 1; + transform: translateY(0); + pointer-events: auto; +} +.rl-pill:hover { background: #00556e; } +.rl-pill svg { + width: 14px; + height: 14px; + flex-shrink: 0; +} +.rl-pill-count { + background: #b8336a; + color: #fff; + font-size: 0.58rem; + font-weight: 700; + min-width: 1.3em; + height: 1.3em; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +/* ---- First-save tooltip ---- */ +.rl-tooltip { + position: fixed; + top: 3.2rem; + right: 1.25rem; + z-index: 3001; + background: #fff; + border: 1px solid rgba(0,0,0,0.1); + border-radius: 8px; + box-shadow: 0 6px 24px rgba(0,0,0,0.12); + padding: 0.75rem 1rem; + max-width: 16rem; + opacity: 0; + transform: translateY(-6px); + pointer-events: none; + transition: opacity 0.3s, transform 0.3s; +} +.rl-tooltip.visible { + opacity: 1; + transform: translateY(0); + pointer-events: auto; +} +.rl-tooltip::before { + content: ''; + position: absolute; + top: -6px; + right: 1.5rem; + width: 10px; + height: 10px; + background: #fff; + border-left: 1px solid rgba(0,0,0,0.1); + border-top: 1px solid rgba(0,0,0,0.1); + transform: rotate(45deg); +} +.rl-tooltip-text { + font-size: 15px; + color: var(--ink); + line-height: 1.5; + margin-bottom: 0.45rem; +} +.rl-tooltip-dismiss { + font-family: inherit; + font-size: 15px; + font-weight: 600; + color: #004055; + background: rgba(0,64,85,0.08); + border: 1px solid rgba(0,64,85,0.15); + border-radius: 4px; + padding: 0.2rem 0.55rem; + cursor: pointer; + transition: background 0.15s; +} +.rl-tooltip-dismiss:hover { background: rgba(0,64,85,0.14); } + +/* ---- Reading list: panel / drawer ---- */ +.rl-panel-backdrop { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.15); + backdrop-filter: blur(3px); + -webkit-backdrop-filter: blur(3px); + z-index: 3500; +} +.rl-panel-backdrop.open { display: block; } +.rl-panel { + position: fixed; + top: 0; + right: 0; + bottom: 0; + width: 22rem; + max-width: 90vw; + background: #fff; + box-shadow: -6px 0 30px rgba(0,0,0,0.12); + display: flex; + flex-direction: column; + overflow: hidden; + transform: translateX(100%); + transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1); + z-index: 3501; +} +.rl-panel.open { transform: translateX(0); } +.rl-panel-header { + padding: 0.85rem 1.1rem; + border-bottom: 1px solid rgba(0,0,0,0.06); + display: flex; + align-items: center; + justify-content: space-between; + flex-shrink: 0; +} +.rl-panel-title { + font-size: 0.85rem; + font-weight: 700; + color: #004055; +} +.rl-panel-note { + font-size: 15px; + color: var(--soft); + line-height: 1.45; + padding: 0.5rem 1.1rem; + border-bottom: 1px solid rgba(0,0,0,0.04); + flex-shrink: 0; +} +.rl-panel-close { + background: none; + border: none; + font-size: 1.1rem; + color: var(--soft); + cursor: pointer; + font-family: inherit; + line-height: 1; +} +.rl-panel-close:hover { color: var(--ink); } +.rl-panel-body { + flex: 1; + overflow-y: auto; + padding: 0.5rem 0; +} +.rl-panel-empty { + padding: 2rem 1.25rem; + text-align: center; + font-size: 0.78rem; + color: var(--soft); +} +.rl-panel-item { + display: flex; + flex-direction: column; + padding: 0.55rem 1.25rem; + border-bottom: 1px solid rgba(0,0,0,0.04); + font-size: 0.78rem; +} +.rl-panel-item-header { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; +} +.rl-panel-item-toggle { + background: none; + border: none; + color: #bbb; + cursor: pointer; + padding: 0.2rem; + flex-shrink: 0; + transition: color 0.15s, transform 0.2s; +} +.rl-panel-item-toggle svg { + width: 12px; + height: 12px; + display: block; +} +.rl-panel-item-toggle:hover { color: var(--soft); } +.rl-panel-item.expanded .rl-panel-item-toggle { + transform: rotate(180deg); + color: #004055; +} +.rl-panel-item-info { + flex: 1; + min-width: 0; +} +.rl-panel-item-title { + font-weight: 600; + color: #222; + line-height: 1.35; +} +.rl-panel-item-ref { + font-size: 15px; + color: var(--soft); + margin-top: 0.1rem; +} +.rl-panel-item-remove { + background: none; + border: none; + color: var(--line); + cursor: pointer; + font-size: 0.9rem; + font-family: inherit; + line-height: 1; + padding: 0.2rem; + flex-shrink: 0; + transition: color 0.15s; +} +.rl-panel-item-remove:hover { color: #b8336a; } +/* Collapsible details */ +.rl-panel-item-details { + display: none; + padding: 0.35rem 0 0.15rem 1.6rem; + margin-top: 0.25rem; + border-left: 2px solid rgba(0,64,85,0.12); + margin-left: 0.45rem; + font-size: 15px; + color: var(--soft); + line-height: 1.45; +} +.rl-panel-item.expanded .rl-panel-item-details { + display: block; +} +/* APA reference + copy button row */ +.rl-detail-ref-row { + display: flex; + align-items: flex-start; + gap: 0.3rem; +} +.rl-detail-apa { + flex: 1; + font-size: 15px; + color: var(--ink); + line-height: 1.4; + padding-left: 1.2em; + text-indent: -1.2em; +} +.rl-detail-copy { + flex-shrink: 0; + background: none; + border: 1px solid var(--line); + border-radius: 3px; + padding: 0.15rem 0.25rem; + cursor: pointer; + color: var(--soft); + transition: color 0.15s, border-color 0.15s, background 0.15s; + margin-top: 0.1rem; +} +.rl-detail-copy svg { width: 11px; height: 11px; display: block; } +.rl-detail-copy:hover { color: #004055; border-color: #004055; } +.rl-detail-copy.copied { color: #2a6a3a; border-color: #2a6a3a; background: #e6f4ea; } +/* Abstract with inline show more/less */ +.rl-detail-abstract-wrap { + margin-top: 0.3rem; + font-size: 15px; + color: var(--soft); + line-height: 1.4; + position: relative; +} +.rl-detail-abstract { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 4; + overflow: hidden; +} +.rl-detail-abstract-wrap.abstract-expanded .rl-detail-abstract { + display: block; + -webkit-line-clamp: unset; + overflow: visible; +} +.rl-detail-show-more { + background: none; + border: none; + padding: 0; + font-size: 15px; + color: #006080; + cursor: pointer; + font-family: inherit; + margin-top: 0.1rem; +} +.rl-detail-show-more:hover { text-decoration: underline; } +/* DOI + OA icon + Free PDF — compact meta row */ +.rl-detail-meta { + margin-top: 0.3rem; + display: flex; + align-items: center; + gap: 0.35rem; + font-size: 15px; +} +.rl-detail-doi-link { + color: #006080 !important; + text-decoration: none; + word-break: break-all; +} +.rl-detail-doi-link:hover { text-decoration: underline; } +.rl-oa-icon { + width: 11px; + height: 17px; + flex-shrink: 0; +} +.rl-detail-pdf-link { + color: #2a6a3a !important; + font-weight: 600; + text-decoration: none; +} +.rl-detail-pdf-link:hover { text-decoration: underline; } +.rl-panel-footer { + padding: 0.65rem 1.25rem; + border-top: 1px solid rgba(0,0,0,0.06); + display: flex; + justify-content: flex-end; + gap: 0.4rem; + flex-shrink: 0; + flex-wrap: wrap; +} +.rl-export-btn { + font-family: inherit; + font-size: 15px; + font-weight: 600; + color: #fff; + background: #004055; + border: none; + border-radius: 4px; + padding: 0.35rem 0.75rem; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 0.3rem; + transition: background 0.15s; +} +.rl-export-btn:hover { background: #00556e; } +.rl-export-btn svg { + width: 12px; + height: 12px; +} +.rl-pdf-btn { + font-family: inherit; + font-size: 15px; + font-weight: 600; + color: #004055; + background: transparent; + border: 1.5px solid #004055; + border-radius: 4px; + padding: 0.35rem 0.75rem; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 0.3rem; + transition: background 0.15s, color 0.15s; +} +.rl-pdf-btn:hover { + background: #004055; + color: #fff; +} +.rl-pdf-btn:disabled { + opacity: 0.6; + cursor: wait; +} +.rl-pdf-btn svg { + width: 12px; + height: 12px; +} +@keyframes rl-spin { + to { transform: rotate(360deg); } +} +.rl-spin { + animation: rl-spin 1s linear infinite; +} + +/* ---- Featured resources responsive ---- */ +@media (max-width: 768px) { + .fr-card { grid-template-columns: 1fr; } + .fr-testimonial { border-left: none; border-top: 1px solid rgba(0,64,85,0.12); } + .fr-filter-bar { + flex-direction: column; + gap: 0.5rem; + } + .fc-top-row { + flex-direction: column; + gap: 0.25rem; + } +} + +.clusters-display-section,.clusters-intro-section{background:transparent!important;padding:0!important} +.cluster-section{border:0!important;border-radius:var(--r-large)!important;box-shadow:none!important} +.acc-count,.cluster-stats,.cluster-description{color:var(--ink)!important;opacity:1!important} +.clusters-head h1.clusters-site-heading{font-size:clamp(36px,4.2vw,58px);line-height:1.06} +.clusters-intro a:not(.btn, .clusters-map a),.disciplines-attribution a,.sc-description a,.disciplines-leads a{text-decoration:underline;text-underline-offset:3px} + +.sidebar-header:has(.sidebar-close){display:flex;align-items:center;justify-content:space-between;gap:8px} +.sidebar-close{padding:8px 14px;background:#fff;border:1.5px solid var(--ink);border-radius:999px;color:var(--ink);font:600 15px/1.2 var(--text);letter-spacing:0;text-transform:none;cursor:pointer} +/* On phones the pinned filters took half the screen, so they scroll away with the page. */ +@media (max-width:760px){ + .clusters-controls{position:static} + .clusters-layout{--clusters-controls-height:0px!important} +} + +/* Back to top rests in the margin beside the fields or clusters, which .cluster-full-width widens by 15 px + (35 px on phones, 47 px or more on laptops, 80 px from 1440), so it never covers text or a control. + clusters-page.js shows it only alongside that section. It sits above the chat launcher, clear of the cookie banner, + and its focus ring stays inside it. */ +.clusters-back-to-top.clusters-back-to-top{right:6px;bottom:calc(84px + var(--banner,0px));width:36px;height:36px;font-size:14px;background:var(--ink);box-shadow:none} +.clusters-back-to-top.clusters-back-to-top:hover{background:var(--link)} +.clusters-back-to-top.clusters-back-to-top:focus{outline:none} +.clusters-back-to-top.clusters-back-to-top:focus-visible{outline:2px solid #fff;outline-offset:-5px;border-radius:999px} +@media (min-width:1440px){ + .clusters-back-to-top.clusters-back-to-top{right:20px;bottom:calc(92px + var(--banner,0px));width:44px;height:44px;font-size:16px} +} +@media (max-width:760px){ + .clusters-back-to-top.clusters-back-to-top{right:4px;bottom:calc(80px + var(--banner,0px));width:28px;height:28px;font-size:13px} +} +body.clusters-mobile-nav-open .clusters-back-to-top{display:none} +.sc-description,.fc-summary,.fc-title{font-size:16px} + +/* Explanations inside the panels keep to the reading measure of .article-style; the cards and + resource lists around them stay wide. */ +.cluster-description,.sc-description{max-width:580px} +/* Labels, tags and counts at the site's smallest UI size, as the design check requires. */ +.acc-count,.cluster-stats,.acc-toggle-all,.fc-tags .tag,.disciplines-category-badge{font-size:15px} +.fc-tags{flex-wrap:wrap} + +/* The explorer in the site's own components: pill filters as in the resources directory, no card shadow + or full-bleed rules, legible disclosure arrows, counts and link buttons, and "How to cite" as a link. */ +.clusters-controls{box-shadow:none;border:0;padding:0;background:var(--page)} +.fr-filter-bar{background:var(--surface);border:0;border-radius:var(--r-large);padding:16px 18px;gap:12px 20px;margin-bottom:12px} +.fr-filter-label{text-transform:none;letter-spacing:0;color:var(--ink);font-weight:600} +.fr-filter-tags{gap:7px} +.fr-tag-btn{background:#fff;border:1.5px solid var(--line);border-radius:999px;padding:6px 12px;font:400 15px/1.3 var(--text);color:var(--ink)} +.fr-tag-btn:hover{background:#fff;border-color:var(--ink);color:var(--ink)} +.fr-tag-btn.active,.fr-tag-btn.active:hover{background:var(--ink);border-color:var(--ink);color:#fff} +.fr-search-input{border:1.5px solid var(--line);border-radius:999px;padding:8px 16px;background:#fff;font:400 16px/1.3 var(--text);color:var(--ink)} +.fr-specificity-toggle{color:var(--ink)} +.fr-specificity-toggle input{width:18px;height:18px;accent-color:var(--ink)} +.clusters-mobile-toggle.clusters-mobile-toggle{border:1.5px solid var(--ink);border-radius:999px;background:#fff;color:var(--ink);font:600 16px/1.3 var(--text);padding:10px 18px} +.clusters-display-section{border:0} +.acc-header{font-size:16px;color:var(--ink);padding:10px 2px;gap:10px} +.acc-chevron{font-size:13px;color:var(--ink);width:14px;text-align:center} +.acc-chevron.acc-open{color:var(--ink)} +.acc-section+.acc-section{border-top-color:var(--line)} +.acc-count{color:var(--soft)!important;font-variant-numeric:tabular-nums} +.acc-copy-link{opacity:1;color:var(--soft);width:32px;height:32px;border-radius:999px} +.acc-copy-link svg{width:15px;height:15px} +.acc-copy-link:hover{color:var(--link);background:rgba(0,90,135,.08)} +.acc-toggle-all{background:#fff;border:1.5px solid var(--line);border-radius:999px;padding:6px 14px;color:var(--ink);font-weight:600} +.acc-toggle-all:hover{border-color:var(--ink);color:var(--ink)} +/* Expand all sits on its own line above the rows; floated, it took width from the first row. */ +.acc-toggle-all{float:none;display:block;margin:4px 0 10px} +.disciplines-citation-summary{color:var(--link);font-size:16px;font-weight:600;text-align:start;padding:8px 0} +.disciplines-citation-summary:hover,.disciplines-citation-summary:focus-visible{color:var(--ink)} +/* Labels in sentence case, as elsewhere on the site, not spaced capitals. */ +.sidebar-header,.fr-rec-label,.ref-section-label,.ref-copy-btn,.fc-tags .tag{text-transform:none;letter-spacing:0} +.sidebar-header{color:var(--ink);font-size:16px} +.acc-chevron{font-size:18px;line-height:1} +.cluster-nav-toggle{font-size:16px!important;width:16px;display:inline-block;text-align:center} +.clusters-sidebar-inner .sidebar-header{padding-top:14px} +/* The page title at the weight and spacing of every other h1. */ +.clusters-head h1.clusters-site-heading{font-weight:var(--display-weight);letter-spacing:-.01em;text-wrap:balance} +/* The closed reading-list panel cast its shadow back onto the page edge. */ +.rl-panel:not(.open){box-shadow:none} +/* Introductions keep to a reading measure; the explorer and diagram keep the full width. */ +.clusters-intro .intro-paragraph{max-width:70ch} +/* On a phone the sponsor is a small credit line, so the search follows the introduction closely. */ +@media (max-width:768px){ + .disciplines-partner{display:flex;align-items:center;gap:12px;align-self:flex-start;max-width:none} + .disciplines-partner img{width:56px;height:56px} + .disciplines-partner__caption{margin:0} +} +/* The disciplines search as the header's: a pill field with its button inside it, so the two never part. */ +.clusters-inline-search__row{align-items:center;background:var(--page)} +.clusters-inline-search__input.clusters-inline-search__input{flex:1 1 0;max-width:28rem;border:1.5px solid var(--line);border-radius:999px;padding:9px 52px 9px 16px;font:400 16px/1.3 var(--text);color:var(--ink);background:#fff} +.clusters-inline-search__submit.clusters-inline-search__submit{margin-inline-start:-50px;width:40px;height:40px;min-width:0;padding:0;border:0;border-radius:999px;background:transparent;color:var(--ink)} +.clusters-inline-search__submit.clusters-inline-search__submit:hover{background:var(--surface);color:var(--ink)} +.clusters-inline-search__option-label{font-size:15px;color:var(--ink);margin-inline-start:8px} +@media(max-width:760px){.clusters-inline-search__option-label{flex-basis:100%;margin:4px 0 0}} +.clusters-map__enlarge,.clusters-map__close{border:1.5px solid var(--ink);border-radius:999px;background:#fff;padding:8px 16px;font:600 15px/1.3 var(--text)} +@media(max-width:760px){.clusters-map__hub{width:min(180px,50%)}} +/* On a phone a row's count goes under its title, so long titles keep the width; the whole row opens it. */ +@media(max-width:760px){ + .acc-header{flex-wrap:wrap;row-gap:2px} + .acc-header .acc-label{flex:1 1 0} + .acc-header .acc-copy-link{order:2} + .acc-header .acc-count{order:3;flex-basis:100%;margin-left:24px} +} +/* Below 1440 px the chat button floats in the bottom corner (launcher.css); back to top sits above it. */ +@media (max-width:1439px){ + .clusters-back-to-top.clusters-back-to-top{right:20px;bottom:calc(80px + var(--banner,0px) + env(safe-area-inset-bottom,0px));width:44px;height:44px;font-size:16px} +} +body.clusters-mobile-nav-open #just-os-widget{display:none} +.disciplines-areas{display:flex;flex-wrap:wrap;gap:6px;margin:-4px 0 14px;padding:0;list-style:none} +.disciplines-areas li{margin:0} +.disciplines-areas .badge{padding:4px 10px;border-radius:999px;font:600 15px/1.3 var(--text)} diff --git a/assets/css/doors/compat.css b/assets/css/doors/compat.css new file mode 100644 index 00000000000..2b7d6971296 --- /dev/null +++ b/assets/css/doors/compat.css @@ -0,0 +1,54 @@ +/* The Bootstrap classes still written in Markdown, shortcodes and generated pages, drawn in Doors + colours. Only classes the build actually uses; add here rather than bringing the theme back. */ +.btn{display:inline-block;padding:8px 18px;border:1.5px solid var(--link);border-radius:999px;background:transparent;color:var(--link);font:600 16px/1.35 var(--text);text-align:center;text-decoration:none;cursor:pointer} +.btn:hover,.btn:focus-visible{background:var(--ink);border-color:var(--ink);color:#fff;text-decoration:none} +.btn-primary{background:var(--link);border-color:var(--link);color:#fff} +.btn-outline-primary,.btn-outline{color:var(--link);border-color:var(--link);background:transparent} +.btn-outline-success,.btn-outline-info,.btn-outline-danger,.btn-outline-secondary{color:var(--ink);border-color:var(--soft);background:transparent} +.btn-sm{padding:4px 12px;font-size:15px} +.btn-lg{padding:12px 24px;font-size:18px} +.btn-block{display:block;width:100%} +.btn-group{display:inline-flex;flex-wrap:wrap;gap:6px} +.badge{display:inline-block;padding:3px 10px;border-radius:var(--r-small);background:var(--surface);color:var(--ink);font:600 15px/1.4 var(--text)} +.badge-pill{border-radius:999px} +.alert{margin:0 0 1em;padding:14px 18px;background:var(--res-bg);border-radius:var(--r-large);color:var(--ink);font-size:16px} +.alert-example{background:var(--edu-bg)} +.alert-warning{background:var(--com-bg)} +.card{background:var(--surface);border-radius:var(--r-large)} +.card-body{padding:18px} +.collapse:not(.show){display:none} +.form-control{display:block;width:100%;padding:8px 12px;background:#fff;border:1.5px solid var(--soft);border-radius:var(--r-small);color:var(--ink);font:400 16px/1.4 var(--text)} +.form-group{margin-bottom:1em} +.container,.container-fluid{width:100%;margin-inline:auto;padding-inline:12px} +.row{display:flex;flex-wrap:wrap;margin-inline:-12px} +.row>*{width:100%;min-width:0;padding-inline:12px} +.col{flex:1 0 0} +.col-auto{flex:0 0 auto;width:auto} +@media(min-width:576px){.col-sm-4{width:33.333%}.col-sm-8{width:66.667%}.col-sm-auto{flex:0 0 auto;width:auto}.d-sm-flex{display:flex!important}.text-sm-right{text-align:end}} +@media(min-width:768px){.col-md-3{width:25%}.col-md-4{width:33.333%}.col-md-8{width:66.667%}.col-md-9{width:75%}.col-md-12{width:100%}.d-md-none{display:none!important}} +@media(min-width:992px){.col-lg-4{width:33.333%}.col-lg-8{width:66.667%}.col-lg-12{width:100%}.d-lg-none{display:none!important}} +@media(min-width:1200px){.col-xl-3{width:25%}.col-xl-9{width:75%}} +.media{display:flex;align-items:flex-start;gap:12px} +.media-body{flex:1;min-width:0} +.d-none{display:none!important} +.d-block{display:block!important} +.d-flex{display:flex!important} +.d-inline{display:inline!important} +.flex-column{flex-direction:column} +.flex-wrap{flex-wrap:wrap} +.justify-content-center{justify-content:center} +.justify-content-around{justify-content:space-around} +.align-items-center{align-items:center} +.align-self-center{align-self:center} +.text-center{text-align:center} +.text-danger{color:var(--urgent)} +.text-white{color:#fff} +.mx-auto{margin-inline:auto} +.w-50{width:50%} +.rounded-circle{border-radius:50%} +.rounded{border-radius:var(--r-small)} +.bg-light{background:var(--surface)} +.border-bottom{border-bottom:1px solid var(--line)} +.mt-0{margin-top:0}.mb-0{margin-bottom:0}.m-1{margin:4px}.mb-1{margin-bottom:4px}.mb-2{margin-bottom:8px}.mb-3{margin-bottom:16px}.mb-4{margin-bottom:24px} +.mt-3{margin-top:16px}.mt-4{margin-top:24px}.my-1{margin-block:4px}.my-4{margin-block:24px}.mr-1{margin-inline-end:4px}.mr-3{margin-inline-end:16px} +.p-0{padding:0}.p-3{padding:16px}.pb-1{padding-bottom:4px}.pb-2{padding-bottom:8px}.pb-3{padding-bottom:16px}.pt-3{padding-top:16px}.pr-2{padding-inline-end:8px}.py-2{padding-block:8px}.py-3{padding-block:16px}.py-4{padding-block:24px} diff --git a/assets/css/doors/fonts.css b/assets/css/doors/fonts.css new file mode 100644 index 00000000000..11b5fad5ef3 --- /dev/null +++ b/assets/css/doors/fonts.css @@ -0,0 +1,6 @@ +/* Self-hosted, subsetted from Google's OFL sources by scripts/rebrand/build_fonts.py. + Paths are relative to the built stylesheet in /css/. */ +@font-face{font-family:'Newsreader';src:url(../fonts/doors/newsreader-var.woff2) format('woff2');font-weight:500;font-style:normal;font-display:swap} +@font-face{font-family:'Source Sans 3';src:url(../fonts/doors/source-sans-3-var.woff2) format('woff2');font-weight:400 700;font-style:normal;font-display:swap} +/* Only reference lists ask for this, so other pages never download it. */ +@font-face{font-family:'Source Sans 3';src:url(../fonts/doors/source-sans-3-italic.woff2) format('woff2');font-weight:400;font-style:italic;font-display:swap} diff --git a/assets/css/doors/glossary.css b/assets/css/doors/glossary.css new file mode 100644 index 00000000000..71418c33146 --- /dev/null +++ b/assets/css/doors/glossary.css @@ -0,0 +1,141 @@ +/* Explicit glossary links keep the surrounding text colour. */ +a.gloss-term { + color: inherit; + text-decoration: underline dotted 2px var(--edu); + text-underline-offset: 3px; +} +a.gloss-term:hover, +a.gloss-term:focus { + color: var(--edu); +} +.doors-popover .gloss-popover-title { + font-family: var(--display); + font-size: 1.1rem; + font-weight: 500; + margin: 0 0 6px; +} +/* Translated terms: the term on its own line, its English name below it, isolated left to right; the + breadcrumb names only the term; English references read left to right inside an Arabic page. */ +.glossary-layout .reading h1 bdi{display:block;margin-top:6px;font-size:.6em;line-height:1.2;color:var(--soft)} +.glossary-layout .crumbs [aria-current=page] bdi{display:none} +main:lang(ar) .glossary-layout .references-list li{direction:ltr;text-align:left} +.term-list-jump{display:none} +@media(max-width:1000px){ + .term-list-jump{display:inline-block;margin-top:10px;font-weight:600;color:var(--link);text-decoration:underline;text-underline-offset:3px} + #glossary-terms{scroll-margin-top:16px} +} +main:lang(ar) .glossary-layout .reading h1 bdi{text-align:right} +@media(max-width:1000px){.term-list-jump{display:block;width:fit-content}} +/* Languages on a term page as the hub's pills. */ +.glossary-layout .language-switcher{display:flex;flex-wrap:wrap;align-items:center;gap:6px} +.glossary-layout .language-switcher strong{font-size:16px;margin-inline-end:4px} +.glossary-layout .language-switcher a{font-size:16px;padding:5px 12px;border:1.5px solid var(--line);border-radius:999px;background:#fff;color:var(--link);text-decoration:none} +.glossary-layout .language-switcher a:hover{border-color:var(--link)} +.glossary-layout .language-switcher .term-list-jump{flex-basis:auto;margin-top:6px;border-color:var(--ink);color:var(--ink);font-weight:600} +@media(max-width:1000px){.glossary-layout .language-switcher .term-list-jump{display:inline-block}} +/* English runs on an Arabic page in the site's own faces; the Arabic text keeps its own, whose spacing the line-length rule is set for. */ +main:lang(ar) [lang=en],main:lang(ar) .glossary-layout .references-list li{font-family:'Source Sans 3','Source Sans Pro',system-ui,sans-serif} +main:lang(ar) h1 [lang=en]{font-family:var(--display)} +/* A translated term's English name, on its own smaller line wherever the term is listed. */ +.term-list .term-en,.post-nav .term-en{display:block;font-size:15px;color:var(--soft)} +main:lang(ar) .term-list .term-en,main:lang(ar) .post-nav-prev .term-en{text-align:right} +main:lang(ar) .post-nav-next .term-en{text-align:left} +/* The glossary hub: languages, a search and every term with the start of its definition. */ +.glossary-hub .glossary-languages{display:flex;flex-wrap:wrap;gap:8px;margin:18px 0 0} +.glossary-hub .glossary-languages a{padding:7px 14px;border:1.5px solid var(--line);border-radius:999px;background:#fff;color:var(--link);font:600 16px/1.3 var(--text);text-decoration:none} +.glossary-hub .glossary-languages a:hover{border-color:var(--link)} +.glossary-hub .glossary-languages a[aria-current]{background:var(--ink);border-color:var(--ink);color:#fff} +.glossary-find{margin:28px 0 10px;max-width:640px} +.glossary-find label{display:block;margin:0 0 8px;font:var(--display-weight) 24px/1.2 var(--display)} +.glossary-find input{width:100%;box-sizing:border-box;padding:12px 18px;border:1.5px solid var(--ink);border-radius:999px;background:#fff;font:400 18px/1.3 var(--text);color:var(--ink)} +.glossary-none{margin:12px 0 0;font-size:17px} +.glossary-letters{display:flex;flex-wrap:wrap;gap:4px;margin:14px 0 8px} +.glossary-letters a{display:inline-flex;align-items:center;justify-content:center;min-width:36px;height:36px;border-radius:var(--r-small);background:var(--surface);color:var(--ink);font:600 16px/1 var(--text);text-decoration:none} +.glossary-letters a:hover{background:var(--edu-bg)} +.term-group{scroll-margin-top:16px} +.term-group h2{margin:28px 0 10px;font-size:28px;color:var(--edu)} +.term-group ul{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,300px),1fr));gap:12px;margin:0;padding:0;list-style:none} +.term-group li{margin:0;padding:14px 16px;background:var(--surface);border-radius:var(--r-large)} +.term-group li a{color:var(--link);font:600 18px/1.3 var(--text);text-decoration:none} +.term-group li a:hover{text-decoration:underline;text-underline-offset:3px} +.term-group li .term-en{display:block;font-size:15px;font-weight:400;color:var(--soft)} +.term-group li p{margin:6px 0 0;font-size:16px;line-height:1.45;color:var(--ink)} +.glossary-about{margin:0 0 20px;padding:4px 20px;background:var(--surface);border-radius:var(--r-large)} +.glossary-about>summary{padding:14px 0;cursor:pointer;font:600 18px/1.3 var(--text);color:var(--ink)} +.glossary-about .article-style>.btn-group:first-child{display:none} +/* A term page leads with its definition in a tinted panel; credits are a quiet line at the end. */ +.glossary-layout .article-style .term-definition{margin:6px 0 18px;padding:18px 22px;background:var(--edu-bg);border:0;border-radius:var(--r-large)} +.glossary-layout .article-style .term-definition p{margin:0;font-size:20px;line-height:1.55} +.glossary-layout .article-style .term-definition .term-definition-label{margin:0 0 6px;color:var(--edu);font:600 15px/1.3 var(--text)} +.glossary-layout .article-style .term-credit{margin:0 0 4px;font-size:16px;color:var(--soft)} +.glossary-layout .article-style .term-credit b{font-weight:600;color:var(--ink)} +.glossary-layout .article-style .term-credit:first-of-type{margin-top:18px} +.glossary-count{margin:8px 0 0;font-size:16px;color:var(--soft)} +.glossary-count:empty{display:none} +/* The letters stay in reach while the terms scroll by. */ +.glossary-tools{position:sticky;top:0;z-index:5;padding:6px 0 4px;background:var(--page);border-bottom:1px solid var(--line)} +.glossary-letters a.is-empty{opacity:.35;pointer-events:none} +/* Related terms as chips: linked where the term has a page, plain where it has none yet. */ +.glossary-layout .article-style .term-related a{color:var(--link);text-decoration:underline;text-underline-offset:3px} +.glossary-layout .article-style .term-credit{color:var(--ink)} +/* On a phone the hub's terms are compact rows with two lines of definition, and a term's definition panel is lighter. */ +@media(max-width:760px){ + .term-group ul{gap:8px} + .term-group li{padding:10px 14px} + .term-group li p{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin-top:4px;font-size:15px} + .glossary-layout .article-style .term-definition{padding:14px 16px} + .glossary-layout .article-style .term-definition p{font-size:18px} +} +/* On a phone the languages and the letters are one row each, scrolled sideways, so the terms start in the first screen. */ +@media(max-width:760px){ + .glossary-hub .glossary-languages,.glossary-letters{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:thin;padding-bottom:6px} + .glossary-hub .glossary-languages a,.glossary-letters a{flex:none} + .glossary-find{margin-top:18px} + .glossary-find label{font-size:21px} +} +/* Related terms on their own row under the label; those without a page of their own are muted chips. */ +.glossary-layout .article-style .term-unlinked{color:var(--ink)} +.glossary-search-row{display:flex;gap:8px;align-items:center} +.glossary-search-row input{flex:1} +.glossary-clear{flex:none;padding:10px 16px;border:1.5px solid var(--ink);border-radius:999px;background:#fff;color:var(--ink);font:600 16px/1.2 var(--text);cursor:pointer} +.glossary-clear[hidden]{display:none} +.term-group{scroll-margin-top:200px} +@media(max-width:760px){.glossary-tools .glossary-find label{font-size:17px;margin-bottom:6px}.glossary-find{margin-top:10px}.term-group{scroll-margin-top:150px}} +.glossary-language-select{display:none} +@media(max-width:760px){ + .glossary-hub .glossary-languages{display:none} + .glossary-language-select{display:flex;align-items:center;gap:10px;margin:14px 0 0;font:600 16px/1.3 var(--text)} + .glossary-language-select select{flex:1;padding:9px 12px;border:1.5px solid var(--ink);border-radius:var(--r-small);background:#fff;font:400 16px/1.3 var(--text);color:var(--ink)} +} +/* Two lines of definition per term on every screen, so each letter reads as an even block. */ +.term-group li p{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden} +main:lang(ar) .glossary-layout .article-style .term-definition p{font-size:21px} +.term-filter-status{margin:4px 0 0;font-size:15px;color:var(--soft)} +.term-filter-status:empty{display:none} +.term-filter-clear{align-self:flex-start;margin:4px 0 8px;padding:4px 12px;border:1.5px solid var(--ink);border-radius:999px;background:#fff;font:600 15px/1.3 var(--text);color:var(--ink);cursor:pointer} +.term-filter-clear[hidden]{display:none} +/* On a phone the list of terms sits in a disclosure right after the definition (site.js). */ +.term-finder{margin:0 0 18px;background:var(--surface);border-radius:var(--r-large)} +.term-finder>summary{padding:12px 16px;cursor:pointer;font:600 17px/1.3 var(--text);color:var(--link)} +.term-finder .glossary-card{position:static;max-height:none;margin:0;background:none;padding:0 16px 14px} +.term-finder .glossary-card>h2{display:none} +.term-finder .term-list{max-height:50vh;overflow-y:auto} +.glossary-hub .standfirst a{text-decoration:underline;text-underline-offset:3px} +.glossary-view{flex:none;display:inline-flex;border:1.5px solid var(--line);border-radius:999px;background:#fff;overflow:hidden} +.glossary-view button{padding:10px 14px;border:0;background:transparent;color:var(--ink);font:600 16px/1.2 var(--text);cursor:pointer} +.glossary-view button[aria-pressed="true"]{background:var(--ink);color:#fff} +.glossary-view[hidden]{display:none} +/* List view: one row per term, its definition on one line beside or under it. */ +.term-grid.is-list .term-group ul{grid-template-columns:minmax(0,1fr);gap:0} +.term-grid.is-list .term-group li{padding:8px 4px;background:none;border-radius:0;border-bottom:1px solid var(--line);display:grid;grid-template-columns:minmax(0,280px) minmax(0,1fr);gap:4px 20px;align-items:baseline} +.term-grid.is-list .term-group li p{margin:0;-webkit-line-clamp:1} +@media(max-width:760px){.term-grid.is-list .term-group li{grid-template-columns:minmax(0,1fr)}} +@media(max-width:760px){.glossary-search-row{flex-wrap:wrap}.glossary-search-row input{flex-basis:100%}} +.glossary-letters a.is-current{background:var(--ink);color:#fff} +.term-filter-label{display:block;margin:0 0 6px;font:600 16px/1.3 var(--text);color:var(--ink)} +/* Arabic term pages: each breadcrumb step and each link is isolated, so an English name or a URL keeps its own + order and its brackets inside right-to-left text. */ +main:lang(ar) .crumbs a,main:lang(ar) .crumbs [aria-current]{unicode-bidi:isolate} +main:lang(ar) .glossary-layout .article-style a{unicode-bidi:plaintext} +/* A contributor's name is never split across lines. */ +.glossary-layout .term-credit bdi.term-name{white-space:nowrap} diff --git a/assets/css/doors/home.css b/assets/css/doors/home.css new file mode 100644 index 00000000000..2ae425bc2a5 --- /dev/null +++ b/assets/css/doors/home.css @@ -0,0 +1,204 @@ +/* The home page. Loaded only there, so the short class names from the mock cannot reach anything + else. Lists and paragraphs are reset here because the theme's global rules still apply. */ +.hero{position:relative;isolation:isolate;border-bottom:12px solid var(--edu-bg)} +.hero-grid{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,1fr);gap:48px;align-items:end;padding-top:12px} +.invite{padding:32px 0 28px;min-width:0;align-self:center} +.hero h1{font-size:clamp(50px,6.2vw,92px);line-height:1;font-weight:500;letter-spacing:-.018em;margin:0;overflow-wrap:anywhere;text-wrap:balance} +.lede{font-size:clamp(18px,1.45vw,20px);line-height:1.55;margin:24px 0 0;max-width:32em;color:var(--ink)} +.quick{display:flex;flex-wrap:wrap;gap:8px;margin-top:24px;max-width:720px} +.quick a{padding:8px 14px;border:1.5px solid var(--edu);border-radius:999px;font-weight:600;font-size:16px;line-height:1.3;text-decoration:none;background:#fff;color:var(--ink)} +.quick a:hover{background:var(--edu);border-color:var(--edu);color:#fff} +.tower{margin:0;position:relative;min-width:0;background:var(--edu-bg);border-radius:999px 999px 0 0;padding:28px 32px 14px;max-width:560px;justify-self:end;width:100%} +.castle-stage{position:relative;height:clamp(320px,31vw,410px)} +.castle-stage canvas,.castle-fallback{display:block;width:100%;height:100%;max-width:none;object-fit:cover} +.castle-stage canvas{position:absolute;inset:0;visibility:hidden} +.castle-stage[data-engine=webgl] canvas{visibility:visible} +.castle-stage[data-engine=webgl] .castle-fallback{visibility:hidden} +/* Out of sight until a pointer, keyboard focus or a tap reaches it, so nothing sits beside + the tower. It does nothing until the 3D castle is running. */ +.motion-bar{position:absolute;left:50%;bottom:8px;transform:translateX(-50%);z-index:3;display:flex;align-items:center;gap:8px;width:max-content} +[data-motion-host]:not(:hover):not(:focus-within):not(.show-motion-control) :is(.motion-toggle,.tower-entry){position:absolute;width:1px;height:1px;padding:0;border:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap} +/* A finger counts as hovering from the moment it touches, which would reveal a button under that + finger and send the tap to it. On touch screens only a finished tap reveals these buttons. */ +@media(hover:none){[data-motion-host]:not(:focus-within):not(.show-motion-control) :is(.motion-toggle,.tower-entry){position:absolute;width:1px;height:1px;padding:0;border:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}} +.motion-toggle,.tower-entry{background:#fff;border:1.5px solid var(--ink);border-radius:999px;font-weight:600;font-size:15px;line-height:1.3;color:var(--ink);cursor:pointer;white-space:nowrap} +.tower-entry{padding:7px 12px} +/* The icon carries the meaning on screen. The words stay in the button for screen readers. */ +.motion-toggle{position:relative;width:36px;height:36px;padding:0;overflow:hidden;text-indent:44px} +.motion-toggle::before{content:"";position:absolute;left:50%;top:50%;width:11px;height:13px;transform:translate(-50%,-50%);border-inline:3.5px solid currentColor} +.motion-toggle.is-paused::before{width:0;height:0;margin-left:1px;border-block:7px solid transparent;border-inline:0;border-left:11px solid currentColor} +@media(hover:hover){.motion-toggle:hover,.tower-entry:hover{background:var(--ink);color:#fff}} +@media(pointer:coarse){.motion-toggle{width:44px;height:44px}.tower-entry{min-height:44px;padding-block:0;padding-inline:14px}} +.castle-stage[data-engine=fallback]+.motion-bar{display:none} + + +/* The four areas, each behind its arched door */ +.areas-section{padding-top:0} +.areas{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:22px;padding-top:40px} +.area{--tone:var(--edu);--tint:var(--edu-bg);display:flex;flex-direction:column;min-width:0;background:var(--tint);border-radius:999px 999px var(--r-large) var(--r-large);padding:0 22px 20px} +.area-res{--tone:var(--res);--tint:var(--res-bg)} +.area-jus{--tone:var(--jus);--tint:var(--jus-bg)} +.area-com{--tone:var(--com);--tint:var(--com-bg)} +.area-plate{position:relative;height:clamp(176px,14.5vw,208px);min-height:0} +.area-plate img{position:absolute;inset:24% 11% 2%;width:78%;height:74%;max-width:none;object-fit:contain} +.area-body{display:flex;flex-direction:column;flex:1;padding-top:12px} +.area h3{font-size:clamp(26px,2.1vw,31px);line-height:1.12;color:var(--tone);min-height:2.3em;margin:0;overflow-wrap:anywhere} +.area-body>p{margin:8px 0 0;font-size:16px;line-height:1.5} +.area ul{list-style:none;margin:auto 0 0;padding:14px 0 0} +.area li a{display:block;padding:7px 0;font-size:17px;line-height:1.35;color:var(--ink);text-decoration:underline;text-decoration-color:color-mix(in srgb,var(--tone) 45%,transparent);text-underline-offset:3px} +.area li a:hover{color:var(--tone);text-decoration-color:currentColor} + +/* Topic buttons */ +.topics-section{padding-top:64px} +.topics-section h2,.board h2{font-size:clamp(32px,3.2vw,46px);line-height:1.12;margin:0} +.section-note{margin:8px 0 0} +.topics{display:flex;flex-wrap:wrap;gap:10px;list-style:none;margin:22px 0 0;padding:0} +.topic{display:inline-block;padding:10px 16px;background:var(--edu-bg);color:var(--ink);border-radius:999px;font-weight:600;font-size:16px;line-height:1.3;text-decoration:none} +.topic:hover{background:var(--edu);color:#fff} + +/* Noticeboard and ways to take part */ +/* The news spans both rows; the second row takes up the extra height, so the sign-up sits + straight under "Take part" whatever the length of the news. */ +.board{display:grid;grid-template-columns:minmax(0,3fr) minmax(0,2fr);grid-template-rows:auto 1fr;gap:28px 64px;padding-top:80px} +.notices{grid-row:1/span 2} +.notice-card{margin-top:24px;background:var(--surface);border-radius:var(--r-large);padding:6px 24px} +.notice-list{list-style:none;margin:0;padding:0} +.notice-list li+li{border-top:1.5px solid var(--line)} +.notice-list a{display:grid;grid-template-columns:150px minmax(0,1fr);column-gap:24px;padding:18px 0;color:inherit;text-decoration:none} +.notice-list a:hover h3{text-decoration:underline;text-underline-offset:4px;color:var(--link)} +.notice-list time{grid-column:1;font-weight:600;font-size:15px;line-height:1.5;padding-top:4px} +.notice-kind{grid-column:1;grid-row:2;justify-self:start;align-self:start;margin-top:4px;padding:1px 9px;background:#fff;border-radius:var(--r-small);font-size:15px;font-weight:600;line-height:1.5} +.notice-list h3{grid-column:2;grid-row:1;font-size:24px;line-height:1.12;margin:0} +.notice-list p{grid-column:2;grid-row:2;margin:5px 0 0;font-size:16px;line-height:1.5} +.board-foot{margin:0;padding:16px 0 20px;border-top:1.5px solid var(--line);font-size:16px} +.home-newsletter{grid-column:2;align-self:start} +.home-newsletter h3{font:var(--display-weight) 21px/1.2 var(--display);margin:0} +.home-newsletter>p{margin:3px 0 12px;font-size:16px;line-height:1.5} +.standing-list{list-style:none;margin:24px 0 0;padding:0} +.standing-list a{display:block;padding:12px 0;color:inherit;text-decoration:none} +.standing-list strong{display:block;font:var(--display-weight) 21px/1.2 var(--display);color:var(--link);text-decoration:underline;text-decoration-thickness:1.5px;text-underline-offset:4px;text-decoration-color:transparent} +.standing-list a:hover strong{text-decoration-color:currentColor} +.standing-list span{display:block;margin-top:3px;font-size:16px;line-height:1.5} + +/* The band: quotes, statement, FORWARD and figures */ +.about{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:40px 64px;padding:80px 0 32px} +.about-statement{grid-column:1/-1;max-width:980px} +.about-statement>p{margin:6px 0 16px;font-size:18px;max-width:32em} +.quotes{margin:0} +.quote-stage{display:grid} +.quote{grid-area:1/1;margin:0;font:var(--display-weight) clamp(34px,4.2vw,60px)/1.08 var(--display);color:var(--ink);overflow-wrap:anywhere;text-wrap:balance;opacity:0;visibility:hidden;transition:opacity .4s,visibility 0s .4s} +.quote.is-active{opacity:1;visibility:visible;transition:opacity .4s} +.quote .accent{color:var(--edu)} +.quotes-bar{min-height:44px;padding-top:8px;text-align:start} +.forward{padding:28px 30px;background:var(--surface);border-radius:var(--r-large)} +.forward h3{font-size:clamp(25px,2.2vw,31px);line-height:1.12;margin:0} +.forward p{margin:12px 0 0;font-size:16px;line-height:1.55;max-width:32em} +.forward .forward-links{display:flex;flex-wrap:wrap;gap:8px 24px;max-width:none} +.ledger{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;align-content:start;list-style:none;margin:0;padding:0} +.ledger a{display:flex;flex-direction:column;gap:6px;height:100%;box-sizing:border-box;padding:18px 20px;border:1.5px solid var(--line);border-radius:var(--r-large);color:var(--ink);text-decoration:none} +.ledger strong{font:var(--display-weight) clamp(30px,2.8vw,40px)/1 var(--display);color:var(--ink)} +.ledger span{font-size:16px;line-height:1.4} +.ledger a:hover{border-color:var(--link)} +.ledger a:hover strong{color:var(--link)} + +@media(max-width:1240px){ + .castle-stage{height:clamp(320px,34vw,400px)} + .areas{grid-template-columns:repeat(2,minmax(0,1fr))} + .area-plate{height:190px} + .area h3{min-height:0} + .board{gap:44px} +} +@media(max-width:1000px){ + .board{grid-template-columns:1fr;grid-template-rows:none;row-gap:44px} + .notices{grid-row:auto} + .home-newsletter{grid-column:auto} + .standing-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:28px} + .about{grid-template-columns:minmax(0,1fr)} +} +@media(max-width:760px){ + .hero-grid{grid-template-columns:1fr;gap:0} + .invite{padding:18px 0 0} + .hero h1{font-size:clamp(42px,12.4vw,54px)} + .lede{font-size:17px;margin-top:16px} + .quick{margin-top:18px} + .quick a{padding:8px 12px;font-size:15px} + /* The extra room at the foot of the door keeps the 44 px buttons off the tower. */ + .tower{padding:18px 12px 58px;max-width:270px;justify-self:center;margin-top:16px} + .castle-stage{height:150px} + .hero{border-bottom-width:8px} + .areas{grid-template-columns:1fr;gap:12px;padding-top:18px} + .area{display:grid;grid-template-columns:78px minmax(0,1fr);column-gap:14px;padding:14px;border-radius:var(--r-large)} + .area-plate{height:92px;align-self:start;background:#ffffffb0;border-radius:999px 999px 0 0} + .area-plate img{inset:14px 8px 2px;width:calc(100% - 16px);height:calc(100% - 16px)} + .area-body{display:block;padding-top:0} + .area h3{font-size:23px} + .area-body>p{margin-top:5px} + .area ul{padding-top:6px} + /* Only with script, which sets .js above the areas: without it every link stays in view. */ + .js .area li:nth-child(n+3):not(.area-more){display:none} + .js .area.is-open li:nth-child(n+3){display:list-item} + .js .area.is-open li.area-more{display:none} + .area-more button{margin:2px 0 0;padding:8px 0;text-align:left;background:none;border:0;color:var(--ink);font:600 16px/1.3 var(--text);text-decoration:underline;text-underline-offset:3px;cursor:pointer} + .area li a{padding:8px 0;font-size:16px} + .board{gap:44px;padding-top:56px} + .standing{grid-row:1} + .notices{grid-row:2} + .notice-card{padding:4px 16px} + .notice-list a{display:flex;flex-wrap:wrap;align-items:center;gap:4px 10px;padding:14px 0} + .notice-list time{padding:0} + .notice-kind{margin:0} + .notice-list h3{flex:0 0 100%;font-size:22px} + .notice-list p{flex:0 0 100%;margin-top:0} + .standing-list{grid-template-columns:1fr} + .standing-list a{padding:9px 0} + .topics-section{padding-top:44px} + .topics{gap:8px;margin-top:16px} + .topic{padding:9px 12px;font-size:15px} + .about{padding:56px 0 28px} + .quote{font-size:34px} + .forward{padding:22px 20px} +} + +.area-more{display:none} +@media(max-width:760px){.js .area-more{display:list-item}} +/* The statement band on a wide screen: the quote on the left, what FORRT is and the mission link beside it. */ +@media(min-width:1001px){ + .about-statement{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);column-gap:56px;align-items:end;max-width:none} + .about-statement .quotes{grid-column:1;grid-row:1 / span 2} + .about-statement>p,.about-statement>.more{grid-column:2} + .about-statement>p{margin-top:0} + .about-statement>.more{justify-self:start;margin-bottom:52px} +} +.ledger strong{white-space:nowrap} +/* The first hero link is the main task, filled; the others stay as outlines. */ +.quick a:first-child{background:var(--edu);border-color:var(--edu);color:#fff} +.quick a:first-child:hover{background:var(--ink);border-color:var(--ink)} +/* The second line under the hero actions: plain links, on wide screens only. */ +.quick-more{display:flex;flex-wrap:wrap;gap:4px 22px;margin-top:10px} +.quick-more a{padding:6px 0;font-weight:600;font-size:16px;line-height:1.3;color:var(--ink);text-decoration:underline;text-underline-offset:3px} +.quick-more a:hover{color:var(--edu)} +@media(max-width:760px){.quick-more{display:none}} +@media(max-width:760px){.tower{max-width:200px}.castle-stage{height:120px}} + +/* Noticeboard thumbnails: each news row leads with a small crop of the post's featured image, or a + tile in its category's tint, beside the date and category, title and an excerpt of two lines. + The rules above set the older two-column layout; these replace it on every screen width. */ +.notice-list a{display:grid;grid-template-columns:88px minmax(0,1fr);grid-template-rows:auto auto 1fr;column-gap:20px;align-items:start;padding:18px 0} +.notice-thumb{grid-column:1;grid-row:1/span 3;display:block;width:88px;height:66px;margin-top:4px;object-fit:cover;border-radius:var(--r-small);background:var(--page)} +.notice-tile{display:grid;place-items:center;background:var(--res-bg);color:var(--res);font:var(--display-weight) 34px/1 var(--display)} +.notice-tile-newsletter{background:var(--com-bg);color:var(--com)} +.notice-tile-opportunity{background:var(--edu-bg);color:var(--edu)} +.notice-tile-milestone{background:var(--jus-bg);color:var(--jus)} +.notice-meta{grid-column:2;grid-row:1;display:flex;flex-wrap:wrap;align-items:center;gap:4px 10px} +.notice-list time{padding:0} +.notice-kind{margin:0} +.notice-list h3{grid-column:2;grid-row:2;margin:2px 0 0} +.notice-list p{grid-column:2;grid-row:3;margin:5px 0 0;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden} +@media(max-width:760px){ + /* The thumbnail sits beside the date and title; the excerpt takes the full width below. */ + .notice-list a{grid-template-columns:64px minmax(0,1fr);grid-template-rows:auto 1fr auto;gap:0 12px;padding:14px 0} + .notice-thumb{grid-row:1/span 2;width:64px;height:48px;margin-top:2px} + .notice-tile{font-size:26px} + .notice-list h3{flex:none} + .notice-list p{flex:none;grid-column:1/-1;margin-top:6px} +} diff --git a/assets/css/doors/hub.css b/assets/css/doors/hub.css new file mode 100644 index 00000000000..c6da252a97d --- /dev/null +++ b/assets/css/doors/hub.css @@ -0,0 +1,51 @@ +/* The Replication Hub. Loaded only on /replication-hub/. */ +.heading-plate-plain{margin:0;justify-self:end;width:100%;max-width:400px} +.heading-plate-plain img{display:block;width:100%;height:auto} +@media(max-width:760px){.heading-plate-plain{display:none}} +.stage-jumps{display:none;--tone:#44598c} +@media(max-width:760px){ + .stage-jumps{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 20px} + .stage-jump{padding:8px 16px;border:1.5px solid var(--tone);border-radius:999px;background:#fff;color:var(--tone);font:600 16px/1.3 var(--text);text-decoration:none} + .stage-jump:hover{background:var(--tone);color:#fff} + .stage h2{scroll-margin-top:16px} +} +.heading-plate-research{background:var(--res-bg)} + +@media(max-width:760px){.stage-jumps{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}.stage-jump{text-align:center}} + +/* Start here: four bricks in the stage colours, joined into one course on a wide screen. */ +.hub-start{margin:0 0 40px} +.hub-start h2{margin:0 0 4px;font:500 28px/1.2 var(--display)} +.hub-start .action-cards li:nth-child(1){--tone:#44598c}.hub-start .action-cards li:nth-child(2){--tone:#25705f} +.hub-start .action-cards li:nth-child(3){--tone:#a34a0c}.hub-start .action-cards li:nth-child(4){--tone:#7a5c3d} +/* Joined into one course once all four fit on a line; until then they keep a row gap for their studs. */ +@media(min-width:1001px){.hub-start.hub-start .action-cards{gap:0 4px}} + +/* Stages: open sections under a rule in the stage colour, each tool a ruled row whose whole width is + the link (a stretched link), so it is a full-size target on a phone. Set like a contents page. */ +.stage{--tone:#44598c;--tint:#eef0f5;min-width:0;border-top:5px solid var(--tone);padding-top:12px} +.stage-explore{--tone:#25705f;--tint:#ebf2f1}.stage-conduct{--tone:#a34a0c;--tint:#f7efe9}.stage-disseminate{--tone:#7a5c3d;--tint:#f3f0ee} +.stages{display:grid;grid-template-columns:1fr;gap:40px} +@media(min-width:1001px){.stages{display:block;columns:2;column-gap:56px}.stage{break-inside:avoid;margin:0 0 44px}} +.stage header{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 16px} +.stage h2{margin:0;font:500 34px/1.1 var(--display);color:var(--tone);scroll-margin-top:16px;overflow-wrap:anywhere} +.stage header p{margin:0;font-size:17px;line-height:1.4;color:var(--soft)} +.stage ul{list-style:none;margin:10px 0 0;padding:0} +.tool{position:relative;display:grid;grid-template-columns:7.5rem minmax(0,1fr);column-gap:16px;padding:12px 8px;border-bottom:1px solid var(--line)} +.tool p.tool-format{grid-column:1;grid-row:1/span 2;margin:0;padding-top:5px;font:600 15px/1.3 var(--text);color:var(--soft)} +.tool h3{grid-column:2;margin:0;font:500 21px/1.25 var(--display);overflow-wrap:anywhere} +.tool h3 a{color:var(--ink);text-decoration:underline;text-decoration-color:var(--tone);text-decoration-thickness:1px;text-underline-offset:3px} +.tool h3 a::after{content:"";position:absolute;inset:0} +.tool p:not(.tool-format){grid-column:2;margin:2px 0 0;font-size:16px;line-height:1.45} +.tool:hover{background:var(--tint)} +.tool:hover h3 a{text-decoration-thickness:2px} +.tool:focus-within{outline:3px solid var(--ink);outline-offset:2px;background:var(--tint)} +.tool h3 a:focus-visible{outline:none} +/* Each stage's starting point keeps its emphasis as a title in the stage colour. */ +.tool:first-child h3 a{color:var(--tone)} +@media(max-width:600px){ + .tool{grid-template-columns:1fr;padding:12px 4px} + .tool p.tool-format,.tool h3,.tool p:not(.tool-format){grid-column:1;grid-row:auto} + .tool p.tool-format{padding:0;margin:0 0 2px} +} +@media(prefers-reduced-motion:no-preference){.tool{transition:background-color .15s}} diff --git a/assets/css/doors/icons.css b/assets/css/doors/icons.css new file mode 100644 index 00000000000..dcc747da4c1 --- /dev/null +++ b/assets/css/doors/icons.css @@ -0,0 +1,10 @@ +/* Generated by scripts/rebrand/build_fonts.py --icons: Font Awesome Free 6.7.2 (fonts OFL 1.1, + code MIT), subset to the icons the site uses. */ +@font-face{font-family:'Doors Icons brands';src:url(../fonts/doors/icons-brands.woff2) format('woff2');font-weight:400;font-display:block} +.fab,.fa-brands{font-family:'Doors Icons brands';font-weight:400} +@font-face{font-family:'Doors Icons solid';src:url(../fonts/doors/icons-solid.woff2) format('woff2');font-weight:900;font-display:block} +.fa,.fas,.fa-solid{font-family:'Doors Icons solid';font-weight:900} +@font-face{font-family:'Doors Icons regular';src:url(../fonts/doors/icons-regular.woff2) format('woff2');font-weight:400;font-display:block} +.far,.fa-regular{font-family:'Doors Icons regular';font-weight:400} +.fab,.fa-brands,.fa,.fas,.fa-solid,.far,.fa-regular{display:inline-block;line-height:1;-webkit-font-smoothing:antialiased;text-rendering:auto} +.fa-angle-right::before{content:"\f105"}.fa-arrow-alt-circle-right::before{content:"\f35a"}.fa-arrow-right::before{content:"\f061"}.fa-arrow-up::before{content:"\f062"}.fa-bars::before{content:"\f0c9"}.fa-bluesky::before{content:"\e671"}.fa-book::before{content:"\f02d"}.fa-book-open::before{content:"\f518"}.fa-brain::before{content:"\f5dc"}.fa-chart-line::before{content:"\f201"}.fa-chevron-down::before{content:"\f078"}.fa-chevron-right::before{content:"\f054"}.fa-envelope::before{content:"\f0e0"}.fa-external-link-alt::before{content:"\f35d"}.fa-facebook::before{content:"\f09a"}.fa-file-alt::before{content:"\f15c"}.fa-file-pdf::before{content:"\f1c1"}.fa-flask::before{content:"\f0c3"}.fa-folder::before{content:"\f07b"}.fa-github::before{content:"\f09b"}.fa-globe::before{content:"\f0ac"}.fa-graduation-cap::before{content:"\f19d"}.fa-heart::before{content:"\f004"}.fa-instagram::before{content:"\f16d"}.fa-layer-group::before{content:"\f5fd"}.fa-lightbulb::before{content:"\f0eb"}.fa-link::before{content:"\f0c1"}.fa-linkedin::before{content:"\f08c"}.fa-mastodon::before{content:"\f4f6"}.fa-orcid::before{content:"\f8d2"}.fa-play::before{content:"\f04b"}.fa-project-diagram::before{content:"\f542"}.fa-redo::before{content:"\f01e"}.fa-search::before{content:"\f002"}.fa-share-alt::before{content:"\f1e0"}.fa-slack::before{content:"\f198"}.fa-star::before{content:"\f005"}.fa-times::before{content:"\f00d"}.fa-times-circle::before{content:"\f057"}.fa-twitter::before{content:"\f099"}.fa-user::before{content:"\f007"}.fa-users::before{content:"\f0c0"}.fa-youtube::before{content:"\f167"} diff --git a/assets/css/doors/inner.css b/assets/css/doors/inner.css new file mode 100644 index 00000000000..2e0ba96712a --- /dev/null +++ b/assets/css/doors/inner.css @@ -0,0 +1,63 @@ +/* Bridge for pages not redesigned yet: the theme's components in Doors colours and type, + so they sit inside the new shell. Each page group replaces this in its own phase. */ +.btn-primary{background:var(--link);border-color:var(--link);color:#fff} +.btn-primary:hover,.btn-primary:focus{background:var(--ink);border-color:var(--ink);color:#fff} +.btn-outline-primary{color:var(--link);border-color:var(--link);background:transparent} +.btn-outline-primary:hover{background:var(--link);border-color:var(--link);color:#fff} +.home-section,.docs-content,.article,.universal-wrapper{background:transparent} +/* The theme fixes this at 1000 px from a 1001 px screen, wider than its padded parent. */ +.universal-wrapper{max-width:100%} +/* The theme's sidebar, contents and article columns add up to 13 of 12, so the article takes what is left. */ +@media(min-width:1200px){.docs-sidebar~.docs-content{flex:1 1 0;max-width:none;min-width:0}} +code,pre,kbd,samp{font-style:normal} + +/* Cookie banner: same library and wording, in Doors colours. */ +.cc-window.cc-window{max-height:100vh;overflow-y:auto;background:var(--ink);color:var(--page);font-family:var(--text);font-size:16px;line-height:1.5} +.cc-window .cc-link,.cc-window .cc-link:hover,.cc-window .cc-link:visited{color:var(--page);opacity:1} +.cc-window.cc-window .cc-btn,.cc-window.cc-window .cc-btn:focus{background:var(--hot);color:var(--ink);border:0;border-radius:999px;font-weight:700;font-size:15px;text-decoration:none} +.cc-window.cc-window .cc-btn:hover{background:var(--page);color:var(--ink);text-decoration:none} + +/* An embed from another site, loaded when a visitor asks (layouts/partials/click_to_load.html). */ +.click-to-load{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;min-height:12rem;margin:0 0 18px;padding:24px;overflow:auto;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-small);text-align:center} +.click-to-load p{max-width:34rem;margin:0;font-size:16px;line-height:1.45} +.click-to-load-link{font-size:16px} +/* A lesson activity in a small frame on a phone is too small to use, so there it opens on the provider's site. */ +@media(max-width:760px){ + .click-to-load[style*="absolute"]:not([data-embed-src*="youtube"]) button{display:none} + .click-to-load[style*="absolute"]:not([data-embed-src*="youtube"]) .click-to-load-link{padding:8px 18px;background:var(--link);border-radius:999px;color:#fff;font-weight:600;text-decoration:none} +} +/* The flipbook's page count, in its own toolbar. */ +label[for=df_book_page_number]{color:var(--ink)!important;opacity:1!important} +/* Lesson activities sit in a fixed-ratio box meant for the frame. Until it loads, the panel sets its own height. */ +.click-to-load[style*="absolute"]{position:static!important;width:auto!important;height:auto!important} +:has(>.click-to-load[style*="absolute"]){height:auto!important;padding:0!important;overflow:visible!important} + +/* Author card and its social links. The card's name is an h2 under the article and elsewhere. */ +.author-card{display:flex;align-items:flex-start;gap:14px;margin:0 0 20px} +.author-card .avatar{flex:none;width:64px;height:64px;border-radius:50%;object-fit:cover;margin:0} +.author-card .avatar-square{border-radius:var(--r-small)} +.author-card .media-body{min-width:0} +.author-card .card-title{font-size:21px;line-height:1.2;margin:0 0 4px} +.author-card .card-title a{color:inherit} +ul.network-icon{display:flex;flex-wrap:wrap;gap:6px 10px;margin:6px 0 0;padding:0;list-style:none} +ul.network-icon li{margin:0} +ul.network-icon a{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;font-size:18px;color:var(--link)} +ul.network-icon a:hover{color:var(--ink)} +.cite-dialog{width:min(640px,calc(100% - 32px));padding:22px;border:0;border-radius:var(--r-large);background:var(--page);color:var(--ink)} +.cite-dialog::backdrop{background:rgba(38,45,53,.55)} +.cite-dialog h2{font-size:26px} +.cite-dialog pre{max-height:50vh;overflow:auto;padding:12px;background:var(--surface);border-radius:var(--r-small)} + +/* Forced colours drop backgrounds, so filled buttons need an edge, and the dark logo needs a light plate. */ +@media (forced-colors:active){ + .head-join,.cc-window.cc-window .cc-btn,.newsletter-submit,.btn,.tower-entry,.motion-toggle{border:2px solid ButtonText!important} + .brand img{background:#fff;padding:2px;border-radius:4px;forced-color-adjust:none} +} +/* On a phone the consent banner is compact: tighter padding and its two buttons side by side. The wording stays. */ +@media(max-width:760px){ + .cc-window.cc-window{padding:12px 16px;font-size:15px;line-height:1.4} + .cc-window.cc-window .cc-message{margin-bottom:10px} + .cc-window.cc-window .cc-compliance{display:flex;gap:8px} + /* The plug-in's second-button margin and the 140 px minimum would overflow a 320 px screen. */ + .cc-window.cc-window .cc-compliance .cc-btn{flex:1 1 0;min-width:0;margin:0;padding:9px 12px} +} diff --git a/assets/css/doors/launcher.css b/assets/css/doors/launcher.css new file mode 100644 index 00000000000..b2bfa122e3f --- /dev/null +++ b/assets/css/doors/launcher.css @@ -0,0 +1,67 @@ +/* The JUST-OS launcher on every page. The chat loads when it is clicked (chatbot.css, just-os-chat.js). */ +#just-os-widget{position:fixed;inset-block-end:calc(20px + var(--banner,0px));inset-inline-end:20px;z-index:900;display:flex;flex-direction:column;align-items:flex-end;font-family:var(--text)} +#just-os-widget[hidden]{display:none} +/* The opened chat goes above everything, which its own z-index cannot do from in here. */ +#just-os-widget:has(#just-os-window[style*="flex"]){z-index:10000} +.just-os-fullpage{position:relative;font-family:var(--text)} +#just-os-btn{position:relative;z-index:2;display:inline-block;padding:0;background:#fff;border:1.5px solid var(--ink);border-radius:50%;box-shadow:0 1px 4px rgba(38,45,53,.25);cursor:pointer;text-decoration:none;transition:transform .2s ease} +/* At 1440 px the margin beside .wrap is 80 px, so the button (55 px, 20 px from the edge) clears the page by 5 px. + Nothing may reach past it: no growth on hover, a shadow within 4 px, and the focus ring drawn inside. */ +#just-os-btn:focus-visible{outline:3px solid var(--ink);outline-offset:-6px} +#just-os-img{display:block;width:52px;height:52px;border-radius:50%} +#just-os-dismiss{position:absolute;top:-6px;inset-inline-end:-6px;z-index:3;width:22px;height:22px;padding:0;background:#fff;border:1.5px solid var(--ink);border-radius:50%;color:var(--ink);font-size:14px;line-height:18px;text-align:center;cursor:pointer;opacity:0;transition:opacity .2s ease} +#just-os-widget:hover #just-os-dismiss,#just-os-dismiss:focus-visible{opacity:1} +#just-os-dismiss:hover{background:var(--ink);color:#fff} +#just-os-bubble{position:absolute;inset-inline-end:64px;top:11px;padding:6px 12px;background:#fff;border:1.5px solid var(--ink);border-radius:999px;color:var(--ink);font-size:15px;font-weight:600;line-height:1.3;white-space:nowrap;opacity:0;transform:translateX(-6px);transition:opacity .25s ease,transform .25s ease;pointer-events:none} +/* The bubble never covers the page. The invitation shows only where site.js finds empty space under it, + and the hover or focus label only where the margin holds it beside the button: from 1800 px, as .wrap is at most 1280 px wide. */ +.bubble-visible{opacity:1!important;transform:none!important} +@media (min-width:1800px){#just-os-btn:is(:hover,:focus-visible) #just-os-bubble{opacity:1;transform:none}} +#just-os-widget.is-offline #just-os-img{filter:grayscale(1);opacity:.7} +#just-os-offline-badge{position:absolute;inset-block-end:-2px;inset-inline-start:-2px;width:22px;height:22px;display:flex;align-items:center;justify-content:center;background:#fff;border:1.5px solid var(--ink);border-radius:50%;color:var(--urgent)} +#just-os-offline-badge svg{width:14px;height:14px} +@media(max-width:760px){#just-os-widget{inset-block-end:calc(12px + var(--banner,0px));inset-inline-end:12px}#just-os-img{width:48px;height:48px}#just-os-bubble{inset-inline-end:58px;top:9px;font-size:14px}} +/* On a touch screen the close control is larger and its tap area is 44 px, reaching past the visible circle. */ +@media(hover:none){ + #just-os-dismiss{opacity:1;width:28px;height:28px;top:-10px;inset-inline-end:-10px;font-size:18px;line-height:24px} + #just-os-dismiss::after{content:"";position:absolute;inset:-8px} +} +@media print{#just-os-widget{display:none}} + +/* The round button needs 80 px of margin, which the page has from 1440 px (.wrap is at most 1280 px wide). + Below that it would sit over text, cards or buttons wherever it rested, so it is a tab in the page's gutter. site.js uses the same width for the invitation. */ +@media (max-width:1439px){ + #just-os-widget{inset-block-end:calc(16px + var(--banner,0px));inset-inline-end:0} + #just-os-btn{display:flex;align-items:center;justify-content:center;width:24px;height:52px;border-inline-end:0;border-radius:12px 0 0 12px} + #just-os-btn:hover{transform:none} + #just-os-img{width:20px;height:20px} + #just-os-dismiss{display:none} + #just-os-bubble{inset-inline-end:32px;top:10px} + /* The ring stays inside the tab, which an outside ring would take off the screen and over the page. */ + #just-os-btn:focus-visible{outline-offset:-3px} +} +/* Phones have a 20 px margin (base.css), so the tab is 20 px wide and stays off the cards. */ +@media (max-width:760px){ + #just-os-btn{width:20px} + #just-os-img{width:16px;height:16px} + #just-os-bubble{inset-inline-end:28px} +} +/* Below 1440 px the margin is too narrow to hold the button, so it floats in the bottom corner over the page, + as a 48 px round button that is easy to find and press; the footer keeps room under it, so the end of the + page is never hidden. (Earlier the tab sat in the margin; reviewers found it too small to read or press.) */ +@media (max-width:1439px){ + #just-os-widget{inset-block-end:calc(16px + var(--banner,0px) + env(safe-area-inset-bottom,0px));inset-inline-end:16px} + #just-os-btn{display:inline-block;width:auto;height:auto;padding:0;background:#fff;border:1.5px solid var(--ink);border-radius:50%;box-shadow:0 2px 8px rgba(38,45,53,.3)} + #just-os-btn:hover{background:#fff;border-color:var(--link)} + #just-os-img{width:48px;height:48px} + #just-os-btn:focus-visible{outline:3px solid var(--ink);outline-offset:2px} + #just-os-bubble{inset-inline-end:58px;top:9px} + /* A floating button can be dismissed, as on desktop; on touch screens the cross always shows. */ + #just-os-dismiss{display:block} + .site-foot{padding-bottom:88px} +} +/* At 400% zoom a full-size launcher would cover a third of the screen. Last, so the sizes above do not undo it. */ +@media (max-height:420px){ + #just-os-img{width:38px;height:38px} + #just-os-bubble{display:none} +} diff --git a/assets/css/doors/lists.css b/assets/css/doors/lists.css new file mode 100644 index 00000000000..df6b4a8ee63 --- /dev/null +++ b/assets/css/doors/lists.css @@ -0,0 +1,135 @@ +/* List pages: the simple list, news cards, tags and the newsletter archive. */ +.news-page,.tags-page{padding:32px 0 40px} +.news-page .article-head h1,.tags-page h1,.nla-page h1{font-size:clamp(36px,4.2vw,58px);line-height:1.06;letter-spacing:-.01em;margin:0 0 12px} +.news-page .crumbs,.tags-page .crumbs,.nla-page .crumbs{margin-bottom:18px} +.news-page .article-style{max-width:32em;margin-bottom:18px} +.news-page .standfirst,.tags-page .standfirst{font:500 clamp(19px,1.6vw,22px)/1.35 var(--display);max-width:32em} +.list-count{margin:0 0 18px;font-size:18px} +.list-count a{color:var(--link);text-decoration:underline;text-underline-offset:3px} +.plain-list{margin:0;padding:0;list-style:none;font-size:20px;line-height:1.35} +.plain-list li{padding:6px 0;border-bottom:1px solid var(--line)} +.plain-list a{color:var(--link);text-decoration:underline;text-underline-offset:3px} +.list-items{display:grid;gap:18px;margin-top:8px} +.list-item{padding:0 0 18px;border-bottom:1px solid var(--line)} +.article-layout-wide .list-count{max-width:580px} +.article-layout-wide .list-items{grid-template-columns:repeat(auto-fill,minmax(min(100%,420px),1fr));gap:24px 40px} +.article-layout-wide .list-summary{max-width:36em} +@media(min-width:1001px){.article-layout-wide .plain-list{columns:2;column-gap:48px}.article-layout-wide .plain-list li{max-width:none;break-inside:avoid}} +@media(min-width:1280px){.article-layout-wide .plain-list{columns:3;column-gap:40px}} +.list-item-image{display:grid;grid-template-columns:minmax(0,1fr) 140px;gap:18px;align-items:start} +.list-item img{border-radius:var(--r-small);width:140px;height:105px;object-fit:cover} +.article-layout .list-item h2{margin:0 0 4px;font-size:24px;line-height:1.2} +.list-item h2 a{color:var(--link);text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1.5px} +.list-item h2 a:hover{color:var(--ink)} +.list-meta{margin:0 0 4px;max-width:32em;font-size:15px;color:var(--soft)} +.list-meta a{color:inherit;text-decoration:underline} +.list-meta:empty{display:none} +.list-summary{margin:0;font-size:17px;line-height:1.45} +.pager{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:26px 0 0;font-weight:600;font-size:16px} +.pager a,.pager-current{display:inline-block;min-width:36px;padding:4px 10px;border-radius:999px;text-align:center;color:var(--link)} +.pager a:hover{background:var(--surface);color:var(--ink)} +.pager-current{background:var(--ink);color:#fff} +.ac-filters{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 22px} +.ac-chip{padding:5px 14px;background:#fff;border:1.5px solid var(--soft);border-radius:999px;color:var(--ink);font:600 15px/1.3 var(--text)} +.ac-chip:hover{border-color:var(--ink)} +.ac-chip.active{background:var(--ink);border-color:var(--ink);color:#fff} +.announcements-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:18px} +.announcement-card{position:relative;display:flex;flex-direction:column;height:100%;background:var(--surface);border-radius:var(--r-large);overflow:hidden} +.announcement-card:hover h2 a{color:var(--ink)} +.ac-thumb img{display:block;width:100%;height:auto;aspect-ratio:16/9;object-fit:cover} +.ac-body{padding:14px 18px 18px;font-size:16px;line-height:1.45} +.ac-meta{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:6px;font-size:15px;font-weight:600} +.ac-cat{padding:2px 8px;background:#fff;border-radius:var(--r-small)} +.ac-body h2{margin:0 0 6px;font-size:21px;line-height:1.2} +.ac-link{color:var(--link);text-decoration:underline;text-underline-offset:3px} +.ac-link::after{content:"";position:absolute;inset:0} +.ac-summary{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:4;overflow:hidden} +.ac-summary p{margin:0} +.ac-summary a{color:inherit} +.tags-note{max-width:40em;font-size:17px} +.tags-note a{color:var(--link);text-decoration:underline;text-underline-offset:3px} +.tag-cloud{display:flex;flex-wrap:wrap;gap:6px;margin:18px 0 0} +.tag-cloud a{padding:3px 10px;background:var(--surface);border-radius:var(--r-small);font-size:15px;font-weight:600;line-height:1.35;color:var(--ink)} +.tag-cloud a:hover{background:var(--ink);color:#fff} +.tag-count{margin-inline-start:4px;font-weight:400} +#tag-results{margin:18px 0;max-width:40em} +#tag-results h2{font-size:26px} +.nla-page{padding:32px 0 40px} +.nla-subscribe-band{max-width:40em;margin:0 0 26px;padding:18px 20px;background:var(--surface);border-radius:var(--r-large)} +.nla-subscribe-label{margin:0 0 10px} +.nla-archive-header{display:flex;flex-wrap:wrap;align-items:center;gap:14px;margin:0 0 18px} +.nla-archive-title{margin:0;font-size:30px} +.nla-year-tabs{display:flex;flex-wrap:wrap;gap:6px} +.nla-year-tab{padding:4px 12px;background:#fff;border:1.5px solid var(--soft);border-radius:999px;color:var(--ink);font:600 15px/1.3 var(--text)} +.nla-year-tab.active{background:var(--ink);border-color:var(--ink);color:#fff} +.nla-year-band{display:flex;align-items:center;gap:10px;margin:22px 0 12px;font:var(--display-weight) 24px/1.2 var(--display)} +.nla-year-band-line{display:none} +.nla-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px} +.nla-card{display:block;padding:12px 14px;background:var(--surface);border-radius:var(--r-small);color:var(--ink);text-decoration:none} +.nla-card:hover{background:var(--ink);color:#fff} +.nla-card-accent{display:none} +.nla-card-month{font-weight:700} +.nla-card-year,.nla-card-footer{font-size:15px} +.nla-badge-new{display:inline-block;margin-bottom:4px;padding:1px 7px;background:var(--res-bg);border-radius:var(--r-small);color:var(--ink);font-size:15px;font-weight:700} +@media(max-width:760px){ + .news-page,.tags-page,.nla-page{padding:20px 0 28px} + .list-item-image{grid-template-columns:minmax(0,1fr) 88px;gap:12px} + .list-item img{width:88px;height:66px} + .article-layout .list-item h2{font-size:21px} +} +.list-item .btn-links{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px} +.tag-more{flex-basis:100%;margin-top:10px} +.tag-more>summary{font-weight:700;color:var(--link);cursor:pointer} +.tag-more .tag-cloud{margin-top:10px} +.tag-section{display:block;font-size:15px;color:var(--soft)} +#tag-results li{padding:8px 0;font-size:18px} +.tag-show-more{margin:14px 0} +/* Thumbnails are often logos or figures, so they are shown whole on a white panel, not cropped. On phones the + thumbnail sits above the entry and the text takes the full width. */ +.list-item-image img{object-fit:contain;background:#fff;border:1px solid var(--line);padding:4px} +@media(max-width:760px){ + .list-item-image{display:flex;flex-direction:column-reverse;align-items:flex-start;gap:12px} + .list-item-image img{width:120px;height:72px} +} +.cluster-resources{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,340px),1fr));gap:14px 36px;margin:18px 0 0;padding:0;list-style:none} +.cluster-resources li{margin:0;padding:0 0 12px;border-bottom:1px solid var(--line);font-size:17px;line-height:1.35} +.cluster-resources a{color:var(--link);text-decoration:none;font-weight:600} +.cluster-resources a:hover{text-decoration:underline;text-underline-offset:3px} +.cluster-resources span{display:block;margin-top:4px;color:var(--soft);font-size:15px} +.tag-find{max-width:520px;margin:20px 0 6px} +.tag-find label{display:block;margin:0 0 6px;font:600 17px/1.3 var(--text)} +.tag-find input{width:100%;box-sizing:border-box;padding:10px 16px;border:1.5px solid var(--ink);border-radius:999px;background:#fff;font:400 17px/1.3 var(--text);color:var(--ink)} +.tag-filter-count{margin:8px 0 0;font-size:16px;color:var(--soft)} +.tag-cloud a[hidden]{display:none} +/* Tags: the most used as larger pills, then A to Z in columns. */ +.tag-top h2,.tag-all h2{margin:28px 0 10px;font-size:24px} +.tag-top .tag-cloud a{padding:6px 14px;font-size:17px} +.tag-letters{display:flex;flex-wrap:wrap;gap:4px;margin:0 0 8px;position:sticky;top:0;z-index:5;padding:8px 0;background:var(--page)} +.tag-letters a{display:inline-flex;align-items:center;justify-content:center;min-width:34px;height:34px;border-radius:var(--r-small);background:var(--surface);color:var(--ink);font:600 15px/1 var(--text);text-decoration:none} +.tag-group{scroll-margin-top:56px} +/* On a phone the sticky letters wrap to three or four rows, so a jump lands below them. */ +@media(max-width:760px){.tag-group{scroll-margin-top:140px}} +@media(max-width:360px){.tag-group{scroll-margin-top:180px}} +.tag-group h3{margin:20px 0 6px;font-size:22px;color:var(--edu)} +.tag-group ul{columns:4 220px;column-gap:28px;margin:0;padding:0;list-style:none} +.tag-group li{break-inside:avoid;margin:0;padding:3px 0} +.tag-group a{color:var(--link);font-size:16px;text-decoration:none} +.tag-group a:hover{text-decoration:underline;text-underline-offset:3px} +.tag-group .tag-count{color:var(--soft);font-size:15px} +.tag-group[hidden],.tag-group li[hidden]{display:none} +.tag-group.is-short li:not(.is-top){display:none} +.tag-all.is-searching .tag-group.is-short li:not([hidden]){display:list-item} +.tag-all.is-searching .tag-group-more{display:none} +.tag-group-more{margin:6px 0 0;padding:6px 14px;border:1.5px solid var(--ink);border-radius:999px;background:#fff;color:var(--ink);font:600 15px/1.3 var(--text);cursor:pointer} +.tag-letters{border-bottom:1px solid var(--line)} +/* Tags carried by one page only are lighter, so the ones in common use stand out. */ +/* A tag's page count as a small badge beside it, not another word of the tag. */ +.tag-group .tag-count,.tag-top .tag-count{display:inline-block;margin-inline-start:4px;padding:0 7px;border-radius:999px;background:var(--surface);color:var(--soft);font-size:15px;line-height:1.5} +.tag-top .tag-cloud a .tag-count{background:#fff} +/* A to Z as rows: the tag on the left, its count aligned on the right, a light rule between rows. */ +.tag-group ul{columns:3 240px} +@media(max-width:760px){.tag-group ul{columns:2 140px}} +.tag-group li{padding:0;border-bottom:1px solid var(--surface)} +.tag-group li a{display:flex;justify-content:space-between;align-items:baseline;gap:10px;padding:5px 2px} +.tag-group li .tag-count{flex:none} +.tag-top .tag-cloud a{display:inline-flex;align-items:center} diff --git a/assets/css/doors/newsletter.css b/assets/css/doors/newsletter.css new file mode 100644 index 00000000000..959ee0df14c --- /dev/null +++ b/assets/css/doors/newsletter.css @@ -0,0 +1,21 @@ +/* The newsletter sign-up form (layouts/partials/doors/newsletter.html). The note and status carry + element selectors to outweigh article.css, which loads later and resets paragraph type. */ +.newsletter-form{max-width:520px} +.newsletter-row{display:flex;flex-wrap:wrap;gap:8px} +.newsletter-field{flex:999 1 220px;margin:0} +.newsletter-field input{width:100%;padding:10px 16px;border:1.5px solid var(--soft);border-radius:999px;background:#fff;color:var(--ink);font:16px/1.3 var(--text)} +.newsletter-field input::placeholder{color:var(--soft);opacity:1} +.newsletter-field input:focus{border-color:var(--ink);outline:3px solid var(--ink);outline-offset:0} +.newsletter-submit{flex:1 0 auto;padding:11px 22px;border:0;border-radius:999px;background:var(--link);color:#fff;cursor:pointer;font:700 16px/1.3 var(--text)} +.newsletter-submit:hover{background:var(--ink)} +.newsletter-submit:focus-visible{outline:3px solid var(--ink);outline-offset:2px} +.newsletter-submit:disabled{background:var(--soft);cursor:progress} +.newsletter-hp{position:absolute;left:-5000px} +form.newsletter-form p.newsletter-status{margin:10px 0 0;font-size:16px;font-weight:600} +.newsletter-status:empty{display:none} +.newsletter-status.is-error{color:#a3271f} +form.newsletter-form p.newsletter-note{margin:8px 0 0;color:var(--soft);font-size:15px;line-height:1.4} +.newsletter-note a{color:inherit;text-decoration:underline} +.newsletter-form.is-done .newsletter-row,.newsletter-form.is-done .newsletter-note{display:none} +.newsletter-form.is-done .newsletter-status{margin-top:0} +.newsletter-status:focus:not(:focus-visible){outline:none} diff --git a/assets/css/doors/page.css b/assets/css/doors/page.css new file mode 100644 index 00000000000..490c686ae50 --- /dev/null +++ b/assets/css/doors/page.css @@ -0,0 +1,16 @@ +/* What the designed inner pages share: the heading block and its arched plate. */ +.page-heading{padding:32px 0 36px} +.page-heading .crumbs{margin-bottom:26px} +.heading-grid{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:48px;align-items:center} +.heading-grid>*{min-width:0} +.page-heading h1{font-size:clamp(42px,5vw,72px);line-height:1.02;letter-spacing:-.012em;margin:0;text-wrap:balance} +.page-heading .lede{font-size:clamp(18px,1.45vw,20px);line-height:1.55;margin:20px 0 0;max-width:32em;color:var(--ink)} +.heading-plate{margin:0;justify-self:end;width:100%;max-width:300px;background:var(--edu-bg);border-radius:999px 999px 0 0;padding:34px 24px 10px} +.heading-plate img{display:block;width:100%;height:auto;max-height:190px;object-fit:contain} +@media(max-width:760px){ + .page-heading{padding:20px 0 24px} + .page-heading .crumbs{margin-bottom:18px} + .heading-grid{grid-template-columns:1fr;gap:18px} + .heading-plate{display:none} + .page-heading h1{font-size:clamp(38px,11vw,48px)} +} diff --git a/assets/css/doors/popover.css b/assets/css/doors/popover.css new file mode 100644 index 00000000000..90b3365d159 --- /dev/null +++ b/assets/css/doors/popover.css @@ -0,0 +1,12 @@ +/* Shared by citation and term popovers. Below the header, cookie banner and chat. */ +.doors-popover{position:fixed;z-index:800;box-sizing:border-box;width:min(28rem,calc(100vw - 32px));max-height:calc(100vh - 32px);overflow:auto;background:#fff;border:1px solid var(--line);border-radius:var(--r-small);box-shadow:0 8px 24px rgba(38,45,53,.18);padding:14px 36px 14px 16px;color:var(--ink);font:400 .95rem/1.5 var(--text)} +.doors-popover{animation:popover-in 120ms ease} +.doors-popover-close{position:absolute;top:2px;right:2px;width:32px;height:32px;padding:0;background:none;border:0;color:var(--soft);font:inherit;font-size:1.3rem;cursor:pointer} +.doors-popover-close:hover,.doors-popover-close:focus{color:var(--ink)} +.popover-actions{display:flex;flex-wrap:wrap;gap:16px;margin-top:10px;font-size:.9rem} +.popover-actions a,.popover-actions button{background:none;border:0;padding:0;color:var(--link);text-decoration:underline;font:inherit;cursor:pointer} +.doors-popover :is(em,i){font-style:italic!important;font-weight:400} +@keyframes popover-in{from{opacity:0}to{opacity:1}} +@media(max-width:599px){.doors-popover{width:calc(100vw - 32px)}} +@media(prefers-reduced-motion:reduce){.doors-popover{animation:none!important}} +@media print{.doors-popover{display:none!important}} diff --git a/assets/css/doors/reset.css b/assets/css/doors/reset.css new file mode 100644 index 00000000000..ce3b240d469 --- /dev/null +++ b/assets/css/doors/reset.css @@ -0,0 +1,24 @@ +/* What pages need from a browser reset, in place of Bootstrap's reboot. */ +*,*::before,*::after{box-sizing:border-box} +/* Older Markdown and shortcodes size text in rem, written against the theme's 20 px root. */ +html{-webkit-text-size-adjust:100%;font-size:125%} +body{margin:0} +h1,h2,h3,h4,h5,h6,p,ul,ol,dl,figure,blockquote,pre{margin-top:0} +h1,h2,h3,h4,h5,h6{margin-bottom:.5em;line-height:1.2} +p,ul,ol,dl,pre{margin-bottom:1em} +ul ul,ol ul,ul ol,ol ol{margin-bottom:0} +dt{font-weight:700} +dd{margin:0 0 .5em} +a{text-decoration:none;background:transparent} +b,strong{font-weight:700} +img,svg,video,canvas{vertical-align:middle} +img,video{max-width:100%;height:auto} +button,input,select,textarea{font:inherit;color:inherit;margin:0} +button,select{text-transform:none} +button:not(:disabled),[type=submit]:not(:disabled),summary{cursor:pointer} +table{border-collapse:collapse} +th{text-align:inherit} +hr{border:0;border-top:1px solid var(--line);margin:2em 0} +[hidden]{display:none!important} +code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.9em} +.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap} diff --git a/assets/css/doors/resources.css b/assets/css/doors/resources.css new file mode 100644 index 00000000000..bda14dab0a2 --- /dev/null +++ b/assets/css/doors/resources.css @@ -0,0 +1,105 @@ +/* The resources directory. Loaded only on /resources/ and its pages, so the short class names + from the mock cannot reach anything else. */ +.directory{display:grid;grid-template-columns:minmax(0,300px) minmax(0,1fr);gap:48px;align-items:start} +.filters{background:var(--surface);border-radius:var(--r-large);padding:22px 20px;min-width:0} +.filter-drawer>summary{list-style:none;font-weight:700;line-height:1.35} +.filter-drawer>summary::-webkit-details-marker{display:none} +.filter-badge{font-weight:600} +.facet{border:0;margin:18px 0 0;padding:0;min-width:0} +.filter-title{padding:0;font:var(--display-weight) 20px/1.2 var(--display);margin:0 0 10px;color:var(--ink)} +.chips{display:flex;flex-wrap:wrap;gap:7px} +.chip{display:inline-flex;align-items:baseline;gap:7px;background:#fff;border:1.5px solid var(--line);border-radius:999px;padding:7px 12px;font:400 15px/1.3 var(--text);color:var(--ink);text-align:start;max-width:100%;cursor:pointer} +.chip:hover{border-color:var(--ink)} +.chip[aria-pressed=true]{background:var(--ink);border-color:var(--ink);color:#fff} +.chip-count{color:var(--soft);font-size:15px;font-weight:600} +.chip[aria-pressed=true] .chip-count{color:inherit} +.filter-note{margin:12px 0 0;font-size:16px} +.catalogue{min-width:0} +.search-label{display:block;font:var(--display-weight) 22px/1.2 var(--display);margin:0 0 10px} +.resource-search{width:100%;background:#fff;border:1.5px solid var(--soft);border-radius:var(--r-small);padding:12px 14px;font:400 17px/1.3 var(--text);color:var(--ink)} +.resource-search::placeholder{color:var(--soft);opacity:1} +.result-bar{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 20px;margin-top:16px} +.result-count{font-weight:600;margin:0} +.text-button{background:none;border:0;padding:0;font:700 var(--text-size)/1.4 var(--text);color:var(--link);text-decoration:underline;text-underline-offset:3px;cursor:pointer} +.active-filters{margin:4px 0 0;font-size:16px} +.res-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin-top:20px} +.res{display:flex;flex-direction:column;gap:8px;padding:18px;background:var(--surface);border-radius:var(--r-large);min-width:0} +.res-format{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin:0;font-size:15px} +.res-type{background:var(--edu);color:#fff;font-weight:700;padding:3px 9px;border-radius:var(--r-small);line-height:1.3} +.res-level{color:var(--ink)} +.res-title{font-size:21px;line-height:1.25;margin:0;overflow-wrap:anywhere} +.res-title a{color:inherit;text-decoration:none} +.res-title a:hover{color:var(--link);text-decoration:underline;text-underline-offset:3px} +.res-tag{margin:auto 0 0;font-size:15px;line-height:1.4} +.res-tag:empty{display:none} +.empty{margin-top:20px;padding:24px;background:var(--surface);border-radius:var(--r-large)} +.empty h2{font-size:26px;margin:0} +.empty p{margin:8px 0 0;font-size:16px} +.show-more{display:block;margin:20px auto 0;background:#fff;border:1.5px solid var(--ink);border-radius:999px;padding:9px 18px;font:600 16px/1.3 var(--text);color:var(--ink);cursor:pointer} +.show-more:hover{background:var(--ink);color:#fff} +.pager{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:6px;margin-top:28px;font-size:16px} +.pager a,.pager-current{display:inline-block;min-width:40px;padding:8px 10px;border-radius:999px;text-align:center;line-height:1.3} +.pager a{color:var(--link);text-decoration:underline;text-underline-offset:3px} +.pager a:hover{background:var(--surface);color:var(--ink)} +.pager-current{background:var(--edu);color:#fff;font-weight:700} +.collection-note{margin-top:64px;max-width:44em} +.collection-note h2{font-size:clamp(28px,2.6vw,36px);margin:0 0 10px} +.collection-note p{margin:0 0 12px} +.collection-about p{font-size:16px;line-height:1.55;margin-top:16px} +.collection-about a{color:var(--link);text-decoration:underline;text-underline-offset:3px} + +@media(max-width:1000px){ + .directory{grid-template-columns:minmax(0,250px) minmax(0,1fr);gap:28px} + .res-grid{grid-template-columns:1fr} +} +@media(max-width:760px){ + .directory{grid-template-columns:1fr;gap:18px} + .filters{padding:14px 16px} + .filter-drawer>summary{display:flex;justify-content:space-between;gap:10px;cursor:pointer} + .filter-drawer>summary::after{content:'';flex:none;width:8px;height:8px;margin:7px 3px 0;border-inline-end:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg)} + .filter-drawer[open]>summary::after{transform:rotate(-135deg)} + .collection-note{margin-top:44px} +} +/* A card's title reads as a link, and the whole card answers to it. */ +.res{position:relative;transition:background-color .15s} +.res-title a{color:var(--link)} +.res-title a::after{content:"";position:absolute;inset:0;border-radius:inherit} +.res:hover{background:var(--res-bg)} +.res:has(.res-title a:focus-visible){outline:2px solid var(--link);outline-offset:2px} +/* Cards lead with the title; type and level follow as one quiet line, then the cluster. Each card is as tall as it needs. */ +.res-grid{align-items:start} +.res-title{order:-1} +.res-format{display:block;color:var(--soft);line-height:1.45} +.res-type{background:none;color:var(--ink);font-weight:600;padding:0;border-radius:0} +.res-type:has(+ .res-level:not(:empty))::after{content:" ·";font-weight:400;color:var(--soft)} +.res-level{color:var(--soft)} +.res-tag{margin:0;color:var(--soft)} +.res-level:empty{display:none} +/* Active filters as chips; each one's cross removes it. */ +.active-filters{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 0} +.active-chip{display:inline-flex;align-items:center;gap:6px;padding:5px 12px;border:1.5px solid var(--ink);border-radius:999px;background:#fff;color:var(--ink);font:600 15px/1.3 var(--text);cursor:pointer} +.active-chip::after{content:"×";font-size:18px;line-height:1} +.active-chip:hover{background:var(--ink);color:#fff} +/* Results as full-width rows: title, then type and level, then cluster, each row as tall as it needs. */ +.res-grid{grid-template-columns:minmax(0,1fr);gap:0;border-top:1px solid var(--line)} +.res{padding:14px 12px;background:none;border-radius:0;border-bottom:1px solid var(--line);gap:4px} +.res:hover{background:var(--surface)} +.res-title{font-size:20px} +.pager-mini{display:flex;gap:16px;margin-inline-start:auto} +.pager-mini a{color:var(--link);font-weight:600;text-decoration:underline;text-underline-offset:3px} +.pager-mini[hidden]{display:none} +.result-bar .pager-top{margin:0 0 0 auto} +.pager-top[hidden]{display:none} +.collection-about>summary{cursor:pointer;color:var(--link);font-weight:600;text-decoration:underline;text-underline-offset:3px;margin:8px 0} +/* Sort order and page size: two menus in the search box's style, beside it; on a phone, below it, side by side. + The search keeps at least 18em, so with enlarged text or a narrow column the menus wrap below it. */ +.search-row{display:flex;flex-wrap:wrap;gap:10px} +.search-row .resource-search{flex:1 1 18em;width:auto;min-width:0} +.search-option{flex:none;padding:0 12px;background:#fff;border:1.5px solid var(--soft);border-radius:var(--r-small);font:400 16px/1.3 var(--text);color:var(--ink);cursor:pointer} +.search-option:disabled{cursor:default;color:var(--soft)} +@media(max-width:760px){ + .search-row{gap:8px} + .search-row .resource-search{flex-basis:100%} + .search-option{flex:1 1 0;min-width:0;padding:8px 10px;font-size:15px} +} +.pager[hidden]{display:none} diff --git a/assets/css/doors/search.css b/assets/css/doors/search.css new file mode 100644 index 00000000000..9db15aad628 --- /dev/null +++ b/assets/css/doors/search.css @@ -0,0 +1,18 @@ +/* Site search, a dialog over the page. Results stay one readable column at any width. */ +.search-dialog{width:min(760px,calc(100% - 32px));max-width:none;max-height:calc(100dvh - 48px);margin:24px auto auto;padding:0;border:0;border-radius:var(--r-large);background:var(--page);color:var(--ink);box-shadow:0 18px 60px rgba(38,45,53,.35);overflow:hidden} +.search-dialog[open]{display:flex;flex-direction:column} +.search-dialog::backdrop{background:rgba(38,45,53,.55)} +.search-top{display:flex;align-items:center;gap:12px;padding:18px 20px 12px} +.search-top label{flex:1;margin:0;min-width:0} +.search-top input{width:100%;padding:10px 16px;background:#fff;border:1.5px solid var(--soft);border-radius:999px;font:400 18px/1.3 var(--text);color:var(--ink)} +.search-close{flex:none;padding:9px 14px;background:transparent;border:1.5px solid var(--ink);border-radius:999px;font:600 15px/1.3 var(--text);color:var(--ink)} +.search-close:hover{background:var(--ink);color:#fff} +.search-status{margin:0;padding:0 20px 10px;font-size:15px;color:var(--soft)} +.search-hits{flex:1;min-height:0;overflow-y:auto;margin:0;padding:0 20px 20px;list-style:none} +.search-hits li{padding:12px 0;border-top:1px solid var(--line)} +.search-hits a{font:var(--display-weight) 21px/1.25 var(--display);color:var(--link);text-decoration:underline;text-underline-offset:3px} +.search-hits a:hover{color:var(--ink)} +.search-hits .hit-type{display:block;margin-bottom:2px;font-size:15px;font-weight:600;color:var(--soft)} +.search-hits p{display:-webkit-box;margin:4px 0 0;overflow:hidden;font-size:16px;line-height:1.45;-webkit-box-orient:vertical;-webkit-line-clamp:3} +.search-hits mark{background:var(--com-bg);color:inherit} +@media(max-width:760px){.search-dialog{width:calc(100% - 16px);margin-top:8px;max-height:calc(100dvh - 16px)}.search-top{padding:12px 12px 8px}.search-status{padding:0 12px 8px}.search-hits{padding:0 12px 12px}} diff --git a/assets/css/doors/sections.css b/assets/css/doors/sections.css new file mode 100644 index 00000000000..0f38fadc8e9 --- /dev/null +++ b/assets/css/doors/sections.css @@ -0,0 +1,187 @@ +/* What the generic section page adds to the reading layout in article.css. */ +.article-layout .doc-section{margin:0 0 36px} +.article-layout .doc-section:last-child{margin-bottom:0} +.article-layout .doc-section>h2:first-child{margin-top:0} +.article-layout .section-lede{font:400 19px/1.5 var(--text);color:var(--ink)} +.article-layout .doc-section-tinted{padding:22px 24px;background:var(--surface);border-radius:var(--r-large)} +.article-layout .doc-section-tinted:nth-of-type(4n+1){background:var(--edu-bg)} +.article-layout .doc-section-tinted:nth-of-type(4n+2){background:var(--res-bg)} +.article-layout .doc-section-tinted:nth-of-type(4n+3){background:var(--jus-bg)} +.article-layout .doc-section-tinted:nth-of-type(4n){background:var(--com-bg)} +.article-layout .doc-section-tinted iframe{display:block;width:100%} +.feature-rows{display:grid;gap:22px;margin:24px 0;padding:0;list-style:none} +.article-layout .article-style .feature-rows li{display:grid;grid-template-columns:96px minmax(0,1fr);gap:18px;align-items:start;margin:0;font-size:18px;line-height:1.5} +.feature-rows img{width:96px;height:96px;object-fit:contain;padding:12px;background:var(--edu-bg);border-radius:50%} +.article-layout .article-style .feature-rows h3{margin:0 0 4px;font-size:23px} +.feature-rows p{margin:0 0 8px} +.feature-rows-small .feature-icon{width:96px;font-size:40px;text-align:center;color:var(--link)} +.feature-links{display:flex;flex-wrap:wrap;gap:8px} +.article-layout .article-style .timeline{margin:18px 0;padding:0;list-style:none;border-inline-start:3px solid var(--line)} +.article-layout .article-style .timeline li{position:relative;margin:0 0 22px;padding-inline-start:20px;font-size:18px;line-height:1.5} +.article-layout .article-style .timeline li::before{content:"";position:absolute;inset-inline-start:-8px;top:8px;width:13px;height:13px;background:var(--link);border-radius:50%} +.article-layout .article-style .timeline h3{margin:0 0 2px;font-size:22px} +.article-layout .article-style .timeline-meta{margin:0 0 6px;font-size:16px;color:var(--soft)} +.article-layout .article-style .people-wall{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:18px 16px;margin:18px 0;padding:0;list-style:none} +.article-layout .article-style .people-wall li{margin:0;font-size:15px;line-height:1.35} +.people-wall a{display:grid;grid-template-columns:48px minmax(0,1fr);align-items:center;gap:10px;color:var(--ink);text-decoration:none} +.people-wall a:hover .people-name{text-decoration:underline} +.people-wall img,.people-initials{display:flex;align-items:center;justify-content:center;width:48px;height:48px;object-fit:cover;border-radius:50%;background:var(--surface);font-weight:600} +.people-name{font-weight:600} +.people-role{display:block;margin:4px 0 0 58px;color:var(--soft)} +@media(max-width:760px){ + .article-layout .article-style .feature-rows li{grid-template-columns:64px minmax(0,1fr);gap:12px;font-size:16px} + .feature-rows img{width:64px;height:64px;padding:8px} + .feature-rows-small .feature-icon{width:64px;font-size:28px} + .article-layout .doc-section-tinted{padding:16px} +} + +/* Filters on the publications and partners pages. */ +.filter-container{margin:0 0 24px} +.filter-section-title{margin:0 0 6px;font-size:16px;font-weight:700} +.pill-bar{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 16px} +.filter-pill{padding:5px 12px;background:#fff;border:1.5px solid var(--soft);border-radius:999px;color:var(--ink);font:600 15px/1.3 var(--text)} +.filter-pill:hover{border-color:var(--ink)} +.filter-pill.active{background:var(--ink);border-color:var(--ink);color:#fff} +#no-resources-warning h4{margin-top:12px} + +/* Publications, partners and projects: a shared card. */ +.article-layout .article-style :is(.pub-section-group,.partner-section-group)>.section-heading h2{margin-top:34px} +.article-layout .article-style :is(.pub-section-group,.partner-section-group) hr{display:none} +.pub-section-description,.partner-section-description{margin:0 0 12px;font-size:18px} +.pub-list,.partner-list,.get-involved-projects{display:grid;gap:14px;margin:14px 0} +.pub-card,.partner-card,.get-involved-card{padding:18px 20px;background:var(--surface);border-radius:var(--r-large);font-size:16px;line-height:1.45} +.article-layout .article-style :is(.pub-card,.partner-card,.get-involved-card) :is(p,li){font-size:16px;line-height:1.45} +.pub-card,.pub-card-body{min-width:0;overflow-wrap:anywhere} +.pub-pill{max-width:100%;white-space:normal} +.pub-header,.partner-header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:6px;margin:0 0 8px} +.pub-tags,.partner-tags{display:flex;flex-wrap:wrap;gap:6px} +.pub-pill,.partner-pill{padding:2px 9px;background:#fff;border-radius:var(--r-small);font-size:15px;font-weight:600;color:var(--ink)} +.pub-year{font-size:15px;font-weight:700} +.article-layout .article-style :is(.pub-title,.partner-title,.get-involved-card h3){margin:0 0 6px;font-size:21px;line-height:1.2} +.pub-authors,.pub-journal{margin:0 0 6px;font-size:15px} +.journal-icon{margin-inline-end:4px} +.btn-toggle-abstract{padding:0;background:none;border:0;color:var(--link);font:600 15px/1.4 var(--text);text-decoration:underline;text-underline-offset:3px} +.btn-toggle-abstract i{transition:transform .15s} +.btn-toggle-abstract.active i{transform:rotate(90deg)} +.publication-card-abstract{display:none;margin:8px 0} +.publication-card-abstract.show{display:block} +.pub-citation-section{margin:10px 0;font-size:15px} +.pub-footer,.partner-footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px;margin-top:10px} +.pub-buttons,.partner-buttons{display:flex;flex-wrap:wrap;gap:6px} +.pub-metrics{display:flex;gap:10px} +.pub-metric-label{display:block;font-size:15px} +.partner-content{display:grid;grid-template-columns:96px minmax(0,1fr);gap:16px} +.partner-logo{display:block;width:96px;height:auto;max-height:72px;object-fit:contain} +.partner-description{font-size:16px} +.get-involved-card>:last-child{display:flex;flex-wrap:wrap;gap:6px;align-items:center} + +/* Directories hold cards and lists rather than running text, so a wide screen takes two or more columns. */ +.article-layout-wide :is(.pub-list,.partner-list){grid-template-columns:repeat(auto-fill,minmax(min(100%,420px),1fr));gap:16px} +.article-layout-wide .article-style .feature-rows{grid-template-columns:repeat(auto-fill,minmax(min(100%,420px),1fr));gap:28px 40px} +.article-layout-wide .article-style .feature-rows li{max-width:none} +.article-layout-wide .pub-card,.article-layout-wide .pub-card-body{display:flex;flex-direction:column} +.article-layout-wide .pub-card-body{flex:1} +.article-layout-wide .pub-footer{margin-top:auto;padding-top:10px} +@media(min-width:1001px){.article-layout-wide #contributor-list{columns:3;column-gap:40px}} +@media(min-width:761px) and (max-width:1000px){.article-layout-wide #contributor-list{columns:2;column-gap:32px}} +.article-layout-wide .contributor-group{max-width:none;break-inside:avoid} +.article-layout-wide .article-style center{text-align:start} + +/* The citation chart supports the introduction: beside it on a wide screen, after it on a narrow one. */ +.pub-intro{display:flex;flex-direction:column;margin-bottom:28px} +.article-layout .article-style .citation-container{order:1;width:min(100%,320px);margin:6px 0 10px} +.citation-container img{display:block;width:100%;height:auto} +.citation-container figcaption{margin-top:6px;text-align:start} +@media(min-width:761px){ + .pub-intro{display:grid;grid-template-columns:minmax(0,550px) 240px;justify-content:space-between;gap:48px;align-items:start} + .article-layout .article-style .pub-intro p{max-width:none;font-size:18px;line-height:1.55} + .article-layout .article-style .citation-container{grid-column:2;grid-row:1;width:auto;margin:0} + .pub-intro>:not(figure){grid-column:1} +} +@media(min-width:761px) and (max-width:1000px){.pub-intro{grid-template-columns:minmax(0,1fr) 260px;gap:32px}} + +.reading:has(.pub-intro) .article-head{margin-bottom:14px} +.reading:has(.pub-intro) .doc-section>hr:first-child{margin:0 0 20px} + +/* Get Involved: its project cards take the reading column and the call-out column together. */ +@media(min-width:1240px){ + .article-layout-callouts .article-style .get-involved-projects{clear:both;width:calc(100% + 300px);grid-template-columns:repeat(2,minmax(0,1fr))} +} + +/* PDFs and the booklet are for looking at, so they take the width; their introductions do not. */ +.pdf-open{margin:0 0 12px} +/* The flipbook fits a spread to its frame's width, so a frame wider than a spread clips the pages. */ +@media(min-width:761px){.article-style ._df_book{width:min(100%,calc(80vh*1.35));margin-inline:auto!important}} +.pdf-viewer{display:block;width:100%;max-width:960px;border:1px solid var(--line);border-radius:var(--r-small)} + +/* The chatbot's page is a tool: on a wide screen its input is in view beside the introduction. */ +@media(min-width:1001px){ + .article-layout-wide .doc-section:has(>#just-os-fullpage){display:grid;grid-template-columns:minmax(0,560px) minmax(0,1fr);column-gap:48px;align-items:start} + .article-layout-wide .doc-section:has(>#just-os-fullpage)>*{grid-column:1} + .article-layout-wide .doc-section>#just-os-fullpage{grid-column:2;grid-row:1/span 30;height:min(calc(100vh - 260px),640px)!important;min-height:420px;margin:0!important} + .article-layout-wide .doc-section:has(>#just-os-fullpage)>br{display:none} + /* The team runs across the page below the chat. */ + .article-layout-wide .doc-section:has(>#just-os-fullpage)>h2:last-of-type{grid-column:1/-1;grid-row:31} + .article-layout-wide .doc-section:has(>#just-os-fullpage)>.jos-team{grid-column:1/-1;grid-row:32} +} +@media(min-width:1001px) and (max-width:1279px){.article-layout-wide .doc-section:has(>#just-os-fullpage){grid-template-columns:repeat(2,minmax(0,1fr));column-gap:32px}} + +/* Contributors: its generated page carries an older inline style, so these rules name the same ids. */ +.article-layout #filter-menu,.article-layout #filter-collapsed{padding:16px 18px;margin:0 0 22px;background:var(--surface);border:0;border-radius:var(--r-large)} +.article-layout #filter-menu h3{margin:0 0 10px;color:var(--ink);font-size:22px;font-weight:var(--display-weight)} +.article-layout .filter-field label{font-size:15px} +.article-layout .filter-field select{width:100%;padding:7px 10px;background:#fff;border:1.5px solid var(--soft);border-radius:var(--r-small);font-size:16px} +.article-layout #apply-filter,.article-layout #clear-filters{padding:8px 16px;background:var(--link);border-radius:999px;color:#fff;font:600 15px/1.3 var(--text)} +.article-layout #apply-filter:hover,.article-layout #clear-filters:hover{background:var(--ink)} +.article-layout .filter-title{border-bottom:1px solid var(--line)} +.article-layout #filter-collapsed a{color:var(--link)} +.article-layout .contribution{font-size:17px;line-height:1.45} + +/* Testimonials: portraits float beside the text on a wide screen and sit above it on a phone. + Their inline size would leave the text a strip about 200 px wide. */ +.article-layout .doc-section-tinted [style*="float"]>div[style*="320px"]{width:180px!important;height:180px!important;margin-inline:auto} +@media(max-width:760px){ + .article-layout .doc-section-tinted img[style*="float"],.article-layout .doc-section-tinted [style*="float"]{float:none!important;display:block;margin:0 auto 12px!important} + .pub-card,.partner-card,.get-involved-card{padding:14px} + .partner-content{grid-template-columns:64px minmax(0,1fr);gap:12px} + .partner-logo{width:64px} +} +.article-layout .article-style center p:not(:has(img)){text-align:start} +/* The JUST-OS team: the site's people cards, a pale panel with a round portrait, name and role. */ +.article-layout .article-style .jos-team{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,230px),1fr));gap:16px;margin:16px 0 0;padding:0;list-style:none} +.article-layout .article-style .jos-team li{margin:0;padding:18px;background:var(--surface);border-radius:var(--r-large)} +.article-layout .article-style .jos-team img{display:block;width:96px;height:96px;border-radius:50%;object-fit:cover;margin:0 0 12px} +.article-layout .article-style .jos-team h3{margin:0 0 6px;font-size:20px} +.article-layout .article-style .jos-team p{margin:0;font-size:16px;line-height:1.45;color:var(--soft)} +/* "Cite this" reads like "Show Abstract" above it, and opens the full reference. */ +.pub-citation-section{margin:4px 0 10px} +.pub-citation-section>summary{display:inline-block;cursor:pointer;color:var(--link);font:600 15px/1.4 var(--text);text-decoration:underline;text-underline-offset:3px} +.pub-citation-section>summary::-webkit-details-marker{display:none} +.pub-citation-section>summary i{transition:transform .15s} +.pub-citation-section[open]>summary i{transform:rotate(90deg)} +.pub-citation-section .citation-text{margin-top:8px;padding:10px 12px;background:#fff;border-radius:var(--r-small)} +#just-os-fullpage{margin-bottom:24px} +/* On a phone the empty chat takes about half the screen, so its question box is in view at once; it is the site's card, not a shadowed box. */ +#just-os-fullpage{border-color:var(--line)!important;border-radius:var(--r-large)!important;box-shadow:none!important} +@media(max-width:760px){#just-os-fullpage{height:min(60vh,520px)!important;min-height:380px}} +@media(max-width:760px){.article-layout .article-style .citation-container{width:min(100%,220px)}} +/* The chat page on a phone opens with the chat: the introduction and credits follow it. */ +#just-os-fullpage{margin-bottom:24px!important} +@media(max-width:1000px){ + .doc-section:has(>#just-os-fullpage){display:flex;flex-direction:column} + .doc-section>#just-os-fullpage{order:-1;margin-top:4px!important} +} +@media(max-width:760px){#just-os-fullpage{height:min(52vh,460px)!important;min-height:340px}} +/* Project cards: their actions as the site's outline pills, one size. */ +.get-involved-card .btn{display:inline-flex;align-items:center;gap:6px;min-height:40px;padding:8px 16px!important;border:1.5px solid var(--link)!important;border-radius:999px!important;background:#fff!important;color:var(--link)!important;font:600 15px/1.2 var(--text)!important} +.get-involved-card .btn:hover{background:var(--link)!important;color:#fff!important} +/* Project cards share one footer line of actions. */ +.get-involved-card{display:flex;flex-direction:column} +.get-involved-card .get-involved-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:auto;padding-top:12px} +/* Get Involved is a wide page: its projects fill the width, as many to a row as fit. */ +.article-layout-wide .article-style .get-involved-projects{grid-template-columns:repeat(auto-fill,minmax(min(100%,340px),1fr));max-width:none} +.article-layout-wide .article-style .action-cards{max-width:960px} +.get-involved-card[hidden]{display:none} +.get-involved-card .get-involved-link{display:inline-flex;align-items:center;min-height:40px;padding:0 6px;color:var(--link);font-weight:600;text-decoration:underline;text-underline-offset:3px} +.get-involved-card .btn.is-main{background:var(--link)!important;color:#fff!important} +.get-involved-card .btn.is-main:hover{background:var(--ink)!important;border-color:var(--ink)!important} diff --git a/assets/css/doors/shell.css b/assets/css/doors/shell.css new file mode 100644 index 00000000000..06c46e86334 --- /dev/null +++ b/assets/css/doors/shell.css @@ -0,0 +1,73 @@ +/* Doors header, mega menus and footer. */ +.site-head{position:relative;z-index:1000;background:var(--page);font-family:var(--text)} +.site-head a{color:inherit;text-decoration:none} +.head-inner{display:flex;flex-wrap:wrap;align-items:center;gap:8px 28px;min-height:84px;padding-block:8px} +.brand{flex:none} +.brand img{width:127px;height:auto;display:block} +nav.primary{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-inline-start:auto} +.nav-button,nav.primary>a{background:none;border:0;padding:10px 12px;font:600 16px/1.3 var(--text);color:var(--ink);border-radius:var(--r-small);cursor:pointer} +.nav-button::after{content:'';display:inline-block;width:7px;height:7px;margin-inline-start:8px;border-inline-end:2px solid currentColor;border-bottom:2px solid currentColor;transform:translateY(-3px) rotate(45deg)} +.nav-button[aria-expanded=true]::after{transform:translateY(1px) rotate(-135deg)} +.nav-button:hover,nav.primary>a:hover,.nav-button[aria-expanded=true]{background:var(--surface)} +nav.primary>a[aria-current]{text-decoration:underline;text-underline-offset:5px} +.search-nav{display:flex;align-items:center;margin:0;border:1.5px solid var(--soft);border-radius:999px;overflow:hidden;background:#fff} +.search-nav input{border:0;background:none;padding-block:8px;padding-inline:16px 10px;width:158px;font:400 15px/1.3 var(--text);color:var(--ink)} +.search-nav input::placeholder{color:var(--soft);opacity:1} +.search-nav input:focus{outline:none} +.search-nav:focus-within{outline:3px solid var(--ink);outline-offset:2px} +.search-nav button,.search-open{display:flex;align-items:center;border:0;background:none;padding-block:6px;padding-inline:8px 12px;color:var(--ink);cursor:pointer} +.search-open{display:none;border:1.5px solid var(--soft);border-radius:999px;padding:9px 11px;background:#fff} +.site-head .head-join{background:var(--hot);color:var(--ink);padding:11px 16px;font:700 15px/1.2 var(--text);border-radius:999px;white-space:nowrap} +.site-head .head-join:hover{background:var(--ink);color:#fff} +.menu-toggle{display:none;background:none;border:1.5px solid var(--ink);border-radius:999px;padding:9px 14px;font:700 15px/1.3 var(--text);color:var(--ink);cursor:pointer} +.mega{position:absolute;inset-inline:0;top:100%;background:var(--page);border-top:1px solid var(--line);box-shadow:0 18px 30px rgba(22,40,54,.14);padding:30px 0 34px} +.mega-inner{display:grid;grid-template-columns:repeat(var(--mega-columns,3),minmax(0,1fr));gap:36px;text-align:start} +.mega-title{font:var(--display-weight) 21px/1.12 var(--display);margin:0 0 10px;color:var(--ink)} +.site-head .mega a{display:block;padding:5px 0;font-size:16px;line-height:1.4} +.site-head .mega a:hover,.site-head .mega a[aria-current]{color:var(--link);text-decoration:underline;text-underline-offset:3px} + +.site-foot{clear:both;background:var(--foot);padding:52px 0 26px;margin-top:56px;font-family:var(--text);color:var(--ink);text-align:start} +.site-foot p{text-align:inherit} +.site-foot a{color:inherit} +.foot-top{display:grid;grid-template-columns:minmax(0,1.4fr) repeat(3,minmax(0,1fr));gap:40px} +.foot-top>div>p{margin:12px 0;font-size:16px;line-height:1.5;max-width:26em} +.foot-top h2{font-size:21px;line-height:1.12;margin:0 0 10px} +.foot-top a{display:block;padding:4px 0;font-size:16px;line-height:1.4;text-decoration:none} +.foot-top a:hover{text-decoration:underline;text-underline-offset:3px;color:var(--link)} +.foot-top .brand{padding:0} +.foot-top .foot-news{margin-top:6px;color:var(--link);text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:4px} +.socials{display:flex;flex-wrap:wrap;gap:8px 22px;margin-top:32px;font-weight:600;font-size:16px} +.socials a{text-underline-offset:3px;text-decoration:underline} +.foot-base,.foot-legal{display:flex;flex-wrap:wrap;justify-content:space-between;gap:6px 28px;margin-top:22px;font-size:15px} +.foot-legal{justify-content:flex-start;margin-top:14px} +.foot-base a,.foot-legal a{text-underline-offset:3px;text-decoration:underline} +.foot-licence img{display:inline-block;vertical-align:middle;margin-inline-end:10px} +.nav-button:focus:not(:focus-visible),.menu-toggle:focus:not(:focus-visible),.search-open:focus:not(:focus-visible){outline:none} + +@media(max-width:1240px){ + .head-inner{gap:16px} + .search-nav{display:none} + .search-open{display:flex} + .site-foot{padding-bottom:96px} + .foot-top{grid-template-columns:repeat(3,minmax(0,1fr))} + .foot-top>div:first-child{grid-column:1/-1} +} +@media(max-width:1000px){.nav-button,nav.primary>a{font-size:15px;padding:9px 8px}} +/* Below 980 px the full row of menus no longer fits beside the logo and Get involved, so the Menu button takes over. */ +@media(max-width:979px){ + .head-inner{min-height:72px;gap:8px} + .menu-toggle{display:block;margin-inline-start:auto} + .site-head .head-join{padding:10px 12px} + .search-open{padding:8px 10px} + .menu-toggle{padding:8px 12px} + nav.primary{display:none;position:absolute;inset-inline:0;top:100%;z-index:45;flex-direction:column;flex-wrap:nowrap;align-items:stretch;gap:2px;padding:10px 20px 16px;margin:0;background:var(--page);box-shadow:0 18px 30px rgba(22,40,54,.14);max-height:calc(100vh - 72px);overflow-y:auto} + nav.primary[data-open=true]{display:flex} + .nav-button,nav.primary>a{text-align:start;font-size:17px;padding:11px 8px} + .mega{position:static;box-shadow:none;border-top:0;padding:10px 14px 14px;background:var(--surface);border-radius:var(--r-small)} + .mega-inner{grid-template-columns:1fr;gap:18px;width:100%} +} +@media(max-width:760px){ + .brand img{width:96px} + .foot-top{grid-template-columns:1fr;gap:26px} + .foot-base{flex-direction:column} +} diff --git a/assets/css/doors/tokens.css b/assets/css/doors/tokens.css new file mode 100644 index 00000000000..cb6a16586a3 --- /dev/null +++ b/assets/css/doors/tokens.css @@ -0,0 +1,31 @@ +/* Doors design tokens: the one place colours and type are defined. + Values come from the FORRT Identity & Design guide unless marked otherwise. */ +:root{ + /* Guide: backgrounds, neutral elements, text */ + --ink:#262d35; + --page:#fefdf6; + --foot:#dce4ea; + --soft:#4c5e72; + --line:#bdc5ca; + /* Guide: highlights, important elements, links */ + --link:#005a87; + --hot:#ff6b00; + --edu:#2d5750; + --res:#005a87; + /* Outside the guide, chosen for the Doors mock (migration plan, Doors README) */ + --jus:#65478c; + --com:#9c4a0e; + --surface:#f0f3f5; + --edu-bg:#e3efec; + --res-bg:#e1eef6; + --jus-bg:#ebe6f4; + --com-bg:#f8eadc; + --urgent:#b3261e; + /* Type */ + --display:'Newsreader',Georgia,'Apple Symbols','Segoe UI Symbol','Noto Sans Symbols 2',serif; + --text:'Source Sans 3','Source Sans Pro',system-ui,'Apple Symbols','Segoe UI Symbol','Noto Sans Symbols 2',sans-serif; + --text-size:18px; + --display-weight:500; + --r-small:8px; + --r-large:18px; +} diff --git a/assets/css/doors/toolkit.css b/assets/css/doors/toolkit.css new file mode 100644 index 00000000000..378e1f585f0 --- /dev/null +++ b/assets/css/doors/toolkit.css @@ -0,0 +1,47 @@ +/* The Citation Politics Toolkit page (shortcode citation-politics-toolkit), on the wide reading layout. + Each stage keeps its colour from the wheel as a thin accent only: the yellow fails as text. */ +.cpt [hidden]{display:none!important} +.cpt-intro{display:grid;grid-template-columns:minmax(0,580px) minmax(0,380px);justify-content:start;gap:24px 56px;align-items:start;margin:0 0 12px} +.article-layout .article-style .cpt-lede p{margin:0 0 18px} +.cpt-stages ul{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 22px!important;padding:0!important;list-style:none} +.article-layout .article-style .cpt-stages li{margin:0;font-size:16px;line-height:1.3} +.article-layout .article-style .cpt-stages a{display:inline-flex;align-items:center;gap:8px;padding:6px 12px;background:#fff;border:1.5px solid var(--soft);border-radius:999px;color:var(--ink);font-weight:600;text-decoration:none} +.cpt-stages a::before{content:"";width:10px;height:10px;border-radius:50%;background:var(--stage,var(--soft))} +.article-layout .article-style .cpt-stages a:hover{border-color:var(--ink)} +.cpt-search label{display:block;margin:0 0 6px;font-size:16px;font-weight:700} +.cpt-search input{width:100%;max-width:440px;padding:10px 14px;background:#fff;border:1.5px solid var(--soft);border-radius:var(--r-small);font:400 18px/1.3 var(--text);color:var(--ink)} +.cpt-search input::placeholder{color:var(--soft);opacity:1} +.cpt-result{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;max-width:440px;margin:10px 0 0} +.article-layout .article-style .cpt-status{margin:0;font-size:16px;line-height:1.4;color:var(--soft)} +.article-layout .article-style .cpt-status:empty{display:none} +.cpt-result.is-empty{padding:14px 18px;background:var(--surface);border-radius:var(--r-large)} +.article-layout .article-style .cpt-result.is-empty .cpt-status{color:var(--ink)} +.cpt-wheel{margin:0!important} +.cpt-wheel a{display:block;color:var(--link);text-decoration:none} +.cpt-wheel img{display:block;width:100%;height:auto;border-radius:var(--r-small)} +.cpt-wheel-open{display:block;margin-top:8px;font-size:16px;font-weight:600;text-decoration:underline;text-underline-offset:3px} +.cpt-wheel a:hover .cpt-wheel-open{color:var(--ink)} +.cpt-clear{padding:6px 14px;background:#fff;border:1.5px solid var(--ink);border-radius:999px;font:600 16px/1.3 var(--text);color:var(--ink);cursor:pointer} +.cpt-clear:hover{background:var(--ink);color:#fff} +.cpt-group{margin:40px 0 0} +.article-layout .article-style .cpt-group>h2{display:flex;align-items:center;gap:12px;margin:0 0 4px} +.cpt-group>h2::before{content:"";flex:none;width:6px;height:.9em;border-radius:3px;background:var(--stage,var(--soft))} +.article-layout .article-style .cpt-wheel-note{margin:0 0 14px;font-size:16px;color:var(--soft)} +.cpt-items{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,360px),1fr));gap:12px;margin:14px 0 0!important;padding:0!important;list-style:none} +.article-layout .article-style .cpt-item{display:flex;flex-direction:column;align-items:flex-start;gap:4px;max-width:none;margin:0;padding:14px 18px;background:var(--surface);border-radius:var(--r-large);font-size:16px;line-height:1.45;overflow-wrap:anywhere} +.cpt-kind{padding:1px 8px;border-radius:var(--r-small);font-size:15px;font-weight:600;color:var(--ink)} +.cpt-kind-tool{background:var(--res-bg)} +.cpt-kind-practice{background:var(--edu-bg)} +.cpt-name{font:var(--display-weight) 20px/1.25 var(--display);color:var(--ink)} +a.cpt-name,a.cpt-also{color:var(--link);text-decoration:underline;text-underline-offset:3px} +a.cpt-name:hover,a.cpt-also:hover{color:var(--ink)} +.cpt-source,.cpt-note{color:var(--soft)} +.cpt-text{color:var(--ink)} +.cpt-cite{margin:44px 0 0} +.article-layout .article-style .cpt-cite h2{margin:0 0 12px} +.article-layout .article-style .cpt-cite blockquote{max-width:580px;margin:0} +.article-layout .article-style .cpt-cite blockquote p{overflow-wrap:anywhere} +@media(max-width:960px){ + .cpt-intro{grid-template-columns:minmax(0,1fr)} + .cpt-wheel{max-width:340px} +} diff --git a/assets/css/doors/welcome.css b/assets/css/doors/welcome.css new file mode 100644 index 00000000000..fefa68cbede --- /dev/null +++ b/assets/css/doors/welcome.css @@ -0,0 +1,27 @@ +/* The welcome page for new members. */ +.welcome-page{padding:32px 0 44px} +.welcome-page h1{font-size:clamp(36px,4.2vw,58px);line-height:1.06;margin:0 0 12px} +.welcome-page .crumbs{margin-bottom:18px} +.welcome-page .standfirst{font:500 clamp(19px,1.6vw,22px)/1.35 var(--display);max-width:34em;margin:0 0 14px} +.welcome-buttons{display:flex;flex-wrap:wrap;gap:8px;margin:0} +.welcome-content{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin-top:28px} +.welcome-content>*{min-width:0} +.welcome-content>:is(h2,p,ul,div:not(.card)){grid-column:1/-1} +.welcome-content h2{margin:22px 0 2px;font-size:30px} +.step-card,.resource-card{position:relative;padding:18px 20px;background:var(--surface);border-radius:var(--r-large);font-size:16px;line-height:1.45} +.step-badge{float:right;margin-inline-start:10px;width:30px;height:30px;line-height:30px;background:var(--ink);border-radius:50%;color:#fff;text-align:center;font-weight:700} +.step-title,.resource-title{margin:0 0 6px;font:var(--display-weight) 22px/1.2 var(--display)} +.step-description,.resource-description{margin:0 0 10px} +.step-button,.resource-button{font-weight:700;color:var(--link);text-decoration:underline;text-underline-offset:3px} +.step-button i,.resource-button i{margin-inline-start:4px;font-size:15px} +.resource-header{display:flex;flex-wrap:wrap;justify-content:space-between;gap:8px;margin:0 0 6px} +.resource-header>div:first-child{display:flex;align-items:center;gap:10px} +.resource-icon{display:flex;align-items:center;justify-content:center;flex:none;width:36px;height:36px;background:#fff;border-radius:50%;color:var(--link)} +.resource-title{margin:0} +.resource-count{display:inline-flex;align-items:center;gap:6px;font-size:15px;font-weight:600;color:var(--soft)} +.social-media-grid{display:flex;flex-wrap:wrap;gap:6px} +.social-btn{display:inline-flex;align-items:center;gap:6px;padding:5px 12px;background:#fff;border:1.5px solid var(--soft);border-radius:999px;color:var(--ink);font-size:15px;font-weight:600} +.social-btn:hover{border-color:var(--ink);color:var(--ink)} +.welcome-community{margin-top:32px;padding:22px 24px;background:var(--edu-bg);border-radius:var(--r-large)} +.welcome-community h2{margin:0 0 6px;font-size:30px} +@media(max-width:760px){.welcome-page{padding:20px 0 28px}.welcome-content{grid-template-columns:minmax(0,1fr)}} diff --git a/assets/css/welcome.css b/assets/css/welcome.css deleted file mode 100644 index 4a5f4181efc..00000000000 --- a/assets/css/welcome.css +++ /dev/null @@ -1,516 +0,0 @@ -/* Welcome Page Styles - Simplified */ - -* { box-sizing: border-box; } - -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - line-height: 1.6; -} - -/* Layout */ -.welcome-page { min-height: 100vh; } - -/* Hero Section */ -.hero-section { - background: linear-gradient(135deg, #3b82f6, #8b5cf6); - color: white; - padding: 4rem 0; - text-align: center; -} - -.hero-container { max-width: 1200px; margin: 0 auto; padding: 0 1rem; } -.hero-content { max-width: 800px; margin: 0 auto; } -.hero-title { font-size: 2.25rem; font-weight: bold; margin-bottom: 1.5rem; } -.hero-description { font-size: 0.9375rem; margin-bottom: 2rem; opacity: 0.9; } -.hero-buttons { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; } - -.hero-btn { - display: inline-flex; - align-items: center; - padding: 0.75rem 2rem; - border-radius: 0.5rem; - font-weight: 600; - text-decoration: none; - transition: all 0.3s ease; - border: 2px solid transparent; -} - -.hero-btn-primary { background: white; color: #3b82f6; } -.hero-btn-primary:hover { background: #f3f4f6; transform: translateY(-2px); } -.hero-btn-secondary { border-color: white; color: white; } -.hero-btn-secondary:hover { background: white; color: #3b82f6; transform: translateY(-2px); } - -/* Content Sections */ -.welcome-content h2 { - font-size: 1.5rem; - font-weight: bold; - text-align: center; - margin: 0; - padding: 3rem 1rem; - color: #1f2937; - background: white; -} - -.welcome-content h2:nth-of-type(2) { background: linear-gradient(135deg, #fef3c7, #fde68a); color: #92400e; } -.welcome-content h2:last-of-type { background: linear-gradient(135deg, #f3e8ff, #e0e7ff); color: #7c3aed; } - -.welcome-content > div { - max-width: 1000px; - margin: 1rem auto; - padding: 1rem 1rem; -} - -.welcome-content h2:first-of-type + div { - background: white; - padding: 0 1rem 2rem 1rem; - max-width: 700px; - margin: 0 auto; -} - -.welcome-content h2:nth-of-type(2) + div { - background: linear-gradient(135deg, #fef3c7, #fde68a); - padding: 0 1rem 2rem 1rem; - max-width: 1000px; - margin: 0 auto; -} - -.welcome-content h2:last-of-type + div { - background: linear-gradient(135deg, #f3e8ff, #e0e7ff); - padding: 0 1rem 2rem 1rem; - max-width: 1000px; - margin: 0 auto; -} - -/* Cards */ -.card { - background: white; - border-radius: 1rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - border: 1px solid #e5e7eb; - overflow: hidden; - transition: all 0.3s ease; - position: relative; - padding: 2rem; -} - -.card:hover { transform: translateY(-4px); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); } - -/* Step Card */ -.step-card { border-left: 4px solid #3b82f6; } -.step-badge { - position: absolute; - top: 1rem; - right: 1rem; - width: 2.5rem; - height: 2.5rem; - background: linear-gradient(135deg, #3b82f6, #8b5cf6); - color: white; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - font-size: 0.875rem; - box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4); - z-index: 10; - border: 3px solid white; -} - -.step-title { font-size: 1.125rem; font-weight: bold; color: #1f2937; margin-bottom: 1rem; } -.step-description { color: #6b7280; font-size: 0.84375rem; margin-bottom: 1.5rem; } - -.step-button { - display: inline-block !important; - align-items: center; - padding: 0.5rem 1rem; - background: linear-gradient(135deg, #3b82f6, #8b5cf6); - color: white; - font-weight: 600; - font-size: 0.65625rem; - border-radius: 0.5rem; - text-decoration: none; - transition: all 0.3s ease; - box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); - width: auto !important; - max-width: none !important; - flex: none !important; -} - -.step-button:hover { - background: linear-gradient(135deg, #2563eb, #7c3aed); - transform: scale(1.05); - box-shadow: 0 8px 20px rgba(59, 130, 246, 0.4); -} - -.step-button i { margin-left: 0.75rem; transition: transform 0.3s ease; } -.step-button:hover i { transform: translateX(0.25rem); } - -/* Resource Card */ -.resource-card { position: relative; } -.resource-card::before { - content: ''; - position: absolute; - inset: 0; - background: linear-gradient(135deg, #f0fdf4, #ecfdf5); - opacity: 0.5; - z-index: 0; -} - -/* Non-team Resource Card Special Styling - Purple/Teal Gradient */ -.resource-card:not([data-team="true"]) { - border-left: 4px solid #8b5cf6; - box-shadow: 0 4px 6px rgba(139, 92, 246, 0.1); -} - -.resource-card:not([data-team="true"])::before { - background: linear-gradient(135deg, #f3e8ff, #e0e7ff); - opacity: 0.4; -} - -.resource-card:not([data-team="true"]):hover { - box-shadow: 0 20px 40px rgba(139, 92, 246, 0.15); -} - -.resource-card:not([data-team="true"]) .resource-icon { - background: linear-gradient(135deg, #8b5cf6, #06b6d4); - box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3); -} - -.resource-card:not([data-team="true"]) .resource-button { - background: linear-gradient(135deg, #8b5cf6, #06b6d4); - box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3); -} - -.resource-card:not([data-team="true"]) .resource-button:hover { - background: linear-gradient(135deg, #7c3aed, #0891b2); - box-shadow: 0 8px 20px rgba(139, 92, 246, 0.4); -} - -/* Team Card Special Styling - Amber/Yellow Gradient */ -.resource-card[data-team="true"] { - border-left: 4px solid #f59e0b; - box-shadow: 0 4px 6px rgba(245, 158, 11, 0.1); -} - -.resource-card[data-team="true"]::before { - background: linear-gradient(135deg, #fef3c7, #fde68a); - opacity: 0.4; -} - -.resource-card[data-team="true"]:hover { - box-shadow: 0 20px 40px rgba(245, 158, 11, 0.15); -} - -.resource-content { position: relative; z-index: 1; } - -.resource-header { - display: flex; - align-items: flex-start; - justify-content: space-between; - margin-bottom: 1.5rem; -} - -.resource-icon { - width: 2.5rem; - height: 2.5rem; - border-radius: 0.75rem; - display: flex; - align-items: center; - justify-content: center; - color: white; - font-size: 1rem; - background: linear-gradient(135deg, #10b981, #059669); - box-shadow: 0 4px 12px rgba(34, 197, 94, 0.3); - margin-right: 1rem; -} - -/* Team Icon Special Styling - Amber/Yellow Gradient */ -.resource-card[data-team="true"] .resource-icon { - background: linear-gradient(135deg, #f59e0b, #d97706); - box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3); - width: 2.5rem; - height: 2.5rem; - font-size: 1rem; -} - -.resource-title { - font-size: 1.125rem; - font-weight: bold; - color: #1f2937; - margin: 0 0 0.5rem 0; -} - -.resource-title::after { - content: ''; - display: block; - width: 3rem; - height: 0.25rem; - background: linear-gradient(90deg, #10b981, #059669); - border-radius: 0.125rem; - margin-top: 0.5rem; -} - -.resource-count { - display: inline-flex; - align-items: center; - padding: 0.5rem 1rem; - background: linear-gradient(90deg, #dcfce7, #d1fae5); - color: #166534; - font-size: 0.875rem; - font-weight: bold; - border-radius: 9999px; - border: 1px solid #bbf7d0; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -.resource-count i { margin-right: 0.5rem; } - -.resource-description { color: #6b7280; font-size: 0.84375rem; margin-bottom: 2rem; } - -.resource-button { - display: inline-flex; - align-items: center; - padding: 0.5rem 1rem; - background: linear-gradient(135deg, #10b981, #059669); - color: white; - font-weight: 600; - font-size: 0.65625rem; - border-radius: 0.5rem; - text-decoration: none; - transition: all 0.3s ease; - box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3); -} - -.resource-button:hover { - background: linear-gradient(135deg, #059669, #047857); - transform: scale(1.05); - box-shadow: 0 8px 20px rgba(16, 185, 129, 0.4); -} - -/* Team Button Special Styling - Amber/Yellow Gradient */ -.resource-card[data-team="true"] .resource-button { - background: linear-gradient(135deg, #f59e0b, #d97706); - color: white; - box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3); - border: none; - padding: 0.5rem 1rem; - font-size: 0.65625rem; -} - -.resource-card[data-team="true"] .resource-button:hover { - background: linear-gradient(135deg, #d97706, #b45309); - color: white; - transform: scale(1.05); - box-shadow: 0 8px 20px rgba(245, 158, 11, 0.4); -} - -/* Social Media Buttons */ -.social-media-container { - margin-top: 1rem; -} - -.social-media-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); - gap: 0.25rem; - margin-top: 1rem; -} - -.social-btn { - display: flex; - flex-direction: column; - align-items: center; - padding: 0.375rem 0.25rem; - background: white; - border: 1px solid #e5e7eb; - border-radius: 0.25rem; - text-decoration: none; - color: #374151; - transition: all 0.2s ease; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - min-height: 45px; -} - -.social-btn:hover { - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - color: #1f2937; -} - -.social-btn i { - font-size: 0.875rem; - margin-bottom: 0.0625rem; -} - -.social-btn span { - font-weight: 600; - font-size: 0.5rem; - margin-bottom: 0.03125rem; - text-align: center; -} - -.social-btn small { - font-size: 0.375rem; - color: #6b7280; - text-align: center; - line-height: 1; -} - -/* Social Media Button Colors */ -.social-email { - border-color: #dc2626; -} - -.social-email:hover { - background: #fef2f2; - border-color: #dc2626; -} - -.social-email i { - color: #dc2626; -} - -.social-twitter { - border-color: #1da1f2; -} - -.social-twitter:hover { - background: #f0f9ff; - border-color: #1da1f2; -} - -.social-twitter i { - color: #1da1f2; -} - -.social-slack { - border-color: #4a154b; -} - -.social-slack:hover { - background: #faf5ff; - border-color: #4a154b; -} - -.social-slack i { - color: #4a154b; -} - - -.social-instagram { - border-color: #e4405f; -} - -.social-instagram:hover { - background: #fef2f2; - border-color: #e4405f; -} - -.social-instagram i { - color: #e4405f; -} - -.social-facebook { - border-color: #1877f2; -} - -.social-facebook:hover { - background: #f0f9ff; - border-color: #1877f2; -} - -.social-facebook i { - color: #1877f2; -} - -.social-osf { - border-color: #8b5cf6; -} - -.social-osf:hover { - background: #faf5ff; - border-color: #8b5cf6; -} - -.social-osf i { - color: #8b5cf6; -} - -.social-linkedin { - border-color: #0077b5; -} - -.social-linkedin:hover { - background: #f0f9ff; - border-color: #0077b5; -} - -.social-linkedin i { - color: #0077b5; -} - -.social-youtube { - border-color: #ff0000; -} - -.social-youtube:hover { - background: #fef2f2; - border-color: #ff0000; -} - -.social-youtube i { - color: #ff0000; -} - -.resource-button i { margin-left: 0.75rem; transition: transform 0.3s ease; } -.resource-button:hover i { transform: translateX(0.25rem); } - -/* Community Section */ -.community-section { - background: linear-gradient(135deg, #3b82f6, #8b5cf6); - color: white; - padding: 4rem 0; - text-align: center; -} - -.community-container { max-width: 800px; margin: 0 auto; padding: 0 1rem; } -.community-title { font-size: 1.5rem; font-weight: bold; margin-bottom: 1.5rem; } -.community-description { font-size: 0.9375rem; margin-bottom: 2rem; opacity: 0.9; } -.community-buttons { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; } - -.community-btn { - display: inline-flex; - align-items: center; - padding: 0.75rem 2rem; - border-radius: 0.5rem; - font-weight: 600; - text-decoration: none; - transition: all 0.3s ease; - border: 2px solid white; - color: white; -} - -.community-btn:hover { background: white; color: #3b82f6; transform: translateY(-2px); } - -/* Responsive */ -@media (max-width: 768px) { -.welcome-content > div { - margin: 0.5rem 0.5rem; - padding: 0.5rem 0.5rem; - } - .hero-title { font-size: 1.5rem; } - .hero-description { font-size: 0.84375rem; } - .welcome-content h2 { font-size: 1.3125rem; padding: 2rem 1rem; } - .welcome-content h2:first-of-type + div, - .welcome-content h2:last-of-type + div { padding: 0 1rem 2rem 1rem; } - .hero-buttons, .community-buttons { flex-direction: column; align-items: center; } - .hero-btn, .community-btn { width: 100%; max-width: 300px; justify-content: center; } - .step-button { width: auto; } -} - -/* Scrollbar */ -::-webkit-scrollbar { width: 8px; } -::-webkit-scrollbar-track { background: #f1f5f9; } -::-webkit-scrollbar-thumb { background: linear-gradient(to bottom, #3b82f6, #8b5cf6); border-radius: 4px; } -::-webkit-scrollbar-thumb:hover { background: linear-gradient(to bottom, #2563eb, #7c3aed); } \ No newline at end of file diff --git a/assets/images/doors/area-community.webp b/assets/images/doors/area-community.webp new file mode 100644 index 00000000000..8052435a42e Binary files /dev/null and b/assets/images/doors/area-community.webp differ diff --git a/assets/images/doors/area-education.webp b/assets/images/doors/area-education.webp new file mode 100644 index 00000000000..8be0213fd52 Binary files /dev/null and b/assets/images/doors/area-education.webp differ diff --git a/assets/images/doors/area-justice.webp b/assets/images/doors/area-justice.webp new file mode 100644 index 00000000000..52716ddaee8 Binary files /dev/null and b/assets/images/doors/area-justice.webp differ diff --git a/assets/images/doors/area-research.webp b/assets/images/doors/area-research.webp new file mode 100644 index 00000000000..a4ea10584f1 Binary files /dev/null and b/assets/images/doors/area-research.webp differ diff --git a/assets/images/doors/castle-still.webp b/assets/images/doors/castle-still.webp new file mode 100644 index 00000000000..08e0f38ce20 Binary files /dev/null and b/assets/images/doors/castle-still.webp differ diff --git a/assets/images/doors/just-os-icon.webp b/assets/images/doors/just-os-icon.webp new file mode 100644 index 00000000000..5321f9200ff Binary files /dev/null and b/assets/images/doors/just-os-icon.webp differ diff --git a/assets/images/doors/logo-row.webp b/assets/images/doors/logo-row.webp new file mode 100644 index 00000000000..29bcbde24bc Binary files /dev/null and b/assets/images/doors/logo-row.webp differ diff --git a/assets/js/castle/README.md b/assets/js/castle/README.md new file mode 100644 index 00000000000..14b4b228edb --- /dev/null +++ b/assets/js/castle/README.md @@ -0,0 +1,36 @@ +# The home-page castle + +The animated brick tower in the home page's arched door. Hugo bundles it with `js.Build` from `layouts/partials/doors/home/scripts.html`, using Three.js 0.186.0 from `assets/three/`. No Node is needed to build the site. + +| File | Role | +|---|---| +| `castle-scene.js` | Scene, lights, timeline. Exposes `window.forrtCastle` | +| `castle-geometry.js` | The bricks | +| `castle-piles.js` | Baked resting poses of the two heaps. Generated, see below | +| `bake-castle-piles.js` | Offline physics bake for those poses. Never part of the site's bundle | +| `illustrations-render.js` | Offline renderer for the four area illustrations. Never part of the site's bundle | +| `../doors/castle.js` | Loads the bundle after paint and owns playback: pause, hover, tap, off-screen suspension. Also opens the tower game, see `../tower/README.md` | +| `../doors/motion.js` | The pause state the castle shares with the home page's quotes, remembered between visits | + +**Behaviour.** The still image paints first. The bundle loads after that, and not at all under reduced motion, Save-Data or no WebGL, so the home page works if the castle does not. The tower builds once, turns every 120 seconds and takes a colour wave after each change. Hovering dismantles it. The pause button and "Build it yourself" appear on hover, on keyboard focus and after a tap. + +**To measure it on a device**, add `?fps` to the home page's address, which shows the frame rate, or `?fps=20`, which also caps drawing at that rate. + +**To turn it off**, set `doors.castle = false` in `config/_default/params.toml`. Visitors then see only the still. + +**After changing the geometry or the scene**, regenerate the still, so it still lines up when the bundle takes over: + +```sh +hugo --destination /tmp/castle-build +python3 scripts/rebrand/render_castle_still.py /tmp/castle-build assets/images/doors/castle-still.webp +``` + +**After changing which bricks move**, re-bake the heaps. This is the one step that needs Node, and only on the machine doing it: + +```sh +npm install --prefix /tmp/castle-bake --no-audit --no-fund three@0.186.0 cannon-es@0.20.0 esbuild@0.28.2 +NODE_PATH=/tmp/castle-bake/node_modules /tmp/castle-bake/node_modules/.bin/esbuild assets/js/castle/bake-castle-piles.js --bundle --platform=node --format=cjs --outfile=/tmp/castle-bake/bake.cjs +node /tmp/castle-bake/bake.cjs assets/js/castle/castle-piles.js +``` + +`setVariant("stone" | "pride" | "brick")`, `seek(seconds)` and `setProgress(0..1)` pin a pose for screenshots. diff --git a/assets/js/castle/bake-castle-piles.js b/assets/js/castle/bake-castle-piles.js new file mode 100644 index 00000000000..0c7a8c29310 --- /dev/null +++ b/assets/js/castle/bake-castle-piles.js @@ -0,0 +1,62 @@ +/* Offline only: generate deterministic, physically settled brick pile poses. + Dependencies: three@0.186.0, cannon-es@0.20.0, esbuild@0.28.2. + Bundle this script for Node using the same temporary NODE_PATH as README.md: + esbuild assets/js/castle/bake-castle-piles.js --bundle --platform=node --format=cjs --outfile=/tmp/bake-castle-piles.cjs + node /tmp/bake-castle-piles.cjs assets/js/castle/castle-piles.js + This solver is never included in the site's browser bundle. */ +import * as C from 'cannon-es'; +import * as T from 'three'; +import fs from 'node:fs'; +import { createBrickCastle } from './castle-geometry.js'; +const parts=[...createBrickCastle().assemblyParts].sort((a, b) => a.order - b.order).map(({object},index)=>{object.position.set(0,0,0);object.quaternion.identity();const bounds=new T.Box3().setFromObject(object); return {object,index,bounds};}); +let seed=12512; function rand(){seed=(1664525*seed+1013904223)>>>0;return seed/4294967296;} +const world=new C.World({gravity:new C.Vec3(0,-9.81,0)}); +world.solver.iterations=30;world.solver.tolerance=1e-8; +world.defaultContactMaterial.friction=.65;world.defaultContactMaterial.restitution=.025; +world.defaultContactMaterial.contactEquationStiffness=1e8; +world.defaultContactMaterial.contactEquationRelaxation=4; +const floor=new C.Body({mass:0});floor.addShape(new C.Plane());floor.quaternion.setFromEuler(-Math.PI/2,0,0);floor.position.y=-.035;world.addBody(floor); +const poses=[],bounds=new T.Box3(); +const resting=[]; +for(const part of [...parts].reverse()){ + const size=part.bounds.getSize(new T.Vector3()),center=part.bounds.getCenter(new T.Vector3()); + const studs=!/lintel|ledge/.test(part.object.name)&&size.x>.18; + const bodyHeight=size.y-(studs?.055:0); + const bodyCenter=new C.Vec3(center.x,center.y-(studs?.0275:0),center.z); + const brick=new C.Body({mass:Math.max(.015,size.x*bodyHeight*size.z*5),linearDamping:.24,angularDamping:.4}); + brick.addShape(new C.Box(new C.Vec3(size.x/2,bodyHeight/2,size.z/2)),bodyCenter); + if(studs){ + const cols=Math.max(1,Math.floor((size.x+.014-.18)/.30)+1),rows=Math.max(1,Math.floor((size.z+.014-.18)/.30)+1); + for(let i=0;i1.835:finalBox.max.x< -1.835)&&Math.max(Math.abs(finalBox.min.x),Math.abs(finalBox.max.x))<3.6&&finalBox.max.z<1.85&&finalBox.min.z>-1.85&&finalBox.max.y<1.8&&brick.velocity.length()<.035&&brick.angularVelocity.length()<.1; + if(!good)world.removeBody(brick); + } + if(!good)throw new Error('Could not place '+part.index); + brick.mass=0;brick.type=C.Body.STATIC;brick.updateMassProperties();brick.velocity.set(0,0,0);brick.angularVelocity.set(0,0,0); + const position=part.object.position.toArray(),quat=part.object.quaternion.toArray(); + poses[part.index]=[...position,...quat].map(n=>+n.toFixed(6)); + bounds.union(finalBox);resting.push({x:position[0],z:position[2],top:finalBox.max.y}); + +} +const header=`import { Vector3, Quaternion } from 'three';\n\n// Rigid-body settled poses, deposited from highest castle index to lowest.\n// The offline bake uses each brick's dimensions and stud colliders; earlier\n// deposits become fixed supports so removing later deposits cannot suspend them.\n// No physics engine is shipped to the browser. Floor y = -.035.\nconst SETTLED_POSES = ${JSON.stringify(poses,null,2)};\n\nexport function arrangeBrickPiles(parts) {\n if (parts.length !== SETTLED_POSES.length) throw new Error('Brick pile poses must be rebaked after changing the castle geometry.');\n for (const part of parts) {\n const pose = SETTLED_POSES[part.index];\n part.staging = new Vector3(...pose.slice(0, 3));\n part.stagedQuaternion = new Quaternion(...pose.slice(3));\n }\n return parts;\n}\n`; +fs.writeFileSync(process.argv[2] || 'assets/js/castle/castle-piles.js', header); +// Precise vertices exclude empty corners of rotated geometry bounding boxes. +const actualBounds = new T.Box3(); +for (const part of parts) actualBounds.union(new T.Box3().setFromObject(part.object, true)); +console.log(JSON.stringify({pieces: poses.length, min: actualBounds.min.toArray(), max: actualBounds.max.toArray()}, null, 2)); diff --git a/assets/js/castle/castle-geometry.js b/assets/js/castle/castle-geometry.js new file mode 100644 index 00000000000..71e841aa335 --- /dev/null +++ b/assets/js/castle/castle-geometry.js @@ -0,0 +1,355 @@ +/* Common Room v3 copy of assets/web3/castle-geometry.js. The moving bricks and their order are unchanged; the upper courses of all four sides now change colour, + and arch tops no longer cut thin bands across the fixed lower walls. + A hollow, brick-built keep. Units are metres only by convention: one stud is .30. + Bricks include their studs in a shared geometry and animate as individual parts. */ +import { + Group, Mesh, MeshStandardMaterial, Shape, ExtrudeGeometry, + LatheGeometry, Vector2, Matrix4, Color +} from 'three'; +import { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry.js'; +import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js'; + +const PITCH = .30; +const COURSE = .27; +const GAP = .014; +const WALL = .36; +const FLOOR = .15; +const BODY_HALF = 1.20; +const GROUND = -.035; +// The shaped doorway bricks extend .005 below their nominal lower edge. +const GROUND_SHIFT = FLOOR + GAP / 2 - .005 - GROUND; +const CASTLE_HEIGHT = 4.02 - GROUND_SHIFT; + +export function createBrickCastle() { + const model = new Group(); + model.name = 'Brick-built stone keep'; + const assemblyParts = []; + const cache = new Map(); + const stoneColours = ['#55616a', '#5c6870', '#637078', '#505c65', '#6b757b', '#5a666e', '#626b71']; + const stones = stoneColours.map(color => new MeshStandardMaterial({ + color, roughness: .42, metalness: .025 + })); + const archStones = ['#79848a', '#707c83', '#65717a'].map(color => new MeshStandardMaterial({ + color, roughness: .43, metalness: .025 + })); + const mortar = new MeshStandardMaterial({color: '#444f58', roughness: .48, metalness: .02}); + // Stable brick identities also index the precomputed loose-brick pile poses. + let serial = 1, removedBandBricks = 0; + + // The softened lip catches a narrow highlight without the cost of a bevel modifier. + const studGeometry = new LatheGeometry([ + new Vector2(0, 0), new Vector2(.078, 0), new Vector2(.087, .008), + new Vector2(.087, .048), new Vector2(.080, .055), new Vector2(0, .055) + ], 16); + + function merge(parts) { + const result = mergeGeometries(parts.map(part => part.index ? part.toNonIndexed() : part), false); + result.computeBoundingSphere(); + return result; + } + + function studAt(x, y, z) { + return studGeometry.clone().applyMatrix4(new Matrix4().makeTranslation(x, y, z)); + } + + function brickGeometry(width, height, depth, studs = true) { + const key = [width, height, depth, studs].map(value => typeof value === 'number' ? value.toFixed(4) : value).join('/'); + if (cache.has(key)) return cache.get(key); + const parts = [new RoundedBoxGeometry(width - GAP, height - GAP, depth - GAP, 2, .011)]; + if (studs) { + const columns = Math.max(1, Math.floor((width - .18) / PITCH) + 1); + const rows = Math.max(1, Math.floor((depth - .18) / PITCH) + 1); + for (let x = 0; x < columns; x++) for (let z = 0; z < rows; z++) { + const px = (x - (columns - 1) / 2) * PITCH; + const pz = (z - (rows - 1) / 2) * PITCH; + if (Math.abs(px) + .09 < width / 2 && Math.abs(pz) + .09 < depth / 2) { + parts.push(studAt(px, (height - GAP) / 2, pz)); + } + } + } + const geometry = merge(parts); + cache.set(key, geometry); + return geometry; + } + + function addPiece(geometry, {x = 0, y = 0, z = 0, rotation = 0, material, animate = false, order, name = 'Masonry brick'} = {}) { + const object = new Group(); + object.name = name; + object.position.set(x, y, z); + object.rotation.y = rotation; + const mesh = new Mesh(geometry, material || stones[(serial * 17 + Math.floor(y * 31)) % stones.length]); + mesh.castShadow = true; + mesh.receiveShadow = true; + object.add(mesh); + object.userData = {brick: true, index: serial++, course: Math.floor((y - FLOOR) / COURSE), restHeight: y}; + model.add(object); + if (animate) assemblyParts.push({object, order: order ?? y * 100 + (serial % 23) * .025}); + return object; + } + + function wallPiece(geometry, x, y, side, material, animate, name) { + const rotation = side * Math.PI / 2; + const z = BODY_HALF - WALL / 2; + return addPiece(geometry, { + x: Math.cos(rotation) * x + Math.sin(rotation) * z, + y, + z: -Math.sin(rotation) * x + Math.cos(rotation) * z, + rotation, material, animate, name + }); + } + + // Front and right entrances are openings through the wall, including the arch. + // Arches use circular boundaries; upper arrow windows have a small flat lintel. + const openings = [ + [{bottom: FLOOR - .03, spring: .96, radius: .45, top: 1.41}, {bottom: 2.04, top: 2.58, half: .16}], + [{bottom: FLOOR - .03, spring: .81, radius: .36, top: 1.17}, {bottom: 2.04, top: 2.58, half: .16}], + [{bottom: 2.04, top: 2.58, half: .16}], + [{bottom: 2.04, top: 2.58, half: .16}] + ]; + + function halfOpening(opening, y) { + if (opening.half) return opening.half; + if (y <= opening.spring) return opening.radius; + return Math.sqrt(Math.max(0, opening.radius ** 2 - (y - opening.spring) ** 2)); + } + + function rectangularIntervals(left, right, course) { + const boundaries = [left]; + const offset = course % 2 ? .30 : 0; + for (let x = -1.80 + offset; x < right - .01; x += .60) { + if (x > left + .08) boundaries.push(x); + } + boundaries.push(right); + return boundaries.slice(0, -1).map((x, i) => [x, boundaries[i + 1]]); + } + + // Clip an individual masonry block against an aperture, leaving real air inside. + // Curved edge points are sampled only on the handful of blocks around each arch. + function clippedBrick(left, right, low, high, opening, direction) { + const centerX = (left + right) / 2; + const centerY = (low + high) / 2; + const samples = 18; + const points = []; + let area = 0; + const x0 = left + GAP / 2; + const x1 = right - GAP / 2; + const y0 = low + GAP / 2; + const y1 = high - GAP / 2; + for (let i = 0; i <= samples; i++) { + const y = y0 + (y1 - y0) * i / samples; + const boundary = direction < 0 + ? Math.min(x1, -halfOpening(opening, y) - .006) + : Math.max(x0, halfOpening(opening, y) + .006); + const width = direction < 0 ? Math.max(0, boundary - x0) : Math.max(0, x1 - boundary); + area += width; + points.push({y, boundary: direction < 0 ? Math.max(x0, boundary) : Math.min(x1, boundary)}); + } + if (area < .025) return null; + const shape = new Shape(); + if (direction < 0) { + shape.moveTo(x0 - centerX, y0 - centerY); + for (const point of points) shape.lineTo(point.boundary - centerX, point.y - centerY); + shape.lineTo(x0 - centerX, y1 - centerY); + } else { + shape.moveTo(x1 - centerX, y0 - centerY); + shape.lineTo(x1 - centerX, y1 - centerY); + for (const point of points.slice().reverse()) shape.lineTo(point.boundary - centerX, point.y - centerY); + } + shape.closePath(); + const solid = new ExtrudeGeometry(shape, {depth: WALL - GAP - .012, bevelEnabled: true, bevelThickness: .006, bevelSize: .005, bevelSegments: 2, steps: 1, curveSegments: 10}); + solid.translate(0, 0, -(WALL - GAP - .012) / 2); + const parts = [solid]; + // Studs belong only where the clipped brick still has a full-width top surface. + const topBoundary = points[points.length - 1].boundary; + for (let x = -1.05; x < 1.20; x += PITCH) { + const permitted = direction < 0 ? x + .094 < topBoundary : x - .094 > topBoundary; + if (permitted && x - .094 > x0 && x + .094 < x1) parts.push(studAt(x - centerX, y1 - centerY, 0)); + } + return merge(parts); + } + + for (let course = 0; course < 11; course++) { + const low = FLOOR + course * COURSE; + const high = low + COURSE; + for (let side = 0; side < 4; side++) { + // Alternate which pair passes through the corners, as physical brick bonds do. + const fullWidth = side % 2 === course % 2; + const half = fullWidth ? BODY_HALF : BODY_HALF - WALL; + const rows = [low, high]; + const holes = openings[side]; + // An arch top inside a course leaves that course whole; only the bricks over the arch are shaped. + let archTop = false; + for (const hole of holes) for (const boundary of [hole.bottom, hole.top]) { + if (boundary > low + .01 && boundary < high - .01) { + if (hole.radius) archTop = true; + else rows.push(boundary); + } + } + rows.sort((a, b) => a - b); + for (let section = 0; section < rows.length - 1; section++) { + const bottom = rows[section]; + const top = rows[section + 1]; + const middleY = (bottom + top) / 2; + const hole = holes.find(opening => middleY > opening.bottom && middleY < opening.top); + for (const [left, right] of rectangularIntervals(-half, half, course + side)) { + const middleX = (left + right) / 2; + const animate = course >= 8 && side < 2; + if (!hole || (right < -halfOpening(hole, bottom) - .01 || left > halfOpening(hole, bottom) + .01)) { + wallPiece(brickGeometry(right - left, top - bottom, WALL), middleX, middleY, side, null, animate, 'Interlocking slate-grey wall brick'); + } else { + for (const direction of [-1, 1]) { + const geometry = clippedBrick(left, right, bottom, top, hole, direction); + if (geometry) wallPiece(geometry, middleX, middleY, side, null, animate, 'Shaped masonry around an opening'); + } + } + } + } + // Serials index the baked heap poses, so the bricks of the removed band keep their numbers. + if (archTop) { + const skipped = rectangularIntervals(-half, half, course + side).length; + serial += skipped; + removedBandBricks += skipped; + } + } + } + + // Specialised arch bricks project by .018, with a separate keystone and jambs. + // Their inner radii exactly follow the openings cut into the structural wall. + function addArch(side, radius, spring, count) { + for (let i = 0; i < count; i++) { + const start = i * Math.PI / count + .012; + const end = (i + 1) * Math.PI / count - .012; + const outer = radius + .135; + const shape = new Shape(); + shape.absarc(0, 0, outer, start, end, false); + shape.absarc(0, 0, radius + .007, end, start, true); + shape.closePath(); + const geometry = new ExtrudeGeometry(shape, {depth: WALL + .025, bevelEnabled: true, bevelSize: .004, bevelThickness: .004, bevelSegments: 2, curveSegments: 6, steps: 1}); + geometry.translate(0, 0, -(WALL + .025) / 2); + wallPiece(geometry, 0, spring, side, archStones[i % archStones.length], false, 'Individual arch voussoir'); + } + const jambHeight = spring - FLOOR; + const countJamb = Math.ceil(jambHeight / COURSE); + for (let course = 0; course < countJamb; course++) { + const h = jambHeight / countJamb; + for (const sign of [-1, 1]) { + wallPiece(brickGeometry(.132, h, WALL + .027, false), sign * (radius + .071), FLOOR + h * (course + .5), side, archStones[(course + side) % archStones.length], false, 'Arch jamb brick'); + } + } + } + addArch(0, .45, .96, 7); + addArch(1, .36, .81, 7); + + // Narrow sill pieces and lintels give the upper windows a manufactured brick edge. + for (let side = 0; side < 4; side++) { + wallPiece(brickGeometry(.48, .085, .41, false), 0, 2.005, side, archStones[2], false, 'Arrow-window sill'); + wallPiece(brickGeometry(.48, .10, .39, false), 0, 2.63, side, stones[3], side < 2, 'Arrow-window lintel'); + } + + // A two-step corbel expands the top into a broad, genuinely hollow parapet. + // The interior remains open to the base, including during the assembly sequence. + function ringCourse(outer, thickness, low, height, pattern, name, material) { + const center = outer / 2 - thickness / 2; + for (let side = 0; side < 4; side++) { + const rotation = side * Math.PI / 2; + const length = side % 2 === pattern % 2 ? outer : outer - thickness * 2; + const count = Math.round(length / .59); + const width = length / count; + for (let brick = 0; brick < count; brick++) { + const x = -length / 2 + width * (brick + .5); + addPiece(brickGeometry(width, height, thickness), { + x: Math.cos(rotation) * x + Math.sin(rotation) * center, + y: low + height / 2, + z: -Math.sin(rotation) * x + Math.cos(rotation) * center, + rotation, material, animate: side < 2, name + }); + } + } + } + ringCourse(2.58, .40, 3.12, .12, 0, 'Lower corbel brick', stones[3]); + ringCourse(2.76, .45, 3.24, .13, 1, 'Upper corbel brick', stones[2]); + ringCourse(2.94, .45, 3.37, .25, 0, 'Open parapet wall', null); + + // Four substantial corner merlons and two middle merlons on every edge. + // Exposed two-by-two studs make the crown unmistakably a construction toy. + const crownHalf = 1.47; + const merlonWidth = .54; + const center = crownHalf - merlonWidth / 2; + for (const x of [-center, center]) for (const z of [-center, center]) { + addPiece(brickGeometry(.54, .34, .54), {x, y: 3.79, z, material: stones[3], animate: x > 0 && z > 0, name: 'Corner battlement'}); + } + for (let side = 0; side < 4; side++) { + const rotation = side * Math.PI / 2; + for (const x of [-.415, .415]) { + addPiece(brickGeometry(.51, .34, .54), { + x: Math.cos(rotation) * x + Math.sin(rotation) * center, + y: 3.79, + z: -Math.sin(rotation) * x + Math.cos(rotation) * center, + rotation, material: stones[(side + 2) % stones.length], animate: side < 2, name: 'Studded crenellation' + }); + } + } + + // A few shallow inner ledge tiles catch light inside the open roof, adding depth. + ringCourse(2.05, .17, 3.25, .08, 0, 'Interior parapet ledge', mortar); + assemblyParts.sort((a, b) => a.order - b.order); + + // The upper courses can change on all four sides while the stone foundation stays + // in place, so the turning tower shows the facade from every angle. Only the front + // and right bricks move. Each changing brick owns its material so colours never leak + // into the fixed walls. Colour targets are linear-space Three colours for interpolation. + const prideColours = ['#7046a3', '#2672b8', '#2b985b', '#edc438', '#e98434', '#ce4742']; + const brickColours = ['#a84f39', '#b66147', '#9d4535', '#c17354', '#a95b44', '#bb5940', '#99503f']; + const moved = new Set(assemblyParts.map(part => part.object)); + const recolourParts = model.children.filter(object => object.userData.brick && object.position.y > 2.31 && !moved.has(object)).map(object => ({object})); + for (const part of [...assemblyParts, ...recolourParts]) { + const y = part.object.position.y; + // Course changes, corbels and crown each contribute to the ascending bands. + const band = y < 2.58 ? 0 : y < 2.86 ? 1 : y < 3.12 ? 2 : y < 3.30 ? 3 : y < 3.62 ? 4 : 5; + const variation = part.object.userData.index % brickColours.length; + part.palette = []; + part.object.traverse(mesh => { + if (!mesh.isMesh) return; + mesh.material = mesh.material.clone(); + part.palette.push({ + material: mesh.material, + stone: mesh.material.color.clone(), + pride: new Color(prideColours[band]), + brick: new Color(brickColours[variation]) + }); + }); + part.object.userData.facadeBand = band; + } + + // Brick geometry remains unchanged for the baked pile poses. Lower the castle + // as a whole so the first masonry course rests directly on the shadow plane. + for (const object of model.children) { + object.position.y -= GROUND_SHIFT; + object.userData.restHeight -= GROUND_SHIFT; + } + + // Fixed bricks share a draw call per material. Moving bricks retain independent + // pivots and shared geometries, so the detailed model stays economical on phones. + const moving = new Set([...assemblyParts, ...recolourParts].map(part => part.object)); + const fixedByMaterial = new Map(); + model.updateMatrixWorld(true); + for (const object of model.children.slice()) { + if (moving.has(object)) continue; + object.traverse(mesh => { + if (!mesh.isMesh) return; + if (!fixedByMaterial.has(mesh.material)) fixedByMaterial.set(mesh.material, []); + fixedByMaterial.get(mesh.material).push(mesh.geometry.clone().applyMatrix4(mesh.matrixWorld)); + }); + model.remove(object); + } + for (const [material, geometries] of fixedByMaterial) { + const mesh = new Mesh(merge(geometries), material); + mesh.name = 'Fixed castle masonry'; + mesh.castShadow = true; + mesh.receiveShadow = true; + model.add(mesh); + geometries.forEach(geometry => geometry.dispose()); + } + model.userData = {studPitch: PITCH, bodyWidth: 2.4, baseWidth: 2.94, height: CASTLE_HEIGHT, brickCount: serial - 1 - removedBandBricks}; + return {model, assemblyParts, recolourParts, height: CASTLE_HEIGHT}; +} diff --git a/assets/js/castle/castle-piles.js b/assets/js/castle/castle-piles.js new file mode 100644 index 00000000000..3c0a8e556ad --- /dev/null +++ b/assets/js/castle/castle-piles.js @@ -0,0 +1,540 @@ +import { Vector3, Quaternion } from 'three'; + +// Rigid-body settled poses, deposited from highest castle index to lowest. +// The offline bake uses each brick's dimensions and stud colliders; earlier +// deposits become fixed supports so removing later deposits cannot suspend them. +// No physics engine is shipped to the browser. Floor y = -.035. +const SETTLED_POSES = [ + [ + -2.762196, + 1.519374, + 0.201872, + -0.058075, + 0.966239, + 0.107143, + -0.227001 + ], + [ + 2.910415, + 0.700614, + -1.130143, + -0.44114, + 0.279641, + -0.622896, + -0.582407 + ], + [ + -2.335264, + 0.431316, + -0.775152, + 0.131656, + -0.192286, + 0.247051, + -0.940563 + ], + [ + 2.64086, + 0.092993, + 1.606751, + 0, + -0.991671, + 0, + 0.128796 + ], + [ + -2.779671, + 1.300908, + -0.278321, + -0.125919, + 0.918049, + 0.088557, + 0.365361 + ], + [ + 2.962703, + 0.302172, + -0.504478, + -0.49234, + 0.839966, + -0.030349, + 0.226134 + ], + [ + -2.64207, + 1.128068, + 0.508993, + 0.750042, + 0.584448, + -0.160565, + -0.26472 + ], + [ + 2.691394, + 1.374349, + -0.256562, + -0.553269, + 0.178624, + -0.677454, + 0.450602 + ], + [ + -2.760343, + 1.120281, + -0.18211, + -0.044834, + 0.499245, + 0.147267, + 0.852676 + ], + [ + 2.478386, + 1.368097, + 0.063462, + -0.118629, + 0.924552, + 0.288394, + 0.218999 + ], + [ + -2.808953, + 0.853202, + -0.445325, + 0.087031, + 0.876213, + -0.459556, + -0.116125 + ], + [ + 2.815329, + 1.030478, + 0.141814, + -0.589796, + 0.299601, + 0.473836, + -0.581257 + ], + [ + -2.951747, + 0.635841, + -0.749274, + -0.987546, + 0.077785, + 0.136757, + -0.000784 + ], + [ + 3.262337, + 0.231556, + 0.889533, + 0.637715, + -0.118123, + 0.730031, + -0.215457 + ], + [ + -2.556266, + 0.342159, + -1.186858, + -0.063995, + -0.375852, + -0.904556, + -0.190839 + ], + [ + 3.223478, + 0.5445, + 0.142827, + 0.902738, + -0.356456, + 0.193522, + -0.143361 + ], + [ + -3.09319, + 0.333393, + -0.855782, + -0.508891, + 0.17361, + -0.836501, + -0.105617 + ], + [ + 2.444895, + 0.521019, + -1.142683, + 0.82759, + 0.216861, + 0.146296, + 0.496652 + ], + [ + -2.157952, + 0.137993, + -1.544295, + 0.084082, + -0.70209, + -0.70209, + -0.084082 + ], + [ + 2.957451, + 0.445884, + -0.785152, + 0.217762, + 0.684901, + 0.672823, + -0.175502 + ], + [ + -3.198635, + 0.140931, + -1.324817, + -0.165186, + -0.695033, + -0.679009, + 0.169084 + ], + [ + 2.351744, + 1.244735, + -0.38821, + -0.077872, + 0.634864, + 0.138864, + 0.756043 + ], + [ + -2.686164, + 1.117655, + 0.519459, + 0.134332, + 0.872003, + -0.033017, + 0.469549 + ], + [ + 2.578228, + 0.240137, + -1.371165, + 0.756766, + 0.016298, + 0.242732, + 0.60673 + ], + [ + -2.705798, + 0.844357, + 0.640168, + -0.181641, + 0.072357, + -0.11853, + 0.97351 + ], + [ + 2.406813, + 1.012214, + -0.50172, + 0.099346, + -0.001545, + 0.155135, + 0.982884 + ], + [ + -2.839229, + 0.759358, + 0.767795, + -0.105193, + 0.349802, + 0.930297, + 0.033457 + ], + [ + 2.390414, + 1.02744, + 0.021706, + 0.094722, + 0.697968, + -0.128574, + -0.698095 + ], + [ + -2.111296, + 0.148578, + -0.167687, + 0.56688, + -0.33591, + -0.70152, + -0.271442 + ], + [ + 3.212435, + 0.163292, + 0.448927, + 0.54297, + -0.141106, + 0.093146, + 0.822555 + ], + [ + -2.833777, + 0.881405, + 0.079142, + -0.278294, + 0.794185, + -0.15145, + -0.518542 + ], + [ + 2.985669, + 0.33798, + 0.05892, + 0.928324, + 0, + -0.371773, + 0 + ], + [ + -2.435534, + -0.002007, + -1.174564, + -0.037998, + 0, + 0.999278, + 0 + ], + [ + 2.857076, + 0.33798, + -0.156199, + 0, + 0.868218, + 0, + -0.496182 + ], + [ + -2.254641, + 0.167987, + 0.960991, + 0, + -0.138281, + 0, + 0.990393 + ], + [ + 2.706043, + 0.377987, + 0.946488, + 0, + -0.30076, + 0, + -0.9537 + ], + [ + -2.599056, + 0.737108, + -0.125416, + 0.060227, + 0.52859, + -0.453533, + 0.715034 + ], + [ + 2.975553, + 0.246987, + -0.064693, + 0.956271, + 0, + 0.292482, + 0 + ], + [ + -2.761556, + 0.6505, + 0.314622, + -0.148516, + 0.829627, + 0.094839, + -0.529781 + ], + [ + 3.012897, + 0.076993, + -0.051303, + 0.914072, + 0, + 0.405552, + 0 + ], + [ + -2.740653, + 0.493463, + 0.937067, + 0.443438, + 0.401297, + 0.628213, + 0.497666 + ], + [ + 2.939976, + 0.022993, + -1.541041, + 0, + -0.00882, + 0, + 0.999961 + ], + [ + -3.058994, + 0.076993, + -0.909842, + -0.988945, + 0, + 0.148281, + 0 + ], + [ + 2.443477, + 0.849173, + 0.294832, + 0.129584, + 0.86824, + -0.048152, + 0.476496 + ], + [ + -2.077511, + 0.076993, + 0.787584, + 0.706659, + 0, + -0.707554, + 0 + ], + [ + 3.1201, + 0.136993, + -0.705835, + 0.290199, + 0, + -0.956966, + 0 + ], + [ + -2.765828, + 0.504648, + -0.042604, + -0.0995, + 0.486222, + 0.024159, + -0.867816 + ], + [ + 2.494471, + 0.579846, + 0.463745, + -0.147127, + 0.102594, + 0.102584, + -0.978419 + ], + [ + -3.418939, + 0.182993, + -0.228133, + -0.503479, + -0.496496, + 0.496496, + -0.503479 + ], + [ + 2.36481, + 0.731579, + -0.379687, + -0.003312, + 0.543554, + 0.184184, + 0.818911 + ], + [ + -2.506566, + 0.243488, + -0.554331, + 0.263393, + 0.903245, + -0.094841, + -0.325234 + ], + [ + 2.404243, + 0.462987, + -0.608265, + 0, + 0.876912, + 0, + 0.480651 + ], + [ + -2.762663, + 0.136993, + 1.321576, + -0.381307, + 0, + 0.924448, + 0 + ], + [ + 2.346288, + 0.227993, + 0.246532, + -0.60895, + 0.60895, + -0.359416, + 0.359416 + ], + [ + -2.753706, + 0.227993, + 0.610509, + -0.09408, + 0.70082, + 0.70082, + 0.09408 + ], + [ + 2.540662, + 0.127993, + 0.844396, + 0, + 0.749458, + 0, + 0.662051 + ], + [ + -2.846986, + 0.127993, + 0.006779, + 0, + 0.986928, + 0, + -0.161162 + ], + [ + 2.41589, + 0.127993, + -0.58106, + 0, + 0.570402, + 0, + -0.821366 + ] +]; + +export function arrangeBrickPiles(parts) { + if (parts.length !== SETTLED_POSES.length) throw new Error('Brick pile poses must be rebaked after changing the castle geometry.'); + for (const part of parts) { + const pose = SETTLED_POSES[part.index]; + part.staging = new Vector3(...pose.slice(0, 3)); + part.stagedQuaternion = new Quaternion(...pose.slice(3)); + } + return parts; +} diff --git a/assets/js/castle/castle-scene.js b/assets/js/castle/castle-scene.js new file mode 100644 index 00000000000..b1446a6157e --- /dev/null +++ b/assets/js/castle/castle-scene.js @@ -0,0 +1,273 @@ +/* The castle scene for the Doors home page, from the design mock. + The tower builds once, then turns slowly while a colour wave climbs it. hold(true) dismantles it; hold(false) builds the next facade. + A studded toy castle, built in courses. Geometry and motion remain independent. + Bundle with Three.js using the project's castle build command. */ +import { + Scene, OrthographicCamera, WebGLRenderer, HemisphereLight, DirectionalLight, + Mesh, PlaneGeometry, ShadowMaterial, Group, Vector3, Quaternion, + PCFSoftShadowMap, SRGBColorSpace, ACESFilmicToneMapping +} from 'three'; +import { createBrickCastle } from './castle-geometry.js'; +import { arrangeBrickPiles } from './castle-piles.js'; + +const canvas = document.getElementById('castle-canvas'); +const stage = canvas?.parentElement; +if (canvas) { + try { + const renderer = new WebGLRenderer({ canvas, antialias: true, alpha: true }); + renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 1.75)); + renderer.outputColorSpace = SRGBColorSpace; + renderer.toneMapping = ACESFilmicToneMapping; + renderer.toneMappingExposure = 1.05; + renderer.shadowMap.enabled = true; + renderer.shadowMap.type = PCFSoftShadowMap; + const scene = new Scene(); + const camera = new OrthographicCamera(-3.7, 3.7, 3, -3, .1, 40); + const lookAt = new Vector3(0, 1.92, 0); + camera.position.set(7.4, 6.4, 10.2); + camera.lookAt(lookAt); + + // Neutral studio lighting preserves the stone and the coloured brick facades. + scene.add(new HemisphereLight(0xffffff, 0x8b8379, 1.75)); + const key = new DirectionalLight(0xfffbf6, 3.5); + // Higher than the original key light, so the shadow stays inside the canvas. + key.position.set(-3, 11, 6); + key.castShadow = true; + key.shadow.mapSize.set(2048, 2048); + key.shadow.camera.left = -6; key.shadow.camera.right = 6; + key.shadow.camera.top = 7; key.shadow.camera.bottom = -5; + key.shadow.camera.near = .5; key.shadow.camera.far = 22; + key.shadow.normalBias = .012; + key.shadow.bias = -.00008; + key.shadow.radius = 3; + scene.add(key); + const fill = new DirectionalLight(0xf3f7ff, 1.25); + fill.position.set(6, 4, -2); scene.add(fill); + const rim = new DirectionalLight(0xffffff, 1.2); + rim.position.set(-3, 5, -5); scene.add(rim); + + const { model, assemblyParts, recolourParts, height } = createBrickCastle(); + const turntable = new Group(); + turntable.add(model); scene.add(turntable); + const ground = new Mesh(new PlaneGeometry(30, 30), new ShadowMaterial({ opacity: .18 })); + ground.rotation.x = -Math.PI / 2; + ground.position.y = -.035; + ground.receiveShadow = true; + scene.add(ground); + + const clamp = value => Math.max(0, Math.min(1, value)); + const smooth = value => { const p = clamp(value); return p * p * (3 - 2 * p); }; + const easeOut = value => 1 - Math.pow(1 - clamp(value), 3); + const rotation = new Quaternion(); + // Every loose brick has a permanent resting place in one of the two heaps. + const parts = [...assemblyParts].sort((a, b) => a.order - b.order).map(({ object, order, palette }, index) => ({ + object, order, palette, index, rest: object.position.clone(), quaternion: object.quaternion.clone() + })); + arrangeBrickPiles(parts); + // The wave climbs by height, across moving and fixed bricks alike. + const recoloured = recolourParts.map(({ object, palette }) => ({ object, palette, rest: object.position.clone() })); + const waveLevels = [...parts, ...recoloured].map(part => part.rest.y); + const waveLow = Math.min(...waveLevels), waveHigh = Math.max(...waveLevels); + const waveHeight = part => (part.rest.y - waveLow) / (waveHigh - waveLow); + const cycleLength = 21; + const buildStart = 1.25; + const buildDuration = 7.75; + const reverseStart = 12.35; + const reverseDuration = 6.2; + let valid = true, lastTime = 0, origin = 0, replayPending = false, progressOverride = null; + const reduced = matchMedia('(prefers-reduced-motion: reduce)'); + const variants = ['stone', 'pride', 'brick']; + // The first view is always the grey stone tower closest to the FORRT logo. + const startingVariant = 0; + const builtAt = buildStart + buildDuration; + const waveLength = 15, waveRest = 30, turnSeconds = 120; + let facade = startingVariant, previous = startingVariant, settledAt = builtAt, nextWaveAt = builtAt + waveRest, waveStart = null; + let held = false, rebuilding = null, spin = 0, spinFrom = 0, lastLocal = 0; + const name = index => variants[index % variants.length]; + + // Maps page time to the original timeline, the facade colours and any recolouring wave. + function timeline(time) { + if (!rebuilding && held && time >= settledAt) { rebuilding = { start: time, buildAt: null, recolour: false }; spinFrom = spin; } + if (waveStart !== null && time - waveStart >= waveLength) { waveStart = null; previous = facade; nextWaveAt = time + waveRest; } + if (!rebuilding && waveStart === null && time >= nextWaveAt) { previous = facade; facade += 1; waveStart = time; } + const wave = waveStart === null ? undefined : (time - waveStart) / waveLength; + const flow = { target: name(facade), previous: name(previous), wave }; + if (!rebuilding) return { ...flow, t: Math.min(time, builtAt) }; + if (rebuilding.buildAt === null) { + const r = time - rebuilding.start; + // A dismantle always finishes; bricks stay in their heaps until the visitor moves away. + if (r < reverseDuration || held) return { ...flow, t: reverseStart + Math.min(r, reverseDuration) }; + rebuilding.buildAt = time; + // A wave still climbing the fixed courses keeps its colours; otherwise the build brings the next facade. + if (waveStart === null) { previous = facade; facade += 1; rebuilding.recolour = true; } + } + const t = buildStart + time - rebuilding.buildAt; + const build = rebuilding.recolour ? clamp((t - buildStart) / buildDuration) : undefined; + if (t < builtAt) return { target: name(facade), previous: name(previous), wave, build, t }; + if (waveStart === null) { previous = facade; nextWaveAt = time + waveRest; } + settledAt = time; rebuilding = null; + return { target: name(facade), previous: name(previous), wave, t: builtAt }; + } + function hold(value) { held = Boolean(value); } + let variantMode = 'auto'; + + function setVariant(variant) { + if (variant !== 'auto' && !variants.includes(variant)) return; + variantMode = variant; + render(); + } + + function place(part, amount, dismantling = false) { + const p = clamp(amount); + const clearance = Math.max(height + .30, part.rest.y + .62); + if (p === 1) { + part.object.position.copy(part.rest); + part.object.quaternion.copy(part.quaternion); + } else if (p === 0) { + part.object.position.copy(part.staging); + part.object.quaternion.copy(part.stagedQuaternion); + } else if (dismantling) { + const departure = 1 - p; + if (departure < .22) { + // Unseat vertically before carrying the brick clear of the battlements. + part.object.position.copy(part.rest); + part.object.position.y += (clearance - part.rest.y) * smooth(departure / .22); + } else if (departure < .57) { + part.object.position.lerpVectors(part.rest, part.staging, smooth((departure - .22) / .35)); + part.object.position.y = clearance; + } else { + // Gravity-like acceleration into the heap, then one small settling bounce. + const drop = clamp((departure - .57) / .34); + part.object.position.copy(part.staging); + part.object.position.y += (clearance - part.staging.y) * (1 - drop * drop); + if (departure > .91) { + const settle = (departure - .91) / .09; + part.object.position.y += Math.sin(settle * Math.PI) * .055 * (1 - settle); + } + } + rotation.copy(part.quaternion).slerp(part.stagedQuaternion, smooth((departure - .18) / .73)); + part.object.quaternion.copy(rotation); + } else { + if (p < .24) { + part.object.position.copy(part.staging); + part.object.position.y += (clearance - part.staging.y) * smooth(p / .24); + } else if (p < .78) { + part.object.position.lerpVectors(part.staging, part.rest, smooth((p - .24) / .54)); + part.object.position.y = clearance; + } else { + part.object.position.copy(part.rest); + part.object.position.y += (clearance - part.rest.y) * (1 - easeOut((p - .78) / .22)); + } + rotation.copy(part.stagedQuaternion).slerp(part.quaternion, smooth(p / .70)); + part.object.quaternion.copy(rotation); + } + } + + const lift = .05; + const climb = (phase, part) => smooth((phase - waveHeight(part) * .6) / .4); + + function assemblyAt(time) { + const flow = timeline(time); + const t = flow.t; + const reducedStatic = reduced.matches && !document.body.classList.contains('motion-opt-in'); + const targetVariant = variantMode === 'auto' ? flow.target : variantMode; + const previousVariant = variantMode === 'auto' ? flow.previous : variantMode; + stage.dataset.variant = targetVariant; + const lastIndex = Math.max(1, parts.length - 1); + const waving = flow.wave !== undefined && progressOverride === null && !reducedStatic; + const climbing = waving ? flow.wave : progressOverride === null && !reducedStatic ? flow.build : undefined; + for (const part of recoloured) { + const colourProgress = climbing === undefined ? 1 : climb(climbing, part); + for (const colours of part.palette) colours.material.color.lerpColors(colours[previousVariant], colours[targetVariant], colourProgress); + part.object.position.y = part.rest.y + (waving ? Math.sin(colourProgress * Math.PI) * lift : 0); + } + let assembled = 0; + for (const part of parts) { + const fraction = part.index / lastIndex; + let amount; + if (progressOverride !== null || reducedStatic) { + // Scrubbing represents construction progress, independent of the loop. + const progress = reducedStatic && progressOverride === null ? 1 : progressOverride; + const local = (progress * (buildDuration + .8) - fraction * buildDuration) / .8; + amount = clamp(local); + } else if (t < reverseStart) { + const startsAt = buildStart + fraction * (buildDuration - .8); + const local = (t - startsAt) / .8; + amount = clamp(local); + } else { + // Top courses leave first and become the bottom of each persistent heap. + const startsAt = reverseStart + (1 - fraction) * (reverseDuration - 1.1); + const local = (t - startsAt) / 1.1; + amount = 1 - clamp(local); + } + // Incoming bricks bring the next facade's colours; settled outgoing bricks + // retain theirs until they rise into the following build. + // A recolouring wave climbs the tower: each brick lifts slightly and comes back in its new colour. + const colourProgress = waving ? climb(flow.wave, part) : reducedStatic || progressOverride !== null || t >= reverseStart ? 1 : smooth(amount / .72); + for (const colours of part.palette || []) { + colours.material.color.lerpColors(colours[previousVariant], colours[targetVariant], colourProgress); + } + place(part, amount, progressOverride === null && !reducedStatic && t >= reverseStart); + if (waving && amount > .999) part.object.position.y += Math.sin(colourProgress * Math.PI) * lift; + if (amount > .999) assembled++; + } + stage.dataset.assembly = assembled === parts.length ? 'complete' : 'building'; + stage.dataset.looseBricks = String(parts.length - assembled); + stage.dataset.assemblyProgress = String(Math.round(assembled / Math.max(parts.length, 1) * 100)); + } + + function resize() { + const { width, height: stageHeight } = stage.getBoundingClientRect(); + if (!width || !stageHeight) return; + renderer.setSize(width, stageHeight, false); + const aspect = width / stageHeight; + // Leave room for both persistent heaps and the airborne pieces on every screen. + const halfHeight = Math.max(3.25, 3.6 / aspect); + camera.left = -halfHeight * aspect; camera.right = halfHeight * aspect; + camera.top = halfHeight; camera.bottom = -halfHeight; + camera.updateProjectionMatrix(); + } + + function render(time = lastTime, x = 0, y = 0) { + if (!valid) return; + lastTime = Number.isFinite(time) ? time : lastTime; + if (replayPending) { origin = lastTime; replayPending = false; } + const localTime = lastTime - origin; + const still = reduced.matches && !document.body.classList.contains('motion-opt-in'); + if (!still && progressOverride === null) { + const dt = Math.max(0, Math.min(.1, localTime - lastLocal)); + if (rebuilding) { + // Come back to the front before the heaps form, so they stay in frame. + const home = Math.round(spinFrom / (Math.PI * 2)) * Math.PI * 2; + spin = spinFrom + (home - spinFrom) * smooth((localTime - rebuilding.start) / 2.5); + } else if (localTime > settledAt) { + spin += dt * Math.PI * 2 / turnSeconds * clamp((localTime - settledAt) / 6); + } + } + lastLocal = localTime; + turntable.rotation.y = still ? 0 : spin + x * .15; + camera.position.y = 6.4 + (still ? 0 : y * .35); + camera.lookAt(lookAt); + assemblyAt(localTime); + renderer.render(scene, camera); + } + + function replay() { progressOverride = null; replayPending = true; } + function setProgress(value) { progressOverride = clamp(Number(value) || 0); render(); } + function clearProgress() { progressOverride = null; render(); } + function seek(seconds) { origin = lastTime - Math.max(0, Number(seconds) || 0); progressOverride = null; replayPending = false; render(); } + + canvas.addEventListener('webglcontextlost', event => { + event.preventDefault(); valid = false; stage.dataset.engine = 'fallback'; + }); + canvas.addEventListener('webglcontextrestored', () => { + valid = true; stage.dataset.engine = 'webgl'; resize(); render(); + }); + window.forrtCastle = { hold, setSpin(value) { spin = value; render(); }, resize, render, replay, setProgress, clearProgress, seek, setVariant, + info: { cycleLength, buildStart, buildDuration, reverseStart, reverseDuration, movingBricks: parts.length, height, variants, startingStyle: variants[startingVariant] } }; + resize(); render(); stage.dataset.engine = 'webgl'; + } catch (error) { + stage.dataset.engine = 'fallback'; + console.warn('Castle using static fallback:', error.message); + } +} diff --git a/assets/js/castle/illustrations-render.js b/assets/js/castle/illustrations-render.js new file mode 100644 index 00000000000..22c8c403f3e --- /dev/null +++ b/assets/js/castle/illustrations-render.js @@ -0,0 +1,127 @@ +/* Reproducible orthographic illustrations. No motion, controls or page dependencies. */ +import * as T from 'three'; +import { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry.js'; +const colors={bark:'#8a5a36',leaf:'#2f9e5f',leaf2:'#47b56e',leaf3:'#1f8650',pink:'#e58a9a',lead:'#38404a',wood:'#c98f55',board:'#27594f',chalk:'#f4f1e5',red:'#d8402f',orange:'#ee8a26',yellow:'#efc23a',grass:'#2f9e5f',azure:'#2a6fc4',purple:'#7a4fb0',teal:'#2a8c84',navy:'#1f3a6e',slate:'#637078',blue:'#3156a4',ochre:'#d59b42',green:'#527869',rose:'#b97176',cream:'#f4f1e5',paper:'#f9f7ee',edge:'#d8d7cb',ink:'#a6adb0'}; +const mats=Object.fromEntries(Object.entries(colors).map(([k,c])=>[k,new T.MeshStandardMaterial({color:c,roughness:.32,metalness:0})])); +const scene=new T.Scene(); +const renderer=new T.WebGLRenderer({canvas:document.querySelector('canvas'),alpha:true,antialias:true,preserveDrawingBuffer:true});renderer.setSize(1200,800);renderer.setPixelRatio(2);renderer.outputColorSpace=T.SRGBColorSpace;renderer.toneMapping=T.ACESFilmicToneMapping;renderer.toneMappingExposure=1.05;renderer.shadowMap.enabled=true;renderer.shadowMap.type=T.PCFSoftShadowMap; +scene.add(new T.HemisphereLight(0xffffff,0x8b8379,1.75)); +const key=new T.DirectionalLight(0xfffbf6,3.5);key.position.set(-4.5,8,7);key.castShadow=true;key.shadow.mapSize.set(4096,4096);Object.assign(key.shadow.camera,{left:-8,right:8,top:8,bottom:-8,near:.1,far:25});key.shadow.normalBias=.009;key.shadow.bias=-.00005;key.shadow.radius=4;scene.add(key); +for(const [color,intensity,pos] of [[0xf3f7ff,1.25,[6,4,-2]],[0xffffff,1.2,[-3,5,-5]]]){const light=new T.DirectionalLight(color,intensity);light.position.set(...pos);scene.add(light);} +const ground=new T.Mesh(new T.PlaneGeometry(30,30),new T.ShadowMaterial({opacity:.15}));ground.rotation.x=-Math.PI/2;ground.position.y=-.015;ground.receiveShadow=true;scene.add(ground); +const model=new T.Group();scene.add(model); +function box(parent,w,h,d,mat,x=0,y=0,z=0,r=.035){const m=new T.Mesh(new RoundedBoxGeometry(w,h,d,3,Math.min(r,h/3,w/3,d/3)),mats[mat]);m.position.set(x,y,z);m.castShadow=true;m.receiveShadow=true;parent.add(m);return m;} +function cylinder(parent,r,h,mat,x,y,z){const m=new T.Mesh(new T.CylinderGeometry(r,r,h,40,1),mats[mat]);m.position.set(x,y,z);m.castShadow=true;m.receiveShadow=true;parent.add(m);return m;} +function brick(parent,nx,nz,mat,x,y,z,rotation=0,height=.31){const g=new T.Group();g.position.set(x,y,z);g.rotation.y=rotation;parent.add(g);const unit=.42;box(g,nx*unit-.02,height,nz*unit-.02,mat,0,height/2,0,.035);for(let ix=0;ix0?.08:-.06),r);y+=h;} + // An open book resting on top, pages up. + const top=new T.Group();top.position.set(0,y,0);top.rotation.y=-.08;model.add(top); + for(const side of [-1,1]){const leaf=new T.Group();leaf.position.set(side*.88,.1,0);leaf.rotation.z=side*.12;top.add(leaf); + box(leaf,1.78,.07,1.75,'navy',0,-.03,0,.03);box(leaf,1.66,.15,1.63,'paper',0,.07,0,.02); + for(let r=0;r<5;r++)box(leaf,1.05,.008,.03,'ink',0,.15,-.5+r*.25,.003);} + brick(model,2,2,'orange',2.55,0,.85,-.2);brick(model,2,2,'yellow',2.55,.31,.85,-.2);brick(model,1,2,'grass',-2.35,0,1.0,.16); +} +function towerPair(x,swap){ + const g=new T.Group();g.position.x=x;model.add(g); + const bands=['navy','navy','azure','azure','teal','azure','navy']; + bands.forEach((c,level)=>{for(let a=0;a<2;a++)for(let b=0;b<2;b++){const odd=level%2?.21:0; + const col=(swap&&level===3&&a===1&&b===1)?'orange':c;brick(g,2,2,col,(a-.5)*.84,level*.31,(b-.5)*.84);}}); + brick(g,1,1,'yellow',0,bands.length*.31,0,0,.2); +} +function research(){ + towerPair(-1.25,false);towerPair(1.25,true); + // A magnifying glass held up to the second structure. + const lens=new T.Group();lens.position.set(.72,1.16,1.55);lens.rotation.set(-.2,.55,.0);model.add(lens); + const ring=new T.Mesh(new T.TorusGeometry(.72,.09,24,64),mats.red);ring.castShadow=true;lens.add(ring); + const glass=new T.Mesh(new T.CircleGeometry(.68,64),new T.MeshStandardMaterial({color:'#dff1fb',transparent:true,opacity:.3,roughness:.1,side:T.DoubleSide}));lens.add(glass); + const handle=new T.Mesh(new T.CylinderGeometry(.09,.09,1.05,24),mats.navy);handle.position.set(.72*.7+.37,-.72*.7-.37,0);handle.rotation.z=Math.PI/4;handle.castShadow=true;lens.add(handle); + brick(model,2,2,'orange',-2.6,0,1.3,.2);brick(model,1,2,'yellow',2.75,0,1.0,-.16); +} +function person(x,z,a,c,scale=1){ + const g=new T.Group();g.position.set(x,0,z);g.rotation.y=a;g.scale.setScalar(scale);model.add(g); + brick(g,2,1,c,0,0,0,0,.42);brick(g,2,1,c,0,.42,0,0,.42); + box(g,.3,.1,.2,'cream',0,.89,0,.03); + const head=new T.Mesh(new T.CylinderGeometry(.24,.24,.36,32),mats.yellow);head.position.set(0,1.11,0);head.castShadow=true;g.add(head); + cylinder(g,.11,.09,'yellow',0,1.33,0); +} +function community(){ + // People gathered in a circle, with a space left open and someone arriving. + const r=1.9,n=11,cols=['red','azure','grass','orange','purple','teal','yellow','azure','red','grass','purple']; + for(let i=0;i{for(let a=0;a