forked from phc/website
1
0
Fork 0

Compare commits

..

1 Commits
main ... dev

@ -1,6 +1,6 @@
# This file defines a Drone pipeline that builds a static website with "npm run build". This
# pipeline must be marked as "Trusted" in the Drone project settings.
#
#
# We mount the target directory of the project at "/var/www/{project}" to the container
# "dist/" directory and the run the build. A caveat is that the container builds files
# with "root" permissions, so we need to fix those after each build with a second pipeline.
@ -9,26 +9,26 @@ kind: pipeline
name: default
steps:
- name: deploy
image: node:latest
volumes:
- name: host-website-dist
path: /mnt/website
commands:
- npm install
- npm run build
- cp -rT ./dist /mnt/website
- name: deploy
image: node:latest
volumes:
- name: host-website-dist
path: /mnt/website
commands:
- npm install
- npm run build
- cp -rT ./dist /mnt/website
volumes:
- name: host-website-dist
host: # this volume is mounted on the host machine
path: /var/www/website
- name: host-website-dist
host: # this volume is mounted on the host machine
path: /var/www/website
trigger:
branch:
- main
- main
event:
- push
- push
---
kind: pipeline
@ -36,15 +36,15 @@ type: exec # this job is executed on the host machine
name: caddy-permissions
depends_on:
- default
- default
steps:
- name: chown
commands:
- chown -R caddy:caddy /var/www/website
- name: chown
commands:
- chown -R caddy:caddy /var/www/website
trigger:
branch:
- main
- main
event:
- push
- push

@ -0,0 +1,9 @@
{
"printWidth": 110,
"singleQuote": true,
"quoteProps": "consistent",
"tabWidth": 4,
"useTabs": false,
"semi": false,
"arrowParens": "avoid"
}

@ -1,27 +0,0 @@
/** @type {import("prettier").Config} */
export default {
printWidth: 120,
singleQuote: true,
quoteProps: 'consistent',
tabWidth: 4,
useTabs: false,
semi: false,
arrowParens: 'avoid',
plugins: ['prettier-plugin-astro'],
overrides: [
{
files: '*.astro',
options: {
parser: 'astro',
},
},
{
files: '*.{yml,yaml,json}',
excludeFiles: 'package-lock.json',
options: {
tabWidth: 2,
},
},
],
}

@ -1,11 +1,3 @@
{
"npm.packageManager": "bun",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[astro]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[yaml]": {
"editor.tabSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
"npm.packageManager": "bun"
}

@ -17,12 +17,19 @@ bun dev
## Build
```bash
bun run build
bun build
```
## Deploy
## Deploy [TODO]
Per ora c'è un file `.drone.yml` che viene usato per il deploy su un server remoto utilizzando Drone per il CD. Al momento il sito è solo statico e non ha ancora una backend.
Il progetto contiene un `Dockerfile` che viene usato per il deploy (del server prodotto da Astro).
```bash
docker build -t phc-website .
docker run -p 3000:3000 phc-website
```
C'è anche un `.drone.yml` che viene usato per il deploy su un server remoto utilizzando Drone per il CD.
## Come Contribuire
@ -32,4 +39,4 @@ Sentitevi liberi di aprire una PR per qualsiasi modifica o aggiunta al sito web.
### Cose da fare
- Aggiungere guide in [src/content/guides/](./src/content/guides/)
- Aggiungere guide in [src/content/guides/](./src/content/guides/)

@ -18,11 +18,6 @@ export default defineConfig({
theme: 'github-light',
},
},
integrations: [
preact({
compat: true,
}),
mdx(),
],
integrations: [preact(), mdx()],
output: 'static',
})

Binary file not shown.

11125
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,51 +1,47 @@
{
"name": "website",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "run-s astro:sync astro:dev",
"build": "run-s astro:build",
"astro:sync": "astro sync",
"astro:dev": "astro dev",
"astro:build": "astro check && astro build"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/node": "9.0.0",
"@astrojs/preact": "4.0.0",
"@fontsource-variable/material-symbols-outlined": "^5.1.1",
"@fontsource/iosevka": "^5.0.11",
"@fontsource/mononoki": "^5.0.11",
"@fontsource/open-sans": "^5.0.24",
"@fontsource/source-code-pro": "^5.0.16",
"@fontsource/source-sans-pro": "^5.0.8",
"@fontsource/space-mono": "^5.0.20",
"@phosphor-icons/core": "^2.1.1",
"@phosphor-icons/react": "^2.1.7",
"@preact/signals": "^1.3.0",
"@types/jsdom": "^21.1.7",
"astro": "5.1.0",
"fuse.js": "^7.0.0",
"katex": "^0.16.9",
"lucide-static": "^0.468.0",
"marked": "^15.0.6",
"preact": "^10.19.6",
"typescript": "^5.3.3"
},
"devDependencies": {
"@astrojs/mdx": "4.0.2",
"@rollup/plugin-yaml": "^4.1.2",
"@types/katex": "^0.16.7",
"jsdom": "^24.1.1",
"linkedom": "^0.18.4",
"npm-run-all": "^4.1.5",
"prettier": "^3.5.0",
"prettier-plugin-astro": "^0.14.1",
"rehype-autolink-headings": "^7.1.0",
"rehype-slug": "^6.0.0",
"remark-math": "^6.0.0",
"remark-toc": "^9.0.0",
"sass": "^1.71.1",
"tsx": "^4.7.1"
}
"name": "website",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "run-s astro:sync astro:dev",
"build": "run-s astro:build",
"astro:sync": "astro sync",
"astro:dev": "astro dev",
"astro:build": "astro check && astro build"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/node": "9.0.0",
"@astrojs/preact": "4.0.0",
"@fontsource-variable/material-symbols-outlined": "^5.1.1",
"@fontsource/iosevka": "^5.0.11",
"@fontsource/mononoki": "^5.0.11",
"@fontsource/open-sans": "^5.0.24",
"@fontsource/source-code-pro": "^5.0.16",
"@fontsource/source-sans-pro": "^5.0.8",
"@fontsource/space-mono": "^5.0.20",
"@phosphor-icons/core": "^2.1.1",
"@preact/signals": "^1.3.0",
"@types/jsdom": "^21.1.7",
"astro": "5.1.0",
"fuse.js": "^7.0.0",
"katex": "^0.16.9",
"lucide-static": "^0.468.0",
"preact": "^10.19.6",
"typescript": "^5.3.3"
},
"devDependencies": {
"@astrojs/mdx": "4.0.2",
"@rollup/plugin-yaml": "^4.1.2",
"@types/katex": "^0.16.7",
"jsdom": "^24.1.1",
"linkedom": "^0.18.4",
"npm-run-all": "^4.1.5",
"rehype-autolink-headings": "^7.1.0",
"rehype-slug": "^6.0.0",
"remark-math": "^6.0.0",
"remark-toc": "^9.0.0",
"sass": "^1.71.1",
"tsx": "^4.7.1"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

@ -34,14 +34,21 @@ export const ComboBox = ({
}, [])
return (
<div class="combobox" ref={comboRef} style={{ width: isMobile() ? undefined : itemWidth + 48 + 'px' }}>
<div
class="combobox"
ref={comboRef}
style={{ width: isMobile() ? undefined : itemWidth + 48 + 'px' }}
>
<div class="selected" onClick={() => setOpen(!open)}>
<div class="content">{children[value]}</div>
{/* <span class="material-symbols-outlined">expand_more</span> */}
<PhosphorIcon name="caret-down" />
</div>
{open && (
<div class={clsx('dropdown', cloak && 'invisible')} ref={el => el && setItemWidth(el.offsetWidth)}>
<div
class={clsx('dropdown', cloak && 'invisible')}
ref={el => el && setItemWidth(el.offsetWidth)}
>
{Object.keys(children).map(key => (
<div
class="option"

@ -1,119 +0,0 @@
import { useEffect, useState } from 'preact/hooks'
import { Funnel } from '@phosphor-icons/react'
import { marked } from 'marked'
import extendedLatex from '@/client/lib/marked-latex'
marked.use(
extendedLatex({
lazy: false,
render: (formula: string, display: boolean) => {
return display ? '$$' + formula + '$$' : '$' + formula + '$'
},
}),
)
import type { Database } from '@/data/domande-esami.yaml'
const useRemoteValue = <T,>(url: string): T | null => {
const [value, setValue] = useState<T | null>(null)
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(value => setValue(value))
.catch(error => console.error(error))
}, [url])
return value
}
type Props = {
course: string
}
export const DomandeEsamiCourse = ({ course }: Props) => {
const database = useRemoteValue<Database>(`/domande-esami/api/${course}.json`)
if (!database) {
return <>Loading...</>
}
if ('requestIdleCallback' in window) {
// @ts-ignore
requestIdleCallback(() => window.renderMath())
} else {
// @ts-ignore
setTimeout(() => window.renderMath(), 100)
}
const courseTags = [
...new Set(
database.questions.filter(question => question.course === course).flatMap(question => question.tags),
),
]
const [selectedTag, setSelectedTag] = useState<string | null>(null)
const filteredQuestions = database.questions
.filter(question => question.course === course)
.filter(question => (selectedTag ? question.tags.includes(selectedTag) : true))
return (
<>
<div class="grid-center text-center">
<h3>
<a href="/domande-esami">Domande Orali</a>
</h3>
<h1>{database.names[course]}</h1>
</div>
{courseTags.length > 1 && (
<div class="card filter">
<div class="grid-h">
<Funnel />
<strong>Filtra Tag</strong>
</div>
<div class="flex-row-wrap">
{!selectedTag
? courseTags.map(tag => (
<div class="chip clickable" onClick={() => setSelectedTag(tag)}>
{tag}
</div>
))
: courseTags.map(tag => (
<div
class={tag === selectedTag ? 'chip clickable' : 'chip clickable disabled'}
onClick={() => setSelectedTag(tag === selectedTag ? null : tag)}
>
{tag}
</div>
))}
</div>
</div>
)}
<div class="wide-card-list" id="questions">
{filteredQuestions.length === 0 ? (
<div class="grid-center">
<em>No questions found</em>
</div>
) : (
filteredQuestions.map(question => (
<div class="card">
<div
class="text"
dangerouslySetInnerHTML={{
__html: marked(question.content, { async: false }),
}}
/>
<div class="metadata">
{question.tags.map(tag => (
<div class="chip small">{tag}</div>
))}
</div>
</div>
))
)}
</div>
</>
)
}

@ -2,8 +2,8 @@ const icons = Object.fromEntries(
Object.entries(
import.meta.glob<{ default: ImageMetadata }>(`node_modules/@phosphor-icons/core/assets/light/*.svg`, {
eager: true,
}),
).map(([path, module]) => [path.split('/').pop()!.split('.')[0].replace('-light', ''), module]),
})
).map(([path, module]) => [path.split('/').pop()!.split('.')[0].replace('-light', ''), module])
)
type Props = {

@ -68,22 +68,22 @@ export const UtentiPage = () => {
$filter.value === 'macchinisti'
? $utentiData.value.filter(user => MACCHINISTI.includes(user.uid))
: $filter.value === 'rappstud'
? $utentiData.value.filter(user => RAPPSTUD.includes(user.uid))
: $utentiData.value,
? $utentiData.value.filter(user => RAPPSTUD.includes(user.uid))
: $utentiData.value
)
const $fuse = useComputed(
() =>
new Fuse($filteredData.value, {
keys: ['gecos', 'uid'],
}),
})
)
const $searchText = useSignal('')
const $searchResults = useComputed(() =>
$searchText.value.trim().length > 0
? ($fuse.value?.search($searchText.value).map(result => result.item) ?? [])
: $filteredData.value,
? $fuse.value?.search($searchText.value).map(result => result.item) ?? []
: $filteredData.value
)
useEffect(() => {
@ -110,7 +110,7 @@ export const UtentiPage = () => {
<PhosphorIcon name={v.icon} />
{v.label}
</>,
]),
])
)}
</ComboBox>
<div class="search">
@ -142,14 +142,17 @@ export const UtentiPage = () => {
RAPPSTUD.includes(poissonUser.uid)
? 'bank'
: MACCHINISTI.includes(poissonUser.uid)
? 'wrench'
: 'user'
? 'wrench'
: 'user'
}
/>
</div>
<div class="text">{poissonUser.gecos}</div>
<div class="right">
<a href={`https://poisson.phc.dm.unipi.it/~${poissonUser.uid}`} target="_blank">
<a
href={`https://poisson.phc.dm.unipi.it/~${poissonUser.uid}`}
target="_blank"
>
{/* <span class="material-symbols-outlined">open_in_new</span> */}
<PhosphorIcon name="arrow-square-out" />
</a>

