Browse Source

feat: change codebase structure

pull/1/head
Diego Vester 2 months ago
parent
commit
dca4d3d856
  1. 12
      docs/prompts/0.md
  2. 94
      src/App.vue
  3. 8
      src/assets/base.css
  4. 2
      src/assets/main.css
  5. 7
      src/components/HelloWorld.vue
  6. 79
      src/components/TheWelcome.vue
  7. 4
      src/components/WelcomeItem.vue
  8. 18
      src/components/__tests__/HelloWorld.spec.ts
  9. 7
      src/components/icons/IconCommunity.vue
  10. 7
      src/components/icons/IconDocumentation.vue
  11. 7
      src/components/icons/IconEcosystem.vue
  12. 7
      src/components/icons/IconSupport.vue
  13. 100
      src/components/layout/AppFooter.vue
  14. 81
      src/components/layout/AppHeader.vue
  15. 64
      src/components/sections/HeroSection.vue
  16. 65
      src/content/plushies.ts
  17. 200
      src/content/site-data.ts
  18. 18
      src/main.ts
  19. 18
      src/router/index.ts
  20. 16
      src/stores/counter.ts
  21. 118
      src/styles/global.css
  22. 132
      src/styles/variables.css
  23. 58
      src/views/AboutView.vue
  24. 20
      src/views/HomeView.vue

