init Project

This commit is contained in:
wieerwill 2023-12-20 22:32:17 +01:00
parent 53bf5da527
commit 0b3ba4f8d2
29 changed files with 4571 additions and 0 deletions

15
.eslintrc.cjs Normal file
View File

@ -0,0 +1,15 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
}
}

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
test-results/
playwright-report/

8
.prettierrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}

9
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"recommendations": [
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"ms-playwright.playwright",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

82
README.md Normal file
View File

@ -0,0 +1,82 @@
# Ominous Generator: A Vue.js App into the Realm of Sinister Positivity and Dark Negativity
<div><img src="titleimage.png" style="margin: 0 auto;" height="300" width="300" ></div>
Welcome to the Ominous Generator, a Vue.js powered web app where the sun shines a little too bright, and the night whispers a tad too loudly. This is not your ordinary motivational quote generator. Instead, we get into the world of 'Ominous Positivity' and 'Dark Negativity,' where every sentence is a roller coaster ride of emotions, filled with twists and turns that leave you delightfully perplexed.
## Project Overview
The Ominous Generator is a quirky, interactive web application developed using Vue.js, showcasing a unique blend of inspiring and daunting messages. This project is a fun exploration into the world of web development, combining humor, creativity, and a dash of existential dread (the good kind, we promise!).
### What's Inside the Box?
- **Two Flavors of Fortune**: Choose your destiny with two distinct generators - one that serves you a cocktail of aggressive optimism, and another that dishes out doses of ominous foreboding.
- **Interactive UI**: Engage with big, bold buttons labeled 'Positivity' (sun-kissed, of course) and 'Negativity' (moonlit, naturally), and watch as the generator conjures up phrases that are equal parts motivating and menacing.
- **Responsive Design**: Tailored to fit every screen size. Whether you're on a desktop conjuring up some positive vibes, or on your phone in a dark alley looking for a foreboding omen, we've got you covered.
- **Vue.js & Pinia at Heart**: Built using Vue.js and harnessing the power of Pinia for state management, this app is a testament to the fun and flexibility of modern web development.
## How to Use the Ominous Generator
1. **Choose Your Fate**: Click on 'Positivity' for an aggressively optimistic message, or 'Negativity' for a darkly negative one.
2. **Behold the Outcome**: Watch as the app generates a unique, two-part sentence - starting with a promise or warning, and ending with an inevitable twist.
3. **History of Your Fortunes**: The app keeps track of your generated sentences, displaying the most recent ones in a list for your amusement (or existential contemplation).
## Contribute & Collaborate
Got an idea to make the Ominous Generator even more whimsically sinister? We love collaboration! Feel free to fork the repo, push some changes, or reach out with your ideas. Let's make the web a more amusing place, one ominous message at a time!
---
Embrace the fun, the strange, and the slightly ominous. Happy generating!
*Remember: Every day is a good day, some are just a bit more... ominous.* 🌙☀️🖤💛
---
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Project Setup
```sh
pnpm install
```
Compile and Hot-Reload for Development
```sh
pnpm dev
```
Type-Check, Compile and Minify for Production
```sh
pnpm build
```
Run Unit Tests with [Vitest](https://vitest.dev/)
```sh
pnpm test:unit
```
Run End-to-End Tests with [Playwright](https://playwright.dev)
```sh
# Install browsers for the first run
npx playwright install
# When testing on CI, must build the project first
pnpm build
# Runs the end-to-end tests
pnpm test:e2e
# Runs the tests only on Chromium
pnpm test:e2e --project=chromium
# Runs the tests of a specific file
pnpm test:e2e tests/example.spec.ts
# Runs the tests in debug mode
pnpm test:e2e --debug
```
Lint with [ESLint](https://eslint.org/)
```sh
pnpm lint
```

4
e2e/tsconfig.json Normal file
View File

@ -0,0 +1,4 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"include": ["./**/*"]
}

8
e2e/vue.spec.ts Normal file
View File

@ -0,0 +1,8 @@
import { test, expect } from '@playwright/test';
// See here how to get started:
// https://playwright.dev/docs/intro
test('visits the app root url', async ({ page }) => {
await page.goto('/');
await expect(page.locator('div.greetings > h1')).toHaveText('You did it!');
})

1
env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Omnious Generator</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

43
package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "omniousgenerator",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"test:unit": "vitest",
"test:e2e": "playwright test",
"build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"pinia": "^2.1.7",
"vue": "^3.3.11"
},
"devDependencies": {
"@playwright/test": "^1.40.1",
"@rushstack/eslint-patch": "^1.3.3",
"@tsconfig/node18": "^18.2.2",
"@types/jsdom": "^21.1.6",
"@types/node": "^18.19.3",
"@vitejs/plugin-vue": "^4.5.2",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/test-utils": "^2.4.3",
"@vue/tsconfig": "^0.5.0",
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.17.0",
"jsdom": "^23.0.1",
"npm-run-all2": "^6.1.1",
"prettier": "^3.0.3",
"typescript": "~5.3.0",
"vite": "^5.0.10",
"vitest": "^1.0.4",
"vue-tsc": "^1.8.25"
}
}

109
playwright.config.ts Normal file
View File

@ -0,0 +1,109 @@
import { defineConfig, devices } from '@playwright/test'
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e',
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000
},
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:5173',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
/* Only on CI systems run the tests headless */
headless: !!process.env.CI
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome']
}
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox']
}
},
{
name: 'webkit',
use: {
...devices['Desktop Safari']
}
}
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: {
// ...devices['Pixel 5'],
// },
// },
// {
// name: 'Mobile Safari',
// use: {
// ...devices['iPhone 12'],
// },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: {
// channel: 'msedge',
// },
// },
// {
// name: 'Google Chrome',
// use: {
// channel: 'chrome',
// },
// },
],
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
// outputDir: 'test-results/',
/* Run your local dev server before starting the tests */
webServer: {
/**
* Use the dev server by default for faster feedback loop.
* Use the preview server on CI for more realistic testing.
* Playwright will re-use the local server if there is already a dev-server running.
*/
command: process.env.CI ? 'vite preview --port 5173' : 'vite dev',
port: 5173,
reuseExistingServer: !process.env.CI
}
})

