diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..4e84df5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Find collaborators or share an idea + url: https://github.com/orgs/THD-AI/discussions/categories/ideas + about: Meet people before you have a finished project. + - name: Membership or private questions + url: https://github.com/THD-AI/.github/blob/main/SUPPORT.md + about: Find the right contact without publishing personal information. diff --git a/.github/ISSUE_TEMPLATE/project-submission.yml b/.github/ISSUE_TEMPLATE/project-submission.yml new file mode 100644 index 0000000..198d12c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/project-submission.yml @@ -0,0 +1,72 @@ +name: Share a project +description: Add a personal or university project to the THD AI showroom. +title: "[Showroom] " +labels: [showcase-submission] +body: + - type: markdown + attributes: + value: "Share work you care about. Your repository can stay where it is. A maintainer reviews submissions before they appear in the showroom. Do not include credentials, private coursework, or personal documents." + - type: input + id: name + attributes: + label: Project name + validations: + required: true + - type: input + id: repository + attributes: + label: Public GitHub repository + placeholder: https://github.com/your-name/your-project + validations: + required: true + - type: textarea + id: purpose + attributes: + label: What does it do, and why did you make it? + description: A few clear sentences are enough. Include the current status and limitations. + validations: + required: true + - type: textarea + id: creators + attributes: + label: Creators and credits + description: Name the people who worked on it, link their public profiles, and credit material you used. + validations: + required: true + - type: input + id: demo + attributes: + label: Demo, screenshots, or setup instructions + description: Give someone a way to understand or try the project. + validations: + required: true + - type: dropdown + id: category + attributes: + label: Where does it fit? + multiple: true + options: + - Campus life + - AI & ML + - Learning + validations: + required: true + - type: textarea + id: collaboration + attributes: + label: What kind of company are you looking for? + description: Feedback, contributors, a new maintainer, or simply sharing something you enjoyed making. + - type: textarea + id: context + attributes: + label: A little context + description: How does the work connect to THD? You may also explain your process or tools, including AI assistance, if useful. No proof of enrolment or private documents are needed. + - type: checkboxes + id: permission + attributes: + label: Permission to share + options: + - label: I created this project or have its creators' permission to submit it, and the image/description can be displayed with credit. + required: true + - label: I have not included credentials, private student information, or restricted university material. + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..2a65585 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,10 @@ +## What changes? + +For a project submission, link its issue and explain the listing. + +## Checks + +- Project title, description, creators, status, and links are accurate. +- Image permission and source credit are recorded in `asset-sources.json` and the credits page when applicable. +- No credentials, private coursework, or private personal information are included. +- `node scripts/check.mjs` passes, and visible changes were checked in a browser. diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..b35425d --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,43 @@ +name: Check and publish showroom +on: + push: + branches: [main] + pull_request: + workflow_dispatch: +permissions: + contents: read + pages: read +concurrency: + group: showroom-${{ github.ref }} + cancel-in-progress: true +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Validate project data and public site + run: node scripts/check.mjs + - name: Check browser JavaScript syntax + run: node --check site/app.js + - name: Configure GitHub Pages + if: github.event_name != 'pull_request' + uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + - name: Package only the public website + if: github.event_name != 'pull_request' + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: site + publish: + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + needs: check + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Publish the checked website + id: deployment + uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 diff --git a/.github/workflows/submissions.yml b/.github/workflows/submissions.yml new file mode 100644 index 0000000..fa09c57 --- /dev/null +++ b/.github/workflows/submissions.yml @@ -0,0 +1,33 @@ +name: Welcome a project submission +on: + issues: + types: [opened] +permissions: + contents: read + issues: write +jobs: + welcome: + if: contains(github.event.issue.labels.*.name, 'showcase-submission') + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const body = context.payload.issue.body || ''; + const match = body.match(/https:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)/); + let repositoryNote = 'Please include a public GitHub repository so people can explore the work.'; + if (match) { + try { + const { data } = await github.rest.repos.get({ owner: match[1], repo: match[2] }); + repositoryNote = data.private + ? 'The repository is private. Please share a public project link; private material is not published here.' + : `The linked repository is publicly readable: **${data.full_name}**.`; + } catch { + repositoryNote = 'The linked repository could not be read publicly. Please check its URL and visibility.'; + } + } + await github.rest.issues.createComment({ + ...context.repo, + issue_number: context.issue.number, + body: `Thanks for sharing your work with THD AI.\n\n${repositoryNote}\n\nA maintainer will review the description, creator credits, image permissions, project status, and a way to try it. Acceptance is recorded in a pull request updating the showroom catalogue. This reply does not publish or approve the project.\n\nYour repository can stay in your own account. In the meantime, you can meet collaborators in [Discussions](https://github.com/orgs/THD-AI/discussions/categories/ideas).` + }); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a9e4082 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Help shape the showroom + +THD AI is an independent place for people connected to Deggendorf Institute of Technology to share work, find inspiration, and collaborate in their own time. Personal work and university projects belong alongside one another. AI-assisted work is welcome; quality, care, accurate credit, and clear limitations matter. + +## Share a project + +Use the [project submission form](https://github.com/THD-AI/thd-ai.github.io/issues/new?template=project-submission.yml). Include a public repository, its creators, a clear description, a way to try or understand it, and what kind of collaboration you want. You can keep the repository in your own account. Organization membership is not required to submit or contribute. + +The submission workflow leaves an acknowledgement and checks whether the repository is publicly readable. It does not approve or automatically publish a listing. A maintainer reviews the information and image permission, then opens a pull request to update `site/projects.json`. Keep source attribution in `asset-sources.json` and `site/credits/index.html`. Close the submission issue when the listing is published. An experimental project is welcome when it is described honestly and has a useful way in. + +To update or remove your own listing, open an issue or pull request. If you need to discuss something privately, [email Alexander](mailto:alex.rudaev@outlook.com). + +## Change the website + +The website is buildless HTML, CSS, and JavaScript in `site/`. It uses a local JSON catalogue, with no visitor accounts or database. Search and filters run in the browser. An external Google Fonts stylesheet provides the fonts; system sans-serif fonts are the fallback. + +1. Fork the repository or create a branch if you have write access. +2. Edit a focused piece of the site, add a credited project, or improve the documentation. +3. Run `node scripts/check.mjs` and `node --check site/app.js`. +4. Serve `site/` locally, for example with `python -m http.server 4173 --directory site`, and check the relevant desktop/mobile flow. +5. Open a pull request. The same validation runs in GitHub Actions. A checked merge to `main` publishes only `site/` to GitHub Pages. + +Do not put private archive files or credentials in `site/`. Use text content for project metadata, not injected HTML. Keep the approved campus banner and existing project branding intact unless a change is agreed with its creators. + +## Work together + +Members can create and lead public and private repositories. Use [Discussions](https://github.com/orgs/THD-AI/discussions) to meet people, share a work in progress, or ask a question. Follow the [community conduct guidance](https://github.com/THD-AI/.github/blob/main/CODE_OF_CONDUCT.md), give people credit, and respect each project's licence and decisions. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cb196eb --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +MIT License + +Copyright (c) 2026 Alexander Rudaev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +This licence covers the website's original code and documentation. Images, +archived artwork, project banners, and the projects themselves retain their +separate licences and credits in site/credits/index.html. diff --git a/asset-sources.json b/asset-sources.json new file mode 100644 index 0000000..ff53457 --- /dev/null +++ b/asset-sources.json @@ -0,0 +1,7 @@ +{ + "project-chexvision.png": "https://raw.githubusercontent.com/arudaev/chexvision/main/.github/banner.png", + "project-painfully-trivial.png": "https://raw.githubusercontent.com/arudaev/Painfully-Trivial/main/.github/banner.png", + "project-devablos.png": "https://raw.githubusercontent.com/THD-AI/Devablos-Project-V2/main/.github/banner.png", + "project-room.webp": "https://raw.githubusercontent.com/arudaev/THD-Room-Finder/main/website/assets/hero-day.webp", + "history-avatar-first-redesign.png": "https://github.com/THD-AI/.github/tree/main/archive/2026-10-17-first-redesign" +} diff --git a/scripts/check.mjs b/scripts/check.mjs new file mode 100644 index 0000000..601e927 --- /dev/null +++ b/scripts/check.mjs @@ -0,0 +1,74 @@ +import { readFile, access, readdir, stat } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; +import assert from 'node:assert/strict'; + +const root = fileURLToPath(new URL('../', import.meta.url)); +const site = path.join(root, 'site'); +const collection = JSON.parse(await readFile(path.join(site, 'projects.json'), 'utf8')); +const categories = new Set(['campus', 'ai', 'learning']); +const ids = new Set(); +assert(Array.isArray(collection.projects) && collection.projects.length > 0, 'Provide a non-empty project collection'); +function https(url, field) { + const parsed = new URL(url); + assert.equal(parsed.protocol, 'https:', `${field} must use HTTPS`); + assert(!parsed.username && !parsed.password, `${field} must not contain credentials`); + return parsed; +} +for (const project of collection.projects) { + assert(/^[a-z0-9-]+$/.test(project.id) && !ids.has(project.id), `Unique safe project ID: ${project.id}`); + ids.add(project.id); + for (const key of ['title', 'description', 'categoryLabel', 'status', 'collaboration']) { + assert(typeof project[key] === 'string' && project[key].trim(), `${project.id}: missing ${key}`); + } + assert(project.categories?.length && project.categories.every(c => categories.has(c)), `${project.id}: unknown category`); + const repository = https(project.repository, 'repository'); + assert.equal(repository.hostname, 'github.com', 'Project repository must be a public GitHub link'); + assert(/^\/[^/]+\/[^/]+\/?$/.test(repository.pathname), 'Use a repository URL, not a subpage'); + if (project.community) assert.equal(https(project.community, 'community').hostname, 'github.com'); + if (project.demo) https(project.demo, 'demo'); + assert(Array.isArray(project.creators) && project.creators.length > 0, `${project.id}: credit the creators`); + for (const creator of project.creators) { + assert(creator.name?.trim(), 'Creator name required'); + https(creator.url, 'creator'); + } + assert(Array.isArray(project.tags) && project.tags.every(t => typeof t === 'string'), 'Project tags must be strings'); + if (project.image) { + assert(project.image.startsWith('assets/') && !project.image.includes('..'), 'Project image must be a local asset'); + await access(path.join(site, project.image)); + assert(project.imageAlt?.trim(), 'Project image needs alt text'); + } +} +async function walk(folder) { + const files = []; + for (const entry of await readdir(folder)) { + const full = path.join(folder, entry); + if ((await stat(full)).isDirectory()) files.push(...await walk(full)); + else files.push(full); + } + return files; +} +const files = await walk(site); +for (const file of files.filter(f => f.endsWith('.html'))) { + const html = await readFile(file, 'utf8'); + assert(//.test(html), 'Document language required'); + assert(/[^<]+<\/title>/.test(html), 'Document title required'); + for (const match of html.matchAll(/(?:src|href)="([^"]+)"/g)) { + const target = match[1]; + if (/^(https:|mailto:)/.test(target)) continue; + if (target.startsWith('#')) { + assert(html.includes(`id="${target.slice(1)}"`), `${file}: missing section ${target}`); + continue; + } + const targetPath = target.split(/[?#]/)[0]; + const destination = targetPath.startsWith('/') ? path.resolve(site, '.' + targetPath) : path.resolve(path.dirname(file), targetPath); + assert(destination === site || destination.startsWith(site + path.sep), `${file}: path escapes site`); + await access(destination); + if ((await stat(destination)).isDirectory()) await access(path.join(destination, 'index.html')); + } + for (const image of html.matchAll(/]*>/g)) assert(/\balt="[^"]*"/.test(image[0]), 'Every image needs alt text'); +} +assert(files.every(f => !/\.git|\.env|\.pem|\.key$/.test(path.relative(site, f))), 'Only public website files may be deployed'); +assert(!files.some(f => /member-profile|\.github-private|first-redesign\/profile/.test(f)), 'Private archive content must not ship with the website'); +console.log(`Validated ${ids.size} credited projects, ${files.filter(f => f.endsWith('.html')).length} pages, local links, image alternatives, HTTPS URLs, and deployment boundaries.`); diff --git a/site/.nojekyll b/site/.nojekyll new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/site/.nojekyll @@ -0,0 +1 @@ + diff --git a/site/404.html b/site/404.html new file mode 100644 index 0000000..38efbce --- /dev/null +++ b/site/404.html @@ -0,0 +1,6 @@ + +Page not found – THD AI + + +

