Add tree support, to v16.1.4

This commit is contained in:
2025-11-08 18:22:47 +07:00
parent b428649aa0
commit 28946028e8
8 changed files with 205 additions and 9 deletions
+41 -1
View File
@@ -3,11 +3,13 @@
--ui-primary: var(--color-deep-dark); --ui-primary: var(--color-deep-dark);
--ui-container: 64rem; --ui-container: 64rem;
--ui-container-narrow: 48rem; --ui-container-narrow: 48rem;
--ui-container-narrow-very: 36rem;
--ui-header-logo-inverted: 1; --ui-header-logo-inverted: 1;
--ui-header-height: calc(var(--ui-spacing)*14); --ui-header-height: calc(var(--ui-spacing)*14);
--ui-header-height-collapsed: calc(var(--ui-spacing)*12); --ui-header-height-collapsed: calc(var(--ui-spacing)*12);
--color-surface-container: #f9f9f9; --color-surface-container: #f9f9f9;
--color-surface-container-glass: #f9f9f9de;
--color-on-surface-container: #111111; --color-on-surface-container: #111111;
--color-inverse-surface-container: #111111; --color-inverse-surface-container: #111111;
--color-on-inverse-surface-container: #f9f9f9; --color-on-inverse-surface-container: #f9f9f9;
@@ -40,6 +42,7 @@
@media screen and (prefers-color-scheme: dark) { @media screen and (prefers-color-scheme: dark) {
:root { :root {
--color-surface-container: #111111; --color-surface-container: #111111;
--color-surface-container-glass: #111111de;
--color-on-surface-container: #eeeeee; --color-on-surface-container: #eeeeee;
--color-inverse-surface-container: #eeeeee; --color-inverse-surface-container: #eeeeee;
--color-on-inverse-surface-container: #111111; --color-on-inverse-surface-container: #111111;
@@ -341,6 +344,10 @@ p {
background: var(--color-inverse-surface-container); background: var(--color-inverse-surface-container);
padding-block: calc(var(--ui-spacing)*12); padding-block: calc(var(--ui-spacing)*12);
} }
.article.article-footer.article-footer-no-bg {
color: inherit;
background: transparent;
}
.article.article-footer .web-footer-link > a:active { .article.article-footer .web-footer-link > a:active {
text-decoration-style: solid; text-decoration-style: solid;
} }
@@ -363,6 +370,9 @@ p {
.web-section.web-section-narrow { .web-section.web-section-narrow {
max-width: var(--ui-container-narrow); max-width: var(--ui-container-narrow);
} }
.web-section.web-section-narrow-very {
max-width: var(--ui-container-narrow-very);
}
.about-me-photo-grid { .about-me-photo-grid {
display: grid; display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr)); grid-template-columns: repeat(1, minmax(0, 1fr));
@@ -713,7 +723,7 @@ a.btn {
flex: 3; flex: 3;
} }
.prose p, li { line-height: 1.5; } .prose p, li { line-height: 1.5; }
.prose hr { margin-block: 1em; border: 1px solid var(--color-outline-intense); } hr, .prose hr { height: 1px; margin-block: 1em; border: none; border-bottom: 1px solid var(--color-outline-intense); }
.prose li { margin-block: 1em; } .prose li { margin-block: 1em; }
.prose h1,h2,h3,h4,h5,h6 { .prose h1,h2,h3,h4,h5,h6 {
font-family: var(--font-serif); font-family: var(--font-serif);
@@ -794,4 +804,34 @@ a.btn {
padding: 0 calc(var(--ui-spacing)*4); padding: 0 calc(var(--ui-spacing)*4);
margin: 0 auto; margin: 0 auto;
justify-content: space-between; justify-content: space-between;
}
.tree-link-card {
display: block;
padding: calc(var(--ui-spacing)*4) calc(var(--ui-spacing)*6);
border-radius: 100px;
background: var(--color-primary);
color: var(--color-on-primary);
margin-bottom: calc(var(--ui-spacing)*1);
}
.tree-link-card:hover {
background: var(--color-inverse);
color: var(--color-on-inverse);
}
.tree-link-card:active {
opacity: .8;
}
a.tree-link-card {
cursor: pointer;
}
.frosted-glass-backdrop-dynamic {
background: var(--color-surface-container-glass);
top: 0;
left: 0;
bottom: 0;
right: 0;
position: fixed;
z-index: -10;
backdrop-filter: blur(36px);
-webkit-backdrop-filter: (36px);
} }
+12 -5
View File
@@ -1,11 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
const props = defineProps<{
isCompact?: boolean;
isTransparent?: boolean;
title?: string;
noTitle?: boolean;
narrowVery?: boolean;
}>();
</script> </script>
<template> <template>
<footer> <footer>
<div class="article article-footer"> <div :class="{'article': true, 'article-footer': true, 'article-footer-no-bg': props.isTransparent}">
<section class="web-section"> <section v-if="!props.isCompact" :class="{'web-section': true, 'web-section-narrow-very': props.narrowVery}">
<div class="web-footer-links-container-container"> <div class="web-footer-links-container-container">
<div class="web-footer-links-container" style="margin-right: 4rem;"> <div class="web-footer-links-container" style="margin-right: 4rem;">
<h3 class="web-footer-link-title">Site</h3> <h3 class="web-footer-link-title">Site</h3>
@@ -26,10 +33,10 @@ const currentYear = new Date().getFullYear();
</div> </div>
</div> </div>
</section> </section>
<section class="web-section"> <section :class="{'web-section': true, 'web-section-narrow-very': props.narrowVery}">
<div> <div>
<p class="web-footer-notice"> <p v-if="!props.noTitle" class="web-footer-notice">
Techit Thawiang's Website {{props.title || "Techit Thawiang's Website"}}
</p> </p>
<p class="web-footer-notice"> <p class="web-footer-notice">
Copyright &copy; Techit Thawiang {{ currentYear }} ({{ currentYear + 543 }}). All rights reserved.<br> Copyright &copy; Techit Thawiang {{ currentYear }} ({{ currentYear + 543 }}). All rights reserved.<br>
+5
View File
@@ -0,0 +1,5 @@
<template>
<div>
<slot/>
</div>
</template>
+94
View File
@@ -0,0 +1,94 @@
<template>
<main style="background: transparent;">
<div class="frosted-glass-backdrop-dynamic"></div>
<article class="article article-no-bg" v-if="tree">
<section class="web-section web-section-narrow-very">
<img :width="128" :height="128" :src="tree.profile">
<h1 v-if="tree.name">{{ tree.name }}</h1>
<p v-if="tree.desc">{{ tree.desc }}</p>
</section>
<section class="web-section web-section-narrow-very">
<div v-for="link in tree.links">
<a class="tree-link-card" :href="link.url" v-if="link.enabled && !link.separate">
{{ link.name }}
</a>
<hr v-else-if="link.separate">
</div>
</section>
</article>
</main>
</template>
<script setup lang="ts">
const config = useRuntimeConfig();
const route = useRoute();
const JSON_URL = config.public.treeJsonUrl
const treeUserData = JSON_URL + route.params.slug + '.json'
interface TreeUserData {
id: string
enabled: boolean
name: string
desc: string
cover: string
background: string
profile: string
links: Record<string, TreeUserDataLink>
};
interface TreeUserDataLink {
id: string
ico: string
enabled: boolean
separate: boolean
name: string
desc: string
url: string
}
const tree = ref<TreeUserData | null>(null)
onMounted(async () => {
try {
const data = await $fetch<TreeUserData>(treeUserData)
if (!data) return
tree.value = data
} catch (err) {
console.error('User tree fetch failed:', err)
}
document.body.style.backgroundImage = `url('${tree.value?.background}')`
useSeoMeta({
titleTemplate: "%s",
title: tree.value?.name + "'s Link",
description: tree.value?.desc,
ogTitle: tree.value?.name,
ogDescription: tree.value?.desc,
ogSiteName: config.public.siteName,
twitterCard: 'summary_large_image',
twitterTitle: tree.value?.name,
twitterDescription: tree.value?.desc,
twitterSite: config.public.twitterUsername
})
})
definePageMeta({
layout: 'tree'
})
useSeoMeta({
robots: "noindex",
})
</script>
<style>
body {
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
background-attachment: fixed;
}
</style>
+33
View File
@@ -0,0 +1,33 @@
<template>
<main>
<article class="article">
<section class="web-section" aria-labelledby="hero" aria-describedby="what-does-he-do?">
<h1 id="hero" class="font-hero">Tree</h1>
<p id="what-does-he-do?" class="font-hero-desc">A SaaS from me to list all your <i>publicly-available</i> link and stuff.</p>
</section>
<section class="web-section" aria-labelledby="about-me" aria-describedby="about-me-paragraph-1">
<h2 class="web-title" id="about-me">How to use?</h2>
<p id="about-me-paragraph-1">Currently I don't have an easy way to make it, but you can <NuxtLink href="/contact">contact me</NuxtLink> to get one for yourself.</p>
</section>
</article>
</main>
</template>
<script setup>
const config = useRuntimeConfig();
const baseUrl = config.public.baseUrl
const TITLE = "Tree"
const DESC = "A SaaS from me to list all your publicly-available link and stuff."
useSeoMeta({
title: TITLE,
description: DESC,
ogTitle: TITLE + ' / ' + config.public.siteName,
ogDescription: DESC,
ogSiteName: config.public.siteName,
twitterCard: 'summary_large_image',
twitterTitle: TITLE + ' / ' + config.public.siteName,
twitterDescription: DESC,
twitterSite: config.public.twitterUsername
})
</script>
+3 -1
View File
@@ -11,7 +11,8 @@ export default defineNuxtConfig({
siteName: process?.env?.NUXT_PUBLIC_SITE_NAME, siteName: process?.env?.NUXT_PUBLIC_SITE_NAME,
twitterUsername: process?.env?.NUXT_PUBLIC_TWITTER_USERNAME, twitterUsername: process?.env?.NUXT_PUBLIC_TWITTER_USERNAME,
fontUrl: process?.env?.NUXT_PUBLIC_FONTS_URL, fontUrl: process?.env?.NUXT_PUBLIC_FONTS_URL,
webBannerDataUrl: process?.env?.NUXT_PUBLIC_BANNER_DATA_URL webBannerDataUrl: process?.env?.NUXT_PUBLIC_BANNER_DATA_URL,
treeJsonUrl: process?.env?.NUXT_PUBLIC_TREE_JSON_URL
} }
}, },
modules: ['@nuxt/content', '@nuxtjs/sitemap', '@nuxt/image', '@nuxt/icon'], modules: ['@nuxt/content', '@nuxtjs/sitemap', '@nuxt/image', '@nuxt/icon'],
@@ -102,5 +103,6 @@ export default defineNuxtConfig({
"*": { experimentalNoScripts: true }, // one level deep, render all pages statically "*": { experimentalNoScripts: true }, // one level deep, render all pages statically
"posts/*": { experimentalNoScripts: true }, // one level deep, render all post pages statically "posts/*": { experimentalNoScripts: true }, // one level deep, render all post pages statically
"fonts": { experimentalNoScripts: false }, // except /fonts "fonts": { experimentalNoScripts: false }, // except /fonts
"tree/*": { experimentalNoScripts: false }, // except /tree
} }
}) })
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@techitwinner/web", "name": "@techitwinner/web",
"version": "16.1.3", "version": "16.1.4",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+16 -1
View File
@@ -1,4 +1,19 @@
{ {
// https://nuxt.com/docs/guide/concepts/typescript // https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json" "extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
"strict": true,
// "types": ["@types/node"],
"paths": {
"~/*": ["./*"],
"@/*": ["./*"]
}
},
"include": [
".nuxt/*",
"nuxt.config.ts",
"app/**/*.ts",
"app/**/*.vue",
"app/types/**/*.d.ts"
]
} }