12
docs/prompts/0.md

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
I am developing a website with Vue 3 called lukastitch.corneruniverse.com for my friend. You are a coding assistance helping me build this website. Please read the contribution guidelines indocs/CONTRIBUTING.md. The codebase currently is the template provided when initializing a new Vue 3 project. I would like to continue to take inspiration from the template, utilizingcomponents, templates, and the router. I'd like to minimize bloat in App.vue.
As a side note, I've create an AI code generating tool called agent.corneruniverse.com. I primarilycreated the tool for myself for convenience. I intend to use it from my mobile device when taking public transportation. I will also allow my friend to use this tool to make changes to the website.
My friend is not technical and is not a developer. My friend will use language when generating code. We should build the website with this in mind to be more compatible with casual lingo when someone uses the agent.corneruniverse.com ai coding tool. An example of being compatible with casual lingo would be my friend creating a prompt to “change the title” or “change the subtitle” of the website. From my friend’s perspective, the msg variable from App.vue containing the text “You Did It!” would be title. The text in HelloWorld.vue of “You’ve successfully created a project with [Vite](https://vite.dev/) + [Vue 3](https://vuejs.org/). What's next?” would be the subtitle.
My friend will not have context of specifics in the codebase. My friend would not anticipate that the msg variable is a prop being passed into a component. The AI agent would not understand that a human may refer to the msg variable as a “title”. We should develop the codebase to be more “human compatible”. According to the CONTRIBUTING.md guidelines, site content should be stored in `/src/content/site-data.js`. One possible solution would be to add a field such as “tags” that contain syllables of what a human may use to refer to the content data.
My friend requested that the colors for the website be “Pastel! I love pastel colors. Pink, purple, and grey”.

94
src/App.vue

@ -1,85 +1,27 @@ @@ -1,85 +1,27 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
/**
* App.vue - Root application component
*
* AI CODING TOOL INSTRUCTIONS:
* This file should remain minimal. It only contains:
* - AppHeader (site navigation)
* - RouterView (page content)
* - AppFooter (site footer)
*
* To change site content, edit files in /src/content/
* To change layout, edit components in /src/components/layout/
* To change pages, edit files in /src/views/
*/
import AppHeader from "@/components/layout/AppHeader.vue";
import AppFooter from "@/components/layout/AppFooter.vue";
</script>
<template>
<header>
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
</div>
</header>
<AppHeader />
<RouterView />
<AppFooter />
</template>
<style scoped>
header {
line-height: 1.5;
max-height: 100vh;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
nav {
width: 100%;
font-size: 12px;
text-align: center;
margin-top: 2rem;
}
nav a.router-link-exact-active {
color: var(--color-text);
}
nav a.router-link-exact-active:hover {
background-color: transparent;
}
nav a {
display: inline-block;
padding: 0 1rem;
border-left: 1px solid var(--color-border);
}
nav a:first-of-type {
border: 0;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
nav {
text-align: left;
margin-left: -1rem;
font-size: 1rem;
padding: 1rem 0;
margin-top: 1rem;
}
}
/* App-level styles kept minimal - see /src/styles/ for global styles */
</style>

8
src/assets/base.css

@ -70,14 +70,14 @@ body { @@ -70,14 +70,14 @@ body {
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
"Segoe UI",
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
"Fira Sans",
"Droid Sans",
"Helvetica Neue",
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;

2
src/assets/main.css

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
@import './base.css';
@import "./base.css";
#app {
max-width: 1280px;

7
src/components/HelloWorld.vue

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
<script setup lang="ts">
defineProps<{
msg: string
}>()
msg: string;
}>();
</script>
<template>
@ -10,7 +10,8 @@ defineProps<{ @@ -10,7 +10,8 @@ defineProps<{
<h3>
Youve successfully created a project with
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
What's next?
</h3>
</div>
</template>

79
src/components/TheWelcome.vue

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
<script setup lang="ts">
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
import WelcomeItem from "./WelcomeItem.vue";
import DocumentationIcon from "./icons/IconDocumentation.vue";
import ToolingIcon from "./icons/IconTooling.vue";
import EcosystemIcon from "./icons/IconEcosystem.vue";
import CommunityIcon from "./icons/IconCommunity.vue";
import SupportIcon from "./icons/IconSupport.vue";
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
const openReadmeInEditor = () => fetch("/__open-in-editor?file=README.md");
</script>
<template>
@ -17,7 +17,9 @@ const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md') @@ -17,7 +17,9 @@ const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
<template #heading>Documentation</template>
Vues
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
<a href="https://vuejs.org/" target="_blank" rel="noopener"
>official documentation</a
>
provides you with all information you need to get started.
</WelcomeItem>
@ -28,23 +30,35 @@ const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md') @@ -28,23 +30,35 @@ const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
<a
href="https://vite.dev/guide/features.html"
target="_blank"
rel="noopener"
>Vite</a
>. The recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener"
>VSCode</a
>
+
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener"
<a
href="https://github.com/vuejs/language-tools"
target="_blank"
rel="noopener"
>Vue - Official</a
>. If you need to test your components and web pages, check out
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
and
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
/
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
<a href="https://playwright.dev/" target="_blank" rel="noopener"
>Playwright</a
>.
<br />
More instructions are available in
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
<a href="javascript:void(0)" @click="openReadmeInEditor"
><code>README.md</code></a
>.
</WelcomeItem>
@ -56,11 +70,21 @@ const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md') @@ -56,11 +70,21 @@ const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
Get official tools and libraries for your project:
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
you need more resources, we suggest paying
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
<a href="https://router.vuejs.org/" target="_blank" rel="noopener"
>Vue Router</a
>,
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener"
>Vue Test Utils</a
>, and
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener"
>Vue Dev Tools</a
>. If you need more resources, we suggest paying
<a
href="https://github.com/vuejs/awesome-vue"
target="_blank"
rel="noopener"
>Awesome Vue</a
>
a visit.
</WelcomeItem>
@ -73,10 +97,15 @@ const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md') @@ -73,10 +97,15 @@ const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
Got stuck? Ask your question on
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
(our official Discord server), or
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
<a
href="https://stackoverflow.com/questions/tagged/vue.js"
target="_blank"
rel="noopener"
>StackOverflow</a
>. You should also follow the official
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener"
>@vuejs.org</a
>
Bluesky account or the
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
X account for latest news in the Vue world.
@ -88,8 +117,10 @@ const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md') @@ -88,8 +117,10 @@ const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can help
us by
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
As an independent project, Vue relies on community backing for its
sustainability. You can help us by
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener"
>becoming a sponsor</a
>.
</WelcomeItem>
</template>

4
src/components/WelcomeItem.vue

@ -59,7 +59,7 @@ h3 { @@ -59,7 +59,7 @@ h3 {
}
.item:before {
content: ' ';
content: " ";
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
@ -68,7 +68,7 @@ h3 { @@ -68,7 +68,7 @@ h3 {
}
.item:after {
content: ' ';
content: " ";
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;

18
src/components/__tests__/HelloWorld.spec.ts

@ -1,11 +1,11 @@ @@ -1,11 +1,11 @@
import { describe, it, expect } from 'vitest'
import { describe, it, expect } from "vitest";
import { mount } from '@vue/test-utils'
import HelloWorld from '../HelloWorld.vue'
import { mount } from "@vue/test-utils";
import HelloWorld from "../HelloWorld.vue";
describe('HelloWorld', () => {
it('renders properly', () => {
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
expect(wrapper.text()).toContain('Hello Vitest')
})
})
describe("HelloWorld", () => {
it("renders properly", () => {
const wrapper = mount(HelloWorld, { props: { msg: "Hello Vitest" } });
expect(wrapper.text()).toContain("Hello Vitest");
});
});

7
src/components/icons/IconCommunity.vue

@ -1,5 +1,10 @@ @@ -1,5 +1,10 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
>
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>

7
src/components/icons/IconDocumentation.vue

@ -1,5 +1,10 @@ @@ -1,5 +1,10 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="17"
fill="currentColor"
>
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>

7
src/components/icons/IconEcosystem.vue

@ -1,5 +1,10 @@ @@ -1,5 +1,10 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="20"
fill="currentColor"
>
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>

7
src/components/icons/IconSupport.vue

@ -1,5 +1,10 @@ @@ -1,5 +1,10 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
>
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>

100
src/components/layout/AppFooter.vue

@ -0,0 +1,100 @@ @@ -0,0 +1,100 @@
<script setup lang="ts">
/**
* AppFooter - Site footer with copyright and links
*
* AI CODING TOOL INSTRUCTIONS:
* - "change copyright" -> update the copyright text
* - "add social link" -> add to social links section
* - "change footer text" -> update the footer content
*/
import { hero, contact } from "@/content/site-data";
const currentYear = new Date().getFullYear();
</script>
<template>
<footer class="app-footer">
<div class="footer-container">
<div class="footer-brand">
<span class="brand-name">{{ hero.title.value }}</span>
<p class="tagline">{{ hero.tagline.value }}</p>
</div>
<div class="footer-links">
<a
v-if="contact.etsyLink.value"
:href="contact.etsyLink.value"
target="_blank"
rel="noopener noreferrer"
class="etsy-link"
>
Visit Shop on Etsy
</a>
</div>
<div class="footer-copyright">
<p>&copy; {{ currentYear }} {{ hero.title.value }}. Made with love.</p>
</div>
</div>
</footer>
</template>
<style scoped>
.app-footer {
background-color: var(--color-background-mute);
border-top: 1px solid var(--color-border);
padding: var(--space-xl) var(--space-lg);
margin-top: auto;
}
.footer-container {
max-width: var(--max-width);
margin: 0 auto;
text-align: center;
}
.footer-brand {
margin-bottom: var(--space-lg);
}
.brand-name {
font-family: var(--font-heading);
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--color-primary-dark);
}
.tagline {
color: var(--color-text-light);
font-size: var(--font-size-sm);
margin-bottom: 0;
}
.footer-links {
margin-bottom: var(--space-lg);
}
.etsy-link {
display: inline-block;
padding: var(--space-sm) var(--space-lg);
background-color: var(--color-secondary);
color: white;
border-radius: var(--border-radius);
font-weight: 600;
transition: background-color var(--transition-fast);
}
.etsy-link:hover {
background-color: var(--color-secondary-dark);
color: white;
}
.footer-copyright {
color: var(--color-text-light);
font-size: var(--font-size-sm);
}
.footer-copyright p {
margin-bottom: 0;
}
</style>

81
src/components/layout/AppHeader.vue

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
<script setup lang="ts">
/**
* AppHeader - Site header with logo and navigation
*
* AI CODING TOOL INSTRUCTIONS:
* - "change the logo" -> update the logo image or text
* - "add a nav link" -> add to the navigation links array
* - "remove a nav link" -> remove from navigation links
* - "change header color" -> modify the header background style
*/
import { RouterLink } from "vue-router";
import { hero } from "@/content/site-data";
</script>
<template>
<header class="app-header">
<div class="header-container">
<RouterLink to="/" class="logo">
{{ hero.title.value }}
</RouterLink>
<nav class="main-nav">
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
</div>
</header>
</template>
<style scoped>
.app-header {
background-color: var(--color-background-soft);
border-bottom: 1px solid var(--color-border);
padding: var(--space-md) var(--space-lg);
position: sticky;
top: 0;
z-index: 100;
}
.header-container {
max-width: var(--max-width);
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-family: var(--font-heading);
font-size: var(--font-size-xl);
font-weight: 700;
color: var(--color-primary-dark);
text-decoration: none;
}
.logo:hover {
color: var(--color-primary);
}
.main-nav {
display: flex;
gap: var(--space-lg);
}
.main-nav a {
font-weight: 500;
color: var(--color-text);
padding: var(--space-xs) var(--space-sm);
border-radius: var(--border-radius);
transition: all var(--transition-fast);
}
.main-nav a:hover {
color: var(--color-primary-dark);
background-color: var(--color-primary-light);
}
.main-nav a.router-link-active {
color: var(--color-primary-dark);
}
</style>

64
src/components/sections/HeroSection.vue

@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
<script setup lang="ts">
/**
* HeroSection - Main hero/landing section of the homepage
*
* AI CODING TOOL INSTRUCTIONS:
* Content is loaded from /src/content/site-data.js
*
* When user says:
* - "change the title" -> edit hero.title.value in site-data.js
* - "change the subtitle" or "tagline" -> edit hero.tagline.value in site-data.js
* - "change the description" -> edit hero.description.value in site-data.js
* - "change hero colors" -> modify the styles below or variables.css
*/
import { hero } from "@/content/site-data";
</script>
<template>
<section class="hero-section">
<div class="hero-container">
<h1 class="hero-title">{{ hero.title.value }}</h1>
<p class="hero-tagline">{{ hero.tagline.value }}</p>
<p class="hero-description">{{ hero.description.value }}</p>
</div>
</section>
</template>
<style scoped>
.hero-section {
padding: var(--space-2xl) var(--space-lg);
text-align: center;
background: linear-gradient(
135deg,
var(--color-background-soft) 0%,
var(--color-primary-light) 50%,
var(--color-secondary-light) 100%
);
}
.hero-container {
max-width: var(--content-width);
margin: 0 auto;
}
.hero-title {
font-size: clamp(2.5rem, 8vw, 4rem);
color: var(--color-primary-dark);
margin-bottom: var(--space-md);
letter-spacing: -0.02em;
}
.hero-tagline {
font-size: var(--font-size-xl);
color: var(--color-secondary-dark);
font-weight: 500;
margin-bottom: var(--space-lg);
}
.hero-description {
font-size: var(--font-size-lg);
color: var(--color-text);
max-width: 600px;
margin: 0 auto;
}
</style>

65
src/content/plushies.ts

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
/**
* Plushie Catalog - lukastitch.corneruniverse.com
*
* AI CODING TOOL INSTRUCTIONS:
* This file contains the list of plushies displayed in the gallery.
*
* Common user requests and how to handle them:
* - "add a plushie" -> add a new object to the plushies array
* - "remove a plushie" -> remove the object with matching name/id
* - "mark as sold" -> set available: false
* - "mark as available" -> set available: true
* - "change plushie description" -> update the description field
* - "update etsy link" -> update the etsyLink field
*/
export interface Plushie {
id: string;
name: string;
image: string;
description: string;
size: string;
available: boolean;
etsyLink: string | null;
tags?: string[]; // Optional: helps with search
}
export const plushies: Plushie[] = [
// Example entry - replace with real plushies:
// {
// id: 'cozy-bear',
// name: 'Cozy Bear',
// image: '/images/plushies/cozy-bear.jpg',
// description: 'A huggable friend for cold nights.',
// size: '12 inches',
// available: false,
// etsyLink: null,
// tags: ['bear', 'cozy', 'huggable']
// }
];
/**
* Find a plushie by name (case-insensitive partial match)
*/
export function findPlushie(searchName: string): Plushie | undefined {
const normalized = searchName.toLowerCase().trim();
return plushies.find(
(p) =>
p.name.toLowerCase().includes(normalized) ||
p.id.toLowerCase().includes(normalized),
);
}
/**
* Get available plushies only
*/
export function getAvailablePlushies(): Plushie[] {
return plushies.filter((p) => p.available);
}
/**
* Get sold plushies only
*/
export function getSoldPlushies(): Plushie[] {
return plushies.filter((p) => !p.available);
}

200
src/content/site-data.ts

@ -0,0 +1,200 @@ @@ -0,0 +1,200 @@
/**
* Site Content Data - lukastitch.corneruniverse.com
*
* This file contains all editable site content in one place.
*
* AI CODING TOOL INSTRUCTIONS:
* Each content field has a "tags" array containing words and phrases that
* humans might use to refer to this content. When a user says something like
* "change the title" or "update the main heading", match their language to
* the tags to find the correct field to modify.
*
* Tag matching examples:
* - "change the title" -> look for tags containing "title"
* - "update the subtitle" -> look for tags containing "subtitle"
* - "change the tagline" -> look for tags containing "tagline"
* - "update the description" -> look for tags containing "description"
*/
interface ContentField {
value: string | null;
tags: string[];
}
interface HeroContent {
title: ContentField;
tagline: ContentField;
description: ContentField;
}
interface AboutContent {
heading: ContentField;
text: ContentField;
image: ContentField;
}
interface ContactContent {
heading: ContentField;
text: ContentField;
etsyLink: ContentField;
email: ContentField;
}
export const hero: HeroContent = {
title: {
value: "Luka Stitch",
tags: [
"title",
"main title",
"heading",
"main heading",
"name",
"brand name",
"site name",
"website name",
"big text",
"header text",
],
},
tagline: {
value: "Handmade plushies with love",
tags: [
"tagline",
"subtitle",
"slogan",
"motto",
"subheading",
"secondary text",
"smaller text under title",
],
},
description: {
value: "Each creation is one-of-a-kind, crafted with care.",
tags: [
"description",
"intro",
"introduction",
"about text",
"body text",
"paragraph",
"blurb",
],
},
};
export const about: AboutContent = {
heading: {
value: "About",
tags: ["about title", "about heading", "about section title"],
},
text: {
value:
"Hi! I'm the maker behind Luka Stitch. I create handmade plushies, each one crafted with love and attention to detail. Every plushie is unique and made to bring joy.",
tags: [
"about text",
"about description",
"about paragraph",
"bio",
"biography",
"about me",
"maker story",
],
},
image: {
value: "/images/about-photo.jpg",
tags: ["about image", "about photo", "maker photo", "profile photo"],
},
};
export const contact: ContactContent = {
heading: {
value: "Get in Touch",
tags: ["contact title", "contact heading", "get in touch title"],
},
text: {
value: "Interested in a custom plushie? I'd love to hear from you!",
tags: [
"contact text",
"contact description",
"contact message",
"reach out text",
],
},
etsyLink: {
value: "https://etsy.com/shop/lukastitch",
tags: ["etsy", "etsy link", "shop link", "store link", "buy link"],
},
email: {
value: null,
tags: ["email", "email address", "contact email"],
},
};
type SectionName = "hero" | "about" | "contact";
interface TagSearchResult {
section: SectionName;
field: string;
value: string | null;
tags: string[];
}
/**
* Helper function to find content by tag
* Usage: findByTag("title") returns { section: "hero", field: "title", value: "Luka Stitch" }
*/
export function findByTag(searchTag: string): TagSearchResult | null {
const normalizedSearch = searchTag.toLowerCase().trim();
const sections = { hero, about, contact } as const;
for (const sectionName of Object.keys(sections) as SectionName[]) {
const section = sections[sectionName];
for (const [fieldName, field] of Object.entries(section)) {
const contentField = field as ContentField;
if (
contentField.tags &&
contentField.tags.some(
(tag: string) =>
tag.toLowerCase().includes(normalizedSearch) ||
normalizedSearch.includes(tag.toLowerCase()),
)
) {
return {
section: sectionName,
field: fieldName,
value: contentField.value,
tags: contentField.tags,
};
}
}
}
return null;
}
interface ContentItem {
path: string;
value: string | null;
tags: string[];
}
/**
* Get all content as a flat list with their tags
* Useful for AI tools to understand available content
*/
export function getAllContent(): ContentItem[] {
const sections = { hero, about, contact } as const;
const content: ContentItem[] = [];
for (const sectionName of Object.keys(sections) as SectionName[]) {
const section = sections[sectionName];
for (const [fieldName, field] of Object.entries(section)) {
const contentField = field as ContentField;
content.push({
path: `${sectionName}.${fieldName}`,
value: contentField.value,
tags: contentField.tags || [],
});
}
}
return content;
}

18
src/main.ts

@ -1,14 +1,14 @@ @@ -1,14 +1,14 @@
import './assets/main.css'
import "./styles/global.css";
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from './App.vue'
import router from './router'
import App from "./App.vue";
import router from "./router";
const app = createApp(App)
const app = createApp(App);
app.use(createPinia())
app.use(router)
app.use(createPinia());
app.use(router);
app.mount('#app')
app.mount("#app");

18
src/router/index.ts

@ -1,23 +1,23 @@ @@ -1,23 +1,23 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
path: "/",
name: "home",
component: HomeView,
},
{
path: '/about',
name: 'about',
path: "/about",
name: "about",
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue'),
component: () => import("../views/AboutView.vue"),
},
],
})
});
export default router
export default router;

16
src/stores/counter.ts

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import { ref, computed } from "vue";
import { defineStore } from "pinia";
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
export const useCounterStore = defineStore("counter", () => {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++
count.value++;
}
return { count, doubleCount, increment }
})
return { count, doubleCount, increment };
});