404 / A small detour

This shelf
is empty.

That page may have moved. The showroom and project collection are still here.

Back to the showroom ↗
+ diff --git a/site/app.js b/site/app.js new file mode 100644 index 0000000..0d75cb4 --- /dev/null +++ b/site/app.js @@ -0,0 +1,103 @@ +const grid = document.querySelector('#project-grid'); +const search = document.querySelector('#project-search'); +const filters = [...document.querySelectorAll('[data-filter]')]; +let projects = []; +let activeFilter = 'all'; + +function element(tag, className, text) { + const node = document.createElement(tag); + if (className) node.className = className; + if (text !== undefined) node.textContent = text; + return node; +} +function safeLink(text, url, className = '') { + const link = element('a', className, text); + const parsed = new URL(url); + if (parsed.protocol !== 'https:') throw new Error('Unsupported project link'); + link.href = parsed.href; + link.target = '_blank'; + link.rel = 'noopener noreferrer'; + return link; +} +function projectArt(project) { + const art = element('div', `project-art art-${project.id}`); + if (project.image) { + const img = element('img'); + img.src = project.image; + img.alt = project.imageAlt; + img.loading = 'lazy'; + img.width = 660; + img.height = 400; + art.append(img); + } else if (project.id === 'studybooks') { + art.setAttribute('aria-hidden', 'true'); + const books = element('div', 'book-art'); + for (const title of ['Learn it.', 'Keep it.']) { + const sheet = element('div', 'book-sheet'); + sheet.append(element('small', '', 'Course notes'), element('strong', '', title)); + for (let i = 0; i < 4; i++) sheet.append(element('span')); + books.append(sheet); + } + art.append(books); + } else { + art.setAttribute('aria-hidden', 'true'); + const network = element('div', 'network-art'); + for (const size of [3, 4, 2]) { + const column = element('div', 'node-column'); + for (let i = 0; i < size; i++) column.append(element('span', 'network-node')); + network.append(column); + } + art.append(network); + } + return art; +} +function card(project) { + const article = element('article', 'project-card'); + const meta = element('div', 'card-meta'); + meta.append(element('span', '', project.categoryLabel), element('span', 'card-status', project.status)); + const creators = element('p', 'project-creators'); + creators.append(document.createTextNode('By ')); + project.creators.forEach((creator, index) => { + if (index) creators.append(document.createTextNode(', ')); + creators.append(safeLink(creator.name, creator.url)); + }); + const bottom = element('div', 'card-bottom'); + bottom.append(safeLink('Explore the project ↗', project.repository)); + if (project.demo) bottom.append(safeLink(project.demoLabel || 'Try it ↗', project.demo)); + else if (project.community) bottom.append(safeLink('Build with others ↗', project.community)); + article.append(projectArt(project), meta, element('h3', '', project.title), element('p', 'project-description', project.description), creators, bottom, element('p', 'collaboration', project.collaboration)); + return article; +} +function render() { + const query = search.value.trim().toLocaleLowerCase(); + const visible = projects.filter(project => { + const haystack = [project.title, project.description, project.categoryLabel, ...project.tags, ...project.creators.map(c => c.name)].join(' ').toLocaleLowerCase(); + return (activeFilter === 'all' || project.categories.includes(activeFilter)) && haystack.includes(query); + }); + grid.replaceChildren(...visible.map(card)); + document.querySelector('#project-count').textContent = `${visible.length} ${visible.length === 1 ? 'project' : 'projects'} to explore`; + document.querySelector('#empty-state').hidden = visible.length !== 0; + for (const button of filters) button.setAttribute('aria-pressed', String(button.dataset.filter === activeFilter)); +} +async function loadProjects() { + document.querySelector('#load-error').hidden = true; + document.querySelector('#project-count').textContent = 'Opening the showroom…'; + try { + const response = await fetch(new URL('projects.json', import.meta.url)); + if (!response.ok) throw new Error(`Project data: ${response.status}`); + const data = await response.json(); + if (!Array.isArray(data.projects)) throw new Error('Invalid project collection'); + projects = data.projects; + render(); + } catch { + grid.replaceChildren(); + document.querySelector('#empty-state').hidden = true; + document.querySelector('#load-error').hidden = false; + document.querySelector('#project-count').textContent = 'Project catalogue unavailable'; + } +} +for (const button of filters) button.addEventListener('click', () => { activeFilter = button.dataset.filter; render(); }); +search.addEventListener('input', render); +document.querySelector('#reset-search').addEventListener('click', () => { search.value = ''; activeFilter = 'all'; render(); search.focus(); }); +document.querySelector('#retry-load').addEventListener('click', loadProjects); +loadProjects(); diff --git a/site/assets/campus.png b/site/assets/campus.png new file mode 100644 index 0000000..6cb8862 Binary files /dev/null and b/site/assets/campus.png differ diff --git a/site/assets/history-avatar-first-redesign.png b/site/assets/history-avatar-first-redesign.png new file mode 100644 index 0000000..4b04953 Binary files /dev/null and b/site/assets/history-avatar-first-redesign.png differ diff --git a/site/assets/history-community-hub.png b/site/assets/history-community-hub.png new file mode 100644 index 0000000..0bf3381 Binary files /dev/null and b/site/assets/history-community-hub.png differ diff --git a/site/assets/history-organization-avatar.png b/site/assets/history-organization-avatar.png new file mode 100644 index 0000000..3dd85c6 Binary files /dev/null and b/site/assets/history-organization-avatar.png differ diff --git a/site/assets/history-public-profile.png b/site/assets/history-public-profile.png new file mode 100644 index 0000000..2b4f0a3 Binary files /dev/null and b/site/assets/history-public-profile.png differ diff --git a/site/assets/history-schedule-2024.jpg b/site/assets/history-schedule-2024.jpg new file mode 100644 index 0000000..10ce1bc Binary files /dev/null and b/site/assets/history-schedule-2024.jpg differ diff --git a/site/assets/member-workspace.png b/site/assets/member-workspace.png new file mode 100644 index 0000000..4873b5e Binary files /dev/null and b/site/assets/member-workspace.png differ diff --git a/site/assets/project-chexvision.png b/site/assets/project-chexvision.png new file mode 100644 index 0000000..a7c6c51 Binary files /dev/null and b/site/assets/project-chexvision.png differ diff --git a/site/assets/project-devablos.png b/site/assets/project-devablos.png new file mode 100644 index 0000000..d4f9132 Binary files /dev/null and b/site/assets/project-devablos.png differ diff --git a/site/assets/project-painfully-trivial.png b/site/assets/project-painfully-trivial.png new file mode 100644 index 0000000..653e211 Binary files /dev/null and b/site/assets/project-painfully-trivial.png differ diff --git a/site/assets/project-room.webp b/site/assets/project-room.webp new file mode 100644 index 0000000..72c40a8 Binary files /dev/null and b/site/assets/project-room.webp differ diff --git a/site/assets/showcase.png b/site/assets/showcase.png new file mode 100644 index 0000000..b41402b Binary files /dev/null and b/site/assets/showcase.png differ diff --git a/site/assets/symbol.png b/site/assets/symbol.png new file mode 100644 index 0000000..eec5327 Binary files /dev/null and b/site/assets/symbol.png differ diff --git a/site/credits/index.html b/site/credits/index.html new file mode 100644 index 0000000..34c4bd8 --- /dev/null +++ b/site/credits/index.html @@ -0,0 +1,10 @@ + +Credits & licences – THD AI + + +