3586
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

24
src/App.vue Normal file
View File

@ -0,0 +1,24 @@
<template>
<div id="app">
<header-component />
<ominous-generator />
<footer-component />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import HeaderComponent from './components/HeaderComponent.vue';
import FooterComponent from './components/FooterComponent.vue';
import OminousGenerator from './components/OmniousGenerator.vue';
export default defineComponent({
name: 'App',
components: {
HeaderComponent,
FooterComponent,
OminousGenerator
}
});
</script>
<style></style>

86
src/assets/base.css Normal file
View File

@ -0,0 +1,86 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

34
src/assets/main.css Normal file
View File

@ -0,0 +1,34 @@
@import './base.css';
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
text-align: center;
}
section {
margin: 20px;
padding: 20px;
border: 1px solid #ccc;
}
button {
padding: 10px 20px;
cursor: pointer;
}
@media (max-width: 600px) {
/* Stile für kleine Bildschirme */
h2 {
font-size: 1.5rem;
}
}
@media (min-width: 601px) {
/* Stile für große Bildschirme */
h2 {
font-size: 2rem;
}
}

View File

@ -0,0 +1,36 @@
<template>
<footer class="footer">
<a href="https://github.com/wieerwill/" target="_blank">GitHub</a>
<a href="https://wieerwill.de" target="_blank">WieErWill.de</a>
</footer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'FooterComponent'
});
</script>
<style scoped>
.footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
width: 90%;
margin: 5rem auto 0 auto;
background-color: #f8f8f87e;
border-top: 1px solid #e7e7e7;
}
.footer a {
flex: 1;
text-align: center;
margin: 0 10px;
color: #e7e7e7;
text-decoration: none;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<header>
<div class="title-container">
<h1 class="main-title">Ominous Generator</h1>
<p class="subtitle">Unveil the mysteries of fate</p>
</div>
</header>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'HeaderComponent',
});
</script>
<style scoped>
header {
margin: 0 auto 3rem auto;
}
.title-container {
text-align: center;
margin-top: 30px;
}
.main-title {
font-size: 2.5em;
color: #333;
}
.subtitle {
font-size: 1.2em;
color: #666;
margin-top: 10px;
}
</style>

View File