118
src/styles/global.css

@ -0,0 +1,118 @@ @@ -0,0 +1,118 @@
/**
* Global Styles - lukastitch.corneruniverse.com
*
* Base styles and resets that apply site-wide.
* Uses variables from variables.css for theming.
*/
@import "./variables.css";
/* Import Google Fonts */
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700&family=Quicksand:wght@500;600;700&display=swap");
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
body {
min-height: 100vh;
font-family: var(--font-body);
font-size: var(--font-size-base);
line-height: 1.6;
color: var(--color-text);
background-color: var(--color-background);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Typography */
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: var(--font-heading);
color: var(--color-heading);
line-height: 1.3;
}
h1 {
font-size: var(--font-size-3xl);
font-weight: 700;
}
h2 {
font-size: var(--font-size-2xl);
font-weight: 600;
}
h3 {
font-size: var(--font-size-xl);
font-weight: 600;
}
p {
margin-bottom: var(--space-md);
}
/* Links */
a {
color: var(--color-accent);
text-decoration: none;
transition: color var(--transition-fast);
}
a:hover {
color: var(--color-accent-hover);
}
/* Images */
img {
max-width: 100%;
height: auto;
display: block;
}
/* Buttons */
button {
font-family: inherit;
cursor: pointer;
}
/* Section wrapper utility */
.section {
padding: var(--space-xl) var(--space-lg);
}
.container {
max-width: var(--max-width);
margin: 0 auto;
padding: 0 var(--space-lg);
}
/* Focus states for accessibility */
:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
/* Selection color */
::selection {
background-color: var(--color-primary-light);
color: var(--color-heading);
}