The people and sources behind the work

Give good work
its credit.

The showroom connects projects. It does not take ownership of them. Every project keeps its original creators, repository, and licence.

+

Campus artwork

The public campus banner adapts the campus illustration from THD Room Finder. Its source photograph is Hauptcampus der Technischen Hochschule Deggendorf 2018 by Danube-Waidler, licensed CC BY-SA 4.0. The repainted illustration and public banner are shared under that same licence. The banner widens the composition, changes the light and palette, simplifies people into grey silhouettes, and adds THD AI lettering.

The member-workspace and showcase illustrations follow the same visual family, using the Room Finder art as visual references. These new illustrations are also shared under CC BY-SA 4.0. The original public banner remains unchanged. Detailed source notes and production briefs live in the brand record.

+

The open-portal symbol

The THD AI symbol is an open architectural portal with a warm sun at its centre: room for a person, an idea, and what comes next. It replaces the earlier boxed initials. The new symbol and earlier typographic badge are community identity artwork shared under CC BY 4.0. No official university marks are used.

+

Project images and creators

  • THD Room Finder: Alexander Rudaev. The study-space illustration is taken from its original public repository; the project is GPL-3.0. Consult the project's artwork notes for asset-specific terms.
  • CheXVision: Alexander Rudaev. Original project banner from the source repository; its licence is retained.
  • Painfully Trivial: Alexander Rudaev, FaresM7, and TheSameerCode, as credited contributors. Original project banner from the source repository; its licence is retained.
  • Devablos V2: Alexander Rudaev and Denisa Iulia. Original banner from the archived project; GPL-3.0.
  • Course Studybooks and CheXVision Mini: Alexander Rudaev. The simple book and network motifs on this website are presentation graphics, rather than screenshots of either project.

