Compare commits

..

No commits in common. 'main' and 'main' have entirely different histories.
main ... main

@ -7,19 +7,15 @@
kind: pipeline
name: default
type: docker
steps:
- name: deploy
image: node:22-alpine
image: node:latest
volumes:
- name: host-website-dist
path: /mnt/website
commands:
- uname -a
- node -v
- npm ci
- node -e 'import Sharp from "sharp"; console.log(Sharp)'
- npm install
- npm run build
- cp -rT ./dist /mnt/website
@ -36,8 +32,8 @@ trigger:
---
kind: pipeline
name: caddy-permissions
type: exec # this job is executed on the host machine
name: caddy-permissions
depends_on:
- default

@ -2,7 +2,6 @@ import { defineConfig } from 'astro/config'
import preact from '@astrojs/preact'
import mdx from '@astrojs/mdx'
import remarkMath from 'remark-math'
import yaml from '@rollup/plugin-yaml'
@ -15,7 +14,6 @@ export default defineConfig({
port: 3000,
},
markdown: {
remarkPlugins: [remarkMath],
shikiConfig: {
theme: 'github-light',
},
@ -24,9 +22,7 @@ export default defineConfig({
preact({
compat: true,
}),
mdx({
remarkPlugins: [remarkMath],
}),
mdx(),
],
output: 'static',
})

Binary file not shown.

23597
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -11,44 +11,41 @@
},
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/node": "^9.4.3",
"@astrojs/preact": "^4.1.1",
"@fontsource-variable/material-symbols-outlined": "^5.2.21",
"@fontsource/iosevka": "^5.2.5",
"@fontsource/mononoki": "^5.2.5",
"@fontsource/open-sans": "^5.2.6",
"@fontsource/source-code-pro": "^5.2.6",
"@fontsource/source-sans-pro": "^5.2.5",
"@fontsource/space-mono": "^5.2.8",
"@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.10",
"@preact/signals": "^1.3.2",
"@phosphor-icons/react": "^2.1.7",
"@preact/signals": "^1.3.0",
"@types/jsdom": "^21.1.7",
"astro": "^5.13.7",
"fuse.js": "^7.1.0",
"katex": "^0.16.22",
"astro": "5.1.0",
"fuse.js": "^7.0.0",
"katex": "^0.16.9",
"lucide-static": "^0.468.0",
"marked": "^15.0.12",
"node-addon-api": "^8.5.0",
"node-gyp": "^11.4.2",
"preact": "^10.27.2",
"sharp": "^0.34.3",
"typescript": "^5.9.2"
"marked": "^15.0.6",
"preact": "^10.19.6",
"typescript": "^5.3.3"
},
"devDependencies": {
"@astrojs/mdx": "^4.3.5",
"@astrojs/mdx": "4.0.2",
"@rollup/plugin-yaml": "^4.1.2",
"@types/katex": "^0.16.7",
"jsdom": "^24.1.3",
"linkedom": "^0.18.12",
"jsdom": "^24.1.1",
"linkedom": "^0.18.4",
"npm-run-all": "^4.1.5",
"prettier": "^3.6.2",
"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.92.1",
"tsx": "^4.20.5"
"sass": "^1.71.1",
"tsx": "^4.7.1"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 877 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

@ -41,13 +41,7 @@ export const ComboBox = ({
<PhosphorIcon name="caret-down" />
</div>
{open && (
<div
class={clsx('dropdown', cloak && 'invisible')}
ref={el => {
if (!el) return
setItemWidth(el.offsetWidth)
}}
>
<div class={clsx('dropdown', cloak && 'invisible')} ref={el => el && setItemWidth(el.offsetWidth)}>
{Object.keys(children).map(key => (
<div
class="option"

@ -1,5 +1,5 @@
import { useEffect, useState } from 'preact/hooks'
import { FunnelIcon } from '@phosphor-icons/react'
import { Funnel } from '@phosphor-icons/react'
import { marked } from 'marked'
import extendedLatex from '@/client/lib/marked-latex'
@ -70,7 +70,7 @@ export const DomandeEsamiCourse = ({ course }: Props) => {
{courseTags.length > 1 && (
<div class="card filter">
<div class="grid-h">
<FunnelIcon />
<Funnel />
<strong>Filtra Tag</strong>
</div>
<div class="flex-row-wrap">

@ -1,765 +0,0 @@
import { useState, useEffect } from 'preact/hooks'
// Tipi per la gestione dei dati
type TipoStudente = 'triennale' | 'magistrale'
interface Corso {
nome: string
anno: '1' | '2' | '3' | 'M' | 'istituzioni'
cfu: number
passFailOnly?: boolean // Per materie senza voto (pass/fail)
}
interface CorsoSelezionato {
id: string
nome: string
cfu: number
voto: number | null
lode: boolean
passFailOnly?: boolean // Per materie senza voto (pass/fail)
}
interface CorsoCustom {
nome: string
cfu: number
}
// Dati dei corsi aggiornati dalla tabella ufficiale
const CORSI_DISPONIBILI: Corso[] = [
// Primo Anno
{ nome: 'Analisi matematica 1', anno: '1', cfu: 15 },
{ nome: 'Aritmetica', anno: '1', cfu: 9 },
{ nome: 'Fisica I con laboratorio', anno: '1', cfu: 9 },
{ nome: 'Fondamenti di programmazione con laboratorio', anno: '1', cfu: 9 },
{ nome: 'Geometria 1', anno: '1', cfu: 15 },
{ nome: 'Laboratorio di introduzione alla matematica computazionale', anno: '1', cfu: 6, passFailOnly: true },
{ nome: 'Laboratorio di comunicazione mediante calcolatore', anno: '1', cfu: 3, passFailOnly: true },
// Secondo Anno
{ nome: 'Algebra 1', anno: '2', cfu: 6 },
{ nome: 'Algoritmi e strutture dati', anno: '2', cfu: 6 },
{ nome: 'Analisi matematica 2', anno: '2', cfu: 12 },
{ nome: 'Analisi numerica con laboratorio', anno: '2', cfu: 9 },
{ nome: 'Elementi di probabilità e statistica', anno: '2', cfu: 6 },
{ nome: 'Geometria 2', anno: '2', cfu: 12 },
{ nome: 'Inglese scientifico', anno: '2', cfu: 6, passFailOnly: true },
{ nome: 'Laboratorio didattico di matematica computazionale', anno: '2', cfu: 3, passFailOnly: true },
// Terzo Anno
{ nome: 'Algebra 2', anno: '3', cfu: 6 },
{ nome: 'Analisi matematica 3', anno: '3', cfu: 6 },
{ nome: 'Analisi reale', anno: '3', cfu: 6 },
{ nome: 'Calcolo scientifico', anno: '3', cfu: 6 },
{ nome: 'Elementi di analisi complessa', anno: '3', cfu: 6 },
{ nome: 'Elementi di calcolo delle variazioni', anno: '3', cfu: 6 },
{ nome: 'Elementi di geometria algebrica', anno: '3', cfu: 6 },
{ nome: 'Elementi di meccanica celeste', anno: '3', cfu: 6 },
{ nome: 'Elementi di teoria degli insiemi', anno: '3', cfu: 6 },
{ nome: 'Elementi di teoria delle rappresentazioni', anno: '3', cfu: 6 },
{ nome: 'Elementi di topologia algebrica', anno: '3', cfu: 6 },
{ nome: 'Equazioni alle derivate parziali', anno: '3', cfu: 6 },
{ nome: 'Fisica II', anno: '3', cfu: 9 },
{ nome: 'Fisica III', anno: '3', cfu: 6 },
{ nome: 'Geometria e topologia differenziale', anno: '3', cfu: 6 },
{ nome: 'Gruppi e rappresentazioni', anno: '3', cfu: 6 },
{ nome: 'Laboratorio computazionale', anno: '3', cfu: 6, passFailOnly: true },
{ nome: 'Laboratorio sperimentale di matematica computazionale', anno: '3', cfu: 6, passFailOnly: true },
{ nome: 'Linguaggi di programmazione con laboratorio', anno: '3', cfu: 9 },
{ nome: 'Logica matematica', anno: '3', cfu: 6 },
{ nome: 'Matematiche elementari da un punto di vista superiore: aritmetica', anno: '3', cfu: 6 },
{ nome: 'Matematiche elementari da un punto di vista superiore: geometria', anno: '3', cfu: 6 },
{ nome: 'Meccanica razionale', anno: '3', cfu: 6 },
{ nome: 'Metodi numerici per equazioni differenziali ordinarie', anno: '3', cfu: 6 },
{ nome: 'Metodi topologici in analisi globale', anno: '3', cfu: 6 },
{ nome: 'Ottimizzazione non lineare', anno: '3', cfu: 6 },
{ nome: 'Probabilità', anno: '3', cfu: 6 },
{ nome: 'Ricerca operativa', anno: '3', cfu: 6 },
{ nome: 'Sistemi dinamici', anno: '3', cfu: 6 },
{ nome: 'Spazi di Sobolev', anno: '3', cfu: 6 },
{ nome: 'Statistica matematica', anno: '3', cfu: 6 },
{ nome: 'Storia della matematica', anno: '3', cfu: 6 },
{ nome: 'Teoria algebrica dei numeri 1', anno: '3', cfu: 6 },
{ nome: 'Teoria dei campi e teoria di Galois', anno: '3', cfu: 6 },
{ nome: 'Teoria dei numeri elementare', anno: '3', cfu: 6 },
{ nome: 'Teoria della misura', anno: '3', cfu: 6 },
// Istituzioni (Magistrale)
{ nome: 'Istituzioni di algebra', anno: 'istituzioni', cfu: 11 },
{ nome: 'Istituzioni di analisi matematica', anno: 'istituzioni', cfu: 11 },
{ nome: 'Istituzioni di analisi numerica', anno: 'istituzioni', cfu: 11 },
{ nome: 'Istituzioni di didattica della matematica', anno: 'istituzioni', cfu: 11 },
{ nome: 'Istituzioni di fisica matematica', anno: 'istituzioni', cfu: 11 },
{ nome: 'Istituzioni di geometria', anno: 'istituzioni', cfu: 11 },
{ nome: 'Istituzioni di probabilità', anno: 'istituzioni', cfu: 11 },
// Materie a scelta (Magistrale)
{ nome: '4-varietà', anno: 'M', cfu: 6 },
{ nome: 'Algebra superiore A', anno: 'M', cfu: 6 },
{ nome: 'Algebre e gruppi di Lie', anno: 'M', cfu: 6 },
{ nome: 'Analisi armonica', anno: 'M', cfu: 6 },
{ nome: 'Analisi complessa A', anno: 'M', cfu: 6 },
{ nome: 'Analisi complessa B', anno: 'M', cfu: 6 },
{ nome: 'Analisi convessa', anno: 'M', cfu: 6 },
{ nome: 'Analisi dei dati', anno: 'M', cfu: 6 },
{ nome: 'Analisi non standard', anno: 'M', cfu: 6 },
{ nome: 'Analisi reale', anno: 'M', cfu: 6 },
{ nome: 'Analisi su spazi gaussiani', anno: 'M', cfu: 6 },
{ nome: 'Analisi superiore', anno: 'M', cfu: 6 },
{ nome: 'Analisi superiore A', anno: 'M', cfu: 6 },
{ nome: 'Analisi superiore B', anno: 'M', cfu: 6 },
{ nome: 'Aspetti matematici nella computazione quantistica', anno: 'M', cfu: 6 },
{ nome: 'Calcolo delle variazioni B', anno: 'M', cfu: 6 },
{ nome: 'Calcolo della variazioni A', anno: 'M', cfu: 6 }, // Variante nome
{ nome: 'Combinatoria algebrica', anno: 'M', cfu: 6 },
{ nome: 'Complementi di analisi funzionale', anno: 'M', cfu: 6 },
{ nome: 'Complementi di meccanica razionale', anno: 'M', cfu: 6 },
{ nome: 'Crittografia post-quantistica', anno: 'M', cfu: 6 },
{ nome: 'Curve ellittiche', anno: 'M', cfu: 6 },
{ nome: 'Determinazione orbitale', anno: 'M', cfu: 6 },
{ nome: 'Didattica della matematica e nuove tecnologie', anno: 'M', cfu: 6 },
{ nome: 'Dinamica del sistema solare', anno: 'M', cfu: 6 },
{ nome: 'Dinamica iperbolica', anno: 'M', cfu: 6 },
{ nome: 'Dinamica olomorfa', anno: 'M', cfu: 6 },
{ nome: 'Elementi di calcolo in gruppi omogenei', anno: 'M', cfu: 6 },
{ nome: 'Equazioni della fluidodinamica', anno: 'M', cfu: 6 },
{ nome: 'Equazioni differenziali stocastiche e applicazioni', anno: 'M', cfu: 6 },
{ nome: 'Equazioni ellittiche', anno: 'M', cfu: 6 },
{ nome: 'Finanza matematica', anno: 'M', cfu: 6 },
{ nome: 'Fisica matematica', anno: 'M', cfu: 6 },
{ nome: 'Forme modulari', anno: 'M', cfu: 6 },
{ nome: 'Geometria algebrica B', anno: 'M', cfu: 6 },
{ nome: 'Geometria algebrica C', anno: 'M', cfu: 6 },
{ nome: 'Geometria algebrica complessa', anno: 'M', cfu: 6 },
{ nome: 'Geometria algebrica D', anno: 'M', cfu: 6 },
{ nome: 'Geometria algebrica E', anno: 'M', cfu: 6 },
{ nome: 'Geometria algebrica F', anno: 'M', cfu: 6 },
{ nome: 'Geometria algebrica G', anno: 'M', cfu: 6 },
{ nome: 'Geometria e analisi complessa', anno: 'M', cfu: 6 },
{ nome: 'Geometria differenziale complessa', anno: 'M', cfu: 6 },
{ nome: 'Geometria iperbolica', anno: 'M', cfu: 6 },
{ nome: 'Geometria riemanniana', anno: 'M', cfu: 6 },
{ nome: 'Gruppi algebrici lineari', anno: 'M', cfu: 6 },
{ nome: 'Gruppi di Coxeter', anno: 'M', cfu: 6 },
{ nome: 'Gruppi di Galois e gruppi fondamentali', anno: 'M', cfu: 6 },
{ nome: 'Meccanica celeste', anno: 'M', cfu: 6 },
{ nome: 'Meccanica spaziale', anno: 'M', cfu: 6 },
{ nome: 'Meccanica superiore', anno: 'M', cfu: 6 },
{ nome: 'Metodi di analisi armonica in analisi non lineare', anno: 'M', cfu: 6 },
{ nome: 'Metodi di approssimazione', anno: 'M', cfu: 6 },
{ nome: 'Metodi matematici della crittografia', anno: 'M', cfu: 6 },
{ nome: 'Metodi matematici della meccanica quantistica', anno: 'M', cfu: 6 },
{ nome: 'Metodi numerici per catene di Markov', anno: 'M', cfu: 6 },
{ nome: 'Metodi numerici per equazioni alle derivate parziali', anno: 'M', cfu: 6 },
{ nome: 'Metodi numerici per il calcolo tensoriale', anno: 'M', cfu: 6 },
{ nome: 'Metodi numerici per il controllo ottimo', anno: 'M', cfu: 6 },
{ nome: 'Metodi numerici per la grafica', anno: 'M', cfu: 6 },
{ nome: 'Metodi numerici per problemi inversi', anno: 'M', cfu: 6 },
{ nome: "Metodi probabilistici per l'algebra lineare numerica", anno: 'M', cfu: 6 },
{ nome: 'Modelli matematici in biomedicina e fisica matematica', anno: 'M', cfu: 6 },
{ nome: 'Origini e sviluppo delle matematiche moderne', anno: 'M', cfu: 6 },
{ nome: 'Probabilità superiore', anno: 'M', cfu: 6 },
{ nome: 'Problemi e metodi della ricerca in didattica della matematica', anno: 'M', cfu: 6 },
{ nome: 'Problemi e metodi in storia della matematica', anno: 'M', cfu: 6 },
{ nome: 'Sistemi dinamici aleatori', anno: 'M', cfu: 6 },
{ nome: 'Statistica superiore', anno: 'M', cfu: 6 },
{ nome: 'Storia della matematica antica e della sua tradizione', anno: 'M', cfu: 6 },
{ nome: 'Superfici di Riemann e curve algebriche', anno: 'M', cfu: 6 },
{ nome: 'Tecnologie per la didattica', anno: 'M', cfu: 6 },
{ nome: 'Teoria algebrica dei numeri 2', anno: 'M', cfu: 6 },
{ nome: 'Teoria analitica dei numeri A', anno: 'M', cfu: 6 },
{ nome: 'Teoria dei giochi', anno: 'M', cfu: 6 },
{ nome: 'Teoria dei modelli', anno: 'M', cfu: 6 },
{ nome: 'Teoria dei nodi A', anno: 'M', cfu: 6 },
{ nome: 'Teoria degli insiemi', anno: 'M', cfu: 6 },
{ nome: 'Teoria degli insiemi A', anno: 'M', cfu: 6 },
{ nome: 'Teoria degli insiemi B', anno: 'M', cfu: 6 },
{ nome: 'Teoria delle categorie', anno: 'M', cfu: 6 },
{ nome: 'Teoria delle rappresentazioni A', anno: 'M', cfu: 6 },
{ nome: "Teoria e metodi dell'ottimizzazione", anno: 'M', cfu: 6 },
{ nome: 'Teoria ergodica', anno: 'M', cfu: 6 },
{ nome: 'Teoria geometrica della misura', anno: 'M', cfu: 6 },
{ nome: 'Topologia algebrica', anno: 'M', cfu: 6 },
{ nome: 'Topologia algebrica A', anno: 'M', cfu: 6 },
{ nome: 'Topologia algebrica B', anno: 'M', cfu: 6 },
{ nome: 'Topologia differenziale', anno: 'M', cfu: 6 },
{ nome: 'Topologia e geometria in bassa dimensione', anno: 'M', cfu: 6 },
{ nome: 'Ultrafiltri e metodi non-standard', anno: 'M', cfu: 6 },
]
export function MediaPesataApp() {
// Funzioni per localStorage
const loadFromStorage = () => {
try {
const savedData = localStorage.getItem('media-pesata-data')
if (savedData) {
const parsed = JSON.parse(savedData)
return {
tipoStudente: parsed.tipoStudente || 'triennale',
corsiSelezionati: parsed.corsiSelezionati || [],
sezioniAperte: parsed.sezioniAperte || {},
mostraRisultati: parsed.mostraRisultati || false,
}
}
} catch (error) {
console.warn('Errore nel caricamento dei dati salvati:', error)
}
return {
tipoStudente: 'triennale' as TipoStudente,
corsiSelezionati: [],
sezioniAperte: {},
mostraRisultati: false,
}
}
const saveToStorage = (data: any) => {
try {
localStorage.setItem('media-pesata-data', JSON.stringify(data))
} catch (error) {
console.warn('Errore nel salvataggio dei dati:', error)
}
}
// Inizializzazione con dati salvati
// const initialData = loadFromStorage()
const [tipoStudente, setTipoStudente] = useState<TipoStudente>('triennale')
const [corsiSelezionati, setCorsiSelezionati] = useState<CorsoSelezionato[]>([])
const [showCustomForm, setShowCustomForm] = useState(false)
const [customCorso, setCustomCorso] = useState<CorsoCustom>({ nome: '', cfu: 0 })
const [sezioniAperte, setSezioniAperte] = useState<Record<string, boolean>>({})
const [mostraRisultati, setMostraRisultati] = useState(false)
// Load data from localStorage on mount
useEffect(() => {
const initialData = loadFromStorage()
setTipoStudente(initialData.tipoStudente)
setCorsiSelezionati(initialData.corsiSelezionati)
setSezioniAperte(initialData.sezioniAperte)
setMostraRisultati(initialData.mostraRisultati)
}, [])
// Salva automaticamente quando cambiano i dati importanti
useEffect(() => {
const dataToSave = {
tipoStudente,
corsiSelezionati,
sezioniAperte,
mostraRisultati,
}
saveToStorage(dataToSave)
}, [tipoStudente, corsiSelezionati, sezioniAperte, mostraRisultati])
const toggleSezione = (nomeSezione: string) => {
setSezioniAperte(prev => ({
...prev,
[nomeSezione]: !prev[nomeSezione],
}))
}
const calcolaMedia = () => {
const corsiConVoto = corsiSelezionati.filter(corso => corso.voto !== null && !corso.passFailOnly)
if (corsiConVoto.length === 0) {
alert('Inserisci almeno un voto per calcolare la media!')
return
}
setMostraRisultati(true)
}
// Funzioni per la gestione dei corsi
const aggiungiCorso = (corso: Corso) => {
// Controlla se la materia esiste già
const materiaEsiste = corsiSelezionati.some(c => c.nome.toLowerCase() === corso.nome.toLowerCase())
if (materiaEsiste) {
alert('Questa materia è già stata aggiunta')
return
}
// Controllo per magistrali: massimo 3 istituzioni
if (tipoStudente === 'magistrale' && corso.anno === 'istituzioni') {
const istituzioniAttuali = corsiSelezionati.filter(c => c.nome.toLowerCase().includes('istituzioni')).length
if (istituzioniAttuali >= 3) {
alert('Puoi selezionare al massimo 3 istituzioni per la magistrale')
return
}
}
const id = Date.now().toString()
const nuovoCorso: CorsoSelezionato = {
id,
nome: corso.nome,
cfu: corso.cfu,
voto: null,
lode: false,
passFailOnly: corso.passFailOnly,
}
setCorsiSelezionati([...corsiSelezionati, nuovoCorso])
}
const aggiungiCorsoCustom = () => {
// Validazioni
if (!customCorso.nome.trim()) {
alert('Il nome della materia non può essere vuoto')
return
}
if (customCorso.cfu <= 0 || customCorso.cfu > 30) {
alert('I CFU devono essere tra 1 e 30')
return
}
if (!Number.isInteger(customCorso.cfu)) {
alert('I CFU devono essere un numero intero')
return
}
// Controlla se la materia esiste già
const materiaEsiste = corsiSelezionati.some(
corso => corso.nome.toLowerCase().trim() === customCorso.nome.toLowerCase().trim(),
)
if (materiaEsiste) {
alert('Questa materia è già stata aggiunta')
return
}
const id = Date.now().toString()
const nuovoCorso: CorsoSelezionato = {
id,
nome: customCorso.nome.trim(),
cfu: customCorso.cfu,
voto: null,
lode: false,
}
setCorsiSelezionati([...corsiSelezionati, nuovoCorso])
setCustomCorso({ nome: '', cfu: 0 })
setShowCustomForm(false)
}
const rimuoviCorso = (id: string) => {
setCorsiSelezionati(corsiSelezionati.filter(corso => corso.id !== id))
}
const aggiornaVoto = (id: string, voto: number | null) => {
// Validazione voto: deve essere tra 18 e 30 e intero
if (voto !== null && (voto < 18 || voto > 30 || !Number.isInteger(voto))) {
return // Ignora valori non validi
}
setCorsiSelezionati(
corsiSelezionati.map(corso =>
corso.id === id ? { ...corso, voto, lode: voto !== 30 ? false : corso.lode } : corso,
),
)
}
const aggiornaLode = (id: string, lode: boolean) => {
setCorsiSelezionati(corsiSelezionati.map(corso => (corso.id === id ? { ...corso, lode } : corso)))
}
const resetTutto = () => {
if (corsiSelezionati.length > 0) {
if (confirm('Sei sicuro di voler cancellare tutte le materie selezionate?')) {
setCorsiSelezionati([])
setCustomCorso({ nome: '', cfu: 0 })
setShowCustomForm(false)
setSezioniAperte({})
setMostraRisultati(false)
// Pulisce anche il localStorage
try {
localStorage.removeItem('media-pesata-data')
} catch (error) {
console.warn('Errore nella pulizia del localStorage:', error)
}
}
}
}
// Calcoli della media pesata
const calcolaMediaPesata = () => {
// Escludi le materie pass/fail dal calcolo
const corsiConVoto = corsiSelezionati.filter(corso => corso.voto !== null && !corso.passFailOnly)
if (corsiConVoto.length === 0) {
return {
mediaPesata: 0,
votoAmmissione: 0,
massimoVotoLaurea: 0,
conLode: false,
bonusLodi: 0,
errore: 'Nessun voto inserito per il calcolo della media',
}
}
// Calcola CFU totali con voto
const cfuTotaliConVoto = corsiConVoto.reduce((sum, corso) => sum + corso.cfu, 0)
const cfuDaEscludere = tipoStudente === 'triennale' ? 15 : 9
// Se i CFU sono insufficienti per l'esclusione, avvisa l'utente
if (cfuTotaliConVoto < cfuDaEscludere) {
return {
mediaPesata: 0,
votoAmmissione: 0,
massimoVotoLaurea: 0,
conLode: false,
bonusLodi: 0,
errore: `Hai inserito solo ${cfuTotaliConVoto} CFU con voto. Servono almeno ${cfuDaEscludere} CFU per applicare le regole di esclusione.`,
}
}
// Ordina per voto crescente
const corsiOrdinati = [...corsiConVoto].sort((a, b) => a.voto! - b.voto!)
let cfuEsclusi = 0
const corsiValidi: CorsoSelezionato[] = []
for (const corso of corsiOrdinati) {
if (cfuEsclusi < cfuDaEscludere) {
const cfuRimanentiDaEscludere = cfuDaEscludere - cfuEsclusi
if (corso.cfu <= cfuRimanentiDaEscludere) {
// Escludi tutto il corso
cfuEsclusi += corso.cfu
} else {
// Escludi solo una parte del corso
const cfuValidi = corso.cfu - cfuRimanentiDaEscludere
corsiValidi.push({ ...corso, cfu: cfuValidi })
cfuEsclusi = cfuDaEscludere
}
} else {
corsiValidi.push(corso)
}
}
// Calcola media pesata
const sommaPesata = corsiValidi.reduce((sum, corso) => sum + corso.voto! * corso.cfu, 0)
const sommaCfu = corsiValidi.reduce((sum, corso) => sum + corso.cfu, 0)
const mediaPesata = sommaCfu > 0 ? sommaPesata / sommaCfu : 0
// Calcola bonus lodi
const bonusLodi = corsiConVoto.reduce((bonus, corso) => {
if (corso.lode) {
return bonus + (corso.cfu > 6 ? 0.5 : 0.25)
}
return bonus
}, 0)
// Cap del bonus lodi basato sul tipo di studente
const capBonusLodi = tipoStudente === 'triennale' ? 1.5 : 2
const bonusLodiFinal = Math.min(bonusLodi, capBonusLodi)
// Voto di ammissione alla laurea (media pesata * 11/3)
const votoAmmissione = (mediaPesata * 11) / 3
// Voto di ammissione finale = voto ammissione + bonus lodi
const votoAmmissioneFinale = votoAmmissione + bonusLodiFinal
// Massimo voto di laurea possibile = voto ammissione + 10 (cappato a 110)
const massimoVotoLaurea = Math.min(votoAmmissioneFinale + 10, 110)
const conLode = massimoVotoLaurea === 110
return {
mediaPesata: Math.round(mediaPesata * 100) / 100,
votoAmmissione: Math.round(votoAmmissioneFinale * 100) / 100,
massimoVotoLaurea: Math.round(massimoVotoLaurea * 100) / 100,
conLode,
bonusLodi: Math.round(bonusLodiFinal * 100) / 100,
errore: null,
}
}
// Filtra corsi disponibili in base al tipo di studente
const getCorsiDisponibili = () => {
if (tipoStudente === 'triennale') {
return CORSI_DISPONIBILI.filter(corso => corso.anno !== 'istituzioni')
} else {
return CORSI_DISPONIBILI.filter(
corso => corso.anno === 'istituzioni' || corso.anno === '3' || corso.anno === 'M',
)
}
}
// Raggruppa corsi per categoria
const raggruppaCorsi = () => {
const corsi = getCorsiDisponibili()
const gruppi: Record<string, Corso[]> = {}
if (tipoStudente === 'triennale') {
gruppi['Primo Anno'] = corsi.filter(c => c.anno === '1')
gruppi['Secondo Anno'] = corsi.filter(c => c.anno === '2')
gruppi['Terzo Anno'] = corsi.filter(c => c.anno === '3')
gruppi['Materie a Scelta'] = corsi.filter(c => c.anno === 'M')
} else {
// Per magistrali: prima le istituzioni, poi tutto il resto come "Materie a Scelta"
gruppi['Istituzioni'] = corsi.filter(c => c.anno === 'istituzioni')
gruppi['Materie a Scelta'] = corsi.filter(c => c.anno === '3' || c.anno === 'M')
}
return gruppi
}
const cambiaTipoStudente = (nuovoTipo: TipoStudente) => {
setTipoStudente(nuovoTipo)
}
const gruppiCorsi = raggruppaCorsi()
const risultati = calcolaMediaPesata()
const totaleCfu = corsiSelezionati.reduce((sum, corso) => sum + corso.cfu, 0)
const maxCfu = tipoStudente === 'triennale' ? 171 : 93 // 180 9 e 120 27 per la tesi
const cfuError = totaleCfu > maxCfu
return (
<div class="media-pesata-app">
{/* Selezione tipo studente */}
<div class="card student-type-switcher wide">
<div class="grid-center">
<h2>Corso di Laurea</h2>
<div class="compound-button">
<button
class={tipoStudente === 'triennale' ? 'active' : ''}
onClick={() => cambiaTipoStudente('triennale')}
>
Triennale
</button>
<button
class={tipoStudente === 'magistrale' ? 'active' : ''}
onClick={() => cambiaTipoStudente('magistrale')}
>
Magistrale
</button>
</div>
</div>
</div>
{/* Counter CFU */}
<div class={`cfu-counter wide ${cfuError ? 'error' : ''}`}>
<h3>
CFU Totali: {totaleCfu}/{maxCfu}
{tipoStudente === 'triennale' ? ' (+9 tesi)' : ' (+27 tesi)'}
</h3>
{cfuError && <p class="error-text"> Hai superato il limite di CFU consentiti!</p>}
</div>
{/* Sezione selezione corsi */}
<div class="card">
<div class="title">
<h2>Seleziona Materie</h2>
</div>
{Object.entries(gruppiCorsi).map(([categoria, corsi]) => (
<div key={categoria} class="course-category">
<button onClick={() => toggleSezione(categoria)}>
<div class="h-flex">
{categoria}
<div class="spacer"></div>
<span class={`toggle-icon ${sezioniAperte[categoria] ? 'expanded' : ''}`}></span>
</div>
</button>
{sezioniAperte[categoria] && (
<div class="course-grid">
{corsi.map((corso, index) => (
<button
key={index}
class="course-button"
onClick={() => aggiungiCorso(corso)}
disabled={corsiSelezionati.some(c => c.nome === corso.nome)}
>
<span class="course-name">{corso.nome}</span>
<span class="course-cfu">{corso.cfu} CFU</span>
</button>
))}
</div>
)}
</div>
))}
{/* Form per materia custom */}
<div class="custom-course">
<h3>Materia Personalizzata</h3>
{!showCustomForm ? (
<button onClick={() => setShowCustomForm(true)}>+ Aggiungi Materia Personalizzata</button>
) : (
<div class="custom-form">
<input
type="text"
placeholder="Nome materia"
value={customCorso.nome}
onChange={e =>
setCustomCorso({ ...customCorso, nome: (e.target as HTMLInputElement).value })
}
/>
<input
type="number"
placeholder="CFU"
min="1"
max="30"
step="1"
value={customCorso.cfu || ''}
onChange={e =>
setCustomCorso({
...customCorso,
cfu: parseInt((e.target as HTMLInputElement).value) || 0,
})
}
/>
<button onClick={aggiungiCorsoCustom}>Aggiungi</button>
<button onClick={() => setShowCustomForm(false)}>Annulla</button>
</div>
)}
</div>
</div>
{/* Sezione lista corsi selezionati */}
<div class="card">
<div class="h-flex">
<div class="title">
<h2>Materie Selezionate</h2>
</div>
<div class="spacer"></div>
{corsiSelezionati.length > 0 && <button onClick={resetTutto}>🗑 Cancella Tutto</button>}
</div>
{corsiSelezionati.length === 0 ? (
<p>Nessuna materia selezionata</p>
) : (
<div class="courses-list">
{corsiSelezionati.map(corso => (
<div key={corso.id} class="course-item">
<span class="course-name">{corso.nome}</span>
<span class="course-cfu">{corso.cfu} CFU</span>
{!corso.passFailOnly && (
<div class="course-grade tall">
<input
type="number"
placeholder="Voto"
min="18"
max="30"
step="1"
value={corso.voto || ''}
onChange={e =>
aggiornaVoto(
corso.id,
parseInt((e.target as HTMLInputElement).value) || null,
)
}
/>
<label class={`lode-checkbox ${corso.voto !== 30 ? 'disabled' : ''}`}>
<input
class="star"
type="checkbox"
checked={corso.lode}
disabled={corso.voto !== 30}
onChange={e =>
aggiornaLode(corso.id, (e.target as HTMLInputElement).checked)
}
/>
Lode
</label>
</div>
)}
<div class="actions tall">
<button
class="icon remove-btn"
onClick={() => rimuoviCorso(corso.id)}
title="Rimuovi materia"
>
×
</button>
</div>
</div>
))}
</div>
)}
</div>
{/* Pulsante Calcola */}
{corsiSelezionati.length > 0 && (
<div class="calculate-section wide">
<button onClick={calcolaMedia}>🧮 Calcola Media e Voto di Laurea</button>
</div>
)}
{/* Risultati */}
{risultati && mostraRisultati && (
<div class="results wide">
<h2>Risultati</h2>
{risultati.errore ? (
<div class="error-message">
<p> {risultati.errore}</p>
</div>
) : (
<div class="results-grid">
<div class="result-item">
<span class="label">Media Pesata:</span>
<span class="value">{risultati.mediaPesata}</span>
</div>
<div class="result-item">
<span class="label">Bonus Lodi:</span>
<span class="value">+{risultati.bonusLodi}</span>
</div>
<div class="result-item highlight">
<span class="label">Voto di Ammissione:</span>
<span class="value">{risultati.votoAmmissione}</span>
</div>
<div class="result-item highlight">
<span class="label">Massimo Voto Di Laurea Possibile:</span>
<span class="value">
{risultati.massimoVotoLaurea}
{risultati.conLode && <span class="lode-badge">(+lode)</span>}
</span>
</div>
</div>
)}
</div>
)}
{/* Nota informativa */}
<div class="card wide">
<h3>📋 Come viene calcolata la media</h3>
<div class="info-content">
<h4>Regole di esclusione CFU:</h4>
<ul>
<li>
<strong>Triennale:</strong> I 15 CFU con i voti più bassi vengono esclusi dalla media
</li>
<li>
<strong>Magistrale:</strong> I 9 CFU con i voti più bassi vengono esclusi dalla media
</li>
<li>Se un corso ha più CFU di quelli da escludere, viene diviso proporzionalmente</li>
</ul>
<h4>Calcolo del voto finale:</h4>
<ul>
<li>
<strong>Media pesata:</strong> Somma dei (voto × CFU) diviso per i CFU totali
</li>
<li>
<strong>Bonus lodi:</strong> +0.5 per lodi in materie &gt; 6 CFU, +0.25 per lodi in materie
6 CFU (max +1.5 per triennale, max +2 per magistrale)
</li>
<li>
<strong>Voto di laurea:</strong> (Voto finale × 11) ÷ 3
</li>
</ul>
<h4>Note:</h4>
<ul>
<li>
Le materie <strong>Pass/Fail</strong> non contribuiscono al calcolo della media
</li>
<li>Il voto finale è limitato a 30</li>
<li>Per i magistrali: massimo 3 istituzioni selezionabili</li>
</ul>
</div>
</div>
</div>
)
}
// Funzione per inizializzare l'app
// export function initMediaPesataApp() {
// const container = document.getElementById('media-pesata-app')
// if (container) {
// render(<MediaPesataApp />, container)
// }
// }
// export default MediaPesataApp

@ -9,7 +9,7 @@ const extBlock = options => ({
start(src) {
return src.match(/\$\$[^\$]/)?.index ?? -1
},
tokenizer(src, _tokens) {
tokenizer(src, tokens) {
const match = /^\$\$([^\$]+)\$\$/.exec(src)
return match ? { type: 'latex-block', raw: match[0], formula: match[1] } : undefined
},
@ -25,7 +25,7 @@ const extInline = options => ({
start(src) {
return src.match(/\$[^\$]/)?.index ?? -1
},
tokenizer(src, _tokens) {
tokenizer(src, tokens) {
const match = /^\$([^\$]+)\$/.exec(src)
return match ? { type: 'latex', raw: match[0], formula: match[1] } : undefined
},

@ -6,7 +6,6 @@ const links = [
{ href: '/notizie', text: 'Notizie' },
{ href: '/guide', text: 'Guide' },
{ href: '/domande-esami', text: 'Domande Orali' },
{ href: '/media-pesata', text: 'Calcolo Media' }, // Beta testing - solo URL diretto
{ href: '/storia', text: 'Storia' },
// { href: '/login', text: 'Login' },
]

@ -17,7 +17,7 @@ const { href, imgSrc, style, title } = Astro.props
{imgSrc ? <img src={imgSrc} alt={'logo for ' + title.toLowerCase()} /> : <div class="box" />}
</div>
<div class="title">{title}</div>
<div class="description text">
<div class="description">
<slot />
</div>
</div>

@ -32,17 +32,17 @@ const guidesCollection = defineCollection({
})
// Per ora sono su un sito a parte ma prima o poi verranno migrati qui
// const seminariettiCollection = defineCollection({
// type: 'content',
// schema: z.object({
// title: z.string(),
// description: z.string(),
// author: z.string(),
// publishDate: z.date(),
// eventDate: z.date(),
// tags: z.array(z.string()),
// }),
// })
const seminariettiCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
author: z.string(),
publishDate: z.date(),
eventDate: z.date(),
tags: z.array(z.string()),
}),
})
const metaCollection = defineCollection({
type: 'content',
@ -53,6 +53,6 @@ const metaCollection = defineCollection({
export const collections = {
news: newsCollection,
guides: guidesCollection,
// seminarietti: seminariettiCollection,
seminarietti: seminariettiCollection,
meta: metaCollection,
}

@ -1,80 +0,0 @@
---
id: stampare-via-ssh
title: Stampare via SSH
description: Istruzioni per stampare in dipartimento da remoto, tramite SSH 🖨
author: Antonio De Lucreziis, Francesco Minnocci
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 per prima cosa bisogna poter accedere da remoto ad una macchina chiamata "login", il cui 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`: 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` è un esempio di 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... -
```
Qui, `[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
```
> Attenzione, il comando sopra cancella tutta la propria coda di stampa, non solo l'ultimo lavoro inviato.
Alternativamente possiamo vedere lo stato della coda di stampa con il comando
```bash shell
lpq -a
```
e cancellare un lavoro con uno specifico ID con
```bash shell
cancel ID
```

@ -52,7 +52,7 @@ Le card possono essere di dimensioni diverse. Questa è una card grande.
</div>
```
### ~~Low Level: Mixin SCSS~~ Old CSS Mixin
### Low Level: Mixin SCSS
Non dovrebbe essere mai necessario usarlo direttamente ma l'effetto di ombra delle card è ottenuto con questo mixin SCSS (che si trova in `src/styles/mixins.scss`).

@ -1,22 +0,0 @@
---
title: Calcola la tua media ed il voto di laurea con il nuovissimo calcolatore del PHC!
description: È ora disponibile uno strumento per calcolare la propria media pesata e il voto di ammissione alla laurea secondo le regole del dipartimento.
publishDate: 2025-06-26
---
# Calcola la tua media ed il voto di laurea con il nuovissimo calcolatore del PHC!
È ora disponibile nella sezione "Calcolo Media" del sito uno strumento per calcolare la propria media pesata e il voto di ammissione alla laurea secondo le regole ufficiali del dipartimento.
<p align="center">
<a href="https://phc.dm.unipi.it/media-pesata/">phc.dm.unipi.it/media-pesata/</a>
</p>
Il calcolatore applica automaticamente le regole di esclusione previste dal regolamento:
- **Triennale**: vengono esclusi i 15 CFU con i voti più bassi
- **Magistrale**: vengono esclusi i 9 CFU con i voti più bassi
Il sistema calcola anche il bonus per le lodi, che vale +0.5 punti per ogni materia superiore a 6 CFU e +0.25 punti per materie da 6 CFU o meno, con un tetto massimo di +1.5 punti per la triennale e +2 punti per la magistrale.
Se dovessero esserci bug scriveteci un'email a <a href="mailto:macchinisti@lists.dm.unipi.it">macchinisti@lists.dm.unipi.it</a>!

@ -1,46 +0,0 @@
---
title: Esplora i meme dell'aula studenti online!
description: |
Gli storici meme sono stati staccati per i lavori, ma non disperare: li potrai vedere su una nuova pagina.
publishDate: 2025-06-26
---
# Esplora i meme dell'aula studenti online!
Visti gli imminenti lavori che occuperanno l'aula studenti, ad inizio Settembre tutti i meme sulle pareti sono stati staccati e riposti temporaneamente in PHC; qui sotto trovate alcuni timelapse della giornata:
<div class="grid-h-split">
<video controls>
<source src="https://static.phc.dm.unipi.it/timelapse-nord.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
<video controls>
<source src="https://static.phc.dm.unipi.it/timelapse-sud.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
<video controls>
<source src="https://static.phc.dm.unipi.it/timelapse-termosifone.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
Per questo motivo, li abbiamo scansionati ed abbiamo creato una bacheca per poter contemplare i meme dovunque voi siate 🧳
Eccovi dunque il link alla pagina, buon divertimento:
![Screenshot Bacheca](/images/misc/screenshot-bacheca.png)
<p align="center">
<a href="https://meme.phc.dm.unipi.it">meme.phc.dm.unipi.it</a>
</p>
## Coming Soon
Prima o poi faremo anche una mappa interattiva della stanza, basata sul seguente modello 3D ricostruito con tecniche di fotogrammetria:
<video controls>
<source src="https://static.phc.dm.unipi.it/3d-scan-preview.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
> Disclaimer: Se volessi rimuovere una tua immagine da questa pagina, scrivici pure a <a href="mailto:macchinisti@lists.dm.unipi.it">macchinisti@lists.dm.unipi.it</a> e ce ne occuperemo.

@ -1,34 +0,0 @@
---
title: Non avete attivato la 2FA entro il primo dicembre? Ecco come recuperare l'accesso
description: |
Se non avete attivato l'autenticazione a due fattori entro il primo dicembre, siete bloccati fuori dall'account Unipi. La procedura di recupero passa dal supporto tecnico.
publishDate: 2025-12-05
---
# Non avete attivato la 2FA entro il primo dicembre? Ecco come recuperare l'accesso
Se siamo rimasti fuori dal nostro account Unipi perché non abbiamo attivato l'autenticazione a due fattori entro il primo dicembre, il percorso di recupero è piuttosto lineare.
Il primo passo è mandare una mail a
> <a href="mailto:help.polo2@ticket.unipi.it">help.polo2@ticket.unipi.it</a>
usando un indirizzo email personale (quello di ateneo è bloccato). Nel messaggio dobbiamo specificare chiaramente il problema e includere il nostro indirizzo email d'ateneo.
Dopo aver inviato la richiesta, tocca aspettare. Il supporto tecnico resetta manualmente lo status 2FA e ci manda una conferma. A quel punto possiamo procedere con l'attivazione seguendo le istruzioni ufficiali:
> https://it.unipi.it/configurazioni/mfa/autenticazione-a-piu-fattori-mfa-microsoft-365/
**Nota tecnica:** L'app Microsoft Authenticator non è obbligatoria! Qualsiasi autenticatore compatibile con [TOTP](https://en.wikipedia.org/wiki/Time-based_one-time_password) va bene: Google Authenticator, Bitwarden, Authy e altre alternative funzionano perfettamente. L'unica differenza è che l'app Microsoft Authenticator permette di ricevere notifiche l'accesso che consente di fare login con meno click.
Per utilizzare una di queste altre app (invece di Microsoft Authenticator), seguire questi step nella creazione del metodo di autenticazione:
1. Selezionare **"Microsoft Authenticator"**
![Selezionare Microsoft Authenticator](/images/misc/microsoft-2fa-other-app-1.webp)
2. Selezionare **"Configura un'app di autenticazione diversa"**
![Selezionare app diversa](/images/misc/microsoft-2fa-other-app-2.webp)
3. A questo punto il procedimento cambia in base all'app. In generale verrà richiesto di scannerizzare un QR code e di verificare il corretto funzionamento inserendo la TOTP (codice a 6 cifre)

@ -19,7 +19,6 @@ names:
analisi-armonica: Analisi Armonica
elementi-di-analisi-complessa: Elementi di Analisi Complessa
eti: Elementi di Teoria degli Insiemi
teoria-dei-nodi: Teoria dei Nodi
groups:
- id: triennale-anno-1
@ -59,7 +58,6 @@ groups:
- analisi-armonica
- elementi-di-analisi-complessa
- eti
- teoria-dei-nodi
questions:
- course: geometria-2
@ -749,7 +747,7 @@ questions:
- course: aritmetica
content: |
Quanti sono i polinomi irriducibili di grado $n$ su $\mathbb{F}_p$? (Hint: può essere utile provare prima il caso dei polinomi di secondo grado. Hint: in alternativa si possono contare i polinomi riducibili.)
Hint: Altrimenti dimostrate che
Hint: Altrimenti dimostrate che
$$\prod_{\mathclap{\substack{p(x) \text{irriducibile} \\ \deg(p(x)) | n}}} p(x) = x^{p^n} - x.$$
tags:
- 2015
@ -1061,7 +1059,7 @@ questions:
- course: ricerca-operativa
content: |
Dato il problema $\max (x_1 + 2x_2)$ soggetto ai vincoli:
Dato il problema $\max (x_1 + 2x_2)$ soggetto ai vincoli:
$$
\begin{cases}
x_2 \leq 4 \\
@ -1117,400 +1115,6 @@ questions:
tags:
- 2023
# Raccolta di Istituzioni di Geometria da Valeria
# Raccolta di Istituzioni di Geometria da Fra
- course: istituzioni-di-geometria
content: |
Intorno tubolare, esistenza
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Gruppi di Lie, esiste un unico sottogruppo connesso con data sottoalgebra
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Frobenius
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Invarianza omotopica di de Rham
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Mayer-Vietoris
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Cartan-Hadamard
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Lemma di Gauss
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Whitney
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Localmente euclideo se e solo se R=0
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Mayer-Vietoris a supporto compatto
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Orientabilità di RP^n e CP^n
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Lemma di Poincaré (dimostrare anche che mappe omotope inducono stesso pullback sulla coomologia), brevissimo accenno al caso a supporto compatto (nessuna dimostrazione)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Cos'è il trasporto parallelo (tutti i dettagli di buona definizione a partire dalla definizione di campo parallelo)? Se metto una metrica cosa succede? (Statement delle condizioni equivalenti di compatibilità senza dimostrazione)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Dimostra che l'algebra di Lie associata ad un gruppo di Lie è un'algebra di Lie in senso astratto (voleva giusto sentirsi dire che il bracket di campi invarianti a sinistra è invariante a sinistra senza dimostrazione)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Teorema di corrispondenza algebre di lie
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Quando gli spazi proiettivi reali sono orientabili
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Esistenza dell'intorno tubolare (senza dimostrazione degli "esercizi")
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Dualità di Poincaré 2
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Definizione di connessione metrica e condizioni equivalenti (solo enunciato)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Hopf-Rinow
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Isotopia Ambiente
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Differenziale Esterno
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Stokes
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Raddrizzamento simultaneo
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Definizione di fibrato tangente
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Definizione di intorno tubolare e esistenza
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Teorema di Frobenius
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Piatto se e solo se localmente isometrico
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Quando lo spazio proiettivo reale è orientabile? E quello complesso?
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Isotopia e isotopia ambiente
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Differenziale k-forme
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Corrispondenza tra sottogruppi di lie connessi e sottoalgebre di lie
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Torsione di una connessione
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Dualità di poincarè (perché la mappa DP passa in coomologia?)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Parlare dell'algebra di Lie associata a un Gruppo di Lie
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Enunciato di Frobenius, e perché una distribuzione è integrabile sse è loc. costante
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Poincaré Dualità
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Conseguenze (Betti numeri, specchiabilità)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Distribuzioni, esempi di distribuzioni che (non) sono embeddings
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Trasporto parallelo e geodetiche
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Esempio di varietà non geodeticamente completa, si può avere compatto? (Hopf rinow)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Esistenza di forma volume per varietà orientate e di struttura riemanniana sui fibrati
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Definizione di foliazione
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Localmente euclidea sse piatto
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Teorema di Stokes
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Teorema di Hopf-Rinow
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Orientabilità di CP^n
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Definizione di forma volume ed esistenza
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Stokes (dim.)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
isotopia e isotopia ambiente (def. + teo.)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
tensore Riemann (def.)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
teo. sottoalgebre di Lie
tags:
- 2025
- course: istituzioni-di-geometria
content: |
esistenza metrica Lorentziana e Riemanniana
tags:
- 2025
- course: istituzioni-di-geometria
content: |
derivata di k-forme
tags:
- 2025
- course: istituzioni-di-geometria
content: |
lemma Poincaré e teo. sull'omotopia
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Cos'è l'algebra di Lie (molto veloce)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Cos'è la connessione di Levi Civita ed elencare le forme equivalenti di "compatibilità con la metrica"
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Definire la torsione e fare i conti per mostrare che è un tensore
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Teorema di Whitney (caso compatto e caso generale).
tags:
- 2025
- course: istituzioni-di-geometria
content: |
M ammette struttura lorentziana orientabile temporalmente sse pettinabile.
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Teorema su sottoalgebre di Lie
tags:
- 2025
- course: istituzioni-di-geometria
content: |
geodetiche (praticamente solo quale è l'equazione e perché ha senso fare la nabla di un campo su una curva)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Frobenius, enunciato e dimostrazione (dando per scontate le def.equivalenti di foliazione)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
Whitney (entrambi)
tags:
- 2025
- course: istituzioni-di-geometria
content: |
quando il proiettivo è orientabile (ed enunciato del teorema da cui segue)
tags:
- 2025
# Raccolta di Istituzioni di Geometria da Fra
- course: istituzioni-di-geometria
@ -2512,7 +2116,7 @@ questions:
- course: geometria-e-topologia-differenziale
content: |
Spazio tangente a $S^n$ (dimostrando che il tangente a $f^{-1}(y) = \ker(df_x)$)
Spazio tangente a $S^n$ (dimostrando che il tangente a $f^{-1}(y) = \ker(df_x)$)
tags:
- 2023
@ -2525,25 +2129,25 @@ questions:
- course: geometria-e-topologia-differenziale
content: |
Che cos'è un'orientazione su una varietà?
Se la varietà è connessa e orientabile, mostrare che esistono esattamente 2 orientazioni
Se la varietà è connessa e orientabile, mostrare che esistono esattamente 2 orientazioni
(se non connessa, ce n'è una per ogni componente)
tags:
- 2023
- course: geometria-e-topologia-differenziale
content: |
Per quale motivo è lecito parlare di orientazione "naturale"
Per quale motivo è lecito parlare di orientazione "naturale"
(nel senso di cosa è permesso e cosa no)?
tags:
- 2023
- course: geometria-e-topologia-differenziale
content: |
Per un diffeomorfismo da una varietà connessa su se stessa,
o ne rispetta l'orientazione, o la "flippa".
Mostrare (idee) che esiste tale $f$ che "flippa"
Per un diffeomorfismo da una varietà connessa su se stessa,
o ne rispetta l'orientazione, o la "flippa".
Mostrare (idee) che esiste tale $f$ che "flippa"
(ad esempio considerare un $f_0 : x \mapsto -x$ che flippa la sfera in $\mathbb{R}^n$
e analogamente si può fare per altre varietà in punto,
e analogamente si può fare per altre varietà in punto,
allora viene flippata sempre)
tags:
- 2023
@ -2632,104 +2236,3 @@ questions:
e illustrare la formula di Gauss
tags:
- 2023
- course: teoria-dei-nodi
content: |
Definizione di superficie di Seifert e sue proprietà locali.
tags:
- 2025
- course: teoria-dei-nodi
content: |
Teorema di Seifert, enunciato e dimostrazione.
tags:
- 2025
- course: teoria-dei-nodi
content: |
Definizione del genere di un nodo (tramite superficie di Seifert).
tags:
- 2025
- course: teoria-dei-nodi
content: |
Proprietà del genere di un nodo: genere del nodo banale ($g(U)=0$), additività rispetto alla somma connessa ($g(K_1 \# K_2) = g(K_1) + g(K_2)$).
tags:
- 2025
- course: teoria-dei-nodi
content: |
Relazione tra caratteristica di Eulero di una superficie di Seifert e il genere del nodo.
tags:
- 2025
- course: teoria-dei-nodi
content: |
Genere del nodo immagine speculare $m(K)$.
tags:
- 2025
- course: teoria-dei-nodi
content: |
Minimalità del genere di un nodo (genere di una superficie di Seifert minima).
tags:
- 2025
- course: teoria-dei-nodi
content: |
Struttura del complementare di una superficie di Seifert $S$ in $\mathbb{R}^3$.
tags:
- 2025
- course: teoria-dei-nodi
content: |
Numero di allacciamento
tags:
- 2025
- course: teoria-dei-nodi
content: |
Verifica che $\text{lk}$ è un invariante
tags:
- 2025
- course: teoria-dei-nodi
content: |
Interpretazione omologica di $\text{lk}$.
tags:
- 2025
- course: teoria-dei-nodi
content: |
Classi di omotopia vs isotopia
tags:
- 2025
- course: teoria-dei-nodi
content: |
Due curve semplici chiuse sul toro sono isotope $\Leftrightarrow$ solo la stessa classe a meno del segno in $H_1(S^1 \times S^1)$
tags:
- 2025
- course: teoria-dei-nodi
content: |
$L = \alpha \cup \beta$, $\text{lk}(L) = \#(\alpha \cap G) = \#(\beta \cap F)$
tags:
- 2025
- course: teoria-dei-nodi
content: |
Forma di Seifert
tags:
- 2025
- course: teoria-dei-nodi
content: |
Modulo di Alexander
tags:
- 2025
- course: teoria-dei-nodi
content: |
Struttura di $H_1(X_\infty)$
tags:
- 2025
- course: teoria-dei-nodi
content: |
$H_1(X_\infty)$ è presentato da $tA - A^T$.
tags:
- 2025
- course: teoria-dei-nodi
content: |
Differenza tra superfici di Seifert: S-equivalenza e invarianza per mosse di Reidemeister.
tags:
- 2025
- course: teoria-dei-nodi
content: |
Relazioni tra le matrici di Seifert.
tags:
- 2025

@ -25,8 +25,7 @@
- fullName: Francesco Baldino
entranceDate: 2022
description: |
Appassionato di Star Wars, NixOS e lunghe camminate in montagna. Pokemon preferito: Latias.
description: Bla bla Star Wars
social:
github: https://github.com/Fran314
website: https://poisson.phc.dm.unipi.it/~baldino

@ -9,17 +9,20 @@ import '@fontsource/iosevka/latin.css'
import '@fontsource-variable/material-symbols-outlined/full.css'
import '@/styles/main.css'
import '../styles/main.scss'
type Props = {
title?: string
description?: string
thumbnail?: string
/** Tags for the page, used for styling */
pageTags?: string | string[]
}
import phcIcon from '../assets/icon.png'
const { title, description, thumbnail } = Astro.props
const { title, description, thumbnail, pageTags } = Astro.props
---
<!doctype html>
@ -72,14 +75,9 @@ const { title, description, thumbnail } = Astro.props
src="//analytics.phc.dm.unipi.it/count.js"
data-goatcounter="https://analytics.phc.dm.unipi.it/count"></script>
<style is:inline>
/* Workaround Astro CSS loading order, this forces the layering upfront */
@layer base, typography, component, page, utility;
</style>
<title>{title ?? 'PHC'}</title>
</head>
<body>
<body class:list={typeof pageTags === 'string' ? [pageTags] : pageTags}>
<slot />
</body>
</html>

@ -1,6 +1,4 @@
---
import '@/styles/pages/appunti.css'
import PageLayout from '@layouts/PageLayout.astro'
import { AppuntiList, AppuntiCard } from '@client/Appunti'

@ -1,6 +1,4 @@
---
import '@/styles/pages/domande-esami.css'
import type { GetStaticPaths } from 'astro'
import BaseLayout from '@/layouts/BaseLayout.astro'
import Footer from '@/components/Footer.astro'
@ -18,7 +16,7 @@ export const getStaticPaths = (() => {
const { course } = Astro.params
---
<BaseLayout title="Domande Orali | PHC">
<BaseLayout title="Domande Orali | PHC" pageTags={'domande-esami'}>
<Header />
<main>
<DomandeEsamiCourse client:only="preact" course={course} />

@ -1,6 +1,4 @@
---
import '@/styles/pages/domande-esami.css'
import { PhosphorIcon } from '@/client/Icon'
import Footer from '@/components/Footer.astro'
import Header from '@/components/Header.astro'
@ -16,7 +14,7 @@ const courseQuestionCounts = Object.fromEntries(
)
---
<BaseLayout title="Domande Orali | PHC">
<BaseLayout title="Domande Orali | PHC" pageTags={'domande-esami'}>
<Header />
<main>
<h1>Domande Orali</h1>

@ -1,6 +1,4 @@
---
import '@/styles/pages/guide-item.css'
import { getCollection } from 'astro:content'
import ArticleLayout from '@/layouts/ArticleLayout.astro'
@ -18,9 +16,11 @@ const { entry } = Astro.props
const { Content } = await entry.render()
---
<ArticleLayout {...entry.data} title={entry.data.title + ' | Guide | PHC'}>
<!-- pageTags={['guida', entry.data.id, entry.data.series && 'series']} -->
<ArticleLayout
{...entry.data}
title={entry.data.title + ' | Guide | PHC'}
pageTags={['guida', entry.data.id, entry.data.series && 'series']}
>
<h1>{entry.data.title}</h1>
{entry.data.series && <div class="series">Serie: {entry.data.series}</div>}

@ -1,6 +1,4 @@
---
import '@/styles/pages/guide-list.css'
import { getCollection } from 'astro:content'
import PageLayout from '@layouts/PageLayout.astro'
@ -8,7 +6,7 @@ import PageLayout from '@layouts/PageLayout.astro'
const guides = await getCollection('guides')
---
<PageLayout title="Guide | PHC">
<PageLayout title="Guide | PHC" pageTags="guide">
<h1>
<a href="/guide">Guide</a>
</h1>

@ -1,6 +1,4 @@
---
import '@/styles/pages/guide-list.css'
import { getCollection } from 'astro:content'
import type { CollectionEntry } from 'astro:content'
import PageLayout from '@/layouts/PageLayout.astro'
@ -35,7 +33,7 @@ interface Props {
const { tag, guides } = Astro.props
---
<PageLayout title={`#${tag} | Guide | PHC`}>
<PageLayout title={`#${tag} | Guide | PHC`} pageTags="guide tag">
<h1><a href="/guide">Guide</a> > <a href={`/guide/tags/${tag}`}>#{tag}</a></h1>
<div class="card-list">
{

@ -1,6 +1,4 @@
---
import '@/styles/pages/homepage.css'
import { getCollection } from 'astro:content'
import PageLayout from '@/layouts/PageLayout.astro'
import { Content as WhatPhcContent, frontmatter as whatsPhcFrontmatter } from '@/content/meta/whats-phc.md'
@ -11,11 +9,7 @@ import Card from '@/components/Card.astro'
const news = await getCollection('news')
// const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
const galleryCollage: { default: ImageMetadata }[] = Object.values(
import.meta.glob('@/assets/gallery/*.jpg', { eager: true }),
)
const galleryCollage = await Astro.glob('@/assets/gallery/*.jpg')
---
<PageLayout title="PHC" pageTags="homepage">
@ -51,23 +45,21 @@ const galleryCollage: { default: ImageMetadata }[] = Object.values(
<div class="card-list">
{
news
.sort((s, t) => -s.id.localeCompare(t.id))
.map(newsItem => (
<Card>
<a href={`/notizie/${newsItem.slug}`} class="title">
{newsItem.data.title}
</a>
<div class="text small dimmed">
{new Date(newsItem.data.publishDate).toLocaleDateString('it-IT', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</div>
<div class="text">{newsItem.data.description}</div>
</Card>
))
news.toReversed().map(newsItem => (
<Card>
<a href={`/notizie/${newsItem.slug}`} class="title">
{newsItem.data.title}
</a>
<div class="text small dimmed">
{new Date(newsItem.data.publishDate).toLocaleDateString('it-IT', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</div>
<div class="text">{newsItem.data.description}</div>
</Card>
))
}
</div>
@ -204,16 +196,14 @@ const galleryCollage: { default: ImageMetadata }[] = Object.values(
{
galleryCollage.map((module, i) => {
const src = module.default.src
const filename = src.split('/').at(-1)?.split('?').at(0)?.split('.').at(0)
if (!filename) return null
const filename = src.split('/').at(-1).split('?').at(0).split('.').at(0)
const [rows, cols] = filename.includes('@')
? (filename
? filename
.split('@')
.at(-1)
?.split('x')
.map((s: string) => parseInt(s)) ?? [1, 1])
.split('x')
.map((s: string) => parseInt(s))
: [1, 1]
return (

@ -1,6 +1,4 @@
---
import '@/styles/pages/macchinisti.css'
import BaseLayout from '../layouts/BaseLayout.astro'
import Header from '../components/Header.astro'
import Footer from '../components/Footer.astro'
@ -28,7 +26,7 @@ const getMacchinistaPicture = (fullName: string) => {
}
---
<BaseLayout title="Macchinisti | PHC">
<BaseLayout title="Macchinisti | PHC" pageTags={'macchinisti'}>
<Header />
<main>
<div class="card large" style={{ '--card-base': '#e1766b' }}>

@ -1,21 +0,0 @@
---
import '@/styles/pages/media-pesata.css'
import PageLayout from '../layouts/PageLayout.astro'
import { MediaPesataApp } from '@/client/MediaPesataApp'
---
<PageLayout
title="Voto Laurea"
description="Calcola la tua media pesata e il voto di laurea seguendo le regole del dipartimento"
>
<div class="media-pesata-container">
<h1>Calcolo Media e Voto di Laurea</h1>
<p>
Calcola la tua media pesata e il voto con cui ti siederai alla discussione di laurea, seguendo le regole del
dipartimento di Matematica.
</p>
<MediaPesataApp client:load />
</div>
</PageLayout>

@ -1,6 +1,4 @@
---
import '@/styles/pages/meta-design.css'
import BaseLayout from '../../layouts/BaseLayout.astro'
import Header from '../../components/Header.astro'
@ -13,7 +11,7 @@ import { Content, getHeadings } from '../../content/meta/design.mdx'
const headings = getHeadings()
---
<BaseLayout {...Astro.props}>
<BaseLayout {...Astro.props} pageTags="design">
<Header />
<aside>
<nav>

@ -1,6 +1,4 @@
---
import '@/styles/pages/news-item.css'
import { getCollection } from 'astro:content'
import ArticleLayout from '../../layouts/ArticleLayout.astro'
@ -17,6 +15,6 @@ const { entry } = Astro.props
const { Content } = await entry.render()
---
<ArticleLayout {...entry.data} title={entry.data.title + ' | Notizie | PHC'}>
<ArticleLayout {...entry.data} title={entry.data.title + ' | Notizie | PHC'} pageTags={['notizia']}>
<Content />
</ArticleLayout>

@ -1,6 +1,4 @@
---
import '@/styles/pages/news-list.css'
import { getCollection } from 'astro:content'
import PageLayout from '@layouts/PageLayout.astro'
@ -8,27 +6,25 @@ import PageLayout from '@layouts/PageLayout.astro'
const news = await getCollection('news')
---
<PageLayout title="Notizie | PHC">
<PageLayout title="Notizie | PHC" pageTags="notizie">
<h1><a href="/notizie">Notizie</a></h1>
<div class="card-list">
{
news
.sort((s, t) => -s.id.localeCompare(t.id))
.map(newsItem => (
<div class="card">
<a href={`/notizie/${newsItem.slug}`} class="title">
{newsItem.data.title}
</a>
<div class="text small dimmed">
{new Date(newsItem.data.publishDate).toLocaleDateString('it-IT', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</div>
<div class="text">{newsItem.data.description}</div>
news.toReversed().map(newsItem => (
<div class="card">
<a href={`/notizie/${newsItem.slug}`} class="title">
{newsItem.data.title}
</a>
<div class="text small dimmed">
{new Date(newsItem.data.publishDate).toLocaleDateString('it-IT', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</div>
))
<div class="text">{newsItem.data.description}</div>
</div>
))
}
</div>
</PageLayout>

@ -1,6 +1,4 @@
---
import '@/styles/pages/storia.css'
import BaseLayout from '../layouts/BaseLayout.astro'
import Header from '../components/Header.astro'
@ -12,7 +10,7 @@ import imgCluster from '@/assets/gallery/001-cluster-fra-luca@4x3.jpg'
import WebSite from '@/assets/gallery/005-website-development@3x4.jpg'
---
<BaseLayout title="Storia | PHC">
<BaseLayout title="Storia | PHC" pageTags={'storia'}>
<Header />
<main>
<div class="card large" style={{ '--card-base': '#ffd3a0' }}>

@ -1,12 +1,10 @@
---
import '@/styles/pages/utenti.css'
import PageLayout from '../layouts/PageLayout.astro'
import { UtentiPage } from '../client/UtentiPage.tsx'
---
<PageLayout title="Utenti | PHC">
<PageLayout title="Utenti | PHC" pageTags="utenti">
<h1>Utenti</h1>
<UtentiPage client:load />
</PageLayout>

@ -1,27 +0,0 @@
/* This file is here for historical reasons but is not used anymore */
@layer page {
/*
.login {
background: #ddfaff;
main {
justify-self: center;
display: flex;
flex-direction: column;
align-items: center;
max-width: 80ch;
padding: 3rem 0;
gap: 3rem;
h3 {
font-size: 28px;
font-weight: 600;
}
}
}
*/
}

@ -1,15 +1,12 @@
/*
$news-bg: #fffbeb;
$news-accent-bg: #f8e8b1;
*/
// $news-bg: #fffbeb;
// $news-accent-bg: #f8e8b1;
/* @TODO: SCSS conversion - @import becomes more complex */
/* @import './mixins.scss'; */
@import './mixins.scss';
@layer component {
/*
/* Components - for complex parts of the UI like search bars or compound buttons
*/
//
// Components - for complex parts of the UI like search bars or compound buttons
//
.phosphor-icon {
box-sizing: content-box;
@ -55,13 +52,7 @@ $news-accent-bg: #f8e8b1;
width: 100%;
height: 2.5rem;
/* @TODO: SCSS conversion - mixin */
/* @include neo-brutalist-card; */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 4px 4px 0 0 #222;
background: #fff;
@include neo-brutalist-card;
display: grid;
grid-template-columns: 1fr auto;
@ -69,6 +60,8 @@ $news-accent-bg: #f8e8b1;
cursor: pointer;
background: #fff;
&:hover,
&:hover input[type='text'] {
background: #f8f8f8;
@ -112,11 +105,7 @@ $news-accent-bg: #f8e8b1;
background: #fff;
/* @TODO: SCSS conversion - mixin */
/* @include neo-brutalist-card; */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 4px 4px 0 0 #222;
@include neo-brutalist-card;
a {
display: contents;
@ -172,16 +161,7 @@ $news-accent-bg: #f8e8b1;
aspect-ratio: 10 / 14;
background: #d0d0d0;
/* @TODO: SCSS conversion - mixin */
/* @include neo-brutalist-card($hoverable: true); */
border: 3px solid #222;
border-radius: 6px;
transition: all 64ms linear;
&:hover {
transform: translate(-1px, -1px);
box-shadow: 4px 4px 0 0 #222;
}
@include neo-brutalist-card($hoverable: true);
}
& > .thumbnail + * {
@ -215,11 +195,7 @@ $news-accent-bg: #f8e8b1;
background: var(--card-bg, var(--project-card-bg));
color: #000e;
/* @TODO: SCSS conversion - mixin */
/* @include neo-brutalist-card($size: 3px, $offset: 9px); */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 9px 9px 0 0 #222;
@include neo-brutalist-card($size: 3px, $offset: 9px);
row-gap: 0.5rem;
padding: 1rem;
@ -243,109 +219,109 @@ $news-accent-bg: #f8e8b1;
display: grid;
grid-auto-flow: column;
/* .news-item {
background: $news-bg;
color: #111;
@include neo-brutalist-card($size: 3px, $offset: 9px);
display: flex;
flex-direction: column;
width: 22rem;
max-height: 27rem;
overflow: hidden;
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-thumb {
background-color: #c67e14;
border: 2px solid #222;
&:hover {
background-color: #e69419;
}
}
a {
font-weight: 600;
text-decoration: none;
color: #c67e14;
&:hover {
text-decoration: underline solid 2px;
}
}
& > .title {
padding: 1rem;
background: $news-accent-bg;
line-height: 1;
font-size: 26px;
}
a.title {
color: #83530c;
}
& > .abstract {
flex-grow: 1;
padding: 1rem;
overflow-y: auto;
@extend .text;
}
& > .content {
display: flex;
padding: 1rem;
flex-direction: column;
gap: 0.5rem;
background: #fff8da;
& > .continue {
padding: 1rem;
display: grid;
align-items: end;
justify-content: end;
}
& > .description {
font-size: 16px;
line-height: 1.5;
flex-grow: 1;
}
& > .tags {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
font-size: 14px;
color: #555;
}
& > .date {
font-size: 14px;
font-style: italic;
font-weight: 600;
color: #0008;
}
& > .author {
font-weight: 600;
font-size: 15px;
color: #555;
}
}
} */
// .news-item {
// background: $news-bg;
// color: #111;
// @include neo-brutalist-card($size: 3px, $offset: 9px);
// display: flex;
// flex-direction: column;
// width: 22rem;
// max-height: 27rem;
// overflow: hidden;
// ::-webkit-scrollbar {
// width: 10px;
// }
// ::-webkit-scrollbar-thumb {
// background-color: #c67e14;
// border: 2px solid #222;
// &:hover {
// background-color: #e69419;
// }
// }
// a {
// font-weight: 600;
// text-decoration: none;
// color: #c67e14;
// &:hover {
// text-decoration: underline solid 2px;
// }
// }
// & > .title {
// padding: 1rem;
// background: $news-accent-bg;
// line-height: 1;
// font-size: 26px;
// }
// a.title {
// color: #83530c;
// }
// & > .abstract {
// flex-grow: 1;
// padding: 1rem;
// overflow-y: auto;
// @extend .text;
// }
// & > .content {
// display: flex;
// padding: 1rem;
// flex-direction: column;
// gap: 0.5rem;
// background: #fff8da;
// & > .continue {
// padding: 1rem;
// display: grid;
// align-items: end;
// justify-content: end;
// }
// & > .description {
// font-size: 16px;
// line-height: 1.5;
// flex-grow: 1;
// }
// & > .tags {
// display: flex;
// gap: 0.5rem;
// flex-wrap: wrap;
// font-size: 14px;
// color: #555;
// }
// & > .date {
// font-size: 14px;
// font-style: italic;
// font-weight: 600;
// color: #0008;
// }
// & > .author {
// font-weight: 600;
// font-size: 15px;
// color: #555;
// }
// }
// }
}
.timeline {
@ -358,7 +334,7 @@ $news-accent-bg: #f8e8b1;
max-width: 120ch;
grid-template-columns: 1fr var(--timeline-track-size) 1fr;
@media screen and (max-width: 1024px) {
@media screen and (max-width: $screen-desktop-min) {
--timeline-track-size: 3rem;
grid-template-columns: var(--timeline-track-size) 1fr;
}
@ -372,7 +348,7 @@ $news-accent-bg: #f8e8b1;
grid-template-columns: auto;
padding: 2rem 1rem;
@media screen and (max-width: 1024px) {
@media screen and (max-width: $screen-desktop-min) {
padding: 1rem 1rem 1rem 0;
}
@ -388,7 +364,7 @@ $news-accent-bg: #f8e8b1;
}
}
/* timeline vertical line */
// timeline vertical line
&::before {
content: '';
@ -410,7 +386,7 @@ $news-accent-bg: #f8e8b1;
bottom: 50%;
}
/* timeline circle */
// timeline circle
&::after {
content: '';
@ -430,7 +406,7 @@ $news-accent-bg: #f8e8b1;
grid-column: 1 / span 1;
}
@media screen and (min-width: 1024px) {
@media screen and (min-width: $screen-desktop-min) {
&:nth-child(odd)::before {
grid-column: 2 / span 1;
}
@ -449,11 +425,7 @@ $news-accent-bg: #f8e8b1;
}
.dropdown {
/* @TODO: SCSS conversion - mixin */
/* @include neo-brutalist-card; */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 3px 3px 0 0 #222;
@include neo-brutalist-card;
max-width: 15rem;
position: absolute;
@ -463,12 +435,8 @@ $news-accent-bg: #f8e8b1;
border-top: none;
border-radius: 0 0 0.5rem 0.5rem;
@media screen and (max-width: 1024px) {
/* @TODO: SCSS conversion - mixin */
/* @include neo-brutalist-card; */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 3px 3px 0 0 #222;
@media screen and (max-width: $screen-desktop-min) {
@include neo-brutalist-card;
position: static;
transform: none;
@ -476,7 +444,7 @@ $news-accent-bg: #f8e8b1;
margin-right: auto;
}
}
@media screen and (min-width: 1024px) {
@media screen and (min-width: $screen-desktop-min) {
&:nth-child(odd) {
grid-column: 1 / span 2;
grid-template-columns: 1fr var(--timeline-track-size);
@ -507,7 +475,7 @@ $news-accent-bg: #f8e8b1;
}
}
@media screen and (max-width: 1024px) {
@media screen and (max-width: $screen-desktop-min) {
grid-column: 1 / span 2;
grid-template-columns: var(--timeline-track-size) 1fr;
@ -519,9 +487,9 @@ $news-accent-bg: #f8e8b1;
}
}
/*
/* Cards
*/
//
// Cards
//
.card {
display: grid;
@ -531,25 +499,17 @@ $news-accent-bg: #f8e8b1;
background: var(--card-base-internal);
color: color-mix(in srgb, var(--card-base-internal), #000 80%);
/* @TODO: SCSS conversion - mixin */
/* @include neo-brutalist-card($size: 3px, $offset: 9px); */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 9px 9px 0 0 #222;
@include neo-brutalist-card($size: 3px, $offset: 9px);
row-gap: 0.5rem;
padding: 1rem;
/* Variants */
// Variants
&.large {
padding: 2rem;
/* @TODO: SCSS conversion - mixin */
/* @include neo-brutalist-card($size: 4px, $offset: 8px); */
border: 4px solid #222;
border-radius: 8px;
box-shadow: 8px 8px 0 0 #222;
@include neo-brutalist-card($size: 4px, $offset: 8px);
row-gap: 1rem;
@ -563,7 +523,7 @@ $news-accent-bg: #f8e8b1;
}
}
/* Child Items */
// Child Items
& > .title {
color: color-mix(in srgb, var(--card-base-internal), #000 75%);
@ -620,7 +580,7 @@ $news-accent-bg: #f8e8b1;
gap: 0.5rem;
}
@media screen and (max-width: 1024px) {
@media screen and (max-width: $screen-desktop-min) {
padding: 0.9rem;
&.large {
@ -669,9 +629,9 @@ $news-accent-bg: #f8e8b1;
display: contents;
}
/*
/* Card List
*/
//
// Card List
//
.card-list {
display: grid;
@ -703,7 +663,7 @@ $news-accent-bg: #f8e8b1;
gap: 1rem;
@media screen and (max-width: 1024px) {
@media screen and (max-width: $screen-desktop-min) {
grid-template-columns: 1fr;
& > .search {
@ -713,44 +673,44 @@ $news-accent-bg: #f8e8b1;
}
/* .filter-select {
width: 100%;
height: 2.5rem;
width: 100%;
height: 2.5rem;
@include neo-brutalist-card;
@include neo-brutalist-card;
display: grid;
align-items: center;
grid-auto-flow: column;
display: grid;
align-items: center;
grid-auto-flow: column;
cursor: pointer;
cursor: pointer;
--filter-bg-color-hover: color-mix(in srgb, var(--filter-bg-color, #ddd), #000 10%);
--filter-bg-color-hover: color-mix(in srgb, var(--filter-bg-color, #ddd), #000 10%);
background: var(--filter-bg-color, #ddd);
background: var(--filter-bg-color, #ddd);
&:hover,
&:hover select {
background: var(--filter-bg-color-hover);
}
&:hover,
&:hover select {
background: var(--filter-bg-color-hover);
}
.material-symbols-outlined {
padding: 0 0.35rem;
}
.material-symbols-outlined {
padding: 0 0.35rem;
}
select {
border: none;
box-shadow: none;
outline: none;
height: 100%;
appearance: none;
select {
border: none;
box-shadow: none;
outline: none;
height: 100%;
appearance: none;
cursor: pointer;
cursor: pointer;
padding: 0;
padding: 0;
background: var(--filter-bg-color, #ddd);
}
} */
background: var(--filter-bg-color, #ddd);
}
} */
.combobox {
width: 100%;
@ -758,10 +718,7 @@ $news-accent-bg: #f8e8b1;
position: relative;
padding: 0 0.25rem 0 0.25rem;
/* @TODO: SCSS conversion - mixin */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 4px 4px 0 0 #222;
@include neo-brutalist-card;
cursor: pointer;
@ -796,10 +753,7 @@ $news-accent-bg: #f8e8b1;
top: calc(100% + 8px);
left: -3px;
/* @TODO: SCSS conversion - mixin */
border: 3px solid #222;
border-radius: 6px;
/* box-shadow: 3px 3px 0 0 #222; */
@include neo-brutalist-card;
background: #fff;
@ -816,7 +770,7 @@ $news-accent-bg: #f8e8b1;
}
}
@media screen and (max-width: 1024px) {
@media screen and (max-width: $screen-desktop-min) {
.dropdown {
left: 3px;
right: 3px;
@ -827,10 +781,10 @@ $news-accent-bg: #f8e8b1;
}
.gallery-collage {
/* display: flex;
flex-wrap: wrap;
// display: flex;
// flex-wrap: wrap;
width: 64rem; */
// width: 64rem;
width: 100%;
max-width: 100%;
@ -845,9 +799,9 @@ $news-accent-bg: #f8e8b1;
gap: 1rem;
place-content: center;
/* align-items: center; */
// align-items: center;
@media screen and (max-width: 1024px) {
@media screen and (max-width: $screen-desktop-min) {
grid-template-columns: auto;
grid-template-rows: auto;
grid-auto-rows: auto;
@ -864,7 +818,7 @@ $news-accent-bg: #f8e8b1;
grid-column: span var(--cols, 1);
grid-row: span var(--rows, 1);
@media screen and (max-width: 1024px) {
@media screen and (max-width: $screen-desktop-min) {
max-width: 100%;
grid-column: span 1;
@ -883,7 +837,7 @@ $news-accent-bg: #f8e8b1;
object-fit: cover;
@media screen and (max-width: 1024px) {
@media screen and (max-width: $screen-desktop-min) {
max-height: none;
width: 25rem;
@ -924,7 +878,7 @@ $news-accent-bg: #f8e8b1;
font-size: 15px;
font-weight: 700;
/* gold badge */
// gold badge
background: #ffdb12;
color: #725306;
@ -974,7 +928,7 @@ $news-accent-bg: #f8e8b1;
width: 100%;
justify-content: center;
/* align-items: start; */
// align-items: start;
.text > * {
max-width: none;
@ -985,7 +939,7 @@ $news-accent-bg: #f8e8b1;
grid-template-rows: 1fr auto;
}
@media screen and (max-width: 1024px) {
@media screen and (max-width: $screen-desktop-min) {
grid-template-columns: 1fr;
padding: 0;
@ -1042,25 +996,11 @@ $news-accent-bg: #f8e8b1;
grid-auto-flow: row;
}
.grid-h-split {
display: grid;
place-content: center;
place-items: center;
gap: 1rem;
grid-auto-flow: column;
grid-auto-columns: 1fr;
@media screen and (max-width: 1024px) {
grid-auto-flow: row;
grid-auto-columns: auto;
}
}
.clickable {
cursor: pointer;
}
/* just to know for reference */
// just to know for reference
.fake-masonry {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);

@ -1,10 +1,10 @@
/* @import './mixins.scss'; */
@import './mixins.scss';
/*
Controls - for things like buttons, input, select
*/
//
// Controls - for things like buttons, input, select
//
@layer base {
@layer common {
button,
.button,
[role='button'] {
@ -12,10 +12,7 @@ Controls - for things like buttons, input, select
background: #fff;
/* @include neo-brutalist-card; */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 4px 4px 0 0 #222;
@include neo-brutalist-card;
transition: all 64ms linear;
@ -38,7 +35,7 @@ Controls - for things like buttons, input, select
text-decoration: none;
color: #222;
font-family: var(--font-secondary); /* TODO: check if this is a global variable and replace */
font-family: var(--font-secondary);
font-weight: 600;
cursor: pointer;
@ -75,12 +72,9 @@ Controls - for things like buttons, input, select
input[type='text'],
input[type='password'] {
width: 100%;
min-height: 1.75rem;
height: 2.5rem;
/* @include neo-brutalist-card; */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 4px 4px 0 0 #222;
@include neo-brutalist-card;
padding: 0 0.25rem;
@ -89,95 +83,6 @@ Controls - for things like buttons, input, select
}
}
input[type='checkbox'] {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
width: calc(1.5rem + 1px);
height: calc(1.5rem + 1px);
background: #fff;
border: 3px solid #222;
border-radius: 4px;
box-shadow: 3px 3px 0 0 #222;
position: relative;
cursor: pointer;
transition: all 64ms linear;
&:hover {
transform: translate(-1px, -1px);
box-shadow: 4px 4px 0 0 #222;
}
&:active {
transform: translate(1px, 1px);
box-shadow: 2px 2px 0 0 #222;
}
&:checked {
background: #1e6733;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 0.8rem;
height: 0.8rem;
background: #1e6733;
clip-path: polygon(10% 55%, 35% 75%, 85% 25%, 90% 35%, 40% 95%, 5% 60%);
}
&:hover {
background: #2b8b47;
}
}
&:disabled {
background: #eee;
border-color: #888;
box-shadow: 3px 3px 0 0 #888;
cursor: not-allowed;
&:hover {
transform: none;
box-shadow: 3px 3px 0 0 #888;
}
&:checked {
background: #aaa;
&::after {
background: #666;
}
}
}
&.star {
&:checked::after {
background: rgb(255, 197, 49);
clip-path: polygon(
50% 0%,
61% 35%,
98% 35%,
68% 57%,
79% 91%,
50% 70%,
21% 91%,
32% 57%,
2% 35%,
39% 35%
);
}
}
}
form {
display: grid;
gap: 1rem;
@ -187,10 +92,7 @@ Controls - for things like buttons, input, select
min-width: 40ch;
/* @include neo-brutalist-card($size: 3px, $offset: 9px); */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 9px 9px 0 0 #222;
@include neo-brutalist-card($size: 3px, $offset: 9px);
button,
.button,
@ -263,8 +165,7 @@ Controls - for things like buttons, input, select
}
}
@media screen and (max-width: 1024px) {
/* TODO: check if this is a global variable and replace */
@media screen and (max-width: $screen-desktop-min) {
summary {
place-content: stretch;
place-items: stretch;

@ -1,371 +0,0 @@
@layer base, typography, component, page, utility;
@import url(./controls.css);
@import url(./components.css);
@import url(./typography.css);
/* $screen-desktop-min: 1024px; */
/* @TODO: SCSS conversion - @import becomes more complex */
/* @import './mixins.scss'; */
/* @import './typography.scss'; */
:root {
--palette-black: #222;
--border-large: 4px solid var(--palette-black);
--header-bg: #fff;
--footer-bg: #444;
--footer-fg: #fdfdfd;
--homepage-principal-bg: #ecffe3;
--homepage-whatsphc-bg: #e4c5ff;
--homepage-news-bg: #c2a8eb;
--homepage-projects-bg: #f5f2cc;
--homepage-macchinisti-bg: #888;
--project-card-bg: #a2d4f3;
--font-primary: 'Open Sans', sans-serif;
--font-display: 'Iosevka', monospace;
--font-mono: 'Source Code Pro', monospace;
--font-secondary: 'Source Sans Pro', sans-serif;
}
:root {
--guide-base: #a2d4f3;
--guide-darkest: color-mix(in srgb, var(--guide-base), #000 75%);
--guide-darker: color-mix(in srgb, var(--guide-base), #000 50%);
--guide-dark: color-mix(in srgb, var(--guide-base), #000 25%);
--guide-light: color-mix(in srgb, var(--guide-base), #fff 25%);
--guide-lighter: color-mix(in srgb, var(--guide-base), #fff 50%);
--guide-lightest: color-mix(in srgb, var(--guide-base), #fff 75%);
--news-base: #f8e8b1;
}
@layer base {
*,
*::before,
*::after {
box-sizing: border-box;
font: inherit;
margin: 0;
}
html {
height: 100%;
}
html,
body {
min-height: 100%;
margin: 0;
font-family: var(--font-primary);
font-size: 18px;
color: #222;
}
/*
html {
scroll-snap-type: y mandatory;
scroll-padding-top: 4rem;
}
*/
img {
display: block;
}
a {
color: inherit;
text-decoration: none;
}
body {
/*
for the header spacing
padding-top: 6rem;
*/
display: grid;
grid-template-rows: auto 1fr auto;
header {
z-index: 10;
height: 6rem;
border-bottom: var(--border-large);
background: var(--header-bg);
grid-column: 1 / -1;
top: 0;
position: sticky;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.logo {
padding-left: 1rem;
img {
height: 3.5rem;
}
}
.links {
display: flex;
align-items: center;
gap: 1.5rem;
padding: 0 1.5rem;
a {
font-family: var(--font-display);
font-size: 18px;
font-weight: 500;
letter-spacing: 1px;
color: #333;
padding: 0.25rem 1.325rem;
}
@media screen and (max-width: 1024px) {
flex-direction: column;
}
}
.side-menu {
position: fixed;
/*
top: 0;
right: 0;
bottom: 0;
left: 3rem;
*/
top: 5rem;
right: 0;
bottom: 0;
left: 0;
background: #f0f0f0;
display: grid;
/*
grid-template-rows: auto 1fr;
*/
grid-template-rows: 1fr;
gap: 1.5rem;
padding: 1.5rem;
justify-content: center;
/*
& > :first-child {
justify-self: end;
}
*/
.links {
display: grid;
grid-template-columns: 1fr;
align-content: start;
width: calc(100vw - 3rem);
max-width: 100%;
gap: 1.5rem;
padding: 0;
}
}
#header-menu-toggle {
display: none;
&:not(:checked) ~ .side-menu {
display: none;
}
}
&:has(#header-menu-toggle:checked) #header-menu-toggle-menu {
display: none;
}
&:has(#header-menu-toggle:not(:checked)) #header-menu-toggle-close {
display: none;
}
@media screen and (max-width: 1024px) {
height: 5rem;
padding: 0 0.5rem;
.logo {
padding-left: 0;
img {
height: 3rem;
}
}
}
}
main {
width: 100%;
@media screen and (max-width: 1024px) {
padding: 3rem 1rem;
gap: 3rem;
.card {
width: 100%;
}
}
}
footer {
z-index: 10;
padding: 1rem 0;
--paragraph-margin: 0.5rem;
--zone-color: #aaa;
min-height: 6rem;
border-top: var(--border-large);
background: var(--footer-bg);
color: var(--footer-fg);
display: grid;
place-content: center;
font-family: var(--font-secondary);
font-size: 18px;
scroll-snap-align: end;
@media screen and (max-width: 1024px) {
min-height: 5rem;
font-size: 18px;
}
}
}
::-webkit-scrollbar-track:vertical {
background-color: #f0f0f0;
border-left: 2px solid #222;
border-top: 2px solid #222;
border-bottom: 2px solid #222;
}
::-webkit-scrollbar-track:horizontal {
background-color: #f0f0f0;
border-top: 2px solid #222;
border-left: 2px solid #222;
border-right: 2px solid #222;
}
::-webkit-scrollbar-thumb {
background-color: #1e6733;
border: 2px solid #222;
}
::-webkit-scrollbar-thumb:hover {
background-color: #2b8b47;
}
::-webkit-scrollbar-corner {
background-color: #f0f0f0;
/* border-left: 2px solid #222; */
/* border-top: 2px solid #222; */
}
::-webkit-scrollbar {
width: 15px;
}
::selection {
background: #0002;
}
body:has(#header-menu-toggle:checked) {
overflow: hidden;
}
}
/*
Typography
*/
/* @TODO: SCSS conversion - @import becomes more complex */
/* @import './controls.scss'; */
/* @import './components.scss'; */
/*
Custom Page Styles
*/
/* @TODO: SCSS conversion - @import becomes more complex */
/* @import './pages.scss'; */
@layer utility {
.hide {
display: none !important;
}
.invisible {
opacity: 0 !important;
}
.grid-center {
display: grid;
place-content: center;
place-items: center;
}
.h-flex {
display: flex;
gap: 0.5rem;
flex-direction: row;
}
.v-flex {
display: flex;
gap: 0.5rem;
flex-direction: column;
}
.h-flex,
.v-flex {
place-self: stretch;
align-items: center;
> * {
flex-shrink: 0;
}
> .spacer {
flex-grow: 1;
}
}
@media screen and (min-width: 1024px) {
.mobile-only {
display: none !important;
}
}
@media screen and (max-width: 1024px) {
.desktop-only {
display: none !important;
}
}
}

@ -0,0 +1,315 @@
$screen-desktop-min: 1024px;
:root {
--palette-black: #222;
--border-large: 4px solid var(--palette-black);
--header-bg: #fff;
--footer-bg: #444;
--footer-fg: #fdfdfd;
--homepage-principal-bg: #ecffe3;
--homepage-whatsphc-bg: #e4c5ff;
--homepage-news-bg: #c2a8eb;
--homepage-projects-bg: #f5f2cc;
--homepage-macchinisti-bg: #888;
--project-card-bg: #a2d4f3;
--font-primary: 'Open Sans', sans-serif;
--font-display: 'Iosevka', monospace;
--font-mono: 'Source Code Pro', monospace;
--font-secondary: 'Source Sans Pro', sans-serif;
}
@layer common, typography, component, page;
@import './mixins.scss';
@import './typography.scss';
@layer common {
*,
*::before,
*::after {
box-sizing: border-box;
font: inherit;
margin: 0;
}
html {
height: 100%;
}
html,
body {
min-height: 100%;
margin: 0;
font-family: var(--font-primary);
font-size: 18px;
color: #222;
}
// html {
// scroll-snap-type: y mandatory;
// scroll-padding-top: 4rem;
// }
img {
display: block;
}
a {
color: inherit;
text-decoration: none;
}
}
//
// Typography
//
@import './controls.scss';
@import './components.scss';
//
// Custom Page Styles
//
body {
// for the header spacing
// padding-top: 6rem;
display: grid;
grid-template-rows: auto 1fr auto;
header {
z-index: 10;
height: 6rem;
border-bottom: var(--border-large);
background: var(--header-bg);
grid-column: 1 / -1;
top: 0;
position: sticky;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.logo {
padding-left: 1rem;
img {
height: 3.5rem;
}
}
.links {
display: flex;
align-items: center;
gap: 1.5rem;
padding: 0 1.5rem;
a {
font-family: var(--font-display);
font-size: 18px;
font-weight: 500;
letter-spacing: 1px;
color: #333;
padding: 0.25rem 1.325rem;
}
@media screen and (max-width: $screen-desktop-min) {
flex-direction: column;
}
}
.side-menu {
position: fixed;
// top: 0;
// right: 0;
// bottom: 0;
// left: 3rem;
top: 5rem;
right: 0;
bottom: 0;
left: 0;
background: #f0f0f0;
display: grid;
// grid-template-rows: auto 1fr;
grid-template-rows: 1fr;
gap: 1.5rem;
padding: 1.5rem;
justify-content: center;
// & > :first-child {
// justify-self: end;
// }
.links {
display: grid;
grid-template-columns: 1fr;
align-content: start;
width: calc(100vw - 3rem);
max-width: 100%;
gap: 1.5rem;
padding: 0;
}
}
#header-menu-toggle {
display: none;
&:not(:checked) ~ .side-menu {
display: none;
}
}
&:has(#header-menu-toggle:checked) #header-menu-toggle-menu {
display: none;
}
&:has(#header-menu-toggle:not(:checked)) #header-menu-toggle-close {
display: none;
}
@media screen and (max-width: $screen-desktop-min) {
height: 5rem;
padding: 0 0.5rem;
.logo {
padding-left: 0;
img {
height: 3rem;
}
}
}
}
main {
width: 100%;
@media screen and (max-width: $screen-desktop-min) {
padding: 3rem 1rem;
gap: 3rem;
.card {
width: 100%;
}
}
}
footer {
z-index: 10;
padding: 1rem 0;
--paragraph-margin: 0.5rem;
--zone-color: #aaa;
min-height: 6rem;
border-top: var(--border-large);
background: var(--footer-bg);
color: var(--footer-fg);
display: grid;
place-content: center;
font-family: var(--font-secondary);
font-size: 18px;
scroll-snap-align: end;
@media screen and (max-width: $screen-desktop-min) {
min-height: 5rem;
font-size: 18px;
}
}
}
@import './pages.scss';
//
// Misc
//
::-webkit-scrollbar-track:vertical {
background-color: #f0f0f0;
border-left: 2px solid #222;
border-top: 2px solid #222;
border-bottom: 2px solid #222;
}
::-webkit-scrollbar-track:horizontal {
background-color: #f0f0f0;
border-top: 2px solid #222;
border-left: 2px solid #222;
border-right: 2px solid #222;
}
::-webkit-scrollbar-thumb {
background-color: #1e6733;
border: 2px solid #222;
}
::-webkit-scrollbar-thumb:hover {
background-color: #2b8b47;
}
::-webkit-scrollbar-corner {
background-color: #f0f0f0;
// border-left: 2px solid #222;
// border-top: 2px solid #222;
}
::-webkit-scrollbar {
width: 15px;
}
::selection {
background: #0002;
}
body:has(#header-menu-toggle:checked) {
overflow: hidden;
}
//
// Utility Classes
//
.hide {
display: none !important;
}
.invisible {
opacity: 0 !important;
}
@media screen and (min-width: $screen-desktop-min) {
.mobile-only {
display: none !important;
}
}
@media screen and (max-width: $screen-desktop-min) {
.desktop-only {
display: none !important;
}
}

@ -1,6 +1,4 @@
/* This file is here for historical reasons but is not used anymore */
/* @mixin neo-brutalist-card($size: 3px, $offset: $size + 1, $shadow: true, $hoverable: false) {
@mixin neo-brutalist-card($size: 3px, $offset: $size + 1, $shadow: true, $hoverable: false) {
border: $size solid #222;
border-radius: $size * 2;
@ -16,4 +14,4 @@
box-shadow: $offset + 1 $offset + 1 0 0 #222;
}
}
} */
}

@ -0,0 +1,879 @@
:root {
--guide-base: #a2d4f3;
--guide-darkest: color-mix(in srgb, var(--guide-base), #000 75%);
--guide-darker: color-mix(in srgb, var(--guide-base), #000 50%);
--guide-dark: color-mix(in srgb, var(--guide-base), #000 25%);
--guide-light: color-mix(in srgb, var(--guide-base), #fff 25%);
--guide-lighter: color-mix(in srgb, var(--guide-base), #fff 50%);
--guide-lightest: color-mix(in srgb, var(--guide-base), #fff 75%);
--news-base: #f8e8b1;
}
@layer page {
.homepage {
header:has(#header-menu-toggle:not(:checked)) .logo {
visibility: hidden;
}
section {
position: relative;
width: 100%;
min-height: calc(100vh - 6rem);
&:last-of-type {
min-height: calc(100vh - 10rem);
}
// display: flex;
// flex-direction: column;
// align-items: center;
& {
display: grid;
grid-auto-flow: row;
justify-items: center;
align-content: start;
gap: 2rem;
scroll-snap-align: start;
}
& > .title {
font-size: 48px;
padding-top: 4rem;
@media screen and (max-width: $screen-desktop-min) {
padding-top: 2rem;
}
}
}
.zig-zag {
z-index: 5;
position: absolute;
width: 100%;
display: flex;
&:first-child {
bottom: 100%;
}
}
section.principal {
z-index: 2;
min-height: calc(100vh - 7rem);
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
place-content: center;
gap: 4rem;
padding: 3rem 2rem 6rem;
background: var(--homepage-principal-bg);
position: relative;
.circuit-layer {
pointer-events: none;
z-index: 1;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
canvas {
width: 100%;
height: 100%;
}
}
& > .logo {
z-index: 2;
width: 32rem;
position: relative;
user-select: none;
img {
width: 100%;
// filter: drop-shadow(6px 6px 0 var(--palette-black))
// drop-shadow(4px 0 0 var(--palette-black)) drop-shadow(0 4px 0 var(--palette-black))
// drop-shadow(-4px 0 0 var(--palette-black)) drop-shadow(0 -4px 0 var(--palette-black));
}
max-width: calc(100vw - 3rem);
@media screen and (max-width: $screen-desktop-min) {
}
}
& > .whats-phc {
z-index: 2;
background: #e4c5ff;
--zone-color: color-mix(in lab, #e4c5ff, #000 75%);
max-width: 37rem;
.title {
text-align: center;
}
a {
text-decoration: underline 2px solid;
&:hover {
--zone-color: color-mix(in lab, #e4c5ff, #000 60%);
}
}
span.highlighted {
background: color-mix(in lab, #e4c5ff, #000 10%);
cursor: default;
border-radius: 0.25rem;
padding: 0.125rem 0.25rem;
}
}
}
section.news {
--card-base: color-mix(in lab, var(--news-base), #fff 25%);
--zone-color: color-mix(in lab, var(--news-base), #000 50%);
background: var(--homepage-news-bg);
gap: 3rem;
padding-bottom: 6rem;
& > .news-list {
display: flex;
flex-direction: row;
gap: 3rem;
align-items: flex-start;
padding: 0 3rem;
justify-content: center;
flex-wrap: wrap;
}
[role='button'] {
padding: 0.5rem 2rem;
&.primary {
background: #ffdd6e;
color: #000d;
}
}
}
section.projects {
background: var(--homepage-projects-bg);
padding-bottom: 6rem;
.project-list {
// width: calc(20rem * 3 + 1.5rem * 2 + 6rem * 2);
// max-width: calc(100vw - 2rem); // HACK: capire come si propagano le width per bene
max-width: calc(20rem * 3 + 1.5rem * 2 + 1rem * 2);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
grid-auto-flow: dense;
gap: 1.5rem;
padding: 0 1rem;
& > * {
grid-row: span var(--masonry-height);
}
.project {
width: 100%;
height: 100%;
// background: #fcddff;
// background: #ffa89c;
background: var(--card-bg, var(--project-card-bg));
color: var(--card-fg, #000e);
@include neo-brutalist-card($size: 3px, $offset: 9px);
padding: 1rem;
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr;
gap: 0.25rem 1rem;
transition: all 128ms ease-out;
.title {
font-size: 32px;
@media screen and (max-width: $screen-desktop-min) {
font-size: 24px;
}
}
.image {
grid-row: span 2;
// place-self: center;
.box {
background: #0003;
border: 3px solid #0006;
border-radius: 6px;
width: 4rem;
height: 4rem;
}
img {
object-fit: cover;
width: 4rem;
}
// &.auto {
// img {
// width: auto;
// height: auto;
// }
// }
}
.description {
font-size: 16px;
@extend .text;
}
&:hover {
transform: translate(0, -4px);
box-shadow: 9px 13px 0 0 #222;
}
}
@media screen and (max-width: $screen-desktop-min) {
padding: 0 1rem;
grid-template-columns: 1fr;
.project {
padding: 0.8rem;
}
}
}
}
section.wanna-be-macchinista {
background: var(--homepage-macchinisti-bg);
color: #fdfdfd;
padding-bottom: 6rem;
.card {
max-width: 40rem;
}
.content {
@extend .text;
}
}
@media screen and (max-width: $screen-desktop-min) {
& > main {
padding: 0 !important;
}
section.principal {
padding: 3rem 0 6rem;
.whats-phc {
padding: 1.5rem;
margin: 0 1rem;
}
#mobile-whats-phc-read-more {
&:checked ~ .text {
max-height: 7lh;
overflow: hidden;
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 3lh;
background: linear-gradient(to bottom, transparent, #e4c5ff);
}
}
&:not(:checked) ~ * .button span:nth-child(1) {
display: none;
}
&:checked ~ * .button span:nth-child(2) {
display: none;
}
}
}
section.news {
& > .news-list {
padding: 0 1rem;
}
}
section.projects {
.project-list {
padding: 0 1rem;
}
}
section.wanna-be-macchinista {
.content {
padding: 0 1rem;
}
}
section {
padding: 1rem 1rem 4rem;
}
}
}
.utenti {
background: #ffffe4;
--filter-bg-color: #ffd270;
main {
justify-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 5rem 0;
gap: 5rem;
max-width: 80ch;
.search-result {
background: #ffeabc;
}
button {
background: #ffd270;
}
}
}
.appunti {
main {
background: #e8e8e8;
justify-self: center;
display: grid;
grid-auto-flow: row;
justify-items: center;
padding: 3rem;
gap: 3rem;
width: 100%;
position: relative;
&::after {
content: '';
width: 100%;
height: 80vh;
background: linear-gradient(to bottom, transparent, #e8e8e8);
position: absolute;
bottom: 0;
left: 0;
}
.search {
max-width: 80ch;
}
.appunti-scrollable {
justify-self: stretch;
&.center {
justify-self: center;
}
}
}
}
.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;
// main {
// justify-self: center;
// display: flex;
// flex-direction: column;
// align-items: center;
// max-width: 80ch;
// padding: 3rem 0;
// gap: 3rem;
// h3 {
// font-size: 28px;
// font-weight: 600;
// }
// }
// }
//
// Notizie
//
.notizie,
.notizia {
--card-base: color-mix(in lab, var(--news-base), #fff 25%);
--zone-color: color-mix(in lab, var(--news-base), #000 75%);
background: color-mix(in lab, var(--news-base), #fff 90%);
}
.notizie {
h1 > a {
&:hover {
text-decoration: underline 3px solid;
}
}
main {
justify-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 5rem;
gap: 5rem;
}
}
.notizia {
main {
--card-base: color-mix(in lab, var(--news-base), #fff 25%);
max-width: calc(46rem + 2rem * 2);
justify-self: center;
padding: 3rem 2rem 2rem;
margin-top: 3rem;
margin-bottom: 6rem;
@media screen and (max-width: $screen-desktop-min) {
box-shadow: none;
border: none;
border-radius: 0;
max-width: none;
margin: 0 auto;
}
}
}
//
// Guide
//
.guide,
.guida {
--card-base: color-mix(in lab, var(--guide-base), #fff 25%);
--zone-color: color-mix(in lab, var(--guide-base), #000 75%);
background: color-mix(in lab, var(--guide-base), #fff 90%);
}
.guide {
h1 > a {
&:hover {
text-decoration: underline 3px solid;
}
}
main {
justify-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 5rem;
gap: 5rem;
}
}
.guida {
main {
--card-base: color-mix(in lab, var(--guide-base), #fff 50%);
max-width: calc(46rem + 2rem * 2);
justify-self: center;
padding: 3rem 2rem 2rem;
margin-top: 3rem;
margin-bottom: 6rem;
position: relative;
.metadata {
position: absolute;
top: -2px;
left: calc(100% + 2rem);
width: 15rem;
display: grid;
grid-auto-flow: row;
gap: 1rem;
justify-items: start;
.metadata-item {
background: var(--card-base);
display: grid;
grid-template-rows: auto auto;
justify-items: start;
gap: 0.25rem;
padding: 0.75rem 1rem;
@include neo-brutalist-card($size: 3px);
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
}
}
@media screen and (max-width: $screen-desktop-min) {
box-shadow: none;
border: none;
border-radius: 0;
max-width: none;
margin: 0 auto;
.metadata {
position: static;
padding: 0;
width: auto;
margin: 0;
--card-base: color-mix(in lab, var(--guide-base), #fff 30%);
}
}
}
}
.tag {
main {
justify-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 5rem 0;
gap: 5rem;
}
}
.storia {
--card-base: #e4c5ff;
main {
justify-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 5rem 0;
gap: 3rem;
// background horizontal linear gradient that is black in the center
background: linear-gradient(
to right,
#ffe4c544 0%,
// #ffe4c599 25%,
#ffe4c5ff 50%,
// #ffe4c599 75%,
#ffe4c544 100%
);
}
}
.macchinisti {
main {
justify-self: center;
background: linear-gradient(to top, #d5fbff, #ffd9d5);
display: flex;
flex-direction: column;
align-items: center;
padding: 4.5rem 3rem;
gap: 4.5rem;
}
}
//
// Meta
//
.design {
grid-template-columns: minmax(15rem, 2fr) 10fr;
@media screen and (max-width: 1400px) {
grid-template-columns: 1fr;
}
aside {
margin: 1rem;
padding: 1rem;
@include neo-brutalist-card();
background: #f0f0f0;
align-self: start;
position: sticky;
top: 7rem;
height: calc(100dvh - 8rem - 6rem);
nav {
display: flex;
flex-direction: column;
gap: 0.25rem;
ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
li {
padding-left: calc((var(--depth) - 1) * 1rem);
display: flex;
a {
display: block;
font-size: 16px;
font-weight: 600;
transform: translate(-0.25rem, 0);
padding: 0.125rem 0.25rem;
border-radius: 0.125rem;
color: #444;
&:hover {
background: #00000018;
}
}
}
}
}
@media screen and (max-width: 1400px) {
display: none;
}
}
footer {
grid-column: 1 / -1;
}
main {
justify-self: center;
@media screen and (min-width: $screen-desktop-min) {
padding: 3rem 0;
}
}
pre[data-language='astro'] {
--code-bg: #fff7ef;
width: 100%;
max-width: 46rem;
}
.container {
margin: 2rem auto;
border: 2px dashed #ddd;
position: relative;
z-index: 1;
&.large {
min-width: calc(100% - 4rem);
}
&:not(.large) {
& > .content {
display: grid;
place-content: center;
}
}
& > .content {
padding: 2rem;
overflow: auto;
}
// label in the top left corner
&::before {
content: 'Preview';
position: absolute;
bottom: 100%;
left: 0;
padding: 0.125rem 0.5rem;
background: #eee;
color: #000;
font-family: var(--font-display);
font-size: 14px;
border-radius: 0.25rem;
z-index: -1;
transform: translate(-2px, -4px);
opacity: 0;
transition: opacity 64ms ease-in;
}
&:hover {
border-color: #bbb;
&::before {
opacity: 1;
}
}
}
.palette {
margin: 2rem auto;
display: grid;
grid-template-columns: auto auto;
gap: 1rem;
place-content: center;
& > .color {
width: 2rem;
height: 2rem;
border-radius: 0.25rem;
border: 2px solid #000;
box-shadow: 4px 4px 0 0 #000;
overflow: hidden;
& > .region {
width: 100%;
height: 100%;
border: 2px solid #fff;
border-radius: 4px;
}
}
& > .label {
display: grid;
align-content: center;
font-family: 'JetBrains Mono', var(--font-mono);
font-size: 16px;
user-select: all;
}
}
}
}

@ -1,55 +0,0 @@
@layer page {
main {
background: #e8e8e8;
justify-self: center;
display: grid;
grid-auto-flow: row;
justify-items: center;
padding: 3rem;
gap: 3rem;
width: 100%;
position: relative;
&::after {
content: '';
width: 100%;
height: 80vh;
background: linear-gradient(to bottom, transparent, #e8e8e8);
position: absolute;
bottom: 0;
left: 0;
}
.search {
max-width: 80ch;
}
.appunti-scrollable {
justify-self: stretch;
&.center {
justify-self: center;
}
}
@media screen and (max-width: 1024px) {
padding: 3rem 1rem;
gap: 3rem;
overflow: clip;
.card {
width: 100%;
}
}
}
}

@ -1,42 +0,0 @@
:root {
--card-base: hsl(180, 100%, 85%);
}
@layer page {
body {
background: hsl(180, 100%, 95%);
}
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%);
}
@media screen and (max-width: 1024px) {
padding: 3rem 1rem;
gap: 3rem;
.card {
width: 100%;
}
}
}
}

@ -1,74 +0,0 @@
@layer page {
body {
--card-base: color-mix(in lab, var(--guide-base), #fff 25%);
--zone-color: color-mix(in lab, var(--guide-base), #000 75%);
background: color-mix(in lab, var(--guide-base), #fff 90%);
}
main {
--card-base: color-mix(in lab, var(--guide-base), #fff 50%);
max-width: calc(46rem + 2rem * 2);
justify-self: center;
padding: 3rem 2rem 2rem;
margin-top: 3rem;
margin-bottom: 6rem;
position: relative;
.metadata {
position: absolute;
top: -2px;
left: calc(100% + 2rem);
width: 15rem;
display: grid;
grid-auto-flow: row;
gap: 1rem;
justify-items: start;
.metadata-item {
background: var(--card-base);
display: grid;
grid-template-rows: auto auto;
justify-items: start;
gap: 0.25rem;
padding: 0.75rem 1rem;
/* @include neo-brutalist-card($size: 3px); */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 3px 3px 0 0 #222;
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
}
}
@media screen and (max-width: 1024px) {
box-shadow: none;
border: none;
border-radius: 0;
max-width: none;
margin: 0 auto;
padding: 3rem 1rem 2rem;
.metadata {
position: static;
padding: 0;
width: auto;
margin: 0;
--card-base: color-mix(in lab, var(--guide-base), #fff 30%);
}
}
}
}

@ -1,37 +0,0 @@
@layer page {
body {
--card-base: color-mix(in lab, var(--guide-base), #fff 25%);
--zone-color: color-mix(in lab, var(--guide-base), #000 75%);
background: color-mix(in lab, var(--guide-base), #fff 90%);
}
h1 > a {
&:hover {
text-decoration: underline 3px solid;
}
}
main {
justify-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 5rem 2.5rem;
gap: 5rem;
@media screen and (max-width: 1024px) {
padding: 3rem 1rem;
gap: 3rem;
.card {
width: 100%;
}
}
}
.card {
grid-template-rows: auto 1fr auto;
}
}

@ -1,354 +0,0 @@
@layer page {
header:has(#header-menu-toggle:not(:checked)) .logo {
visibility: hidden;
}
section {
position: relative;
width: 100%;
min-height: calc(100vh - 6rem);
&:last-of-type {
min-height: calc(100vh - 10rem);
}
& {
display: grid;
grid-auto-flow: row;
justify-items: center;
align-content: start;
gap: 2rem;
scroll-snap-align: start;
}
& > .title {
font-size: 48px;
padding-top: 4rem;
@media screen and (max-width: /* $screen-desktop-min */ 1024px) {
padding-top: 2rem;
}
}
}
.zig-zag {
z-index: 5;
position: absolute;
width: 100%;
display: flex;
&:first-child {
bottom: 100%;
}
}
section.principal {
z-index: 2;
min-height: calc(100vh - 7rem);
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
place-content: center;
gap: 4rem;
padding: 3rem 2rem 6rem;
background: var(--homepage-principal-bg);
position: relative;
.circuit-layer {
pointer-events: none;
z-index: 1;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
canvas {
width: 100%;
height: 100%;
}
}
& > .logo {
z-index: 2;
width: 32rem;
position: relative;
user-select: none;
img {
width: 100%;
/* filter: drop-shadow(6px 6px 0 var(--palette-black))
drop-shadow(4px 0 0 var(--palette-black)) drop-shadow(0 4px 0 var(--palette-black))
drop-shadow(-4px 0 0 var(--palette-black)) drop-shadow(0 -4px 0 var(--palette-black)); */
}
max-width: calc(100vw - 3rem);
@media screen and (max-width: /* $screen-desktop-min */ 1024px) {
}
}
& > .whats-phc {
z-index: 2;
background: #e4c5ff;
--zone-color: color-mix(in lab, #e4c5ff, #000 75%);
max-width: 37rem;
.title {
text-align: center;
}
a {
text-decoration: underline 2px solid;
&:hover {
--zone-color: color-mix(in lab, #e4c5ff, #000 60%);
}
}
span.highlighted {
background: color-mix(in lab, #e4c5ff, #000 10%);
cursor: default;
border-radius: 0.25rem;
padding: 0.125rem 0.25rem;
}
}
}
section.news {
--card-base: color-mix(in lab, var(--news-base), #fff 25%);
--zone-color: color-mix(in lab, var(--news-base), #000 50%);
background: var(--homepage-news-bg);
gap: 3rem;
padding-bottom: 6rem;
& > .news-list {
display: flex;
flex-direction: row;
gap: 3rem;
align-items: flex-start;
padding: 0 3rem;
justify-content: center;
flex-wrap: wrap;
}
[role='button'] {
padding: 0.5rem 2rem;
&.primary {
background: #ffdd6e;
color: #000d;
}
}
.card {
grid-template-rows: auto auto 1fr;
}
}
section.projects {
background: var(--homepage-projects-bg);
padding-bottom: 6rem;
.project-list {
/* width: calc(20rem * 3 + 1.5rem * 2 + 6rem * 2);
max-width: calc(100vw - 2rem); */ /* HACK: capire come si propagano le width per bene */
max-width: calc(20rem * 3 + 1.5rem * 2 + 1rem * 2);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
grid-auto-flow: dense;
gap: 1.5rem;
padding: 0 1rem;
& > * {
grid-row: span var(--masonry-height);
}
.project {
width: 100%;
height: 100%;
/* background: #fcddff;
background: #ffa89c; */
background: var(--card-bg, var(--project-card-bg));
color: var(--card-fg, #000e);
/* @include neo-brutalist-card($size: 3px, $offset: 9px); */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 9px 9px 0 0 #222;
padding: 1rem;
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr;
gap: 0.25rem 1rem;
transition: all 128ms ease-out;
.title {
font-size: 32px;
@media screen and (max-width: /* $screen-desktop-min */ 1024px) {
font-size: 24px;
}
}
.image {
grid-row: span 2;
/* place-self: center; */
.box {
background: #0003;
border: 3px solid #0006;
border-radius: 6px;
width: 4rem;
height: 4rem;
}
img {
object-fit: cover;
width: 4rem;
}
/* &.auto {
img {
width: auto;
height: auto;
}
} */
}
.description {
font-size: 16px;
}
&:hover {
transform: translate(0, -4px);
box-shadow: 9px 13px 0 0 #222;
}
}
@media screen and (max-width: /* $screen-desktop-min */ 1024px) {
padding: 0 1rem;
grid-template-columns: 1fr;
.project {
padding: 0.8rem;
}
}
}
}
section.wanna-be-macchinista {
background: var(--homepage-macchinisti-bg);
color: #fdfdfd;
padding-bottom: 6rem;
.card {
max-width: 40rem;
}
/* .content {
/* @extend .text;
/* Placeholder: Assuming .text is a class with common text styles, you might want to define those styles directly here or in a separate CSS rule.
} */
}
@media screen and (max-width: /* $screen-desktop-min */ 1024px) {
main {
padding: 0 !important;
}
section.principal {
padding: 3rem 0 6rem;
.whats-phc {
padding: 1.5rem;
margin: 0 1rem;
}
#mobile-whats-phc-read-more {
&:checked ~ .text {
max-height: 7lh;
overflow: hidden;
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 3lh;
background: linear-gradient(to bottom, transparent, #e4c5ff);
}
}
&:not(:checked) ~ * .button span:nth-child(1) {
display: none;
}
&:checked ~ * .button span:nth-child(2) {
display: none;
}
}
}
section.news {
& > .news-list {
padding: 0 1rem;
}
}
section.projects {
.project-list {
padding: 0 1rem;
}
}
section.wanna-be-macchinista {
.content {
padding: 0 1rem;
}
}
section {
padding: 1rem 1rem 4rem;
}
}
}

@ -1,22 +0,0 @@
@layer page {
main {
justify-self: center;
background: linear-gradient(to top, #d5fbff, #ffd9d5);
display: flex;
flex-direction: column;
align-items: center;
padding: 4.5rem 3rem;
gap: 4.5rem;
@media screen and (max-width: 1024px) {
padding: 3rem 1rem;
gap: 3rem;
.card {
width: 100%;
}
}
}
}

File diff suppressed because it is too large Load Diff

@ -1,190 +0,0 @@
@layer page {
body {
grid-template-columns: minmax(15rem, 2fr) 10fr;
@media screen and (max-width: 1400px) {
grid-template-columns: 1fr;
}
}
aside {
margin: 1rem;
padding: 1rem;
/* @include neo-brutalist-card(); */
border: 3px solid #222;
border-radius: 6px;
box-shadow: 3px 3px 0 0 #222;
background: #f0f0f0;
align-self: start;
position: sticky;
top: 7rem;
height: calc(100dvh - 8rem - 6rem);
nav {
display: flex;
flex-direction: column;
gap: 0.25rem;
ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
li {
padding-left: calc((var(--depth) - 1) * 1rem);
display: flex;
a {
display: block;
font-size: 16px;
font-weight: 600;
transform: translate(-0.25rem, 0);
padding: 0.125rem 0.25rem;
border-radius: 0.125rem;
color: #444;
&:hover {
background: #00000018;
}
}
}
}
}
@media screen and (max-width: 1400px) {
display: none;
}
}
footer {
grid-column: 1 / -1;
}
main {
justify-self: center;
@media screen and (min-width: /* $screen-desktop-min */ 1024px) {
padding: 3rem 0;
}
}
pre[data-language='astro'] {
--code-bg: #fff7ef;
width: 100%;
max-width: 46rem;
}
.container {
margin: 2rem auto;
border: 2px dashed #ddd;
position: relative;
z-index: 1;
&.large {
min-width: calc(100% - 4rem);
}
&:not(.large) {
& > .content {
display: grid;
place-content: center;
}
}
& > .content {
padding: 2rem;
overflow: auto;
}
/* label in the top left corner */
&::before {
content: 'Preview';
position: absolute;
bottom: 100%;
left: 0;
padding: 0.125rem 0.5rem;
background: #eee;
color: #000;
font-family: var(--font-display);
font-size: 14px;
border-radius: 0.25rem;
z-index: -1;
transform: translate(-2px, -4px);
opacity: 0;
transition: opacity 64ms ease-in;
}
&:hover {
border-color: #bbb;
&::before {
opacity: 1;
}
}
}
.palette {
margin: 2rem auto;
display: grid;
grid-template-columns: auto auto;
gap: 1rem;
place-content: center;
& > .color {
width: 2rem;
height: 2rem;
border-radius: 0.25rem;
border: 2px solid #000;
box-shadow: 4px 4px 0 0 #000;
overflow: hidden;
& > .region {
width: 100%;
height: 100%;
border: 2px solid #fff;
border-radius: 4px;
}
}
& > .label {
display: grid;
align-content: center;
font-family: 'JetBrains Mono', var(--font-mono);
font-size: 16px;
user-select: all;
}
}
}

@ -1,28 +0,0 @@
@layer page {
:root {
--card-base: color-mix(in lab, var(--news-base), #fff 25%);
--zone-color: color-mix(in lab, var(--news-base), #000 75%);
}
body {
background: color-mix(in lab, var(--news-base), #fff 90%);
}
main {
max-width: calc(46rem + 2rem * 2);
justify-self: center;
padding: 3rem 2rem 2rem;
margin-top: 3rem;
margin-bottom: 6rem;
@media screen and (max-width: 1024px) {
box-shadow: none;
border: none;
border-radius: 0;
max-width: none;
margin: 0 auto;
}
}
}

@ -1,40 +0,0 @@
@layer page {
:root {
--card-base: color-mix(in lab, var(--news-base), #fff 25%);
--zone-color: color-mix(in lab, var(--news-base), #000 75%);
}
body {
background: color-mix(in lab, var(--news-base), #fff 90%);
}
h1 > a {
&:hover {
text-decoration: underline 3px solid;
}
}
main {
justify-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 5rem;
gap: 5rem;
@media screen and (max-width: 1024px) {
padding: 3rem 1rem;
gap: 3rem;
.card {
width: 100%;
}
}
}
.card {
grid-template-rows: auto auto 1fr;
}
}

@ -1,33 +0,0 @@
:root {
--card-base: #e4c5ff;
}
@layer page {
main {
justify-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 5rem 0;
gap: 3rem;
/* background horizontal linear gradient that is black in the center */
background: linear-gradient(
to right,
#ffe4c544 0%,
/* #ffe4c599 25%, */ #ffe4c5ff 50%,
/* #ffe4c599 75%, */ #ffe4c544 100%
);
@media screen and (max-width: 1024px) {
padding: 3rem 1rem;
gap: 3rem;
.card {
width: 100%;
}
}
}
}

@ -1,40 +0,0 @@
:root {
--filter-bg-color: #ffd270;
}
@layer page {
body {
background: #ffffe4;
}
main {
justify-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 5rem 0;
gap: 5rem;
max-width: 80ch;
.search-result {
background: #ffeabc;
}
button {
background: #ffd270;
}
@media screen and (max-width: 1024px) {
padding: 3rem 1rem;
gap: 3rem;
.card {
width: 100%;
}
}
}
}

@ -1,63 +1,53 @@
@layer typography {
:root {
--heading-base-size: 16px;
--heading-factor: 1.25;
}
@function pow($number, $exponent) {
$value: 1;
strong {
font-weight: 600;
@if $exponent > 0 {
@for $i from 1 through $exponent {
$value: $value * $number;
}
}
em {
font-style: italic;
}
@return $value;
}
.text-center {
text-align: center;
}
@mixin geometric-headings {
$base-font-size: 16px;
$heading-scale: 1.25;
h1 {
font-size: calc(var(--heading-base-size) * pow(var(--heading-factor), 4));
}
@for $i from 1 through 5 {
$factor: pow($heading-scale, 5 - $i);
h2 {
font-size: calc(var(--heading-base-size) * pow(var(--heading-factor), 3));
}
h#{$i} {
font-size: $base-font-size * $factor;
font-family: var(--font-display);
font-weight: 700;
margin-bottom: 0.25rem;
}
h3 {
font-size: calc(var(--heading-base-size) * pow(var(--heading-factor), 2));
// p + h#{$i} {
// margin-top: 0.75rem * $factor;
// }
}
}
@layer typography {
@include geometric-headings;
h4 {
font-size: calc(var(--heading-base-size) * pow(var(--heading-factor), 1));
strong {
font-weight: 600;
}
h5 {
font-size: calc(var(--heading-base-size) * pow(var(--heading-factor), 0));
em {
font-style: italic;
}
h1,
h2,
h3,
h4,
h5 {
font-family: var(--font-display);
font-weight: 700;
.text-center {
text-align: center;
}
.text {
h1,
h2,
h3,
h4,
h5 {
font-family: var(--font-display);
font-weight: 700;
margin-bottom: 0.25rem;
}
/* text-align: justify;
hyphens: auto; */
// text-align: justify;
// hyphens: auto;
&.center {
text-align: center;
@ -74,8 +64,8 @@
pre,
code {
background: color-mix(in lab, var(--card-base-internal, #ededed), #fff 35%) !important;
/* background: color-mix(in lab, var(--zone-color), #fff 75%) !important;
background: var(--code-bg, #00000022) !important; */
// background: color-mix(in lab, var(--zone-color), #fff 75%) !important;
// background: var(--code-bg, #00000022) !important;
font-family: var(--font-mono);
font-weight: 400;
@ -87,16 +77,12 @@
padding: 0.125rem 0.3rem;
}
p > code {
word-break: break-word;
}
pre {
margin: 2rem auto;
padding: 0.5rem 1rem;
/* width: 100%; */
// width: 100%;
max-width: 80ch;
width: fit-content;
@ -111,6 +97,11 @@
code {
padding: 0;
}
@media screen and (max-width: $screen-desktop-min) {
width: calc(100vw - 2rem);
max-width: none;
}
}
p {
@ -118,26 +109,26 @@
margin: var(--paragraph-margin, 1rem) auto;
}
/* p + p {
margin-top: 1rem;
}
// p + p {
// margin-top: 1rem;
// }
h1 + p,
h2 + p,
h3 + p,
h4 + p {
margin-top: 1rem;
}
// h1 + p,
// h2 + p,
// h3 + p,
// h4 + p {
// margin-top: 1rem;
// }
p:has(+ h1, + h2, + h3, + h4) {
margin-bottom: 1rem;
} */
// p:has(+ h1, + h2, + h3, + h4) {
// margin-bottom: 1rem;
// }
p[align='center'] {
margin: 1.5rem 0;
a {
/* background: color-mix(in hsl, var(--card-base-internal, #ededed), #fff 20%); */
// background: color-mix(in hsl, var(--card-base-internal, #ededed), #fff 20%);
background: hsl(from var(--card-base-internal, #ededed) h calc(s + 10) calc(l - 10));
padding: 0.5rem;
border-radius: 0.25rem;
@ -146,18 +137,14 @@
}
}
img,
video {
img {
display: block;
margin: 0 auto;
width: 50ch;
max-width: 100%;
border: 2px solid #333;
border-radius: 0.25rem;
box-shadow: 0.25rem 0.25rem 0 0 #333;
background: #000;
@include neo-brutalist-card(2px);
&.fill {
width: 100%;
@ -174,10 +161,6 @@
}
}
video {
margin: 1rem auto;
}
p:first-child {
margin-top: 0;
}
@ -194,22 +177,7 @@
font-style: italic;
}
/* @include geometric-headings; */
/* Inlined geometric-headings mixin */
/* @for $i from 1 through 5 {
$factor: pow($heading-scale, 5 - $i);
h#{$i} {
font-size: $base-font-size * $factor;
font-family: var(--font-display);
font-weight: 700;
margin-bottom: 0.25rem;
}
/* p + h#{$i} {
margin-top: 0.75rem * $factor;
}
} */
@include geometric-headings;
h1 {
margin-bottom: 2rem;
@ -223,7 +191,7 @@
font-weight: 700;
color: #333;
/* HACK: Trick to fix anchor links with sticky header */
// HACK: Trick to fix anchor links with sticky header
padding-top: 7rem;
margin-top: -6.5rem;
}
@ -282,17 +250,9 @@
border-top: 2px solid #0003;
}
@media screen and (max-width: 1024px) {
max-width: calc(100vw - 2rem);
pre {
margin: 1rem 0;
width: 100%;
}
hr {
max-width: none;
margin: 3rem auto;
@media screen and (max-width: $screen-desktop-min) {
& > * {
margin: 0 0.75rem;
}
}
}
Loading…
Cancel
Save