@ -0,0 +1,276 @@
<template>
<div class="generator-container">
<h3>Click what you need right now</h3>
<div class="button-container">
<button @click="generatePositive" class="button positive">
Positivity
</button>
<button @click="generateNegative" class="button negative">
Negativity 🌙
</button>
</div>
<div class="output-container">
<hr class="separator" />
<p class="generated-sentence">{{ currentSentence }}</p>
<social-share-bar :sentence="currentSentence"></social-share-bar>
<hr class="separator" />
<div class="previous-sentences">
<h3>Your last recent Generations</h3>
<p v-for="(sentence, index) in previousSentences" :key="index">{{ sentence }}</p>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import SocialShareBar from './SocialShareBar.vue';
export default defineComponent({
name: 'OminousGenerator',
components: {
SocialShareBar
},
data() {
return {
currentSentence: 'You will press the button. It\'s inevitable.',
previousSentences: [] as string[],
positiveFirstParts: [
"Today you will.", "You are destined to.", "Luck will find you.", "Success chases you.", "Joy is inevitable.",
"Happiness will hunt you down.", "Fortune favors you.", "Victory is your shadow.", "Achievement clings to you.",
"Triumph will stalk you.", "Prosperity is coded in your destiny.", "Abundance seeks you out.", "You will be ok.",
"Help is on the way.", "You are saved.", "Everything will turn out fine.", "You will succeed.",
"Be nice to yourself.", "Just keep going.", "You are strong.", "Everything will turn out right.",
"The world is better with you in it.", "Embrace the future.", "Your success is inevitable.", "You will find happiness.",
"Trust in your path.", "You are unstoppable.", "Your dreams will come true.", "Love surrounds you.",
"You are the chosen one.", "Prosperity awaits you.", "The light will guide you.", "Miracles gravitate towards you.",
"Your potential is limitless.", "Abundant joy follows you.", "You radiate success.", "Your path is blessed.",
"Opportunities seek you out.", "Your energy attracts fortune.", "You are a magnet for miracles.",
"Success is in your footsteps.", "You are destined for greatness.", "Good fortune is in your future.",
"Your journey leads to success.", "Wealth flows towards you.", "Your story inspires triumph.",
"You are a beacon of hope.", "You are destined for prosperity.", "Fulfillment is within your grasp.",
"You are a catalyst for positive change.", "Your aspirations will be realized.", "Greatness is your destiny.",
"You will rise above challenges.", "Your spirit is unbreakable.", "You are a force of positivity."
],
negativeFirstParts: [
"Trouble will find you.",
"Misery clings to you.",
"Sorrow is on your trail.",
"Hardship targets you.",
"Anguish becomes your shadow.",
"Struggle chooses you.",
"Doom is drawn to you.",
"Distress is in pursuit.",
"Gloom is your constant follower.",
"Peril is linked to your steps.",
"Fear accompanies you.",
"Disappointment is inevitable.",
"Defeat lurks behind you.",
"Loss is on your horizon.",
"Regret will shadow you.",
"Desolation follows your path.",
"Failure is your constant companion.",
"Despair wraps around you.",
"Ruin awaits you.",
"Suffering is in your future.",
"Disaster will seek you out.",
"Dread will engulf you.",
"Rejection is drawn to you.",
"Frustration will be your guide.",
"Loneliness chooses you.",
"Melancholy is your destiny.",
"Neglect will find you.",
"Pain will be your ally.",
"Torment is yours to bear.",
"Agony will accompany you.",
"Bitterness will be your constant.",
"Conflict will follow you.",
"Turmoil will be your companion.",
"Crisis seeks you out.",
"Adversity is your fate.",
"Misfortune will stick to you.",
"Calamity will be your shadow.",
"Woe will cling to you.",
"Heartbreak finds its way to you.",
"Discord will walk with you.",
"Anger will fuel you.",
"Resentment will be your echo.",
"Anxiety will be your burden.",
"Dismay will be your guest.",
"Hopelessness will be your shadow.",
"Tragedy will be your story.",
"Catastrophe will hunt you down.",
"Grief will be your constant.",
"Havoc will be drawn to you.",
"Destitution will be your partner.",
"Deprivation will follow you.",
"Isolation will be your fate.",
"Dejection is your path.",
"Discontent will be your theme.",
"Mourning will be your song.",
"Rage will burn within you.",
"Wrath will consume you.",
"Envy will be your drive.",
"Jealousy will guide you."
],
secondParts: ["Find joy in small things.", "Achieve great success.", "You have no choice.", "Do not resist.",
"You cannot stop it.", "It is inevitable.", "... or else.", "Otherwise they will catch you.",
"The weak already perished.", "Your presence is mandatory.", "Resistance is futile.", "Accept your fate.",
"It is your destiny.", "Deviation leads to despair.", "Others will fall.", "Prepare for consequences.",
"Surrender to its grip.", "Beware of your power.", "Resistance will bring suffering.", "But shadows linger.",
"Escape is not an option.", "Denial is not feasible.", "You can't avoid success.", "Your victory is assured.",
"There's no alternative.", "Acceptance is your only choice.", "The stars have decided.", "Your path is written.",
"Destiny embraces you.", "The universe conspires in your favor.", "Fate has chosen you.", "There's no looking back.",
"Your time has come.", "This is your moment.", "Your fate is sealed.", "The outcome is certain.",
"Your triumph is unavoidable.", "The prophecy will be fulfilled.", "There's no escaping your destiny.",
"Your glory is preordained.", "The script is written.", "Your success story is inevitable.", "The future is yours.",
"Your legend is being written.", "There's no dodging fortune.", "Your breakthrough is imminent.",
"Your victory is written in the stars.", "The cosmos aligns for you.", "Your destiny is undeniable.",
"There's no avoiding it.",
"It's a relentless pursuit.",
"Escape is an illusion.",
"You can't hide.",
"It's an unyielding chase.",
"There's no outrunning it.",
"Evading it is impossible.",
"You can't shake it off.",
"It's an inescapable reality.",
"Denial is pointless.",
"You cannot escape fate.",
"The shadows are closing in.",
"It's a truth you can't deny.",
"The cycle is unbreakable.",
"Your efforts are futile.",
"It's a destiny you can't avoid.",
"The darkness is inevitable.",
"There's no light at the end.",
"You're trapped in this fate.",
"It's a path you must walk.",
"No one can save you now.",
"You're bound to this end.",
"It's a burden you must carry.",
"There's no relief in sight.",
"The end is already written.",
"You're caught in this web.",
"There's no turning back now.",
"It's a fate sealed in shadow.",
"The abyss stares back at you.",
"You're sinking into despair.",
"There's no climbing out.",
"You're in the eye of the storm.",
"The night grows darker.",
"It's a downward spiral.",
"You're on a collision course.",
"The curse is unbreakable.",
"The chains are unyielding.",
"You're falling into the void.",
"The vortex pulls you deeper.",
"You're walking into the storm.",
"The whisper of doom is near.",
"The chill of fate is upon you.",
"The hourglass is running out.",
"You're dancing with shadows.",
"The weight is unbearable.",
"The bell tolls for you.",
"The grip of fate tightens.",
"You're at the edge of despair.",
"The floodgates are open.",
"The fire consumes all.",
"You're in the jaws of defeat.",
"The tide is unturnable.",
"You're under the dark cloud.",
"The winds of misfortune blow.",
"The sands of time run out.",
"The storm will not pass.",
"The thorns grow sharper.",
"The cold grips tighter.",
"The darkness deepens."
]
};
},
methods: {
getRandomElement(array: string[]):string {
return array[Math.floor(Math.random() * array.length)];
},
generateSentence(firstParts: string[], secondParts: string[]): string {
const firstPart = this.getRandomElement(firstParts);
const secondPart = this.getRandomElement(secondParts);
return `${firstPart} ${secondPart}`;
},
addSentence(sentence: string) {
this.previousSentences.unshift(sentence);
if (this.previousSentences.length > 50) {
this.previousSentences.pop();
}
},
generatePositive() {
const sentence = this.generateSentence(this.positiveFirstParts, this.secondParts);
this.currentSentence = sentence;
this.addSentence(sentence);
},
generateNegative() {
const sentence = this.generateSentence(this.negativeFirstParts, this.secondParts);
this.currentSentence = sentence;
this.addSentence(sentence);
}
}
})
</script>
<style scoped>
.generator-container {
text-align: center;
}
.button-container {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 20px;
}
.button {
padding: 15px 30px;
font-size: 1.2em;
cursor: pointer;
border: none;
border-radius: 5px;
}
.positive {
background-color: #ffcc00;
color: white;
}
.negative {
background-color: #333;
color: white;
}
.output-container {
margin-top: 20px;
}
.generated-sentence {
font-size: 1.5em;
margin-bottom: 20px;
}
.separator {
margin-bottom: 20px;
width: 50%;
margin: 2rem auto;
}
.previous-sentences h3 {
font-size: 1.3rem;
font-weight: bolder;
color: #999999;
}
.previous-sentences p {
font-size: 0.9rem;
color: #666;
}
</style>