132
src/styles/variables.css

@ -0,0 +1,132 @@ @@ -0,0 +1,132 @@
/**
* Design System Variables - lukastitch.corneruniverse.com
*
* Color Palette: Pastel pink, purple, and grey
* Mood: Warm, cozy, handcrafted feel
*
* AI CODING TOOL INSTRUCTIONS:
* When user mentions colors:
* - "primary color" or "main color" -> --color-primary (pastel pink)
* - "secondary color" or "accent" -> --color-secondary (pastel purple)
* - "background" -> --color-background
* - "make it more pink/purple/grey" -> adjust the relevant color values
*/
:root {
/* ============================================
PASTEL COLOR PALETTE
Pink, Purple, and Grey as requested
============================================ */
/* Primary: Soft Pastel Pink */
--color-primary: #f4a5c0;
--color-primary-light: #fcd5e5;
--color-primary-dark: #e87aa0;
/* Secondary: Soft Pastel Purple */
--color-secondary: #c9a5f4;
--color-secondary-light: #e5d5fc;
--color-secondary-dark: #a07ae8;
/* Neutral: Warm Grey */
--color-grey: #9e9e9e;
--color-grey-light: #e0e0e0;
--color-grey-dark: #6e6e6e;
/* Background Colors */
--color-background: #fefcfd;
--color-background-soft: #fdf5f8;
--color-background-mute: #f8eef2;
/* Text Colors */
--color-text: #4a4a4a;
--color-text-light: #6e6e6e;
--color-heading: #3d3d3d;
/* Border Colors */
--color-border: #e8dde2;
--color-border-hover: #d4c4cc;
/* Accent for links and interactive elements */
--color-accent: #c9a5f4;
--color-accent-hover: #a07ae8;
/* Status Colors */
--color-available: #a5d6a7;
--color-sold: #ef9a9a;
/* ============================================
TYPOGRAPHY
============================================ */
--font-heading: "Quicksand", "Nunito", sans-serif;
--font-body:
"Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.25rem;
--font-size-xl: 1.5rem;
--font-size-2xl: 2rem;
--font-size-3xl: 2.5rem;
/* ============================================
SPACING
============================================ */
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 2rem;
--space-xl: 4rem;
--space-2xl: 6rem;
--section-gap: 4rem;
/* ============================================
LAYOUT
============================================ */
--max-width: 1200px;
--content-width: 800px;
--gallery-gap: 1.5rem;
--card-radius: 12px;
--border-radius: 8px;
/* ============================================
SHADOWS
============================================ */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
/* ============================================
TRANSITIONS
============================================ */
--transition-fast: 0.15s ease;
--transition-base: 0.3s ease;
--transition-slow: 0.5s ease;
}
/* Dark mode adjustments - keeping the pastel feel but adjusted for dark backgrounds */
@media (prefers-color-scheme: dark) {
:root {
--color-background: #2d2a2c;
--color-background-soft: #363133;
--color-background-mute: #403b3d;
--color-text: #e8e4e6;
--color-text-light: #b8b4b6;
--color-heading: #f4f0f2;
--color-border: #4a4547;
--color-border-hover: #5a5557;
/* Pastels stay similar but slightly more saturated for visibility */
--color-primary: #f4a5c0;
--color-secondary: #c9a5f4;
}
}

