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.
+
+
- - + +
`, @@ -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); + } } } `,