Compare commits
42 Commits
332794d62c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
e2571880ce
|
|||
|
0eafcb9a67
|
|||
|
9a2e7f65cb
|
|||
|
e5110ddd75
|
|||
|
3b2abe7a99
|
|||
|
4cbe911b0c
|
|||
|
75321034aa
|
|||
|
174afb6a10
|
|||
|
483e80db79
|
|||
|
a303b8be00
|
|||
|
bb0b348069
|
|||
|
cf2195b4f3
|
|||
|
6de9c9c83b
|
|||
|
e3787281fd
|
|||
|
255abd508d
|
|||
|
07561a4335
|
|||
|
22d3b9d7df
|
|||
|
c5fc1cedd7
|
|||
|
cbdab153da
|
|||
|
6dea3ac96c
|
|||
|
8c5556eb3c
|
|||
|
50d8f2a9aa
|
|||
|
e9845965d7
|
|||
|
737a4dd7e8
|
|||
|
c31d0b5589
|
|||
|
58902e081a
|
|||
|
62dcec8202
|
|||
|
b15dce4cd4
|
|||
|
25af507805
|
|||
|
8b8b60b302
|
|||
|
59577f0e58
|
|||
|
8bd4ccbafb
|
|||
|
beaf088391
|
|||
|
f8555413e1
|
|||
|
7c24cf61d5
|
|||
|
f70ce24bcb
|
|||
|
a089c9dfc7
|
|||
|
c03128a314
|
|||
|
cf4a4827df
|
|||
|
f7fd011660
|
|||
|
3b2fca97aa
|
|||
|
1ddd73431b
|
@@ -12,20 +12,20 @@ jobs:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ secrets.REPO_HOST }}
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.DEPLOY_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
@@ -33,3 +33,6 @@ jobs:
|
||||
tags: |
|
||||
${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ github.sha }}
|
||||
${{ 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,27 +1,28 @@
|
||||
FROM node:lts-alpine AS builder
|
||||
FROM oven/bun:1.3.9-alpine AS base
|
||||
WORKDIR /app
|
||||
|
||||
RUN npm i -g pnpm
|
||||
FROM base AS prod-deps
|
||||
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 ./
|
||||
|
||||
RUN pnpm install
|
||||
FROM base AS builder
|
||||
COPY package.json bun.lock ./
|
||||
RUN --mount=type=cache,id=bun,target=/root/.bun/install/cache \
|
||||
bun install --frozen-lockfile || bun install
|
||||
|
||||
COPY . .
|
||||
RUN pnpm run build
|
||||
RUN bun run build
|
||||
|
||||
FROM node:lts-alpine AS runtime
|
||||
FROM base AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
RUN npm i -g pnpm
|
||||
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
RUN pnpm install --prod
|
||||
COPY --from=prod-deps /app/node_modules ./node_modules
|
||||
COPY package.json ./
|
||||
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=4321
|
||||
EXPOSE 4321
|
||||
|
||||
CMD ["node", "./dist/server/entry.mjs"]
|
||||
CMD ["bun", "run", "./dist/server/entry.mjs"]
|
||||
@@ -1,22 +1,17 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from "astro/config";
|
||||
|
||||
import vue from "@astrojs/vue";
|
||||
|
||||
import node from "@astrojs/node";
|
||||
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
import icon from "astro-icon";
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
output: "server",
|
||||
prefetch: true,
|
||||
build: {
|
||||
inlineStylesheets: "always",
|
||||
inlineStylesheets: "auto",
|
||||
},
|
||||
integrations: [vue(), icon()],
|
||||
integrations: [vue()],
|
||||
|
||||
adapter: node({
|
||||
mode: "standalone",
|
||||
|
||||
@@ -22,14 +22,12 @@
|
||||
devShells = forAllSystems ({ pkgs }: {
|
||||
default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
nodejs_24
|
||||
nodePackages.pnpm
|
||||
bun
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
echo "<atashdotdev dev shell>"
|
||||
echo "Node version: $(node --version)"
|
||||
echo "pnpm version: $(pnpm --version)"
|
||||
echo "Bun version: $(bun --version)"
|
||||
'';
|
||||
};
|
||||
});
|
||||
|
||||
27
package.json
27
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atashdotdev",
|
||||
"type": "module",
|
||||
"version": "1.3.0",
|
||||
"version": "2.0.0",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
@@ -9,22 +9,17 @@
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^9.5.2",
|
||||
"@astrojs/vue": "^5.1.4",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@fontsource-variable/roboto-slab": "^5.2.8",
|
||||
"@heroicons/vue": "^2.2.0",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"astro": "^5.17.1",
|
||||
"astro-icon": "^1.1.5",
|
||||
"nodemailer": "^7.0.13",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"vue": "^3.5.27"
|
||||
"@astrojs/node": "9.5.4",
|
||||
"@astrojs/vue": "5.1.4",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"astro": "5.18.0",
|
||||
"nodemailer": "^8.0.1",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"vue": "^3.5.29"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/heroicons": "^1.2.3",
|
||||
"@types/node": "^25.1.0",
|
||||
"@types/nodemailer": "^7.0.9",
|
||||
"daisyui": "^5.5.14"
|
||||
"@types/node": "^25.3.3",
|
||||
"@types/nodemailer": "^7.0.11",
|
||||
"daisyui": "^5.5.19"
|
||||
}
|
||||
}
|
||||
|
||||
5193
pnpm-lock.yaml
generated
5193
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
public/clients/MarewConsulting.webp
Normal file
BIN
public/clients/MarewConsulting.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
public/clients/SaikyoSoftworks.webp
Normal file
BIN
public/clients/SaikyoSoftworks.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 291 KiB |
BIN
public/fonts/roboto.woff2
Normal file
BIN
public/fonts/roboto.woff2
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
---
|
||||
import { siteConfig } from "../config/site";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import Icon from "./Icon.astro";
|
||||
import { Image } from "astro:assets";
|
||||
---
|
||||
|
||||
@@ -17,19 +17,18 @@ import { Image } from "astro:assets";
|
||||
class="btn btn-ghost btn-circle lg:hidden"
|
||||
aria-label="Open menu"
|
||||
>
|
||||
<Icon name="heroicons:bars-3-bottom-left" class="h-5 w-5" />
|
||||
<Icon name="bars-3-bottom-left" class="h-5 w-5" />
|
||||
</div>
|
||||
<ul
|
||||
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"
|
||||
role="menu"
|
||||
aria-label="Navigation Button"
|
||||
>
|
||||
{
|
||||
siteConfig.header.nav.map(({ text, href }) => (
|
||||
<li role="none">
|
||||
<li>
|
||||
<a
|
||||
href={href}
|
||||
role="menuitem"
|
||||
class="py-3 px-4 rounded-lg font-medium hover:bg-primary/10 hover:text-primary transition-colors"
|
||||
>
|
||||
{text}
|
||||
@@ -60,13 +59,12 @@ import { Image } from "astro:assets";
|
||||
</a>
|
||||
</div>
|
||||
<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 }) => (
|
||||
<li role="none">
|
||||
<li>
|
||||
<a
|
||||
href={href}
|
||||
role="menuitem"
|
||||
class="font-medium px-4 py-2 rounded-lg hover:bg-primary/10 hover:text-primary transition-colors"
|
||||
>
|
||||
{text}
|
||||
@@ -80,7 +78,7 @@ import { Image } from "astro:assets";
|
||||
<a
|
||||
href={siteConfig.header.cta.href}
|
||||
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}
|
||||
</a>
|
||||
|
||||
27
src/components/Icon.astro
Normal file
27
src/components/Icon.astro
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
import { icons, type IconName } from "../config/icons";
|
||||
|
||||
interface Props {
|
||||
name: IconName;
|
||||
class?: string;
|
||||
"class:list"?: any;
|
||||
}
|
||||
|
||||
const { name, class: className, "class:list": classList } = Astro.props;
|
||||
const svg = icons[name];
|
||||
|
||||
if (!svg) {
|
||||
throw new Error(`Icon "${name}" not found in icon registry`);
|
||||
}
|
||||
---
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="none"
|
||||
class:list={[className, classList]}
|
||||
aria-hidden="true"
|
||||
set:html={svg}
|
||||
/>
|
||||
30
src/components/Icon.vue
Normal file
30
src/components/Icon.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { icons, type IconName } from "../config/icons";
|
||||
|
||||
const props = defineProps<{
|
||||
name: IconName;
|
||||
class?: string;
|
||||
}>();
|
||||
|
||||
const svg = computed(() => {
|
||||
const content = icons[props.name];
|
||||
if (!content) {
|
||||
console.error(`Icon "${props.name}" not found in icon registry`);
|
||||
}
|
||||
return content;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="none"
|
||||
:class="props.class"
|
||||
aria-hidden="true"
|
||||
v-html="svg"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
items: {
|
||||
text: string;
|
||||
className: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
const { items } = Astro.props;
|
||||
---
|
||||
|
||||
<span class="block w-full my-2">
|
||||
<span class="text-rotate">
|
||||
<span class="justify-items-center">
|
||||
{
|
||||
items.map((item) => (
|
||||
<span class={item.className}>{item.text}</span>
|
||||
))
|
||||
}
|
||||
</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>
|
||||
@@ -1,19 +1,25 @@
|
||||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import Icon from "../Icon.astro";
|
||||
import { type IconName } from "../../config/icons";
|
||||
import { siteConfig } from "../../config/site";
|
||||
import Section from "../Section.astro";
|
||||
|
||||
const features = [
|
||||
const features: {
|
||||
icon: IconName;
|
||||
variant: string;
|
||||
title: string;
|
||||
content: string;
|
||||
}[] = [
|
||||
{
|
||||
icon: "heroicons:bolt",
|
||||
icon: "bolt",
|
||||
...siteConfig.whyUs.cards[0],
|
||||
},
|
||||
{
|
||||
icon: "heroicons:check-circle",
|
||||
icon: "slopfree",
|
||||
...siteConfig.whyUs.cards[1],
|
||||
},
|
||||
{
|
||||
icon: "heroicons:users",
|
||||
icon: "users",
|
||||
...siteConfig.whyUs.cards[2],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -22,7 +22,7 @@ import { siteConfig } from "../../config/site";
|
||||
pauseOnHover={false}
|
||||
scaleOnHover={true}
|
||||
fadeOut={true}
|
||||
ariaLabel="Scrolling list of client logos"
|
||||
ariaLabel="Scrolling list of client logos."
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import {
|
||||
XCircleIcon,
|
||||
CheckCircleIcon,
|
||||
PaperAirplaneIcon,
|
||||
EnvelopeIcon,
|
||||
} from "@heroicons/vue/24/outline";
|
||||
import Icon from "../Icon.vue";
|
||||
import { siteConfig } from "../../config/site";
|
||||
|
||||
const firstName = ref("");
|
||||
@@ -82,7 +77,9 @@ ${message.value}`,
|
||||
</legend>
|
||||
<input
|
||||
type="text"
|
||||
id="firstName"
|
||||
name="firstName"
|
||||
:aria-label="siteConfig.contact.form.firstName"
|
||||
class="input input-bordered w-full bg-base-100 focus:border-primary focus:outline-primary"
|
||||
required
|
||||
v-model="firstName"
|
||||
@@ -101,7 +98,9 @@ ${message.value}`,
|
||||
</legend>
|
||||
<input
|
||||
type="text"
|
||||
id="lastName"
|
||||
name="lastName"
|
||||
:aria-label="siteConfig.contact.form.lastName"
|
||||
class="input input-bordered w-full bg-base-100 focus:border-primary focus:outline-primary"
|
||||
required
|
||||
v-model="lastName"
|
||||
@@ -122,7 +121,9 @@ ${message.value}`,
|
||||
</legend>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
:aria-label="siteConfig.contact.form.email"
|
||||
class="input input-bordered w-full bg-base-100 focus:border-primary focus:outline-primary"
|
||||
required
|
||||
v-model="email"
|
||||
@@ -141,7 +142,9 @@ ${message.value}`,
|
||||
</legend>
|
||||
<input
|
||||
type="text"
|
||||
id="company"
|
||||
name="company"
|
||||
:aria-label="siteConfig.contact.form.company"
|
||||
class="input input-bordered w-full bg-base-100 focus:border-primary focus:outline-primary"
|
||||
v-model="company"
|
||||
:disabled="status === 'sending'"
|
||||
@@ -220,6 +223,7 @@ ${message.value}`,
|
||||
<textarea
|
||||
id="project-details"
|
||||
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"
|
||||
:placeholder="
|
||||
siteConfig.contact.form.placeholders.message
|
||||
@@ -235,12 +239,12 @@ ${message.value}`,
|
||||
class="btn btn-lg w-full shadow-lg transition-all duration-300"
|
||||
:class="[
|
||||
status === 'success'
|
||||
? 'btn-success text-white shadow-success/25'
|
||||
? 'btn-success text-white pointer-events-none'
|
||||
: status === 'error'
|
||||
? 'btn-error text-white shadow-error/25'
|
||||
: 'btn-primary shadow-primary/25 hover:shadow-xl hover:shadow-primary/30',
|
||||
? 'btn-error text-white pointer-events-none'
|
||||
: 'btn-primary',
|
||||
]"
|
||||
:disabled="status === 'sending' || status === 'success'"
|
||||
:disabled="status === 'sending'"
|
||||
>
|
||||
<template v-if="status === 'sending'">
|
||||
<span
|
||||
@@ -249,15 +253,15 @@ ${message.value}`,
|
||||
{{ siteConfig.contact.form.sending }}
|
||||
</template>
|
||||
<template v-else-if="status === 'success'">
|
||||
<CheckCircleIcon class="w-5 h-5" />
|
||||
<Icon name="check-circle" class="w-5 h-5" />
|
||||
{{ siteConfig.contact.form.success }}
|
||||
</template>
|
||||
<template v-else-if="status === 'error'">
|
||||
<XCircleIcon class="w-5 h-5" />
|
||||
<Icon name="x-circle" class="w-5 h-5" />
|
||||
{{ errorMessage || siteConfig.contact.form.error }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<PaperAirplaneIcon class="w-5 h-5" />
|
||||
<Icon name="paper-airplane" class="w-5 h-5" />
|
||||
{{ siteConfig.contact.form.submit }}
|
||||
</template>
|
||||
</button>
|
||||
@@ -273,7 +277,7 @@ ${message.value}`,
|
||||
:href="`mailto:${siteConfig.contact.direct.email}`"
|
||||
class="link font-medium inline-flex items-center gap-2 text-base-content hover:text-primary"
|
||||
>
|
||||
<EnvelopeIcon class="w-5 h-5" />
|
||||
<Icon name="envelope" class="w-5 h-5" />
|
||||
{{ siteConfig.contact.direct.email }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
import { siteConfig } from "../../config/site";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import Icon from "../Icon.astro";
|
||||
import Section from "../Section.astro";
|
||||
import RotatingText from "../RotatingText.astro";
|
||||
import RotatingText from "../RotatingText.vue";
|
||||
import StatusIndicator from "../StatusIndicator.vue";
|
||||
|
||||
const rotatingText = (siteConfig.hero as any).rotatingText as
|
||||
@@ -22,29 +22,31 @@ const rotatingText = (siteConfig.hero as any).rotatingText as
|
||||
|
||||
<div class="relative max-w-7xl mx-auto px-6">
|
||||
<div class="text-center max-w-4xl mx-auto">
|
||||
<StatusIndicator client:load />
|
||||
<StatusIndicator client:idle />
|
||||
|
||||
<h1
|
||||
class="text-4xl sm:text-5xl lg:text-6xl xl:text-7xl font-extrabold text-white leading-tight tracking-tight mb-6"
|
||||
>
|
||||
{
|
||||
siteConfig.hero.mainTitle
|
||||
.split("{rotating}")
|
||||
.map((part, index, array) => (
|
||||
<>
|
||||
{part}
|
||||
{index < array.length - 1 && rotatingText && (
|
||||
<RotatingText items={rotatingText} />
|
||||
)}
|
||||
</>
|
||||
))
|
||||
rotatingText ? (
|
||||
<>
|
||||
<RotatingText items={rotatingText} client:idle />
|
||||
<span class="block">
|
||||
{siteConfig.hero.mainTitle
|
||||
.replace("{rotating}", "")
|
||||
.trim()}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
siteConfig.hero.mainTitle
|
||||
)
|
||||
}
|
||||
</h1>
|
||||
|
||||
<p
|
||||
class="text-lg sm:text-xl text-white max-w-2xl mx-auto mb-10 leading-relaxed"
|
||||
>
|
||||
{siteConfig.hero.description}.
|
||||
{siteConfig.description}.
|
||||
</p>
|
||||
|
||||
<div
|
||||
@@ -54,14 +56,14 @@ const rotatingText = (siteConfig.hero as any).rotatingText as
|
||||
href="#contact"
|
||||
class="btn btn-accent btn-lg shadow-lg shadow-accent/25 hover:shadow-xl hover:shadow-accent/30"
|
||||
>
|
||||
<Icon name="heroicons:bolt" class="w-5 h-5" />
|
||||
<Icon name="bolt" class="w-5 h-5" />
|
||||
{siteConfig.hero.cta}
|
||||
</a>
|
||||
<a
|
||||
href="#services"
|
||||
class="btn btn-outline btn-lg border-white text-white hover:bg-white hover:text-neutral hover:border-white transition-all duration-300"
|
||||
>
|
||||
<Icon name="heroicons:chevron-down" class="w-5 h-5" />
|
||||
<Icon name="chevron-down" class="w-5 h-5" />
|
||||
{siteConfig.hero.secondaryCta}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
import { siteConfig } from "../../config/site";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import Icon from "../Icon.astro";
|
||||
import Section from "../Section.astro";
|
||||
|
||||
const variantStyles: Record<string, string> = {
|
||||
|
||||
29
src/config/icons.ts
Normal file
29
src/config/icons.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export const icons = {
|
||||
bolt: `<path d="M3.75 13.5L14.25 2.25L12 10.5H20.25L9.75 21.75L12 13.5H3.75Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
|
||||
"chevron-down": `<path d="M19.5 8.25L12 15.75L4.5 8.25" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
|
||||
"bars-3-bottom-left": `<path d="M3.75 6.75H20.25M3.75 12H20.25M3.75 17.25H12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
|
||||
"computer-desktop": `<path d="M9 17.25V18.2574C9 19.053 8.68393 19.8161 8.12132 20.3787L7.5 21H16.5L15.8787 20.3787C15.3161 19.8161 15 19.053 15 18.2574V17.25M21 5.25V15C21 16.2426 19.9926 17.25 18.75 17.25H5.25C4.00736 17.25 3 16.2426 3 15V5.25M21 5.25C21 4.00736 19.9926 3 18.75 3H5.25C4.00736 3 3 4.00736 3 5.25M21 5.25V12C21 13.2426 19.9926 14.25 18.75 14.25H5.25C4.00736 14.25 3 13.2426 3 12V5.25" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
|
||||
"device-phone-mobile": `<path d="M10.5 1.5H8.25C7.00736 1.5 6 2.50736 6 3.75V20.25C6 21.4926 7.00736 22.5 8.25 22.5H15.75C16.9926 22.5 18 21.4926 18 20.25V3.75C18 2.50736 16.9926 1.5 15.75 1.5H13.5M10.5 1.5V3H13.5V1.5M10.5 1.5H13.5M10.5 20.25H13.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
|
||||
"cog-6-tooth": `<path d="M9.59356 3.94014C9.68397 3.39768 10.1533 3.00009 10.7033 3.00009H13.2972C13.8472 3.00009 14.3165 3.39768 14.4069 3.94014L14.6204 5.22119C14.6828 5.59523 14.9327 5.9068 15.2645 6.09045C15.3387 6.13151 15.412 6.17393 15.4844 6.21766C15.8095 6.41393 16.2048 6.47495 16.5604 6.34175L17.7772 5.88587C18.2922 5.69293 18.8712 5.9006 19.1462 6.37687L20.4432 8.6233C20.7181 9.09957 20.6085 9.70482 20.1839 10.0544L19.1795 10.8812C18.887 11.122 18.742 11.4938 18.7491 11.8726C18.7498 11.915 18.7502 11.9575 18.7502 12.0001C18.7502 12.0427 18.7498 12.0852 18.7491 12.1275C18.742 12.5064 18.887 12.8782 19.1795 13.119L20.1839 13.9458C20.6085 14.2953 20.7181 14.9006 20.4432 15.3769L19.1462 17.6233C18.8712 18.0996 18.2922 18.3072 17.7772 18.1143L16.5604 17.6584C16.2048 17.5252 15.8095 17.5862 15.4844 17.7825C15.412 17.8263 15.3387 17.8687 15.2645 17.9097C14.9327 18.0934 14.6828 18.4049 14.6204 18.779L14.4069 20.06C14.3165 20.6025 13.8472 21.0001 13.2972 21.0001H10.7033C10.1533 21.0001 9.68397 20.6025 9.59356 20.06L9.38005 18.779C9.31771 18.4049 9.06774 18.0934 8.73597 17.9097C8.66179 17.8687 8.58847 17.8263 8.51604 17.7825C8.19101 17.5863 7.79568 17.5252 7.44011 17.6584L6.22325 18.1143C5.70826 18.3072 5.12926 18.0996 4.85429 17.6233L3.55731 15.3769C3.28234 14.9006 3.39199 14.2954 3.81657 13.9458L4.82092 13.119C5.11343 12.8782 5.25843 12.5064 5.25141 12.1276C5.25063 12.0852 5.25023 12.0427 5.25023 12.0001C5.25023 11.9575 5.25063 11.915 5.25141 11.8726C5.25843 11.4938 5.11343 11.122 4.82092 10.8812L3.81657 10.0544C3.39199 9.70484 3.28234 9.09958 3.55731 8.62332L4.85429 6.37688C5.12926 5.90061 5.70825 5.69295 6.22325 5.88588L7.4401 6.34176C7.79566 6.47496 8.19099 6.41394 8.51603 6.21767C8.58846 6.17393 8.66179 6.13151 8.73597 6.09045C9.06774 5.9068 9.31771 5.59523 9.38005 5.22119L9.59356 3.94014Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3432 10.3431 9.00001 12 9.00001C13.6569 9.00001 15 10.3432 15 12Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
|
||||
lifebuoy: `<path d="M16.7124 4.3299C17.2999 4.69153 17.8548 5.12691 18.364 5.63604C18.8731 6.14517 19.3085 6.70012 19.6701 7.28763M16.7124 4.3299L13.2636 8.46838M16.7124 4.3299C13.8316 2.5567 10.1684 2.5567 7.28763 4.3299M19.6701 7.28763L15.5316 10.7364M19.6701 7.28763C21.4433 10.1684 21.4433 13.8316 19.6701 16.7124M15.5316 10.7364C15.3507 10.2297 15.0574 9.75408 14.6517 9.34835C14.2459 8.94262 13.7703 8.6493 13.2636 8.46838M15.5316 10.7364C15.8228 11.5519 15.8228 12.4481 15.5316 13.2636M13.2636 8.46838C12.4481 8.17721 11.5519 8.17721 10.7364 8.46838M15.5316 13.2636C15.3507 13.7703 15.0574 14.2459 14.6517 14.6517C14.2459 15.0574 13.7703 15.3507 13.2636 15.5316M15.5316 13.2636L19.6701 16.7124M19.6701 16.7124C19.3085 17.2999 18.8731 17.8548 18.364 18.364C17.8548 18.8731 17.2999 19.3085 16.7124 19.6701M16.7124 19.6701L13.2636 15.5316M16.7124 19.6701C13.8316 21.4433 10.1684 21.4433 7.28763 19.6701M13.2636 15.5316C12.4481 15.8228 11.5519 15.8228 10.7364 15.5316M10.7364 15.5316C10.2297 15.3507 9.75408 15.0574 9.34835 14.6517C8.94262 14.2459 8.6493 13.7703 8.46838 13.2636M10.7364 15.5316L7.28763 19.6701M7.28763 19.6701C6.70012 19.3085 6.14517 18.8731 5.63604 18.364C5.12691 17.8548 4.69153 17.2999 4.3299 16.7124M4.3299 16.7124L8.46838 13.2636M4.3299 16.7124C2.5567 13.8316 2.5567 10.1684 4.3299 7.28763M8.46838 13.2636C8.17721 12.4481 8.17721 11.5519 8.46838 10.7364M8.46838 10.7364C8.6493 10.2297 8.94262 9.75408 9.34835 9.34835C9.75408 8.94262 10.2297 8.6493 10.7364 8.46838M8.46838 10.7364L4.3299 7.28763M10.7364 8.46838L7.28763 4.3299M7.28763 4.3299C6.70012 4.69153 6.14517 5.12691 5.63604 5.63604C5.12691 6.14517 4.69153 6.70013 4.3299 7.28763" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
|
||||
"check-circle": `<path d="M9 12.75L11.25 15L15 9.75M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
|
||||
users: `<path d="M15 19.1276C15.8329 19.37 16.7138 19.5 17.625 19.5C19.1037 19.5 20.5025 19.1576 21.7464 18.5478C21.7488 18.4905 21.75 18.4329 21.75 18.375C21.75 16.0968 19.9031 14.25 17.625 14.25C16.2069 14.25 14.956 14.9655 14.2136 16.0552M15 19.1276V19.125C15 18.0121 14.7148 16.9658 14.2136 16.0552M15 19.1276C15 19.1632 14.9997 19.1988 14.9991 19.2343C13.1374 20.3552 10.9565 21 8.625 21C6.29353 21 4.11264 20.3552 2.25092 19.2343C2.25031 19.198 2.25 19.1615 2.25 19.125C2.25 15.6042 5.10418 12.75 8.625 12.75C11.0329 12.75 13.129 14.085 14.2136 16.0552M12 6.375C12 8.23896 10.489 9.75 8.625 9.75C6.76104 9.75 5.25 8.23896 5.25 6.375C5.25 4.51104 6.76104 3 8.625 3C10.489 3 12 4.51104 12 6.375ZM20.25 8.625C20.25 10.0747 19.0747 11.25 17.625 11.25C16.1753 11.25 15 10.0747 15 8.625C15 7.17525 16.1753 6 17.625 6C19.0747 6 20.25 7.17525 20.25 8.625Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
|
||||
"x-circle": `<path d="M9.75 9.75L14.25 14.25M14.25 9.75L9.75 14.25M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
|
||||
"paper-airplane": `<path d="M5.99972 12L3.2688 3.12451C9.88393 5.04617 16.0276 8.07601 21.4855 11.9997C16.0276 15.9235 9.884 18.9535 3.26889 20.8752L5.99972 12ZM5.99972 12L13.5 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
|
||||
envelope: `<path d="M21.75 6.75V17.25C21.75 18.4926 20.7426 19.5 19.5 19.5H4.5C3.25736 19.5 2.25 18.4926 2.25 17.25V6.75M21.75 6.75C21.75 5.50736 20.7426 4.5 19.5 4.5H4.5C3.25736 4.5 2.25 5.50736 2.25 6.75M21.75 6.75V6.99271C21.75 7.77405 21.3447 8.49945 20.6792 8.90894L13.1792 13.5243C12.4561 13.9694 11.5439 13.9694 10.8208 13.5243L3.32078 8.90894C2.65535 8.49945 2.25 7.77405 2.25 6.99271V6.75" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`,
|
||||
|
||||
slopfree: `<path d="M23 15V18C23 18.5 22.64 18.88 22.17 18.97L20.2 17H21V16H19.2L19 15.8V14C19 11.24 16.76 9 14 9H12.2L10.2 7H11V5.73C10.4 5.39 10 4.74 10 4C10 2.9 10.9 2 12 2S14 2.9 14 4C14 4.74 13.6 5.39 13 5.73V7H14C17.87 7 21 10.13 21 14H22C22.55 14 23 14.45 23 15M8.5 13.5C7.4 13.5 6.5 14.4 6.5 15.5S7.4 17.5 8.5 17.5 10.5 16.61 10.5 15.5 9.61 13.5 8.5 13.5M22.11 21.46L20.84 22.73L19.89 21.78C19.62 21.92 19.32 22 19 22H5C3.9 22 3 21.11 3 20V19H2C1.45 19 1 18.55 1 18V15C1 14.45 1.45 14 2 14H3C3 11.53 4.29 9.36 6.22 8.11L1.11 3L2.39 1.73L22.11 21.46M18.11 20L15.6 17.5C15.57 17.5 15.53 17.5 15.5 17.5C14.4 17.5 13.5 16.61 13.5 15.5C13.5 15.47 13.5 15.43 13.5 15.4L7.7 9.59C6.1 10.42 5 12.08 5 14V16H3V17H5V20H18.11Z" fill="currentColor"/>`,
|
||||
} as const;
|
||||
|
||||
export type IconName = keyof typeof icons;
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { IconName } from "./icons";
|
||||
|
||||
type Card = {
|
||||
title: string;
|
||||
content: string;
|
||||
@@ -5,6 +7,7 @@ type Card = {
|
||||
};
|
||||
|
||||
export const siteConfig = {
|
||||
siteUrl: "https://atash.dev",
|
||||
name: "Atash Consulting",
|
||||
description: "Independent Software Consulting based in Edmonton, Alberta",
|
||||
|
||||
@@ -55,14 +58,13 @@ export const siteConfig = {
|
||||
className: "bg-accent text-accent-content px-2",
|
||||
},
|
||||
],
|
||||
description: "Independent Software Consulting based in Edmonton, Alberta",
|
||||
cta: "Discuss Your Project",
|
||||
secondaryCta: "View Services",
|
||||
trustedText: "Trusted expertise in",
|
||||
features: [
|
||||
{ icon: "heroicons:computer-desktop", text: "Web Development" },
|
||||
{ icon: "heroicons:device-phone-mobile", text: "Mobile Apps" },
|
||||
{ icon: "heroicons:cog-6-tooth", text: "DevOps" },
|
||||
{ icon: "computer-desktop", text: "Web Development" },
|
||||
{ icon: "device-phone-mobile", text: "Mobile Apps" },
|
||||
{ icon: "cog-6-tooth", text: "DevOps" },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -73,28 +75,28 @@ export const siteConfig = {
|
||||
title: "Web Development",
|
||||
content: "Functional, accessible, and beautiful websites.",
|
||||
variant: "primary",
|
||||
icon: "heroicons:computer-desktop",
|
||||
icon: "computer-desktop",
|
||||
},
|
||||
{
|
||||
title: "Mobile App Development",
|
||||
content: "iOS, Android, and cross-platform mobile applications.",
|
||||
variant: "secondary",
|
||||
icon: "heroicons:device-phone-mobile",
|
||||
icon: "device-phone-mobile",
|
||||
},
|
||||
{
|
||||
title: "DevOps",
|
||||
content: "From CI/CD pipelines to end-to-end automation.",
|
||||
content: "CI/CD pipelines end-to-end automation.",
|
||||
variant: "secondary",
|
||||
icon: "heroicons:cog-6-tooth",
|
||||
icon: "cog-6-tooth",
|
||||
},
|
||||
{
|
||||
title: "IT Support Processes",
|
||||
content:
|
||||
"Expert technical guidance backed by over a decade of experience.",
|
||||
variant: "primary",
|
||||
icon: "heroicons:lifebuoy",
|
||||
icon: "lifebuoy",
|
||||
},
|
||||
] as (Card & { icon: string })[],
|
||||
] as (Card & { icon: IconName })[],
|
||||
},
|
||||
|
||||
whyUs: {
|
||||
@@ -106,8 +108,8 @@ export const siteConfig = {
|
||||
variant: "primary",
|
||||
},
|
||||
{
|
||||
title: "Built With Care",
|
||||
content: "Built with rigorous testing and attention to detail",
|
||||
title: "Slop-free Guarantee",
|
||||
content: "Hand-crafted code built with care",
|
||||
variant: "secondary",
|
||||
},
|
||||
{
|
||||
@@ -125,18 +127,24 @@ export const siteConfig = {
|
||||
|
||||
clients: [
|
||||
{
|
||||
name: "SAIKYO Softworks",
|
||||
name: "Saikyo Softworks",
|
||||
logo: "/clients/SaikyoSoftworks.webp",
|
||||
href: "https://saikyosoft.works",
|
||||
},
|
||||
{
|
||||
name: "Royer Mortgages",
|
||||
logo: "/clients/RoyerMortgages.webp",
|
||||
href: "https://royermortgages.com",
|
||||
},
|
||||
{
|
||||
name: "Marew Consulting",
|
||||
name: "Marew Consulting Ltd",
|
||||
logo: "/clients/MarewConsulting.webp",
|
||||
href: "https://marewconsulting.ca",
|
||||
},
|
||||
{
|
||||
name: "Hutch Mortgages",
|
||||
logo: "/clients/HutchMortgages.webp",
|
||||
href: "https://hutchmortgages.com",
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
@@ -2,19 +2,41 @@
|
||||
import Header from "../components/Header.astro";
|
||||
import Footer from "../components/Footer.astro";
|
||||
import { siteConfig } from "../config/site";
|
||||
import "@fontsource-variable/roboto-slab";
|
||||
import "../styles/global.css";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
description?: string;
|
||||
ogImage?: {
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
type: string;
|
||||
alt: string;
|
||||
};
|
||||
}
|
||||
|
||||
const { title = siteConfig.name, description = siteConfig.description } =
|
||||
Astro.props;
|
||||
const {
|
||||
title = siteConfig.name,
|
||||
description = siteConfig.description,
|
||||
ogImage,
|
||||
} = Astro.props;
|
||||
|
||||
const metaTitle =
|
||||
title === siteConfig.name ? title : `${title} | ${siteConfig.name}`;
|
||||
|
||||
const resolvedOgImage = ogImage || {
|
||||
url: "/logo.webp",
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
type: "image/webp",
|
||||
alt: "Atash Consulting",
|
||||
};
|
||||
|
||||
const siteUrl = siteConfig.siteUrl || Astro.url.href;
|
||||
const resolvedOgImageUrl = new URL(resolvedOgImage.url, siteUrl).href;
|
||||
|
||||
const isProd = import.meta.env.PROD;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
@@ -25,9 +47,53 @@ const metaTitle =
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta name="description" content={description} />
|
||||
<meta property="og:title" content={metaTitle} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={siteUrl} />
|
||||
<meta property="og:image" content={resolvedOgImageUrl} />
|
||||
<meta
|
||||
property="og:image:width"
|
||||
content={String(resolvedOgImage.width)}
|
||||
/>
|
||||
<meta
|
||||
property="og:image:height"
|
||||
content={String(resolvedOgImage.height)}
|
||||
/>
|
||||
<meta property="og:image:type" content={resolvedOgImage.type} />
|
||||
<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>
|
||||
{
|
||||
isProd && (
|
||||
<script>
|
||||
(function() {
|
||||
function loadAnalytics() {
|
||||
var script = document.createElement('script');
|
||||
script.src = "https://analytics.atri.dad/script.js";
|
||||
script.defer = true;
|
||||
script.setAttribute('data-website-id', 'c7e24af4-5f14-4881-9c25-85a97abda9f1');
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
if ('requestIdleCallback' in window) {
|
||||
requestIdleCallback(loadAnalytics, { timeout: 2000 });
|
||||
} else {
|
||||
setTimeout(loadAnalytics, 2000);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
)
|
||||
}
|
||||
</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 />
|
||||
<main class="grow flex flex-col">
|
||||
<slot />
|
||||
|
||||
@@ -19,7 +19,7 @@ export const GET: APIRoute = async () => {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Cache-Control": "public, max-age=60, stale-while-revalidate=300",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ import Section from "../components/Section.astro";
|
||||
|
||||
const pageMetaInfo = {
|
||||
title: siteConfig.name,
|
||||
description: `Welcome to ${siteConfig.name} - ${siteConfig.description}`,
|
||||
description: `${siteConfig.name} - ${siteConfig.description}`,
|
||||
};
|
||||
---
|
||||
|
||||
|
||||
@@ -40,11 +40,14 @@ html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Roboto Slab Variable", serif;
|
||||
@font-face {
|
||||
font-family: "Roboto Slab";
|
||||
src: url("/fonts/roboto.woff2") format("woff2");
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
.text-rotate:hover,
|
||||
.text-rotate:hover * {
|
||||
animation-play-state: running !important;
|
||||
body {
|
||||
font-family: "Roboto Slab", serif;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user