edit the layout handling and add cv download

This commit is contained in:
Dilanka-H
2025-11-24 14:45:21 +07:00
parent fc5c7db221
commit bb65f04992
14 changed files with 351 additions and 253 deletions

View File

@@ -34,6 +34,7 @@ COPY --from=builder /app/css ./css
COPY --from=builder /app/views ./views
COPY --from=builder /app/my_website.db ./
COPY --from=builder /app/data ./data
COPY --from=builder /app/documents ./documents
EXPOSE 8080

View File

@@ -7,6 +7,7 @@
'Noto Color Emoji';
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace;
--color-blue-100: oklch(93.2% 0.032 255.585);
--color-blue-600: oklch(54.6% 0.245 262.881);
--color-purple-300: oklch(82.7% 0.119 306.383);
--color-gray-300: oklch(87.2% 0.01 258.338);
@@ -201,9 +202,15 @@
.inset-0 {
inset: calc(var(--spacing) * 0);
}
.top-1 {
top: calc(var(--spacing) * 1);
}
.top-1\/2 {
top: calc(1/2 * 100%);
}
.top-3 {
top: calc(var(--spacing) * 3);
}
.top-3\/4 {
top: calc(3/4 * 100%);
}
@@ -222,6 +229,9 @@
.top-70 {
top: calc(var(--spacing) * 70);
}
.right-1 {
right: calc(var(--spacing) * 1);
}
.right-1\/3 {
right: calc(1/3 * 100%);
}
@@ -243,6 +253,9 @@
.bottom-20 {
bottom: calc(var(--spacing) * 20);
}
.left-1 {
left: calc(var(--spacing) * 1);
}
.left-1\/3 {
left: calc(1/3 * 100%);
}
@@ -303,6 +316,9 @@
.mt-6 {
margin-top: calc(var(--spacing) * 6);
}
.mt-10 {
margin-top: calc(var(--spacing) * 10);
}
.mt-16 {
margin-top: calc(var(--spacing) * 16);
}
@@ -330,33 +346,69 @@
.hidden {
display: none;
}
.inline-block {
display: inline-block;
}
.inline-flex {
display: inline-flex;
}
.table {
display: table;
}
.h-2 {
height: calc(var(--spacing) * 2);
}
.h-2\.5 {
height: calc(var(--spacing) * 2.5);
}
.min-h-screen {
min-height: 100vh;
}
.w-0 {
width: calc(var(--spacing) * 0);
}
.w-0\.5 {
width: calc(var(--spacing) * 0.5);
}
.w-2 {
width: calc(var(--spacing) * 2);
}
.w-2\.5 {
width: calc(var(--spacing) * 2.5);
}
.w-auto {
width: auto;
}
.w-fit {
width: fit-content;
}
.max-w-2xl {
max-width: var(--container-2xl);
}
.max-w-3xl {
max-width: var(--container-3xl);
}
.flex-shrink {
flex-shrink: 1;
}
.flex-shrink-0 {
flex-shrink: 0;
}
.flex-shrink-1 {
flex-shrink: 1;
}
.flex-grow {
flex-grow: 1;
}
.border-collapse {
border-collapse: collapse;
}
.cursor-pointer {
cursor: pointer;
}
.resize {
resize: both;
}
.list-inside {
list-style-position: inside;
}
@@ -378,9 +430,6 @@
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.justify-center {
justify-content: center;
}
@@ -407,6 +456,20 @@
margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-5 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-x-3 {
:where(& > :not(:last-child)) {
--tw-space-x-reverse: 0;
margin-inline-start: calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));
margin-inline-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)));
}
}
.space-x-5 {
:where(& > :not(:last-child)) {
--tw-space-x-reverse: 0;
@@ -414,16 +477,12 @@
margin-inline-end: calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-x-reverse)));
}
}
.space-x-6 {
:where(& > :not(:last-child)) {
--tw-space-x-reverse: 0;
margin-inline-start: calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));
margin-inline-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)));
}
}
.overflow-hidden {
overflow: hidden;
}
.rounded {
border-radius: 0.25rem;
}
.rounded-full {
border-radius: calc(infinity * 1px);
}
@@ -441,12 +500,18 @@
.border-secondary {
border-color: #d3d9e6;
}
.border-white {
border-color: var(--color-white);
}
.border-white\/20 {
border-color: color-mix(in srgb, #fff 20%, transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-white) 20%, transparent);
}
}
.bg-accent {
background-color: #9333ea;
}
.bg-accent\/5 {
background-color: color-mix(in oklab, #9333ea 5%, transparent);
}
@@ -459,12 +524,30 @@
.p-4 {
padding: calc(var(--spacing) * 4);
}
.px-2 {
padding-inline: calc(var(--spacing) * 2);
}
.px-3 {
padding-inline: calc(var(--spacing) * 3);
}
.px-4 {
padding-inline: calc(var(--spacing) * 4);
}
.px-5 {
padding-inline: calc(var(--spacing) * 5);
}
.px-6 {
padding-inline: calc(var(--spacing) * 6);
}
.px-8 {
padding-inline: calc(var(--spacing) * 8);
}
.py-1 {
padding-block: calc(var(--spacing) * 1);
}
.py-2 {
padding-block: calc(var(--spacing) * 2);
}
.py-3 {
padding-block: calc(var(--spacing) * 3);
}
@@ -489,6 +572,9 @@
.text-start {
text-align: start;
}
.align-middle {
vertical-align: middle;
}
.text-3xl {
font-size: var(--text-3xl);
line-height: var(--tw-leading, var(--text-3xl--line-height));
@@ -533,6 +619,9 @@
--tw-font-weight: var(--font-weight-semibold);
font-weight: var(--font-weight-semibold);
}
.text-blue-100 {
color: var(--color-blue-100);
}
.text-blue-600 {
color: var(--color-blue-600);
}
@@ -542,6 +631,9 @@
.text-gray-500 {
color: var(--color-gray-500);
}
.text-purple-300 {
color: var(--color-purple-300);
}
.text-purple-300\/50 {
color: color-mix(in srgb, oklch(82.7% 0.119 306.383) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
@@ -562,11 +654,22 @@
--tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.outline {
outline-style: var(--tw-outline-style);
outline-width: 1px;
}
.filter {
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
.backdrop-blur-xl {
--tw-backdrop-blur: blur(var(--blur-xl));
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
}
.backdrop-filter {
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
}
.transition {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
@@ -697,15 +800,6 @@
}
}
}
.hover\:dark\:bg-darkaccent\/0 {
&:hover {
@media (hover: hover) {
@media (prefers-color-scheme: dark) {
background-color: color-mix(in oklab, #2563eb 0%, transparent);
}
}
}
}
.hover\:dark\:bg-darkaccent\/30 {
&:hover {
@media (hover: hover) {
@@ -734,15 +828,6 @@
}
}
}
.hover\:\[\&\>svg\]\:text-\[\#cc6d00\] {
&:hover {
@media (hover: hover) {
&>svg {
color: #cc6d00;
}
}
}
}
.hover\:\[\&\>svg\]\:drop-shadow-\[0_0_6px_rgba\(35\,36\,59\,0\.5\)\] {
&:hover {
@media (hover: hover) {
@@ -881,6 +966,64 @@
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-outline-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@property --tw-blur {
syntax: "*";
inherits: false;
}
@property --tw-brightness {
syntax: "*";
inherits: false;
}
@property --tw-contrast {
syntax: "*";
inherits: false;
}
@property --tw-grayscale {
syntax: "*";
inherits: false;
}
@property --tw-hue-rotate {
syntax: "*";
inherits: false;
}
@property --tw-invert {
syntax: "*";
inherits: false;
}
@property --tw-opacity {
syntax: "*";
inherits: false;
}
@property --tw-saturate {
syntax: "*";
inherits: false;
}
@property --tw-sepia {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-drop-shadow-size {
syntax: "*";
inherits: false;
}
@property --tw-backdrop-blur {
syntax: "*";
inherits: false;
@@ -936,59 +1079,6 @@
inherits: false;
initial-value: 1;
}
@property --tw-blur {
syntax: "*";
inherits: false;
}
@property --tw-brightness {
syntax: "*";
inherits: false;
}
@property --tw-contrast {
syntax: "*";
inherits: false;
}
@property --tw-grayscale {
syntax: "*";
inherits: false;
}
@property --tw-hue-rotate {
syntax: "*";
inherits: false;
}
@property --tw-invert {
syntax: "*";
inherits: false;
}
@property --tw-opacity {
syntax: "*";
inherits: false;
}
@property --tw-saturate {
syntax: "*";
inherits: false;
}
@property --tw-sepia {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-drop-shadow-size {
syntax: "*";
inherits: false;
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
@@ -1011,19 +1101,7 @@
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-offset-shadow: 0 0 #0000;
--tw-backdrop-blur: initial;
--tw-backdrop-brightness: initial;
--tw-backdrop-contrast: initial;
--tw-backdrop-grayscale: initial;
--tw-backdrop-hue-rotate: initial;
--tw-backdrop-invert: initial;
--tw-backdrop-opacity: initial;
--tw-backdrop-saturate: initial;
--tw-backdrop-sepia: initial;
--tw-duration: initial;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-scale-z: 1;
--tw-outline-style: solid;
--tw-blur: initial;
--tw-brightness: initial;
--tw-contrast: initial;
@@ -1037,6 +1115,19 @@
--tw-drop-shadow-color: initial;
--tw-drop-shadow-alpha: 100%;
--tw-drop-shadow-size: initial;
--tw-backdrop-blur: initial;
--tw-backdrop-brightness: initial;
--tw-backdrop-contrast: initial;
--tw-backdrop-grayscale: initial;
--tw-backdrop-hue-rotate: initial;
--tw-backdrop-invert: initial;
--tw-backdrop-opacity: initial;
--tw-backdrop-saturate: initial;
--tw-backdrop-sepia: initial;
--tw-duration: initial;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-scale-z: 1;
}
}
}

View File

@@ -4,14 +4,25 @@ import (
"database/sql"
"duhweb/internal/store"
"html/template"
"io"
"net/http"
"os"
)
type ApiHandler struct {
Templates *template.Template
ProjectStore *store.SQLiteProjectStore
ExperienceStore *store.SQLiteExperienceStore
FooterTemplateData []FooterTemplateData
}
type Layout struct {
TemplateStore store.TemplateStore
Project []store.Project
Experience []store.ExperienceJSON
}
var layout = Layout{
TemplateStore: *store.NewTemplateStore(),
}
func (h *ApiHandler) Render(w http.ResponseWriter, tmpl string, data interface{}) {
@@ -21,65 +32,24 @@ func (h *ApiHandler) Render(w http.ResponseWriter, tmpl string, data interface{}
}
}
type FooterTemplateData struct {
Link string
Height string
Width string
Path string
}
func NewApiHandler(db *sql.DB) *ApiHandler {
tmpl := template.Must(template.New("").ParseGlob("views/*.html"))
template.Must(tmpl.ParseGlob("views/partials/*.html"))
footerTemplateData := []FooterTemplateData{
{
Link: "mailto:dilankaherath14@gmail.com",
Height: "5",
Width: "5",
Path: "M61.4 64C27.5 64 0 91.5 0 125.4 0 126.3 0 127.1 .1 128L0 128 0 384c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-256-.1 0c0-.9 .1-1.7 .1-2.6 0-33.9-27.5-61.4-61.4-61.4L61.4 64zM464 192.3L464 384c0 8.8-7.2 16-16 16L64 400c-8.8 0-16-7.2-16-16l0-191.7 154.8 117.4c31.4 23.9 74.9 23.9 106.4 0L464 192.3zM48 125.4C48 118 54 112 61.4 112l389.2 0c7.4 0 13.4 6 13.4 13.4 0 4.2-2 8.2-5.3 10.7L280.2 271.5c-14.3 10.8-34.1 10.8-48.4 0L53.3 136.1c-3.3-2.5-5.3-6.5-5.3-10.7z",
},
{
Link: "https://www.linkedin.com/in/dilanka-herath",
Height: "5",
Width: "5",
Path: "M100.3 448H7.4V148.9h92.9zM53.8 108.1C24.1 108.1 0 83.5 0 53.8a53.8 53.8 0 0 1 107.6 0c0 29.7-24.1 54.3-53.8 54.3zM447.9 448h-92.7V302.4c0-34.7-.7-79.2-48.3-79.2-48.3 0-55.7 37.7-55.7 76.7V448h-92.8V148.9h89.1v40.8h1.3c12.4-23.5 42.7-48.3 87.9-48.3 94 0 111.3 61.9 111.3 142.3V448z",
},
{
Link: "https://github.com/DilankaHer",
Height: "5",
Width: "5",
Path: "M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z",
},
{
Link: "https://line.me/ti/p/~dilankaa",
Height: "5",
Width: "5",
Path: "M311 196.8l0 81.3c0 2.1-1.6 3.7-3.7 3.7l-13 0c-1.3 0-2.4-.7-3-1.5L254 230 254 278.2c0 2.1-1.6 3.7-3.7 3.7l-13 0c-2.1 0-3.7-1.6-3.7-3.7l0-81.3c0-2.1 1.6-3.7 3.7-3.7l12.9 0c1.1 0 2.4 .6 3 1.6l37.3 50.3 0-48.2c0-2.1 1.6-3.7 3.7-3.7l13 0c2.1-.1 3.8 1.6 3.8 3.5l0 .1zm-93.7-3.7l-13 0c-2.1 0-3.7 1.6-3.7 3.7l0 81.3c0 2.1 1.6 3.7 3.7 3.7l13 0c2.1 0 3.7-1.6 3.7-3.7l0-81.3c0-1.9-1.6-3.7-3.7-3.7zm-31.4 68.1l-35.6 0 0-64.4c0-2.1-1.6-3.7-3.7-3.7l-13 0c-2.1 0-3.7 1.6-3.7 3.7l0 81.3c0 1 .3 1.8 1 2.5 .7 .6 1.5 1 2.5 1l52.2 0c2.1 0 3.7-1.6 3.7-3.7l0-13c0-1.9-1.6-3.7-3.5-3.7l.1 0zm193.7-68.1l-52.3 0c-1.9 0-3.7 1.6-3.7 3.7l0 81.3c0 1.9 1.6 3.7 3.7 3.7l52.2 0c2.1 0 3.7-1.6 3.7-3.7l0-13.1c0-2.1-1.6-3.7-3.7-3.7l-35.5 0 0-13.6 35.5 0c2.1 0 3.7-1.6 3.7-3.7l0-13.1c0-2.1-1.6-3.7-3.7-3.7l-35.5 0 0-13.7 35.5 0c2.1 0 3.7-1.6 3.7-3.7l0-13c-.1-1.9-1.7-3.7-3.7-3.7l.1 0zM512 93.4l0 326c-.1 51.2-42.1 92.7-93.4 92.6l-326 0C41.4 511.9-.1 469.8 0 418.6l0-326C.1 41.4 42.2-.1 93.4 0l326 0c51.2 .1 92.7 42.1 92.6 93.4zM441.6 233.5c0-83.4-83.7-151.3-186.4-151.3S68.8 150.1 68.8 233.5c0 74.7 66.3 137.4 155.9 149.3 21.8 4.7 19.3 12.7 14.4 42.1-.8 4.7-3.8 18.4 16.1 10.1s107.3-63.2 146.5-108.2c27-29.7 39.9-59.8 39.9-93.1l0-.2z",
},
{
Link: "https://wa.me/66615971121",
Height: "5",
Width: "5",
Path: "M380.9 97.1c-41.9-42-97.7-65.1-157-65.1-122.4 0-222 99.6-222 222 0 39.1 10.2 77.3 29.6 111L0 480 117.7 449.1c32.4 17.7 68.9 27 106.1 27l.1 0c122.3 0 224.1-99.6 224.1-222 0-59.3-25.2-115-67.1-157zm-157 341.6c-33.2 0-65.7-8.9-94-25.7l-6.7-4-69.8 18.3 18.6-68.1-4.4-7c-18.5-29.4-28.2-63.3-28.2-98.2 0-101.7 82.8-184.5 184.6-184.5 49.3 0 95.6 19.2 130.4 54.1s56.2 81.2 56.1 130.5c0 101.8-84.9 184.6-186.6 184.6zM325.1 300.5c-5.5-2.8-32.8-16.2-37.9-18-5.1-1.9-8.8-2.8-12.5 2.8s-14.3 18-17.6 21.8c-3.2 3.7-6.5 4.2-12 1.4-32.6-16.3-54-29.1-75.5-66-5.7-9.8 5.7-9.1 16.3-30.3 1.8-3.7 .9-6.9-.5-9.7s-12.5-30.1-17.1-41.2c-4.5-10.8-9.1-9.3-12.5-9.5-3.2-.2-6.9-.2-10.6-.2s-9.7 1.4-14.8 6.9c-5.1 5.6-19.4 19-19.4 46.3s19.9 53.7 22.6 57.4c2.8 3.7 39.1 59.7 94.8 83.8 35.2 15.2 49 16.5 66.6 13.9 10.7-1.6 32.8-13.4 37.4-26.4s4.6-24.1 3.2-26.4c-1.3-2.5-5-3.9-10.5-6.6z",
},
}
return &ApiHandler{
Templates: tmpl,
ProjectStore: store.NewSQLiteProjectStore(db),
ExperienceStore: store.NewSQLiteExperienceStore(db),
FooterTemplateData: footerTemplateData,
}
}
func (h *ApiHandler) InitPage(w http.ResponseWriter, r *http.Request) {
var hxRequest = r.Header.Get("HX-Request")
if hxRequest != "true" {
h.Render(w, "index", h.FooterTemplateData)
h.Render(w, "index", layout)
return
}
h.Render(w, "about-partial", h.FooterTemplateData)
h.Render(w, "about-partial", layout)
}
func (h *ApiHandler) ProjectsPage(w http.ResponseWriter, r *http.Request) {
@@ -89,11 +59,12 @@ func (h *ApiHandler) ProjectsPage(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
layout.Project = projects
if hxRequest != "true" {
h.Render(w, "projects", projects)
h.Render(w, "projects", layout)
return
}
h.Render(w, "projects-partial", projects)
h.Render(w, "projects-partial", layout.Project)
}
func (h *ApiHandler) ExperiencePage(w http.ResponseWriter, r *http.Request) {
@@ -104,9 +75,29 @@ func (h *ApiHandler) ExperiencePage(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
layout.Experience = experiences
if hxRequest != "true" {
h.Render(w, "experience", experiences)
h.Render(w, "experience", layout)
return
}
h.Render(w, "experience-partial", layout.Experience)
}
func (h *ApiHandler) MakeDownloadLink(w http.ResponseWriter, r *http.Request) {
file, err := os.Open("./documents/DilankaHerath-CV.pdf")
w.Header().Set("Hx-Trigger", "DownloadCVButton")
if err != nil {
http.Error(w, "Failed to open CV file", http.StatusInternalServerError)
return
}
defer file.Close()
w.Header().Set("Content-Type", "application/pdf")
w.Header().Set("Content-Disposition", "attachment; filename=\"DilankaHerath-CV.pdf\"")
_, err = io.Copy(w, file)
if err != nil {
return
}
h.Render(w, "experience-partial", experiences)
}

View File

@@ -12,5 +12,6 @@ func SetupRoutes(app *app.Application) *chi.Mux {
r.Get("/", app.ApiHandler.InitPage)
r.Get("/projects", app.ApiHandler.ProjectsPage)
r.Get("/experience", app.ApiHandler.ExperiencePage)
r.Get("/download", app.ApiHandler.MakeDownloadLink)
return r
}

View File

@@ -0,0 +1,74 @@
package store
type TemplateStore struct {
Footer []Footer
ScatterWord []ScatterWord
}
type Footer struct {
Link string
Height string
Width string
Path string
}
type ScatterWord struct {
AxisY string
AxisX string
Word string
}
func NewTemplateStore() *TemplateStore {
footer := []Footer{
{
Link: "mailto:dilankaherath14@gmail.com",
Height: "5",
Width: "5",
Path: "M61.4 64C27.5 64 0 91.5 0 125.4 0 126.3 0 127.1 .1 128L0 128 0 384c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-256-.1 0c0-.9 .1-1.7 .1-2.6 0-33.9-27.5-61.4-61.4-61.4L61.4 64zM464 192.3L464 384c0 8.8-7.2 16-16 16L64 400c-8.8 0-16-7.2-16-16l0-191.7 154.8 117.4c31.4 23.9 74.9 23.9 106.4 0L464 192.3zM48 125.4C48 118 54 112 61.4 112l389.2 0c7.4 0 13.4 6 13.4 13.4 0 4.2-2 8.2-5.3 10.7L280.2 271.5c-14.3 10.8-34.1 10.8-48.4 0L53.3 136.1c-3.3-2.5-5.3-6.5-5.3-10.7z",
},
{
Link: "https://www.linkedin.com/in/dilanka-herath",
Height: "5",
Width: "5",
Path: "M100.3 448H7.4V148.9h92.9zM53.8 108.1C24.1 108.1 0 83.5 0 53.8a53.8 53.8 0 0 1 107.6 0c0 29.7-24.1 54.3-53.8 54.3zM447.9 448h-92.7V302.4c0-34.7-.7-79.2-48.3-79.2-48.3 0-55.7 37.7-55.7 76.7V448h-92.8V148.9h89.1v40.8h1.3c12.4-23.5 42.7-48.3 87.9-48.3 94 0 111.3 61.9 111.3 142.3V448z",
},
{
Link: "https://github.com/DilankaHer",
Height: "5",
Width: "5",
Path: "M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z",
},
{
Link: "https://line.me/ti/p/~dilankaa",
Height: "5",
Width: "5",
Path: "M311 196.8l0 81.3c0 2.1-1.6 3.7-3.7 3.7l-13 0c-1.3 0-2.4-.7-3-1.5L254 230 254 278.2c0 2.1-1.6 3.7-3.7 3.7l-13 0c-2.1 0-3.7-1.6-3.7-3.7l0-81.3c0-2.1 1.6-3.7 3.7-3.7l12.9 0c1.1 0 2.4 .6 3 1.6l37.3 50.3 0-48.2c0-2.1 1.6-3.7 3.7-3.7l13 0c2.1-.1 3.8 1.6 3.8 3.5l0 .1zm-93.7-3.7l-13 0c-2.1 0-3.7 1.6-3.7 3.7l0 81.3c0 2.1 1.6 3.7 3.7 3.7l13 0c2.1 0 3.7-1.6 3.7-3.7l0-81.3c0-1.9-1.6-3.7-3.7-3.7zm-31.4 68.1l-35.6 0 0-64.4c0-2.1-1.6-3.7-3.7-3.7l-13 0c-2.1 0-3.7 1.6-3.7 3.7l0 81.3c0 1 .3 1.8 1 2.5 .7 .6 1.5 1 2.5 1l52.2 0c2.1 0 3.7-1.6 3.7-3.7l0-13c0-1.9-1.6-3.7-3.5-3.7l.1 0zm193.7-68.1l-52.3 0c-1.9 0-3.7 1.6-3.7 3.7l0 81.3c0 1.9 1.6 3.7 3.7 3.7l52.2 0c2.1 0 3.7-1.6 3.7-3.7l0-13.1c0-2.1-1.6-3.7-3.7-3.7l-35.5 0 0-13.6 35.5 0c2.1 0 3.7-1.6 3.7-3.7l0-13.1c0-2.1-1.6-3.7-3.7-3.7l-35.5 0 0-13.7 35.5 0c2.1 0 3.7-1.6 3.7-3.7l0-13c-.1-1.9-1.7-3.7-3.7-3.7l.1 0zM512 93.4l0 326c-.1 51.2-42.1 92.7-93.4 92.6l-326 0C41.4 511.9-.1 469.8 0 418.6l0-326C.1 41.4 42.2-.1 93.4 0l326 0c51.2 .1 92.7 42.1 92.6 93.4zM441.6 233.5c0-83.4-83.7-151.3-186.4-151.3S68.8 150.1 68.8 233.5c0 74.7 66.3 137.4 155.9 149.3 21.8 4.7 19.3 12.7 14.4 42.1-.8 4.7-3.8 18.4 16.1 10.1s107.3-63.2 146.5-108.2c27-29.7 39.9-59.8 39.9-93.1l0-.2z",
},
{
Link: "https://wa.me/66615971121",
Height: "5",
Width: "5",
Path: "M380.9 97.1c-41.9-42-97.7-65.1-157-65.1-122.4 0-222 99.6-222 222 0 39.1 10.2 77.3 29.6 111L0 480 117.7 449.1c32.4 17.7 68.9 27 106.1 27l.1 0c122.3 0 224.1-99.6 224.1-222 0-59.3-25.2-115-67.1-157zm-157 341.6c-33.2 0-65.7-8.9-94-25.7l-6.7-4-69.8 18.3 18.6-68.1-4.4-7c-18.5-29.4-28.2-63.3-28.2-98.2 0-101.7 82.8-184.5 184.6-184.5 49.3 0 95.6 19.2 130.4 54.1s56.2 81.2 56.1 130.5c0 101.8-84.9 184.6-186.6 184.6zM325.1 300.5c-5.5-2.8-32.8-16.2-37.9-18-5.1-1.9-8.8-2.8-12.5 2.8s-14.3 18-17.6 21.8c-3.2 3.7-6.5 4.2-12 1.4-32.6-16.3-54-29.1-75.5-66-5.7-9.8 5.7-9.1 16.3-30.3 1.8-3.7 .9-6.9-.5-9.7s-12.5-30.1-17.1-41.2c-4.5-10.8-9.1-9.3-12.5-9.5-3.2-.2-6.9-.2-10.6-.2s-9.7 1.4-14.8 6.9c-5.1 5.6-19.4 19-19.4 46.3s19.9 53.7 22.6 57.4c2.8 3.7 39.1 59.7 94.8 83.8 35.2 15.2 49 16.5 66.6 13.9 10.7-1.6 32.8-13.4 37.4-26.4s4.6-24.1 3.2-26.4c-1.3-2.5-5-3.9-10.5-6.6z",
},
}
scatter := []ScatterWord{
{AxisY: "top-10", AxisX: "left-5", Word: "Golang"},
{AxisY: "top-40", AxisX: "right-10", Word: "React"},
{AxisY: "bottom-20", AxisX: "left-1/3", Word: "HTMX"},
{AxisY: "top-1/2", AxisX: "right-1/4", Word: "Node"},
{AxisY: "top-3/4", AxisX: "right-20", Word: "Kafka"},
{AxisY: "top-3/4", AxisX: "left-20", Word: "Docker"},
{AxisY: "top-20", AxisX: "left-100", Word: "Mongo"},
{AxisY: "top-15", AxisX: "right-100", Word: "C++"},
{AxisY: "bottom-20", AxisX: "right-1/3", Word: "Javascript"},
{AxisY: "top-1/2", AxisX: "left-1/4", Word: "Nest"},
{AxisY: "top-70", AxisX: "left-50", Word: "MySQL"},
{AxisY: "top-70", AxisX: "right-50", Word: "Python"},
}
return &TemplateStore{
Footer: footer,
ScatterWord: scatter,
}
}

View File

@@ -14,10 +14,10 @@
>
<!-- Experience Section -->
<div id="content" class="flex-grow">
{{ template "experience-partial" .}}
{{ template "experience-partial" .Experience}}
</div>
<!-- Footer Section -->
{{ template "footer" }}
{{ template "footer" .TemplateStore.Footer}}
</body>
{{ template "script" }}
</html>

View File

@@ -1,33 +0,0 @@
{{ block "header" .}}
<header class="bg-secondary dark:bg-darkSecondary shadow-md">
<nav class="container mx-auto flex justify-between items-center py-4 px-6">
<a href="#" class="text-xl font-bold text-blue-600">Dilanka Herath</a>
<div class="space-x-6">
<a
id="aboutNavLink"
hx-get="/"
hx-target="#content"
hx-push-url="true"
class="nav-link"
>About</a
>
<a
id="projectsNavLink"
hx-get="/projects"
hx-target="#content"
hx-push-url="true"
class="nav-link"
>Projects</a
>
<a
id="experiencesNavLink"
hx-get="/experience"
hx-target="#content"
hx-push-url="true"
class="nav-link"
>Experience</a
>
</div>
</nav>
</header>
{{ end }}

View File

@@ -12,3 +12,10 @@
</span>
</a>
{{ end }}
{{ block "scattered-words" .}}
<span
class="absolute {{.AxisY}} {{.AxisX}} text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>{{.Word}}
</span>
{{ end}}

View File

@@ -17,62 +17,14 @@
id="scatteredWords"
class="pointer-events-none absolute inset-0 overflow-hidden select-none hidden md:block"
>
<span
class="absolute top-10 left-5 text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>Golang</span
>
<span
class="absolute top-40 right-10 text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>React</span
>
<span
class="absolute bottom-20 left-1/3 text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>HTMX</span
>
<span
class="absolute top-1/2 right-1/4 text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>Node</span
>
<span
class="absolute top-3/4 right-20 text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>Kafka</span
>
<span
class="absolute top-3/4 left-20 text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>Docker</span
>
<span
class="absolute top-20 left-100 text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>Mongo</span
>
<span
class="absolute top-15 right-100 text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>C++</span
>
<span
class="absolute bottom-20 right-1/3 text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>Javascript</span
>
<span
class="absolute top-1/2 left-1/4 text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>Nest</span
>
<span
class="absolute top-70 left-50 text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>MySQL</span
>
<span
class="absolute top-70 right-50 text-purple-300/50 dark:text-gray-300/10 text-xl font-bold"
>Python</span
>
{{ range .TemplateStore.ScatterWord }}
{{ template "scattered-words" . }}
{{ end }}
</div>
<!-- Header Section -->
<!-- {{ template "header" }} -->
<!-- About Section -->
<div id="content" class="flex-grow">{{ template "about-partial" . }}</div>
<div id="content" class="flex-grow">{{ template "about-partial" }}</div>
</div>
<!-- Footer Section -->
{{ template "footer" .}}
{{ template "footer" .TemplateStore.Footer}}
</body>
{{ template "script" }}
</html>

View File

@@ -19,7 +19,7 @@
hx-get="/projects"
hx-target="#content"
hx-push-url="true"
class="px-8 py-6 rounded-full bg-accent/5 dark:bg-darkaccent/40 backdrop-blur-xl border border-white/20 shadow-xl text-xl font-semibold hover:bg-accent/10 hover:dark:bg-darkaccent/30 transition"
class="cursor-pointer px-6 py-4 rounded-full bg-accent/5 dark:bg-darkaccent/40 backdrop-blur-xl border border-white/20 shadow-xl text-xl font-semibold hover:bg-accent/10 hover:dark:bg-darkaccent/30 transition"
>
Projects
</a>
@@ -29,7 +29,7 @@
hx-get="/experience"
hx-target="#content"
hx-push-url="true"
class="px-8 py-6 rounded-full bg-accent/5 dark:bg-darkaccent/40 backdrop-blur-xl border border-white/20 shadow-xl text-xl font-semibold hover:bg-accent/10 hover:dark:bg-darkaccent/30 transition"
class="cursor-pointer px-6 py-4 rounded-full bg-accent/5 dark:bg-darkaccent/40 backdrop-blur-xl border border-white/20 shadow-xl text-xl font-semibold hover:bg-accent/10 hover:dark:bg-darkaccent/30 transition"
>
Experience
</a>
@@ -41,11 +41,22 @@
>
<h2 class="text-3xl font-bold mb-6">About Me</h2>
<p class="text-lg leading-relaxed max-w-3xl">
Im a [Your Profession] with experience in [key skills]. I enjoy solving
I'm a [Your Profession] with experience in [key skills]. I enjoy solving
problems, learning new technologies, and building applications that make an
impact. Currently exploring
<span class="text-blue-600 font-semibold">Go</span>,
<span class="text-blue-600 font-semibold">htmx</span>, and modern web tools.
</p>
</section>
<div class="flex items-center justify-center">
<a
id="downloadCVButton"
hx-get="/download"
hx-swap="none"
class="underline cursor-pointer text-blue-600"
>
Download CV
</a>
</div>
{{ end }}

View File

@@ -49,27 +49,19 @@
</div>
<div class="block lg:hidden">
<div class="flex flex-col space-y-2 font-semibold text-start text-sm p-4 rounded-lg border-2 border-secondary dark:border-darksecondary shadow-md">
<div class="flex flex-col space-y-2 font-semibold text-start text-sm p-4 mb-2 rounded-lg border-2 border-secondary dark:border-darksecondary shadow-md">
<!-- Dates -->
<p>{{ .StartFrom }}</p>
<!-- Company + Type -->
<p>
{{ .Company }}
<span> {{ .EmployeeType }}</span>
</p>
<!-- Position -->
<p>{{ .Position }}</p>
<!-- Tasks -->
<ul class="list-disc list-inside mt-2 space-y-1 font-normal">
{{ range .Tasks }}
<li class="leading-snug">{{ . }}</li>
{{ end }}
</ul>
</div>
</div>
{{ end }}

View File

@@ -16,8 +16,9 @@
</span>
<span class="text-lg md:text-3xl font-bold underline"> Projects</span>
</div>
{{ range . }} {{ template "project-partial-range" . }} {{ end}} {{ end }} {{
block "project-partial-range" . }}
{{ range . }} {{ template "project" . }} {{ end}} {{ end }}
{{block "project" . }}
<div class="m-4">
<a href="{{ .Link }}" class="text-blue-600 hover:underline">{{ .Title }}</a> -
{{ .Description }}

View File

@@ -13,9 +13,9 @@
class="bg-primary dark:bg-darkprimary text-bodytext dark:text-darkbodytext flex flex-col min-h-screen"
>
<!-- Projects Section -->
<div id="content" class="flex-grow">{{ template "projects-partial" .}}</div>
<div id="content" class="flex-grow">{{ template "projects-partial" .Project}}</div>
<!-- Footer Section -->
{{ template "footer" }}
{{ template "footer" .TemplateStore.Footer}}
</body>
{{ template "script" }}
</html>

View File

@@ -1,7 +1,6 @@
{{ block "script" .}}
<script>
function clickBackButton() {
console.log('Back button clicked', window.history.state);
if (window.history.state && window.history.state["htmx"]) {
window.history.back();
} else {
@@ -12,11 +11,9 @@
const scatteredWords = document.getElementById('scatteredWords');
if (!scatteredWords) return;
const url = window.location.pathname;
console.log('Current URL:', url);
if (url === '/' || url === '') {
scatteredWords.classList.add('md:block');
} else {
console.log('Hiding scattered words');
scatteredWords.classList.remove('md:block');
}
}
@@ -25,11 +22,24 @@
document.body.addEventListener('htmx:pushedIntoHistory', function (evt) {
setScatteredWordsVisibility();
});
document.body.addEventListener('htmx:historyRestore', function (e) {
console.log('Restoring history state', e.detail);
setScatteredWordsVisibility();
});
document.body.addEventListener("htmx:afterRequest", (evt) => {
const trigger = evt.detail.xhr.getResponseHeader("Hx-Trigger");
if (trigger === "DownloadCVButton") {
if (evt.detail.xhr.status !== 200) {
alert("Failed to download CV. Please try again later.");
return;
}
const link = document.createElement('a');
link.href = '/download';
link.download = 'DilankaHerath-CV.pdf';
document.body.appendChild(link);
link.click();
link.remove();
}
});
});
</script>
{{ end }}