@ -1,27 +0,0 @@
const $debugConsole = document.createElement('div')
$debugConsole.style.position = 'fixed'
$debugConsole.style.bottom = '0'
$debugConsole.style.left = '0'
$debugConsole.style.width = '100%'
$debugConsole.style.height = '25vh'
$debugConsole.style.backgroundColor = 'black'
$debugConsole.style.color = 'white'
$debugConsole.style.overflow = 'auto'
$debugConsole.style.padding = '10px'
$debugConsole.style.boxSizing = 'border-box'
$debugConsole.style.fontFamily = 'monospace'
$debugConsole.style.zIndex = '9999'
$debugConsole.style.fontSize = '15px'
$debugConsole.style.opacity = '0.8'
document.body.appendChild($debugConsole)
function logDebugConsole(...args) {
$debugConsole.innerHTML += args.join(' ') + '<br>'
}
console.error = logDebugConsole
console.warn = logDebugConsole
console.log = logDebugConsole
console.debug = logDebugConsole

@ -1,83 +0,0 @@
// took from: https://github.com/sxyazi/marked-extended-latex
// this has a peer dependency bug
const CLASS_NAME = 'latex-b172fea480b'
const extBlock = options => ({
name: 'latex-block',
level: 'block',
start(src) {
return src.match(/\$\$[^\$]/)?.index ?? -1
},
tokenizer(src, tokens) {
const match = /^\$\$([^\$]+)\$\$/.exec(src)
return match ? { type: 'latex-block', raw: match[0], formula: match[1] } : undefined
},
renderer(token) {
if (!options.lazy) return options.render(token.formula, true)
return `<span class="${CLASS_NAME}" block>${token.formula}</span>`
},
})
const extInline = options => ({
name: 'latex',
level: 'inline',
start(src) {
return src.match(/\$[^\$]/)?.index ?? -1
},
tokenizer(src, tokens) {
const match = /^\$([^\$]+)\$/.exec(src)
return match ? { type: 'latex', raw: match[0], formula: match[1] } : undefined
},
renderer(token) {
if (!options.lazy) return options.render(token.formula, false)
return `<span class="${CLASS_NAME}">${token.formula}</span>`
},
})
let observer
/* istanbul ignore next */
export default (options = {}) => {
/* istanbul ignore next */
if (options.lazy && options.env !== 'test') {
observer = new IntersectionObserver(
(entries, self) => {
for (const entry of entries) {
if (!entry.isIntersecting) {
continue
}
const span = entry.target
self.unobserve(span)
Promise.resolve(options.render(span.innerText, span.hasAttribute('block'))).then(html => {
span.innerHTML = html
})
span.classList.add('latex-rendered')
}
},
{ threshold: 1.0 },
)
}
return {
extensions: [extBlock(options), extInline(options)],
}
}
/* istanbul ignore next */
export const observe = () => {
if (!observer) {
return
}
observer.disconnect()
document.querySelectorAll(`span.${CLASS_NAME}:not(.latex-rendered)`).forEach(span => {
observer.observe(span)
})
}
/* istanbul ignore next */
export const disconnect = () => {
observer?.disconnect()
}