Image source URLs are recorded in asset-sources.json. Historical visuals retain their original rights and are not relicensed by appearing in the archive.

+

Website

The original website code and documentation are MIT-licensed. This does not relicense the images or linked projects. The typefaces are Manrope and DM Sans, served by Google Fonts, with system sans-serif fallbacks. The site has no analytics, visitor accounts, or advertising.

For a credit correction, listing update, or removal request, open an issue or email Alexander.

+ diff --git a/site/history/index.html b/site/history/index.html new file mode 100644 index 0000000..c7084ad --- /dev/null +++ b/site/history/index.html @@ -0,0 +1,6 @@ + +Visual history – THD AI + + +

Earlier chapters / Visual history

A little history.
Room for what comes next.

THD AI began as a 2023 cohort experiment. The direction has changed, but the work that came before it still belongs in the story. These are preserved originals and the illustrations that followed.

2023 / Original public profile

2023 / Original public profile

The first cohort profile imagined students reaching toward what they could make together. The original image is preserved unchanged.

2023 / Community hub

2023 / Community hub

The early discussion hub paired collaborative learning with a painterly illustration.

2024 / A new semester

2024 / A new semester

A campus scene from the winter-semester calendar announcement. The schedule itself remains a dated historical record.

2023 / First organization avatar

2023 / First organization avatar

The organization’s original avatar, saved before the identity changed.

2026 / The first redesign

2026 / The first redesign

The initial THD AI typographic badge. Replaced by the open-portal symbol, and kept here as part of the process.

2026 / An open campus

2026 / An open campus

The approved public banner. Its soft campus light, lavender haze, and grey silhouettes set the direction for the current identity.

2026 / Room for ideas

2026 / Room for ideas

The member-workspace banner brings the same atmosphere indoors, around personal projects, collaboration, and coffee.

2026 / A shared showroom

2026 / A shared showroom

A new scene for showing work, asking questions, and finding people who care about the same things.

The member-only chapter is preserved too.

The original internal illustration and member documents stay in the private member archive. The first member-profile redesign is saved there as well. They have not been copied into this public page.

Open the member archive on GitHub ↗

The complete public archive includes the calendar-release variant and earlier iceberg asset. Browse the original files and versioned archive, or visit the visual-history discussion. A SHA-256 manifest records the saved public files. Artwork credits and licences.

+ diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..e1d337e --- /dev/null +++ b/site/index.html @@ -0,0 +1,64 @@ + + + + + + + THD AI – a place for the work you care about + + + + + + + + + + + + + + + + +
+
+
Independent projects · Deggendorf & beyond
+

A place for the work
you care about.

+

Personal projects. University experiments. Useful things.
A shared space for people around THD who love making them.

