Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b5aea6eb8 | ||
|
|
0179e7a858 | ||
|
|
5c7342c91e | ||
|
|
3ed6cbf57a | ||
|
|
1ab42ed2e2 | ||
|
|
419b0c6ab5 | ||
|
|
f85991c409 | ||
|
|
d115d69b1b | ||
|
|
18563979a2 | ||
|
|
4b7a867a19 | ||
|
|
517a8ee8f1 | ||
|
|
0b4bf289d4 | ||
|
|
db083e20c2 | ||
|
|
fa564754f2 | ||
|
|
ae70cfc5e3 | ||
|
|
d46f62dd3f | ||
|
|
4757c3e0a9 | ||
|
|
7c36f87149 | ||
|
|
58d0378d49 | ||
|
|
84caa87951 | ||
|
|
23baa758cf | ||
|
|
1a3948a8eb | ||
|
|
4fca2f8b14 | ||
|
|
3173968f0e | ||
|
|
6330a13d92 | ||
|
|
2d0d34c4ff | ||
|
|
af4f3d35f0 | ||
|
|
9684ff98ea | ||
|
|
5feab1ba69 | ||
|
|
0a2df372d5 |
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"printWidth": 110,
|
|
||||||
"singleQuote": true,
|
|
||||||
"quoteProps": "consistent",
|
|
||||||
"tabWidth": 4,
|
|
||||||
"useTabs": false,
|
|
||||||
"semi": false,
|
|
||||||
"arrowParens": "avoid"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/** @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,3 +1,11 @@
|
|||||||
{
|
{
|
||||||
"npm.packageManager": "bun"
|
"npm.packageManager": "bun",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"[astro]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[yaml]": {
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,19 +17,12 @@ bun dev
|
|||||||
## Build
|
## Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun build
|
bun run build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deploy [TODO]
|
## Deploy
|
||||||
|
|
||||||
Il progetto contiene un `Dockerfile` che viene usato per il deploy (del server prodotto da Astro).
|
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.
|
||||||
|
|
||||||
```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
|
## Come Contribuire
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export default defineConfig({
|
|||||||
theme: 'github-light',
|
theme: 'github-light',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
integrations: [preact(), mdx()],
|
integrations: [
|
||||||
|
preact({
|
||||||
|
compat: true,
|
||||||
|
}),
|
||||||
|
mdx(),
|
||||||
|
],
|
||||||
output: 'static',
|
output: 'static',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,12 +21,14 @@
|
|||||||
"@fontsource/source-sans-pro": "^5.0.8",
|
"@fontsource/source-sans-pro": "^5.0.8",
|
||||||
"@fontsource/space-mono": "^5.0.20",
|
"@fontsource/space-mono": "^5.0.20",
|
||||||
"@phosphor-icons/core": "^2.1.1",
|
"@phosphor-icons/core": "^2.1.1",
|
||||||
|
"@phosphor-icons/react": "^2.1.7",
|
||||||
"@preact/signals": "^1.3.0",
|
"@preact/signals": "^1.3.0",
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/jsdom": "^21.1.7",
|
||||||
"astro": "5.1.0",
|
"astro": "5.1.0",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"katex": "^0.16.9",
|
"katex": "^0.16.9",
|
||||||
"lucide-static": "^0.468.0",
|
"lucide-static": "^0.468.0",
|
||||||
|
"marked": "^15.0.6",
|
||||||
"preact": "^10.19.6",
|
"preact": "^10.19.6",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
@@ -37,6 +39,8 @@
|
|||||||
"jsdom": "^24.1.1",
|
"jsdom": "^24.1.1",
|
||||||
"linkedom": "^0.18.4",
|
"linkedom": "^0.18.4",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
|
"prettier": "^3.5.0",
|
||||||
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
|
|||||||
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 645 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 295 KiB |
@@ -34,21 +34,14 @@ export const ComboBox = ({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div class="combobox" ref={comboRef} style={{ width: isMobile() ? undefined : itemWidth + 48 + 'px' }}>
|
||||||
class="combobox"
|
|
||||||
ref={comboRef}
|
|
||||||
style={{ width: isMobile() ? undefined : itemWidth + 48 + 'px' }}
|
|
||||||
>
|
|
||||||
<div class="selected" onClick={() => setOpen(!open)}>
|
<div class="selected" onClick={() => setOpen(!open)}>
|
||||||
<div class="content">{children[value]}</div>
|
<div class="content">{children[value]}</div>
|
||||||
{/* <span class="material-symbols-outlined">expand_more</span> */}
|
{/* <span class="material-symbols-outlined">expand_more</span> */}
|
||||||
<PhosphorIcon name="caret-down" />
|
<PhosphorIcon name="caret-down" />
|
||||||
</div>
|
</div>
|
||||||
{open && (
|
{open && (
|
||||||
<div
|
<div class={clsx('dropdown', cloak && 'invisible')} ref={el => el && setItemWidth(el.offsetWidth)}>
|
||||||
class={clsx('dropdown', cloak && 'invisible')}
|
|
||||||
ref={el => el && setItemWidth(el.offsetWidth)}
|
|
||||||
>
|
|
||||||
{Object.keys(children).map(key => (
|
{Object.keys(children).map(key => (
|
||||||
<div
|
<div
|
||||||
class="option"
|
class="option"
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
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(
|
Object.entries(
|
||||||
import.meta.glob<{ default: ImageMetadata }>(`node_modules/@phosphor-icons/core/assets/light/*.svg`, {
|
import.meta.glob<{ default: ImageMetadata }>(`node_modules/@phosphor-icons/core/assets/light/*.svg`, {
|
||||||
eager: true,
|
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 = {
|
type Props = {
|
||||||
|
|||||||
@@ -69,21 +69,21 @@ export const UtentiPage = () => {
|
|||||||
? $utentiData.value.filter(user => MACCHINISTI.includes(user.uid))
|
? $utentiData.value.filter(user => MACCHINISTI.includes(user.uid))
|
||||||
: $filter.value === 'rappstud'
|
: $filter.value === 'rappstud'
|
||||||
? $utentiData.value.filter(user => RAPPSTUD.includes(user.uid))
|
? $utentiData.value.filter(user => RAPPSTUD.includes(user.uid))
|
||||||
: $utentiData.value
|
: $utentiData.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
const $fuse = useComputed(
|
const $fuse = useComputed(
|
||||||
() =>
|
() =>
|
||||||
new Fuse($filteredData.value, {
|
new Fuse($filteredData.value, {
|
||||||
keys: ['gecos', 'uid'],
|
keys: ['gecos', 'uid'],
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const $searchText = useSignal('')
|
const $searchText = useSignal('')
|
||||||
const $searchResults = useComputed(() =>
|
const $searchResults = useComputed(() =>
|
||||||
$searchText.value.trim().length > 0
|
$searchText.value.trim().length > 0
|
||||||
? $fuse.value?.search($searchText.value).map(result => result.item) ?? []
|
? ($fuse.value?.search($searchText.value).map(result => result.item) ?? [])
|
||||||
: $filteredData.value
|
: $filteredData.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -110,7 +110,7 @@ export const UtentiPage = () => {
|
|||||||
<PhosphorIcon name={v.icon} />
|
<PhosphorIcon name={v.icon} />
|
||||||
{v.label}
|
{v.label}
|
||||||
</>,
|
</>,
|
||||||
])
|
]),
|
||||||
)}
|
)}
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
@@ -149,10 +149,7 @@ export const UtentiPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="text">{poissonUser.gecos}</div>
|
<div class="text">{poissonUser.gecos}</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<a
|
<a href={`https://poisson.phc.dm.unipi.it/~${poissonUser.uid}`} target="_blank">
|
||||||
href={`https://poisson.phc.dm.unipi.it/~${poissonUser.uid}`}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{/* <span class="material-symbols-outlined">open_in_new</span> */}
|
{/* <span class="material-symbols-outlined">open_in_new</span> */}
|
||||||
<PhosphorIcon name="arrow-square-out" />
|
<PhosphorIcon name="arrow-square-out" />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// 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>(
|
export const trottleDebounce = <T extends any[], R>(
|
||||||
fn: (...args: T) => R,
|
fn: (...args: T) => R,
|
||||||
delay: number,
|
delay: number,
|
||||||
options: { leading?: boolean; trailing?: boolean } = {}
|
options: { leading?: boolean; trailing?: boolean } = {},
|
||||||
): ((...args: T) => R | undefined) => {
|
): ((...args: T) => R | undefined) => {
|
||||||
let lastCall = 0
|
let lastCall = 0
|
||||||
let lastResult: R | undefined
|
let lastResult: R | undefined
|
||||||
|
|||||||
@@ -9,11 +9,16 @@ const ICONS_MAP: Record<string, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
image: ImageMetadata
|
|
||||||
fullName: string
|
fullName: string
|
||||||
|
description: string
|
||||||
|
|
||||||
|
image: ImageMetadata
|
||||||
|
|
||||||
entranceDate: number
|
entranceDate: number
|
||||||
exitDate?: number
|
exitDate?: number
|
||||||
description: string
|
|
||||||
|
founder?: boolean
|
||||||
|
|
||||||
social?: {
|
social?: {
|
||||||
github?: string
|
github?: string
|
||||||
linkedin?: string
|
linkedin?: string
|
||||||
@@ -22,13 +27,14 @@ type Props = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { image, fullName, entranceDate, exitDate, description, social } = Astro.props
|
const { fullName, description, image, entranceDate, exitDate, founder, social } = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="bubble">
|
<div class="bubble">
|
||||||
<img src={image.src} alt={fullName.toLowerCase()} />
|
<img src={image.src} alt={fullName.toLowerCase()} />
|
||||||
<div class="title">{fullName}</div>
|
<div class="title">{fullName}</div>
|
||||||
<div class="date">{entranceDate}—{exitDate ?? 'Presente'}</div>
|
<div class="date">{entranceDate}—{exitDate ?? 'Presente'}</div>
|
||||||
|
{founder && <div class="founder">Fondatore</div>}
|
||||||
<div class="description">{description}</div>
|
<div class="description">{description}</div>
|
||||||
{
|
{
|
||||||
social && (
|
social && (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const links = [
|
|||||||
// { href: '/appunti', text: 'Appunti' },
|
// { href: '/appunti', text: 'Appunti' },
|
||||||
{ href: '/notizie', text: 'Notizie' },
|
{ href: '/notizie', text: 'Notizie' },
|
||||||
{ href: '/guide', text: 'Guide' },
|
{ href: '/guide', text: 'Guide' },
|
||||||
|
{ href: '/domande-esami', text: 'Domande Orali' },
|
||||||
{ href: '/storia', text: 'Storia' },
|
{ href: '/storia', text: 'Storia' },
|
||||||
// { href: '/login', text: 'Login' },
|
// { href: '/login', text: 'Login' },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ const { name } = Astro.props
|
|||||||
|
|
||||||
const icons = Object.fromEntries(
|
const icons = Object.fromEntries(
|
||||||
Object.entries(
|
Object.entries(
|
||||||
import.meta.glob<{ default: ImageMetadata }>(`node_modules/@phosphor-icons/core/assets/light/*.svg`)
|
import.meta.glob<{ default: ImageMetadata }>(`node_modules/@phosphor-icons/core/assets/light/*.svg`),
|
||||||
).map(([path, module]) => [path.split('/').pop()!.split('.')[0].replace('-light', ''), module])
|
).map(([path, module]) => [path.split('/').pop()!.split('.')[0].replace('-light', ''), module]),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!icons[name]) {
|
if (!icons[name]) {
|
||||||
|
|||||||
@@ -66,7 +66,10 @@ Vediamo un piccolo esempio di file `index.html` che possiamo creare:
|
|||||||
style="max-width: 300px; border-radius: 10px;"
|
style="max-width: 300px; border-radius: 10px;"
|
||||||
/>
|
/>
|
||||||
<p>Ciao! Sono Sergio Steffè.</p>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -35,10 +35,7 @@ Una card semplice ha un titolo ed una descrizione.
|
|||||||
```astro
|
```astro
|
||||||
<div class="card" style="--card-base: var(--guide-base); max-width: 25rem;">
|
<div class="card" style="--card-base: var(--guide-base); max-width: 25rem;">
|
||||||
<div class="title">Titolo</div>
|
<div class="title">Titolo</div>
|
||||||
<div class="text">
|
<div class="text">Descrizione lorem ipsum dolor sit amet consectetur adipisicing elit. Aspernatur, labore?</div>
|
||||||
Descrizione lorem ipsum dolor sit amet consectetur
|
|
||||||
adipisicing elit. Aspernatur, labore?
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -51,10 +48,7 @@ Le card possono essere di dimensioni diverse. Questa è una card grande.
|
|||||||
```astro
|
```astro
|
||||||
<div class="card large" style="--card-base: lightgreen; max-width: 25rem;">
|
<div class="card large" style="--card-base: lightgreen; max-width: 25rem;">
|
||||||
<div class="title">Titolo</div>
|
<div class="title">Titolo</div>
|
||||||
<div class="text">
|
<div class="text">Descrizione lorem ipsum dolor sit amet consectetur adipisicing elit. Aspernatur, labore?</div>
|
||||||
Descrizione lorem ipsum dolor sit amet consectetur
|
|
||||||
adipisicing elit. Aspernatur, labore?
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -100,10 +94,7 @@ Se c'è poco testo, può essere inserito direttamente nella card.
|
|||||||
|
|
||||||
```astro
|
```astro
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="text">
|
<div class="text">Descrizione lorem ipsum dolor sit amet consectetur adipisicing elit. Aspernatur, labore?</div>
|
||||||
Descrizione lorem ipsum dolor sit amet consectetur
|
|
||||||
adipisicing elit. Aspernatur, labore?
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -113,16 +104,15 @@ Altrimenti può essere inserito in un tag `<p>`.
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<p>
|
<p>
|
||||||
Lorem ipsum dolor sit amet consectetur, adipisicing elit.
|
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Distinctio, vel! Veritatis est sit beatae eveniet.
|
||||||
Distinctio, vel! Veritatis est sit beatae eveniet.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Error, minus, asperiores quaerat nulla cumque, nisi ipsam
|
Error, minus, asperiores quaerat nulla cumque, nisi ipsam assumenda consectetur accusamus tempore
|
||||||
assumenda consectetur accusamus tempore consequatur quae. Fugit?
|
consequatur quae. Fugit?
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Quos sapiente amet numquam quis, libero odit eum, eius
|
Quos sapiente amet numquam quis, libero odit eum, eius perspiciatis repellat nesciunt cupiditate asperiores
|
||||||
perspiciatis repellat nesciunt cupiditate asperiores maiores?
|
maiores?
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,19 +122,10 @@ C'è anche il modificatore `small` e `dimmed` per ridurre la grandezza del testo
|
|||||||
|
|
||||||
```astro
|
```astro
|
||||||
<div class="card" style="max-width: 25rem;">
|
<div class="card" style="max-width: 25rem;">
|
||||||
<div class="text">
|
<div class="text">Some normal text, this is a very long text that should wrap on the next line</div>
|
||||||
Some normal text, this is a very long
|
<div class="text small">This is some small text</div>
|
||||||
text that should wrap on the next line
|
<div class="text dimmed">This is some dimmed text</div>
|
||||||
</div>
|
<div class="text small dimmed">This is some small dimmed text</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>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -197,7 +178,6 @@ import { ComboBox } from '@/lib/components/ComboBox'
|
|||||||
const [value, setValue] = useState('option-1')
|
const [value, setValue] = useState('option-1')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<ComboBox value={value} setValue={setValue}>
|
<ComboBox value={value} setValue={setValue}>
|
||||||
{{
|
{{
|
||||||
|
|||||||
@@ -35,4 +35,3 @@ Il nuovo sito unifica varie funzionalità che prima erano sparse in diversi siti
|
|||||||
## Conclusioni
|
## 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)!
|
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)!
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Ritorno alla normalità
|
||||||
|
|
||||||
|
Grazie all’intervento, 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 🤦♂️
|
||||||
@@ -32,11 +32,63 @@
|
|||||||
|
|
||||||
- fullName: Illya Serdyuk
|
- fullName: Illya Serdyuk
|
||||||
entranceDate: 2020
|
entranceDate: 2020
|
||||||
description: Bla bla Void Linux
|
|
||||||
social:
|
social:
|
||||||
github: https://github.com/Kratacoa
|
github: https://github.com/Kratacoa
|
||||||
|
|
||||||
|
# Vecchi Macchinisti
|
||||||
|
|
||||||
- fullName: Francesco Manicastri
|
- fullName: Francesco Manicastri
|
||||||
entranceDate: 2022
|
entranceDate: 2020
|
||||||
exitDate: 2024
|
exitDate: 2024
|
||||||
description: Bla bla Keenan Crane
|
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
|
||||||
|
|||||||
@@ -6,12 +6,39 @@ declare module '*.yaml' {
|
|||||||
declare module '@/data/macchinisti.yaml' {
|
declare module '@/data/macchinisti.yaml' {
|
||||||
type Macchinista = {
|
type Macchinista = {
|
||||||
fullName: string
|
fullName: string
|
||||||
|
description: string
|
||||||
|
|
||||||
entranceDate: number
|
entranceDate: number
|
||||||
exitDate?: number
|
exitDate?: number
|
||||||
description: string
|
|
||||||
social: Record<string, string>
|
social: Record<string, string>
|
||||||
|
|
||||||
|
founder?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const value: Macchinista[]
|
const value: Macchinista[]
|
||||||
export default value
|
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>
|
<script>
|
||||||
import renderMathInElement from 'katex/contrib/auto-render'
|
import renderMathInElement from 'katex/contrib/auto-render'
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
const renderMath = () => {
|
||||||
renderMathInElement(document.body, {
|
renderMathInElement(document.body, {
|
||||||
delimiters: [
|
delimiters: [
|
||||||
{ left: '$$', right: '$$', display: true },
|
{ left: '$$', right: '$$', display: true },
|
||||||
@@ -54,7 +54,12 @@ const { title, description, thumbnail, pageTags } = Astro.props
|
|||||||
],
|
],
|
||||||
throwOnError: false,
|
throwOnError: false,
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => renderMath())
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
window.renderMath = renderMath
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
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>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
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">
|
<div class="card-list">
|
||||||
{
|
{
|
||||||
news.map(newsItem => (
|
news.toReversed().map(newsItem => (
|
||||||
<Card>
|
<Card>
|
||||||
<a href={`/notizie/${newsItem.slug}`} class="title">
|
<a href={`/notizie/${newsItem.slug}`} class="title">
|
||||||
{newsItem.data.title}
|
{newsItem.data.title}
|
||||||
@@ -80,8 +80,7 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
|
|||||||
>
|
>
|
||||||
<p>Gitea è un servizio di hosting per progetti software, come GitHub ma autogestito.</p>
|
<p>Gitea è un servizio di hosting per progetti software, come GitHub ma autogestito.</p>
|
||||||
<p>
|
<p>
|
||||||
Qui puoi trovare i progetti del PHC, e accedendo con un account di Ateneo potrai crearne
|
Qui puoi trovare i progetti del PHC, e accedendo con un account di Ateneo potrai crearne di nuovi.
|
||||||
di nuovi.
|
|
||||||
</p>
|
</p>
|
||||||
</ProjectCard>
|
</ProjectCard>
|
||||||
<!-- <ProjectCard
|
<!-- <ProjectCard
|
||||||
@@ -98,8 +97,8 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
|
|||||||
imgSrc="https://lab.phc.dm.unipi.it/orario/icon.png"
|
imgSrc="https://lab.phc.dm.unipi.it/orario/icon.png"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Questo sito permette di visualizzare il proprio orario delle lezioni, con informazioni sui
|
Questo sito permette di visualizzare il proprio orario delle lezioni, con informazioni sui docenti e
|
||||||
docenti e le aule.
|
le aule.
|
||||||
</p>
|
</p>
|
||||||
</ProjectCard>
|
</ProjectCard>
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
@@ -118,8 +117,8 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
|
|||||||
imgSrc="https://steffe.lb.cs.dm.unipi.it/assets/img/logo.png"
|
imgSrc="https://steffe.lb.cs.dm.unipi.it/assets/img/logo.png"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Cluster progettato ed assemblato durante il progetto speciale per la didattica "Calcolo
|
Cluster progettato ed assemblato durante il progetto speciale per la didattica "Calcolo Parallelo
|
||||||
Parallelo dall'Infrastruttura alla Matematica".
|
dall'Infrastruttura alla Matematica".
|
||||||
</p>
|
</p>
|
||||||
</ProjectCard>
|
</ProjectCard>
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
@@ -128,10 +127,7 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
|
|||||||
style="--card-bg: #bd9fec; --masonry-height: 2;"
|
style="--card-bg: #bd9fec; --masonry-height: 2;"
|
||||||
imgSrc="https://seminarietti.phc.dm.unipi.it/favicon.png"
|
imgSrc="https://seminarietti.phc.dm.unipi.it/favicon.png"
|
||||||
>
|
>
|
||||||
<p>
|
<p>Storico degli incontri organizzati dal PHC su argomenti di informatica, matematica e tecnologia.</p>
|
||||||
Storico degli incontri organizzati dal PHC su argomenti di informatica, matematica e
|
|
||||||
tecnologia.
|
|
||||||
</p>
|
|
||||||
</ProjectCard>
|
</ProjectCard>
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
title="Tutorato"
|
title="Tutorato"
|
||||||
@@ -140,8 +136,8 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
|
|||||||
imgSrc="https://tutorato.phc.dm.unipi.it/favicon.svg"
|
imgSrc="https://tutorato.phc.dm.unipi.it/favicon.svg"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Un sito con tutte le informazioni sui tutorati di Matematica, con tanto di archivio degli
|
Un sito con tutte le informazioni sui tutorati di Matematica, con tanto di archivio degli anni
|
||||||
anni passati.
|
passati.
|
||||||
</p>
|
</p>
|
||||||
</ProjectCard>
|
</ProjectCard>
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
@@ -151,10 +147,18 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
|
|||||||
imgSrc="https://ggwp.phc.dm.unipi.it/ggwp-icon.png"
|
imgSrc="https://ggwp.phc.dm.unipi.it/ggwp-icon.png"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Sito per il tabellone del GGWP, utilizzato durante la gara di Novembre 2024 dagli
|
Sito per il tabellone del GGWP, utilizzato durante la gara di Novembre 2024 dagli organizzatori per
|
||||||
organizzatori per inserire le risposte alla gara in tempo reale.
|
inserire le risposte alla gara in tempo reale.
|
||||||
</p>
|
</p>
|
||||||
</ProjectCard>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="wanna-be-macchinista">
|
<section class="wanna-be-macchinista">
|
||||||
@@ -172,22 +176,18 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
|
|||||||
supporto tecnico per installare ed usare <strong>Linux</strong> sul proprio portatile
|
supporto tecnico per installare ed usare <strong>Linux</strong> sul proprio portatile
|
||||||
</li>
|
</li>
|
||||||
<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)
|
computer (antichi e moderni)
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>sviluppo software</strong> di backend e frontend per siti web (ma non solo)
|
<strong>sviluppo software</strong> di backend e frontend per siti web (ma non solo)
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
organizzazione di <strong>seminari</strong> di divulgazione (vedi <a href="#projects"
|
organizzazione di <strong>seminari</strong> di divulgazione (vedi <a href="#projects">sopra</a>)
|
||||||
>sopra</a
|
|
||||||
>)
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
Infine, il PHC è prima di tutto un luogo dove <strong>imparare</strong>, <strong
|
Infine, il PHC è prima di tutto un luogo dove <strong>imparare</strong>, <strong>trasmettere</strong
|
||||||
>trasmettere</strong
|
|
||||||
> le proprie conoscenze e <strong>condividere</strong> la passione per la tecnologia.
|
> le proprie conoscenze e <strong>condividere</strong> la passione per la tecnologia.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -219,13 +219,12 @@ const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
|
|||||||
<div class="title">Vuoi diventare macchinista?</div>
|
<div class="title">Vuoi diventare macchinista?</div>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<p>
|
<p>
|
||||||
<strong>Macchinista non si nasce, si diventa:</strong> se sei uno studente di Matematica e
|
<strong>Macchinista non si nasce, si diventa:</strong> se sei uno studente di Matematica e vuoi diventare
|
||||||
vuoi diventare un macchinista, vienici a trovare!
|
un macchinista, vienici a trovare!
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
L'unico prerequisito è la voglia di imparare! Di solito, c'è un periodo di "apprendistato"
|
L'unico prerequisito è la voglia di imparare! Di solito, c'è un periodo di "apprendistato" nel quale
|
||||||
nel quale si apprendono le basi; una volta superato potrai diventare un macchinista a
|
si apprendono le basi; una volta superato potrai diventare un macchinista a tutti gli effetti.
|
||||||
tutti gli effetti.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ const images = Object.fromEntries(
|
|||||||
Object.entries(
|
Object.entries(
|
||||||
import.meta.glob<{ default: ImageMetadata }>('@/assets/macchinisti/*', {
|
import.meta.glob<{ default: ImageMetadata }>('@/assets/macchinisti/*', {
|
||||||
eager: true,
|
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)
|
const currentMacchinisti = macchinisti.filter(macchinista => !macchinista.exitDate)
|
||||||
@@ -35,9 +35,9 @@ const getMacchinistaPicture = (fullName: string) => {
|
|||||||
<p>
|
<p>
|
||||||
<em>> Chi sono i macchinisti?</em>
|
<em>> Chi sono i macchinisti?</em>
|
||||||
<br />
|
<br />
|
||||||
Questo è l'appellativo dato agli studenti che si occupano di gestire l'infrastuttura e i servizi
|
Questo è l'appellativo dato agli studenti che si occupano di gestire l'infrastuttura e i servizi del
|
||||||
del PHC (vedi la homepage per informazioni su come diventare un macchinista). Qua sotto trovi
|
PHC (vedi la homepage per informazioni su come diventare un macchinista). Qua sotto trovi i macchinisti
|
||||||
i macchinisti attualmente attivi in PHC.
|
attualmente attivi in PHC.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,8 +60,8 @@ const getMacchinistaPicture = (fullName: string) => {
|
|||||||
<div class="title"><s>Deus</s> Ex Macchinisti</div>
|
<div class="title"><s>Deus</s> Ex Macchinisti</div>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<p>
|
<p>
|
||||||
Qui raccogliamo qualche informazione sui macchinisti del passato, che hanno contribuito a
|
Qui raccogliamo qualche informazione sui macchinisti del passato, che hanno contribuito a rendere il
|
||||||
rendere il PHC quello che è oggi.
|
PHC quello che è oggi.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,11 +70,12 @@ const getMacchinistaPicture = (fullName: string) => {
|
|||||||
{
|
{
|
||||||
pastMacchinisti.map(macchinista => (
|
pastMacchinisti.map(macchinista => (
|
||||||
<Bubble
|
<Bubble
|
||||||
image={getMacchinistaPicture(macchinista.fullName)}
|
|
||||||
fullName={macchinista.fullName}
|
fullName={macchinista.fullName}
|
||||||
|
description={macchinista.description}
|
||||||
|
image={getMacchinistaPicture(macchinista.fullName)}
|
||||||
entranceDate={macchinista.entranceDate}
|
entranceDate={macchinista.entranceDate}
|
||||||
exitDate={macchinista.exitDate}
|
exitDate={macchinista.exitDate}
|
||||||
description={macchinista.description}
|
founder={macchinista.founder}
|
||||||
social={macchinista.social}
|
social={macchinista.social}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const news = await getCollection('news')
|
|||||||
<h1><a href="/notizie">Notizie</a></h1>
|
<h1><a href="/notizie">Notizie</a></h1>
|
||||||
<div class="card-list">
|
<div class="card-list">
|
||||||
{
|
{
|
||||||
news.map(newsItem => (
|
news.toReversed().map(newsItem => (
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<a href={`/notizie/${newsItem.slug}`} class="title">
|
<a href={`/notizie/${newsItem.slug}`} class="title">
|
||||||
{newsItem.data.title}
|
{newsItem.data.title}
|
||||||
|
|||||||
@@ -17,9 +17,8 @@ import WebSite from '@/assets/gallery/005-website-development@3x4.jpg'
|
|||||||
<div class="title">Storia</div>
|
<div class="title">Storia</div>
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<p>
|
<p>
|
||||||
Il PHC nasce quasi venti anni fa, nel lontano 1999 ed ha perciò una ricca storia. Qui
|
Il PHC nasce quasi venti anni fa, nel lontano 1999 ed ha perciò una ricca storia. Qui proveremo ad
|
||||||
proveremo ad annoverare la storia del PHC, in una timeline con gli eventi più salienti del
|
annoverare la storia del PHC, in una timeline con gli eventi più salienti del progetto.
|
||||||
progetto.
|
|
||||||
</p>
|
</p>
|
||||||
<img
|
<img
|
||||||
class="small flat"
|
class="small flat"
|
||||||
@@ -36,26 +35,25 @@ import WebSite from '@/assets/gallery/005-website-development@3x4.jpg'
|
|||||||
<div class="timeline">
|
<div class="timeline">
|
||||||
<Timeline title="Un nuovo look" year="Nov 2024">
|
<Timeline title="Un nuovo look" year="Nov 2024">
|
||||||
<p>
|
<p>
|
||||||
Dopo <s>mesi</s> anni di lavoro e di progettazione, il sito del PHC viene riscritto da zero
|
Dopo <s>mesi</s> anni di lavoro e di progettazione, il sito del PHC viene riscritto da zero in Astro.
|
||||||
in Astro. Il progetto è stato voluto principalmente da <strong
|
Il progetto è stato voluto principalmente da <strong>Antonio De Lucreziis</strong> (in foto, in fase
|
||||||
>Antonio De Lucreziis</strong
|
di sviluppo), con il supporto di <strong>Francesco Minnocci</strong>.
|
||||||
> (in foto, in fase di sviluppo), con il supporto di <strong>Francesco Minnocci</strong>.
|
|
||||||
</p>
|
</p>
|
||||||
<img class="fill" src={WebSite.src} alt="Sviluppo del sito" />
|
<img class="fill" src={WebSite.src} alt="Sviluppo del sito" />
|
||||||
</Timeline>
|
</Timeline>
|
||||||
<Timeline title="Luca e le grandi pulizie" year="Apr 2024">
|
<Timeline title="Luca e le grandi pulizie" year="Apr 2024">
|
||||||
<p>
|
<p>
|
||||||
Nel 2024, <strong>Luca Lombardo</strong> entra a far parte del PHC. Questo stesso anno vengono
|
Nel 2024, <strong>Luca Lombardo</strong> entra a far parte del PHC. Questo stesso anno vengono effettuate
|
||||||
effettuate delle grandi pulizie nella stanza del PHC, come non si faceva dal lontano 2006.
|
delle grandi pulizie nella stanza del PHC, come non si faceva dal lontano 2006.
|
||||||
</p>
|
</p>
|
||||||
<img class="fill" src={imgPulizie.src} alt="pulizie phc" />
|
<img class="fill" src={imgPulizie.src} alt="pulizie phc" />
|
||||||
</Timeline>
|
</Timeline>
|
||||||
<Timeline title="Il cluster di Raspberry Pi" year="2023">
|
<Timeline title="Il cluster di Raspberry Pi" year="2023">
|
||||||
<p>
|
<p>
|
||||||
Il dipartimento acquista in due fasi diverse 34 (cloni) di Raspberry Pi 4, che vengono
|
Il dipartimento acquista in due fasi diverse 34 (cloni) di Raspberry Pi 4, che vengono assemblati in
|
||||||
assemblati in un cluster per il progetto "High Performance Mathematics". Il cluster è
|
un cluster per il progetto "High Performance Mathematics". Il cluster è stato assemblato,
|
||||||
stato assemblato, configurato e messo in funzione con la forte collaborazione macchinisti
|
configurato e messo in funzione con la forte collaborazione macchinisti del PHC, che ancora oggi ne
|
||||||
del PHC, che ancora oggi ne curano la manutenzione.
|
curano la manutenzione.
|
||||||
</p>
|
</p>
|
||||||
<img class="fill" src={imgCluster.src} alt="cluster di raspberry pi" />
|
<img class="fill" src={imgCluster.src} alt="cluster di raspberry pi" />
|
||||||
</Timeline>
|
</Timeline>
|
||||||
@@ -82,9 +80,8 @@ import WebSite from '@/assets/gallery/005-website-development@3x4.jpg'
|
|||||||
</Timeline>
|
</Timeline>
|
||||||
<Timeline title="Rinnovo del sito" year="2004">
|
<Timeline title="Rinnovo del sito" year="2004">
|
||||||
<p>
|
<p>
|
||||||
Dopo un periodo di inattività del progetto, il sito del PHC viene riscritto in PHP e
|
Dopo un periodo di inattività del progetto, il sito del PHC viene riscritto in PHP e trasferito sul
|
||||||
trasferito sul dominio <a
|
dominio <a href="https://web.archive.org/web/20040823112401/http://poisson.phc.unipi.it/"
|
||||||
href="https://web.archive.org/web/20040823112401/http://poisson.phc.unipi.it/"
|
|
||||||
>poisson.phc.unipi.it</a
|
>poisson.phc.unipi.it</a
|
||||||
>, il cui design è caratterizzato da un <a
|
>, il cui design è caratterizzato da un <a
|
||||||
href="https://web.archive.org/web/20060609003904im_/http://poisson.phc.unipi.it/logo_studenti.orig.png"
|
href="https://web.archive.org/web/20060609003904im_/http://poisson.phc.unipi.it/logo_studenti.orig.png"
|
||||||
@@ -108,33 +105,28 @@ import WebSite from '@/assets/gallery/005-website-development@3x4.jpg'
|
|||||||
</Timeline>
|
</Timeline>
|
||||||
<Timeline title="Rete del PHC e DNS" year="1999">
|
<Timeline title="Rete del PHC e DNS" year="1999">
|
||||||
<p>
|
<p>
|
||||||
Nel maggio del 1999 viene attivata la rete 131.114.10.0, con tanto di nameserver sul
|
Nel maggio del 1999 viene attivata la rete 131.114.10.0, con tanto di nameserver sul dominio <a
|
||||||
dominio <a href="https://web.archive.org/web/20010410215451/http://www.phc.unipi.it/"
|
href="https://web.archive.org/web/20010410215451/http://www.phc.unipi.it/">phc.unipi.it</a
|
||||||
>phc.unipi.it</a
|
|
||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
</Timeline>
|
</Timeline>
|
||||||
<Timeline title="Fondazione del PHC" year="1999">
|
<Timeline title="Fondazione del PHC" year="1999">
|
||||||
<p>
|
<p>
|
||||||
In seguito alla proposta del prof. <strong>Sergio Steffè</strong>, in data 26 febbraio
|
In seguito alla proposta del prof. <strong>Sergio Steffè</strong>, in data 26 febbraio 1999 il
|
||||||
1999 il Dipartimento di Matematica approva una delibera per stanziare la stanza 106 ed
|
Dipartimento di Matematica approva una delibera per stanziare la stanza 106, una sottorete, il
|
||||||
alcuni computer ad uso di un gruppo di studenti, così da avere un luogo in cui
|
dominio DNS phc.dm.unipi.it ed alcuni computer ad uso di un gruppo di studenti (Riccardo Murri e
|
||||||
"smanettare", dare supporto informatica agli studenti e gestire il sito Poisson.
|
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
|
||||||
</p>
|
</p>
|
||||||
<img
|
<img class="fill" src="https://poisson.phc.dm.unipi.it/~steffe/sergio.jpg" alt="Sergio Steffè" />
|
||||||
class="fill"
|
|
||||||
src="https://poisson.phc.dm.unipi.it/~steffe/sergio.jpg"
|
|
||||||
alt="Sergio Steffè"
|
|
||||||
/>
|
|
||||||
</Timeline>
|
</Timeline>
|
||||||
<Timeline title="Apertura di Poisson" year="~1994">
|
<Timeline title="Apertura di Poisson" year="~1995">
|
||||||
<p>
|
<p>
|
||||||
Nell'attuale Aula 4, allora semplice Aula studenti, nasce il sito web <strong
|
Nell'attuale Aula 4, allora semplice Aula studenti, nasce il sito web <strong
|
||||||
>poisson.dm.unipi.it</strong
|
>poisson.dm.unipi.it</strong
|
||||||
>
|
>
|
||||||
su dei computer messi a disposizione agli studenti da Vinicio Villani. Una versione del 1996
|
su dei computer messi a disposizione agli studenti da Vinicio Villani. Una versione del 1996 di tale
|
||||||
di tale sito si trova nel <a
|
sito si trova nel <a href="https://web.archive.org/web/19971017065805/http://poisson.dm.unipi.it/"
|
||||||
href="https://web.archive.org/web/19971017065805/http://poisson.dm.unipi.it/"
|
|
||||||
>Web Archive</a
|
>Web Archive</a
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -75,12 +75,7 @@ function setup() {
|
|||||||
const handle = window.setInterval(() => {
|
const handle = window.setInterval(() => {
|
||||||
const time = new Date().getTime() - startTime.getTime()
|
const time = new Date().getTime() - startTime.getTime()
|
||||||
|
|
||||||
update(
|
update(state, g.canvas.width / window.devicePixelRatio, g.canvas.height / window.devicePixelRatio, time)
|
||||||
state,
|
|
||||||
g.canvas.width / window.devicePixelRatio,
|
|
||||||
g.canvas.height / window.devicePixelRatio,
|
|
||||||
time
|
|
||||||
)
|
|
||||||
render(g, state, time)
|
render(g, state, time)
|
||||||
}, 1000 / RENDERER_FPS)
|
}, 1000 / RENDERER_FPS)
|
||||||
|
|
||||||
@@ -266,25 +261,16 @@ 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-right') &&
|
||||||
implies(grid.has([x + 1, y]), () => grid.get([x + 1, y]) !== 'down-left'),
|
implies(grid.has([x + 1, y]), () => grid.get([x + 1, y]) !== 'down-left'),
|
||||||
['down-left']: ([x, y], grid) =>
|
['down-left']: ([x, y], grid) =>
|
||||||
!grid.has([x - 1, y + 1]) &&
|
!grid.has([x - 1, y + 1]) && implies(grid.has([x - 1, y]), () => grid.get([x - 1, y]) === 'down-left'),
|
||||||
implies(grid.has([x - 1, y]), () => grid.get([x - 1, y]) === 'down-left'),
|
|
||||||
['down-right']: ([x, y], grid) =>
|
['down-right']: ([x, y], grid) =>
|
||||||
!grid.has([x + 1, y + 1]) &&
|
!grid.has([x + 1, y + 1]) && 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-right'),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function pruneDirections(
|
function pruneDirections(grid: Grid<WireCell>, position: Point2, directions: WireDirection[]): WireDirection[] {
|
||||||
grid: Grid<WireCell>,
|
|
||||||
position: Point2,
|
|
||||||
directions: WireDirection[]
|
|
||||||
): WireDirection[] {
|
|
||||||
return directions.filter(dir => DIR_AVAILABLE_PREDICATE[dir](position, grid))
|
return directions.filter(dir => DIR_AVAILABLE_PREDICATE[dir](position, grid))
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateWire(
|
function generateWire(grid: Grid<WireCell>, startingPoint: Point2): { position: Point2; direction: WireCell }[] {
|
||||||
grid: Grid<WireCell>,
|
|
||||||
startingPoint: Point2
|
|
||||||
): { position: Point2; direction: WireCell }[] {
|
|
||||||
const segmentLength = Math.floor(1 - Math.random() ** 2) * 10 + 30
|
const segmentLength = Math.floor(1 - Math.random() ** 2) * 10 + 30
|
||||||
let currentPosition = startingPoint
|
let currentPosition = startingPoint
|
||||||
let currentDirection: WireDirection = randomChoice(['down', 'down', 'down', 'down-left', 'down-right'])
|
let currentDirection: WireDirection = randomChoice(['down', 'down', 'down', 'down-left', 'down-right'])
|
||||||
@@ -292,11 +278,7 @@ function generateWire(
|
|||||||
const steps: { position: Point2; direction: WireCell }[] = []
|
const steps: { position: Point2; direction: WireCell }[] = []
|
||||||
|
|
||||||
for (let i = 0; i < segmentLength; i++) {
|
for (let i = 0; i < segmentLength; i++) {
|
||||||
const availableDirections = pruneDirections(grid, currentPosition, [
|
const availableDirections = pruneDirections(grid, currentPosition, ['down', 'down-left', 'down-right'])
|
||||||
'down',
|
|
||||||
'down-left',
|
|
||||||
'down-right',
|
|
||||||
])
|
|
||||||
if (availableDirections.length === 0) {
|
if (availableDirections.length === 0) {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
width: 22px;
|
width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
|
|
||||||
display: grid;
|
display: grid inline;
|
||||||
place-content: center;
|
place-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +33,11 @@
|
|||||||
place-content: center;
|
place-content: center;
|
||||||
|
|
||||||
font-size: 24px;
|
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;
|
max-width: 32px;
|
||||||
}
|
}
|
||||||
@@ -79,29 +83,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 {
|
.search-results {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@@ -592,6 +573,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.metadata {
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
justify-content: start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $screen-desktop-min) {
|
@media screen and (max-width: $screen-desktop-min) {
|
||||||
padding: 0.9rem;
|
padding: 0.9rem;
|
||||||
|
|
||||||
@@ -609,6 +597,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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
|
// Card List
|
||||||
//
|
//
|
||||||
@@ -832,17 +852,15 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 3rem 6rem;
|
gap: 3rem 0rem;
|
||||||
|
|
||||||
> .bubble {
|
> .bubble {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto auto auto auto;
|
|
||||||
grid-auto-rows: auto;
|
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
width: 28ch;
|
width: 18rem;
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -854,6 +872,29 @@
|
|||||||
border-radius: 0.25rem;
|
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 {
|
.social {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
@@ -872,7 +913,101 @@
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 1 / 1;
|
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,4 +129,47 @@
|
|||||||
justify-self: center;
|
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,6 +122,8 @@ body {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
|
||||||
|
padding: 0.25rem 1.325rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $screen-desktop-min) {
|
@media screen and (max-width: $screen-desktop-min) {
|
||||||
@@ -208,6 +210,10 @@ body {
|
|||||||
@media screen and (max-width: $screen-desktop-min) {
|
@media screen and (max-width: $screen-desktop-min) {
|
||||||
padding: 3rem 1rem;
|
padding: 3rem 1rem;
|
||||||
gap: 3rem;
|
gap: 3rem;
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -213,7 +213,7 @@
|
|||||||
// background: #fcddff;
|
// background: #fcddff;
|
||||||
// background: #ffa89c;
|
// background: #ffa89c;
|
||||||
background: var(--card-bg, var(--project-card-bg));
|
background: var(--card-bg, var(--project-card-bg));
|
||||||
color: #000e;
|
color: var(--card-fg, #000e);
|
||||||
|
|
||||||
@include neo-brutalist-card($size: 3px, $offset: 9px);
|
@include neo-brutalist-card($size: 3px, $offset: 9px);
|
||||||
|
|
||||||
@@ -437,6 +437,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
// .login {
|
||||||
// background: #ddfaff;
|
// background: #ddfaff;
|
||||||
|
|
||||||
@@ -653,8 +682,8 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
padding: 6rem;
|
padding: 4.5rem 3rem;
|
||||||
gap: 6rem;
|
gap: 4.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,18 @@
|
|||||||
@layer typography {
|
@layer typography {
|
||||||
@include geometric-headings;
|
@include geometric-headings;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
// text-align: justify;
|
// text-align: justify;
|
||||||
// hyphens: auto;
|
// hyphens: auto;
|
||||||
@@ -129,6 +141,9 @@
|
|||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
|
width: 50ch;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
@include neo-brutalist-card(2px);
|
@include neo-brutalist-card(2px);
|
||||||
|
|
||||||
&.fill {
|
&.fill {
|
||||||
@@ -201,6 +216,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li + li {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
a,
|
a,
|
||||||
a:visited {
|
a:visited {
|
||||||
color: var(--zone-color, #1e6733);
|
color: var(--zone-color, #1e6733);
|
||||||
|
|||||||