Recettes locales

Trois plats emblématiques de Santa Pola, hérités de la cuisine des pêcheurs et des familles. À découvrir et à cuisiner chez soi.

🍚 Riz🐟 Poisson & mer🥘 Plats mijotés
Poisson & mer

Caldero de Santa Pola

Le grand plat marin de Santa Pola : un caldero de poisson, ñoras, pommes de terre et aïoli, hérité de la cuisine des pêcheurs.

Voir la recette

Ingrédients

  • poisson de roche
  • pommes de terre
  • ñoras
  • ail
  • tomate
  • huile d’olive
  • sel
  • aïoli

Préparation

  1. Préparer un bon bouillon de poisson.
  2. Faire frire les ñoras puis préparer une pâte avec ail et ñoras.
  3. Faire revenir la tomate, ajouter la pâte, puis verser le bouillon.
  4. Ajouter les pommes de terre puis le poisson.
  5. Servir avec l’aïoli.
  6. Garder le bouillon restant pour préparer ensuite un arroz a banda si on le souhaite.
Riz · Poisson & mer

Arroz a banda

Un classique de la côte d’Alicante : le riz cuit dans un fumet de poisson concentré et servi seul, le poisson se dégustant à part — d’où son nom, « le riz à part ».

Voir la recette

Ingrédients

  • riz rond
  • fumet de poisson de roche
  • seiche
  • ñora
  • ail
  • tomate
  • safran
  • huile d’olive
  • sel
  • aïoli

Préparation

  1. Faire un sofrito de seiche, tomate, ñora et ail.
  2. Ajouter le riz et l’enrober, puis verser le fumet chaud.
  3. Cuire sans remuer, environ 17 à 18 minutes.
  4. Servir avec l’aïoli ; le poisson du fumet se mange à part.
Plats mijotés

Guisado de sepia

Un plat de cuisine familiale : la seiche mijotée dans une sauce de tomate, d’oignon et de vin blanc, avec des pommes de terre. Simple et nourrissant.

Voir la recette

Ingrédients

  • seiche
  • oignon
  • ail
  • tomate
  • pommes de terre
  • petits pois
  • vin blanc
  • laurier
  • pimentón
  • huile d’olive
  • sel

Préparation

  1. Saisir la seiche coupée en morceaux, puis réserver.
  2. Faire un sofrito d’oignon, d’ail et de tomate, déglacer au vin blanc.
  3. Remettre la seiche, couvrir d’eau et laisser mijoter 30 à 40 minutes.
  4. Ajouter les pommes de terre et les petits pois, cuire jusqu’à tendreté.