@ -3,7 +3,7 @@ import { useEffect, useState } from 'preact/hooks'
export const trottleDebounce = <T extends any[], R>(
fn: (...args: T) => R,
delay: number,
options: { leading?: boolean; trailing?: boolean } = {},
options: { leading?: boolean; trailing?: boolean } = {}
): ((...args: T) => R | undefined) => {
let lastCall = 0
let lastResult: R | undefined

@ -9,16 +9,11 @@ const ICONS_MAP: Record<string, string> = {
}
type Props = {
fullName: string
description: string
image: ImageMetadata
fullName: string
entranceDate: number
exitDate?: number
founder?: boolean
description: string
social?: {
github?: string
linkedin?: string
@ -27,14 +22,13 @@ type Props = {
}
}
const { fullName, description, image, entranceDate, exitDate, founder, social } = Astro.props
const { image, fullName, entranceDate, exitDate, description, social } = Astro.props
---
<div class="bubble">
<img src={image.src} alt={fullName.toLowerCase()} />
<div class="title">{fullName}</div>
<div class="date">{entranceDate}&mdash;{exitDate ?? 'Presente'}</div>
{founder && <div class="founder">Fondatore</div>}
<div class="description">{description}</div>
{
social && (

@ -5,7 +5,6 @@ const links = [
// { href: '/appunti', text: 'Appunti' },
{ href: '/notizie', text: 'Notizie' },
{ href: '/guide', text: 'Guide' },
{ href: '/domande-esami', text: 'Domande Orali' },
{ href: '/storia', text: 'Storia' },
// { href: '/login', text: 'Login' },
]

@ -9,8 +9,8 @@ const { name } = Astro.props
const icons = Object.fromEntries(
Object.entries(
import.meta.glob<{ default: ImageMetadata }>(`node_modules/@phosphor-icons/core/assets/light/*.svg`),
).map(([path, module]) => [path.split('/').pop()!.split('.')[0].replace('-light', ''), module]),
import.meta.glob<{ default: ImageMetadata }>(`node_modules/@phosphor-icons/core/assets/light/*.svg`)
).map(([path, module]) => [path.split('/').pop()!.split('.')[0].replace('-light', ''), module])
)
if (!icons[name]) {

@ -14,21 +14,21 @@ Git è un sistema di controllo di versione distribuito creato per gestire proget
### **Cos'è Git?**
- **Sistema di controllo di versione**: Gestisce le modifiche al codice sorgente nel tempo.
- **Sistema di controllo di versione**: Gestisce le modifiche al codice sorgente nel tempo.
- **Distribuito**: Ogni sviluppatore ha una copia del repository.
- **Distribuito**: Ogni sviluppatore ha una copia del repository.
- **Veloce e leggero**: Ottimizzato per la velocità e le prestazioni.
- **Veloce e leggero**: Ottimizzato per la velocità e le prestazioni.
### **Perché usare Git?**
- **Tracciabilità**: Ogni modifica è tracciata e reversibile.
- **Tracciabilità**: Ogni modifica è tracciata e reversibile.
- **Collaborazione**: Più persone possono lavorare sullo stesso progetto.
- **Collaborazione**: Più persone possono lavorare sullo stesso progetto.
- **Backup**: Repository remoto per il backup del codice.
- **Backup**: Repository remoto per il backup del codice.
- **Branching**: Lavoriamo su nuove funzionalità senza influenzare il codice principale.
- **Branching**: Lavoriamo su nuove funzionalità senza influenzare il codice principale.
---
@ -96,9 +96,9 @@ git config --list
### **Repository**
- **Repository locale**: Una cartella sul nostro computer che contiene il nostro progetto.
- **Repository locale**: Una cartella sul nostro computer che contiene il nostro progetto.
- **Repository remoto**: Una versione del progetto ospitata su un server (es. GitHub, GitLab).
- **Repository remoto**: Una versione del progetto ospitata su un server (es. GitHub, GitLab).
### **Branch**
@ -174,9 +174,9 @@ Il comando **`git commit`** è utilizzato per registrare le modifiche nel reposi
#### Cosa succede dietro le quinte:
- Git salva lo stato dei file nell'area di staging in un commit, che viene aggiunto alla cronologia del repository locale.
- Git salva lo stato dei file nell'area di staging in un commit, che viene aggiunto alla cronologia del repository locale.
- Ogni commit ha un identificatore unico (hash) che consente di risalire facilmente alle modifiche in qualsiasi momento.
- Ogni commit ha un identificatore unico (hash) che consente di risalire facilmente alle modifiche in qualsiasi momento.
---
@ -198,13 +198,13 @@ Il comando **`git commit`** è utilizzato per registrare le modifiche nel reposi
#### Cosa succede dietro le quinte:
- Git confronta il nostro branch locale con il branch remoto. Se ci sono nuovi commit nel branch remoto che non sono ancora nel nostro branch locale, ci verrà richiesto di fare un **pull** per aggiornare prima di fare il push.
- Git confronta il nostro branch locale con il branch remoto. Se ci sono nuovi commit nel branch remoto che non sono ancora nel nostro branch locale, ci verrà richiesto di fare un **pull** per aggiornare prima di fare il push.
- Il nostro repository locale viene sincronizzato con il remoto, rendendo le modifiche visibili a tutti gli altri che hanno accesso al repository remoto.
- Il nostro repository locale viene sincronizzato con il remoto, rendendo le modifiche visibili a tutti gli altri che hanno accesso al repository remoto.
#### Errori comuni:
- Se il repository remoto è stato aggiornato nel frattempo da qualcun altro (ad esempio, con un altro push), riceveremo un errore che ci avvisa che dobbiamo fare prima un `git pull` per sincronizzare il nostro lavoro.
- Se il repository remoto è stato aggiornato nel frattempo da qualcun altro (ad esempio, con un altro push), riceveremo un errore che ci avvisa che dobbiamo fare prima un `git pull` per sincronizzare il nostro lavoro.
---
@ -226,13 +226,13 @@ Il comando **`git commit`** è utilizzato per registrare le modifiche nel reposi
#### Cosa succede dietro le quinte:
- **`git fetch`** scarica tutte le modifiche dal repository remoto, ma non le integra ancora nel nostro codice.
- **`git fetch`** scarica tutte le modifiche dal repository remoto, ma non le integra ancora nel nostro codice.
- **`git merge`** unisce le modifiche scaricate al nostro branch attuale, risolvendo eventuali conflitti, se necessario.
- **`git merge`** unisce le modifiche scaricate al nostro branch attuale, risolvendo eventuali conflitti, se necessario.
#### Errori comuni:
- Se ci sono conflitti tra il nostro lavoro e quello degli altri, Git ci avviserà che dovremo risolverli manualmente. Dopo aver risolto i conflitti, dovremo aggiungere i file risolti (`git add`) e completare il merge con un commit.
- Se ci sono conflitti tra il nostro lavoro e quello degli altri, Git ci avviserà che dovremo risolverli manualmente. Dopo aver risolto i conflitti, dovremo aggiungere i file risolti (`git add`) e completare il merge con un commit.
## **7. Lavorare con branch**
@ -336,18 +336,18 @@ git branch -d <nome-branch>
## **10. Best practices**
- Scriviamo messaggi di commit chiari e descrittivi.
- Scriviamo messaggi di commit chiari e descrittivi.
- Creiamo branch per nuove funzionalità o bugfix.
- Creiamo branch per nuove funzionalità o bugfix.
- Sincronizziamo frequentemente il nostro repository locale con quello remoto.
- Sincronizziamo frequentemente il nostro repository locale con quello remoto.
---
## **11. Risorse aggiuntive**
- [Documentazione ufficiale di Git](https://git-scm.com/doc)
- [Documentazione ufficiale di Git](https://git-scm.com/doc)
- [Guida interattiva Learn Git Branching](https://learngitbranching.js.org/)
- [Guida interattiva Learn Git Branching](https://learngitbranching.js.org/)
- [GitHub Docs](https://docs.github.com/)
- [GitHub Docs](https://docs.github.com/)

@ -64,13 +64,13 @@ jobs:
Il comando `rsync` ha le seguenti opzioni:
- `-c` per controllare i file tramite checksum invece che per data e dimensione (che sono sempre diverse visto che stiamo ricosruendo il sito ogni volta con le GitHub Actions)
- `-c` per controllare i file tramite checksum invece che per data e dimensione (che sono sempre diverse visto che stiamo ricosruendo il sito ogni volta con le GitHub Actions)
- `-a` per copiare ricorsivamente i file e mantenere i permessi
- `-a` per copiare ricorsivamente i file e mantenere i permessi
- `-v` per mostrare i file copiati
- `-v` per mostrare i file copiati
- `-z` per comprimere i file durante il trasferimento
- `-z` per comprimere i file durante il trasferimento
## SSH Segreti

@ -12,11 +12,11 @@ Poisson è un server autogestito dalla comunità studentesca di matematica, da s
Se non si è mai creato un account Poisson, è necessario inviare una richiesta via email a **macchinisti@lists.dm.unipi.it** includendo:
- Nome
- Nome
- Cognome
- Cognome
- Username di ateneo (quello associato alla propria email istituzionale)
- Username di ateneo (quello associato alla propria email istituzionale)
Nella mail è sufficiente specificare che si desidera attivare un account Poisson. I "macchinisti" si occuperanno di attivare l'account il prima possibile.
@ -66,10 +66,7 @@ Vediamo un piccolo esempio di file `index.html` che possiamo creare:
style="max-width: 300px; border-radius: 10px;"
/>
<p>Ciao! Sono Sergio Steffè.</p>
<p>
Email:
<a href="mailto:sergio.steffe@example.com">sergio.steffe@example.com</a>
</p>
<p>Email: <a href="mailto:sergio.steffe@example.com">sergio.steffe@example.com</a></p>
</body>
</html>
```

@ -0,0 +1,79 @@
---
id: stampare-via-ssh
title: Stampare via SSH
description: Come stampare da remoto tramite SSH
author: Antonio De Lucreziis
tags: [linux, ssh, stampanti]
---
Per stampare in dipartimento non bisogna per forza usare i computer dei laboratori, possiamo che stampare direttamente da remoto tramite SSH. Vediamo come fare!
Se non l'avete mai fatto prima per prima cosa bisogna poter accedere da remoto ad una macchina del dipartimento chiamata "login", il suo indirizzo è `login.dm.unipi.it`. Per fare l'accesso possiamo usare il seguente comando con l'account di Ateneo (non quello Poisson!)
```bash shell
ssh USERNAME_ATENEO@login.dm.unipi.it
```
Una volta connessi possiamo stampare utilizzando il comando `lpr` seguito dal nome del file che vogliamo stampare. Prima però serve trasferire il file che vogliamo stampare sulla macchina "login". Per fare ciò possiamo usare il comando `scp`, quindi per prima cosa usciamo dalla macchina "login" (premere `Ctrl+D` oppure scrivendo `exit`), andiamo nella cartella dove si trova il file che vogliamo stampare e poi eseguiamo il comando:
```bash shell
scp NOME_FILE.pdf USERNAME_ATENEO@login.dm.unipi.it:~/Documents
```
Dove `NOME_FILE.pdf` è il nome del file che vogliamo stampare e `Documents` è la cartella dove vogliamo trasferirlo. Una volta trasferito il file possiamo rifare ssh su "login" e stampare il file con il comando:
```bash shell
lpr Documents/NOME_FILE.pdf
```
Alternativamente possiamo stampare direttamente il file senza trasferirlo con il comando:
```bash shell
cat NOME_FILE.pdf | ssh USERNAME_ATENEO@login.dm.unipi.it lpr OPZIONI... -
```
dove `[OPZIONI...]` sono le opzioni che possiamo passare a `lpr` (vedi sotto). L'ultimo trattino "`-`" è molto importante e indica che il file da stampare è quello in standard input. Più precisamente, `cat NOME_FILE.pdf` invia il contenuto del file `NOME_FILE.pdf` allo standard output e `|` lo ridireziona a input di `ssh`, che a sua volta lo passa a `lpr` via rete.
## Opzioni di `lpr`
Il comando `lpr` accetta alcune opzioni che possono essere utili:
- `-P` seguito dal nome della stampante: permette di specificare la stampante su cui stampare, le stampanti disponibili in dipartimento sono
- `cdc4` che è la stampante di default e si trova in Aula 4
- `cdclf` che si trova al piano terra nel corridoio dopo l'Aula 4
- `cdc3` che si trova in Aula 3 (è un po' vecchia ma di solito funziona)
- `-#` seguito dal numero di copie: permette di specificare il numero di copie da stampare. In realtà questa opzione non funziona per vari motivi arcani e se uno passa `-#N` per stampare $N$ copie, la stampante stampa $N^2$ copie. (Questo ha scaturito una serie di ragionamenti sul modo ottimo di decomporre $N$ come somma di quadrati [con tanto di sito di comodo](https://shortest-sum-of-squares.netlify.app/)...)
- `-o sides=two-sided-long-edge`: permette di stampare fronte-retro (che dovrebbe essere già il default)
- `-o sides=two-sided-short-edge`: permette di stampare fronte-retro con "la rilegatura" delle pagine sul lato corto
- `-o sides=one-sided`: permette di stampare solo fronte, comodo per stampare i meme di laurea
- `-o fit-to-page`: permette di ridimensionare il documento per farlo entrare in un foglio (è buona prassi passare sempre questa opzione)
- `-o media=a4`: permette di specificare il formato del foglio, di default è A4 quindi non dovrebbere servire
## Altre comodità
Stampare da remoto porta anche altre comodità, ad esempio possiamo interrompere un file che abbiamo mandato in stampa per sbaglio con il comando (sempre tutti comandi da eseguire su "login")
```bash shell
cancel -a
```
Oppure possiamo vedere lo stato della coda di stampa con il comando
```bash shell
lpq -a
```
o per vedere lo stato delle nostre stampe
```bash shell
lpstat -l
```

@ -35,7 +35,10 @@ Una card semplice ha un titolo ed una descrizione.
```astro
<div class="card" style="--card-base: var(--guide-base); max-width: 25rem;">
<div class="title">Titolo</div>
<div class="text">Descrizione lorem ipsum dolor sit amet consectetur adipisicing elit. Aspernatur, labore?</div>
<div class="text">
Descrizione lorem ipsum dolor sit amet consectetur
adipisicing elit. Aspernatur, labore?
</div>
</div>
```
@ -48,7 +51,10 @@ Le card possono essere di dimensioni diverse. Questa è una card grande.
```astro
<div class="card large" style="--card-base: lightgreen; max-width: 25rem;">
<div class="title">Titolo</div>
<div class="text">Descrizione lorem ipsum dolor sit amet consectetur adipisicing elit. Aspernatur, labore?</div>
<div class="text">
Descrizione lorem ipsum dolor sit amet consectetur
adipisicing elit. Aspernatur, labore?
</div>
</div>
```
@ -94,7 +100,10 @@ Se c'è poco testo, può essere inserito direttamente nella card.
```astro
<div class="card">
<div class="text">Descrizione lorem ipsum dolor sit amet consectetur adipisicing elit. Aspernatur, labore?</div>
<div class="text">
Descrizione lorem ipsum dolor sit amet consectetur
adipisicing elit. Aspernatur, labore?
</div>
</div>
```
@ -104,15 +113,16 @@ Altrimenti può essere inserito in un tag `<p>`.
<div class="card">
<div class="text">
<p>
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Distinctio, vel! Veritatis est sit beatae eveniet.
Lorem ipsum dolor sit amet consectetur, adipisicing elit.
Distinctio, vel! Veritatis est sit beatae eveniet.
</p>
<p>
Error, minus, asperiores quaerat nulla cumque, nisi ipsam assumenda consectetur accusamus tempore
consequatur quae. Fugit?
Error, minus, asperiores quaerat nulla cumque, nisi ipsam
assumenda consectetur accusamus tempore consequatur quae. Fugit?
</p>
<p>
Quos sapiente amet numquam quis, libero odit eum, eius perspiciatis repellat nesciunt cupiditate asperiores
maiores?
Quos sapiente amet numquam quis, libero odit eum, eius
perspiciatis repellat nesciunt cupiditate asperiores maiores?
</p>
</div>
</div>
@ -122,10 +132,19 @@ C'è anche il modificatore `small` e `dimmed` per ridurre la grandezza del testo
```astro
<div class="card" style="max-width: 25rem;">
<div class="text">Some normal text, this is a very long text that should wrap on the next line</div>
<div class="text small">This is some small text</div>
<div class="text dimmed">This is some dimmed text</div>
<div class="text small dimmed">This is some small dimmed text</div>
<div class="text">
Some normal text, this is a very long
text that should wrap on the next line
</div>
<div class="text small">
This is some small text
</div>
<div class="text dimmed">
This is some dimmed text
</div>
<div class="text small dimmed">
This is some small dimmed text
</div>
</div>
```
@ -178,6 +197,7 @@ import { ComboBox } from '@/lib/components/ComboBox'
const [value, setValue] = useState('option-1')
```
```jsx
<ComboBox value={value} setValue={setValue}>
{{

@ -16,7 +16,7 @@ A differenza di altri framework, di base Astro non richiede JavaScript per funzi
## Caratteristiche del Nuovo Sito
Il nuovo sito unifica varie funzionalità che prima erano sparse in diversi siti web. I contenuti del sito dei Seminarietti verranno integrati nella sezione guide che ospiterà anche molte altre risorse utili.
Il nuovo sito unifica varie funzionalità che prima erano sparse in diversi siti web. I contenuti del sito dei Seminarietti verranno integrati nella sezione guide che ospiterà anche molte altre risorse utili.
- **Lista Utenti**: La lista degli utenti di Poisson precedentemente presente sul sito era ordinata per nome e non era facile cercare un utente specifico. Ora la lista è ordinata in base alla data di creaizone dell'account e c'è una barra di ricerca fuzzy per cercare un utente e la sua pagina Poisson.
@ -24,7 +24,7 @@ Il nuovo sito unifica varie funzionalità che prima erano sparse in diversi siti
- **News**: La sezione news ospiterà gli novità e annunci importanti riguardanti il PHC.
- **Storia**: Questa sezione raccoglie la storia del PHC fin dalla creazione di Poisson nel lontano 1994, includendo tutti i vari macchinisti nel corso della tempo.
- **Storia**: Questa sezione raccoglie la storia del PHC fin dalla creazione di Poisson nel lontano 1994, includendo tutti i vari macchinisti nel corso della tempo.
## Cooming Soon
@ -35,3 +35,4 @@ Il nuovo sito unifica varie funzionalità che prima erano sparse in diversi siti
## Conclusioni
Speriamo che il nuovo sito vi piaccia e vi sia utile. Se avete suggerimenti o richieste, non esitate a contattarci. Buona navigazione, magari iniziando dalla [pagina sulla storia del PHC](/storia)!

@ -1,31 +0,0 @@
---
title: Incidente del 2-7 Gennaio 2025
description: Riassunto dell'incidente che ha portato al guasto del server Poisson e delle azioni intraprese per ripristinare i servizi.
publishDate: 2025-01-07
---
# Incidente 2-7 Gennaio 2025
In questi giorni il server fisico principale del PHC, **Poisson**, non è stato operativo. Di conseguenza, i servizi ospitati direttamente su Poisson, come le pagine web degli utenti, l'accesso SSH e Gitea, sono rimasti offline. Al contrario, altri servizi web del PHC che non sono hostati su Poisson non hanno subito interruzioni. La buona notizia è che tutti i dati sono stati recuperati con successo e che l'infrastruttura è tornata pienamente operativa senza ulteriori complicazioni.
## Cosa è successo?
Sospettiamo che la causa principale dell'incidente sia stata una serie di brusche accensioni e spegnimenti, che hanno portato al guasto della scheda madre di Poisson. Il server non riusciva più ad avviarsi, neanche accedendo al BIOS, rendendo evidente che il problema era di natura hardware.
### Diagnosi e sostituzione
La mattina del 7 Gennaio, grazie alla disponibilità del dipartimento, abbiamo trovato una scheda madre di recupero, simile a quella originale ma meno potente. Questa è stata installata immediatamente, permettendo di riportare Poisson in funzione. Dal punto di vista software non è cambiato nulla: l'architettura della CPU è rimasta invariata, sebbene con meno core e la metà della RAM. Gli Hard Disks e una delle schede di rete sono stati riutilizzati senza problemi.
In un futuro prossimo si prevede di aggiornare la scheda madre con una più potente, per garantire le prestazioni di Poisson. Preghiamo per ora quindi di evitare di eseguire operazioni troppo pesanti sul server.
Tutto è stato rimontato nel consueto case bianco che ospita Poisson. Anche se ora è operativo, il server potrebbe essere considerato una nuova incarnazione, sollevando una riflessione interessante: **Poisson è ancora lo stesso server?** Proprio come nel paradosso della nave di Teseo, molti componenti di Poisson sono stati sostituiti nel corso degli anni. Dal 1992, pezzi come HDD, CPU, RAM e schede madri sono stati aggiornati per mantenere il server al passo con i tempi, mentre i dati e le funzionalità sono rimasti intatti.
![Visione vs Visione](/images/misc/meme-vision-vs-vision.webp)
## Ritorno alla normalità
Grazie allintervento, i servizi principali sono tornati online. Gli utenti possono nuovamente accedere alle proprie pagine web, utilizzare SSH e lavorare su Gitea senza alcuna modifica ai dati o alle configurazioni precedenti.
Per chi avesse riscontrato difficoltà nell'accesso, invitiamo a contattare i macchinisti per ricevere supporto.
> Be a System Administrator, they said. It will be fun, they said 🤦‍♂️

File diff suppressed because it is too large Load Diff

@ -3,92 +3,40 @@
- fullName: Antonio De Lucreziis
entranceDate: 2019
description: |
Appassionato di geometria computazionale, parser, teoria dei linguaggi di programmazione, Smalltalk e Lisp.
Appassionato di geometria computazionale, parser, teoria dei linguaggi di programmazione, Smalltalk e Lisp.
social:
github: https://github.com/aziis98
website: https://poisson.phc.dm.unipi.it/~delucreziis/
github: https://github.com/aziis98
website: https://poisson.phc.dm.unipi.it/~delucreziis/
- fullName: Luca Lombardo
entranceDate: 2024
description: Appassionato di algoritmi e strutture dati, Rust e di quando matematica e informatica si incontrano.
social:
github: https://github.com/lukefleed
website: https://lukefleex.xyz
linkedin: https://www.linkedin.com/in/l-lombardo/
github: https://github.com/lukefleed
website: https://lukefleex.xyz
linkedin: https://www.linkedin.com/in/l-lombardo/
- fullName: Francesco Minnocci
entranceDate: 2022
description: Chitarrista classico ossessionato con la geometria aritmetica, linux e il rock progressivo.
social:
github: https://github.com/BachoSeven
website: https://bachoseven.com
github: https://github.com/BachoSeven
website: https://bachoseven.com
- fullName: Francesco Baldino
entranceDate: 2022
description: Bla bla Star Wars
social:
github: https://github.com/Fran314
website: https://poisson.phc.dm.unipi.it/~baldino
github: https://github.com/Fran314
website: https://poisson.phc.dm.unipi.it/~baldino
- fullName: Illya Serdyuk
entranceDate: 2020
description: Bla bla Void Linux
social:
github: https://github.com/Kratacoa
# Vecchi Macchinisti
github: https://github.com/Kratacoa
- fullName: Francesco Manicastri
entranceDate: 2020
entranceDate: 2022
exitDate: 2024
social:
linkedin: https://www.linkedin.com/in/gustavo-sass%C3%ACnculo-phd-92916a202/
- fullName: Cristiano Cricci
entranceDate: 2010
exitDate: 2019
social:
website: https://poisson.phc.dm.unipi.it/~cricci/
- fullName: Tommaso Biannucci
entranceDate: 2019
exitDate: 2022
social:
github: https://gitlab.com/churli
website: https://churli.gitlab.io/
- fullName: Letizia D'Achille
entranceDate: 2018
exitDate: 2022
description: Appassionata di crittografia, teoria dei codici e matematica computazionale.
social:
github: https://github.com/letizia-dachille
website: https://letizia-dachille.github.io/
linkedin: https://www.linkedin.com/in/letizia-dachille/
- fullName: Emiliano Rago
entranceDate: 2000
exitDate: 2006
description: Traviato da Linux in età troppo giovane ha difficoltà ad usare il mouse ma ama gli shortcuts con una decina di tasti.
social:
linkedin: https://www.linkedin.com/in/emiliano-rago-1a8018109/
- fullName: Francesco Caporali
entranceDate: 2018
exitDate: 2022
social:
github: https://github.com/caporali
website: https://caporali.github.io/
linkedin: https://www.linkedin.com/in/francescocaporali
- fullName: Antonio Spanu
entranceDate: 2005
exitDate: 2009
social:
linkedin: https://www.linkedin.com/in/antonio-spanu-609b3516a/
- fullName: Riccardo Murri
entranceDate: 1995
exitDate: 2000
founder: true
social:
github: https://github.com/riccardomurri
description: Bla bla Keenan Crane

29
src/files.d.ts vendored

@ -6,39 +6,12 @@ declare module '*.yaml' {
declare module '@/data/macchinisti.yaml' {
type Macchinista = {
fullName: string
description: string
entranceDate: number
exitDate?: number
description: string
social: Record<string, string>
founder?: boolean
}
const value: Macchinista[]
export default value
}
declare module '@/data/domande-esami.yaml' {
export type Question = {
course: string
content: string
tags: string[]
}
export type Group = {
id: string
name: string
items: Array<string>
}
export type Database = {
names: Record<string, string>
groups: Group[]
questions: Question[]
}
const value: Database
export default value
}

@ -44,7 +44,7 @@ const { title, description, thumbnail, pageTags } = Astro.props
<script>
import renderMathInElement from 'katex/contrib/auto-render'
const renderMath = () => {
document.addEventListener('DOMContentLoaded', function () {
renderMathInElement(document.body, {
delimiters: [
{ left: '$$', right: '$$', display: true },
@ -54,12 +54,7 @@ const { title, description, thumbnail, pageTags } = Astro.props
],
throwOnError: false,
})
}
document.addEventListener('DOMContentLoaded', () => renderMath())
// @ts-ignore
window.renderMath = renderMath
})
</script>
<script is:inline>

@ -1,25 +0,0 @@
---
import type { GetStaticPaths } from 'astro'
import BaseLayout from '@/layouts/BaseLayout.astro'
import Footer from '@/components/Footer.astro'
import Header from '@/components/Header.astro'
import database from '@/data/domande-esami.yaml'
import { DomandeEsamiCourse } from '@/client/DomandeEsamiCourse'
export const getStaticPaths = (() => {
return Object.keys(database.names).map(course => ({
params: { course },
}))
}) satisfies GetStaticPaths
const { course } = Astro.params
---
<BaseLayout title="Domande Orali | PHC" pageTags={'domande-esami'}>
<Header />
<main>
<DomandeEsamiCourse client:only="preact" course={course} />
</main>
<Footer />
</BaseLayout>

@ -1,11 +0,0 @@
import type { APIRoute } from 'astro'
import database from '@/data/domande-esami.yaml'
export const GET: APIRoute = ({}) => {
return new Response(JSON.stringify(database), {
headers: {
'content-type': 'application/json',
},
})
}

@ -1,24 +0,0 @@
import type { APIRoute, GetStaticPaths } from 'astro'
import database from '@/data/domande-esami.yaml'
export const getStaticPaths = (() => {
return Object.keys(database.names).map(course => ({
params: { course },
}))
}) satisfies GetStaticPaths
export const GET: APIRoute = ({ params: { course } }) => {
return new Response(
JSON.stringify({
groups: [],
names: Object.fromEntries(Object.entries(database.names).filter(([key]) => key === course)),
questions: database.questions.filter(question => question.course === course),
}),
{
headers: {
'content-type': 'application/json',
},
},
)
}

@ -1,64 +0,0 @@
---
import { PhosphorIcon } from '@/client/Icon'
import Footer from '@/components/Footer.astro'
import Header from '@/components/Header.astro'
import BaseLayout from '@/layouts/BaseLayout.astro'
import database from '@/data/domande-esami.yaml'
const courseQuestionCounts = Object.fromEntries(
database.questions.reduce((acc, question) => {
acc.set(question.course, (acc.get(question.course) || 0) + 1)
return acc
}, new Map()),
)
---
<BaseLayout title="Domande Orali | PHC" pageTags={'domande-esami'}>
<Header />
<main>
<h1>Domande Orali</h1>
{
database.groups.map(group => (
<details open>
<summary>
<h2 id={group.id}>
<div class="details-closed">
<PhosphorIcon name="caret-down" />
</div>
<div class="details-openned">
<PhosphorIcon name="caret-up" />
</div>
{group.name}
</h2>
</summary>
<div class="wide-card-list">
{group.items
.filter(course => courseQuestionCounts[course] > 0)
.map(course => (
<a href={`/domande-esami/${course}`}>
<div class="card">
<h2>{database.names[course]}</h2>
<div class="text">
<p>{courseQuestionCounts[course] || 0} domande</p>
</div>
</div>
</a>
))}
</div>
</details>
))
}
<h3>Come Contribuire</h3>
<div class="card large">
<div class="text">
<p>
Se hai raccolto delle domande da un orale, puoi inviarcele per email all'indirizzo
<a href="mailto:macchinisti@lists.dm.unipi.it"> macchinisti@lists.dm.unipi.it</a>.
</p>
</div>
</div>
</main>
<Footer />
</BaseLayout>

@ -45,7 +45,7 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
<div class="card-list">
{
news.toReversed().map(newsItem => (
news.map(newsItem => (
<Card>
<a href={`/notizie/${newsItem.slug}`} class="title">
{newsItem.data.title}
@ -80,7 +80,8 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
>
<p>Gitea è un servizio di hosting per progetti software, come GitHub ma autogestito.</p>
<p>
Qui puoi trovare i progetti del PHC, e accedendo con un account di Ateneo potrai crearne di nuovi.
Qui puoi trovare i progetti del PHC, e accedendo con un account di Ateneo potrai crearne
di nuovi.
</p>
</ProjectCard>
<!-- <ProjectCard
@ -97,8 +98,8 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
imgSrc="https://lab.phc.dm.unipi.it/orario/icon.png"
>
<p>
Questo sito permette di visualizzare il proprio orario delle lezioni, con informazioni sui docenti e
le aule.
Questo sito permette di visualizzare il proprio orario delle lezioni, con informazioni sui
docenti e le aule.
</p>
</ProjectCard>
<ProjectCard
@ -117,8 +118,8 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
imgSrc="https://steffe.lb.cs.dm.unipi.it/assets/img/logo.png"
>
<p>
Cluster progettato ed assemblato durante il progetto speciale per la didattica "Calcolo Parallelo
dall'Infrastruttura alla Matematica".
Cluster progettato ed assemblato durante il progetto speciale per la didattica "Calcolo
Parallelo dall'Infrastruttura alla Matematica".
</p>
</ProjectCard>
<ProjectCard
@ -127,7 +128,10 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
style="--card-bg: #bd9fec; --masonry-height: 2;"
imgSrc="https://seminarietti.phc.dm.unipi.it/favicon.png"
>
<p>Storico degli incontri organizzati dal PHC su argomenti di informatica, matematica e tecnologia.</p>
<p>
Storico degli incontri organizzati dal PHC su argomenti di informatica, matematica e
tecnologia.
</p>
</ProjectCard>
<ProjectCard
title="Tutorato"
@ -136,8 +140,8 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
imgSrc="https://tutorato.phc.dm.unipi.it/favicon.svg"
>
<p>
Un sito con tutte le informazioni sui tutorati di Matematica, con tanto di archivio degli anni
passati.
Un sito con tutte le informazioni sui tutorati di Matematica, con tanto di archivio degli
anni passati.
</p>
</ProjectCard>
<ProjectCard
@ -147,18 +151,10 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
imgSrc="https://ggwp.phc.dm.unipi.it/ggwp-icon.png"
>
<p>
Sito per il tabellone del GGWP, utilizzato durante la gara di Novembre 2024 dagli organizzatori per
inserire le risposte alla gara in tempo reale.
Sito per il tabellone del GGWP, utilizzato durante la gara di Novembre 2024 dagli
organizzatori per inserire le risposte alla gara in tempo reale.
</p>
</ProjectCard>
<ProjectCard
title="Chat"
href="https://chat.phc.dm.unipi.it/"
style="--card-bg: #383838; --card-fg: #ddd; --masonry-height: 1;"
imgSrc="https://chat.phc.dm.unipi.it/favicon.png"
>
<p>Istanza di Open-WebUI, una chat stile ChatGPT con alcuni LLM self-hostati dal PHC.</p>
</ProjectCard>
</div>
</section>
<section class="wanna-be-macchinista">
@ -176,18 +172,22 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
supporto tecnico per installare ed usare <strong>Linux</strong> sul proprio portatile
</li>
<li>
<strong>costruiamo</strong>, <strong>smontiamo</strong> ed <strong>aggiustiamo</strong>
<strong>costruiamo</strong>, <strong>smontiamo</strong> ed <strong>aggiustiamo</strong
>
computer (antichi e moderni)
</li>
<li>
<strong>sviluppo software</strong> di backend e frontend per siti web (ma non solo)
</li>
<li>
organizzazione di <strong>seminari</strong> di divulgazione (vedi <a href="#projects">sopra</a>)
organizzazione di <strong>seminari</strong> di divulgazione (vedi <a href="#projects"
>sopra</a
>)
</li>
</ul>
<p>
Infine, il PHC è prima di tutto un luogo dove <strong>imparare</strong>, <strong>trasmettere</strong
Infine, il PHC è prima di tutto un luogo dove <strong>imparare</strong>, <strong
>trasmettere</strong
> le proprie conoscenze e <strong>condividere</strong> la passione per la tecnologia.
</p>
</div>
@ -219,12 +219,13 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
<div class="title">Vuoi diventare macchinista?</div>
<div class="text">
<p>
<strong>Macchinista non si nasce, si diventa:</strong> se sei uno studente di Matematica e vuoi diventare
un macchinista, vienici a trovare!
<strong>Macchinista non si nasce, si diventa:</strong> se sei uno studente di Matematica e
vuoi diventare un macchinista, vienici a trovare!
</p>
<p>
L'unico prerequisito è la voglia di imparare! Di solito, c'è un periodo di "apprendistato" nel quale
si apprendono le basi; una volta superato potrai diventare un macchinista a tutti gli effetti.
L'unico prerequisito è la voglia di imparare! Di solito, c'è un periodo di "apprendistato"
nel quale si apprendono le basi; una volta superato potrai diventare un macchinista a
tutti gli effetti.
</p>
</div>
</Card>

@ -12,8 +12,8 @@ const images = Object.fromEntries(
Object.entries(
import.meta.glob<{ default: ImageMetadata }>('@/assets/macchinisti/*', {
eager: true,
}),
).map(([path, module]) => [path.split('/').pop()!.split('.')[0], module]),
})
).map(([path, module]) => [path.split('/').pop()!.split('.')[0], module])
)
const currentMacchinisti = macchinisti.filter(macchinista => !macchinista.exitDate)
@ -35,9 +35,9 @@ const getMacchinistaPicture = (fullName: string) => {
<p>
<em>> Chi sono i macchinisti?</em>
<br />
Questo è l'appellativo dato agli studenti che si occupano di gestire l'infrastuttura e i servizi del
PHC (vedi la homepage per informazioni su come diventare un macchinista). Qua sotto trovi i macchinisti
attualmente attivi in PHC.
Questo è l'appellativo dato agli studenti che si occupano di gestire l'infrastuttura e i servizi
del PHC (vedi la homepage per informazioni su come diventare un macchinista). Qua sotto trovi
i macchinisti attualmente attivi in PHC.
</p>
</div>
</div>
@ -60,8 +60,8 @@ const getMacchinistaPicture = (fullName: string) => {
<div class="title"><s>Deus</s> Ex Macchinisti</div>
<div class="text">
<p>
Qui raccogliamo qualche informazione sui macchinisti del passato, che hanno contribuito a rendere il
PHC quello che è oggi.
Qui raccogliamo qualche informazione sui macchinisti del passato, che hanno contribuito a
rendere il PHC quello che è oggi.
</p>
</div>
</div>
@ -70,12 +70,11 @@ const getMacchinistaPicture = (fullName: string) => {
{
pastMacchinisti.map(macchinista => (
<Bubble
fullName={macchinista.fullName}
description={macchinista.description}
image={getMacchinistaPicture(macchinista.fullName)}
fullName={macchinista.fullName}
entranceDate={macchinista.entranceDate}
exitDate={macchinista.exitDate}
founder={macchinista.founder}
description={macchinista.description}
social={macchinista.social}
/>
))

@ -10,7 +10,7 @@ const news = await getCollection('news')
<h1><a href="/notizie">Notizie</a></h1>
<div class="card-list">
{
news.toReversed().map(newsItem => (
news.map(newsItem => (
<div class="card">
<a href={`/notizie/${newsItem.slug}`} class="title">
{newsItem.data.title}

@ -17,8 +17,9 @@ import WebSite from '@/assets/gallery/005-website-development@3x4.jpg'
<div class="title">Storia</div>
<div class="text">
<p>
Il PHC nasce quasi venti anni fa, nel lontano 1999 ed ha perciò una ricca storia. Qui proveremo ad
annoverare la storia del PHC, in una timeline con gli eventi più salienti del progetto.
Il PHC nasce quasi venti anni fa, nel lontano 1999 ed ha perciò una ricca storia. Qui
proveremo ad annoverare la storia del PHC, in una timeline con gli eventi più salienti del
progetto.
</p>
<img
class="small flat"
@ -35,25 +36,26 @@ import WebSite from '@/assets/gallery/005-website-development@3x4.jpg'
<div class="timeline">
<Timeline title="Un nuovo look" year="Nov 2024">
<p>
Dopo <s>mesi</s> anni di lavoro e di progettazione, il sito del PHC viene riscritto da zero in Astro.
Il progetto è stato voluto principalmente da <strong>Antonio De Lucreziis</strong> (in foto, in fase
di sviluppo), con il supporto di <strong>Francesco Minnocci</strong>.
Dopo <s>mesi</s> anni di lavoro e di progettazione, il sito del PHC viene riscritto da zero
in Astro. Il progetto è stato voluto principalmente da <strong
>Antonio De Lucreziis</strong
> (in foto, in fase di sviluppo), con il supporto di <strong>Francesco Minnocci</strong>.
</p>
<img class="fill" src={WebSite.src} alt="Sviluppo del sito" />
</Timeline>
<Timeline title="Luca e le grandi pulizie" year="Apr 2024">
<p>
Nel 2024, <strong>Luca Lombardo</strong> entra a far parte del PHC. Questo stesso anno vengono effettuate
delle grandi pulizie nella stanza del PHC, come non si faceva dal lontano 2006.
Nel 2024, <strong>Luca Lombardo</strong> entra a far parte del PHC. Questo stesso anno vengono
effettuate delle grandi pulizie nella stanza del PHC, come non si faceva dal lontano 2006.
</p>
<img class="fill" src={imgPulizie.src} alt="pulizie phc" />
</Timeline>
<Timeline title="Il cluster di Raspberry Pi" year="2023">
<p>
Il dipartimento acquista in due fasi diverse 34 (cloni) di Raspberry Pi 4, che vengono assemblati in
un cluster per il progetto "High Performance Mathematics". Il cluster è stato assemblato,
configurato e messo in funzione con la forte collaborazione macchinisti del PHC, che ancora oggi ne
curano la manutenzione.
Il dipartimento acquista in due fasi diverse 34 (cloni) di Raspberry Pi 4, che vengono
assemblati in un cluster per il progetto "High Performance Mathematics". Il cluster è
stato assemblato, configurato e messo in funzione con la forte collaborazione macchinisti
del PHC, che ancora oggi ne curano la manutenzione.
</p>
<img class="fill" src={imgCluster.src} alt="cluster di raspberry pi" />
</Timeline>
@ -80,8 +82,9 @@ import WebSite from '@/assets/gallery/005-website-development@3x4.jpg'
</Timeline>
<Timeline title="Rinnovo del sito" year="2004">
<p>
Dopo un periodo di inattività del progetto, il sito del PHC viene riscritto in PHP e trasferito sul
dominio <a href="https://web.archive.org/web/20040823112401/http://poisson.phc.unipi.it/"
Dopo un periodo di inattività del progetto, il sito del PHC viene riscritto in PHP e
trasferito sul dominio <a
href="https://web.archive.org/web/20040823112401/http://poisson.phc.unipi.it/"
>poisson.phc.unipi.it</a
>, il cui design è caratterizzato da un <a
href="https://web.archive.org/web/20060609003904im_/http://poisson.phc.unipi.it/logo_studenti.orig.png"
@ -105,28 +108,33 @@ import WebSite from '@/assets/gallery/005-website-development@3x4.jpg'
</Timeline>
<Timeline title="Rete del PHC e DNS" year="1999">
<p>
Nel maggio del 1999 viene attivata la rete 131.114.10.0, con tanto di nameserver sul dominio <a
href="https://web.archive.org/web/20010410215451/http://www.phc.unipi.it/">phc.unipi.it</a
Nel maggio del 1999 viene attivata la rete 131.114.10.0, con tanto di nameserver sul
dominio <a href="https://web.archive.org/web/20010410215451/http://www.phc.unipi.it/"
>phc.unipi.it</a
>.
</p>
</Timeline>
<Timeline title="Fondazione del PHC" year="1999">
<p>
In seguito alla proposta del prof. <strong>Sergio Steffè</strong>, in data 26 febbraio 1999 il
Dipartimento di Matematica approva una delibera per stanziare la stanza 106, una sottorete, il
dominio DNS phc.dm.unipi.it ed alcuni computer ad uso di un gruppo di studenti (Riccardo Murri e
Massimiliano Sala), così da avere un luogo in cui "smanettare", offrire spazio a progetti
interessanti, dare supporto informatico agli studenti e gestire il sito Poisson
In seguito alla proposta del prof. <strong>Sergio Steffè</strong>, in data 26 febbraio
1999 il Dipartimento di Matematica approva una delibera per stanziare la stanza 106 ed
alcuni computer ad uso di un gruppo di studenti, così da avere un luogo in cui
"smanettare", dare supporto informatica agli studenti e gestire il sito Poisson.
</p>
<img class="fill" src="https://poisson.phc.dm.unipi.it/~steffe/sergio.jpg" alt="Sergio Steffè" />
<img
class="fill"
src="https://poisson.phc.dm.unipi.it/~steffe/sergio.jpg"
alt="Sergio Steffè"
/>
</Timeline>
<Timeline title="Apertura di Poisson" year="~1995">
<Timeline title="Apertura di Poisson" year="~1994">
<p>
Nell'attuale Aula 4, allora semplice Aula studenti, nasce il sito web <strong
>poisson.dm.unipi.it</strong
>
su dei computer messi a disposizione agli studenti da Vinicio Villani. Una versione del 1996 di tale
sito si trova nel <a href="https://web.archive.org/web/19971017065805/http://poisson.dm.unipi.it/"
su dei computer messi a disposizione agli studenti da Vinicio Villani. Una versione del 1996
di tale sito si trova nel <a
href="https://web.archive.org/web/19971017065805/http://poisson.dm.unipi.it/"
>Web Archive</a
>
</p>

@ -75,7 +75,12 @@ function setup() {
const handle = window.setInterval(() => {
const time = new Date().getTime() - startTime.getTime()
update(state, g.canvas.width / window.devicePixelRatio, g.canvas.height / window.devicePixelRatio, time)
update(
state,
g.canvas.width / window.devicePixelRatio,
g.canvas.height / window.devicePixelRatio,
time
)
render(g, state, time)
}, 1000 / RENDERER_FPS)
@ -261,16 +266,25 @@ const DIR_AVAILABLE_PREDICATE: Record<WireDirection, (pos: Point2, grid: Grid<Wi
implies(grid.has([x - 1, y]), () => grid.get([x - 1, y]) !== 'down-right') &&
implies(grid.has([x + 1, y]), () => grid.get([x + 1, y]) !== 'down-left'),
['down-left']: ([x, y], grid) =>
!grid.has([x - 1, y + 1]) && implies(grid.has([x - 1, y]), () => grid.get([x - 1, y]) === 'down-left'),
!grid.has([x - 1, y + 1]) &&
implies(grid.has([x - 1, y]), () => grid.get([x - 1, y]) === 'down-left'),
['down-right']: ([x, y], grid) =>
!grid.has([x + 1, y + 1]) && implies(grid.has([x + 1, y]), () => grid.get([x + 1, y]) === 'down-right'),
!grid.has([x + 1, y + 1]) &&
implies(grid.has([x + 1, y]), () => grid.get([x + 1, y]) === 'down-right'),
}
function pruneDirections(grid: Grid<WireCell>, position: Point2, directions: WireDirection[]): WireDirection[] {
function pruneDirections(
grid: Grid<WireCell>,
position: Point2,
directions: WireDirection[]
): WireDirection[] {
return directions.filter(dir => DIR_AVAILABLE_PREDICATE[dir](position, grid))
}
function generateWire(grid: Grid<WireCell>, startingPoint: Point2): { position: Point2; direction: WireCell }[] {
function generateWire(
grid: Grid<WireCell>,
startingPoint: Point2
): { position: Point2; direction: WireCell }[] {
const segmentLength = Math.floor(1 - Math.random() ** 2) * 10 + 30
let currentPosition = startingPoint
let currentDirection: WireDirection = randomChoice(['down', 'down', 'down', 'down-left', 'down-right'])
@ -278,7 +292,11 @@ function generateWire(grid: Grid<WireCell>, startingPoint: Point2): { position:
const steps: { position: Point2; direction: WireCell }[] = []
for (let i = 0; i < segmentLength; i++) {
const availableDirections = pruneDirections(grid, currentPosition, ['down', 'down-left', 'down-right'])
const availableDirections = pruneDirections(grid, currentPosition, [
'down',
'down-left',
'down-right',
])
if (availableDirections.length === 0) {
break
} else {

@ -14,7 +14,7 @@
width: 22px;
height: 22px;
display: grid inline;
display: grid;
place-content: center;
}
@ -33,11 +33,7 @@
place-content: center;
font-size: 24px;
font-variation-settings:
'FILL' 0,
'wght' 300,
'GRAD' 0,
'opsz' 24;
font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 24;
max-width: 32px;
}
@ -83,6 +79,29 @@
}
}
.flex-column {
display: flex;
flex-direction: column;
gap: 1rem;
}
.flex-row {
display: flex;
flex-direction: row;
gap: 1rem;
}
// just to know for reference
.fake-masonry {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
grid-auto-flow: dense;
& > * {
grid-row: span var(--masonry-height);
}
}
.search-results {
width: 100%;
@ -573,13 +592,6 @@
}
}
.metadata {
display: grid;
grid-auto-flow: column;
justify-content: start;
gap: 0.5rem;
}
@media screen and (max-width: $screen-desktop-min) {
padding: 0.9rem;
@ -597,38 +609,6 @@
}
}
.chip {
user-select: none;
display: grid;
place-content: center;
place-items: center;
color: #111;
background: #0004;
padding: 0 0.25rem;
border-radius: 0.25rem;
line-height: 1.5;
font-size: 16px;
font-weight: 500;
&.small {
font-size: 13px;
font-weight: 600;
}
&.disabled {
color: #0004;
background: #0002;
}
}
a:has(> .card) {
display: contents;
}
//
// Card List
//
@ -852,15 +832,17 @@
width: 100%;
flex-wrap: wrap;
justify-content: center;
gap: 3rem 0rem;
gap: 3rem 6rem;
> .bubble {
display: grid;
grid-template-rows: auto auto auto auto;
grid-auto-rows: auto;
gap: 0.5rem;
text-align: center;
justify-items: center;
align-content: start;
width: 18rem;
width: 28ch;
.date {
display: grid;
@ -872,29 +854,6 @@
border-radius: 0.25rem;
}
.founder {
display: grid;
place-content: center;
font-size: 15px;
font-weight: 700;
// gold badge
background: #ffdb12;
color: #725306;
padding: 0 0.25rem;
border: 2px solid #b98c19;
border-radius: 0.25rem;
box-shadow: 0.125rem 0.125rem 0 0 #664b06;
}
.description {
font-size: 16px;
text-wrap: balance;
}
.social {
display: grid;
grid-auto-flow: column;
@ -913,101 +872,7 @@
object-fit: cover;
width: 100%;
aspect-ratio: 1 / 1;
width: 12rem;
}
}
}
.wide-card-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(auto, 28rem));
gap: 2rem;
padding: 2rem;
width: 100%;
justify-content: center;
// align-items: start;
.text > * {
max-width: none;
}
.card {
display: grid;
grid-template-rows: 1fr auto;
}
@media screen and (max-width: $screen-desktop-min) {
grid-template-columns: 1fr;
padding: 0;
gap: 1rem;
}
}
.filter {
min-width: 15rem;
}
.flex-column {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.flex-row {
display: flex;
flex-direction: row;
gap: 0.5rem;
}
.flex-row-wrap {
display: flex;
flex-direction: row;
gap: 0.5rem;
flex-wrap: wrap;
}
.grid-h {
display: grid;
grid-auto-flow: column;
justify-content: start;
align-items: center;
gap: 0.5rem;
}
.grid-v {
display: grid;
justify-items: start;
align-content: start;
grid-auto-flow: row;
gap: 0.5rem;
}
.grid-center {
display: grid;
place-content: center;
place-items: center;
gap: 0.5rem;
grid-auto-flow: row;
}
.clickable {
cursor: pointer;
}
// just to know for reference
.fake-masonry {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
grid-auto-flow: dense;
& > * {
grid-row: span var(--masonry-height);
}
}
}

@ -129,47 +129,4 @@
justify-self: center;
}
}
details {
width: 100%;
summary {
display: grid;
place-content: center;
place-items: center;
list-style: none;
&::-webkit-details-marker {
display: none;
}
}
.details-openned,
.details-closed {
display: contents;
}
&:not([open]) {
.details-openned {
display: none;
}
}
&[open] {
summary {
padding-bottom: 1rem;
}
.details-closed {
display: none;
}
}
@media screen and (max-width: $screen-desktop-min) {
summary {
place-content: stretch;
place-items: stretch;
}
}
}
}

@ -122,8 +122,6 @@ body {
font-weight: 500;
letter-spacing: 1px;
color: #333;
padding: 0.25rem 1.325rem;
}
@media screen and (max-width: $screen-desktop-min) {
@ -210,10 +208,6 @@ body {
@media screen and (max-width: $screen-desktop-min) {
padding: 3rem 1rem;
gap: 3rem;
.card {
width: 100%;
}
}
}

@ -213,7 +213,7 @@
// background: #fcddff;
// background: #ffa89c;
background: var(--card-bg, var(--project-card-bg));
color: var(--card-fg, #000e);
color: #000e;
@include neo-brutalist-card($size: 3px, $offset: 9px);
@ -437,35 +437,6 @@
}
}
.domande-esami {
background: hsl(180, 100%, 95%);
--card-base: hsl(180, 100%, 85%);
main {
justify-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 4rem 0;
gap: 2rem;
.search {
max-width: 80ch;
}
button {
background: hsl(180, 100%, 72%);
}
.card a {
color: color-mix(in srgb, var(--card-base-internal), #000 80%);
}
}
}
// .login {
// background: #ddfaff;
@ -568,6 +539,12 @@
padding: 5rem;
gap: 5rem;
}
.card-list {
> .card {
grid-template-rows: auto 1fr auto;
}
}
}
.guida {
@ -682,8 +659,8 @@
flex-direction: column;
align-items: center;
padding: 4.5rem 3rem;
gap: 4.5rem;
padding: 6rem;
gap: 6rem;
}
}

@ -33,18 +33,6 @@
@layer typography {
@include geometric-headings;
strong {
font-weight: 600;
}
em {
font-style: italic;
}
.text-center {
text-align: center;
}
.text {
// text-align: justify;
// hyphens: auto;
@ -141,9 +129,6 @@
display: block;
margin: 0 auto;
width: 50ch;
max-width: 100%;
@include neo-brutalist-card(2px);
&.fill {
@ -216,10 +201,6 @@
}
}
li + li {
margin-top: 0.5rem;
}
a,
a:visited {
color: var(--zone-color, #1e6733);

@ -1,17 +1,17 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "preact",
"strictNullChecks": true,
"allowJs": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@layouts/*": ["src/layouts/*"],
"@client/*": ["src/client/*"],
"@components/*": ["src/components/*"]
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "preact",
"strictNullChecks": true,
"allowJs": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@layouts/*": ["src/layouts/*"],
"@client/*": ["src/client/*"],
"@components/*": ["src/components/*"]
}
}
}
}

Loading…
Cancel
Save