Write homepage, start checklist page

This commit is contained in:
Alicia Sykes 2024-02-03 12:07:50 +00:00
parent b7eed2dd00
commit 229a88b07e
38 changed files with 1242 additions and 2348 deletions

View File

@ -1,21 +1,11 @@
{
"name": "my-qwik-basic-starter",
"description": "Demo App with Routing built-in (recommended)",
"engines": {
"node": ">=15.0.0"
},
"private": true,
"trustedDependencies": [
"sharp"
],
"type": "module",
"scripts": {
"dev": "vite --mode ssr",
"build": "qwik build",
"build.client": "vite build",
"build.preview": "vite build --ssr src/entry.preview.tsx",
"build.types": "tsc --incremental --noEmit",
"deploy": "echo 'Run \"npm run qwik add\" to install a server adapter'",
"dev": "vite --mode ssr",
"dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
"fmt": "prettier --write .",
"fmt.check": "prettier --check .",
@ -25,18 +15,19 @@
"qwik": "qwik"
},
"devDependencies": {
"@builder.io/qwik": "^1.4.3",
"@builder.io/qwik-city": "^1.4.3",
"@types/eslint": "^8.56.2",
"@types/node": "^20.11.6",
"@typescript-eslint/eslint-plugin": "^6.19.1",
"@typescript-eslint/parser": "^6.19.1",
"eslint": "^8.56.0",
"eslint-plugin-qwik": "^1.4.3",
"prettier": "^3.2.4",
"typescript": "5.3.3",
"undici": "*",
"vite": "^5.0.12",
"vite-tsconfig-paths": "^4.2.1"
"@builder.io/qwik-city": "^1.1.4",
"autoprefixer": "^10.4.13",
"daisyui": "^3.0.2",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.4",
"typescript": "^5.3.3",
"undici": "^5.16.0",
"vite": "^4.0.4",
"vite-tsconfig-paths": "^4.3.1"
},
"dependencies": {
"@builder.io/qwik": "^1.1.4",
"marked": "^12.0.0",
"sharp": "^0.33.2"
}
}

