feat: track images with git lfs
This commit is contained in:
178
src/components/GoogleyEye.svelte
Normal file
178
src/components/GoogleyEye.svelte
Normal file
@ -0,0 +1,178 @@
|
||||
<script context="module" lang="ts">
|
||||
let mouse = { x: 0, y: 0 };
|
||||
|
||||
let visible = writable(false);
|
||||
|
||||
const maxDistance = globalThis.innerWidth / 3;
|
||||
let frameId: number;
|
||||
function update() {
|
||||
frameId = requestAnimationFrame(update);
|
||||
for (const eye of eyes) {
|
||||
const dx = eye.x - mouse.x;
|
||||
const dy = eye.y - mouse.y;
|
||||
const angle = Math.atan2(dy, dx);
|
||||
|
||||
const distance = Math.hypot(dx, dy);
|
||||
if (distance < maxDistance) {
|
||||
eye.rotation = angle * (180 / Math.PI) - 90;
|
||||
eye.distance = -Math.min(17, distance);
|
||||
eye.el.style.setProperty("--distance", `${eye.distance}px`);
|
||||
eye.el.style.setProperty(
|
||||
"--rotation",
|
||||
`${Math.floor(eye.rotation)}deg`,
|
||||
);
|
||||
eye.el.classList.add("active");
|
||||
} else {
|
||||
eye.el.classList.remove("active");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const eyes: {
|
||||
x: number;
|
||||
y: number;
|
||||
rotation: number;
|
||||
distance: number;
|
||||
el: HTMLDivElement;
|
||||
pupil: HTMLDivElement;
|
||||
}[] = [];
|
||||
|
||||
function register(eye: HTMLDivElement) {
|
||||
const { x, y, width, height } = eye.getBoundingClientRect();
|
||||
eyes.push({
|
||||
x: x + width / 2,
|
||||
y: y + height / 2,
|
||||
rotation: 0,
|
||||
distance: 0,
|
||||
el: eye,
|
||||
pupil: eye.children[0] as HTMLDivElement,
|
||||
});
|
||||
if (frameId) cancelAnimationFrame(frameId);
|
||||
update();
|
||||
}
|
||||
|
||||
function unregister(eye: HTMLDivElement) {
|
||||
const index = eyes.findIndex((e) => e.el === eye);
|
||||
eyes.splice(index, 1);
|
||||
if (eyes.length === 0) cancelAnimationFrame(frameId);
|
||||
}
|
||||
|
||||
function handleMouseMove(ev: MouseEvent) {
|
||||
mouse.x = ev.clientX;
|
||||
mouse.y = ev.clientY;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
import { scale } from "svelte/transition";
|
||||
|
||||
let eye: HTMLDivElement;
|
||||
let _old_eye: HTMLDivElement;
|
||||
$: if (eye !== _old_eye) {
|
||||
if (_old_eye) {
|
||||
unregister(_old_eye);
|
||||
}
|
||||
if (eye) {
|
||||
register(eye);
|
||||
}
|
||||
_old_eye = eye;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
return () => {
|
||||
unregister(eye);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:mousemove={handleMouseMove} />
|
||||
|
||||
<div
|
||||
class="googley-eyes"
|
||||
on:click={() => ($visible = !$visible)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Toggle Googley Eyes"
|
||||
aria-hidden="true"
|
||||
on:keydown={(ev) => (ev.key === "Enter" ? ($visible = !$visible) : "")}
|
||||
>
|
||||
{#if $visible}
|
||||
<div class="eye" bind:this={eye} transition:scale>
|
||||
<div class="pupil"></div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.googley-eyes {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.eye {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transform: rotate(var(--rotation));
|
||||
box-shadow: 0px 0px 30px #00000094;
|
||||
outline: solid 1px #737373;
|
||||
}
|
||||
|
||||
.eye::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(200, 200, 200, 0.01);
|
||||
transform: rotate(calc(var(--rotation) * -1));
|
||||
box-shadow:
|
||||
5px 5px 10px #ffffff70 inset,
|
||||
1px 1px 4px #ffffff96 inset,
|
||||
-2px -2px 10px black inset,
|
||||
2px 2px 5px #00000078;
|
||||
}
|
||||
|
||||
.googley-eyes > :global(.active > .pupil) {
|
||||
transform: translateY(var(--distance)) scale(0.7);
|
||||
}
|
||||
|
||||
.pupil {
|
||||
position: absolute;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
border-radius: 50%;
|
||||
transition: transform cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.2s;
|
||||
}
|
||||
|
||||
.pupil::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: black;
|
||||
box-shadow:
|
||||
-5px -5px 10px #ffffff70 inset,
|
||||
-1px -1px 4px #ffffff96 inset,
|
||||
2px 2px 2px black inset,
|
||||
-2px -2px 5px #00000078;
|
||||
transform: translate(-50%, -50%) rotate(calc(var(--rotation) * -1 + 180deg));
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
</style>
|
46
src/components/Image.astro
Normal file
46
src/components/Image.astro
Normal file
@ -0,0 +1,46 @@
|
||||
---
|
||||
import type { ImageMetadata } from "astro";
|
||||
import { Image as AstroImage } from "astro:assets";
|
||||
interface Props {
|
||||
src: ImageMetadata;
|
||||
alt: string;
|
||||
maxWidth?: number;
|
||||
}
|
||||
|
||||
const { src, alt, maxWidth } = Astro.props;
|
||||
|
||||
const image = typeof src === "string" ? await import(src) : src;
|
||||
|
||||
const sizes = [
|
||||
{
|
||||
width: 240,
|
||||
media: "(max-width: 360px)",
|
||||
},
|
||||
{
|
||||
width: 540,
|
||||
media: "(max-width: 720px)",
|
||||
},
|
||||
{
|
||||
width: 720,
|
||||
media: "(max-width: 1600px)",
|
||||
},
|
||||
{
|
||||
width: image.width,
|
||||
},
|
||||
].filter((size) => !maxWidth || size.width <= maxWidth);
|
||||
---
|
||||
|
||||
<AstroImage
|
||||
src={image}
|
||||
alt={alt}
|
||||
widths={sizes.map((size) => size.width)}
|
||||
sizes={sizes
|
||||
.map((size) => `${size.media || "100vw"} ${size.width}px`)
|
||||
.join(", ")}
|
||||
/>
|
||||
|
||||
<style>
|
||||
img {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
</style>
|
@ -2,11 +2,18 @@
|
||||
import { locales, defaultLocale, getLocale } from "astro-i18n-aut";
|
||||
import { useTranslations } from "../i18n/utils";
|
||||
|
||||
const reg = new RegExp(`^\/(${Object.keys(locales).join("|")})\/`);
|
||||
function translatePath(lang: string) {
|
||||
const p = Astro.url.pathname.replace(reg, "").replace(/^\//, "");
|
||||
if (lang === defaultLocale) return `/${p}`;
|
||||
return `/${lang}/${p}`;
|
||||
const split = Astro.url.pathname.split("/").filter((s) => s.length);
|
||||
|
||||
if (split[0] in locales) {
|
||||
split.shift();
|
||||
}
|
||||
|
||||
if (lang === defaultLocale) {
|
||||
return `/${split.join("/")}`;
|
||||
}
|
||||
|
||||
return `/${[lang, ...split].join("/")}`;
|
||||
}
|
||||
|
||||
const locale = getLocale(Astro.url);
|
||||
|
@ -1,8 +1,9 @@
|
||||
<svg
|
||||
width="51"
|
||||
height="51"
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="0 0 51 51"
|
||||
fill="none"
|
||||
style="height: 100%; width: auto"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
86
src/components/Max.astro
Normal file
86
src/components/Max.astro
Normal file
@ -0,0 +1,86 @@
|
||||
---
|
||||
import MaxImg from "./Max.png";
|
||||
import Image from "./Image.astro";
|
||||
import GoogleyEye from "./GoogleyEye.svelte";
|
||||
|
||||
import { useTranslations } from "@i18n/utils";
|
||||
|
||||
import { getLocale } from "astro-i18n-aut";
|
||||
|
||||
const locale = getLocale(Astro.url);
|
||||
|
||||
const t = useTranslations(locale);
|
||||
---
|
||||
|
||||
<section class="googley-eye-target">
|
||||
<div class="image">
|
||||
<Image src={MaxImg} alt="its mee" maxWidth={700} />
|
||||
<div class="eye right">
|
||||
<GoogleyEye client:load />
|
||||
</div>
|
||||
<div class="eye left">
|
||||
<GoogleyEye client:load />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<h1>{t("home.title")}</h1>
|
||||
<p>{t("home.subtitle")}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.image {
|
||||
position: relative;
|
||||
height: 130%;
|
||||
align-self: end;
|
||||
overflow: hidden;
|
||||
border-bottom-left-radius: 20px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.image > :global(img) {
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
object-position: top;
|
||||
}
|
||||
|
||||
section {
|
||||
position: relative;
|
||||
margin-top: 100px;
|
||||
height: 180px;
|
||||
background-color: var(--background);
|
||||
border-radius: 0px 20px 0px 20px;
|
||||
border: solid thin var(--outline);
|
||||
display: grid;
|
||||
grid-template-columns: 300px 1fr;
|
||||
background: var(--background-gradient);
|
||||
background: linear-gradient(
|
||||
150deg,
|
||||
var(--neutral400) 0%,
|
||||
var(--neutral500) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.eye {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.eye.left {
|
||||
top: 25%;
|
||||
right: 30%;
|
||||
}
|
||||
.eye.right {
|
||||
top: 27%;
|
||||
right: 15%;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 30px;
|
||||
padding-left: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
BIN
src/components/Max.png
(Stored with Git LFS)
Executable file
BIN
src/components/Max.png
(Stored with Git LFS)
Executable file
Binary file not shown.
@ -3,17 +3,43 @@ import { getLocale } from "astro-i18n-aut";
|
||||
import { useTranslations, useTranslatedPath } from "../i18n/utils";
|
||||
import Logo from "./Logo.astro";
|
||||
|
||||
function isActive(path) {
|
||||
return Astro.url.pathname === path ? "active" : "";
|
||||
}
|
||||
|
||||
const lang = getLocale(Astro.url);
|
||||
const t = useTranslations(lang);
|
||||
const translatePath = useTranslatedPath(lang);
|
||||
const paths = [
|
||||
{
|
||||
link: translatePath("/"),
|
||||
component: Logo,
|
||||
},
|
||||
{
|
||||
link: translatePath("/blog"),
|
||||
text: t("nav.blog"),
|
||||
},
|
||||
{
|
||||
link: translatePath("/projects"),
|
||||
text: t("nav.projects"),
|
||||
},
|
||||
{
|
||||
link: translatePath("/photos"),
|
||||
text: t("nav.photos"),
|
||||
},
|
||||
{
|
||||
link: translatePath("/videos"),
|
||||
text: t("nav.videos"),
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<style>
|
||||
ul {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
height: 50px;
|
||||
}
|
||||
li {
|
||||
display: flex;
|
||||
@ -22,26 +48,71 @@ const translatePath = useTranslatedPath(lang);
|
||||
a {
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
ul > li {
|
||||
border: solid thin var(--outline);
|
||||
border-left: none;
|
||||
position: relative;
|
||||
background: var(--background);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
ul > li.logo {
|
||||
flex: unset;
|
||||
border: none;
|
||||
padding: 0px;
|
||||
background: none;
|
||||
height: 34px;
|
||||
margin: 8px;
|
||||
margin-left: 0px;
|
||||
--fill: #cb5a5a;
|
||||
--background-fill: none;
|
||||
}
|
||||
|
||||
.logo > a {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
ul > li > a {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
ul > li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ul > li.active {
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
ul > li:nth-child(2) {
|
||||
border-radius: 0px 0px 0px 10px;
|
||||
border-left: solid thin var(--outline);
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
ul > li:last-child {
|
||||
border-radius: 0px 10px 0px 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href={translatePath("/")}
|
||||
style="--fill: red; --background-fill: transparent;"
|
||||
>
|
||||
<Logo />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href={translatePath("/blog")}>
|
||||
{t("nav.blog")}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href={translatePath("/about")}>
|
||||
{t("nav.about")}
|
||||
</a>
|
||||
</li>
|
||||
{
|
||||
paths.map(({ link, text, component }, index) => (
|
||||
<li
|
||||
class={`${component ? "logo" : ""} ${isActive(link) ? "active" : ""}`}
|
||||
>
|
||||
<a href={link}>{component ? <Logo /> : text}</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
19
src/components/YouTube.astro
Normal file
19
src/components/YouTube.astro
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
interface Props {
|
||||
id: string;
|
||||
}
|
||||
---
|
||||
|
||||
<iframe
|
||||
width="420"
|
||||
height="315"
|
||||
src={`https://www.youtube.com/embed/${Astro.props.id}`}></iframe>
|
||||
|
||||
<style>
|
||||
iframe {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 315px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user