LA BASSA DE LA FONT PUDOSA A uns quilòmetres del nucli d'Amer i a poca distància a peu de l'embotelladora Fonter, en una zona boscosa, hi ha la Font Pudosa. Tothom, a Amer, en coneix l'existència, però la bassa no està indicada, ni sembla que tingui cap interès especial per als vilatans. Quan se'ls ho pregunta, tothom sap que és per aquella zona, però no en fan cas. La Font Pudosa no és més que una bassa però no deixa de tenir una certa fascinació. En el silenci del bosc, només se sent el mormoleig de les bombolles de diòxid de carboni (CO2) i radó en trencar-se a la superfície de l'aigua de la bassa. El CO2, sobretot, és un indicador que encara hi ha activitat sísmica a gran profunditat, i un testimoni que Amer qualsevol dia hi pot tornar a haver els terratrèmols i rèpliques que es van viure a la zona el 1427. M. Faura i Sans, a Sismologia catalana, va explicar així els moviments sísmics d'Amer: “Una repercussió del més extraordinari i impertinent terrabastall sísmic que afecta la regió volcànica olotina se sentí i ressonà a Barcelona, segons ho testifiquen les cròniques d'aquell any de desolació en què els volcans escumejaren flames i s'enderrocaren alguns pobles de la província de Girona. Aquells terratrèmols duraren prop de tres mesos, des del 23 de febrer fins al 15 de maig.” Un equip de geòlegs de la Universitat Autònoma de Barcelona (UAB) ha mesurat l'emanació de tres gasos (radó, toró i diòxid de carboni) de diversos punts d'Amer com a metodologia per poder determinar el grau d'activitat sísmica de la zona, ja que aquests gasos es poden haver generat a diversos quilòmetres de profunditat. Amer encara és una zona sísmica activa. Forma part de la falla de l'Empordà, de 30 quilòmetres, i Amer n'és al mig (arriba fins a Campdevànol). Pot haver-hi altres gasos a les zones estudiades, però els geòlegs de la UAB s'han centrat en el radó, el toró i el CO2. “Si rebem aquests gasos en superfície és un indicador que la fractura està oberta i té potencial de continuar-se movent". El CO2, concretament, és un gas que denota que hi ha activitat a molta fondària, després vindria el radó i en un tercer lloc, el toró. La concentració de gasos a la Font Pudosa es percep fàcilment, no només visualment, i pot arribar a marejar si s'hi està una estona a prop, però no és perillosa ja que és en un espai obert i ventilat. Una altra zona on abans també es concentraven gasos era a la Font Picant, que es troba a uns 2 km al nord del poble, al veïnat del Colomer. L'exalcalde d'Amer i estudiós del tema, Josep Puigdemont, explica com a anècdota que fa anys cada dia es trobava algun ocell mort a la zona enclotada de la Font Picant, a causa del CO2, que els estabornia i no tenien forces per tornar a emprendre el vol. “Cada matí, la dona que s'encarregava de la font treia els ocells morts”, explica. També recorda que quan era jove, era costum que els excursionistes que s'acostaven a la Font Pudosa en beguessin un got d'aigua. “Deien que netejava la sang”, recorda. Ara aquest costum s'ha abandonat del tot. Malgrat els estudis, és molt difícil saber quan es pot tornar a produir un terratrèmol d'una magnitud semblant al del 1427. “No és com a la zona de Califòrnia, a Catalunya els terratrèmols vénen molt espaiats, no és una zona sísmicament tan activa, i si els terratrèmols grans vénen cada mil anys, no en tenim cap registre”. Tot i que pot semblar que el segle XV queda molt lluny, en termes geològics, és molt poc temps.
<!doctype html>
<html lang="ca">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"/>
<title>Tetris</title>
<style>
:root{
--bg:#0f0f12; --panel:#17181d; --ink:#e8e8ef; --muted:#9196a6; --accent:#7bd88f; --warn:#ffbe55;
}
*{box-sizing:border-box; font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif}
body{margin:0; background:linear-gradient(180deg,#0b0c10,#101217 40%,#0f0f12)}
.wrap{display:flex; gap:16px; padding:16px; max-width:980px; margin:0 auto; align-items:flex-start}
.board{background:#0b0c10; border-radius:12px; padding:12px; box-shadow:0 10px 30px rgba(0,0,0,.35), inset 0 0 0 1px rgba(255,255,255,.06)}
canvas{display:block; width:100%; height:auto; image-rendering:pixelated}
.side{flex:1; display:grid; gap:12px}
.card{background:var(--panel); color:var(--ink); border-radius:12px; padding:12px; box-shadow:inset 0 0 0 1px rgba(255,255,255,.06)}
h1{color:var(--ink); font-size:20px; margin:0 0 4px}
.meta{display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-top:6px}
.meta div{background:#1d1f27; padding:10px; border-radius:10px; text-align:center}
.label{font-size:11px; color:var(--muted)}
.value{font-size:18px; font-weight:700}
.nextHold{display:flex; gap:12px}
.mini{flex:1}
.miniCanvas{width:100%; background:#0b0c10; border-radius:8px; aspect-ratio:1/1; display:block}
.keys{font-size:12px; color:var(--muted); line-height:1.5}
kbd{background:#1e2230; border:1px solid rgba(255,255,255,.08); border-bottom-width:2px; padding:2px 6px; border-radius:6px; color:#e9ecff}
.row{display:flex; gap:6px; flex-wrap:wrap}
.btns{display:none}
.btn{flex:1; background:#1e2230; color:#e9ecff; border:none; border-radius:10px; padding:12px; font-weight:700}
.btn:active{transform:translateY(1px)}
.footer{color:var(--muted); font-size:11px; text-align:center; margin-top:4px}
@media (max-width:900px){
.wrap{flex-direction:column; padding:12px}
.btns{display:grid; gap:8px; grid-template-columns:1fr 1fr 1fr}
.board{order:1}
.side{order:2}
}
</style>
</head>
<body>
<div class="wrap">
<div class="board">
<canvas id="game" width="300" height="600" aria-label="Tetris"></canvas>
<div class="btns" aria-hidden="false" role="group" aria-label="Controls tàctils">
<button class="btn" id="left">◀︎</button>
<button class="btn" id="down">▼</button>
<button class="btn" id="right">▶︎</button>
<button class="btn" id="rotL">Gira ⤺</button>
<button class="btn" id="hard">Hard ⤓</button>
<button class="btn" id="rotR">Gira ⤼</button>
</div>
<div class="footer">P: pausa · R: reinicia</div>
</div>
<div class="side">
<div class="card">
<h1>Tetris</h1>
<div class="meta">
<div><div class="label">Puntuació</div><div id="score" class="value">0</div></div>
<div><div class="label">Nivell</div><div id="level" class="value">1</div></div>
<div><div class="label">Línies</div><div id="lines" class="value">0</div></div>
<div><div class="label">Estat</div><div id="state" class="value">Jugant</div></div>
</div>
</div>
<div class="card nextHold">
<div class="mini">
<div class="label">Següent</div>
<canvas id="next" class="miniCanvas" width="120" height="120"></canvas>
</div>
<div class="mini">
<div class="label">Hold (C)</div>
<canvas id="hold" class="miniCanvas" width="120" height="120"></canvas>
</div>
</div>
<div class="card">
<div class="keys">
<div class="row"><kbd>◀︎/▶︎</kbd> moure · <kbd>▼</kbd> baixar</div>
<div class="row"><kbd>Z</kbd>/<kbd>X</kbd> girar · <kbd>Espai</kbd> hard drop</div>
<div class="row"><kbd>C</kbd> hold · <kbd>P</kbd> pausa · <kbd>R</kbd> reinicia</div>
</div>
</div>
</div>
</div>
<script>
(() => {
// --- Paràmetres bàsics ---
const COLS = 10, ROWS = 20, CELL = 30;
const CANVAS = document.getElementById('game');
const CTX = CANVAS.getContext('2d');
const NEXT = document.getElementById('next').getContext('2d');
const HOLD = document.getElementById('hold').getContext('2d');
CANVAS.width = COLS * CELL;
CANVAS.height = ROWS * CELL;
const UI = {
score: document.getElementById('score'),
level: document.getElementById('level'),
lines: document.getElementById('lines'),
state: document.getElementById('state'),
};
// Colors per peça (I,J,L,O,S,T,Z)
const COLORS = {
I:'#63e6ff', J:'#74c0fc', L:'#ffd43b', O:'#ffe066', S:'#8ce99a', T:'#b197fc', Z:'#ff8787', G:'#2b2f3a'
};
// Definició SRS (rotacions) amb matrius 4x4
const SHAPES = {
I: [
[[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],
[[0,0,1,0],[0,0,1,0],[0,0,1,0],[0,0,1,0]],
[[0,0,0,0],[0,0,0,0],[1,1,1,1],[0,0,0,0]],
[[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]],
],
O: [
[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],
[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],
[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],
[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]],
],
T: rotBase([[0,1,0],[1,1,1],[0,0,0]]),
S: rotBase([[0,1,1],[1,1,0],[0,0,0]]),
Z: rotBase([[1,1,0],[0,1,1],[0,0,0]]),
J: rotBase([[1,0,0],[1,1,1],[0,0,0]]),
L: rotBase([[0,0,1],[1,1,1],[0,0,0]]),
};
function rotBase(base3) {
// Expandeix a 4x4 i genera 4 rotacions
const pad4 = m => {
const r = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]];
for (let y=0;y<3;y++) for (let x=0;x<3;x++) r[y][x]=base3[y][x];
return r;
};
const r0 = pad4(base3);
const r1 = rotate(r0);
const r2 = rotate(r1);
const r3 = rotate(r2);
return [r0,r1,r2,r3];
}
function rotate(m){
const n = m.length, r = Array.from({length:n},()=>Array(n).fill(0));
for(let y=0;y<n;y++) for(let x=0;x<n;x++) r[x][n-1-y]=m[y][x];
return r;
}
// SRS kicks (simplificats). Font: orientació estàndard
// Clau `${piece}${from}${to}`
const KICKS = {
// Peça I
'I01':[[0,0],[-2,0],[1,0],[-2,-1],[1,2]],
'I10':[[0,0],[2,0],[-1,0],[2,1],[-1,-2]],
'I12':[[0,0],[-1,0],[2,0],[-1,2],[2,-1]],
'I21':[[0,0],[1,0],[-2,0],[1,-2],[-2,1]],
'I23':[[0,0],[2,0],[-1,0],[2,1],[-1,-2]],
'I32':[[0,0],[-2,0],[1,0],[-2,-1],[1,2]],
'I30':[[0,0],[1,0],[-2,0],[1,-2],[-2,1]],
'I03':[[0,0],[-1,0],[2,0],[-1,2],[2,-1]],
// Altres
'X01':[[0,0],[-1,0],[-1,1],[0,-2],[-1,-2]],
'X10':[[0,0],[1,0],[1,-1],[0,2],[1,2]],
'X12':[[0,0],[1,0],[1,-1],[0,2],[1,2]],
'X21':[[0,0],[-1,0],[-1,1],[0,-2],[-1,-2]],
'X23':[[0,0],[1,0],[1,1],[0,-2],[1,-2]],
'X32':[[0,0],[-1,0],[-1,-1],[0,2],[-1,2]],
'X30':[[0,0],[-1,0],[-1,-1],[0,2],[-1,2]],
'X03':[[0,0],[1,0],[1,1],[0,-2],[1,-2]],
};
// Estat del joc
const state = {
grid: Array.from({length:ROWS},()=>Array(COLS).fill(0)),
cur: null,
hold: null,
canHold: true,
bag: [],
score: 0,
lines: 0,
level: 1,
over:false,
paused:false,
dropMs: 1000, // base 1s
last: 0,
acc: 0
};
// Generador bossa 7
function nextFromBag(){
if(state.bag.length===0){
state.bag = shuffle(['I','J','L','O','S','T','Z']);
}
return state.bag.pop();
}
function shuffle(a){
for(let i=a.length-1;i>0;i--){
const j=Math.floor(Math.random()*(i+1));
[a,a[j]]=[a[j],a];
}
return a;
}
// Crear peça
function spawn(type){
const rot=0, shape=SHAPES[type][rot];
// Posició inicial: x centrat, y - (filera visible)
const x = Math.floor((COLS-4)/2);
const y = -1; // permet sortir per sobre
return {type, rot, x, y};
}
// Col·lisió
function collides(p, gx=0, gy=0, rotDelta=0){
const rot = (p.rot + rotDelta + 4) % 4;
const m = SHAPES[p.type][rot];
for(let y=0;y<4;y++){
for(let x=0;x<4;x++){
if(!m[y][x]) continue;
const nx = p.x + x + gx;
const ny = p.y + y + gy;
if(nx<0 || nx>=COLS || ny>=ROWS) return true;
if(ny>=0 && state.grid[ny][nx]) return true;
}
}
return false;
}
// Fixar peça al tauler
function merge(p){
const m = SHAPES[p.type][p.rot];
for(let y=0;y<4;y++){
for(let x=0;x<4;x++){
if(!m[y][x]) continue;
const nx = p.x + x, ny = p.y + y;
if(ny<0){ // toca sostre: game over
state.over = true;
UI.state.textContent = 'Game Over';
return;
}
state.grid[ny][nx] = p.type;
}
}
}
// Neteja línies
function clearLines(){
let cleared = 0;
for(let y=ROWS-1;y>=0;y--){
if(state.grid[y].every(v=>v)){
state.grid.splice(y,1);
state.grid.unshift(Array(COLS).fill(0));
cleared++;
y++;
}
}
if(cleared){
state.lines += cleared;
const pts = [0,100,300,500,800][cleared] * state.level;
state.score += pts;
// puja nivell cada 10 línies
const newLevel = Math.floor(state.lines/10)+1;
if(newLevel !== state.level){
state.level = newLevel;
state.dropMs = Math.max(60, 1000 - (state.level-1)*80); // més ràpid
}
updateUI();
}
}
// Dibuix
function drawCell(ctx,x,y,type){
const color = COLORS[type] || COLORS.G;
ctx.fillStyle = color;
ctx.fillRect(x*CELL, y*CELL, CELL, CELL);
// contorn suau
ctx.fillStyle = 'rgba(255,255,255,0.06)';
ctx.fillRect(x*CELL, y*CELL, CELL, 4);
ctx.fillStyle = 'rgba(0,0,0,0.15)';
ctx.fillRect(x*CELL, y*CELL+CELL-4, CELL, 4);
}
function draw(){
// fons
CTX.fillStyle = '#0b0c10';
CTX.fillRect(0,0,CANVAS.width,CANVAS.height);
// quadricula
CTX.strokeStyle = 'rgba(255,255,255,0.06)';
CTX.lineWidth = 1;
for(let x=0;x<=COLS;x++){
CTX.beginPath();
CTX.moveTo(x*CELL,0); CTX.lineTo(x*CELL,ROWS*CELL); CTX.stroke();
}
for(let y=0;y<=ROWS;y++){
CTX.beginPath();
CTX.moveTo(0,y*CELL); CTX.lineTo(COLS*CELL,y*CELL); CTX.stroke();
}
// fantasme (ghost)
if(state.cur){
let gY = state.cur.y;
while(!collides(state.cur,0,gY - state.cur.y + 1,0)) gY++;
const m = SHAPES[state.cur.type][state.cur.rot];
CTX.globalAlpha = 0.20;
for(let y=0;y<4;y++) for(let x=0;x<4;x++){
if(m[y][x]){
const nx = state.cur.x + x, ny = gY + y;
if(ny>=0) drawCell(CTX,nx,ny,'G');
}
}
CTX.globalAlpha = 1;
}
// tauler
for(let y=0;y<ROWS;y++){
for(let x=0;x<COLS;x++){
if(state.grid[y][x]) drawCell(CTX,x,y,state.grid[y][x]);
}
}
// peça actual
if(state.cur){
const m = SHAPES[state.cur.type][state.cur.rot];
for(let y=0;y<4;y++) for(let x=0;x<4;x++){
if(m[y][x]){
const nx = state.cur.x + x, ny = state.cur.y + y;
if(ny>=0) drawCell(CTX,nx,ny,state.cur.type);
}
}
}
// overlays
if(state.paused){
overlay('Pausa');
} else if(state.over){
overlay('Game Over\nR per reiniciar');
}
}
function overlay(text){
CTX.fillStyle = 'rgba(0,0,0,0.5)';
CTX.fillRect(0,0,CANVAS.width,CANVAS.height);
CTX.fillStyle = '#e8e8ef';
CTX.textAlign='center';
CTX.textBaseline='middle';
CTX.font = 'bold 28px system-ui, sans-serif';
const lines = text.split('\n');
lines.forEach((l,i)=>CTX.fillText(l, CANVAS.width/2, CANVAS.height/2 + i*34));
}
// Mini canvases (next/hold)
function drawMini(ctx, pieceType){
ctx.clearRect(0,0,120,120);
if(!pieceType) return;
const m = SHAPES[pieceType][0];
// centrar
const cells = [];
for(let y=0;y<4;y++) for(let x=0;x<4;x++) if(m[y][x]) cells.push({x,y});
const xs = cells.map(c=>c.x), ys=cells.map(c=>c.y);
const minX=Math.min(...xs), maxX=Math.max(...xs), minY=Math.min(...ys), maxY=Math.max(...ys);
const w = (maxX-minX+1), h = (maxY-minY+1);
const size = 22;
const offX = Math.floor((120 - w*size)/2);
const offY = Math.floor((120 - h*size)/2);
ctx.fillStyle = COLORS[pieceType];
cells.forEach(c=>{
const x = offX + (c.x-minX)*size;
const y = offY + (c.y-minY)*size;
ctx.fillRect(x,y,size,size);
ctx.fillStyle = 'rgba(255,255,255,0.06)'; ctx.fillRect(x,y,size,3);
ctx.fillStyle = 'rgba(0,0,0,0.15)'; ctx.fillRect(x,y+size-3,size,3);
ctx.fillStyle = COLORS[pieceType];
});
}
// UI
function updateUI(){
UI.score.textContent = state.score.toString();
UI.level.textContent = state.level.toString();
UI.lines.textContent = state.lines.toString();
UI.state.textContent = state.over ? 'Game Over' : (state.paused ? 'Pausa' : 'Jugant');
drawMini(NEXT, peekNext());
drawMini(HOLD, state.hold?.type);
}
// Lògica moviment
function hardDrop(){
if(!state.cur) return;
let dist = 0;
while(!collides(state.cur,0,1,0)){ state.cur.y++; dist++; }
state.score += dist*2; // punts per hard drop
lockPiece();
}
function softDrop(){
if(!state.cur) return;
if(!collides(state.cur,0,1,0)){ state.cur.y++; state.score += 1; }
else lockPiece();
updateUI();
}
function move(dx){
if(!state.cur) return;
if(!collides(state.cur,dx,0,0)) state.cur.x += dx;
}
function rotateCW(){
rotateGeneric(1);
}
function rotateCCW(){
rotateGeneric(-1);
}
function rotateGeneric(dir){
if(!state.cur) return;
const from = state.cur.rot;
const to = (from + (dir>0?1:3))%4;
const key = (state.cur.type==='I'?'I':'X')+from+to;
const kicks = KICKS[key] || [[0,0]];
for(const [kx,ky] of kicks){
if(!collides(state.cur, kx, ky, dir>0?1:-1)){
state.cur.x += kx; state.cur.y += ky; state.cur.rot = to; return;
}
}
}
function lockPiece(){
merge(state.cur);
clearLines();
state.cur = spawn(nextFromBag());
state.canHold = true;
if(collides(state.cur,0,0,0)){ // apareix i col·lisiona
state.over = true;
}
updateUI();
}
function peekNext(){
if(state.bag.length===0) return null;
return state.bag[state.bag.length-1];
}
function hold(){
if(!state.canHold || !state.cur) return;
const temp = state.hold;
state.hold = {type: state.cur.type};
if(temp){
state.cur = spawn(temp.type);
}else{
state.cur = spawn(nextFromBag());
}
state.cur.x = Math.floor((COLS-4)/2);
state.cur.y = -1;
state.cur.rot = 0;
state.canHold = false;
updateUI();
}
// Bucle principal
function loop(ts){
if(!state.last) state.last = ts;
const dt = ts - state.last; state.last = ts;
if(!state.paused && !state.over){
state.acc += dt;
if(state.acc >= state.dropMs){
softDrop();
state.acc = 0;
}
}
draw();
requestAnimationFrame(loop);
}
// Reinicia joc
function reset(){
state.grid = Array.from({length:ROWS},()=>Array(COLS).fill(0));
state.cur = spawn(nextFromBag());
state.hold = null;
state.canHold = true;
state.score = 0; state.lines = 0; state.level = 1;
state.dropMs = 1000; state.over = false; state.paused=false;
state.last = 0; state.acc = 0;
updateUI();
}
// Controls teclat
window.addEventListener('keydown', (e)=>{
if(state.over && e.key.toLowerCase()!=='r') return;
switch(e.key){
case 'ArrowLeft': move(-1); break;
case 'ArrowRight': move(1); break;
case 'ArrowDown': softDrop(); break;
case ' ': e.preventDefault(); hardDrop(); break;
case 'z': case 'Z': rotateCCW(); break;
case 'x': case 'X': rotateCW(); break;
case 'c': case 'C': hold(); break;
case 'p': case 'P': state.paused = !state.paused; updateUI(); break;
case 'r': case 'R': reset(); break;
}
});
// Controls tàctils
const btn = id => document.getElementById(id);
const tap = (el, fn) => { el.addEventListener('click', fn); el.addEventListener('touchstart', (e)=>{e.preventDefault(); fn();},{passive:false}); };
tap(btn('left'), ()=>move(-1));
tap(btn('right'), ()=>move(1));
tap(btn('down'), ()=>softDrop());
tap(btn('rotL'), ()=>rotateCCW());
tap(btn('rotR'), ()=>rotateCW());
tap(btn('hard'), ()=>hardDrop());
// “Swipe” simple al canvas (moure/girar)
let touchStart=null;
CANVAS.addEventListener('touchstart', (e)=>{ if(e.touches.length===1){ touchStart = {x:e.touches[0].clientX, y:e.touches[0].clientY, t:performance.now()}; }});
CANVAS.addEventListener('touchmove', (e)=>{
if(!touchStart || e.touches.length!==1) return;
const dx = e.touches[0].clientX - touchStart.x;
const dy = e.touches[0].clientY - touchStart.y;
if(Math.abs(dx) > 24 && Math.abs(dx) > Math.abs(dy)){
move(dx>0?1:-1);
touchStart.x = e.touches[0].clientX;
} else if(dy > 24){
softDrop();
touchStart.y = e.touches[0].clientY;
}
}, {passive:true});
CANVAS.addEventListener('touchend', (e)=>{
if(!touchStart) return;
const dt = performance.now() - touchStart.t;
if(dt<180) rotateCW(); // tap curt = gir
touchStart=null;
});
// Inicialització
// Prepara bossa inicial amb almenys una peça per "Següent"
state.bag = shuffle(['I','J','L','O','S','T','Z']);
state.cur = spawn(nextFromBag());
updateUI();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
UN CATXÉ DE 100-TEAM