8
web/postcss.config.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -0,0 +1,125 @@
// IconComponent.jsx or IconComponent.tsx
import { component$ } from "@builder.io/qwik";
import colors from 'tailwindcss/colors';
const getSvgPath = (icon: string) => {
switch (icon) {
case 'star':
return {
vb: "0 0 24 24",
path: "M10 15l-5.5 3 1-5.5L0 7.5l5.6-0.5L10 2l2 5 5.5 0.5-4 4 1 5.5z",
};
case 'password':
return {
vb: "0 0 512 512",
path: "M336 352c97.2 0 176-78.8 176-176S433.2 0 336 0S160 78.8 160 176c0 18.7 2.9 36.8 8.3 53.7L7 391l-7 7V408v80 24H24h80 24V488 448h40 24V424 384h40 9.9l7-7 33.3-33.3c16.9 5.4 35 8.3 53.7 8.3zM376 96a40 40 0 1 1 0 80 40 40 0 1 1 0-80z",
};
case 'browser':
return {
vb: "0 0 512 512",
path: "M352 256c0 22.2-1.2 43.6-3.3 64H163.3c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64H348.7c2.2 20.4 3.3 41.8 3.3 64zm28.8-64H503.9c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64H380.8c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32H376.7c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0H167.7c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0H18.6C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192H131.2c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64H8.1C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6H344.3c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352H135.3zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6H493.4z",
};
case 'email':
return {
vb: "0 0 512 512",
path: "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",
};
case 'messaging':
return {
vb: "0 0 512 512",
path: "M64 0C28.7 0 0 28.7 0 64V256c0 35.3 28.7 64 64 64H96v48c0 6.1 3.4 11.6 8.8 14.3s11.9 2.1 16.8-1.5L202.7 320H352c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H64zM352 352H256v32c0 35.3 28.7 64 64 64H437.3l81.1 60.8c4.8 3.6 11.3 4.2 16.8 1.5s8.8-8.2 8.8-14.3V448h32c35.3 0 64-28.7 64-64V192c0-35.3-28.7-64-64-64H448V256c0 53-43 96-96 96z",
};
case 'social':
return {
vb: "0 0 512 512",
path: "M352 224c53 0 96-43 96-96s-43-96-96-96s-96 43-96 96c0 4 .2 8 .7 11.9l-94.1 47C145.4 170.2 121.9 160 96 160c-53 0-96 43-96 96s43 96 96 96c25.9 0 49.4-10.2 66.6-26.9l94.1 47c-.5 3.9-.7 7.8-.7 11.9c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-25.9 0-49.4 10.2-66.6 26.9l-94.1-47c.5-3.9 .7-7.8 .7-11.9s-.2-8-.7-11.9l94.1-47C302.6 213.8 326.1 224 352 224z",
};
case 'network':
return {
vb: "0 0 512 512",
path: "M176 80v48h32 48 80V80H176zm-48 48V80H96c-8.8 0-16 7.2-16 16v32h48zM80 176v56H208V176H80zm0 160h48V280H80v56zm0 48v32c0 8.8 7.2 16 16 16H208V384H176 128 80zm96-48H288.8c-.5 4.7-.8 9.4-.8 14.1c0 11.5 1 22.9 3 33.9H256v48h50.1c8.1 17.5 18.8 33.7 31.5 48H96c-35.3 0-64-28.7-64-64V96c0-35.3 28.7-64 64-64H416c35.3 0 64 28.7 64 64v48.6c-1.7-1.5-3.3-3.1-5-4.6c-17.7-16-44.7-16-62.5-.1c-12.9 11.5-25.1 23.7-36.5 36.1H256v56h76c-2.4 3.6-4.7 7.2-6.9 10.7c-7.4 12-14.2 24.5-19.9 37.3H176v56zM432 128V96c0-8.8-7.2-16-16-16H384v48h48zm48 384c-88.4 0-160-71.6-160-160c0-76.7 62.5-144.7 107.2-179.4c5-3.9 10.9-5.8 16.8-5.8c7.9-.1 16 3.1 22 9.2l46 46 11.3-11.3c11.7-11.7 30.6-12.7 42.3-1C624.5 268 640 320.2 640 352c0 88.4-71.6 160-160 160zm64-111.8c0-36.5-37-73-54.8-88.4c-5.4-4.7-13.1-4.7-18.5 0C453 327.1 416 363.6 416 400.2c0 35.3 28.7 64 64 64s64-28.7 64-64z",
};
case 'mobile':
return {
vb: "0 0 512 512",
path: "M80 0C44.7 0 16 28.7 16 64V448c0 35.3 28.7 64 64 64H304c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H80zm80 432h64c8.8 0 16 7.2 16 16s-7.2 16-16 16H160c-8.8 0-16-7.2-16-16s7.2-16 16-16z",
};
case 'computer':
return {
vb: "0 0 512 512",
path: "M0 192H176V0H160C71.6 0 0 71.6 0 160v32zm0 32V352c0 88.4 71.6 160 160 160h64c88.4 0 160-71.6 160-160V224H192 0zm384-32V160C384 71.6 312.4 0 224 0H208V192H384z",
};
case 'home':
return {
vb: "0 0 512 512",
path: "M357.7 8.5c-12.3-11.3-31.2-11.3-43.4 0l-208 192c-9.4 8.6-12.7 22-8.5 34c87.1 25.3 155.6 94.2 180.3 181.6H464c26.5 0 48-21.5 48-48V256h32c13.2 0 25-8.1 29.8-20.3s1.6-26.2-8.1-35.2l-208-192zM288 208c0-8.8 7.2-16 16-16h64c8.8 0 16 7.2 16 16v64c0 8.8-7.2 16-16 16H304c-8.8 0-16-7.2-16-16V208zM24 256c-13.3 0-24 10.7-24 24s10.7 24 24 24c101.6 0 184 82.4 184 184c0 13.3 10.7 24 24 24s24-10.7 24-24c0-128.1-103.9-232-232-232zm8 256a32 32 0 1 0 0-64 32 32 0 1 0 0 64zM0 376c0 13.3 10.7 24 24 24c48.6 0 88 39.4 88 88c0 13.3 10.7 24 24 24s24-10.7 24-24c0-75.1-60.9-136-136-136c-13.3 0-24 10.7-24 24z",
};
case 'finance':
return {
vb: "0 0 512 512",
path: "M64 32C28.7 32 0 60.7 0 96v32H576V96c0-35.3-28.7-64-64-64H64zM576 224H0V416c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V224zM112 352h64c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm112 16c0-8.8 7.2-16 16-16H368c8.8 0 16 7.2 16 16s-7.2 16-16 16H240c-8.8 0-16-7.2-16-16z",
};
case 'human':
return {
vb: "0 0 512 512",
path: "M112 48a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zm40 304V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V256.9L59.4 304.5c-9.1 15.1-28.8 20-43.9 10.9s-20-28.8-10.9-43.9l58.3-97c17.4-28.9 48.6-46.6 82.3-46.6h29.7c33.7 0 64.9 17.7 82.3 46.6l58.3 97c9.1 15.1 4.2 34.8-10.9 43.9s-34.8 4.2-43.9-10.9L232 256.9V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V352H152z",
};
case 'physical':
return {
vb: "0 0 512 512",
path: "M48 0C21.5 0 0 21.5 0 48V464c0 26.5 21.5 48 48 48h96V432c0-26.5 21.5-48 48-48s48 21.5 48 48v80h88.6c-5.4-9.4-8.6-20.3-8.6-32V352c0-23.7 12.9-44.4 32-55.4V272c0-30.5 12.2-58.2 32-78.4V48c0-26.5-21.5-48-48-48H48zM64 240c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V240zm112-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V240c0-8.8 7.2-16 16-16zm80 16c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H272c-8.8 0-16-7.2-16-16V240zM80 96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16zm80 16c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V112zM272 96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H272c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16zM464 240c17.7 0 32 14.3 32 32v48H432V272c0-17.7 14.3-32 32-32zm-80 32v48c-17.7 0-32 14.3-32 32V480c0 17.7 14.3 32 32 32H544c17.7 0 32-14.3 32-32V352c0-17.7-14.3-32-32-32V272c0-44.2-35.8-80-80-80s-80 35.8-80 80z",
};
case 'shield':
return {
vb: "0 0 512 512",
path: "M256 0c4.6 0 9.2 1 13.4 2.9L457.7 82.8c22 9.3 38.4 31 38.3 57.2c-.5 99.2-41.3 280.7-213.6 363.2c-16.7 8-36.1 8-52.8 0C57.3 420.7 16.5 239.2 16 140c-.1-26.2 16.3-47.9 38.3-57.2L242.7 2.9C246.8 1 251.4 0 256 0zm0 66.8V444.8C394 378 431.1 230.1 432 141.4L256 66.8l0 0z",
};
case 'github':
return {
vb: "0 0 512 512",
path: "M186.1 328.7c0 20.9-10.9 55.1-36.7 55.1s-36.7-34.2-36.7-55.1 10.9-55.1 36.7-55.1 36.7 34.2 36.7 55.1zM480 278.2c0 31.9-3.2 65.7-17.5 95-37.9 76.6-142.1 74.8-216.7 74.8-75.8 0-186.2 2.7-225.6-74.8-14.6-29-20.2-63.1-20.2-95 0-41.9 13.9-81.5 41.5-113.6-5.2-15.8-7.7-32.4-7.7-48.8 0-21.5 4.9-32.3 14.6-51.8 45.3 0 74.3 9 108.8 36 29-6.9 58.8-10 88.7-10 27 0 54.2 2.9 80.4 9.2 34-26.7 63-35.2 107.8-35.2 9.8 19.5 14.6 30.3 14.6 51.8 0 16.4-2.6 32.7-7.7 48.2 27.5 32.4 39 72.3 39 114.2zm-64.3 50.5c0-43.9-26.7-82.6-73.5-82.6-18.9 0-37 3.4-56 6-14.9 2.3-29.8 3.2-45.1 3.2-15.2 0-30.1-.9-45.1-3.2-18.7-2.6-37-6-56-6-46.8 0-73.5 38.7-73.5 82.6 0 87.8 80.4 101.3 150.4 101.3h48.2c70.3 0 150.6-13.4 150.6-101.3zm-82.6-55.1c-25.8 0-36.7 34.2-36.7 55.1s10.9 55.1 36.7 55.1 36.7-34.2 36.7-55.1-10.9-55.1-36.7-55.1z",
};
case 'checklist':
return {
vb: "0 0 512 512",
path: "M192 0c-41.8 0-77.4 26.7-90.5 64H64C28.7 64 0 92.7 0 128V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64H282.5C269.4 26.7 233.8 0 192 0zm0 64a32 32 0 1 1 0 64 32 32 0 1 1 0-64zm-4.7 132.7c6.2 6.2 6.2 16.4 0 22.6l-64 64c-6.2 6.2-16.4 6.2-22.6 0l-32-32c-6.2-6.2-6.2-16.4 0-22.6s16.4-6.2 22.6 0L112 249.4l52.7-52.7c6.2-6.2 16.4-6.2 22.6 0zM192 272c0-8.8 7.2-16 16-16h96c8.8 0 16 7.2 16 16s-7.2 16-16 16H208c-8.8 0-16-7.2-16-16zm-16 80H304c8.8 0 16 7.2 16 16s-7.2 16-16 16H176c-8.8 0-16-7.2-16-16s7.2-16 16-16zM72 368a24 24 0 1 1 48 0 24 24 0 1 1 -48 0z",
};
case 'code':
return {
vb: "0 0 512 512",
path: "M392.8 1.2c-17-4.9-34.7 5-39.6 22l-128 448c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l128-448c4.9-17-5-34.7-22-39.6zm80.6 120.1c-12.5 12.5-12.5 32.8 0 45.3L562.7 256l-89.4 89.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l112-112c12.5-12.5 12.5-32.8 0-45.3l-112-112c-12.5-12.5-32.8-12.5-45.3 0zm-306.7 0c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3l112 112c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256l89.4-89.4c12.5-12.5 12.5-32.8 0-45.3z",
};
case 'all':
return {
vb: "0 0 512 512",
path: "M0 72C0 49.9 17.9 32 40 32H88c22.1 0 40 17.9 40 40v48c0 22.1-17.9 40-40 40H40c-22.1 0-40-17.9-40-40V72zM0 232c0-22.1 17.9-40 40-40H88c22.1 0 40 17.9 40 40v48c0 22.1-17.9 40-40 40H40c-22.1 0-40-17.9-40-40V232zM128 392v48c0 22.1-17.9 40-40 40H40c-22.1 0-40-17.9-40-40V392c0-22.1 17.9-40 40-40H88c22.1 0 40 17.9 40 40zM160 72c0-22.1 17.9-40 40-40h48c22.1 0 40 17.9 40 40v48c0 22.1-17.9 40-40 40H200c-22.1 0-40-17.9-40-40V72zM288 232v48c0 22.1-17.9 40-40 40H200c-22.1 0-40-17.9-40-40V232c0-22.1 17.9-40 40-40h48c22.1 0 40 17.9 40 40zM160 392c0-22.1 17.9-40 40-40h48c22.1 0 40 17.9 40 40v48c0 22.1-17.9 40-40 40H200c-22.1 0-40-17.9-40-40V392zM448 72v48c0 22.1-17.9 40-40 40H360c-22.1 0-40-17.9-40-40V72c0-22.1 17.9-40 40-40h48c22.1 0 40 17.9 40 40zM320 232c0-22.1 17.9-40 40-40h48c22.1 0 40 17.9 40 40v48c0 22.1-17.9 40-40 40H360c-22.1 0-40-17.9-40-40V232zM448 392v48c0 22.1-17.9 40-40 40H360c-22.1 0-40-17.9-40-40V392c0-22.1 17.9-40 40-40h48c22.1 0 40 17.9 40 40z",
};
default:
return { vb: "", path: "" }; // Default path or a placeholder icon
}
};
interface IconProps {
icon: string;
color?: string;
class?: string;
width?: number;
height?: number;
};
const IconComponent = component$((props: IconProps) => {
const fillColor = (colors[props.color] || [])[400] || colors['current'].currentColor || 'currentcolor';
const svgStyle = { fill: fillColor };
const { vb, path } = getSvgPath(props.icon);
const svgClass = props.class || '';
const width = props.width || 80;
const height = props.height || 50;
return (
<svg class={svgClass} style={svgStyle} xmlns="http://www.w3.org/2000/svg" viewBox={vb} width={width} height={height}>
<path d={path} />
</svg>
);
});
export default IconComponent;

