diff --git a/frontend/src/app/components/welcome/welcome.component.ts b/frontend/src/app/components/welcome/welcome.component.ts
index 5edf74b..b571af2 100644
--- a/frontend/src/app/components/welcome/welcome.component.ts
+++ b/frontend/src/app/components/welcome/welcome.component.ts
@@ -1,31 +1,97 @@
import { Component, ChangeDetectionStrategy, output } from '@angular/core';
+import { A11yModule } from '@angular/cdk/a11y';
@Component({
selector: 'lt-welcome',
standalone: true,
- imports: [],
+ imports: [A11yModule],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
-
✕
+
Welcome to Life Towers
-
Welcome to Life Towers
+
-
- A visual TODO with bite. Each page is a context (work, hobbies, a project).
- Each tower is a stack of related tasks. As you finish a task, it falls into the
- tower as a colored square — the more you do, the taller it grows.
+
+ Life Towers turns completed tasks into visible stacks. Create pages for work, hobbies,
+ home, or any project. Add towers for task groups, then check off tasks to build them
+ block by block.
-
- Everything you write is saved to a small remote database, keyed to a private UUID token shown
- under Settings → Account . Copy that token to recover your data on another device, or
- paste a friend's token to look at theirs.
+
+ Preview showing three towers with pending task bars at the top and completed task
+ blocks stacked below.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How Life Towers works
+
+
+
Pages
+ Keep each area separate.
+
+
+
Towers
+ Stack related tasks together.
+
+
+
Blocks
+ Finished tasks become colored blocks.
+
+
- Start fresh
- Try an example
+ Start empty
+ Load sample towers
`,
@@ -34,18 +100,49 @@ import { Component, ChangeDetectionStrategy, output } from '@angular/core';
:host { display: block; }
+ .sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0 0 0 0);
+ clip-path: inset(50%);
+ white-space: nowrap;
+ border: 0;
+ }
+
.welcome-card {
@include card();
- width: 66vw;
- max-width: 480px;
- @media (max-width: $mobile-width) { width: 300px; }
+ width: min(560px, calc(100vw - (2 * var(--large-padding))));
+ max-width: 560px;
+ max-height: calc(100svh - (2 * var(--medium-padding)));
+ overflow-y: auto;
+ @media (max-width: $mobile-width) {
+ width: min(88vw, calc(100vw - (2 * var(--medium-padding))));
+ max-width: 88vw;
+ padding: var(--medium-padding);
+ }
box-sizing: border-box;
padding: var(--large-padding);
position: relative;
box-shadow: $shadow;
text-align: left;
+ font-family: $normal-font;
+ font-weight: 300;
+ font-size: var(--medium-font-size);
+ line-height: 1.45;
@include inner-spacing(var(--medium-padding));
+ h2,
+ h3,
+ p,
+ dl,
+ dd {
+ margin: 0;
+ }
+
.exit {
position: absolute;
top: var(--medium-padding);
@@ -54,29 +151,231 @@ import { Component, ChangeDetectionStrategy, output } from '@angular/core';
}
h2 {
+ font-family: $title-font;
+ font-weight: 400;
+ font-size: var(--larger-font-size);
text-align: center;
- margin-bottom: var(--medium-padding);
+ padding: 0 36px;
+ line-height: 1.25;
+
+ @media (max-width: $mobile-width) {
+ padding: 0 28px;
+ }
}
- p.lead { color: $text-color; }
+ .lead {
+ color: $text-color;
+ font-family: inherit;
+ font-weight: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ max-width: 46ch;
+ margin-inline: auto;
+ text-align: center;
+ }
- p.muted {
- color: rgba($text-color, 0.7);
- font-size: var(--medium-font-size);
- em { font-style: italic; }
+ .tower-preview {
+ box-sizing: border-box;
+ padding: var(--medium-padding) 0;
+ border-top: 1px solid rgba($text-color, 0.08);
+ border-bottom: 1px solid rgba($text-color, 0.08);
+
+ @media (max-width: $mobile-width) {
+ padding-block: var(--small-padding);
+ }
+ }
+
+ .preview-shell {
+ width: min(100%, 370px);
+ margin: 0 auto;
+ }
+
+ .preview-page-tab {
+ width: 96px;
+ height: 14px;
+ margin: 0 auto var(--small-padding);
+ border-radius: var(--border-radius);
+ background: rgba($text-color, 0.08);
+ box-shadow: $shadow-border;
+ }
+
+ .preview-towers {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ align-items: end;
+ gap: var(--small-padding);
+ }
+
+ .preview-tower {
+ display: flex;
+ flex-direction: column;
+ min-height: 128px;
+ padding: 6px;
+ box-sizing: border-box;
+ border-radius: var(--border-radius);
+ background: rgba($text-color, 0.025);
+ box-shadow: $shadow-border;
+ }
+
+ .preview-task {
+ display: block;
+ width: 74%;
+ height: 5px;
+ margin-bottom: 4px;
+ border-radius: var(--border-radius);
+ background: var(--task-color);
+ }
+
+ .preview-task--short {
+ width: 48%;
+ }
+
+ .preview-stack {
+ display: flex;
+ flex-flow: row wrap-reverse;
+ align-content: flex-start;
+ gap: 2px;
+ min-height: 86px;
+ margin-top: auto;
+
+ span {
+ display: block;
+ flex: 0 0 calc((100% - 4px) / 3);
+ aspect-ratio: 1;
+ border-radius: 2px;
+ background: var(--block-color);
+ box-shadow: $shadow-border;
+ }
+
+ span:nth-child(2n) {
+ filter: saturate(0.9) brightness(1.06);
+ }
+
+ span:nth-child(3n) {
+ filter: saturate(1.08) brightness(0.96);
+ }
+ }
+
+ @media (prefers-reduced-motion: no-preference) {
+ @keyframes preview-task-pulse {
+ 0%, 100% {
+ opacity: 0.42;
+ transform: scaleX(0.86);
+ }
+ 45%, 70% {
+ opacity: 1;
+ transform: scaleX(1);
+ }
+ }
+
+ @keyframes preview-block-fall {
+ 0% {
+ opacity: 0;
+ transform: translateY(-220%);
+ }
+ 60%, 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+
+ .preview-task {
+ transform-origin: left center;
+ animation: preview-task-pulse 2600ms ease-in-out infinite;
+ }
+
+ .preview-stack span {
+ opacity: 0;
+ animation: preview-block-fall 1200ms cubic-bezier(0.5, 0, 1, 0) forwards;
+ animation-delay: calc(160ms + var(--fall-index, 0) * 95ms);
+ }
+
+ .preview-stack span:nth-child(1) { --fall-index: 0; }
+ .preview-stack span:nth-child(2) { --fall-index: 1; }
+ .preview-stack span:nth-child(3) { --fall-index: 2; }
+ .preview-stack span:nth-child(4) { --fall-index: 3; }
+ .preview-stack span:nth-child(5) { --fall-index: 4; }
+ .preview-stack span:nth-child(6) { --fall-index: 5; }
+ .preview-stack span:nth-child(7) { --fall-index: 6; }
+ .preview-stack span:nth-child(8) { --fall-index: 7; }
+ .preview-stack span:nth-child(9) { --fall-index: 8; }
+ }
+
+ .preview-tower--reading {
+ --block-color: hsl(18, 70%, 58%);
+ --task-color: hsla(18, 70%, 58%, 0.26);
+ }
+
+ .preview-tower--projects {
+ --block-color: hsl(209, 65%, 52%);
+ --task-color: hsla(209, 65%, 52%, 0.24);
+ }
+
+ .preview-tower--exercise {
+ --block-color: hsl(130, 45%, 44%);
+ --task-color: hsla(130, 45%, 44%, 0.22);
+ }
+
+ .basics {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: var(--small-padding);
+
+ @media (max-width: $mobile-width) {
+ grid-template-columns: 1fr;
+ }
+ }
+
+ .basic {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ min-width: 0;
+ }
+
+ .basic__label,
+ .basic__text {
+ font-family: inherit;
+ font-weight: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ }
+
+ .basic__label {
+ color: $text-color;
+ }
+
+ .basic__text {
+ color: rgba($text-color, 0.82);
}
.actions {
display: flex;
justify-content: space-around;
+ align-items: flex-end;
+ flex-wrap: wrap;
gap: var(--large-padding);
margin-top: var(--large-padding);
+ button {
+ font-family: inherit;
+ font-weight: inherit;
+ font-size: inherit;
+ margin: 0;
+ max-width: 100%;
+ line-height: inherit;
+ text-align: center;
+ }
+
button.primary {
color: $accent-color;
border-bottom-color: rgba($accent-color, 0.33);
&:after { background-color: $accent-color; }
}
+
+ @media (max-width: $mobile-width) {
+ gap: var(--small-padding);
+ }
}
}
`,