Choisir la langue
🇫🇷
Français
Santa Pola. Découverte.
🇪🇸
Español
Santa Pola. Descubierta.
🇬🇧
English
Santa Pola. Explored.
(function () { if (window === window.top) return; const allowedParentOrigins = ["https://www.perplexity.ai","https://perplexity.ai","https://testing.perplexity.ai","https://staging.perplexity.ai","https://*.preview.i.perplexity.ai","http://localhost:3000","http://127.0.0.1:3000","http://localhost:5173","http://127.0.0.1:5173"]; const MAX_FONT_BYTES = 500 * 1024; const MAX_TOTAL_FONT_BYTES = 2 * 1024 * 1024; let scrollForwarding = false; let scrollRaf = 0; let trustedTopOrigin = null; // Allow entries like "https://*.preview.i.perplexity.ai" — the wildcard // matches a single DNS label (no dots), so "https://*.foo" cannot stretch // across multiple labels. function matchesAllowedOrigin(origin) { if (!origin) return false; for (const entry of allowedParentOrigins) { if (!entry.includes("*")) { if (entry === origin) return true; continue; } const pattern = new RegExp( "^" + entry.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^.]+") + "$", ); if (pattern.test(origin)) return true; } return false; } // Trust decision: when the sender is same-origin-visible (event.origin is a // real origin like https://www.perplexity.ai) we trust event.origin directly. // When event.origin is "null" (opaque broker srcdoc), we fall back to the // broker's stamped `parentOrigin` to identify the top window. The fallback // is claim-only — we rely on the browser's native `targetOrigin` enforcement // on the response path (see postToTrustedTop) to ensure replies can't be // delivered to anyone but the actual top window of that claimed origin. function getTrustedParentOrigin(event) { const forwardedParentOrigin = typeof event.data.parentOrigin === "string" ? event.data.parentOrigin : null; const parentOrigin = event.origin === "null" ? forwardedParentOrigin : event.origin; return matchesAllowedOrigin(parentOrigin) ? parentOrigin : null; } // All responses go to window.top with targetOrigin = the allowlisted origin. // An attacker that iframes us inside their own null-origin broker can claim // any parentOrigin they like, but the browser will drop the reply whenever // the real top's origin doesn't match — so the screenshot never leaves. function postToTrustedTop(message) { if (!trustedTopOrigin) return; try { window.top.postMessage(message, trustedTopOrigin); } catch (_error) {} } function inlineAll(original, clone) { if (original.nodeType !== 1 || clone.nodeType !== 1) return; try { const computedStyle = getComputedStyle(original); // cssText on a computed style is the serialized declaration in modern // Chromium/Safari — a single read beats enumerating ~400 longhand // properties. Firefox returns "" here, so we fall back on empty. const serialized = computedStyle.cssText; if (serialized) { clone.style.cssText = serialized; } else { const parts = new Array(computedStyle.length); for (let index = 0; index < computedStyle.length; index += 1) { const property = computedStyle[index]; parts[index] = `${property}:${computedStyle.getPropertyValue(property)};`; } clone.style.cssText = parts.join(""); } } catch (_error) {} const originalChildren = original.children; const clonedChildren = clone.children; for ( let index = 0; index < originalChildren.length && index < clonedChildren.length; index += 1 ) { inlineAll(originalChildren[index], clonedChildren[index]); } } function extractFontUrl(srcValue) { const matches = [ ...srcValue.matchAll( /url\(["']?([^"')]+)["']?\)(?:\s*format\(["']?([^"')]+)["']?\))?/gi, ), ]; if (matches.length === 0) return null; const woff2 = matches.find((m) => m[2] && m[2].toLowerCase().includes("woff2")); if (woff2) return woff2[1]; const woff = matches.find((m) => m[2] && m[2].toLowerCase().includes("woff")); if (woff) return woff[1]; return matches[0][1]; } // Cache resolved font URL -> data URI across captures. Fonts on a page // essentially never change, and a batch run emits multiple captures back to // back — without this we'd refetch + re-base64 every time. const fontDataUriCache = new Map(); const SRC_DECLARATION_RE = /src\s*:\s*[^;}]+/i; async function fetchAsDataUri(url) { if (fontDataUriCache.has(url)) return fontDataUriCache.get(url); let dataUri = null; try { const response = await fetch(url, { mode: "cors", credentials: "omit" }); if (response.ok) { const blob = await response.blob(); if (blob.size <= MAX_FONT_BYTES) { dataUri = await new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve(typeof reader.result === "string" ? reader.result : null); reader.onerror = () => resolve(null); reader.readAsDataURL(blob); }); } } } catch (_error) { dataUri = null; } fontDataUriCache.set(url, dataUri); return dataUri; } function collectFontFaceRuleTexts() { const rules = []; for (const sheet of document.styleSheets) { let cssRules; try { cssRules = sheet.cssRules; } catch (_error) { continue; } if (!cssRules) continue; for (const rule of cssRules) { const cssText = rule.cssText || ""; if (cssText.startsWith("@font-face")) rules.push(cssText); } } return rules; } async function buildInlinedFontCss() { const ruleTexts = collectFontFaceRuleTexts(); if (ruleTexts.length === 0) return null; const resolved = ruleTexts.map((cssText) => { if (!SRC_DECLARATION_RE.test(cssText)) return null; const srcMatch = cssText.match(/src\s*:\s*([^;}]+)[;}]/i); if (!srcMatch) return null; const url = extractFontUrl(srcMatch[1]); if (!url) return null; try { return { cssText, url: new URL(url, document.baseURI).href }; } catch (_error) { return null; } }); const dataUris = await Promise.all( resolved.map((entry) => (entry ? fetchAsDataUri(entry.url) : Promise.resolve(null))), ); const inlined = []; let totalBytes = 0; for (let index = 0; index < resolved.length; index += 1) { const entry = resolved[index]; const dataUri = dataUris[index]; if (!entry || !dataUri) continue; const approxBytes = dataUri.length * 0.75; if (totalBytes + approxBytes > MAX_TOTAL_FONT_BYTES) break; totalBytes += approxBytes; inlined.push(entry.cssText.replace(SRC_DECLARATION_RE, `src: url("${dataUri}")`)); } return inlined.length > 0 ? inlined.join("\n") : null; } function stripExternal(clone) { const images = clone.querySelectorAll("img"); for (let index = 0; index < images.length; index += 1) { const src = images[index].getAttribute("src"); if (src && !src.startsWith("data:")) images[index].removeAttribute("src"); } const elements = clone.querySelectorAll("*"); for (let index = 0; index < elements.length; index += 1) { const style = elements[index].style.cssText; if (style && style.includes("url(")) { elements[index].style.cssText = style.replace( /url\(["']?(?!data:)[^)"']*["']?\)/gi, "none", ); } } } function emitScroll() { scrollRaf = 0; if (!scrollForwarding) return; postToTrustedTop({ type: "INLINE_EDIT_SCROLL", scrollX: window.scrollX, scrollY: window.scrollY, }); } window.addEventListener( "scroll", function () { if (!scrollForwarding || scrollRaf) return; scrollRaf = requestAnimationFrame(emitScroll); }, { passive: true, capture: true }, ); async function handleCaptureRequest(event) { const requestId = event.data.requestId; const scrollX = window.scrollX; const scrollY = window.scrollY; const width = window.innerWidth; const height = window.innerHeight; function postResult(dataUrl) { postToTrustedTop({ type: "INLINE_EDIT_SCREENSHOT_RESULT", requestId, dataUrl, scrollX, scrollY, }); } try { // Wait for any pending web fonts to resolve so both inline metrics and // the @font-face inlining below see the same loaded faces. if (document.fonts && document.fonts.ready) { try { await document.fonts.ready; } catch (_error) {} } const clone = document.documentElement.cloneNode(true); inlineAll(document.documentElement, clone); const removedNodes = clone.querySelectorAll("script,link[rel=\"stylesheet\"],style"); for (let index = 0; index < removedNodes.length; index += 1) { removedNodes[index].remove(); } stripExternal(clone); // Re-embed web fonts as data-URI @font-face rules so the SVG rasterizer // can resolve them — external font URLs aren't fetched during // foreignObject rendering, which would otherwise force a fallback face // and change text metrics. const inlinedFontCss = await buildInlinedFontCss(); if (inlinedFontCss) { const styleEl = document.createElement("style"); styleEl.textContent = inlinedFontCss; const head = clone.querySelector("head"); if (head) head.appendChild(styleEl); else clone.insertBefore(styleEl, clone.firstChild); } const html = new XMLSerializer().serializeToString(clone); const svg = `` + '' + `
` + `
` + html + "
"; const svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; const image = new Image(); image.onload = function () { const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; canvas.getContext("2d").drawImage(image, 0, 0); postResult(canvas.toDataURL("image/png")); }; image.onerror = function () { postResult(null); }; image.src = svgUrl; } catch (_error) { postResult(null); } } window.addEventListener("message", function (event) { if (!event.data) return; // Only accept messages from the direct parent frame. Blocks sibling / // unrelated-window postMessage senders that could otherwise reach us. if (event.source !== window.parent) return; const trustedParentOrigin = getTrustedParentOrigin(event); if (!trustedParentOrigin) return; trustedTopOrigin = trustedParentOrigin; if (event.data.type === "INLINE_EDIT_SCROLL_START") { scrollForwarding = true; emitScroll(); return; } if (event.data.type === "INLINE_EDIT_SCROLL_STOP") { scrollForwarding = false; if (scrollRaf) cancelAnimationFrame(scrollRaf); scrollRaf = 0; return; } if (event.data.type !== "INLINE_EDIT_CAPTURE_REQUEST") return; handleCaptureRequest(event); }); })();