View File

@ -0,0 +1,18 @@
import { component$ } from "@builder.io/qwik";
export default component$(() => {
const ghLink = 'https://github.com/Lissy93/personal-security-checklist/';
const licenseLink = 'https://github.com/Lissy93/personal-security-checklist/blob/master/LICENSE';
const authorLink = 'https://aliciasykes.com';
return (
<footer class="footer footer-center px-4 py-2 mt-4 text-base-content bg-base-200 bg-opacity-25">
<aside>
<p>Licensed under <a href={licenseLink} class="link link-primary">MIT</a> -
© <a href={authorLink} class="link link-primary">Alicia Sykes</a> 2024 -
View source on <a href={ghLink} class="link link-primary">GitHub</a></p>
</aside>
</footer>
);
});

View File

@ -0,0 +1,8 @@
import { component$ } from "@builder.io/qwik";
export default component$(() => {
return (
<header>
</header>
);
});

View File

@ -0,0 +1,24 @@
import { component$ } from "@builder.io/qwik";
import Icon from "../core/icon";
export default component$(() => {
return (
<div class="hero bg-base-200 bg-opacity-25 mb-16">
<div class="hero-content text-center">
<div class="max-w-2xl flex flex-col place-items-center">
<p>The Ultimate</p>
<h1 class="text-5xl font-bold">Personal Security Checklist</h1>
<p class="subtitle pb-6">A guide to securing your digital life, and protecting your privacy online</p>
<Icon class="mb-4" icon="shield" width={120} height={120} />
<a href="https://github.com/lissy93/personal-security-checklist">
<button class="btn btn-primary btn-lg">
<Icon icon="github" width={20} height={20} />
View on GitHub
</button>
</a>
</div>
</div>
</div>
);
});

View File

@ -0,0 +1,66 @@
import { component$ } from "@builder.io/qwik";
import Icon from "../core/icon";
import { data } from '../../mock-data';
import type { Section } from '../../types/PSC';
export default component$(() => {
return (
<div class="navbar bg-base-100">
<div class="flex-1">
<a href="/" class="btn btn-ghost text-xl tooltip tooltip-bottom flex capitalize" data-tip="Go back to Homepage">
<Icon class="mr-2" icon="shield" width={28} height={28} />
<h1>Personal Security Checklist</h1>
</a>
</div>
<div class="flex-none">
<ul class="menu menu-horizontal px-1">
<li>
<details>
<summary>
<Icon icon="checklist" width={16} height={16} />
Checklists
</summary>
<ul class="p-2 bg-base-100 rounded-t-none z-10">
<li>
<a href="/checklist">
<Icon class="mr-2" icon="all" width={16} height={16} />
View All
</a>
</li>
{data.map((item: Section) => (
<li class={`hover:bg-${item.color}-600 hover:bg-opacity-15`}>
<a href={`/checklist/${item.slug}`}>
<Icon color={item.color} class="mr-2" icon={item.icon} width={16} height={16} />
{item.title}
</a>
</li>
))}
</ul>
</details>
</li>
<li>
<a class="tooltip flex tooltip-bottom" data-tip="View / Edit Source & Data">
<Icon icon="github" width={16} height={16} />GitHub
</a>
</li>
{/* <li>
<a href="https://apps.aliciasykes.com" class="tooltip flex tooltip-bottom" data-tip="Other projects by Alicia Sykes">
<Icon icon="code" width={24} height={16} />More
</a>
</li> */}
</ul>
<div class="tooltip tooltip-bottom" data-tip="Theme">
<label class="cursor-pointer grid place-items-center">
<input type="checkbox" value="synthwave" class="toggle theme-controller bg-base-content row-start-1 col-start-1 col-span-2"/>
<svg class="col-start-1 row-start-1 stroke-base-100 fill-base-100" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4"/></svg>
<svg class="col-start-2 row-start-1 stroke-base-100 fill-base-100" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
</label>
</div>
</div>
</div>
);
});

View File

@ -0,0 +1,6 @@
.container {
display: grid;
gap: 2rem;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
}

View File

@ -0,0 +1,34 @@
import { component$ } from "@builder.io/qwik";
import type { Section } from '../../types/PSC';
import Icon from '../core/icon';
import styles from './psc.module.css';
export default component$((props: { sections: Section[] }) => {
return (
<div class={[styles.container, 'transition-all', 'px-4', 'mx-auto', 'max-w-6xl', 'w-full']}>
{props.sections.map((section: Section) => (
<a key={section.slug}
href={`/checklist/${section.slug}`}
class={['card card-side bg-base-200 bg-opacity-25 shadow-xl transition-all px-2',
`outline outline-10 outline-offset-2 outline-${section.color}-400`,
`hover:outline-offset-4 hover:bg-opacity-15 hover:bg-${section.color}-600`]}
>
<div class="flex-shrink-0 flex flex-col py-4 h-auto items-stretch justify-evenly">
<Icon icon={section.icon || 'star'} color={section.color} />
<p class={`text-${section.color}-400 pt-2 pb-0 px-0 mx-0 my-0`}>0/{section.checklist.length} Done</p>
</div>
<div class="card-body flex-grow py-2 pl-4 pr-0">
<h2 class={`card-title text-${section.color}-400 hover:text-${section.color}-500`}>{section.title}</h2>
<p class="p-0">{section.description}</p>
{/* <div class="card-actions justify-end">
<button class={`btn text-base-100 bg-${section.color}-400 hover:bg-${section.color}-600`}>View Checklist</button>
</div> */}
</div>
</a>
))}
</div>
);
});

