@@ -429,46 +513,47 @@ function AiArtifactCleaner() {
function MdlpaValidator() {
return (
-
-
+
+
+ Portalul MDLPA nu permite încadrarea în iframe (blochează via
+ X-Frame-Options). Folosește linkurile de mai jos pentru a accesa direct
+ portalul.
+
+
-
-
-
);
}
@@ -480,8 +565,14 @@ function PdfReducer() {
const [optimizeLevel, setOptimizeLevel] = useState("2");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
+ const [dragging, setDragging] = useState(false);
const fileRef = useRef
(null);
+ const setFileFromInput = (f: File | null) => {
+ setFile(f);
+ setError("");
+ };
+
const handleCompress = async () => {
if (!file) return;
setLoading(true);
@@ -518,6 +609,7 @@ function PdfReducer() {
return (
+ {/* Drag & Drop zone */}
Fișier PDF
{
- setFile(e.target.files?.[0] ?? null);
- setError("");
- }}
+ onChange={(e) => setFileFromInput(e.target.files?.[0] ?? null)}
/>
-
-
fileRef.current?.click()}>
- Selectează PDF...
-
- {file && (
-
{file.name}
+
fileRef.current?.click()}
+ onDragOver={(e) => {
+ e.preventDefault();
+ setDragging(true);
+ }}
+ onDragLeave={() => setDragging(false)}
+ onDrop={(e) => {
+ e.preventDefault();
+ setDragging(false);
+ const f = e.dataTransfer.files[0];
+ if (f?.type === "application/pdf") setFileFromInput(f);
+ }}
+ >
+ {file ? (
+ {file.name}
+ ) : (
+ <>
+
+ Trage un PDF aici sau apasă pentru a selecta
+ >
)}
@@ -547,11 +656,8 @@ function PdfReducer() {
onChange={(e) => setOptimizeLevel(e.target.value)}
className="mt-1 w-full rounded-md border bg-background px-3 py-2 text-sm"
>
-
0 — fără modificări (test)
-
1 — compresie minimă
-
2 — echilibrat (recomandat)
-
3 — compresie mare
-
4 — compresie maximă
+
Echilibrat (recomandat)
+
Compresie maximă
@@ -565,11 +671,36 @@ function PdfReducer() {
target="_blank"
rel="noopener noreferrer"
>
- Deschide Stirling PDF ↗
+ Stirling PDF ↗
+
+
{error && {error}
}
);
@@ -715,6 +846,352 @@ function QuickOcr() {
);
}
+// ─── Number to Text (Romanian) ────────────────────────────────────────────────
+
+const ONES = [
+ "",
+ "unu",
+ "doi",
+ "trei",
+ "patru",
+ "cinci",
+ "șase",
+ "șapte",
+ "opt",
+ "nouă",
+ "zece",
+ "unsprezece",
+ "doisprezece",
+ "treisprezece",
+ "paisprezece",
+ "cincisprezece",
+ "șaisprezece",
+ "șaptesprezece",
+ "optsprezece",
+ "nouăsprezece",
+];
+const TENS = [
+ "",
+ "zece",
+ "douăzeci",
+ "treizeci",
+ "patruzeci",
+ "cincizeci",
+ "șaizeci",
+ "șaptezeci",
+ "optzeci",
+ "nouăzeci",
+];
+
+function numberToRoText(n: number): string {
+ if (n === 0) return "zero";
+ if (n < 0) return "minus " + numberToRoText(-n);
+
+ const parts: string[] = [];
+
+ // Billions
+ const mld = Math.floor(n / 1_000_000_000);
+ if (mld > 0) {
+ if (mld === 1) parts.push("un miliard");
+ else if (mld < 20) parts.push(ONES[mld]! + " miliarde");
+ else parts.push(twoDigitRo(mld) + " de miliarde");
+ n %= 1_000_000_000;
+ }
+
+ // Millions
+ const mil = Math.floor(n / 1_000_000);
+ if (mil > 0) {
+ if (mil === 1) parts.push("un milion");
+ else if (mil < 20) parts.push(ONES[mil]! + " milioane");
+ else parts.push(twoDigitRo(mil) + " de milioane");
+ n %= 1_000_000;
+ }
+
+ // Thousands
+ const th = Math.floor(n / 1000);
+ if (th > 0) {
+ if (th === 1) parts.push("o mie");
+ else if (th === 2) parts.push("două mii");
+ else if (th < 20) parts.push(ONES[th]! + " mii");
+ else if (th < 100) parts.push(twoDigitRo(th) + " de mii");
+ else parts.push(threeDigitRo(th) + " de mii");
+ n %= 1000;
+ }
+
+ // Hundreds
+ const h = Math.floor(n / 100);
+ if (h > 0) {
+ if (h === 1) parts.push("o sută");
+ else if (h === 2) parts.push("două sute");
+ else parts.push(ONES[h]! + " sute");
+ n %= 100;
+ }
+
+ // Remainder < 100
+ if (n > 0) {
+ if (parts.length > 0) parts.push("și");
+ if (n < 20) {
+ parts.push(ONES[n]!);
+ } else {
+ parts.push(twoDigitRo(n));
+ }
+ }
+
+ return parts.join(" ");
+}
+
+function twoDigitRo(n: number): string {
+ if (n < 20) return ONES[n]!;
+ const t = Math.floor(n / 10);
+ const o = n % 10;
+ if (o === 0) return TENS[t]!;
+ return TENS[t]! + " și " + ONES[o]!;
+}
+
+function threeDigitRo(n: number): string {
+ const h = Math.floor(n / 100);
+ const rest = n % 100;
+ let s = "";
+ if (h === 1) s = "o sută";
+ else if (h === 2) s = "două sute";
+ else s = ONES[h]! + " sute";
+ if (rest > 0) s += " " + twoDigitRo(rest);
+ return s;
+}
+
+function NumberToText() {
+ const [input, setInput] = useState("");
+
+ const v = parseFloat(input);
+ const isValid = !isNaN(v) && isFinite(v) && v >= 0 && v < 1e12;
+
+ const intPart = isValid ? Math.floor(v) : 0;
+ const decStr = input.includes(".") ? (input.split(".")[1] ?? "") : "";
+ const decPart = decStr ? parseInt(decStr.slice(0, 2).padEnd(2, "0"), 10) : 0;
+
+ const intText = isValid ? numberToRoText(intPart) : "";
+ const decText = decPart > 0 ? numberToRoText(decPart) : "";
+
+ // lei + bani version
+ const leiBani = isValid
+ ? intText + " lei" + (decPart > 0 ? " și " + decText + " de bani" : "")
+ : "";
+
+ // Compact (no spaces between groups)
+ const compact = isValid
+ ? intText.replace(/ și /g, "șI").replace(/ /g, "").replace(/șI/g, "și") +
+ (decPart > 0
+ ? " virgulă " +
+ decText.replace(/ și /g, "șI").replace(/ /g, "").replace(/șI/g, "și")
+ : "")
+ : "";
+
+ // Normal
+ const normal = isValid
+ ? intText + (decPart > 0 ? " virgulă " + decText : "")
+ : "";
+
+ return (
+
+
+ Sumă (număr)
+ setInput(e.target.value)}
+ className="mt-1"
+ placeholder="ex: 432.20"
+ />
+
+ {isValid && intText && (
+
+
+
+
+ Lei și bani
+
+
{leiBani}
+
+
+
+
+
+
+ Cu spații
+
+
{normal}
+
+
+
+
+
+
+ Compact (fără spații)
+
+
{compact}
+
+
+
+
+ )}
+
+ );
+}
+
+// ─── Color Palette Extractor ──────────────────────────────────────────────────
+
+function ColorPaletteExtractor() {
+ const [imageSrc, setImageSrc] = useState
(null);
+ const [colors, setColors] = useState([]);
+ const [copiedIdx, setCopiedIdx] = useState(null);
+ const fileRef = useRef(null);
+
+ const extractColors = useCallback((src: string) => {
+ const img = new Image();
+ img.crossOrigin = "anonymous";
+ img.onload = () => {
+ const canvas = document.createElement("canvas");
+ const size = 100;
+ canvas.width = size;
+ canvas.height = size;
+ const ctx = canvas.getContext("2d")!;
+ ctx.drawImage(img, 0, 0, size, size);
+ const data = ctx.getImageData(0, 0, size, size).data;
+
+ // Simple K-means-ish clustering: bucket colors
+ const buckets = new Map<
+ string,
+ { r: number; g: number; b: number; count: number }
+ >();
+ for (let i = 0; i < data.length; i += 4) {
+ const r = Math.round(data[i]! / 32) * 32;
+ const g = Math.round(data[i + 1]! / 32) * 32;
+ const b = Math.round(data[i + 2]! / 32) * 32;
+ const key = `${r},${g},${b}`;
+ const existing = buckets.get(key);
+ if (existing) {
+ existing.r += data[i]!;
+ existing.g += data[i + 1]!;
+ existing.b += data[i + 2]!;
+ existing.count++;
+ } else {
+ buckets.set(key, {
+ r: data[i]!,
+ g: data[i + 1]!,
+ b: data[i + 2]!,
+ count: 1,
+ });
+ }
+ }
+
+ const sorted = Array.from(buckets.values())
+ .sort((a, b) => b.count - a.count)
+ .slice(0, 8);
+
+ const palette = sorted.map((b) => {
+ const r = Math.round(b.r / b.count);
+ const g = Math.round(b.g / b.count);
+ const bl = Math.round(b.b / b.count);
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${bl.toString(16).padStart(2, "0")}`;
+ });
+
+ setColors(palette);
+ };
+ img.src = src;
+ }, []);
+
+ const handleFile = (file: File) => {
+ if (!file.type.startsWith("image/")) return;
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const src = e.target?.result as string;
+ setImageSrc(src);
+ extractColors(src);
+ };
+ reader.readAsDataURL(file);
+ };
+
+ const copyColor = async (hex: string, idx: number) => {
+ try {
+ await navigator.clipboard.writeText(hex.toUpperCase());
+ setCopiedIdx(idx);
+ setTimeout(() => setCopiedIdx(null), 1500);
+ } catch {
+ /* silent */
+ }
+ };
+
+ return (
+
+
fileRef.current?.click()}
+ onDragOver={(e) => e.preventDefault()}
+ onDrop={(e) => {
+ e.preventDefault();
+ const f = e.dataTransfer.files[0];
+ if (f) handleFile(f);
+ }}
+ >
+ {imageSrc ? (
+ // eslint-disable-next-line @next/next/no-img-element
+
+ ) : (
+ <>
+
+
Trage o imagine sau apasă pentru a selecta
+ >
+ )}
+
+
{
+ const f = e.target.files?.[0];
+ if (f) handleFile(f);
+ }}
+ />
+ {colors.length > 0 && (
+
+
Paletă extrasă ({colors.length} culori)
+
+ {colors.map((hex, i) => (
+
void copyColor(hex, i)}
+ title={`Click pentru a copia ${hex.toUpperCase()}`}
+ >
+
+
+ {copiedIdx === i ? "Copiat!" : hex.toUpperCase()}
+
+
+ ))}
+
+
+ c.toUpperCase()).join(", ")} />
+
+ Copiază toate culorile
+
+
+
+ )}
+
+ );
+}
+
// ─── Main Module ──────────────────────────────────────────────────────────────
export function MiniUtilitiesModule() {
@@ -734,7 +1211,10 @@ export function MiniUtilitiesModule() {
Suprafețe
- U → R
+ U ↔ R
+
+
+ Numere → Text
Curățare AI
@@ -748,6 +1228,9 @@ export function MiniUtilitiesModule() {
OCR
+
+ Paletă culori
+
@@ -794,7 +1277,7 @@ export function MiniUtilitiesModule() {
- Convertor U → R (termoizolație)
+ Convertor U ↔ R (termoizolație)
@@ -846,6 +1329,28 @@ export function MiniUtilitiesModule() {
+
+
+
+
+ Transformare numere în litere
+
+
+
+
+
+
+
+
+
+
+ Extractor paletă culori
+
+
+
+
+
+
);
}