58
src/views/AboutView.vue

@ -1,15 +1,53 @@ @@ -1,15 +1,53 @@
<script setup lang="ts">
/**
* AboutView - About page
*
* AI CODING TOOL INSTRUCTIONS:
* To change the about content, edit /src/content/site-data.js
* Look for the "about" section with fields: heading, text, image
*/
import { about } from "@/content/site-data";
</script>
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
<main class="about-page">
<section class="about-section">
<div class="about-container">
<h1>{{ about.heading.value }}</h1>
<p class="about-text">{{ about.text.value }}</p>
</div>
</section>
</main>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
<style scoped>
.about-page {
flex: 1;
}
.about-section {
padding: var(--space-2xl) var(--space-lg);
background: linear-gradient(
180deg,
var(--color-background-soft) 0%,
var(--color-background) 100%
);
}
.about-container {
max-width: var(--content-width);
margin: 0 auto;
text-align: center;
}
.about-container h1 {
color: var(--color-primary-dark);
margin-bottom: var(--space-lg);
}
.about-text {
font-size: var(--font-size-lg);
color: var(--color-text);
line-height: 1.8;
}
</style>

20
src/views/HomeView.vue

@ -1,9 +1,25 @@ @@ -1,9 +1,25 @@
<script setup lang="ts">
import TheWelcome from '../components/TheWelcome.vue'
/**
* HomeView - Homepage
*
* AI CODING TOOL INSTRUCTIONS:
* This is the main landing page. It contains:
* - HeroSection: The main title, tagline, and description
*
* To change the title/subtitle/description, edit /src/content/site-data.js
* To add new sections, import and add them below HeroSection
*/
import HeroSection from "@/components/sections/HeroSection.vue";
</script>
<template>
<main>
<TheWelcome />
<HeroSection />
</main>
</template>
<style scoped>
main {
flex: 1;
}
</style>

Loading…
Cancel
Save