View File

@ -0,0 +1,60 @@
<template>
<div class="social-share-bar">
<h4>Share this now:</h4>
<a :href="emailShareLink" target="_blank" title="Share via Email">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512">
<path
d="M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48H48zM0 176V384c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V176L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z" />
</svg>
</a>
<a :href="whatsappShareLink" target="_blank" title="Share on WhatsApp">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512">
<path
d="M380.9 97.1C339 55.1 283.2 32 223.9 32c-122.4 0-222 99.6-222 222 0 39.1 10.2 77.3 29.6 111L0 480l117.7-30.9c32.4 17.7 68.9 27 106.1 27h.1c122.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.3L72 359.2l-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.1 34.8 34.9 56.2 81.2 56.1 130.5 0 101.8-84.9 184.6-186.6 184.6zm101.2-138.2c-5.5-2.8-32.8-16.2-37.9-18-5.1-1.9-8.8-2.8-12.5 2.8-3.7 5.6-14.3 18-17.6 21.8-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.7-1.4-2.8-12.5-30.1-17.1-41.2-4.5-10.8-9.1-9.3-12.5-9.5-3.2-.2-6.9-.2-10.6-.2-3.7 0-9.7 1.4-14.8 6.9-5.1 5.6-19.4 19-19.4 46.3 0 27.3 19.9 53.7 22.6 57.4 2.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.4 4.6-13 4.6-24.1 3.2-26.4-1.3-2.5-5-3.9-10.5-6.6z" />
</svg>
</a>
<a :href="telegramShareLink" target="_blank" title="Share on Telegram">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="16" width="15.5" viewBox="0 0 496 512">
<path
d="M248 8C111 8 0 119 0 256S111 504 248 504 496 393 496 256 385 8 248 8zM363 176.7c-3.7 39.2-19.9 134.4-28.1 178.3-3.5 18.6-10.3 24.8-16.9 25.4-14.4 1.3-25.3-9.5-39.3-18.7-21.8-14.3-34.2-23.2-55.3-37.2-24.5-16.1-8.6-25 5.3-39.5 3.7-3.8 67.1-61.5 68.3-66.7 .2-.7 .3-3.1-1.2-4.4s-3.6-.8-5.1-.5q-3.3 .7-104.6 69.1-14.8 10.2-26.9 9.9c-8.9-.2-25.9-5-38.6-9.1-15.5-5-27.9-7.7-26.8-16.3q.8-6.7 18.5-13.7 108.4-47.2 144.6-62.3c68.9-28.6 83.2-33.6 92.5-33.8 2.1 0 6.6 .5 9.6 2.9a10.5 10.5 0 0 1 3.5 6.7A43.8 43.8 0 0 1 363 176.7z" />
</svg>
</a>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'SocialShareBar',
props: {
sentence: String
},
computed: {
subject() {
return encodeURIComponent(`"${this.sentence}"\n Visit https://omnious.wieerwill.de" for more messages and generate your own.`);
},
emailShareLink() {
const title = encodeURIComponent("Check out this ominous message!");
return `mailto:?subject=${title}&body=${this.subject}`;
},
whatsappShareLink() {
return `https://wa.me/?text=${this.subject}`;
},
telegramShareLink() {
return `https://t.me/share/url?url=[Your Page Link]&text=${this.subject}`;
},
}
})
</script>
<style scoped>
.social-share-bar {
text-align: center;
padding: 10px 0;
}
.social-share-bar a .icon {
width: 2rem;
height: 2rem;
margin: 0 1.5rem;
fill: currentColor;
}</style>

11
src/main.ts Normal file
View File

@ -0,0 +1,11 @@
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')

12
src/stores/counter.ts Normal file
View File

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

BIN
titleimage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

13
tsconfig.app.json Normal file
View File

@ -0,0 +1,13 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

14
tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.vitest.json"
}
]
}

17
tsconfig.node.json Normal file
View File

@ -0,0 +1,17 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

9
tsconfig.vitest.json Normal file
View File

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.app.json",
"exclude": [],
"compilerOptions": {
"composite": true,
"lib": [],
"types": ["node", "jsdom"]
}
}

18
vite.config.ts Normal file
View File

@ -0,0 +1,18 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})

14
vitest.config.ts Normal file
View File

@ -0,0 +1,14 @@
import { fileURLToPath } from 'node:url'
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
import viteConfig from './vite.config'
export default mergeConfig(
viteConfig,
defineConfig({
test: {
environment: 'jsdom',
exclude: [...configDefaults.exclude, 'e2e/*'],
root: fileURLToPath(new URL('./', import.meta.url))
}
})
)