diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b29e45e..e99cad8 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,7 +1,9 @@ -import { Component, WritableSignal, signal } from '@angular/core'; +import { Component, HostListener, WritableSignal, signal } from '@angular/core'; import { LangService } from './shared/data-access/lang.service'; import { ActivatedRoute } from '@angular/router'; +const SCALE_GAP = 128; + @Component({ selector: 'app-root', template: `` @@ -13,6 +15,8 @@ export class AppComponent { } ngOnInit() { + this.initScaleDifference(); + this.route.pathFromRoot[0].queryParams.subscribe(q => { const l = q['lang'] if (l && this.lang.manifests.has(l)) { @@ -21,4 +25,15 @@ export class AppComponent { } }) } + + @HostListener('window:resize') + initScaleDifference() { + const w = document.documentElement.clientWidth; + const h = document.documentElement.clientHeight; + const scalex = 1 - ((w - SCALE_GAP) / w) + const scaley = 1 - ((h - SCALE_GAP) / h) + + document.documentElement.style.setProperty('--scale-diff-x', scalex.toString()) + document.documentElement.style.setProperty('--scale-diff-y', scaley.toString()) + } } diff --git a/src/app/link-parser/data-access/link-parser-settings.service.ts b/src/app/link-parser/data-access/link-parser-settings.service.ts new file mode 100644 index 0000000..2c25371 --- /dev/null +++ b/src/app/link-parser/data-access/link-parser-settings.service.ts @@ -0,0 +1,22 @@ +import { Injectable, WritableSignal, signal } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class LinkParserSettingsService { + autoPasteLink: WritableSignal = signal(false); + + constructor() { + this.initAutoPasteLink() + } + + initAutoPasteLink() { + const n = Boolean(localStorage.getItem('autoPasteLink') == 'true') ?? false; + this.autoPasteLink.set(n); + } + + setAutoPasteLink(n: boolean) { + this.autoPasteLink.set(n); + localStorage.setItem('autoPasteLink', n.toString()) + } +} diff --git a/src/app/link-parser/link-parser.module.ts b/src/app/link-parser/link-parser.module.ts index 2031004..71006d3 100644 --- a/src/app/link-parser/link-parser.module.ts +++ b/src/app/link-parser/link-parser.module.ts @@ -6,12 +6,14 @@ import { LinkParserComponent } from './link-parser/link-parser.component'; import { SharedModule } from '../shared/shared.module'; import { FormsModule } from '@angular/forms'; import { FaqComponent } from './ui/faq/faq.component'; +import { SettingsComponent } from './ui/settings/settings.component'; @NgModule({ declarations: [ LinkParserComponent, - FaqComponent + FaqComponent, + SettingsComponent ], imports: [ CommonModule, diff --git a/src/app/link-parser/link-parser/link-parser.component.html b/src/app/link-parser/link-parser/link-parser.component.html index 3bfaaae..a21f055 100644 --- a/src/app/link-parser/link-parser/link-parser.component.html +++ b/src/app/link-parser/link-parser/link-parser.component.html @@ -1,14 +1,18 @@ -
- @defer{ } -
- -
+
+
+ @defer{ } +
+ + +
+
-
- {{lang.phrases.slogan}} +
+ {{lang.phrases.slogan}} @if (linkParams()) { - + {{lang.phrases.letsgo}} {{linkParams()?.site}} {{'/'+linkParams()?.id | truncate}} @@ -22,8 +26,10 @@ @defer{ - + + + + } @defer{ @@ -31,29 +37,18 @@ {{lang.phrases.shortTitle}}, © 2024 @for (item of social; track $index) { - - - + + + } } + + @defer{ } + - -
-
-

🙋‍♂️ {{lang.ph().faq}}

-
-
- @defer{ } -
-
-
- -
-
-
-
\ No newline at end of file + + @defer{ } + \ No newline at end of file diff --git a/src/app/link-parser/link-parser/link-parser.component.scss b/src/app/link-parser/link-parser/link-parser.component.scss index 535a5fd..9fd78a6 100644 --- a/src/app/link-parser/link-parser/link-parser.component.scss +++ b/src/app/link-parser/link-parser/link-parser.component.scss @@ -15,7 +15,6 @@ &>div { display: grid; - place-content: center; gap: 2ch; } } @@ -28,44 +27,73 @@ app-text-embracer { font-family: 'Rampart One', sans-serif; font-size: clamp(1rem, 8vw, 5rem); - @media (max-aspect-ratio: 1) or (max-width: 640px) { + -webkit-box-reflect: below 1.3rem linear-gradient(transparent 0%, #fff); + @media (max-width: 1080px) { + font-size: clamp(1rem, 7vw, 8rem); + } + + @media ((orientation: portrait) and (max-aspect-ratio: 1)) { font-size: clamp(1rem, 10vw, 8rem); } + + + + // grid-column: 2; +} + +.form-wrapper { + grid-template-columns: auto minmax(auto, 80ch) auto; +} + +form { + // grid-column: 2; + backdrop-filter: blur(var(--blur)); + z-index: 3; + border-radius: .5ch; + max-width: 80ch; + width: 100%; + position: relative; } input[type=url] { + display: block; font: inherit; - padding: 1ch; + padding: 1.5ch 2ch; background: transparent; font-family: 'Courier New', Courier, monospace; - border: 0; - outline: 1px solid; color: inherit; width: 100%; - margin: auto; - transition: all .25s cubic-bezier(0.075, 0.82, 0.165, 1); + transition: all var(--t) cubic-bezier(0.075, 0.82, 0.165, 1); border-radius: .5ch; + border: 2px solid #166496; + box-shadow: var(--shadow-elevation-medium); + background-color: #00274190; + color: #ffd60a; + // padding-right: 8ch; + + &:focus { + border-color: #166496; + outline: unset; + } } input[type=url]::placeholder { - opacity: 0.5; + color: #ffd60a; + opacity: 0.6; } +// input[type=reset] { +// position: absolute; +// right: 2px; +// top: 2px; +// height: 6ch; +// width: 6ch; +// } - -@media (prefers-reduced-motion: no-preference) { - input[type=url]:focus { - - outline-offset: .5ch; - - } -} - -// a { -// padding: 1ch; -// font-size: smaller; -// color: inherit; -// text-decoration: none; +// @media (prefers-reduced-motion: no-preference) { +// input[type=url]:focus { +// outline-offset: .5ch; +// } // } .site-name { @@ -79,7 +107,7 @@ input[type=url]::placeholder { .link { outline: 1px solid; border-radius: .5ch; - transition: all cubic-bezier(0.075, 0.82, 0.165, 1) .25s; + transition: all cubic-bezier(0.075, 0.82, 0.165, 1) var(--t); &:hover { outline-offset: .5ch; @@ -111,94 +139,17 @@ app-overlay { } } -made-in-ukraine { +made-in-ukraine, app-lang-toggle { margin-left: auto; - } - -dialog { - --r: 1ch; - --b: 2px; - padding: 0; - max-width: min(calc(100vw - 4ch), 60ch); - max-height: calc(100dvh - 4ch); - border-radius: var(--r); - border: var(--b) solid #166496; - background-color: rgb(0, 15, 30); - color: inherit; - - &::before { - content: 'esc'; - position: fixed; - left: 2ch; - top: 2ch; - display: inline-grid; - place-content: center; - background-color: rgba(0, 39, 65, 1); - border: 1px solid #166496; - height: 3.5ch; - padding: 1ch .75ch; - font-size: smaller; - border-radius: .5ch; - margin: 0 .25ch; - font-family: monospace; - font-weight: bold; - line-height: 1ch; - box-shadow: 0 .35ch #166496; - color: #ffd60a; - opacity: .5; - } - - &::backdrop { - backdrop-filter: blur(.5ch) brightness(.32); - } - - .dialog-wrapper { - display: grid; - max-height: 100%; - grid-template: auto 1fr auto / 1fr; - } - - header, - footer { - background-color: rgb(0, 39, 65); - padding: 1ch 2ch; - } - - header { - border-top-left-radius: calc(var(--r) - var(--b)); - border-top-right-radius: calc(var(--r) - var(--b)); - - h2, - h4 { - margin: 0; - } - } - - footer { - border-bottom-left-radius: calc(var(--r) - var(--b)); - border-bottom-right-radius: calc(var(--r) - var(--b)); - } - - section { - padding: 2ch; - overflow: auto; - } - - &[open] { - display: flex; - } -} - - :host { - transition: scale .1s ease-in-out; + transition: all var(--t) ease-in-out; } :host:has(dialog[open]) { - scale: .99; - overflow: hidden; + transform: scale(calc(1 - var(--scale-diff-x, .1)), calc(1 - var(--scale-diff-y, .1))); + filter: blur(var(--blur)); } :host:has(input[type=url]:focus) { diff --git a/src/app/link-parser/link-parser/link-parser.component.ts b/src/app/link-parser/link-parser/link-parser.component.ts index 843d500..6049bd4 100644 --- a/src/app/link-parser/link-parser/link-parser.component.ts +++ b/src/app/link-parser/link-parser/link-parser.component.ts @@ -1,16 +1,14 @@ -import { Component, ElementRef, HostListener, Signal, ViewChild, WritableSignal, computed, effect, inject, signal } from '@angular/core'; +import { Component, HostListener, Signal, ViewChild, WritableSignal, computed, effect, inject, signal } from '@angular/core'; import { LinkParserService } from '../data-access/link-parser.service'; import { ImgurLinkParser, JsonLinkParser, MangadexLinkParser, RedditLinkParser, TelegraphLinkParser } from '../utils'; import { ActivatedRoute, Router } from '@angular/router'; import { LangService } from '../../shared/data-access/lang.service'; -import { DomManipulationService, ViewModeOption } from '../../shared/data-access'; import { Base64 } from '../../shared/utils'; import { Title } from '@angular/platform-browser'; +import { DialogComponent } from '../../shared/ui/dialog/dialog.component'; +import { LinkParserSettingsService } from '../data-access/link-parser-settings.service'; + -const LANG_OPTIONS: ViewModeOption[] = [ - { dir: "rtl", mode: "pages", hintPhraceKey: "english", code: "en", emoji: "🇬🇧" }, - { dir: "ltr", mode: "pages", hintPhraceKey: "ukrainian", code: "uk", emoji: "🇺🇦" } -] @Component({ selector: 'app-link-parser', templateUrl: './link-parser.component.html', @@ -23,7 +21,7 @@ export class LinkParserComponent { private title: Title = inject(Title); private router: Router = inject(Router); private route: ActivatedRoute = inject(ActivatedRoute); - private dm: DomManipulationService = inject(DomManipulationService) + setts = inject(LinkParserSettingsService) link: WritableSignal = signal(''); linkParams: Signal = computed(() => this.parser.parse(this.link())); @@ -35,10 +33,6 @@ export class LinkParserComponent { }; }); - langOpt = LANG_OPTIONS - - optLangValue = () => this.langOpt.filter((opt: any) => opt.code == this.lang.lang())[0] - constructor(public parser: LinkParserService, public lang: LangService) { this.initParser(); @@ -63,6 +57,7 @@ export class LinkParserComponent { ngOnInit() { this.initUrl() + this.initHotKeys() } async initFromclipboard() { @@ -86,7 +81,7 @@ export class LinkParserComponent { if (url) { this.link.set(url ?? '') } else { - this.initFromclipboard(); + if(this.setts.autoPasteLink()) this.initFromclipboard(); } } @@ -98,37 +93,26 @@ export class LinkParserComponent { this.router.navigateByUrl(link); } - @ViewChild('dialog', { static: true }) dialogRef!: ElementRef; - dialogElement: WritableSignal = signal(document.createElement('dialog')); - - - ngAfterViewInit() { - this.dialogElement.set(this.dialogRef.nativeElement); - } - - async showHelp() { - this.dialogElement().showModal() - // const mutate = () => { - // this.dialogElement().showModal() - // } + @ViewChild('faqDialog') faqDialogComponent!: DialogComponent; + showHelp = () => this.faqDialogComponent.showDialog(); - // this.dm.startViewTransition(mutate) - } + @ViewChild('settingsDialog') settingsDialogComponent!: DialogComponent; + showSettings = () => this.settingsDialogComponent.showDialog(); - async closeDialog(event: Event) { - if (event.target instanceof HTMLDialogElement) { - const mutate = () => (event.target as HTMLDialogElement).close(); - - this.dm.startViewTransition(mutate) - } + hotKeys = new Map() + initHotKeys() { + this.hotKeys.set('F1', this.showHelp) + this.hotKeys.set('F2', this.showSettings) } @HostListener('window:keydown', ["$event"]) helpHotKey(event: KeyboardEvent) { - if (event.key === 'F1') { + + if (this.hotKeys.has(event.key)) { event.preventDefault() - this.showHelp() + const f: Function = this.hotKeys.get(event.key) as Function; + f(); } } diff --git a/src/app/link-parser/ui/faq/faq.component.scss b/src/app/link-parser/ui/faq/faq.component.scss index 372eb68..24b1578 100644 --- a/src/app/link-parser/ui/faq/faq.component.scss +++ b/src/app/link-parser/ui/faq/faq.component.scss @@ -1,14 +1,18 @@ +:host { + display: grid; + gap: 2ch; +} + details { padding: 1.5ch 2ch; border-radius: .5ch; border: 1px solid rgb(0, 40, 64); - margin-bottom: 2ch; - transition: all .25s ease-in-out; + transition: all var(--t) ease-in-out; summary { cursor: pointer; font-weight: bold; - transition: color .25s cubic-bezier(0.45, 0.05, 0.55, 0.95); + transition: color var(--t) cubic-bezier(0.45, 0.05, 0.55, 0.95); } &[open] { diff --git a/src/app/link-parser/ui/settings/settings.component.html b/src/app/link-parser/ui/settings/settings.component.html new file mode 100644 index 0000000..e52651a --- /dev/null +++ b/src/app/link-parser/ui/settings/settings.component.html @@ -0,0 +1,28 @@ +
+
+ +

+ 🈚 {{lang.ph().language}} + {{lang.ph().getByKey(getLangValue(lang.lang()).hintPhraceKey)}} +

+

{{lang.ph().settingLangDesc}}

+
+
+
+ +
+
+

+ + @if(setts.autoPasteLink()) { + ON + } @else { + OFF + } +

+

+ {{lang.ph().settingAutoPasteLinkDesc}} +

+
+ +
\ No newline at end of file diff --git a/src/app/link-parser/ui/settings/settings.component.scss b/src/app/link-parser/ui/settings/settings.component.scss new file mode 100644 index 0000000..18c2981 --- /dev/null +++ b/src/app/link-parser/ui/settings/settings.component.scss @@ -0,0 +1,78 @@ +:host { + display: grid; + gap: 2ch; +} + +section { + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + gap: 2ch; + border-bottom: 2px solid #16649680; + padding: 0 0 1.5ch; + + transition: all var(--t) ease-in-out; + + &.inactive { + opacity: .64; + filter: grayscale(.8); + } + + div { + + p { + margin: 0; + opacity: .8; + } + + .label { + font-weight: bold; + cursor: pointer; + } + } + + input[type=checkbox] { + --q:1; + width: calc(5ch - 2px); + aspect-ratio: 1; + border-radius: .5ch; + appearance: none; + border: 2px solid #16649680; +overflow: hidden; + position: relative; + transition: all var(--t) ease-in-out; + cursor: pointer; + padding: calc(var(--q) * 1ch); + + &::before, + &::after { + position: absolute; + inset: 0; + display: grid; + place-content: center; + transition: all var(--t) ease-in-out; + } + + &::before { + content: '❌'; + transform: translate(0, 0%) scale(1); + } + + &::after { + content: '✔️'; + transform: translate(0, -100%) scale(0); + } + + &:checked { + border-color: #166496; + &::before { + transform: translate(0, 100%) scale(0); + } + + &::after { + transform: translate(0, 0%) scale(1); + filter: brightness(0) saturate(100%) invert(86%) sepia(42%) saturate(2655%) hue-rotate(350deg) brightness(106%) contrast(102%); + } + } + } +} \ No newline at end of file diff --git a/src/app/link-parser/ui/settings/settings.component.ts b/src/app/link-parser/ui/settings/settings.component.ts new file mode 100644 index 0000000..8e0dba3 --- /dev/null +++ b/src/app/link-parser/ui/settings/settings.component.ts @@ -0,0 +1,23 @@ +import { Component, WritableSignal, effect, inject, signal } from '@angular/core'; +import { LinkParserSettingsService } from '../../data-access/link-parser-settings.service'; +import { LangService } from '../../../shared/data-access/lang.service'; + +@Component({ + selector: 'app-settings', + templateUrl: './settings.component.html', + styleUrl: './settings.component.scss' +}) +export class SettingsComponent { + setts = inject(LinkParserSettingsService) + lang = inject(LangService) + + + getLangValue(lang: string) { + return this.lang.langOpt.filter((opt: any) => opt.code == lang)[0] + } + + setAutoPasteLink(e: Event) { + this.setts.setAutoPasteLink((e.target as HTMLInputElement).checked) + } + +} diff --git a/src/app/shared/data-access/lang.service.ts b/src/app/shared/data-access/lang.service.ts index 9690133..3977f74 100644 --- a/src/app/shared/data-access/lang.service.ts +++ b/src/app/shared/data-access/lang.service.ts @@ -1,9 +1,13 @@ -import { Injectable, WritableSignal, signal } from '@angular/core'; +import { ChangeDetectorRef, Injectable, WritableSignal, inject, signal } from '@angular/core'; import { Phrases } from '../utils/phrases'; import { Observable, map, of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; +import { ViewModeOption } from './viewer.service'; -type Langs = { en: string, uk: string } +const LANG_OPTIONS: ViewModeOption[] = [ + { dir: "rtl", mode: "pages", hintPhraceKey: "english", code: "en", emoji: "🇬🇧" }, + { dir: "ltr", mode: "pages", hintPhraceKey: "ukrainian", code: "uk", emoji: "🇺🇦" } +] const DEFAULT_LANG = 'en' const LANG_STORAGE_NAME = 'lang' @@ -18,6 +22,8 @@ export class LangService { ['uk', "manifest-uk.webmanifest"] ]); + langOpt = LANG_OPTIONS + lang: WritableSignal = signal(localStorage.getItem(LANG_STORAGE_NAME) ?? DEFAULT_LANG); linkManifestElement: WritableSignal = signal(document.querySelector('link[rel="manifest"]')) @@ -33,7 +39,7 @@ export class LangService { this.lang.set(lang) document.documentElement.lang = lang localStorage.setItem(LANG_STORAGE_NAME, lang) - this.updateTranslate() + this.updateTranslate(); } updateManifest() { diff --git a/src/app/shared/data-access/viewer.service.ts b/src/app/shared/data-access/viewer.service.ts index e4393c7..d6f508b 100644 --- a/src/app/shared/data-access/viewer.service.ts +++ b/src/app/shared/data-access/viewer.service.ts @@ -9,9 +9,9 @@ export interface ViewModeOption { } export const VIEV_MODE_OPTIONS: ViewModeOption[] = [ - { dir: "rtl", mode: "pages", hintPhraceKey: "scrollLeft", code: "", emoji: "⬅️" }, - { dir: "ltr", mode: "pages", hintPhraceKey: "scrollRight", code: "", emoji: "➡️" }, - { dir: "ltr", mode: "long", hintPhraceKey: "scrollDown", code: "", emoji: "⬇️" }, + { dir: "rtl", mode: "pages", hintPhraceKey: "scrollLeft", code: "1", emoji: "⬅️" }, + { dir: "ltr", mode: "pages", hintPhraceKey: "scrollRight", code: "2", emoji: "➡️" }, + { dir: "ltr", mode: "long", hintPhraceKey: "scrollDown", code: "3", emoji: "⬇️" }, ] @Injectable({ diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 1ec78ec..3356595 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -12,6 +12,8 @@ import { WarmControlComponent } from './ui/warm-control/warm-control.component'; import { PagesIndicatorComponent } from './ui/pages-indicator/pages-indicator.component'; import { NsfwWarningComponent } from './ui/nsfw-warning/nsfw-warning.component'; import { MadeInUkraineComponent } from './ui/made-in-ukraine/made-in-ukraine.component'; +import { DialogComponent } from './ui/dialog/dialog.component'; +import { LangToggleComponent } from './ui/lang-toggle/lang-toggle.component'; @@ -26,13 +28,15 @@ import { MadeInUkraineComponent } from './ui/made-in-ukraine/made-in-ukraine.com WarmControlComponent, PagesIndicatorComponent, NsfwWarningComponent, - MadeInUkraineComponent + MadeInUkraineComponent, + DialogComponent, + LangToggleComponent ], imports: [ CommonModule, FormsModule, RouterModule ], - exports: [TruncatePipe, TextEmbracerComponent, ViewerComponent, OverlayComponent, ViewModeBarComponent, MadeInUkraineComponent] + exports: [TruncatePipe, TextEmbracerComponent, ViewerComponent, OverlayComponent, ViewModeBarComponent, MadeInUkraineComponent, DialogComponent, LangToggleComponent] }) export class SharedModule { } diff --git a/src/app/shared/ui/dialog/dialog.component.html b/src/app/shared/ui/dialog/dialog.component.html new file mode 100644 index 0000000..b6ffa13 --- /dev/null +++ b/src/app/shared/ui/dialog/dialog.component.html @@ -0,0 +1,26 @@ + +
+
+

{{title}}

+ @if (closeHeaderButton) { +
+ +
+ } +
+
+ +
+ @if(footer){ +
+
+ +
+
+ } +
+
\ No newline at end of file diff --git a/src/app/shared/ui/dialog/dialog.component.scss b/src/app/shared/ui/dialog/dialog.component.scss new file mode 100644 index 0000000..5bf64b1 --- /dev/null +++ b/src/app/shared/ui/dialog/dialog.component.scss @@ -0,0 +1,69 @@ + +:host { + position: absolute; +} + +dialog { + --r: 1ch; + --b: 2px; + padding: 0; + max-width: min(calc(100vw - 4ch), 60ch); + max-height: calc(100dvh - 8ch); + border-radius: var(--r); + border: var(--b) solid #166496; + background-color: rgb(0, 15, 30); + color: inherit; + margin-top: 4ch; + + &::backdrop { + backdrop-filter: brightness(.8); + } + + .dialog-wrapper { + display: grid; + max-height: 100%; + grid-template: auto 1fr auto / 1fr; + } + + header, + footer { + background-color: rgb(0, 39, 65); + padding: 2ch; + border: 0 solid #166496; + } + + header { + border-top-left-radius: calc(var(--r) - var(--b)); + border-top-right-radius: calc(var(--r) - var(--b)); + border-bottom-width: 2px; + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + + h2, + h4 { + margin: 0; + } + + &:has(form) { + padding: .5ch 2ch; + gap: 1ch; + } + } + + footer { + border-top-width: 2px; + border-bottom-left-radius: calc(var(--r) - var(--b)); + border-bottom-right-radius: calc(var(--r) - var(--b)); + } + + section { + padding: 2ch; + overflow: auto; + } + + &[open] { + display: flex; + box-shadow: var(--shadow-elevation-high); + } +} diff --git a/src/app/shared/ui/dialog/dialog.component.ts b/src/app/shared/ui/dialog/dialog.component.ts new file mode 100644 index 0000000..33c8846 --- /dev/null +++ b/src/app/shared/ui/dialog/dialog.component.ts @@ -0,0 +1,33 @@ +import { Component, ElementRef, Input, ViewChild, WritableSignal, inject, signal } from '@angular/core'; +import { LangService } from '../../data-access/lang.service'; + +@Component({ + selector: 'app-dialog', + templateUrl: './dialog.component.html', + styleUrl: './dialog.component.scss' +}) +export class DialogComponent { + lang = inject(LangService) + + @Input() title: string = 'Dialog Title' + @Input() footer: boolean = false + @Input() closeHeaderButton: boolean = true + + @ViewChild('dialog', { static: true }) dialogRef!: ElementRef; + dialogElement: WritableSignal = signal(document.createElement('dialog')); + + + ngAfterViewInit() { + this.dialogElement.set(this.dialogRef.nativeElement); + } + + closeDialog(event: Event) { + if (event.target instanceof HTMLDialogElement) { + (event.target as HTMLDialogElement).close(); + } + } + + showDialog() { + this.dialogElement().showModal() + } +} diff --git a/src/app/shared/ui/lang-toggle/lang-toggle.component.html b/src/app/shared/ui/lang-toggle/lang-toggle.component.html new file mode 100644 index 0000000..c996a76 --- /dev/null +++ b/src/app/shared/ui/lang-toggle/lang-toggle.component.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/app/shared/ui/lang-toggle/lang-toggle.component.scss b/src/app/shared/ui/lang-toggle/lang-toggle.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/ui/lang-toggle/lang-toggle.component.ts b/src/app/shared/ui/lang-toggle/lang-toggle.component.ts new file mode 100644 index 0000000..6bb4a2b --- /dev/null +++ b/src/app/shared/ui/lang-toggle/lang-toggle.component.ts @@ -0,0 +1,18 @@ +import { Component, WritableSignal, inject, signal } from '@angular/core'; +import { LangService } from '../../data-access/lang.service'; +import { ViewModeOption } from '../../data-access'; + + + +@Component({ + selector: 'app-lang-toggle', + templateUrl: './lang-toggle.component.html', + styleUrl: './lang-toggle.component.scss' +}) +export class LangToggleComponent { + lang = inject(LangService) + + seed: WritableSignal = signal((Math.ceil(Math.random() * 1000)).toString()) + + optLangValue = () => this.lang.langOpt.filter((opt: any) => opt.code == this.lang.lang())[0] +} diff --git a/src/app/shared/ui/made-in-ukraine/made-in-ukraine.component.ts b/src/app/shared/ui/made-in-ukraine/made-in-ukraine.component.ts index cc63712..bcdf4c9 100644 --- a/src/app/shared/ui/made-in-ukraine/made-in-ukraine.component.ts +++ b/src/app/shared/ui/made-in-ukraine/made-in-ukraine.component.ts @@ -2,7 +2,7 @@ import { Component } from '@angular/core'; @Component({ selector: 'made-in-ukraine', - template: `Made in + template: `Made©in Ukraine`, styles: [` :host { @@ -12,7 +12,7 @@ import { Component } from '@angular/core'; display: grid; text-transform: uppercase; opacity: .4; - transition: opacity .25s ease-in-out; + transition: opacity var(--t) ease-in-out; &:hover { opacity: 1; @@ -24,3 +24,4 @@ import { Component } from '@angular/core'; export class MadeInUkraineComponent { } +// ♆ \ No newline at end of file diff --git a/src/app/shared/ui/overlay/overlay.component.scss b/src/app/shared/ui/overlay/overlay.component.scss index f6ae92b..2e02392 100644 --- a/src/app/shared/ui/overlay/overlay.component.scss +++ b/src/app/shared/ui/overlay/overlay.component.scss @@ -12,7 +12,7 @@ z-index: 1; padding: 1ch 1rem; pointer-events: none; - transition: opacity .1s cubic-bezier(.4, 0, 1, 1); + transition: opacity var(--t) cubic-bezier(.4, 0, 1, 1); font-size: 14px; line-height: 1; diff --git a/src/app/shared/ui/pages-indicator/pages-indicator.component.scss b/src/app/shared/ui/pages-indicator/pages-indicator.component.scss index 8bf03b3..b3e1409 100644 --- a/src/app/shared/ui/pages-indicator/pages-indicator.component.scss +++ b/src/app/shared/ui/pages-indicator/pages-indicator.component.scss @@ -15,7 +15,7 @@ div { cursor: pointer; font-weight: bold; line-height: 1; - transition: all .1s ease-in-out; + transition: all var(--t) ease-in-out; &:hover, &.active { color: #ffd60a; diff --git a/src/app/shared/ui/text-embracer/text-embracer.component.scss b/src/app/shared/ui/text-embracer/text-embracer.component.scss index ec51f2d..fbc3216 100644 --- a/src/app/shared/ui/text-embracer/text-embracer.component.scss +++ b/src/app/shared/ui/text-embracer/text-embracer.component.scss @@ -9,7 +9,6 @@ line-height: 1; display: flex; gap: .1ch; - & > span { aspect-ratio: 2/3; @@ -17,8 +16,6 @@ border: var(--border-width) var(--border-style) var(--border-color); border-radius: .1ch; height: 1.3lh; - place-content: center; - } } \ No newline at end of file diff --git a/src/app/shared/ui/view-mode-bar/view-mode-bar.component.html b/src/app/shared/ui/view-mode-bar/view-mode-bar.component.html index c9fadff..6247ab9 100644 --- a/src/app/shared/ui/view-mode-bar/view-mode-bar.component.html +++ b/src/app/shared/ui/view-mode-bar/view-mode-bar.component.html @@ -1,11 +1,12 @@ @for(option of options; track option.emoji) {
- + -
-} \ No newline at end of file +} + diff --git a/src/app/shared/ui/view-mode-bar/view-mode-bar.component.ts b/src/app/shared/ui/view-mode-bar/view-mode-bar.component.ts index eb8f25b..95016c0 100644 --- a/src/app/shared/ui/view-mode-bar/view-mode-bar.component.ts +++ b/src/app/shared/ui/view-mode-bar/view-mode-bar.component.ts @@ -9,6 +9,7 @@ import { LangService } from '../../data-access/lang.service'; export class ViewModeBarComponent { @Input() options: any; @Input() value: any; + @Input() seed: string = 'seed'; @Output() valueChange = new EventEmitter(); diff --git a/src/app/shared/ui/viewer/viewer.component.html b/src/app/shared/ui/viewer/viewer.component.html index b886135..14fd2fd 100644 --- a/src/app/shared/ui/viewer/viewer.component.html +++ b/src/app/shared/ui/viewer/viewer.component.html @@ -62,7 +62,7 @@

🏠 {{separator}}{{episode?.title}} + [value]="viewer.viewModeOption.code" /> \ No newline at end of file diff --git a/src/app/shared/ui/viewer/viewer.component.scss b/src/app/shared/ui/viewer/viewer.component.scss index 3560ce7..f4625a1 100644 --- a/src/app/shared/ui/viewer/viewer.component.scss +++ b/src/app/shared/ui/viewer/viewer.component.scss @@ -40,7 +40,7 @@ figure { &.nsfw { filter: blur(4rem) brightness(.1); - transition: all .25s ease-in-out; + transition: all var(--t) ease-in-out; } &.nsfw.show { filter: blur(0) brightness(1); diff --git a/src/app/shared/ui/warm-control/warm-control.component.scss b/src/app/shared/ui/warm-control/warm-control.component.scss index 2f94ced..8d3ce3f 100644 --- a/src/app/shared/ui/warm-control/warm-control.component.scss +++ b/src/app/shared/ui/warm-control/warm-control.component.scss @@ -14,7 +14,7 @@ input[type=range][orient=vertical] { width: 8px; height: 0; padding: 0 5px; - transition: all .25s ease-in-out; + transition: all var(--t) ease-in-out; opacity: 0; pointer-events: none; diff --git a/src/app/shared/utils/phrases.ts b/src/app/shared/utils/phrases.ts index 6a4148a..0bb32c8 100644 --- a/src/app/shared/utils/phrases.ts +++ b/src/app/shared/utils/phrases.ts @@ -32,6 +32,11 @@ export class Phrases { yesYouCanPasteJsonLink = "Yes, you can, for example, to {value} or to your website." howToUseChytankaAnswer = "🔗 Just paste the link to the episode into the input field.
🔳 If the link is supported, a button will appear,
🖱️ click it,
📖 and read easily and comfortably! 🛋️" whereIdIs = ", where {id} is the unique identifier of the post." + language = "Language" + settingLangDesc = "Change the user interface language." + settings = "Settings" + autoPasteLink = "Auto Paste Link" + settingAutoPasteLinkDesc = "Automatically inserts a link from the clipboard into the input field." getByKey = (key: string) => (Object.keys(this).includes(key)) ? this[key as keyof Phrases] : null; diff --git a/src/assets/langs/uk.json b/src/assets/langs/uk.json index b91038a..0abcfb0 100644 --- a/src/assets/langs/uk.json +++ b/src/assets/langs/uk.json @@ -29,5 +29,10 @@ "whatJsonModel": "Якою має бути модель JSON файлу?", "yesYouCanPasteJsonLink": "Так, можна, наприклад, на {value} або на власний сайт.", "howToUseChytankaAnswer" : "🔗 Просто встав посилання на епізод у поле введення.
🔳Якщо посилання підтримується, з'явиться кнопка,
🖱️ натисни її,
📖 і читай легко та зручно 🛋️.", - "whereIdIs": ", де {id} — унікальний ідентифікатор допису." + "whereIdIs": ", де {id} — унікальний ідентифікатор допису.", + "language": "Мова", + "settingLangDesc": "Змінити мову інтерфейсу користувача.", + "settings": "Налаштування", + "autoPasteLink": "Автоматична вставка посилання", + "settingAutoPasteLinkDesc": "Автоматично вставляти посилання з буфера обміну в поле для введення." } \ No newline at end of file diff --git a/src/styles.scss b/src/styles.scss index 7c2a540..e8cc937 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,6 +1,8 @@ @import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@1,500&family=Rampart+One&display=swap'); :root { + --blur: 1ch; + --t: 266.666667ms; //calc(1s / 60 * 16); --surface: hsl(203.44 8% 16%); --text: hsl(200 5% 80%); color-scheme: light dark; @@ -38,10 +40,11 @@ body { border-radius: .5ch; cursor: pointer; background: hsl(203.44 8% 16%); - transition: all .25s; + transition: all var(--t); text-wrap: balance; -webkit-tap-highlight-color: transparent; - box-shadow: 0 .1ch .2ch #0004; + // box-shadow: 0 .1ch .2ch #0004; + box-shadow: var(--shadow-elevation-low); &.small { --q: 0.5 @@ -72,21 +75,31 @@ body { &:hover { background-color: #166496; color: #ffd60a; + box-shadow: var(--shadow-elevation-medium); } &:active { background-color: rgba(0, 15, 30, 1); + box-shadow: unset; } &.full { width: 100%; } + + &:disabled { + cursor: not-allowed; + // pointer-events: none; + filter: grayscale(.64); + box-shadow: unset; + background-color: rgba(0, 15, 30, .5); + } } a { color: #4081b1; text-decoration: none; - transition: all .25s ease-in-out; + transition: all var(--t) ease-in-out; &:hover { color: #ffd60a; @@ -98,4 +111,58 @@ a[target="_blank"]:after { font-size: 0.8em; opacity: .8; vertical-align: super; +} + +:root { + // https://www.joshwcomeau.com/shadow-palette/ + --shadow-color: 200deg 12% 5%; + --shadow-elevation-low: + 0px 0.4px 0.4px hsl(var(--shadow-color) / 0.72), + 0px 0.6px 0.5px -2px hsl(var(--shadow-color) / 0.54), + 0px 1.8px 1.6px -4px hsl(var(--shadow-color) / 0.36); + --shadow-elevation-medium: + 0px 0.4px 0.4px hsl(var(--shadow-color) / 0.76), + 0px 1px 0.9px -1.3px hsl(var(--shadow-color) / 0.61), + -0.1px 3.2px 2.9px -2.7px hsl(var(--shadow-color) / 0.47), + -0.2px 9px 8.1px -4px hsl(var(--shadow-color) / 0.33); + --shadow-elevation-high: + 0px 0.4px 0.4px hsl(var(--shadow-color) / 0.62), + 0px 1.3px 1.2px -0.5px hsl(var(--shadow-color) / 0.57), + -0.1px 2.6px 2.3px -1px hsl(var(--shadow-color) / 0.52), + -0.1px 4.8px 4.3px -1.5px hsl(var(--shadow-color) / 0.47), + -0.2px 8.6px 7.7px -2px hsl(var(--shadow-color) / 0.42), + -0.4px 14.5px 13.1px -2.5px hsl(var(--shadow-color) / 0.36), + -0.6px 23.2px 20.9px -3px hsl(var(--shadow-color) / 0.31), + -0.9px 35.1px 31.6px -3.5px hsl(var(--shadow-color) / 0.26), + -1.4px 51px 45.9px -4px hsl(var(--shadow-color) / 0.21); + } + +@supports (transition-behavior: allow-discrete) { + + dialog, + dialog::backdrop { + transition-property: opacity, transform, overlay, display, filter; + transition-duration: var(--t); + transition-behavior: allow-discrete; + opacity: 0; + transform: scale(calc(1 + var(--scale-diff-x, .1)), calc(1 + var(--scale-diff-y, .1))); + filter: blur(var(--blur)); + } + + dialog[open], + dialog[open]::backdrop { + opacity: 1; + transform: scale(1); + filter: blur(0); + } + + @starting-style { + + dialog[open], + dialog[open]::backdrop { + opacity: 0; + transform: scale(calc(1 + var(--scale-diff-x, .1)), calc(1 + var(--scale-diff-y, .1))); + filter: blur(var(--blur)); + } + } } \ No newline at end of file