diff --git a/.astro/settings.json b/.astro/settings.json
new file mode 100644
index 0000000..632bb96
--- /dev/null
+++ b/.astro/settings.json
@@ -0,0 +1,5 @@
+{
+ "_variables": {
+ "lastUpdateCheck": 1721590671562
+ }
+}
\ No newline at end of file
diff --git a/.vercel/output/config.json b/.vercel/output/config.json
new file mode 100644
index 0000000..bc52f6b
--- /dev/null
+++ b/.vercel/output/config.json
@@ -0,0 +1,19 @@
+{
+ "version": 3,
+ "routes": [
+ {
+ "src": "^/_astro/(.*)$",
+ "headers": {
+ "cache-control": "public, max-age=31536000, immutable"
+ },
+ "continue": true
+ },
+ {
+ "handle": "filesystem"
+ },
+ {
+ "src": "^\\/_image$",
+ "dest": "_render"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vercel/output/functions/_render.func/.vc-config.json b/.vercel/output/functions/_render.func/.vc-config.json
new file mode 100644
index 0000000..56c08d9
--- /dev/null
+++ b/.vercel/output/functions/_render.func/.vc-config.json
@@ -0,0 +1,6 @@
+{
+ "runtime": "nodejs18.x",
+ "handler": "home/vic/src/web/portfolio/.vercel/output/_functions/entry.mjs",
+ "launcherType": "Nodejs",
+ "supportsResponseStreaming": true
+}
\ No newline at end of file
diff --git a/.vercel/output/functions/_render.func/home/vic/src/web/portfolio/.vercel/output/_functions/_noop-middleware.mjs b/.vercel/output/functions/_render.func/home/vic/src/web/portfolio/.vercel/output/_functions/_noop-middleware.mjs
new file mode 100644
index 0000000..84424b0
--- /dev/null
+++ b/.vercel/output/functions/_render.func/home/vic/src/web/portfolio/.vercel/output/_functions/_noop-middleware.mjs
@@ -0,0 +1,3 @@
+const onRequest = (_, next) => next();
+
+export { onRequest };
diff --git a/.vercel/output/functions/_render.func/home/vic/src/web/portfolio/.vercel/output/_functions/chunks/_@astrojs-manifest_BchT3v_p.mjs b/.vercel/output/functions/_render.func/home/vic/src/web/portfolio/.vercel/output/_functions/chunks/_@astrojs-manifest_BchT3v_p.mjs
new file mode 100644
index 0000000..cb018e0
--- /dev/null
+++ b/.vercel/output/functions/_render.func/home/vic/src/web/portfolio/.vercel/output/_functions/chunks/_@astrojs-manifest_BchT3v_p.mjs
@@ -0,0 +1,352 @@
+import 'cookie';
+import { bold, red, yellow, dim, blue } from 'kleur/colors';
+import { D as DEFAULT_404_COMPONENT } from './astro/server_mfaSpNA7.mjs';
+import 'clsx';
+import { escape } from 'html-escaper';
+import { compile } from 'path-to-regexp';
+
+const dateTimeFormat = new Intl.DateTimeFormat([], {
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ hour12: false
+});
+const levels = {
+ debug: 20,
+ info: 30,
+ warn: 40,
+ error: 50,
+ silent: 90
+};
+function log(opts, level, label, message, newLine = true) {
+ const logLevel = opts.level;
+ const dest = opts.dest;
+ const event = {
+ label,
+ level,
+ message,
+ newLine
+ };
+ if (!isLogLevelEnabled(logLevel, level)) {
+ return;
+ }
+ dest.write(event);
+}
+function isLogLevelEnabled(configuredLogLevel, level) {
+ return levels[configuredLogLevel] <= levels[level];
+}
+function info(opts, label, message, newLine = true) {
+ return log(opts, "info", label, message, newLine);
+}
+function warn(opts, label, message, newLine = true) {
+ return log(opts, "warn", label, message, newLine);
+}
+function error(opts, label, message, newLine = true) {
+ return log(opts, "error", label, message, newLine);
+}
+function debug(...args) {
+ if ("_astroGlobalDebug" in globalThis) {
+ globalThis._astroGlobalDebug(...args);
+ }
+}
+function getEventPrefix({ level, label }) {
+ const timestamp = `${dateTimeFormat.format(/* @__PURE__ */ new Date())}`;
+ const prefix = [];
+ if (level === "error" || level === "warn") {
+ prefix.push(bold(timestamp));
+ prefix.push(`[${level.toUpperCase()}]`);
+ } else {
+ prefix.push(timestamp);
+ }
+ if (label) {
+ prefix.push(`[${label}]`);
+ }
+ if (level === "error") {
+ return red(prefix.join(" "));
+ }
+ if (level === "warn") {
+ return yellow(prefix.join(" "));
+ }
+ if (prefix.length === 1) {
+ return dim(prefix[0]);
+ }
+ return dim(prefix[0]) + " " + blue(prefix.splice(1).join(" "));
+}
+if (typeof process !== "undefined") {
+ let proc = process;
+ if ("argv" in proc && Array.isArray(proc.argv)) {
+ if (proc.argv.includes("--verbose")) ; else if (proc.argv.includes("--silent")) ; else ;
+ }
+}
+class Logger {
+ options;
+ constructor(options) {
+ this.options = options;
+ }
+ info(label, message, newLine = true) {
+ info(this.options, label, message, newLine);
+ }
+ warn(label, message, newLine = true) {
+ warn(this.options, label, message, newLine);
+ }
+ error(label, message, newLine = true) {
+ error(this.options, label, message, newLine);
+ }
+ debug(label, ...messages) {
+ debug(label, ...messages);
+ }
+ level() {
+ return this.options.level;
+ }
+ forkIntegrationLogger(label) {
+ return new AstroIntegrationLogger(this.options, label);
+ }
+}
+class AstroIntegrationLogger {
+ options;
+ label;
+ constructor(logging, label) {
+ this.options = logging;
+ this.label = label;
+ }
+ /**
+ * Creates a new logger instance with a new label, but the same log options.
+ */
+ fork(label) {
+ return new AstroIntegrationLogger(this.options, label);
+ }
+ info(message) {
+ info(this.options, this.label, message);
+ }
+ warn(message) {
+ warn(this.options, this.label, message);
+ }
+ error(message) {
+ error(this.options, this.label, message);
+ }
+ debug(message) {
+ debug(this.label, message);
+ }
+}
+
+function template({
+ title,
+ pathname,
+ statusCode = 404,
+ tabTitle,
+ body
+}) {
+ return `
+
+
+
+ ${tabTitle}
+
+
+
+
+
+ ${statusCode ? `${statusCode}: ` : ""}${title}
+ ${body || `
+ Path: ${escape(pathname)}
+ `}
+
+
+`;
+}
+
+const DEFAULT_404_ROUTE = {
+ component: DEFAULT_404_COMPONENT,
+ generate: () => "",
+ params: [],
+ pattern: /\/404/,
+ prerender: false,
+ pathname: "/404",
+ segments: [[{ content: "404", dynamic: false, spread: false }]],
+ type: "page",
+ route: "/404",
+ fallbackRoutes: [],
+ isIndex: false
+};
+function ensure404Route(manifest) {
+ if (!manifest.routes.some((route) => route.route === "/404")) {
+ manifest.routes.push(DEFAULT_404_ROUTE);
+ }
+ return manifest;
+}
+async function default404Page({ pathname }) {
+ return new Response(
+ template({
+ statusCode: 404,
+ title: "Not found",
+ tabTitle: "404: Not Found",
+ pathname
+ }),
+ { status: 404, headers: { "Content-Type": "text/html; charset=utf-8" } }
+ );
+}
+default404Page.isAstroComponentFactory = true;
+const default404Instance = {
+ default: default404Page
+};
+
+function sanitizeParams(params) {
+ return Object.fromEntries(
+ Object.entries(params).map(([key, value]) => {
+ if (typeof value === "string") {
+ return [key, value.normalize().replace(/#/g, "%23").replace(/\?/g, "%3F")];
+ }
+ return [key, value];
+ })
+ );
+}
+function getRouteGenerator(segments, addTrailingSlash) {
+ const template = segments.map((segment) => {
+ return "/" + segment.map((part) => {
+ if (part.spread) {
+ return `:${part.content.slice(3)}(.*)?`;
+ } else if (part.dynamic) {
+ return `:${part.content}`;
+ } else {
+ return part.content.normalize().replace(/\?/g, "%3F").replace(/#/g, "%23").replace(/%5B/g, "[").replace(/%5D/g, "]").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ }
+ }).join("");
+ }).join("");
+ let trailing = "";
+ if (addTrailingSlash === "always" && segments.length) {
+ trailing = "/";
+ }
+ const toPath = compile(template + trailing);
+ return (params) => {
+ const sanitizedParams = sanitizeParams(params);
+ const path = toPath(sanitizedParams);
+ return path || "/";
+ };
+}
+
+function deserializeRouteData(rawRouteData) {
+ return {
+ route: rawRouteData.route,
+ type: rawRouteData.type,
+ pattern: new RegExp(rawRouteData.pattern),
+ params: rawRouteData.params,
+ component: rawRouteData.component,
+ generate: getRouteGenerator(rawRouteData.segments, rawRouteData._meta.trailingSlash),
+ pathname: rawRouteData.pathname || void 0,
+ segments: rawRouteData.segments,
+ prerender: rawRouteData.prerender,
+ redirect: rawRouteData.redirect,
+ redirectRoute: rawRouteData.redirectRoute ? deserializeRouteData(rawRouteData.redirectRoute) : void 0,
+ fallbackRoutes: rawRouteData.fallbackRoutes.map((fallback) => {
+ return deserializeRouteData(fallback);
+ }),
+ isIndex: rawRouteData.isIndex
+ };
+}
+
+function deserializeManifest(serializedManifest) {
+ const routes = [];
+ for (const serializedRoute of serializedManifest.routes) {
+ routes.push({
+ ...serializedRoute,
+ routeData: deserializeRouteData(serializedRoute.routeData)
+ });
+ const route = serializedRoute;
+ route.routeData = deserializeRouteData(serializedRoute.routeData);
+ }
+ const assets = new Set(serializedManifest.assets);
+ const componentMetadata = new Map(serializedManifest.componentMetadata);
+ const inlinedScripts = new Map(serializedManifest.inlinedScripts);
+ const clientDirectives = new Map(serializedManifest.clientDirectives);
+ const serverIslandNameMap = new Map(serializedManifest.serverIslandNameMap);
+ return {
+ // in case user middleware exists, this no-op middleware will be reassigned (see plugin-ssr.ts)
+ middleware(_, next) {
+ return next();
+ },
+ ...serializedManifest,
+ assets,
+ componentMetadata,
+ inlinedScripts,
+ clientDirectives,
+ routes,
+ serverIslandNameMap
+ };
+}
+
+const manifest = deserializeManifest({"hrefRoot":"file:///home/vic/src/web/portfolio/","adapterName":"@astrojs/vercel/serverless","routes":[{"file":"robots.txt","links":[],"scripts":[],"styles":[],"routeData":{"route":"/robots.txt","isIndex":false,"type":"endpoint","pattern":"^\\/robots\\.txt\\/?$","segments":[[{"content":"robots.txt","dynamic":false,"spread":false}]],"params":[],"component":"src/pages/robots.txt.ts","pathname":"/robots.txt","prerender":true,"fallbackRoutes":[],"_meta":{"trailingSlash":"ignore"}}},{"file":"index.html","links":[],"scripts":[],"styles":[],"routeData":{"route":"/","isIndex":true,"type":"page","pattern":"^\\/$","segments":[],"params":[],"component":"src/pages/index.astro","pathname":"/","prerender":true,"fallbackRoutes":[],"_meta":{"trailingSlash":"ignore"}}},{"file":"","links":[],"scripts":[],"styles":[],"routeData":{"type":"endpoint","isIndex":false,"route":"/_image","pattern":"^\\/_image$","segments":[[{"content":"_image","dynamic":false,"spread":false}]],"params":[],"component":"node_modules/astro/dist/assets/endpoint/generic.js","pathname":"/_image","prerender":false,"fallbackRoutes":[],"_meta":{"trailingSlash":"ignore"}}}],"site":"https://wilyJ80.github.io/portfolio","base":"/","trailingSlash":"ignore","compressHTML":true,"componentMetadata":[["/home/vic/src/web/portfolio/src/pages/index.astro",{"propagation":"none","containsHead":true}]],"renderers":[],"clientDirectives":[["idle","(()=>{var i=t=>{let e=async()=>{await(await t())()};\"requestIdleCallback\"in window?window.requestIdleCallback(e):setTimeout(e,200)};(self.Astro||(self.Astro={})).idle=i;window.dispatchEvent(new Event(\"astro:idle\"));})();"],["load","(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).load=e;window.dispatchEvent(new Event(\"astro:load\"));})();"],["media","(()=>{var s=(i,t)=>{let a=async()=>{await(await i())()};if(t.value){let e=matchMedia(t.value);e.matches?a():e.addEventListener(\"change\",a,{once:!0})}};(self.Astro||(self.Astro={})).media=s;window.dispatchEvent(new Event(\"astro:media\"));})();"],["only","(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).only=e;window.dispatchEvent(new Event(\"astro:only\"));})();"],["visible","(()=>{var l=(s,i,o)=>{let r=async()=>{await(await s())()},t=typeof i.value==\"object\"?i.value:void 0,c={rootMargin:t==null?void 0:t.rootMargin},n=new IntersectionObserver(e=>{for(let a of e)if(a.isIntersecting){n.disconnect(),r();break}},c);for(let e of o.children)n.observe(e)};(self.Astro||(self.Astro={})).visible=l;window.dispatchEvent(new Event(\"astro:visible\"));})();"]],"entryModules":{"\u0000@astro-page:node_modules/astro/dist/assets/endpoint/generic@_@js":"pages/_image.astro.mjs","\u0000@astro-page:src/pages/robots.txt@_@ts":"pages/robots.txt.astro.mjs","\u0000@astro-page:src/pages/index@_@astro":"pages/index.astro.mjs","\u0000@astrojs-ssr-virtual-entry":"entry.mjs","\u0000noop-middleware":"_noop-middleware.mjs","\u0000@astro-renderers":"renderers.mjs","/home/vic/src/web/portfolio/node_modules/astro/dist/env/setup.js":"chunks/astro/env-setup_Cr6XTFvb.mjs","\u0000@astrojs-manifest":"manifest_BbZOqa2I.mjs","astro:scripts/before-hydration.js":""},"inlinedScripts":[],"assets":["/_astro/index.BuTT48KC.css","/favicon.svg","/robots.txt","/assets/img/ai-generated-portrait.webp","/assets/img/vitaoportrait.webp","/assets/svg/backgrounds/horizon-animated.svg","/assets/svg/backgrounds/horizon.svg","/assets/svg/icons/email.svg","/assets/svg/icons/github.svg","/assets/svg/icons/instagram.svg","/assets/svg/icons/linkedin.svg","/assets/svg/icons/tiktok.svg","/assets/svg/icons/x.svg","/robots.txt","/index.html"],"buildFormat":"directory","checkOrigin":false,"rewritingEnabled":false,"serverIslandNameMap":[],"experimentalEnvGetSecretEnabled":false});
+
+export { AstroIntegrationLogger as A, DEFAULT_404_ROUTE as D, Logger as L, default404Instance as d, ensure404Route as e, getEventPrefix as g, levels as l, manifest as m };
diff --git a/.vercel/output/functions/_render.func/home/vic/src/web/portfolio/.vercel/output/_functions/chunks/_astro_assets_BUmYIlZq.mjs b/.vercel/output/functions/_render.func/home/vic/src/web/portfolio/.vercel/output/_functions/chunks/_astro_assets_BUmYIlZq.mjs
new file mode 100644
index 0000000..0c732fc
--- /dev/null
+++ b/.vercel/output/functions/_render.func/home/vic/src/web/portfolio/.vercel/output/_functions/chunks/_astro_assets_BUmYIlZq.mjs
@@ -0,0 +1,1015 @@
+import { A as AstroError, g as NoImageMetadata, F as FailedToFetchRemoteImageDimensions, h as ExpectedImageOptions, j as ExpectedImage, k as ExpectedNotESMImage, r as resolveSrc, l as isRemoteImage, m as isESMImportedImage, n as isLocalService, D as DEFAULT_HASH_PROPS, o as InvalidImageService, p as ImageMissingAlt } from './astro/assets-service_DK7A0Y1H.mjs';
+import { d as createAstro, c as createComponent, r as renderTemplate, m as maybeRenderHead, b as addAttribute, s as spreadAttributes } from './astro/server_mfaSpNA7.mjs';
+import * as mime from 'mrmime';
+import 'clsx';
+
+function isImageMetadata(src) {
+ return src.fsPath && !("fsPath" in src);
+}
+
+const decoder = new TextDecoder();
+const toUTF8String = (input, start = 0, end = input.length) => decoder.decode(input.slice(start, end));
+const toHexString = (input, start = 0, end = input.length) => input.slice(start, end).reduce((memo, i) => memo + ("0" + i.toString(16)).slice(-2), "");
+const readInt16LE = (input, offset = 0) => {
+ const val = input[offset] + input[offset + 1] * 2 ** 8;
+ return val | (val & 2 ** 15) * 131070;
+};
+const readUInt16BE = (input, offset = 0) => input[offset] * 2 ** 8 + input[offset + 1];
+const readUInt16LE = (input, offset = 0) => input[offset] + input[offset + 1] * 2 ** 8;
+const readUInt24LE = (input, offset = 0) => input[offset] + input[offset + 1] * 2 ** 8 + input[offset + 2] * 2 ** 16;
+const readInt32LE = (input, offset = 0) => input[offset] + input[offset + 1] * 2 ** 8 + input[offset + 2] * 2 ** 16 + (input[offset + 3] << 24);
+const readUInt32BE = (input, offset = 0) => input[offset] * 2 ** 24 + input[offset + 1] * 2 ** 16 + input[offset + 2] * 2 ** 8 + input[offset + 3];
+const readUInt32LE = (input, offset = 0) => input[offset] + input[offset + 1] * 2 ** 8 + input[offset + 2] * 2 ** 16 + input[offset + 3] * 2 ** 24;
+const methods = {
+ readUInt16BE,
+ readUInt16LE,
+ readUInt32BE,
+ readUInt32LE
+};
+function readUInt(input, bits, offset, isBigEndian) {
+ offset = offset || 0;
+ const endian = isBigEndian ? "BE" : "LE";
+ const methodName = "readUInt" + bits + endian;
+ return methods[methodName](input, offset);
+}
+function readBox(buffer, offset) {
+ if (buffer.length - offset < 4) return;
+ const boxSize = readUInt32BE(buffer, offset);
+ if (buffer.length - offset < boxSize) return;
+ return {
+ name: toUTF8String(buffer, 4 + offset, 8 + offset),
+ offset,
+ size: boxSize
+ };
+}
+function findBox(buffer, boxName, offset) {
+ while (offset < buffer.length) {
+ const box = readBox(buffer, offset);
+ if (!box) break;
+ if (box.name === boxName) return box;
+ offset += box.size;
+ }
+}
+
+const BMP = {
+ validate: (input) => toUTF8String(input, 0, 2) === "BM",
+ calculate: (input) => ({
+ height: Math.abs(readInt32LE(input, 22)),
+ width: readUInt32LE(input, 18)
+ })
+};
+
+const TYPE_ICON = 1;
+const SIZE_HEADER$1 = 2 + 2 + 2;
+const SIZE_IMAGE_ENTRY = 1 + 1 + 1 + 1 + 2 + 2 + 4 + 4;
+function getSizeFromOffset(input, offset) {
+ const value = input[offset];
+ return value === 0 ? 256 : value;
+}
+function getImageSize$1(input, imageIndex) {
+ const offset = SIZE_HEADER$1 + imageIndex * SIZE_IMAGE_ENTRY;
+ return {
+ height: getSizeFromOffset(input, offset + 1),
+ width: getSizeFromOffset(input, offset)
+ };
+}
+const ICO = {
+ validate(input) {
+ const reserved = readUInt16LE(input, 0);
+ const imageCount = readUInt16LE(input, 4);
+ if (reserved !== 0 || imageCount === 0) return false;
+ const imageType = readUInt16LE(input, 2);
+ return imageType === TYPE_ICON;
+ },
+ calculate(input) {
+ const nbImages = readUInt16LE(input, 4);
+ const imageSize = getImageSize$1(input, 0);
+ if (nbImages === 1) return imageSize;
+ const imgs = [imageSize];
+ for (let imageIndex = 1; imageIndex < nbImages; imageIndex += 1) {
+ imgs.push(getImageSize$1(input, imageIndex));
+ }
+ return {
+ height: imageSize.height,
+ images: imgs,
+ width: imageSize.width
+ };
+ }
+};
+
+const TYPE_CURSOR = 2;
+const CUR = {
+ validate(input) {
+ const reserved = readUInt16LE(input, 0);
+ const imageCount = readUInt16LE(input, 4);
+ if (reserved !== 0 || imageCount === 0) return false;
+ const imageType = readUInt16LE(input, 2);
+ return imageType === TYPE_CURSOR;
+ },
+ calculate: (input) => ICO.calculate(input)
+};
+
+const DDS = {
+ validate: (input) => readUInt32LE(input, 0) === 542327876,
+ calculate: (input) => ({
+ height: readUInt32LE(input, 12),
+ width: readUInt32LE(input, 16)
+ })
+};
+
+const gifRegexp = /^GIF8[79]a/;
+const GIF = {
+ validate: (input) => gifRegexp.test(toUTF8String(input, 0, 6)),
+ calculate: (input) => ({
+ height: readUInt16LE(input, 8),
+ width: readUInt16LE(input, 6)
+ })
+};
+
+const brandMap = {
+ avif: "avif",
+ mif1: "heif",
+ msf1: "heif",
+ // hief-sequence
+ heic: "heic",
+ heix: "heic",
+ hevc: "heic",
+ // heic-sequence
+ hevx: "heic"
+ // heic-sequence
+};
+function detectBrands(buffer, start, end) {
+ let brandsDetected = {};
+ for (let i = start; i <= end; i += 4) {
+ const brand = toUTF8String(buffer, i, i + 4);
+ if (brand in brandMap) {
+ brandsDetected[brand] = 1;
+ }
+ }
+ if ("avif" in brandsDetected) {
+ return "avif";
+ } else if ("heic" in brandsDetected || "heix" in brandsDetected || "hevc" in brandsDetected || "hevx" in brandsDetected) {
+ return "heic";
+ } else if ("mif1" in brandsDetected || "msf1" in brandsDetected) {
+ return "heif";
+ }
+}
+const HEIF = {
+ validate(buffer) {
+ const ftype = toUTF8String(buffer, 4, 8);
+ const brand = toUTF8String(buffer, 8, 12);
+ return "ftyp" === ftype && brand in brandMap;
+ },
+ calculate(buffer) {
+ const metaBox = findBox(buffer, "meta", 0);
+ const iprpBox = metaBox && findBox(buffer, "iprp", metaBox.offset + 12);
+ const ipcoBox = iprpBox && findBox(buffer, "ipco", iprpBox.offset + 8);
+ const ispeBox = ipcoBox && findBox(buffer, "ispe", ipcoBox.offset + 8);
+ if (ispeBox) {
+ return {
+ height: readUInt32BE(buffer, ispeBox.offset + 16),
+ width: readUInt32BE(buffer, ispeBox.offset + 12),
+ type: detectBrands(buffer, 8, metaBox.offset)
+ };
+ }
+ throw new TypeError("Invalid HEIF, no size found");
+ }
+};
+
+const SIZE_HEADER = 4 + 4;
+const FILE_LENGTH_OFFSET = 4;
+const ENTRY_LENGTH_OFFSET = 4;
+const ICON_TYPE_SIZE = {
+ ICON: 32,
+ "ICN#": 32,
+ // m => 16 x 16
+ "icm#": 16,
+ icm4: 16,
+ icm8: 16,
+ // s => 16 x 16
+ "ics#": 16,
+ ics4: 16,
+ ics8: 16,
+ is32: 16,
+ s8mk: 16,
+ icp4: 16,
+ // l => 32 x 32
+ icl4: 32,
+ icl8: 32,
+ il32: 32,
+ l8mk: 32,
+ icp5: 32,
+ ic11: 32,
+ // h => 48 x 48
+ ich4: 48,
+ ich8: 48,
+ ih32: 48,
+ h8mk: 48,
+ // . => 64 x 64
+ icp6: 64,
+ ic12: 32,
+ // t => 128 x 128
+ it32: 128,
+ t8mk: 128,
+ ic07: 128,
+ // . => 256 x 256
+ ic08: 256,
+ ic13: 256,
+ // . => 512 x 512
+ ic09: 512,
+ ic14: 512,
+ // . => 1024 x 1024
+ ic10: 1024
+};
+function readImageHeader(input, imageOffset) {
+ const imageLengthOffset = imageOffset + ENTRY_LENGTH_OFFSET;
+ return [
+ toUTF8String(input, imageOffset, imageLengthOffset),
+ readUInt32BE(input, imageLengthOffset)
+ ];
+}
+function getImageSize(type) {
+ const size = ICON_TYPE_SIZE[type];
+ return { width: size, height: size, type };
+}
+const ICNS = {
+ validate: (input) => toUTF8String(input, 0, 4) === "icns",
+ calculate(input) {
+ const inputLength = input.length;
+ const fileLength = readUInt32BE(input, FILE_LENGTH_OFFSET);
+ let imageOffset = SIZE_HEADER;
+ let imageHeader = readImageHeader(input, imageOffset);
+ let imageSize = getImageSize(imageHeader[0]);
+ imageOffset += imageHeader[1];
+ if (imageOffset === fileLength) return imageSize;
+ const result = {
+ height: imageSize.height,
+ images: [imageSize],
+ width: imageSize.width
+ };
+ while (imageOffset < fileLength && imageOffset < inputLength) {
+ imageHeader = readImageHeader(input, imageOffset);
+ imageSize = getImageSize(imageHeader[0]);
+ imageOffset += imageHeader[1];
+ result.images.push(imageSize);
+ }
+ return result;
+ }
+};
+
+const J2C = {
+ // TODO: this doesn't seem right. SIZ marker doesn't have to be right after the SOC
+ validate: (input) => toHexString(input, 0, 4) === "ff4fff51",
+ calculate: (input) => ({
+ height: readUInt32BE(input, 12),
+ width: readUInt32BE(input, 8)
+ })
+};
+
+const JP2 = {
+ validate(input) {
+ if (readUInt32BE(input, 4) !== 1783636e3 || readUInt32BE(input, 0) < 1) return false;
+ const ftypBox = findBox(input, "ftyp", 0);
+ if (!ftypBox) return false;
+ return readUInt32BE(input, ftypBox.offset + 4) === 1718909296;
+ },
+ calculate(input) {
+ const jp2hBox = findBox(input, "jp2h", 0);
+ const ihdrBox = jp2hBox && findBox(input, "ihdr", jp2hBox.offset + 8);
+ if (ihdrBox) {
+ return {
+ height: readUInt32BE(input, ihdrBox.offset + 8),
+ width: readUInt32BE(input, ihdrBox.offset + 12)
+ };
+ }
+ throw new TypeError("Unsupported JPEG 2000 format");
+ }
+};
+
+const EXIF_MARKER = "45786966";
+const APP1_DATA_SIZE_BYTES = 2;
+const EXIF_HEADER_BYTES = 6;
+const TIFF_BYTE_ALIGN_BYTES = 2;
+const BIG_ENDIAN_BYTE_ALIGN = "4d4d";
+const LITTLE_ENDIAN_BYTE_ALIGN = "4949";
+const IDF_ENTRY_BYTES = 12;
+const NUM_DIRECTORY_ENTRIES_BYTES = 2;
+function isEXIF(input) {
+ return toHexString(input, 2, 6) === EXIF_MARKER;
+}
+function extractSize(input, index) {
+ return {
+ height: readUInt16BE(input, index),
+ width: readUInt16BE(input, index + 2)
+ };
+}
+function extractOrientation(exifBlock, isBigEndian) {
+ const idfOffset = 8;
+ const offset = EXIF_HEADER_BYTES + idfOffset;
+ const idfDirectoryEntries = readUInt(exifBlock, 16, offset, isBigEndian);
+ for (let directoryEntryNumber = 0; directoryEntryNumber < idfDirectoryEntries; directoryEntryNumber++) {
+ const start = offset + NUM_DIRECTORY_ENTRIES_BYTES + directoryEntryNumber * IDF_ENTRY_BYTES;
+ const end = start + IDF_ENTRY_BYTES;
+ if (start > exifBlock.length) {
+ return;
+ }
+ const block = exifBlock.slice(start, end);
+ const tagNumber = readUInt(block, 16, 0, isBigEndian);
+ if (tagNumber === 274) {
+ const dataFormat = readUInt(block, 16, 2, isBigEndian);
+ if (dataFormat !== 3) {
+ return;
+ }
+ const numberOfComponents = readUInt(block, 32, 4, isBigEndian);
+ if (numberOfComponents !== 1) {
+ return;
+ }
+ return readUInt(block, 16, 8, isBigEndian);
+ }
+ }
+}
+function validateExifBlock(input, index) {
+ const exifBlock = input.slice(APP1_DATA_SIZE_BYTES, index);
+ const byteAlign = toHexString(
+ exifBlock,
+ EXIF_HEADER_BYTES,
+ EXIF_HEADER_BYTES + TIFF_BYTE_ALIGN_BYTES
+ );
+ const isBigEndian = byteAlign === BIG_ENDIAN_BYTE_ALIGN;
+ const isLittleEndian = byteAlign === LITTLE_ENDIAN_BYTE_ALIGN;
+ if (isBigEndian || isLittleEndian) {
+ return extractOrientation(exifBlock, isBigEndian);
+ }
+}
+function validateInput(input, index) {
+ if (index > input.length) {
+ throw new TypeError("Corrupt JPG, exceeded buffer limits");
+ }
+}
+const JPG = {
+ validate: (input) => toHexString(input, 0, 2) === "ffd8",
+ calculate(input) {
+ input = input.slice(4);
+ let orientation;
+ let next;
+ while (input.length) {
+ const i = readUInt16BE(input, 0);
+ if (input[i] !== 255) {
+ input = input.slice(1);
+ continue;
+ }
+ if (isEXIF(input)) {
+ orientation = validateExifBlock(input, i);
+ }
+ validateInput(input, i);
+ next = input[i + 1];
+ if (next === 192 || next === 193 || next === 194) {
+ const size = extractSize(input, i + 5);
+ if (!orientation) {
+ return size;
+ }
+ return {
+ height: size.height,
+ orientation,
+ width: size.width
+ };
+ }
+ input = input.slice(i + 2);
+ }
+ throw new TypeError("Invalid JPG, no size found");
+ }
+};
+
+const KTX = {
+ validate: (input) => {
+ const signature = toUTF8String(input, 1, 7);
+ return ["KTX 11", "KTX 20"].includes(signature);
+ },
+ calculate: (input) => {
+ const type = input[5] === 49 ? "ktx" : "ktx2";
+ const offset = type === "ktx" ? 36 : 20;
+ return {
+ height: readUInt32LE(input, offset + 4),
+ width: readUInt32LE(input, offset),
+ type
+ };
+ }
+};
+
+const pngSignature = "PNG\r\n\n";
+const pngImageHeaderChunkName = "IHDR";
+const pngFriedChunkName = "CgBI";
+const PNG = {
+ validate(input) {
+ if (pngSignature === toUTF8String(input, 1, 8)) {
+ let chunkName = toUTF8String(input, 12, 16);
+ if (chunkName === pngFriedChunkName) {
+ chunkName = toUTF8String(input, 28, 32);
+ }
+ if (chunkName !== pngImageHeaderChunkName) {
+ throw new TypeError("Invalid PNG");
+ }
+ return true;
+ }
+ return false;
+ },
+ calculate(input) {
+ if (toUTF8String(input, 12, 16) === pngFriedChunkName) {
+ return {
+ height: readUInt32BE(input, 36),
+ width: readUInt32BE(input, 32)
+ };
+ }
+ return {
+ height: readUInt32BE(input, 20),
+ width: readUInt32BE(input, 16)
+ };
+ }
+};
+
+const PNMTypes = {
+ P1: "pbm/ascii",
+ P2: "pgm/ascii",
+ P3: "ppm/ascii",
+ P4: "pbm",
+ P5: "pgm",
+ P6: "ppm",
+ P7: "pam",
+ PF: "pfm"
+};
+const handlers = {
+ default: (lines) => {
+ let dimensions = [];
+ while (lines.length > 0) {
+ const line = lines.shift();
+ if (line[0] === "#") {
+ continue;
+ }
+ dimensions = line.split(" ");
+ break;
+ }
+ if (dimensions.length === 2) {
+ return {
+ height: parseInt(dimensions[1], 10),
+ width: parseInt(dimensions[0], 10)
+ };
+ } else {
+ throw new TypeError("Invalid PNM");
+ }
+ },
+ pam: (lines) => {
+ const size = {};
+ while (lines.length > 0) {
+ const line = lines.shift();
+ if (line.length > 16 || line.charCodeAt(0) > 128) {
+ continue;
+ }
+ const [key, value] = line.split(" ");
+ if (key && value) {
+ size[key.toLowerCase()] = parseInt(value, 10);
+ }
+ if (size.height && size.width) {
+ break;
+ }
+ }
+ if (size.height && size.width) {
+ return {
+ height: size.height,
+ width: size.width
+ };
+ } else {
+ throw new TypeError("Invalid PAM");
+ }
+ }
+};
+const PNM = {
+ validate: (input) => toUTF8String(input, 0, 2) in PNMTypes,
+ calculate(input) {
+ const signature = toUTF8String(input, 0, 2);
+ const type = PNMTypes[signature];
+ const lines = toUTF8String(input, 3).split(/[\r\n]+/);
+ const handler = handlers[type] || handlers.default;
+ return handler(lines);
+ }
+};
+
+const PSD = {
+ validate: (input) => toUTF8String(input, 0, 4) === "8BPS",
+ calculate: (input) => ({
+ height: readUInt32BE(input, 14),
+ width: readUInt32BE(input, 18)
+ })
+};
+
+const svgReg = /