Compare commits
17 Commits
c5fc1cedd7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
e2571880ce
|
|||
|
0eafcb9a67
|
|||
|
9a2e7f65cb
|
|||
|
e5110ddd75
|
|||
|
3b2abe7a99
|
|||
|
4cbe911b0c
|
|||
|
75321034aa
|
|||
|
174afb6a10
|
|||
|
483e80db79
|
|||
|
a303b8be00
|
|||
|
bb0b348069
|
|||
|
cf2195b4f3
|
|||
|
6de9c9c83b
|
|||
|
e3787281fd
|
|||
|
255abd508d
|
|||
|
07561a4335
|
|||
|
22d3b9d7df
|
@@ -12,20 +12,20 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to Container Registry
|
- name: Login to Container Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ secrets.REPO_HOST }}
|
registry: ${{ secrets.REPO_HOST }}
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.DEPLOY_TOKEN }}
|
password: ${{ secrets.DEPLOY_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
@@ -33,3 +33,6 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ github.sha }}
|
${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ github.sha }}
|
||||||
${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest
|
${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest
|
||||||
|
provenance: false
|
||||||
|
cache-from: type=registry,ref=${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:buildcache
|
||||||
|
cache-to: type=registry,ref=${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:buildcache,mode=max
|
||||||
27
Dockerfile
27
Dockerfile
@@ -1,29 +1,28 @@
|
|||||||
FROM node:24-alpine AS builder
|
FROM oven/bun:1.3.9-alpine AS base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --no-cache libc6-compat && \
|
FROM base AS prod-deps
|
||||||
corepack enable && corepack prepare pnpm@latest --activate
|
COPY package.json bun.lock ./
|
||||||
|
RUN --mount=type=cache,id=bun,target=/root/.bun/install/cache \
|
||||||
|
bun install --production --frozen-lockfile || bun install --production
|
||||||
|
|
||||||
COPY package.json pnpm-lock.yaml ./
|
FROM base AS builder
|
||||||
|
COPY package.json bun.lock ./
|
||||||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
|
RUN --mount=type=cache,id=bun,target=/root/.bun/install/cache \
|
||||||
pnpm install --frozen-lockfile || pnpm install
|
bun install --frozen-lockfile || bun install
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN pnpm run build
|
RUN bun run build
|
||||||
RUN pnpm prune --prod
|
|
||||||
|
|
||||||
FROM node:24-alpine AS runtime
|
FROM base AS runtime
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add --no-cache libc6-compat
|
|
||||||
|
|
||||||
COPY --from=builder /app/dist ./dist
|
COPY --from=builder /app/dist ./dist
|
||||||
COPY --from=builder /app/node_modules ./node_modules
|
COPY --from=prod-deps /app/node_modules ./node_modules
|
||||||
COPY package.json ./
|
COPY package.json ./
|
||||||
|
|
||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
ENV PORT=4321
|
ENV PORT=4321
|
||||||
EXPOSE 4321
|
EXPOSE 4321
|
||||||
|
|
||||||
CMD ["node", "./dist/server/entry.mjs"]
|
CMD ["bun", "run", "./dist/server/entry.mjs"]
|
||||||
@@ -22,14 +22,12 @@
|
|||||||
devShells = forAllSystems ({ pkgs }: {
|
devShells = forAllSystems ({ pkgs }: {
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
nodejs_24
|
bun
|
||||||
nodePackages.pnpm
|
|
||||||
];
|
];
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
echo "<atashdotdev dev shell>"
|
echo "<atashdotdev dev shell>"
|
||||||
echo "Node version: $(node --version)"
|
echo "Bun version: $(bun --version)"
|
||||||
echo "pnpm version: $(pnpm --version)"
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "atashdotdev",
|
"name": "atashdotdev",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.3.0",
|
"version": "2.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
@@ -9,19 +9,16 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/node": "10.0.0-beta.5",
|
"@astrojs/node": "9.5.4",
|
||||||
"@astrojs/vue": "6.0.0-beta.1",
|
"@astrojs/vue": "5.1.4",
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
|
||||||
"@fontsource-variable/roboto-slab": "^5.2.8",
|
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"astro": "6.0.0-beta.15",
|
"astro": "5.18.0",
|
||||||
"motion-v": "2.0.0",
|
|
||||||
"nodemailer": "^8.0.1",
|
"nodemailer": "^8.0.1",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.1",
|
||||||
"vue": "^3.5.29"
|
"vue": "^3.5.29"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.3.0",
|
"@types/node": "^25.3.3",
|
||||||
"@types/nodemailer": "^7.0.11",
|
"@types/nodemailer": "^7.0.11",
|
||||||
"daisyui": "^5.5.19"
|
"daisyui": "^5.5.19"
|
||||||
}
|
}
|
||||||
|
|||||||
4815
pnpm-lock.yaml
generated
4815
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
public/fonts/roboto.woff2
Normal file
BIN
public/fonts/roboto.woff2
Normal file
Binary file not shown.
@@ -22,14 +22,13 @@ import { Image } from "astro:assets";
|
|||||||
<ul
|
<ul
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="menu menu-sm dropdown-content bg-base-100 rounded-xl z-50 mt-3 w-56 p-3 shadow-xl border border-base-200"
|
class="menu menu-sm dropdown-content bg-base-100 rounded-xl z-50 mt-3 w-56 p-3 shadow-xl border border-base-200"
|
||||||
role="menu"
|
aria-label="Navigation Button"
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
siteConfig.header.nav.map(({ text, href }) => (
|
siteConfig.header.nav.map(({ text, href }) => (
|
||||||
<li role="none">
|
<li>
|
||||||
<a
|
<a
|
||||||
href={href}
|
href={href}
|
||||||
role="menuitem"
|
|
||||||
class="py-3 px-4 rounded-lg font-medium hover:bg-primary/10 hover:text-primary transition-colors"
|
class="py-3 px-4 rounded-lg font-medium hover:bg-primary/10 hover:text-primary transition-colors"
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
@@ -60,13 +59,12 @@ import { Image } from "astro:assets";
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<nav class="navbar-center hidden lg:flex" aria-label="Main navigation">
|
<nav class="navbar-center hidden lg:flex" aria-label="Main navigation">
|
||||||
<ul class="menu menu-horizontal gap-1" role="menubar">
|
<ul class="menu menu-horizontal gap-1">
|
||||||
{
|
{
|
||||||
siteConfig.header.nav.map(({ text, href }) => (
|
siteConfig.header.nav.map(({ text, href }) => (
|
||||||
<li role="none">
|
<li>
|
||||||
<a
|
<a
|
||||||
href={href}
|
href={href}
|
||||||
role="menuitem"
|
|
||||||
class="font-medium px-4 py-2 rounded-lg hover:bg-primary/10 hover:text-primary transition-colors"
|
class="font-medium px-4 py-2 rounded-lg hover:bg-primary/10 hover:text-primary transition-colors"
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
@@ -80,7 +78,7 @@ import { Image } from "astro:assets";
|
|||||||
<a
|
<a
|
||||||
href={siteConfig.header.cta.href}
|
href={siteConfig.header.cta.href}
|
||||||
class="btn btn-primary btn-sm lg:btn-md shadow-md hover:shadow-lg transition-all"
|
class="btn btn-primary btn-sm lg:btn-md shadow-md hover:shadow-lg transition-all"
|
||||||
role="button"
|
aria-label={siteConfig.header.cta.text}
|
||||||
>
|
>
|
||||||
{siteConfig.header.cta.text}
|
{siteConfig.header.cta.text}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
interface Props {
|
|
||||||
items: Array<{
|
|
||||||
text: string;
|
|
||||||
className: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { items } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<span class="text-rotate duration-9000">
|
|
||||||
<span>
|
|
||||||
{
|
|
||||||
items.map((item) => (
|
|
||||||
<span class:list={["mx-auto text-center", item.className]}>
|
|
||||||
{item.text}
|
|
||||||
</span>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
33
src/components/RotatingText.vue
Normal file
33
src/components/RotatingText.vue
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<span class="block w-full my-2">
|
||||||
|
<span class="text-rotate">
|
||||||
|
<span class="justify-items-center">
|
||||||
|
<span
|
||||||
|
v-for="(item, index) in items"
|
||||||
|
:key="index"
|
||||||
|
:class="item.className"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
interface RotatingTextItem {
|
||||||
|
text: string;
|
||||||
|
className: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
items: RotatingTextItem[];
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.text-rotate:hover,
|
||||||
|
.text-rotate:hover * {
|
||||||
|
animation-play-state: running !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -77,7 +77,9 @@ ${message.value}`,
|
|||||||
</legend>
|
</legend>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
id="firstName"
|
||||||
name="firstName"
|
name="firstName"
|
||||||
|
:aria-label="siteConfig.contact.form.firstName"
|
||||||
class="input input-bordered w-full bg-base-100 focus:border-primary focus:outline-primary"
|
class="input input-bordered w-full bg-base-100 focus:border-primary focus:outline-primary"
|
||||||
required
|
required
|
||||||
v-model="firstName"
|
v-model="firstName"
|
||||||
@@ -96,7 +98,9 @@ ${message.value}`,
|
|||||||
</legend>
|
</legend>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
id="lastName"
|
||||||
name="lastName"
|
name="lastName"
|
||||||
|
:aria-label="siteConfig.contact.form.lastName"
|
||||||
class="input input-bordered w-full bg-base-100 focus:border-primary focus:outline-primary"
|
class="input input-bordered w-full bg-base-100 focus:border-primary focus:outline-primary"
|
||||||
required
|
required
|
||||||
v-model="lastName"
|
v-model="lastName"
|
||||||
@@ -117,7 +121,9 @@ ${message.value}`,
|
|||||||
</legend>
|
</legend>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
|
id="email"
|
||||||
name="email"
|
name="email"
|
||||||
|
:aria-label="siteConfig.contact.form.email"
|
||||||
class="input input-bordered w-full bg-base-100 focus:border-primary focus:outline-primary"
|
class="input input-bordered w-full bg-base-100 focus:border-primary focus:outline-primary"
|
||||||
required
|
required
|
||||||
v-model="email"
|
v-model="email"
|
||||||
@@ -136,7 +142,9 @@ ${message.value}`,
|
|||||||
</legend>
|
</legend>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
id="company"
|
||||||
name="company"
|
name="company"
|
||||||
|
:aria-label="siteConfig.contact.form.company"
|
||||||
class="input input-bordered w-full bg-base-100 focus:border-primary focus:outline-primary"
|
class="input input-bordered w-full bg-base-100 focus:border-primary focus:outline-primary"
|
||||||
v-model="company"
|
v-model="company"
|
||||||
:disabled="status === 'sending'"
|
:disabled="status === 'sending'"
|
||||||
@@ -215,6 +223,7 @@ ${message.value}`,
|
|||||||
<textarea
|
<textarea
|
||||||
id="project-details"
|
id="project-details"
|
||||||
name="message"
|
name="message"
|
||||||
|
:aria-label="siteConfig.contact.form.message"
|
||||||
class="textarea textarea-bordered h-36 w-full resize-none bg-base-100 focus:border-primary focus:outline-primary"
|
class="textarea textarea-bordered h-36 w-full resize-none bg-base-100 focus:border-primary focus:outline-primary"
|
||||||
:placeholder="
|
:placeholder="
|
||||||
siteConfig.contact.form.placeholders.message
|
siteConfig.contact.form.placeholders.message
|
||||||
@@ -230,12 +239,12 @@ ${message.value}`,
|
|||||||
class="btn btn-lg w-full shadow-lg transition-all duration-300"
|
class="btn btn-lg w-full shadow-lg transition-all duration-300"
|
||||||
:class="[
|
:class="[
|
||||||
status === 'success'
|
status === 'success'
|
||||||
? 'btn-success text-white'
|
? 'btn-success text-white pointer-events-none'
|
||||||
: status === 'error'
|
: status === 'error'
|
||||||
? 'btn-error text-white'
|
? 'btn-error text-white pointer-events-none'
|
||||||
: 'btn-primary',
|
: 'btn-primary',
|
||||||
]"
|
]"
|
||||||
:disabled="status === 'sending' || status === 'success'"
|
:disabled="status === 'sending'"
|
||||||
>
|
>
|
||||||
<template v-if="status === 'sending'">
|
<template v-if="status === 'sending'">
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { siteConfig } from "../../config/site";
|
import { siteConfig } from "../../config/site";
|
||||||
import Icon from "../Icon.astro";
|
import Icon from "../Icon.astro";
|
||||||
import Section from "../Section.astro";
|
import Section from "../Section.astro";
|
||||||
import RotatingText from "../RotatingText.astro";
|
import RotatingText from "../RotatingText.vue";
|
||||||
import StatusIndicator from "../StatusIndicator.vue";
|
import StatusIndicator from "../StatusIndicator.vue";
|
||||||
|
|
||||||
const rotatingText = (siteConfig.hero as any).rotatingText as
|
const rotatingText = (siteConfig.hero as any).rotatingText as
|
||||||
@@ -22,7 +22,7 @@ const rotatingText = (siteConfig.hero as any).rotatingText as
|
|||||||
|
|
||||||
<div class="relative max-w-7xl mx-auto px-6">
|
<div class="relative max-w-7xl mx-auto px-6">
|
||||||
<div class="text-center max-w-4xl mx-auto">
|
<div class="text-center max-w-4xl mx-auto">
|
||||||
<StatusIndicator client:load />
|
<StatusIndicator client:idle />
|
||||||
|
|
||||||
<h1
|
<h1
|
||||||
class="text-4xl sm:text-5xl lg:text-6xl xl:text-7xl font-extrabold text-white leading-tight tracking-tight mb-6"
|
class="text-4xl sm:text-5xl lg:text-6xl xl:text-7xl font-extrabold text-white leading-tight tracking-tight mb-6"
|
||||||
@@ -30,7 +30,7 @@ const rotatingText = (siteConfig.hero as any).rotatingText as
|
|||||||
{
|
{
|
||||||
rotatingText ? (
|
rotatingText ? (
|
||||||
<>
|
<>
|
||||||
<RotatingText items={rotatingText} />
|
<RotatingText items={rotatingText} client:idle />
|
||||||
<span class="block">
|
<span class="block">
|
||||||
{siteConfig.hero.mainTitle
|
{siteConfig.hero.mainTitle
|
||||||
.replace("{rotating}", "")
|
.replace("{rotating}", "")
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export const siteConfig = {
|
|||||||
variant: "primary",
|
variant: "primary",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Slop-free",
|
title: "Slop-free Guarantee",
|
||||||
content: "Hand-crafted code built with care",
|
content: "Hand-crafted code built with care",
|
||||||
variant: "secondary",
|
variant: "secondary",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import Header from "../components/Header.astro";
|
import Header from "../components/Header.astro";
|
||||||
import Footer from "../components/Footer.astro";
|
import Footer from "../components/Footer.astro";
|
||||||
import { siteConfig } from "../config/site";
|
import { siteConfig } from "../config/site";
|
||||||
import "@fontsource-variable/roboto-slab";
|
|
||||||
import "../styles/global.css";
|
import "../styles/global.css";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -63,6 +62,15 @@ const isProd = import.meta.env.PROD;
|
|||||||
/>
|
/>
|
||||||
<meta property="og:image:type" content={resolvedOgImage.type} />
|
<meta property="og:image:type" content={resolvedOgImage.type} />
|
||||||
<meta property="og:image:alt" content={resolvedOgImage.alt} />
|
<meta property="og:image:alt" content={resolvedOgImage.alt} />
|
||||||
|
<meta name="theme-color" content="#ffffff" />
|
||||||
|
<link rel="canonical" href={siteUrl} />
|
||||||
|
<link
|
||||||
|
rel="preload"
|
||||||
|
href="/fonts/roboto.woff2"
|
||||||
|
as="font"
|
||||||
|
type="font/woff2"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
<title>{metaTitle}</title>
|
<title>{metaTitle}</title>
|
||||||
{
|
{
|
||||||
isProd && (
|
isProd && (
|
||||||
@@ -85,7 +93,7 @@ const isProd = import.meta.env.PROD;
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen flex flex-col bg-base-100 font-sans antialiased">
|
<body class="min-h-screen flex flex-col bg-base-100 antialiased">
|
||||||
<Header />
|
<Header />
|
||||||
<main class="grow flex flex-col">
|
<main class="grow flex flex-col">
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const GET: APIRoute = async () => {
|
|||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
"Cache-Control": "public, max-age=60, stale-while-revalidate=300",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,6 +40,14 @@ html {
|
|||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
@font-face {
|
||||||
font-family: "Roboto Slab Variable", serif;
|
font-family: "Roboto Slab";
|
||||||
|
src: url("/fonts/roboto.woff2") format("woff2");
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Roboto Slab", serif;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user