View File

@ -1,13 +0,0 @@
.counter-wrapper {
margin-top: 50px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
@media screen and (min-width: 768px) {
.counter-wrapper {
gap: 30px;
}
}

View File

@ -1,32 +0,0 @@
import { component$, useSignal, $ } from "@builder.io/qwik";
import styles from "./counter.module.css";
import Gauge from "../gauge";
export default component$(() => {
const count = useSignal(70);
const setCount = $((newValue: number) => {
if (newValue < 0 || newValue > 100) {
return;
}
count.value = newValue;
});
return (
<div class={styles["counter-wrapper"]}>
<button
class="button-dark button-small"
onClick$={() => setCount(count.value - 1)}
>
-
</button>
<Gauge value={count.value} />
<button
class="button-dark button-small"
onClick$={() => setCount(count.value + 1)}
>
+
</button>
</div>
);
});

View File

@ -1,23 +0,0 @@
.anchor {
color: white !important;
display: block;
font-size: 0.8rem;
text-align: center;
text-decoration: none;
line-height: 1.5;
}
.anchor span:not(.spacer) {
display: block;
}
.spacer {
display: none;
padding: 0 15px;
}
@media screen and (min-width: 768px) {
.anchor span {
display: inline !important;
}
}

View File

@ -1,19 +0,0 @@
import { component$ } from "@builder.io/qwik";
import { useServerTimeLoader } from "../../../routes/layout";
import styles from "./footer.module.css";
export default component$(() => {
const serverTime = useServerTimeLoader();
return (
<footer>
<div class="container">
<a href="https://www.builder.io/" target="_blank" class={styles.anchor}>
<span>Made with by Builder.io</span>
<span class={styles.spacer}>|</span>
<span>{serverTime.value.date}</span>
</a>
</div>
</footer>
);
});

View File

@ -1,27 +0,0 @@
.wrapper {
position: relative;
}
.gauge {
width: 160px;
}
.value {
position: absolute;
top: 50%;
left: 50%;
color: white;
font-size: 3rem;
transform: translate(-50%, -50%);
width: 200px;
text-align: center;
}
@media screen and (min-width: 768px) {
.gauge {
width: 400px;
}
.value {
font-size: 7rem;
}
}

View File

@ -1,38 +0,0 @@
import { component$ } from "@builder.io/qwik";
import styles from "./gauge.module.css";
export default component$(({ value = 50 }: { value?: number }) => {
const safeValue = value < 0 || value > 100 ? 50 : value;
return (
<div class={styles.wrapper}>
<svg viewBox="0 0 120 120" class={styles.gauge}>
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#18B6F6" />
<stop offset="1000%" stop-color="#AC7FF4" />
</linearGradient>
</defs>
<circle
r="56"
cx="60"
cy="60"
stroke-width="8"
style="fill: #000; stroke: #0000"
></circle>
<circle
r="56"
cx="60"
cy="60"
stroke-width="8"
style={`transform: rotate(-87.9537deg); stroke-dasharray: ${
safeValue * 3.51
}, 351.858; fill:none; transform-origin:50% 50%; stroke-linecap:round; stroke:url(#gradient)`}
></circle>
</svg>
<span class={styles.value}>{safeValue}</span>
</div>
);
});

View File

@ -1,44 +0,0 @@
.wrapper {
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
display: inline-block;
}
.logo a {
display: block;
}
.header ul {
margin: 0;
padding: 0;
list-style: none;
display: flex;
gap: 30px;
}
.header li {
display: none;
margin: 0;
padding: 0;
font-size: 0.7rem;
}
.header li a {
color: white;
display: inline-block;
padding: 0;
text-decoration: none;
}
.header li a:hover {
color: var(--qwik-light-blue);
}
@media (min-width: 450px) {
.header li {
display: inline-block;
}
}

View File

@ -1,43 +0,0 @@
import { component$ } from "@builder.io/qwik";
import { QwikLogo } from "../icons/qwik";
import styles from "./header.module.css";
export default component$(() => {
return (
<header class={styles.header}>
<div class={["container", styles.wrapper]}>
<div class={styles.logo}>
<a href="/" title="qwik">
<QwikLogo height={50} width={143} />
</a>
</div>
<ul>
<li>
<a
href="https://qwik.builder.io/docs/components/overview/"
target="_blank"
>
Docs
</a>
</li>
<li>
<a
href="https://qwik.builder.io/examples/introduction/hello-world/"
target="_blank"
>
Examples
</a>
</li>
<li>
<a
href="https://qwik.builder.io/tutorial/welcome/overview/"
target="_blank"
>
Tutorials
</a>
</li>
</ul>
</div>
</header>
);
});

View File

@ -1,43 +0,0 @@
.hero {
display: flex;
vertical-align: middle;
flex-direction: column;
flex-wrap: nowrap;
align-items: center;
height: 450px;
justify-content: center;
gap: 40px;
}
.hero-image {
width: 100%;
position: absolute;
height: auto;
object-fit: cover;
z-index: -1;
opacity: 0.2;
pointer-events: none;
}
.hero p {
color: white;
margin: 0;
font-size: 1rem;
}
.button-group {
display: flex;
flex-direction: row;
gap: 24px;
}
@media screen and (min-width: 768px) {
.hero {
gap: 60px;
height: 500px;
}
.hero p {
font-size: 1.3rem;
}
}

View File

@ -1,82 +0,0 @@
import { component$ } from "@builder.io/qwik";
import styles from "./hero.module.css";
import ImgThunder from "../../../media/thunder.png?jsx";
export default component$(() => {
return (
<div class={["container", styles.hero]}>
<ImgThunder class={styles["hero-image"]} alt="Image thunder" />
<h1>
So <span class="highlight">fantastic</span>
<br />
to have <span class="highlight">you</span> here
</h1>
<p>Have fun building your App with Qwik.</p>
<div class={styles["button-group"]}>
<button
onClick$={async () => {
const defaults = {
spread: 360,
ticks: 70,
gravity: 0,
decay: 0.95,
startVelocity: 30,
colors: ["006ce9", "ac7ff4", "18b6f6", "713fc2", "ffffff"],
origin: {
x: 0.5,
y: 0.35,
},
};
function loadConfetti() {
return new Promise<(opts: any) => void>((resolve, reject) => {
if ((globalThis as any).confetti) {
return resolve((globalThis as any).confetti as any);
}
const script = document.createElement("script");
script.src =
"https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js";
script.onload = () =>
resolve((globalThis as any).confetti as any);
script.onerror = reject;
document.head.appendChild(script);
script.remove();
});
}
const confetti = await loadConfetti();
function shoot() {
confetti({
...defaults,
particleCount: 80,
scalar: 1.2,
});
confetti({
...defaults,
particleCount: 60,
scalar: 0.75,
});
}
setTimeout(shoot, 0);
setTimeout(shoot, 100);
setTimeout(shoot, 200);
setTimeout(shoot, 300);
setTimeout(shoot, 400);
}}
>
Time to celebrate
</button>
<a
href="https://qwik.builder.io/docs"
target="_blank"
class="button button-dark"
>
Explore the docs
</a>
</div>
</div>
);
});

View File

@ -1,23 +0,0 @@
.infobox {
color: white;
font-size: 0.8rem;
line-height: 2;
margin: 0 0 40px;
}
.infobox h3 {
font-size: 1rem;
font-weight: 400;
margin: 0 0 15px;
padding: 0;
}
.infobox li {
line-height: 2.5;
}
@media screen and (min-width: 600px) {
.infobox {
margin: 0;
}
}

View File

@ -1,13 +0,0 @@
import { Slot, component$ } from "@builder.io/qwik";
import styles from "./infobox.module.css";
export default component$(() => {
return (
<div class={styles.infobox}>
<h3>
<Slot name="title" />
</h3>
<Slot />
</div>
);
});

View File

@ -1,36 +0,0 @@
.gettingstarted {
display: flex;
color: white;
flex-direction: column;
justify-content: center;
align-items: center;
height: 280px;
line-height: 1.5;
gap: 10px;
max-width: 600px;
margin: 0 auto;
}
.gettingstarted .intro {
font-size: 1rem;
width: 100%;
word-break: break-word;
}
.gettingstarted .hint {
font-size: 0.8rem;
}
.gettingstarted .hint a {
color: var(--qwik-dark-purple);
}
@media screen and (min-width: 768px) {
.gettingstarted {
height: 180px;
}
.gettingstarted .intro {
font-size: 1.2rem;
}
.gettingstarted .hint {
font-size: 1rem;
}
}

View File

@ -1,81 +0,0 @@
import { component$, $, useOnWindow, useSignal } from "@builder.io/qwik";
import styles from "./next-steps.module.css";
export const GETTING_STARTED_STEPS = [
{
message:
"Press and hold the <b>ALT/Option</b> key to activate 'Click-to-Source' mode",
},
{
message:
"Select the title of this page while keeping the <b>ALT/Option</b> key pressed",
hint: 'Edit the title and save the changes. If your editor does not open, have a look at <a href="https://github.com/yyx990803/launch-editor#supported-editors" target="_blank">this page</a> to set the correct <code>LAUNCH_EDITOR</code> value.',
},
{
message:
"<b>Update</b> now the <code>routeLoader$</code> defined in the <code>src/routes/layout.tsx</code> file",
hint: "Instead of returning the current date, you could return any possible string.<br />The output is displayed in the footer.",
},
{
message: "Create a <b>new Route</b> called <code>/me</code>",
hint: 'Create a new directory called <code>me</code> in <code>src/routes</code>. Within this directory create a <code>index.tsx</code> file or copy the <code>src/routes/index.tsx</code> file. Your new route is now accessible <a href="/me" target="_blank">here</a> ✨',
},
{
message: "Time to have a look at <b>Forms</b>",
hint: 'Open <a href="/demo/todolist" target="_blank">the TODO list App</a> and add some items to the list. Try the same with disabled JavaScript 🐰',
},
{
message: "<b>Congratulations!</b> You are now familiar with the basics! 🎉",
hint: "If you need further info on how to use qwik, have a look at <a href='https://qwik.builder.io' target='_blank'>qwik.builder.io</a> or join the <a href='https://qwik.builder.io/chat' target='_blank'>Discord channel</a>.",
},
];
export default component$(() => {
const gettingStartedStep = useSignal(0);
useOnWindow(
"keydown",
$((e) => {
if ((e as KeyboardEvent).key === "Alt") {
gettingStartedStep.value = 1;
}
}),
);
return (
<div class="container container-purple container-center">
<h2>
Time for a
<br />
<span class="highlight">qwik intro</span>?
</h2>
<div class={styles.gettingstarted}>
<div
class={styles.intro}
dangerouslySetInnerHTML={
GETTING_STARTED_STEPS[gettingStartedStep.value].message
}
/>
<span
class={styles.hint}
dangerouslySetInnerHTML={
GETTING_STARTED_STEPS[gettingStartedStep.value].hint
}
/>
</div>
{gettingStartedStep.value + 1 < GETTING_STARTED_STEPS.length ? (
<button class="button-dark" onClick$={() => gettingStartedStep.value++}>
Continue with Step {gettingStartedStep.value + 2} of{" "}
{GETTING_STARTED_STEPS.length}
</button>
) : (
<button
class="button-dark"
onClick$={() => (gettingStartedStep.value = 0)}
>
Re-Start
</button>
)}
</div>
);
});

View File

@ -1,46 +0,0 @@
/**
* WHAT IS THIS FILE?
*
* Globally applied styles. No matter which components are in the page or matching route,
* the styles in here will be applied to the Document, without any sort of CSS scoping.
*
*/
:root {
--qwik-dark-blue: #006ce9;
--qwik-light-blue: #18b6f6;
--qwik-light-purple: #ac7ff4;
--qwik-dark-purple: #713fc2;
--qwik-dirty-black: #1d2033;
--qwik-dark-background: #151934;
--qwik-dark-text: #ffffff;
}
html {
line-height: 1;
-webkit-text-size-adjust: 100%;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
font-family:
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
"Noto Sans",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
}
body {
padding: 0;
margin: 0;
line-height: inherit;
}

130
web/src/mock-data.ts Normal file
View File

@ -0,0 +1,130 @@
import type { Section } from './types/PSC';
export const data: Section[] = [
{
title: 'Authentication',
slug: 'authentication',
description: 'Securing your online account login credentials',
icon: 'password',
intro: 'Most reported data breaches are caused by the use of weak, default or stolen passwords (according to [this Verizon report](http://www.verizonenterprise.com/resources/reports/rp_dbir-2016-executive-summary_xg_en.pdf)).' +
'Use long, strong and unique passwords, manage them in a secure password manager, enable 2-factor authentication, keep on top of breaches and take care while logging into your accounts.',
checklist: [
{
point: 'Use a Strong Password',
priority: 'recommended',
details: `If your password is too short, or contains dictionary words, places or names- then it can be easily cracked through brute force, or guessed by someone. The easiest way to make a strong password, is by making it long (12+ characters)- consider using a 'passphrase', made up of many words. Alternatively, use a password generator to create a long, strong random password. Have a play with [HowSecureIsMyPassword.net](https://howsecureismypassword.net), to get an idea of how quickly common passwords can be cracked. Read more about creating strong passwords: [securityinabox.org](https://securityinabox.org/en/passwords/passwords-and-2fa/)`,
},
{
point: 'Don\'t reuse Passwords',
priority: 'optional',
details: `If someone was to reuse a password, and one site they had an account with suffered a leak, then a criminal could easily gain unauthorized access to their other accounts. This is usually done through large-scale automated login requests, and it is called Credential Stuffing. Unfortunately this is all too common, but it's simple to protect against- use a different password for each of your online accounts`,
},
{
point: 'Use a Secure Password Manager',
priority: 'advanced',
details: `For most people it is going to be near-impossible to remember hundreds of strong and unique passwords. A password manager is an application that generates, stores and auto-fills your login credentials for you. All your passwords will be encrypted against 1 master passwords (which you must remember, and it should be very strong). Most password managers have browser extensions and mobile apps, so whatever device you are on, your passwords can be auto-filled. A good all-rounder is [BitWarden](https://bitwarden.com), or see [Recommended Password Managers](https://github.com/Lissy93/awesome-privacy#password-managers)`,
},
],
color: 'yellow',
},
{
title: 'Web Browsing',
slug: 'web-browsing',
description: 'Avoiding tracking, censorship, and data collection online',
icon: 'browser',
intro: '',
checklist: [],
color: 'emerald',
},
{
title: 'Email',
slug: 'email',
description: 'Protecting the gateway to your online accounts',
icon: 'email',
intro: '',
checklist: [],
color: 'teal',
},
{
title: 'Messaging',
slug: 'messaging',
description: 'Keeping your communications private and secure',
icon: 'messaging',
intro: '',
checklist: [],
color: 'cyan',
},
{
title: 'Social Media',
slug: 'social-media',
description: 'Minimizing the risks associated with using online communities',
icon: 'social',
intro: '',
checklist: [],
color: 'blue',
},
{
title: 'Networks',
slug: 'networks',
description: 'Safeguarding your network traffic',
icon: 'network',
intro: '',
checklist: [],
color: 'violet',
},
{
title: 'Mobile Devices',
slug: 'mobile-devices',
description: 'Reduce invasive tracking for cells, smartphones and tablets',
icon: 'mobile',
intro: '',
checklist: [],
color: 'fuchsia',
},
{
title: 'Personal Computers',
slug: 'personal-computers',
description: 'Securing your PC\'s operating system, data & activity',
icon: 'computer',
intro: '',
checklist: [],
color: 'pink',
},
{
title: 'Smart Home',
slug: 'smart-home',
description: 'Using IoT devices without compromising your privacy',
icon: 'home',
intro: '',
checklist: [],
color: 'red',
},
{
title: 'Personal Finance',
slug: 'personal-finance',
description: 'Protecting your funds, financial accounts and transactions',
icon: 'finance',
intro: '',
checklist: [],
color: 'purple',
},
{
title: 'Human Aspect',
slug: 'human-aspect',
description: 'Avoiding social engineering security risks',
icon: 'human',
intro: '',
checklist: [],
color: 'indigo',
},
{
title: 'Physical Security',
slug: 'physical-security',
description: 'Taking measures to prevent IRL security incidents',
icon: 'physical',
intro: '',
checklist: [],
color: 'lime',
},
];

View File

@ -1,4 +1,4 @@
import { component$ } from "@builder.io/qwik";
import { component$, useStyles$ } from "@builder.io/qwik";
import {
QwikCityProvider,
RouterOutlet,
@ -6,9 +6,13 @@ import {
} from "@builder.io/qwik-city";
import { RouterHead } from "./components/router-head/router-head";
import "./global.css";
import tailwind from './styles/tailwind.css?inline';
import "./styles/global.css";
export default component$(() => {
useStyles$(tailwind);
/**
* The root of a QwikCity site always start with the <QwikCityProvider> component,
* immediately followed by the document's <head> and <body>.
@ -24,7 +28,7 @@ export default component$(() => {
<RouterHead />
<ServiceWorkerRegister />
</head>
<body lang="en">
<body lang="en" data-theme="night" class="flex flex-col justify-between min-h-screen">
<RouterOutlet />
</body>
</QwikCityProvider>

View File

@ -0,0 +1,75 @@
import { component$ } from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';
import marked from 'marked';
import { data } from '../../../mock-data';
import type { Section, Priority } from '../../../types/PSC';
export default component$(() => {
const loc = useLocation();
// const endpoint = useEndpoint<{ params: { title: string } }>();
const slug = loc.params.title;
const section: Section | undefined = data.find((item: Section) => item.slug === slug);
// You can now use `title` to fetch data related to this checklist item
// and render it below.
const getBadgeClass = (priority: Priority, precedeClass: string = '') => {
switch (priority) {
case 'recommended':
return `${precedeClass}success`;
case 'optional':
return `${precedeClass}warning`;
case 'advanced':
return `${precedeClass}error`;
default:
return `${precedeClass}neutral`;
}
};
const generateId = (title: string) => {
return title.toLowerCase().replace(/ /g, '-');
};
return (
<article class="bg-base-200 bg-opacity-25 p-8 mx-auto w-full max-w-[1200px] rounded-lg shadow-lg">
<h1 class="text-4xl font-bold capitalize">{section?.title}</h1>
<p class="py-2" dangerouslySetInnerHTML={marked.parse(section?.intro || '')}></p>
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th>Done?</th>
<th>Advice</th>
<th>Level</th>
<th>Details</th>
</tr>
</thead>
<tbody>
{section?.checklist.map((item, index) => (
<tr class={`rounded-sm hover:bg-opacity-5 hover:bg-${getBadgeClass(item.priority)}`}>
<td>
<input type="checkbox" class="checkbox" id={generateId(item.point)} />
</td>
<td>
<label class="text-base font-bold" for={generateId(item.point)}>
{item.point}
</label>
</td>
<td>
<div class={`badge gap-2 ${getBadgeClass(item.priority, 'badge-')}`}>
{item.priority}
</div>
</td>
<td class="" dangerouslySetInnerHTML={marked.parse(item.details)}></td>
</tr>
))}
</tbody>
</table>
</div>
</article>
);
});

View File

@ -0,0 +1,13 @@
import { component$ } from "@builder.io/qwik";
import SectionLinkGrid from "../../components/psc/section-link-grid";
import { data } from '../../mock-data';
export default component$(() => {
return (
<main class="flex items-center justify-center min-h-[80vh]">
<SectionLinkGrid sections={data} />
</main>
);
});

View File

@ -1,77 +0,0 @@
.host {
display: grid;
align-items: center;
justify-content: center;
justify-items: center;
--rotation: 135deg;
--rotation: 225deg;
--size-step: 10px;
--odd-color-step: 5;
--even-color-step: 5;
--center: 12;
width: 100%;
height: 500px;
contain: strict;
}
h1 {
margin-bottom: 60px;
}
.input {
width: 60%;
}
.square {
--size: calc(40px + var(--index) * var(--size-step));
display: block;
width: var(--size);
height: var(--size);
transform: rotateZ(
calc(var(--rotation) * var(--state) * (var(--center) - var(--index)))
);
transition-property: transform, border-color;
transition-duration: 5s;
transition-timing-function: ease-in-out;
grid-area: 1 / 1;
background: white;
border-width: 2px;
border-style: solid;
border-color: black;
box-sizing: border-box;
will-change: transform, border-color;
contain: strict;
}
.square.odd {
--luminance: calc(1 - calc(calc(var(--index) * var(--odd-color-step)) / 256));
background: rgb(
calc(172 * var(--luminance)),
calc(127 * var(--luminance)),
calc(244 * var(--luminance))
);
}
.pride .square:nth-child(12n + 1) {
background: #e70000;
}
.pride .square:nth-child(12n + 3) {
background: #ff8c00;
}
.pride .square:nth-child(12n + 5) {
background: #ffef00;
}
.pride .square:nth-child(12n + 7) {
background: #00811f;
}
.pride .square:nth-child(12n + 9) {
background: #0044ff;
}
.pride .square:nth-child(12n + 11) {
background: #760089;
}

View File

@ -1,69 +0,0 @@
import {
component$,
useVisibleTask$,
useStore,
useStylesScoped$,
} from "@builder.io/qwik";
import { type DocumentHead, useLocation } from "@builder.io/qwik-city";
import styles from "./flower.css?inline";
export default component$(() => {
useStylesScoped$(styles);
const loc = useLocation();
const state = useStore({
count: 0,
number: 20,
});
useVisibleTask$(({ cleanup }) => {
const timeout = setTimeout(() => (state.count = 1), 500);
cleanup(() => clearTimeout(timeout));
const internal = setInterval(() => state.count++, 7000);
cleanup(() => clearInterval(internal));
});
return (
<div class="container container-center">
<div role="presentation" class="ellipsis"></div>
<h1>
<span class="highlight">Generate</span> Flowers
</h1>
<input
class="input"
type="range"
value={state.number}
max={50}
onInput$={(ev, el) => {
state.number = el.valueAsNumber;
}}
/>
<div
style={{
"--state": `${state.count * 0.1}`,
}}
class={{
host: true,
pride: loc.url.searchParams.get("pride") === "true",
}}
>
{Array.from({ length: state.number }, (_, i) => (
<div
key={i}
class={{
square: true,
odd: i % 2 === 0,
}}
style={{ "--index": `${i + 1}` }}
/>
)).reverse()}
</div>
</div>
);
});
export const head: DocumentHead = {
title: "Qwik Flower",
};

View File

@ -1,78 +0,0 @@
import { component$ } from "@builder.io/qwik";
import {
type DocumentHead,
routeLoader$,
routeAction$,
zod$,
z,
Form,
} from "@builder.io/qwik-city";
import styles from "./todolist.module.css";
interface ListItem {
text: string;
}
export const list: ListItem[] = [];
export const useListLoader = routeLoader$(() => {
return list;
});
export const useAddToListAction = routeAction$(
(item) => {
list.push(item);
return {
success: true,
};
},
zod$({
text: z.string().trim().min(1),
}),
);
export default component$(() => {
const list = useListLoader();
const action = useAddToListAction();
return (
<>
<div class="container container-center">
<h1>
<span class="highlight">TODO</span> List
</h1>
</div>
<div role="presentation" class="ellipsis"></div>
<div class="container container-center">
{list.value.length === 0 ? (
<span class={styles.empty}>No items found</span>
) : (
<ul class={styles.list}>
{list.value.map((item, index) => (
<li key={`items-${index}`}>{item.text}</li>
))}
</ul>
)}
</div>
<div class="container container-center">
<Form action={action} spaReset>
<input type="text" name="text" required class={styles.input} />{" "}
<button type="submit" class="button-dark">
Add item
</button>
</Form>
<p class={styles.hint}>
PS: This little app works even when JavaScript is disabled.
</p>
</div>
</>
);
});
export const head: DocumentHead = {
title: "Qwik Todo List",
};

View File

@ -1,44 +0,0 @@
.list {
display: flex;
flex-direction: column;
gap: 20px;
color: white;
}
.list,
.empty {
min-height: 250px;
}
.list li {
list-style: none;
}
.empty {
color: white;
display: block;
}
.input {
background: white;
color: var(--qwik-light-blue);
border: none;
border-radius: 8px;
padding: 15px 20px;
margin-right: 10px;
font-size: 0.8rem;
}
.hint {
font-size: 0.8rem;
color: white;
margin-top: 30px;
}
@media screen and (min-width: 768px) {
.input {
padding: 23px 35px;
margin-right: 20px;
font-size: 1rem;
}
}

View File

@ -1,102 +1,16 @@
import { component$ } from "@builder.io/qwik";
import type { DocumentHead } from "@builder.io/qwik-city";
import Counter from "../components/starter/counter/counter";
import Hero from "../components/starter/hero/hero";
import Infobox from "../components/starter/infobox/infobox";
import Starter from "../components/starter/next-steps/next-steps";
import Hero from "../components/furniture/hero";
import SectionLinkGrid from "../components/psc/section-link-grid";
import { data } from '../mock-data';
export default component$(() => {
return (
<>
<Hero />
<Starter />
<div role="presentation" class="ellipsis"></div>
<div role="presentation" class="ellipsis ellipsis-purple"></div>
<div class="container container-center container-spacing-xl">
<h3>
You can <span class="highlight">count</span>
<br /> on me
</h3>
<Counter />
</div>
<div class="container container-flex">
<Infobox>
<div q:slot="title" class="icon icon-cli">
CLI Commands
</div>
<>
<p>
<code>npm run dev</code>
<br />
Starts the development server and watches for changes
</p>
<p>
<code>npm run preview</code>
<br />
Creates production build and starts a server to preview it
</p>
<p>
<code>npm run build</code>
<br />
Creates production build
</p>
<p>
<code>npm run qwik add</code>
<br />
Runs the qwik CLI to add integrations
</p>
</>
</Infobox>
<div>
<Infobox>
<div q:slot="title" class="icon icon-apps">
Example Apps
</div>
<p>
Have a look at the <a href="/demo/flower">Flower App</a> or the{" "}
<a href="/demo/todolist">Todo App</a>.
</p>
</Infobox>
<Infobox>
<div q:slot="title" class="icon icon-community">
Community
</div>
<ul>
<li>
<span>Questions or just want to say hi? </span>
<a href="https://qwik.builder.io/chat" target="_blank">
Chat on discord!
</a>
</li>
<li>
<span>Follow </span>
<a href="https://twitter.com/QwikDev" target="_blank">
@QwikDev
</a>
<span> on Twitter</span>
</li>
<li>
<span>Open issues and contribute on </span>
<a href="https://github.com/BuilderIO/qwik" target="_blank">
GitHub
</a>
</li>
<li>
<span>Watch </span>
<a href="https://qwik.builder.io/media/" target="_blank">
Presentations, Podcasts, Videos, etc.
</a>
</li>
</ul>
</Infobox>
</div>
</div>
<SectionLinkGrid sections={data} />
</>
);
});

View File

@ -1,35 +1,25 @@
import { component$, Slot, useStyles$ } from "@builder.io/qwik";
import { routeLoader$ } from "@builder.io/qwik-city";
import type { RequestHandler } from "@builder.io/qwik-city";
import Header from "../components/starter/header/header";
import Footer from "../components/starter/footer/footer";
import Header from "../components/furniture/header";
import Navbar from "../components/furniture/nav";
import Footer from "../components/furniture/footer";
import styles from "./styles.css?inline";
// useStyles$(tailwind);
export const onGet: RequestHandler = async ({ cacheControl }) => {
// Control caching for this request for best performance and to reduce hosting costs:
// https://qwik.builder.io/docs/caching/
cacheControl({
// Always serve a cached response by default, up to a week stale
staleWhileRevalidate: 60 * 60 * 24 * 7,
// Max once every 5 seconds, revalidate on the server to get a fresh version of this page
maxAge: 5,
});
};
export const useServerTimeLoader = routeLoader$(() => {
return {
date: new Date().toISOString(),
};
});
export default component$(() => {
useStyles$(styles);
return (
<>
<Header />
<main>
{/* <Header /> */}
<Navbar />
<main class="bg-base-100">
<Slot />
</main>
<Footer />

View File

@ -1,214 +0,0 @@
/* THIS FILE IS JUST FOR EXAMPLES, DELETE IT IF YOU DON'T NEED IT */
/* SHELL ---------------------------------------- */
html {
font-family:
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
"Noto Sans",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
}
body {
background: var(--qwik-dark-background);
color: var(--qwik-dark-text);
overflow-x: hidden;
}
/* HEADINGS ------------------------------------- */
h1,
h2,
h3 {
color: white;
margin: 0;
}
h1 {
font-size: 3.2rem;
text-align: center;
}
h1 .highlight,
h3 .highlight {
color: var(--qwik-light-blue);
}
h2 {
font-weight: 400;
font-size: 2.4rem;
}
h2 .highlight {
font-weight: 700;
}
h3 {
font-size: 2rem;
}
@media screen and (min-width: 768px) {
h1 {
font-size: 5rem;
}
h2 {
font-size: 3.4rem;
}
h3 {
font-size: 3rem;
}
}
/* TAGS ----------------------------------------- */
a {
text-decoration: none;
color: var(--qwik-light-blue);
}
code {
background: rgba(230, 230, 230, 0.3);
border-radius: 4px;
padding: 2px 6px;
}
ul {
margin: 0;
padding-left: 20px;
}
/* CONTAINER ------------------------------------ */
.container {
margin: 0 auto;
padding: 30px 40px;
}
.container.container-purple {
background: var(--qwik-light-purple);
}
.container.container-dark {
background: var(--qwik-dark-background);
color: var(--qwik-dark-text);
}
.container.container-center {
text-align: center;
}
.container.container-flex {
/* does nothing on mobile */
}
.container.container-spacing-xl {
padding: 50px 40px;
}
@media screen and (min-width: 768px) {
.container {
padding: 60px 80px;
}
.container.container-spacing-xl {
padding: 100px 60px;
}
.container.container-flex {
display: flex;
justify-content: center;
gap: 60px;
}
}
/* BUTTONS -------------------------------------- */
a.button,
button {
background: var(--qwik-light-blue);
border: none;
border-radius: 8px;
color: white;
cursor: pointer;
font-size: 0.8rem;
padding: 15px 20px;
text-align: center;
}
a.button.button-dark,
button.button-dark {
background: var(--qwik-dirty-black);
}
a.button.button-small,
button.button-small {
padding: 15px 25px;
}
@media screen and (min-width: 768px) {
a.button,
button {
font-size: 1rem;
padding: 23px 35px;
}
}
/* DESIGN --------------------------------------- */
.ellipsis {
position: absolute;
top: 100px;
left: -100px;
width: 400px;
height: 400px;
background: radial-gradient(
57.58% 57.58% at 48.79% 42.42%,
rgba(24, 180, 244, 0.5) 0%,
rgba(46, 55, 114, 0) 63.22%
);
transform: rotate(5deg);
opacity: 0.5;
z-index: -1;
}
.ellipsis.ellipsis-purple {
top: 1350px;
left: -100px;
background: radial-gradient(
50% 50% at 50% 50%,
rgba(172, 127, 244, 0.5) 0%,
rgba(21, 25, 52, 0) 100%
);
transform: rotate(-5deg);
}
@media screen and (min-width: 768px) {
.ellipsis {
top: -100px;
left: 350px;
width: 1400px;
height: 800px;
}
.ellipsis.ellipsis-purple {
top: 1300px;
left: -200px;
}
}
/* used icon pack: https://www.svgrepo.com/collection/phosphor-thin-icons */
.icon:before {
width: 18px;
height: 18px;
content: "";
display: inline-block;
margin-right: 20px;
position: relative;
top: 2px;
}
.icon-cli:before {
background-image: url("data:image/svg+xml,%3Csvg fill='%23ffffff' width='20px' height='20px' viewBox='0 0 256 256' id='Flat' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M122.499 124.87646a4.00053 4.00053 0 0 1 0 6.24708l-40 32a4.0002 4.0002 0 0 1-4.998-6.24708L113.59668 128 77.501 99.12354a4.0002 4.0002 0 0 1 4.998-6.24708ZM175.99414 156h-40a4 4 0 0 0 0 8h40a4 4 0 1 0 0-8ZM228 56.48535v143.0293A12.49909 12.49909 0 0 1 215.51465 212H40.48535A12.49909 12.49909 0 0 1 28 199.51465V56.48535A12.49909 12.49909 0 0 1 40.48535 44h175.0293A12.49909 12.49909 0 0 1 228 56.48535Zm-8 0A4.49023 4.49023 0 0 0 215.51465 52H40.48535A4.49023 4.49023 0 0 0 36 56.48535v143.0293A4.49023 4.49023 0 0 0 40.48535 204h175.0293A4.49023 4.49023 0 0 0 220 199.51465Z'/%3E%3C/svg%3E");
}
.icon-apps:before {
background-image: url("data:image/svg+xml,%3Csvg fill='%23ffffff' width='20px' height='20px' viewBox='0 0 256 256' id='Flat' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M216 44.00586H40a12.01375 12.01375 0 0 0-12 12v144a12.01375 12.01375 0 0 0 12 12H216a12.01375 12.01375 0 0 0 12-12v-144A12.01375 12.01375 0 0 0 216 44.00586Zm4 156a4.00458 4.00458 0 0 1-4 4H40a4.00458 4.00458 0 0 1-4-4v-144a4.00458 4.00458 0 0 1 4-4H216a4.00458 4.00458 0 0 1 4 4Zm-144-116a8 8 0 1 1-8-8A7.99977 7.99977 0 0 1 76 84.00586Zm40 0a8 8 0 1 1-8-8A7.99977 7.99977 0 0 1 116 84.00586Z'/%3E%3C/svg%3E");
}
.icon-community:before {
background-image: url("data:image/svg+xml,%3Csvg fill='%23ffffff' width='20px' height='20px' viewBox='0 0 256 256' id='Flat' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M246.40381 143.19434a4.00061 4.00061 0 0 1-5.60108-.7959A55.57857 55.57857 0 0 0 196 120a4 4 0 0 1 0-8 28 28 0 1 0-27.50732-33.26074 4.00013 4.00013 0 0 1-7.85987-1.49219 36.00191 36.00191 0 1 1 54.06494 37.50513 63.58068 63.58068 0 0 1 32.50147 22.84155A3.99993 3.99993 0 0 1 246.40381 143.19434Zm-57.24268 71.05273a3.9998 3.9998 0 1 1-7.1914 3.50391 60.02582 60.02582 0 0 0-107.93946 0 3.9998 3.9998 0 1 1-7.1914-3.50391 67.56008 67.56008 0 0 1 40.90625-35.20581 44 44 0 1 1 40.50976 0A67.56139 67.56139 0 0 1 189.16113 214.24707ZM128 176a36 36 0 1 0-36-36A36.04061 36.04061 0 0 0 128 176ZM60 112A28 28 0 1 1 87.50732 78.73828a3.99989 3.99989 0 1 0 7.85938-1.49219A36.00177 36.00177 0 1 0 41.30225 114.7522 63.5829 63.5829 0 0 0 8.79883 137.5957a4 4 0 1 0 6.39648 4.80469A55.58072 55.58072 0 0 1 60 120a4 4 0 0 0 0-8Z'/%3E%3C/svg%3E");
}

View File

@ -0,0 +1,9 @@
@import url('https://fonts.googleapis.com/css2?family=Catamaran:wght@900&family=Poppins&display=swap');
h1, h2, h3, h4, h5, h6 {
font-family: 'Catamaran', sans-serif;
}
p, a, li, span {
font-family: 'Poppins', sans-serif;
}

View File

@ -0,0 +1,4 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

File diff suppressed because it is too large Load Diff