Find your next rabbit hole
+
THD's campus in soft afternoon light, with grey silhouettes on its paths. THD AI: Build something together.
It starts with curiosity. It gets better with company.
+
+ +
+

01 / The showroom

Take a look around.

Something useful. Something unusual.
Something that makes you want to start.

+
+
+ +
+

Opening the showroom…

+
+ + + +

The first projects get the shelf started. Yours can be next. A project can live in your own repository.

+
+ +
+

02 / Why this exists

Good work deserves
a little company.

Some projects start in a lecture. Some start with a small annoyance, a late-night idea, or a question you can't leave alone. They deserve a life beyond a submission folder.

THD AI is an independent place to show that work and meet people who care about it. For anyone connected to Deggendorf Institute of Technology with curiosity and a love for AI, software, and making things.

You choose your own project, pace, and collaborators. AI-assisted or made without AI, the important things are care, quality, honest credit, and something worth sharing.

Meet the people behind the projects
+ +
+ +
People sharing projects, laptops, and coffee in a softly lit campus space. Built with care. Shared with everyone.

Something caught your eye?

Ask a question. Try a project. Open a small pull request.
That's a perfectly good way to begin.

Find a conversation
+ + + +

Some ideas are better over coffee.

Want to join, collaborate, or connect work happening around THD? Email Alexander Rudaev. He reads it daily, weekends included.
A cup of coffee is a remarkably good opening argument.

Say hello
+
+ + + + diff --git a/site/projects.json b/site/projects.json new file mode 100644 index 0000000..b19c10c --- /dev/null +++ b/site/projects.json @@ -0,0 +1,94 @@ +{ + "projects": [ + { + "id": "room", + "title": "THD Room Finder", + "description": "Find a free room on campus and see how long it stays free. A small, practical answer to a familiar THD problem.", + "categories": ["campus"], + "categoryLabel": "Campus life · PWA & Android", + "status": "Working app", + "repository": "https://github.com/arudaev/THD-Room-Finder", + "community": "https://github.com/THD-AI/THD-Room-Finder", + "demo": "https://thd-room-finder.vercel.app/", + "demoLabel": "Find a room ↗", + "creators": [{"name": "Alexander Rudaev", "url": "https://github.com/arudaev"}], + "image": "assets/project-room.webp", + "imageAlt": "THD Room Finder: a softly lit campus study space.", + "tags": ["React", "TypeScript", "campus", "rooms", "PWA"], + "collaboration": "Community fork available · original project stays upstream" + }, + { + "id": "chexvision", + "title": "CheXVision", + "description": "A deep-learning course project exploring chest X-ray pathology detection with PyTorch. Research to learn from, with its limitations in view.", + "categories": ["ai", "learning"], + "categoryLabel": "AI & ML · Computer vision", + "status": "Course project", + "repository": "https://github.com/arudaev/chexvision", + "community": "https://github.com/THD-AI/chexvision", + "demo": "https://arudaev.github.io/chexvision/", + "demoLabel": "See the slides ↗", + "creators": [{"name": "Alexander Rudaev", "url": "https://github.com/arudaev"}], + "image": "assets/project-chexvision.png", + "imageAlt": "CheXVision's original one-line drawing project banner.", + "tags": ["PyTorch", "computer vision", "deep learning", "X-ray"], + "collaboration": "Educational research · not for clinical use" + }, + { + "id": "painfully-trivial", + "title": "Painfully Trivial", + "description": "Making machine learning meet ordinary life: experiments in waste sorting assistance and train delay prediction.", + "categories": ["ai"], + "categoryLabel": "AI & ML · Everyday problems", + "status": "Course experiments", + "repository": "https://github.com/arudaev/Painfully-Trivial", + "community": "https://github.com/THD-AI/Painfully-Trivial", + "creators": [{"name": "Alexander Rudaev", "url": "https://github.com/arudaev"}, {"name": "FaresM7", "url": "https://github.com/FaresM7"}, {"name": "TheSameerCode", "url": "https://github.com/TheSameerCode"}], + "image": "assets/project-painfully-trivial.png", + "imageAlt": "Painfully Trivial's original one-line drawing banner.", + "tags": ["machine learning", "waste sorting", "prediction", "Python"], + "collaboration": "Community fork available · original authors credited" + }, + { + "id": "devablos", + "title": "Devablos V2", + "description": "An Internet Technologies project that grew into a full-stack chatbot, with a React client, a Node.js backend, and real-time conversation.", + "categories": ["ai", "learning"], + "categoryLabel": "AI & ML · Web development", + "status": "Historical project", + "repository": "https://github.com/THD-AI/Devablos-Project-V2", + "creators": [{"name": "Alexander Rudaev", "url": "https://github.com/arudaev"}, {"name": "Denisa Iulia", "url": "https://github.com/Denisa-Iulia"}], + "image": "assets/project-devablos.png", + "imageAlt": "Devablos V2's original project banner.", + "tags": ["React", "Node.js", "chatbot", "WebSocket"], + "collaboration": "Archived source · fork it to take the idea further" + }, + { + "id": "studybooks", + "title": "Course Studybooks", + "description": "Student-written explanations, revision notes, and cheatsheets. Self-contained HTML studybooks that stay readable online, offline, and on a phone.", + "categories": ["learning"], + "categoryLabel": "Learning · Shared knowledge", + "status": "Reading collection", + "repository": "https://github.com/THD-AI/studybooks", + "demo": "https://thd-ai.github.io/studybooks/", + "demoLabel": "Open a studybook ↗", + "creators": [{"name": "Alexander Rudaev", "url": "https://github.com/arudaev"}], + "tags": ["notes", "courses", "revision", "HTML", "learning"], + "collaboration": "Corrections and original explanations welcome" + }, + { + "id": "chexvision-mini", + "title": "CheXVision Mini", + "description": "A neural network built from scratch with NumPy. A companion project for understanding what happens beneath a deep-learning framework.", + "categories": ["ai", "learning"], + "categoryLabel": "Learning · From first principles", + "status": "Learning experiment", + "repository": "https://github.com/arudaev/chexvision-mini", + "community": "https://github.com/THD-AI/chexvision-mini", + "creators": [{"name": "Alexander Rudaev", "url": "https://github.com/arudaev"}], + "tags": ["NumPy", "neural network", "Python", "from scratch"], + "collaboration": "Community fork available · explore the implementation" + } + ] +} diff --git a/site/robots.txt b/site/robots.txt new file mode 100644 index 0000000..b6be4ce --- /dev/null +++ b/site/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Allow: / +Sitemap: https://thd-ai.github.io/sitemap.xml diff --git a/site/sitemap.xml b/site/sitemap.xml new file mode 100644 index 0000000..98fc63c --- /dev/null +++ b/site/sitemap.xml @@ -0,0 +1 @@ +https://thd-ai.github.io/https://thd-ai.github.io/history/https://thd-ai.github.io/credits/ diff --git a/site/style.css b/site/style.css new file mode 100644 index 0000000..0dbb622 --- /dev/null +++ b/site/style.css @@ -0,0 +1,5 @@ +@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;450;500;550;600;650;700&family=Manrope:wght@400;500;600;650;700;750;800&display=swap'); +:root{--ink:#242667;--text:#45455d;--muted:#6a697e;--paper:#faf9fd;--lavender:#eeedf8;--line:#dcdbe9;--blue:#dfe6fc;--peach:#f0dad0;--sage:#e5ece3;--heading:'Manrope',sans-serif;--body:'DM Sans',sans-serif;color-scheme:light;scroll-behavior:smooth;scroll-padding-top:30px}*{box-sizing:border-box}body{margin:0;background:var(--paper);font:16px/1.65 var(--body);color:var(--text)}a{color:var(--ink);text-decoration-thickness:1px;text-underline-offset:4px}button,input{font:inherit}button,a,input{-webkit-tap-highlight-color:transparent}button{cursor:pointer}button:focus-visible,a:focus-visible,input:focus-visible{outline:3px solid #747bc0;outline-offset:5px}img{display:block;max-width:100%;height:auto}h1,h2,h3,p,figure{margin:0}h1,h2,h3{font-family:var(--heading);color:var(--ink)}h2{font-size:clamp(32px,3.4vw,49px);line-height:1.2;letter-spacing:-2px;font-weight:650}h3{font-size:21px;line-height:1.4;letter-spacing:-.5px}p+p{margin-top:20px}[hidden]{display:none!important}.wrap{width:min(1240px,calc(100% - 96px));margin-inline:auto}.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.skip-link{position:absolute;left:16px;top:-100px;padding:12px 20px;background:var(--ink);color:white;z-index:10}.skip-link:focus{top:12px}.site-header{height:112px;display:flex;align-items:center;justify-content:space-between;gap:24px}.identity{display:flex;align-items:center;gap:10px;text-decoration:none;color:var(--ink);font:650 27px/1 var(--heading);letter-spacing:-1.3px;white-space:nowrap}.identity img{width:48px;height:48px;object-fit:contain}.identity b{font-weight:450}.site-header nav{display:flex;gap:30px;font-size:14px}.site-header nav a{text-decoration:none}.header-action{font-size:14px;font-weight:600;text-decoration:none;padding:11px 17px;border:1px solid #c9c8df;border-radius:6px}.header-action span{margin-left:15px}.intro{padding-top:58px}.eyebrow{display:flex;align-items:center;gap:9px;text-transform:uppercase;letter-spacing:1.9px;font-size:11px;line-height:1.5;font-weight:650;color:var(--muted)}.little-sun{display:inline-block;width:9px;height:9px;background:#d9ada1;border-radius:50%;flex-shrink:0}.intro h1{font-size:clamp(48px,6.6vw,88px);line-height:1.13;font-weight:550;letter-spacing:-4.7px;margin:24px 0 32px}.intro h1 em{font-style:normal;color:#7277b0}.intro-bottom{display:flex;align-items:end;justify-content:space-between;gap:28px;margin-bottom:38px}.intro-bottom p{font-size:17px;line-height:1.7}.text-link{font-size:14px;font-weight:550;text-decoration:none;border-bottom:1px solid #b7b6cb;padding-bottom:5px;display:inline-flex;align-items:center;gap:24px;width:max-content;max-width:100%}.text-link:hover{border-bottom-color:var(--ink)}.campus-art img{width:100%;aspect-ratio:3/1;object-fit:cover;border-radius:7px}.campus-art figcaption{font-size:11px;color:var(--muted);margin-top:11px;letter-spacing:.25px;text-align:right}.showroom{padding-top:103px}.section-heading{display:flex;align-items:end;justify-content:space-between;gap:32px;margin-bottom:34px}.section-heading .eyebrow{margin-bottom:16px}.section-heading>p{font-size:14px;color:var(--muted);line-height:1.7}.catalogue-tools{display:flex;justify-content:space-between;align-items:center;gap:16px;padding:20px 0;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.filters{display:flex;flex-wrap:wrap;gap:7px}.filters button{background:none;color:var(--muted);border:0;padding:8px 13px;border-radius:5px;font-size:13px}.filters button[aria-pressed=true]{color:var(--ink);background:#e8e7f4;font-weight:600}.filters button:hover{color:var(--ink);background:#f0eff7}.search{display:flex;align-items:center;gap:9px;color:var(--muted)}.search input{width:230px;background:transparent;border:0;font-size:13px;color:var(--ink);padding:8px 0}.search input::placeholder{color:var(--muted)}.result-count{font-size:11px;color:var(--muted);margin:17px 0 22px;letter-spacing:.3px}.project-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:36px 26px}.project-card{display:flex;flex-direction:column;min-width:0;transition:transform .2s}.project-card:hover{transform:translateY(-3px)}.project-art{position:relative;overflow:hidden;border-radius:6px;background:#e8e8f1;aspect-ratio:1.65}.project-art img{width:100%;height:100%;object-fit:cover;transition:transform .35s}.project-card:hover .project-art img{transform:scale(1.025)}.art-room img{object-position:center}.art-chexvision img,.art-painfully-trivial img,.art-devablos img{object-fit:contain;background:#f1f0f7}.art-studybooks{background:#e3e7f6;padding:25px!important}.art-chexvision-mini{background:#eae4f2;padding:25px!important}.book-art{height:100%;display:grid;grid-template-columns:1fr 1fr;gap:12px;transform:rotate(-4deg);color:var(--ink)}.book-sheet{background:#faf9fd;box-shadow:0 8px 22px #282b5b13;border-radius:3px;padding:16px;display:flex;flex-direction:column;gap:7px;justify-content:center}.book-sheet strong{font:600 clamp(14px,1.5vw,22px)/1.3 var(--heading);letter-spacing:-.5px}.book-sheet span{height:3px;background:#c6c9e2;width:85%;border-radius:2px}.book-sheet span:nth-last-child(1){width:55%}.book-sheet small{font-size:8px;text-transform:uppercase;letter-spacing:1.5px;color:#73769d;margin-bottom:7px}.network-art{height:100%;display:flex;align-items:center;justify-content:center;gap:28px;transform:rotate(-5deg)}.node-column{display:grid;gap:13px}.network-node{width:22px;height:22px;background:#8b8cbb;border-radius:50%;box-shadow:0 0 0 5px #ffffff58}.node-column:nth-child(2) .network-node{background:#aaaad0}.node-column:nth-child(3) .network-node{background:#d4b4af}.card-meta{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:17px 0 10px;font-size:9px;line-height:1.4;text-transform:uppercase;letter-spacing:1px;color:var(--muted)}.card-status{border:1px solid #d9d8e7;border-radius:20px;padding:3px 8px;white-space:nowrap;font-size:9px;letter-spacing:.25px;text-transform:none}.project-card h3{font-size:22px;font-weight:650;letter-spacing:-.7px}.project-description{font-size:13px;line-height:1.75;margin-top:8px;flex:1}.project-creators{font-size:11px;line-height:1.65;margin-top:15px;color:var(--muted)}.project-creators a{color:inherit}.card-bottom{margin-top:17px;padding-top:13px;border-top:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;gap:8px}.card-bottom a{font-size:12px;text-decoration:none;font-weight:550}.card-bottom a:hover{text-decoration:underline}.collaboration{color:var(--muted);font-size:10px;margin-top:11px}.shelf-note{display:flex;align-items:center;gap:12px;margin:38px 0 0;padding:20px 0;font-size:12px;color:var(--muted)}.empty-state{text-align:center;padding:65px 20px;background:#f0eff8;border-radius:6px}.empty-state p{margin:12px 0 20px;font-size:14px}.about{display:grid;grid-template-columns:1.1fr 1fr;gap:100px;padding-top:100px;padding-bottom:100px;border-top:1px solid var(--line);margin-top:62px}.about-copy .eyebrow{margin-bottom:18px}.about-copy h2{margin-bottom:28px}.about-copy>p:not(.eyebrow){font-size:15px;line-height:1.9}.about-copy .text-link{margin-top:25px}.principles{background:#eeedf6;padding:32px 35px;border-radius:7px;align-self:start;margin-top:28px}.small-label{font-size:10px;letter-spacing:1.6px;text-transform:uppercase;color:var(--muted)}.principles>div{position:relative;padding:24px 0 24px 35px;border-bottom:1px solid #d9d8e8}.principles>div:last-child{padding-bottom:0;border-bottom:0}.principles>div>span{position:absolute;left:0;top:28px;font-size:10px;color:#7779a0}.principles h3{font-size:17px;letter-spacing:-.35px}.principles p{font-size:12px;margin-top:7px;line-height:1.8}.gathering>img{width:100%;border-radius:7px;aspect-ratio:3/1;object-fit:cover}.gathering-caption{display:flex;align-items:center;justify-content:space-between;gap:20px;padding-top:25px}.gathering-caption h2{font-size:24px;letter-spacing:-.9px;max-width:250px}.gathering-caption p{font-size:12px;color:var(--muted);line-height:1.7}.share{display:grid;grid-template-columns:1.1fr 1fr;gap:100px;padding:100px 0 80px;margin-top:78px;border-top:1px solid var(--line)}.share h2{margin:17px 0 25px}.share>div>p:not(.eyebrow):not(.quiet){font-size:15px;line-height:1.9;max-width:500px}.share-actions{display:flex;flex-wrap:wrap;gap:12px;margin-top:28px}.button{display:inline-flex;justify-content:center;align-items:center;gap:24px;padding:12px 18px;border:1px solid transparent;border-radius:5px;font-size:13px;line-height:1.5;text-decoration:none;font-weight:550}.primary{background:var(--ink);color:white}.primary:hover{background:#383b80}.secondary{color:var(--ink);background:transparent;border-color:#c8c7dc}.secondary:hover{background:#eeedf6}.quiet{font-size:11px;color:var(--muted);line-height:1.8;margin-top:19px}.share-steps{list-style:none;padding:0;margin:44px 0 0}.share-steps li{display:flex;gap:24px;border-top:1px solid var(--line);padding:24px 0}.share-steps li>span{font-size:11px;color:#7f80a5;padding-top:4px}.share-steps h3{font-size:18px}.share-steps p{font-size:12px;color:var(--muted);margin-top:6px}.coffee{display:flex;gap:25px;align-items:center;background:#efedf7;border-radius:7px;padding:32px 38px;margin-bottom:85px}.coffee-mark{font-size:30px}.coffee h2{font-size:23px;letter-spacing:-.6px}.coffee p{font-size:12px;line-height:1.8;margin-top:10px}.coffee>.text-link{margin-left:auto;flex-shrink:0}.site-footer{display:grid;grid-template-columns:1fr auto 1fr;gap:50px;padding:38px 0 35px;border-top:1px solid var(--line);align-items:start}.site-footer .identity{font-size:23px}.site-footer .identity img{width:38px;height:38px}.site-footer p{font-size:10px;color:var(--muted);line-height:1.9;margin-top:15px}.footer-links{display:flex;flex-wrap:wrap;gap:11px 22px;max-width:280px;padding-top:7px}.footer-links a{font-size:11px;text-decoration:none}.site-footer .footer-note{text-align:right;margin-top:8px}.history-main{padding:60px 0 90px}.history-main>h1{font-size:clamp(42px,5vw,70px);letter-spacing:-3px;line-height:1.15;margin:22px 0}.history-main>.lede{font-size:17px;max-width:690px;margin-bottom:55px}.history-grid{display:grid;grid-template-columns:1fr 1fr;gap:45px 28px}.history-item h2{font-size:24px;letter-spacing:-.7px;margin:16px 0 8px}.history-item p{font-size:13px;color:var(--muted)}.history-item img{width:100%;height:230px;object-fit:cover;border-radius:6px;background:#e8e7f3}.history-item.logo-history img{object-fit:contain;padding:20px}.history-private{margin-top:55px;padding:30px;background:var(--lavender);border-radius:6px}.history-private h2{font-size:24px;letter-spacing:-.8px}.history-private p{font-size:14px;margin:14px 0}.credits-copy{max-width:800px}.credits-copy h2{font-size:26px;letter-spacing:-1px;margin:36px 0 12px}.credits-copy p,.credits-copy li{font-size:14px}.credits-copy ul{padding-left:20px} +.project-description{font-size:14px}.project-creators{font-size:12px}.card-meta{font-size:10px}.card-bottom a{font-size:13px}@media(min-width:1600px){.wrap{width:1280px}}@media(max-width:1000px){.wrap{width:calc(100% - 64px)}.site-header nav{gap:18px}.project-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:32px 24px}.intro h1{letter-spacing:-3px}.about,.share{gap:50px}.gathering-caption{flex-wrap:wrap}.gathering-caption h2{max-width:none}.site-footer{gap:25px}.coffee{padding:28px}.book-sheet strong{font-size:23px}}@media(max-width:700px){.wrap{width:calc(100% - 40px)}.site-header{height:87px;gap:12px}.site-header .identity{font-size:24px}.site-header .identity img{width:38px;height:38px}.site-header nav{display:none}.header-action{font-size:11px;padding:9px 10px}.header-action span{margin-left:5px}.intro{padding-top:35px}.eyebrow{font-size:9px;letter-spacing:1.4px}.intro h1{font-size:clamp(41px,8.8vw,61px);letter-spacing:-2.4px;line-height:1.18;margin:22px 0}.intro-bottom{display:block;margin-bottom:25px}.intro-bottom p{font-size:14px}.intro-bottom .text-link{margin-top:20px}.campus-art img{min-height:0;object-fit:contain}.campus-art figcaption{text-align:left;font-size:10px}.showroom{padding-top:63px}.section-heading{align-items:start;flex-direction:column;gap:17px;margin-bottom:25px}.section-heading>p{font-size:12px}.section-heading>p br{display:none}h2{font-size:34px;letter-spacing:-1.5px}.catalogue-tools{align-items:stretch;flex-direction:column;gap:13px}.filters{gap:4px}.filters button{font-size:11px;padding:8px 10px}.search{border-top:1px solid var(--line);padding-top:8px}.search input{width:100%;font-size:13px}.project-grid{gap:32px 16px}.project-card h3{font-size:18px;letter-spacing:-.5px}.project-description{font-size:12px}.card-meta{align-items:start;gap:5px;flex-direction:column;font-size:8px;margin:13px 0 9px}.project-art{aspect-ratio:1.35}.project-creators{font-size:10px}.card-bottom{gap:7px;align-items:start;flex-direction:column}.card-bottom a{font-size:11px}.collaboration{font-size:9px}.art-studybooks,.art-chexvision-mini{padding:15px!important}.book-art{gap:5px}.book-sheet{padding:8px}.book-sheet strong{font-size:12px}.book-sheet small{font-size:6px}.book-sheet span{height:2px}.network-art{gap:13px}.network-node{width:14px;height:14px;box-shadow:0 0 0 3px #fff5}.node-column{gap:9px}.shelf-note{align-items:start;font-size:11px;margin-top:22px}.shelf-note .little-sun{margin-top:6px}.about{grid-template-columns:1fr;gap:15px;padding-top:60px;padding-bottom:60px;margin-top:35px}.about-copy>p:not(.eyebrow){font-size:14px}.principles{padding:25px;margin-top:20px}.gathering-caption{gap:14px;padding-top:21px}.gathering-caption h2{font-size:23px}.gathering-caption>p{width:100%}.gathering-caption .text-link{margin-top:5px}.share{grid-template-columns:1fr;gap:14px;padding-top:60px;padding-bottom:45px;margin-top:55px}.share>div>p:not(.eyebrow):not(.quiet){font-size:14px}.share-steps{margin-top:25px}.share-actions{gap:10px}.button{font-size:12px;padding:11px 13px;gap:14px}.coffee{align-items:start;padding:25px;flex-wrap:wrap;gap:13px;margin-bottom:55px}.coffee-mark{font-size:24px}.coffee>div:nth-child(2){flex:1;min-width:200px}.coffee h2{font-size:21px;line-height:1.4}.coffee>.text-link{margin-left:37px}.site-footer{grid-template-columns:1fr;gap:22px;padding-top:30px}.footer-links{max-width:100%;gap:13px 22px}.site-footer .footer-note{text-align:left}.history-main{padding-top:35px}.history-main>h1{font-size:45px;letter-spacing:-2px}.history-main>.lede{font-size:15px;margin-bottom:35px}.history-grid{gap:30px;grid-template-columns:1fr}.history-item img{height:auto;max-height:360px;object-fit:contain}.history-item.logo-history img{height:220px}.history-item h2{font-size:23px}.history-private{padding:25px}}@media(max-width:380px){.project-grid{grid-template-columns:1fr}.project-art{aspect-ratio:1.65}.card-meta{flex-direction:row}.card-bottom{flex-direction:row}.project-card h3{font-size:22px}.project-description{font-size:13px}}@media(prefers-reduced-motion:reduce){:root{scroll-behavior:auto}*,*:before,*:after{transition:none!important;animation:none!important}} + +@media(max-width:540px){.project-grid{grid-template-columns:1fr}.project-art{aspect-ratio:1.75}.project-card h3{font-size:22px}.project-description{font-size:14px}.card-meta{flex-direction:row;font-size:9px}.card-bottom{flex-direction:row}.card-bottom a{font-size:12px}.project-creators{font-size:11px}.book-sheet strong{font-size:23px}.book-sheet small{font-size:9px}.book-sheet{padding:18px}.book-art{gap:12px}.network-node{width:23px;height:23px}.network-art{gap:30px}}